Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add limit and search flags to stack list command #252

Merged
merged 4 commits into from
Aug 30, 2024
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
10 changes: 10 additions & 0 deletions internal/cmd/stack/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,13 @@ var flagInteractive = &cli.BoolFlag{
Aliases: []string{"i"},
Usage: "[Optional] Whether to run the command in interactive mode",
}

var flagLimit = &cli.UintFlag{
Name: "limit",
Usage: "[Optional] Limit the number of items to return",
}

var flagSearch = &cli.StringFlag{
Name: "search",
Usage: "[Optional] Performs a full-text search.",
}
104 changes: 92 additions & 12 deletions internal/cmd/stack/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package stack
import (
"context"
"fmt"
"math"
"slices"
"strings"

"github.com/shurcooL/graphql"
"github.com/spacelift-io/spacectl/client/structs"
"github.com/spacelift-io/spacectl/internal"
"github.com/spacelift-io/spacectl/internal/cmd"
"github.com/urfave/cli/v2"
)
Expand All @@ -18,20 +21,57 @@ func listStacks() cli.ActionFunc {
return err
}

var limit *uint
if cliCtx.IsSet(flagLimit.Name) {
if cliCtx.Uint(flagLimit.Name) == 0 {
return fmt.Errorf("limit must be greater than 0")
}

if cliCtx.Uint(flagLimit.Name) >= math.MaxInt32 {
0michalsokolowski0 marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("limit must be less than %d", math.MaxInt32)
}

limit = internal.Ptr(cliCtx.Uint(flagLimit.Name))
}

var search *string
if cliCtx.IsSet(flagSearch.Name) {
if cliCtx.String(flagSearch.Name) == "" {
return fmt.Errorf("search must be non-empty")
}

search = internal.Ptr(cliCtx.String(flagSearch.Name))
}

switch outputFormat {
case cmd.OutputFormatTable:
return listStacksTable(cliCtx)
return listStacksTable(cliCtx, search, limit)
case cmd.OutputFormatJSON:
return listStacksJSON(cliCtx)
return listStacksJSON(cliCtx, search, limit)
}

return fmt.Errorf("unknown output format: %v", outputFormat)
}
}

func listStacksJSON(ctx *cli.Context) error {
func listStacksJSON(
ctx *cli.Context,
search *string,
limit *uint,
) error {
var first *graphql.Int
if limit != nil {
first = graphql.NewInt(graphql.Int(*limit)) //nolint: gosec
}

var fullTextSearch *graphql.String
if search != nil {
fullTextSearch = graphql.NewString(graphql.String(*search))
}

stacks, err := searchAllStacks(ctx.Context, structs.SearchInput{
First: graphql.NewInt(50),
First: first,
FullTextSearch: fullTextSearch,
})
if err != nil {
return err
Expand All @@ -40,14 +80,31 @@ func listStacksJSON(ctx *cli.Context) error {
return cmd.OutputJSON(stacks)
}

func listStacksTable(ctx *cli.Context) error {
stacks, err := searchAllStacks(ctx.Context, structs.SearchInput{
First: graphql.NewInt(50),
func listStacksTable(
ctx *cli.Context,
search *string,
limit *uint,
) error {
var first *graphql.Int
if limit != nil {
first = graphql.NewInt(graphql.Int(*limit)) //nolint: gosec
}

var fullTextSearch *graphql.String
if search != nil {
fullTextSearch = graphql.NewString(graphql.String(*search))
}

input := structs.SearchInput{
First: first,
FullTextSearch: fullTextSearch,
OrderBy: &structs.QueryOrder{
Field: "starred",
Direction: "DESC",
},
})
}

stacks, err := searchAllStacks(ctx.Context, input)
if err != nil {
return err
}
Expand Down Expand Up @@ -78,19 +135,42 @@ func listStacksTable(ctx *cli.Context) error {
return cmd.OutputTable(tableData, true)
}

// searchStacks returns a list of stacks based on the provided search input.
// input.First limits the total number of returned stacks, if not provided all stacks are returned.
func searchAllStacks(ctx context.Context, input structs.SearchInput) ([]stack, error) {
out := []stack{}
const maxPageSize = 50

var limit int
if input.First != nil {
limit = int(*input.First)
}
fetchAll := limit == 0

out := []stack{}
pageInput := structs.SearchInput{
First: graphql.NewInt(maxPageSize),
FullTextSearch: input.FullTextSearch,
}
for {
result, err := searchStacks(ctx, input)
if !fetchAll {
// Fetch exactly the number of items requested
pageInput.First = graphql.NewInt(
//nolint: gosec
graphql.Int(
slices.Min([]int{maxPageSize, limit - len(out)}),
),
)
}

result, err := searchStacks(ctx, pageInput)
if err != nil {
return nil, err
}

out = append(out, result.Stacks...)

if result.PageInfo.HasNextPage {
input.After = graphql.NewString(graphql.String(result.PageInfo.EndCursor))
if result.PageInfo.HasNextPage && (fetchAll || limit > len(out)) {
pageInput.After = graphql.NewString(graphql.String(result.PageInfo.EndCursor))
} else {
break
}
Expand Down
2 changes: 2 additions & 0 deletions internal/cmd/stack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ func Command() *cli.Command {
flagShowLabels,
cmd.FlagOutputFormat,
cmd.FlagNoColor,
flagLimit,
flagSearch,
},
Action: listStacks(),
Before: cmd.PerformAllBefore(cmd.HandleNoColor, authenticated.Ensure),
Expand Down
5 changes: 5 additions & 0 deletions internal/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package internal

func Ptr[T any](v T) *T {
return &v
}
Loading