@@ -21,6 +21,8 @@ type listOpts struct {
2121 owner string
2222 number int32
2323 query string
24+ fields []string
25+ fieldIDs []string
2426 exporter cmdutil.Exporter
2527}
2628
@@ -55,6 +57,9 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C
5557
5658 # List items with the "bug" label that are not done
5759 $ gh project item-list 1 --owner "@me" --query "label:bug -status:Done"
60+
61+ # Show the "Status" and "Priority" field values as extra columns
62+ $ gh project item-list 1 --owner "@me" --field "Status" --field "Priority"
5863 ` ),
5964 Args : cobra .MaximumNArgs (1 ),
6065 RunE : func (cmd * cobra.Command , args []string ) error {
@@ -71,6 +76,22 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C
7176 opts .number = int32 (num )
7277 }
7378
79+ if err := cmdutil .MutuallyExclusive (
80+ "only one of `--field` or `--field-id` may be used" ,
81+ len (opts .fields ) > 0 ,
82+ len (opts .fieldIDs ) > 0 ,
83+ ); err != nil {
84+ return err
85+ }
86+
87+ if err := cmdutil .MutuallyExclusive (
88+ "cannot use `--format` with `--field` or `--field-id`" ,
89+ opts .exporter != nil ,
90+ len (opts .fields ) > 0 || len (opts .fieldIDs ) > 0 ,
91+ ); err != nil {
92+ return err
93+ }
94+
7495 config := listConfig {
7596 io : f .IOStreams ,
7697 client : client ,
@@ -101,6 +122,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(config listConfig) error) *cobra.C
101122
102123 listCmd .Flags ().StringVar (& opts .owner , "owner" , "" , "Login of the owner. Use \" @me\" for the current user" )
103124 listCmd .Flags ().StringVar (& opts .query , "query" , "" , `Filter items using the Projects filter syntax, e.g. "assignee:octocat -status:Done"` )
125+ listCmd .Flags ().StringArrayVar (& opts .fields , "field" , nil , "Name of a field to show as an extra column" )
126+ listCmd .Flags ().StringArrayVar (& opts .fieldIDs , "field-id" , nil , "ID of a field to show as an extra column" )
104127 cmdutil .AddFormatFlags (listCmd , & opts .exporter )
105128 listCmd .Flags ().IntVarP (& opts .limit , "limit" , "L" , queries .LimitDefault , "Maximum number of items to fetch" )
106129
@@ -142,15 +165,73 @@ func runList(config listConfig) error {
142165 return config .opts .exporter .Write (config .io , project .DetailedItems ())
143166 }
144167
145- return printResults (config , project .Items .Nodes , owner .Login )
168+ // Resolve any requested extra field columns to (header, fieldID) pairs. Name
169+ // columns are resolved against the fields already returned with the items, so
170+ // there is no separate field-ID preflight lookup. When extra columns are
171+ // requested for a project whose field list spans more than one page, fetch the
172+ // complete field list first so resolution does not miss fields beyond the
173+ // first page.
174+ fields := project .Fields .Nodes
175+ if (len (config .opts .fields ) > 0 || len (config .opts .fieldIDs ) > 0 ) && project .Fields .PageInfo .HasNextPage {
176+ withFields , err := config .client .ProjectFields (owner , config .opts .number , project .Fields .TotalCount )
177+ if err != nil {
178+ return err
179+ }
180+ fields = withFields .Fields .Nodes
181+ }
182+
183+ extraFields , err := resolveFieldColumns (config .opts , fields )
184+ if err != nil {
185+ return err
186+ }
187+
188+ return printResults (config , project .Items .Nodes , owner .Login , extraFields )
189+ }
190+
191+ // fieldColumn identifies an extra table column to render for each item: Header
192+ // is the column title and FieldID is the field node ID whose value to show.
193+ type fieldColumn struct {
194+ Header string
195+ FieldID string
196+ }
197+
198+ // resolveFieldColumns turns the --field (names) and --field-id (node IDs) flags
199+ // into ordered column descriptors. Names are resolved with ResolveFieldByName so
200+ // unknown or ambiguous names fail with candidate-carrying errors; IDs are
201+ // validated against the project's fields so an unknown ID also fails clearly.
202+ func resolveFieldColumns (opts listOpts , fields []queries.ProjectField ) ([]fieldColumn , error ) {
203+ columns := make ([]fieldColumn , 0 , len (opts .fields )+ len (opts .fieldIDs ))
204+
205+ for _ , name := range opts .fields {
206+ field , err := queries .ResolveFieldByName (fields , name )
207+ if err != nil {
208+ return nil , err
209+ }
210+ columns = append (columns , fieldColumn {Header : field .Name (), FieldID : field .ID ()})
211+ }
212+
213+ for _ , id := range opts .fieldIDs {
214+ field , err := queries .ResolveFieldByID (fields , id )
215+ if err != nil {
216+ return nil , err
217+ }
218+ columns = append (columns , fieldColumn {Header : field .Name (), FieldID : field .ID ()})
219+ }
220+
221+ return columns , nil
146222}
147223
148- func printResults (config listConfig , items []queries.ProjectItem , login string ) error {
224+ func printResults (config listConfig , items []queries.ProjectItem , login string , extraFields [] fieldColumn ) error {
149225 if len (items ) == 0 {
150226 return cmdutil .NewNoResultsError (fmt .Sprintf ("Project %d for owner %s has no items" , config .opts .number , login ))
151227 }
152228
153- tp := tableprinter .New (config .io , tableprinter .WithHeader ("Type" , "Title" , "Number" , "Repository" , "ID" ))
229+ headers := []string {"Type" , "Title" , "Number" , "Repository" , "ID" }
230+ for _ , f := range extraFields {
231+ headers = append (headers , f .Header )
232+ }
233+
234+ tp := tableprinter .New (config .io , tableprinter .WithHeader (headers ... ))
154235
155236 for _ , i := range items {
156237 tp .AddField (i .Type ())
@@ -162,8 +243,22 @@ func printResults(config listConfig, items []queries.ProjectItem, login string)
162243 }
163244 tp .AddField (i .Repo ())
164245 tp .AddField (i .ID (), tableprinter .WithTruncate (nil ))
246+ for _ , f := range extraFields {
247+ tp .AddField (fieldValueForItem (i , f .FieldID ))
248+ }
165249 tp .EndRow ()
166250 }
167251
168252 return tp .Render ()
169253}
254+
255+ // fieldValueForItem returns the display value of the field identified by fieldID
256+ // on item i, or an empty string when the item has no value for that field.
257+ func fieldValueForItem (i queries.ProjectItem , fieldID string ) string {
258+ for _ , v := range i .FieldValues .Nodes {
259+ if v .ID () == fieldID {
260+ return v .DisplayValue ()
261+ }
262+ }
263+ return ""
264+ }
0 commit comments