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
31 changes: 28 additions & 3 deletions internal/cmd/space/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package space
import (
"context"
"fmt"
"net/url"
"os"

"github.com/spf13/cobra"
Expand All @@ -14,6 +15,7 @@ import (

type listOptions struct {
limit int
cursor string
spaceType string
output string
noColor bool
Expand All @@ -34,6 +36,10 @@ func NewCmdList() *cobra.Command {
# List only global spaces
cfl space list --type global

# Paginate through results
cfl space list --limit 50
cfl space list --cursor "eyJpZCI6MTIzfQ=="

# Output as JSON
cfl space list -o json`,
RunE: func(cmd *cobra.Command, _ []string) error {
Expand All @@ -45,6 +51,7 @@ func NewCmdList() *cobra.Command {
}

cmd.Flags().IntVarP(&opts.limit, "limit", "l", 25, "Maximum number of spaces to return")
cmd.Flags().StringVar(&opts.cursor, "cursor", "", "Pagination cursor from a previous request")
cmd.Flags().StringVarP(&opts.spaceType, "type", "t", "", "Filter by space type (global, personal)")

return cmd
Expand Down Expand Up @@ -89,8 +96,9 @@ func runList(opts *listOptions, client *api.Client) error {

// List spaces
apiOpts := &api.ListSpacesOptions{
Limit: opts.limit,
Type: opts.spaceType,
Limit: opts.limit,
Cursor: opts.cursor,
Type: opts.spaceType,
}

result, err := client.ListSpaces(context.Background(), apiOpts)
Expand Down Expand Up @@ -122,8 +130,25 @@ func runList(opts *listOptions, client *api.Client) error {
renderer.RenderList(headers, rows, result.HasMore())

if result.HasMore() && opts.output != "json" {
fmt.Fprintf(os.Stderr, "\n(showing first %d results, use --limit to see more)\n", len(result.Results))
cursor := extractCursor(result.Links.Next)
if cursor != "" {
fmt.Fprintf(os.Stderr, "\n(more results available, use --cursor %q to see the next page)\n", cursor)
} else {
fmt.Fprintf(os.Stderr, "\n(showing first %d results, use --limit to see more)\n", len(result.Results))
}
}

return nil
}

// extractCursor parses the cursor parameter from a pagination next link URL.
func extractCursor(nextLink string) string {
if nextLink == "" {
return ""
}
u, err := url.Parse(nextLink)
if err != nil {
return ""
}
return u.Query().Get("cursor")
}
42 changes: 42 additions & 0 deletions internal/cmd/space/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,48 @@ func TestRunList_HasMore(t *testing.T) {
require.NoError(t, err)
}

func TestRunList_WithCursor(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "abc123", r.URL.Query().Get("cursor"))

w.WriteHeader(http.StatusOK)
w.Write([]byte(`{
"results": [
{"id": "789012", "key": "DOCS", "name": "Documentation", "type": "global"}
]
}`))
}))
defer server.Close()

client := api.NewClient(server.URL, "test@example.com", "token")
opts := &listOptions{
limit: 25,
cursor: "abc123",
noColor: true,
}

err := runList(opts, client)
require.NoError(t, err)
}

func TestExtractCursor(t *testing.T) {
tests := []struct {
name string
nextLink string
expected string
}{
{"valid cursor", "/wiki/api/v2/spaces?cursor=abc123&limit=25", "abc123"},
{"empty link", "", ""},
{"no cursor param", "/wiki/api/v2/spaces?limit=25", ""},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, extractCursor(tt.nextLink))
})
}
}

func TestRunList_NullDescription(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
Expand Down
Loading