Add --query flag to project item-list#12696
Add --query flag to project item-list#12696williammartin merged 12 commits intotrunkcli/cli:trunkfrom
--query flag to project item-list#12696Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a --query flag to gh project item-list to allow server-side filtering of project items using the GitHub Projects filter syntax (e.g. assignee:..., -status:...), addressing issue #12664.
Changes:
- Extend the project items GraphQL query plumbing to accept a query string and pass it to
ProjectV2.items. - Add
--queryflag parsing + end-to-end command behavior (including output) tests. - Introduce feature detection for whether a host’s schema supports the
ProjectV2.items(query:)argument (with mocks/tests).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/cmd/project/shared/queries/queries.go | Adds query arg support to ProjectItems GraphQL query and method signature. |
| pkg/cmd/project/shared/queries/queries_test.go | Updates expected GraphQL variables and call signature for ProjectItems. |
| pkg/cmd/project/item-list/item_list.go | Adds --query flag and gates it behind feature detection. |
| pkg/cmd/project/item-list/item_list_test.go | Adds parsing + runtime tests for --query and unsupported-host behavior. |
| internal/featuredetection/feature_detection.go | Adds ProjectFeatures() to detect ProjectV2.items(query:) support on enterprise hosts. |
| internal/featuredetection/feature_detection_test.go | Adds tests validating ProjectFeatures() behavior across hosts/schema variants. |
| internal/featuredetection/detector_mock.go | Updates detector mocks to implement the new interface method. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
babakks
left a comment
There was a problem hiding this comment.
Didn't fully test it, but I'm concerned about the query: $queryItems arg add to the GraphQL tag on Project.Items field.
| if host == "" { | ||
| host = ghinstance.Default() | ||
| } | ||
| config.detector = fd.NewDetector(api.NewCachedHTTPClient(httpClient, time.Hour*24), host) |
There was a problem hiding this comment.
I think in other places we populate the detector in the runX func, ofc if it's nil.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
6ab0b3c to
40ce0be
Compare
| Long: heredoc.Doc(` | ||
| List the items in a project. | ||
|
|
||
| If supported by the API host (github.com and GHES 3.20+), the --query option can |
There was a problem hiding this comment.
question: are we sure about GHES 3.20 support for this?
There was a problem hiding this comment.
Well it's not in now in 3.19 and schema changes are always pulled in on release.
| @@ -147,6 +173,27 @@ type Project struct { | ||
| } | ||
| } | ||
|
|
||
| func newProjectFromWithoutItemQuery(source projectWithoutItemQuery) *Project { | ||
| project := &Project{ | ||
| Number: source.Number, | ||
| URL: source.URL, | ||
| ShortDescription: source.ShortDescription, | ||
| Public: source.Public, | ||
| Closed: source.Closed, | ||
| Title: source.Title, | ||
| ID: source.ID, | ||
| Readme: source.Readme, | ||
| Fields: source.Fields, | ||
| } | ||
| project.Items.PageInfo = source.Items.PageInfo | ||
| project.Items.TotalCount = source.Items.TotalCount | ||
| project.Items.Nodes = source.Items.Nodes | ||
| project.Owner.TypeName = source.Owner.TypeName | ||
| project.Owner.User.Login = source.Owner.User.Login | ||
| project.Owner.Organization.Login = source.Owner.Organization.Login | ||
| return project | ||
| } | ||
|
|
||
| func (p Project) DetailedItems() map[string]interface{} { | ||
| return map[string]interface{}{ | ||
| "items": serializeProjectWithItems(&p), | ||
| @@ -508,8 +555,10 @@ func (p ProjectItem) ExportData(_ []string) map[string]interface{} { | ||
| } | ||
|
|
||
| // ProjectItems returns the items of a project. If the OwnerType is VIEWER, no login is required. | ||
| // If limit is 0, the default limit is used. | ||
| func (c *Client) ProjectItems(o *Owner, number int32, limit int) (*Project, error) { | ||
| // If limit is 0, the default limit is used. The queryStr parameter is passed as a server-side | ||
| // filter to the items connection, using the same syntax as the GitHub Projects filter bar | ||
| // (e.g. "assignee:octocat", "status:done"). | ||
| func (c *Client) ProjectItems(o *Owner, number int32, limit int, queryStr string) (*Project, error) { | ||
| project := &Project{} | ||
| if limit == 0 { | ||
| limit = LimitDefault | ||
| @@ -528,20 +577,35 @@ func (c *Client) ProjectItems(o *Owner, number int32, limit int) (*Project, erro | ||
| "afterFields": (*githubv4.String)(nil), | ||
| "number": githubv4.Int(number), | ||
| } | ||
| if queryStr != "" { | ||
| variables["queryItems"] = githubv4.String(queryStr) | ||
| } | ||
|
|
||
| var query pager[ProjectItem] | ||
| var queryName string | ||
| switch o.Type { | ||
| case UserOwner: | ||
| variables["login"] = githubv4.String(o.Login) | ||
| query = &userOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| if queryStr == "" { | ||
| query = &userOwnerWithItemsNoQuery{} // must be a pointer to work with graphql queries | ||
| } else { | ||
| query = &userOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| } | ||
| queryName = "UserProjectWithItems" | ||
| case OrgOwner: | ||
| variables["login"] = githubv4.String(o.Login) | ||
| query = &orgOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| if queryStr == "" { | ||
| query = &orgOwnerWithItemsNoQuery{} // must be a pointer to work with graphql queries | ||
| } else { | ||
| query = &orgOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| } | ||
| queryName = "OrgProjectWithItems" | ||
| case ViewerOwner: | ||
| query = &viewerOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| if queryStr == "" { | ||
| query = &viewerOwnerWithItemsNoQuery{} // must be a pointer to work with graphql queries | ||
| } else { | ||
| query = &viewerOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| } |
There was a problem hiding this comment.
These are getting out of our hands. We already have had 2-3 types per owner type, and we're now adding 3 more.
I'm not blocking this PR, but we should prioritise a follow up to simplify this approach. Perhaps we should fallback to plain text queries?
There was a problem hiding this comment.
Sorry, the range is wrong. I meant the last switch statement.
Co-authored-by: Babak K. Shandiz <babakks@github.com>
Co-authored-by: Babak K. Shandiz <babakks@github.com>
Co-authored-by: Babak K. Shandiz <babakks@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| return err | ||
| } | ||
| if !features.ProjectItemQuery { | ||
| return fmt.Errorf("the `--query` flag is not supported on this GitHub host; most likely you are targeting a version of GHES that does not yet have the query field available") |
There was a problem hiding this comment.
nit: this seems extra for an error message
| if queryStr == "" { | ||
| query = &userOwnerWithItemsNoQuery{} // must be a pointer to work with graphql queries | ||
| } else { | ||
| query = &userOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| } | ||
| queryName = "UserProjectWithItems" | ||
| case OrgOwner: | ||
| variables["login"] = githubv4.String(o.Login) | ||
| query = &orgOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| if queryStr == "" { | ||
| query = &orgOwnerWithItemsNoQuery{} // must be a pointer to work with graphql queries | ||
| } else { | ||
| query = &orgOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| } | ||
| queryName = "OrgProjectWithItems" | ||
| case ViewerOwner: | ||
| query = &viewerOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| if queryStr == "" { | ||
| query = &viewerOwnerWithItemsNoQuery{} // must be a pointer to work with graphql queries | ||
| } else { | ||
| query = &viewerOwnerWithItems{} // must be a pointer to work with graphql queries | ||
| } |
There was a problem hiding this comment.
nit: code comments here could probably be just one comment for all of them

Description
Fixes #12664
Acceptance Criteria
Given I am targeting github.com
When I run
project item-listwith the--queryflagThen the query is respected in the results
Given I am targeting a version of GHES that doesn't support the query flag
When I run
project item-listwith the--queryflagThen I receive an informative error