Skip to content

Commit

Permalink
feat(cli): Introduce workflow pagination on CLI (chainloop-dev#1514)
Browse files Browse the repository at this point in the history
Signed-off-by: Javier Rodriguez <[email protected]>
  • Loading branch information
javirln authored Nov 14, 2024
1 parent 2c8a4ff commit d131bbe
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 18 deletions.
1 change: 1 addition & 0 deletions app/cli/cmd/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const formatTable = "table"
type tabulatedData interface {
[]*action.WorkflowItem |
*action.WorkflowItem |
*action.WorkflowListResult |
*action.AttestationStatusResult |
[]*action.WorkflowRunItem |
*action.WorkflowRunItemFull |
Expand Down
64 changes: 57 additions & 7 deletions app/cli/cmd/workflow_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,82 @@ import (
"github.com/spf13/cobra"
)

const (
// defaultPageSize is the default page size
defaultPageSize = 15
// defaultPage is the default page
defaultPage = 1
)

var (
// page is the current page number
page int
// pageSize is the number of workflows per page
pageSize int
)

func newWorkflowListCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List existing Workflows",
RunE: func(cmd *cobra.Command, args []string) error {
res, err := action.NewWorkflowList(actionOpts).Run()
Example: ` # Let the default pagination apply
chainloop workflow list
# Specify the page and page size
chainloop workflow list --page 2 --page-size 10
# Output in json format to paginate using scripts
chainloop workflow list --page 2 --page-size 10 --output json
# Show the full report
chainloop workflow list --full
`,
PreRunE: func(_ *cobra.Command, _ []string) error {
if page < 1 {
return fmt.Errorf("--page must be greater or equal than 1")
}
if pageSize < 1 {
return fmt.Errorf("--page-size must be greater or equal than 1")
}

return nil
},
RunE: func(_ *cobra.Command, _ []string) error {
res, err := action.NewWorkflowList(actionOpts).Run(page, pageSize)
if err != nil {
return err
}

return encodeOutput(res, WorkflowListTableOutput)
if err := encodeOutput(res, WorkflowListTableOutput); err != nil {
return err
}

pgResponse := res.Pagination

logger.Info().Msg(fmt.Sprintf("Showing %d out of %d", len(res.Workflows), pgResponse.TotalCount))

if pgResponse.TotalCount > pgResponse.Page*pgResponse.PageSize {
logger.Info().Msg(fmt.Sprintf("Next page available: %d", pgResponse.Page+1))
}

return nil
},
}

cmd.Flags().BoolVar(&full, "full", false, "show the full report")
cmd.Flags().IntVar(&page, "page", defaultPage, "page number")
cmd.Flags().IntVar(&pageSize, "page-size", defaultPageSize, "number of workflows per page")

return cmd
}

func workflowItemTableOutput(workflow *action.WorkflowItem) error {
return WorkflowListTableOutput([]*action.WorkflowItem{workflow})
return WorkflowListTableOutput(&action.WorkflowListResult{Workflows: []*action.WorkflowItem{workflow}})
}

func WorkflowListTableOutput(workflows []*action.WorkflowItem) error {
if len(workflows) == 0 {
func WorkflowListTableOutput(workflowListResult *action.WorkflowListResult) error {
if len(workflowListResult.Workflows) == 0 {
fmt.Println("there are no workflows yet")
return nil
}
Expand All @@ -64,7 +114,7 @@ func WorkflowListTableOutput(workflows []*action.WorkflowItem) error {
t.AppendHeader(headerRow)
}

for _, p := range workflows {
for _, p := range workflowListResult.Workflows {
var row table.Row
var lastRunRunner, lastRunState string
if lr := p.LastRun; lr != nil {
Expand Down
2 changes: 1 addition & 1 deletion app/cli/cmd/workflow_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func newWorkflowUpdateCmd() *cobra.Command {
}

logger.Info().Msg("Workflow updated!")
return encodeOutput([]*action.WorkflowItem{res}, WorkflowListTableOutput)
return encodeOutput(&action.WorkflowListResult{Workflows: []*action.WorkflowItem{res}}, WorkflowListTableOutput)
},
}

Expand Down
7 changes: 7 additions & 0 deletions app/cli/internal/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ type ActionsOpts struct {
Logger zerolog.Logger
}

type OffsetPagination struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
TotalPages int `json:"totalPages"`
TotalCount int `json:"totalCount"`
}

func toTimePtr(t time.Time) *time.Time {
return &t
}
Expand Down
54 changes: 44 additions & 10 deletions app/cli/internal/action/workflow_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,43 +44,77 @@ type WorkflowItem struct {
Public bool `json:"public"`
}

// WorkflowListResult holds the output of the workflow list action
type WorkflowListResult struct {
Workflows []*WorkflowItem `json:"workflows"`
Pagination *OffsetPagination `json:"pagination"`
}

// NewWorkflowList creates a new instance of WorkflowList
func NewWorkflowList(cfg *ActionsOpts) *WorkflowList {
return &WorkflowList{cfg}
}

func (action *WorkflowList) Run() ([]*WorkflowItem, error) {
// Run executes the workflow list action
func (action *WorkflowList) Run(page int, pageSize int) (*WorkflowListResult, error) {
if page < 1 {
return nil, fmt.Errorf("page must be greater or equal to 1")
}
if pageSize < 1 {
return nil, fmt.Errorf("page-size must be greater or equal to 1")
}

client := pb.NewWorkflowServiceClient(action.cfg.CPConnection)
resp, err := client.List(context.Background(), &pb.WorkflowServiceListRequest{})
res := &WorkflowListResult{}

resp, err := client.List(context.Background(), &pb.WorkflowServiceListRequest{
Pagination: &pb.OffsetPaginationRequest{
Page: int32(page),
PageSize: int32(pageSize),
},
})
if err != nil {
return nil, err
}

result := make([]*WorkflowItem, 0, len(resp.Result))
// Convert the response to the output format
for _, p := range resp.Result {
result = append(result, pbWorkflowItemToAction(p))
res.Workflows = append(res.Workflows, pbWorkflowItemToAction(p))
}

// Add the pagination details
res.Pagination = &OffsetPagination{
Page: int(resp.GetPagination().GetPage()),
PageSize: int(resp.GetPagination().GetPageSize()),
TotalPages: int(resp.GetPagination().GetTotalPages()),
TotalCount: int(resp.GetPagination().GetTotalCount()),
}

return result, nil
return res, nil
}

// pbWorkflowItemToAction converts API response to WorkflowItem
func pbWorkflowItemToAction(wf *pb.WorkflowItem) *WorkflowItem {
if wf == nil {
return nil
}

res := &WorkflowItem{
Name: wf.Name, ID: wf.Id, CreatedAt: toTimePtr(wf.CreatedAt.AsTime()),
Project: wf.Project, Team: wf.Team, RunsCount: wf.RunsCount,
return &WorkflowItem{
Name: wf.Name,
ID: wf.Id,
CreatedAt: toTimePtr(wf.CreatedAt.AsTime()),
Project: wf.Project,
Team: wf.Team,
RunsCount: wf.RunsCount,
ContractName: wf.ContractName,
ContractRevisionLatest: wf.ContractRevisionLatest,
LastRun: pbWorkflowRunItemToAction(wf.LastRun),
Public: wf.Public,
Description: wf.Description,
}

return res
}

// NamespacedName returns the project and workflow name in a formatted string
func (wi *WorkflowItem) NamespacedName() string {
return fmt.Sprintf("%s/%s", wi.Project, wi.Name)
}

0 comments on commit d131bbe

Please sign in to comment.