From 46e5fbdc843cc9b85e0d489de402f0bc4d239409 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Wed, 4 May 2022 06:02:55 -0700 Subject: [PATCH] Add `--json` support to `label list` (#5567) --- pkg/cmd/label/http.go | 21 +++++++++++---- pkg/cmd/label/list.go | 17 ++++++++++--- pkg/cmd/label/list_test.go | 33 ++++++++++++++++++++++++ pkg/cmd/label/shared.go | 52 +++++++++++++++++++++++++++++++++++--- 4 files changed, 110 insertions(+), 13 deletions(-) diff --git a/pkg/cmd/label/http.go b/pkg/cmd/label/http.go index 4dc35aad2..f526570e0 100644 --- a/pkg/cmd/label/http.go +++ b/pkg/cmd/label/http.go @@ -1,6 +1,7 @@ package label import ( + "fmt" "net/http" "strings" @@ -10,6 +11,12 @@ import ( const maxPageSize = 100 +var defaultFields = []string{ + "color", + "description", + "name", +} + type listLabelsResponseData struct { Repository struct { Labels struct { @@ -28,6 +35,8 @@ type listQueryOptions struct { Query string Order string Sort string + + fields []string } func (opts listQueryOptions) OrderBy() map[string]string { @@ -44,17 +53,19 @@ func (opts listQueryOptions) OrderBy() map[string]string { // listLabels lists the labels in the given repo. Pass -1 for limit to list all labels; // otherwise, only that number of labels is returned for any number of pages. func listLabels(client *http.Client, repo ghrepo.Interface, opts listQueryOptions) ([]label, int, error) { + if len(opts.fields) == 0 { + opts.fields = defaultFields + } + apiClient := api.NewClientFromHTTP(client) - query := ` + fragment := fmt.Sprintf("fragment label on Label{%s}", strings.Join(opts.fields, ",")) + query := fragment + ` query LabelList($owner: String!, $repo: String!, $limit: Int!, $endCursor: String, $query: String, $orderBy: LabelOrder) { repository(owner: $owner, name: $repo) { labels(first: $limit, after: $endCursor, query: $query, orderBy: $orderBy) { totalCount, nodes { - name, - color, - description, - createdAt, + ...label } pageInfo { hasNextPage diff --git a/pkg/cmd/label/list.go b/pkg/cmd/label/list.go index 978ac2fc3..f4ba8ddce 100644 --- a/pkg/cmd/label/list.go +++ b/pkg/cmd/label/list.go @@ -23,8 +23,9 @@ type listOptions struct { HttpClient func() (*http.Client, error) IO *iostreams.IOStreams - Query listQueryOptions - WebMode bool + Exporter cmdutil.Exporter + Query listQueryOptions + WebMode bool } func newCmdList(f *cmdutil.Factory, runF func(*listOptions) error) *cobra.Command { @@ -75,11 +76,11 @@ func newCmdList(f *cmdutil.Factory, runF func(*listOptions) error) *cobra.Comman cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "List labels in the web browser") cmd.Flags().IntVarP(&opts.Query.Limit, "limit", "L", 30, "Maximum number of labels to fetch") cmd.Flags().StringVarP(&opts.Query.Query, "search", "S", "", "Search label names and descriptions") - - // These defaults match the service behavior and, therefore, the initially release of `label list` behavior. cmdutil.StringEnumFlag(cmd, &opts.Query.Order, "order", "", "asc", []string{"asc", "desc"}, "Order of labels returned") cmdutil.StringEnumFlag(cmd, &opts.Query.Sort, "sort", "", "created", []string{"created", "name"}, "Sort fetched labels") + cmdutil.AddJSONFlags(cmd, &opts.Exporter, labelFields) + return cmd } @@ -104,6 +105,10 @@ func listRun(opts *listOptions) error { return opts.Browser.Browse(labelListURL) } + if opts.Exporter != nil { + opts.Query.fields = opts.Exporter.Fields() + } + opts.IO.StartProgressIndicator() labels, totalCount, err := listLabels(httpClient, baseRepo, opts.Query) opts.IO.StopProgressIndicator() @@ -118,6 +123,10 @@ func listRun(opts *listOptions) error { return cmdutil.NewNoResultsError(fmt.Sprintf("no labels found in %s", ghrepo.FullName(baseRepo))) } + if opts.Exporter != nil { + return opts.Exporter.Write(opts.IO, labels) + } + if opts.IO.IsStdoutTTY() { title := listHeader(ghrepo.FullName(baseRepo), len(labels), totalCount) fmt.Fprintf(opts.IO.Out, "\n%s\n\n", title) diff --git a/pkg/cmd/label/list_test.go b/pkg/cmd/label/list_test.go index c2a42f421..36be2181a 100644 --- a/pkg/cmd/label/list_test.go +++ b/pkg/cmd/label/list_test.go @@ -98,6 +98,39 @@ func TestNewCmdList(t *testing.T) { wantErr: true, errMsg: "cannot specify `--order` or `--sort` with `--search`", }, + { + name: "json flag", + input: "--json name,color,description,createdAt", + output: listOptions{ + Query: listQueryOptions{ + Limit: 30, + Order: "asc", + Sort: "created", + fields: []string{ + "name", + "color", + "description", + "createdAt", + }, + }, + }, + }, + { + name: "invalid json flag", + input: "--json invalid", + wantErr: true, + errMsg: heredoc.Doc(` + Unknown JSON field: "invalid" + Available fields: + color + createdAt + description + id + isDefault + name + updatedAt + url`), + }, } for _, tt := range tests { diff --git a/pkg/cmd/label/shared.go b/pkg/cmd/label/shared.go index 0ec725472..dea11fe38 100644 --- a/pkg/cmd/label/shared.go +++ b/pkg/cmd/label/shared.go @@ -1,7 +1,51 @@ package label -type label struct { - Name string `json:"name"` - Color string `json:"color"` - Description string `json:"description,omitempty"` +import ( + "reflect" + "strings" + "time" +) + +var labelFields = []string{ + "color", + "createdAt", + "description", + "id", + "isDefault", + "name", + "updatedAt", + "url", +} + +type label struct { + Color string `json:"color"` + CreatedAt time.Time `json:"createdAt"` + Description string `json:"description"` + ID string `json:"id"` + IsDefault bool `json:"isDefault"` + Name string `json:"name"` + URL string `json:"url"` + UpdatedAt time.Time `json:"updatedAt"` +} + +// ExportData implements cmdutil.exportable +func (l *label) ExportData(fields []string) map[string]interface{} { + v := reflect.ValueOf(l).Elem() + data := map[string]interface{}{} + + for _, f := range fields { + switch f { + default: + sf := fieldByName(v, f) + data[f] = sf.Interface() + } + } + + return data +} + +func fieldByName(v reflect.Value, field string) reflect.Value { + return v.FieldByNameFunc(func(s string) bool { + return strings.EqualFold(field, s) + }) }