Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,23 @@ Command results output JSON. (`--help` and `--version` output plain text.)
}
```

### Summary Field

List and show commands include a `summary` field with a human-readable description of the response. This is useful for quick feedback in scripts or when piping output.

```bash
fizzy board list | jq -r '.summary'
# "5 boards"

fizzy card list --board ABC --all | jq -r '.summary'
# "42 cards (all)"

fizzy search "bug" | jq -r '.summary'
# "7 results for \"bug\""
```

The summary adapts to pagination flags (`--page N` or `--all`) and includes contextual details like unread counts for notifications.

When creating resources, the CLI automatically follows the `Location` header to fetch the complete resource data:

```json
Expand Down
26 changes: 24 additions & 2 deletions internal/commands/board.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package commands

import (
"fmt"

"github.com/robzolkos/fizzy-cli/internal/errors"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -35,8 +37,20 @@ var boardListCmd = &cobra.Command{
exitWithError(err)
}

// Build summary
count := 0
if arr, ok := resp.Data.([]interface{}); ok {
count = len(arr)
}
summary := fmt.Sprintf("%d boards", count)
if boardListAll {
summary += " (all)"
} else if boardListPage > 0 {
summary += fmt.Sprintf(" (page %d)", boardListPage)
}

hasNext := resp.LinkNext != ""
printSuccessWithPagination(resp.Data, hasNext, resp.LinkNext)
printSuccessWithPaginationAndSummary(resp.Data, hasNext, resp.LinkNext, summary)
},
}

Expand All @@ -56,7 +70,15 @@ var boardShowCmd = &cobra.Command{
exitWithError(err)
}

printSuccess(resp.Data)
// Build summary
summary := "Board"
if board, ok := resp.Data.(map[string]interface{}); ok {
if name, ok := board["name"].(string); ok {
summary = fmt.Sprintf("Board: %s", name)
}
}

printSuccessWithSummary(resp.Data, summary)
},
}

Expand Down
25 changes: 23 additions & 2 deletions internal/commands/card.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"fmt"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -173,8 +174,20 @@ var cardListCmd = &cobra.Command{
resp.Data = filtered
}

// Build summary
count := 0
if arr, ok := resp.Data.([]interface{}); ok {
count = len(arr)
}
summary := fmt.Sprintf("%d cards", count)
if cardListAll {
summary += " (all)"
} else if cardListPage > 0 {
summary += fmt.Sprintf(" (page %d)", cardListPage)
}

hasNext := resp.LinkNext != ""
printSuccessWithPagination(resp.Data, hasNext, resp.LinkNext)
printSuccessWithPaginationAndSummary(resp.Data, hasNext, resp.LinkNext, summary)
},
}

Expand All @@ -194,7 +207,15 @@ var cardShowCmd = &cobra.Command{
exitWithError(err)
}

printSuccess(resp.Data)
// Build summary
summary := fmt.Sprintf("Card #%s", args[0])
if card, ok := resp.Data.(map[string]interface{}); ok {
if title, ok := card["title"].(string); ok {
summary = fmt.Sprintf("Card #%s: %s", args[0], title)
}
}

printSuccessWithSummary(resp.Data, summary)
},
}

Expand Down
7 changes: 6 additions & 1 deletion internal/commands/column.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package commands

import (
"fmt"

"github.com/robzolkos/fizzy-cli/internal/errors"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -45,7 +47,10 @@ var columnListCmd = &cobra.Command{
cols = append(cols, data...)
cols = append(cols, pseudoColumnObject(pseudoColumnDone))

printSuccess(cols)
// Build summary
summary := fmt.Sprintf("%d columns", len(cols))

printSuccessWithSummary(cols, summary)
},
}

Expand Down
15 changes: 14 additions & 1 deletion internal/commands/comment.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"fmt"
"os"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -41,8 +42,20 @@ var commentListCmd = &cobra.Command{
exitWithError(err)
}

// Build summary
count := 0
if arr, ok := resp.Data.([]interface{}); ok {
count = len(arr)
}
summary := fmt.Sprintf("%d comments on card #%s", count, commentListCard)
if commentListAll {
summary += " (all)"
} else if commentListPage > 0 {
summary += fmt.Sprintf(" (page %d)", commentListPage)
}

hasNext := resp.LinkNext != ""
printSuccessWithPagination(resp.Data, hasNext, resp.LinkNext)
printSuccessWithPaginationAndSummary(resp.Data, hasNext, resp.LinkNext, summary)
},
}

Expand Down
23 changes: 22 additions & 1 deletion internal/commands/notification.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"fmt"
"strconv"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -36,8 +37,28 @@ var notificationListCmd = &cobra.Command{
exitWithError(err)
}

// Build summary with unread count
count := 0
unreadCount := 0
if arr, ok := resp.Data.([]interface{}); ok {
count = len(arr)
for _, item := range arr {
if notif, ok := item.(map[string]interface{}); ok {
if read, ok := notif["read"].(bool); ok && !read {
unreadCount++
}
}
}
}
summary := fmt.Sprintf("%d notifications (%d unread)", count, unreadCount)
if notificationListAll {
summary = fmt.Sprintf("%d notifications (%d unread, all)", count, unreadCount)
} else if notificationListPage > 0 {
summary = fmt.Sprintf("%d notifications (%d unread, page %d)", count, unreadCount, notificationListPage)
}

hasNext := resp.LinkNext != ""
printSuccessWithPagination(resp.Data, hasNext, resp.LinkNext)
printSuccessWithPaginationAndSummary(resp.Data, hasNext, resp.LinkNext, summary)
},
}

Expand Down
11 changes: 10 additions & 1 deletion internal/commands/reaction.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package commands

import (
"fmt"

"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -36,7 +38,14 @@ var reactionListCmd = &cobra.Command{
exitWithError(err)
}

printSuccess(resp.Data)
// Build summary
count := 0
if arr, ok := resp.Data.([]interface{}); ok {
count = len(arr)
}
summary := fmt.Sprintf("%d reactions on comment", count)

printSuccessWithSummary(resp.Data, summary)
},
}

Expand Down
24 changes: 24 additions & 0 deletions internal/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,30 @@ func printSuccessWithPagination(data interface{}, hasNext bool, nextURL string)
os.Exit(errors.ExitSuccess)
}

// printSuccessWithSummary prints a success response with summary.
func printSuccessWithSummary(data interface{}, summary string) {
resp := response.SuccessWithSummary(data, summary)
if lastResult != nil {
lastResult.Response = resp
lastResult.ExitCode = errors.ExitSuccess
panic(testExitSignal{}) // Signal to stop execution in test mode
}
resp.Print()
os.Exit(errors.ExitSuccess)
}

// printSuccessWithPaginationAndSummary prints a success response with pagination and summary.
func printSuccessWithPaginationAndSummary(data interface{}, hasNext bool, nextURL string, summary string) {
resp := response.SuccessWithPaginationAndSummary(data, hasNext, nextURL, summary)
if lastResult != nil {
lastResult.Response = resp
lastResult.ExitCode = errors.ExitSuccess
panic(testExitSignal{}) // Signal to stop execution in test mode
}
resp.Print()
os.Exit(errors.ExitSuccess)
}

// SetTestMode configures the commands package for testing.
// It sets a mock client factory and captures results instead of exiting.
func SetTestMode(mockClient client.API) *CommandResult {
Expand Down
15 changes: 14 additions & 1 deletion internal/commands/search.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"fmt"
"strconv"
"strings"

Expand Down Expand Up @@ -68,8 +69,20 @@ var searchCmd = &cobra.Command{
exitWithError(err)
}

// Build summary
count := 0
if arr, ok := resp.Data.([]interface{}); ok {
count = len(arr)
}
summary := fmt.Sprintf("%d results for \"%s\"", count, query)
if searchAll {
summary = fmt.Sprintf("%d results for \"%s\" (all)", count, query)
} else if searchPage > 0 {
summary = fmt.Sprintf("%d results for \"%s\" (page %d)", count, query, searchPage)
}

hasNext := resp.LinkNext != ""
printSuccessWithPagination(resp.Data, hasNext, resp.LinkNext)
printSuccessWithPaginationAndSummary(resp.Data, hasNext, resp.LinkNext, summary)
},
}

Expand Down
15 changes: 14 additions & 1 deletion internal/commands/tag.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"fmt"
"strconv"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -36,8 +37,20 @@ var tagListCmd = &cobra.Command{
exitWithError(err)
}

// Build summary
count := 0
if arr, ok := resp.Data.([]interface{}); ok {
count = len(arr)
}
summary := fmt.Sprintf("%d tags", count)
if tagListAll {
summary += " (all)"
} else if tagListPage > 0 {
summary += fmt.Sprintf(" (page %d)", tagListPage)
}

hasNext := resp.LinkNext != ""
printSuccessWithPagination(resp.Data, hasNext, resp.LinkNext)
printSuccessWithPaginationAndSummary(resp.Data, hasNext, resp.LinkNext, summary)
},
}

Expand Down
15 changes: 14 additions & 1 deletion internal/commands/user.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"fmt"
"strconv"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -36,8 +37,20 @@ var userListCmd = &cobra.Command{
exitWithError(err)
}

// Build summary
count := 0
if arr, ok := resp.Data.([]interface{}); ok {
count = len(arr)
}
summary := fmt.Sprintf("%d users", count)
if userListAll {
summary += " (all)"
} else if userListPage > 0 {
summary += fmt.Sprintf(" (page %d)", userListPage)
}

hasNext := resp.LinkNext != ""
printSuccessWithPagination(resp.Data, hasNext, resp.LinkNext)
printSuccessWithPaginationAndSummary(resp.Data, hasNext, resp.LinkNext, summary)
},
}

Expand Down
28 changes: 28 additions & 0 deletions internal/response/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Response struct {
Error *ErrorDetail `json:"error,omitempty"`
Pagination *Pagination `json:"pagination,omitempty"`
Location string `json:"location,omitempty"`
Summary string `json:"summary,omitempty"`
Meta map[string]interface{} `json:"meta,omitempty"`
}

Expand Down Expand Up @@ -78,6 +79,33 @@ func SuccessWithPagination(data interface{}, hasNext bool, nextURL string) *Resp
return resp
}

// SuccessWithSummary creates a successful response with a summary.
func SuccessWithSummary(data interface{}, summary string) *Response {
return &Response{
Success: true,
Data: data,
Summary: summary,
Meta: createMeta(),
}
}

// SuccessWithPaginationAndSummary creates a successful response with pagination and summary.
func SuccessWithPaginationAndSummary(data interface{}, hasNext bool, nextURL string, summary string) *Response {
resp := &Response{
Success: true,
Data: data,
Summary: summary,
Meta: createMeta(),
}
if hasNext || nextURL != "" {
resp.Pagination = &Pagination{
HasNext: hasNext,
NextURL: nextURL,
}
}
return resp
}

// Error creates an error response from a CLIError.
func Error(err *errors.CLIError) *Response {
resp := &Response{
Expand Down