Skip to content

feat: Add remaining Projects v2 endpoints#4319

Open
JamBalaya56562 wants to merge 10 commits into
google:masterfrom
JamBalaya56562:feat/3715-projects-v2-remaining
Open

feat: Add remaining Projects v2 endpoints#4319
JamBalaya56562 wants to merge 10 commits into
google:masterfrom
JamBalaya56562:feat/3715-projects-v2-remaining

Conversation

@JamBalaya56562

Copy link
Copy Markdown
Contributor

Fixes #3715

Summary

ProjectsService already covers projects (get/list), fields (get/list), and items (full CRUD). Comparing the current code against openapi_operations.yaml, eight Projects v2 REST operations were present in the spec but not yet implemented. This PR adds them, completing the Projects v2 REST surface:

Group Organization User
Create draft item CreateOrganizationProjectDraftItem CreateUserProjectDraftItem
Add field AddOrganizationProjectField AddUserProjectField
Create view CreateOrganizationProjectView CreateUserProjectView
List view items ListOrganizationProjectViewItems ListUserProjectViewItems

Notes

  • Reuse: drafts and view items return ProjectV2Item; fields return ProjectV2Field; view items use the existing ListProjectItemsOptions (before/after cursor pagination). New types: ProjectV2View plus CreateProjectV2DraftItemRequest, AddProjectV2FieldRequest, CreateProjectV2ViewRequest (and the single_select/iteration field helper types). Request bodies are passed by value with a Request suffix, per the paramcheck convention.
  • User path quirk (mirrors the upstream spec): the user-owned draft and view create endpoints identify the user by the numeric user_id in the path (POST /user/{user_id}/projectsV2/{project_number}/drafts and POST /users/{user_id}/projectsV2/{project_number}/views), unlike the username-based paths used by the other user endpoints. These methods therefore take a userID int64, consistent with Users.GetByID.
  • No changes to openapi_operations.yaml (the operations were already present); generated accessors/iterators were regenerated via script/generate.sh.

Testing

Added success + error-path tests for all eight methods (including a single_select field, an iteration field, and view-item pagination). go test -race ./github/, go vet, go generate ./... --check, and golangci-lint all pass.

@JamBalaya56562 JamBalaya56562 force-pushed the feat/3715-projects-v2-remaining branch from 2d1843a to 10b097f Compare June 22, 2026 07:23
@gmlewis gmlewis added the NeedsReview PR is awaiting a review before merging. label Jun 22, 2026
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.50%. Comparing base (2801b4b) to head (10bea9e).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4319      +/-   ##
==========================================
+ Coverage   97.48%   97.50%   +0.01%     
==========================================
  Files         193      193              
  Lines       19400    19542     +142     
==========================================
+ Hits        18912    19054     +142     
  Misses        270      270              
  Partials      218      218              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gmlewis gmlewis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @JamBalaya56562!

Comment thread github/projects.go Outdated
Comment thread github/projects.go

@gmlewis gmlewis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @JamBalaya56562!
LGTM.
Awaiting second LGTM+Approval from any other contributor to this repo before merging.

cc: @stevehipwell - @alexandear - @Not-Dhananjay-Mishra

@stevehipwell stevehipwell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to fix the struct types to not use pointers for required fields. I've commented on the first one.

Comment thread github/projects.go Outdated
// CreateProjectV2DraftItemRequest specifies the parameters to create a draft item in a project.
type CreateProjectV2DraftItemRequest struct {
// Title is the title of the draft issue item to create. (Required.)
Title *string `json:"title,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Title *string `json:"title,omitempty"`
Title string `json:"title"`

As title is required it shouldn't be a pointer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — thanks! Made the required request fields non-pointer values (and dropped omitempty): CreateProjectV2DraftItemRequest.Title, CreateProjectV2ViewRequest.Name/Layout (all in the OpenAPI required arrays), plus ProjectV2FieldSingleSelectOption.Name (an option's required display label). I kept AddProjectV2FieldRequest all-pointer since its body is a oneOf where fields are only conditionally required, and left the optional/response fields as-is. e5df91b

@stevehipwell stevehipwell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There still appear to be fields which are required in the responses that are being defined as pointers (e.g. many of the view fields).

Comment thread github/projects_test.go

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use cmp.Diff, cmp.Equal, testJSONMarshal to simplify tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — switched the new draft/field/view/view-item tests to cmp.Diff(want, got) against a fully-populated want (which now also checks every decoded field) in 365ebfb.

@JamBalaya56562

Copy link
Copy Markdown
Contributor Author

@stevehipwell thanks. I've kept the response types pointer-based on purpose, to stay consistent with go-github's convention: response structs use *T + omitempty for every field, even ones that are always present. That's what the Repository example in CONTRIBUTING.md shows (ID *int64, NodeID *string, Owner *User), what the sibling ProjectV2/ProjectV2DraftIssue/ProjectV2FieldOption in this file do, and what User, Repository, etc. do throughout the package. The value-for-required convention applies to request bodies, which I've updated (Title, Name/Layout, single-select option Name). Making only ProjectV2View use value types would make it inconsistent with ProjectV2 right beside it. Happy to switch if you'd prefer to change the response convention package-wide, but that feels like a separate change beyond this PR.

JamBalaya56562 added a commit to JamBalaya56562/go-github that referenced this pull request Jun 22, 2026
PR google#4315 (google#4301) and google#4318 (google#2113) were merged; PR google#4316 (google#3220) was
closed unmerged after maintainers decided the rate-limit sleep callback
belongs in the external gofri/go-github-ratelimit middleware. google#4319
(google#3715, projects v2) remains open and under review.
@stevehipwell

Copy link
Copy Markdown
Contributor

@JamBalaya56562 I think the current required pattern is to only use pointer types for optional fields. See #4202 (closed) where the consensus from the maintainer/reviewers was that required response body fields should be non-pointer types.

Comment thread github/projects.go Outdated

// CreateProjectV2DraftItemRequest specifies the parameters to create a draft item in a project.
type CreateProjectV2DraftItemRequest struct {
// Title is the title of the draft issue item to create. (Required.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Title is the title of the draft issue item to create. (Required.)
// Title is the title of the draft issue item to create.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2aee530.

Comment thread github/projects.go Outdated
type CreateProjectV2DraftItemRequest struct {
// Title is the title of the draft issue item to create. (Required.)
Title string `json:"title"`
// Body is the body content of the draft issue item to create. (Optional.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Body is the body content of the draft issue item to create. (Optional.)
// Body is the body content of the draft issue item to create.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2aee530.

Comment thread github/projects.go Outdated

// ProjectV2FieldSingleSelectOption represents an option to create for a single_select project field.
type ProjectV2FieldSingleSelectOption struct {
// Name is the display name of the option. (Required.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Name is the display name of the option. (Required.)
// Name is the display name of the option.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2aee530.

Comment thread github/projects.go Outdated
return err
}
if len(tuple) != 2 {
return fmt.Errorf("ProjectV2ViewSortBy: expected a [field_id, direction] tuple, got %v elements", len(tuple))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("ProjectV2ViewSortBy: expected a [field_id, direction] tuple, got %v elements", len(tuple))
return fmt.Errorf("expected a [field_id, direction] tuple, got %v elements", len(tuple))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bcc07a6.

Comment thread github/projects.go Outdated
// The OpenAPI schema allows the field_id to be a string as well.
var str string
if err2 := json.Unmarshal(tuple[0], &str); err2 != nil {
return fmt.Errorf("ProjectV2ViewSortBy: invalid field_id: %w", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("ProjectV2ViewSortBy: invalid field_id: %w", err)
return fmt.Errorf("invalid field_id: %w", err2)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bcc07a6 — also switched to wrap err2 (the actual string-parse error) here, thanks for catching that.

Comment thread github/projects.go Outdated
}
fieldID, err = strconv.ParseInt(str, 10, 64)
if err != nil {
return fmt.Errorf("ProjectV2ViewSortBy: invalid field_id %q: %w", str, err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("ProjectV2ViewSortBy: invalid field_id %q: %w", str, err)
return fmt.Errorf("invalid field_id %q: %w", str, err)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bcc07a6.

Comment thread github/projects.go Outdated

var direction string
if err := json.Unmarshal(tuple[1], &direction); err != nil {
return fmt.Errorf("ProjectV2ViewSortBy: invalid direction: %w", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("ProjectV2ViewSortBy: invalid direction: %w", err)
return fmt.Errorf("invalid direction: %w", err)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bcc07a6.

Comment thread github/projects.go Outdated
type CreateProjectV2ViewRequest struct {
// Name is the view's display name. (Required.)
Name string `json:"name"`
// Layout is the view's layout. One of: table, board, roadmap. (Required.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Layout is the view's layout. One of: table, board, roadmap. (Required.)
// Layout is the view's layout. One of: table, board, roadmap.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bcc07a6.

Comment thread github/projects.go Outdated

// CreateProjectV2ViewRequest specifies the parameters to create a project view.
type CreateProjectV2ViewRequest struct {
// Name is the view's display name. (Required.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Name is the view's display name. (Required.)
// Name is the view's display name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bcc07a6.

@JamBalaya56562

Copy link
Copy Markdown
Contributor Author

Thanks @stevehipwell, and apologies for pushing back earlier — you're right. I checked #4202 and the consensus there is indeed that required response fields should be non-pointer (the merged CONTRIBUTING.md hasn't caught up yet, which is what threw me off). I've made ProjectV2View's required scalar fields non-pointer (id, number, name, layout, node_id, project_url, html_url) in 5044ba0, keeping the optional filter, the nested creator, the timestamps and the slice fields as-is. Let me know if you'd like me to extend it to creator/timestamps too.

Comment thread github/projects.go Outdated
// object, so it has custom JSON (un)marshaling.
type ProjectV2ViewSortBy struct {
// FieldID is the ID of the field to sort by.
FieldID *int64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it makes sense:

Suggested change
FieldID *int64
FieldID *int64 `json:"-"`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added json:"-" to FieldID and Direction since they're handled by the custom Marshal/UnmarshalJSON. 362c31a

Comment thread github/projects.go Outdated
Comment on lines +881 to +889
ID int64 `json:"id"`
Number int `json:"number"`
Name string `json:"name"`
Layout string `json:"layout"`
NodeID string `json:"node_id"`
ProjectURL string `json:"project_url"`
HTMLURL string `json:"html_url"`
Creator *User `json:"creator,omitempty"`
Filter *string `json:"filter,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these fields without comments and the rest with?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — I removed the per-field comments to make ProjectV2View consistent (matching the sibling ProjectV2, which has none). Happy to add a comment to every field instead if you'd prefer. 362c31a

Comment thread github/projects.go
}

// ProjectV2View represents a view in a project.
type ProjectV2View struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  "required": [
    "id",
    "number",
    "name",
    "layout",
    "node_id",
    "project_url",
    "html_url",
    "creator",
    "created_at",
    "updated_at",
    "visible_fields",
    "sort_by",
    "group_by",
    "vertical_group_by"
  ]

These fields are required we do not use omitempty and pointer with them.
Still some required field use omitempty like group_by, vertical_group_by etc. (As said in comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — in ProjectV2View the remaining required fields (creator, created_at, updated_at, visible_fields, sort_by, group_by, vertical_group_by) are now non-pointer values without omitempty, matching the convention that only optional fields are pointers. The only field left as a pointer is filter, which is optional. Accessors were regenerated accordingly. 10bea9e

(The branch was also rebased onto the latest master to clear the merge conflict, hence the force-push.)

Add the eight Projects v2 REST endpoints that were present in the OpenAPI
description but not yet implemented, completing the ProjectsService surface:

- Create draft item (organization and user owned projects)
- Add field (organization and user owned projects)
- Create view (organization and user owned projects)
- List items for a project view (organization and user owned projects)

Drafts and view items reuse ProjectV2Item, fields reuse ProjectV2Field, and
a new ProjectV2View type plus request types for the create/add operations
are added. Request bodies are passed by value with a Request suffix per the
paramcheck convention.

Note the user-owned draft and view endpoints identify the user by the numeric
user_id in the path (/user/{user_id}/... and /users/{user_id}/...), mirroring
the upstream REST API, unlike the username-based paths used by the other user
endpoints.

Fixes google#3715
Add subtests for the non-array, unsupported and non-numeric string
field_id, and non-string direction branches of
ProjectV2ViewSortBy.UnmarshalJSON, bringing the method (and MarshalJSON)
to 100% coverage.
Per review, required request-body fields should not be pointers. The
OpenAPI required arrays mark title (draft create) and name/layout (view
create) as required, and a single-select option's name is its required
display label, so make those non-pointer values without omitempty.

AddProjectV2FieldRequest stays all-pointer because its fields are
required only conditionally (the body is a oneOf), as do the optional
fields and the response types.
Replace the hand-written field-by-field assertions in the new draft,
field, view and view-item method tests with a single cmp.Diff against a
fully-populated want struct, which also verifies every decoded field.
Per review (the consensus from google#4202 that required response fields
should be non-pointer), make ProjectV2View's required scalar fields
non-pointer: id, number, name, layout, node_id, project_url and html_url.
Optional (filter), nested struct (creator), timestamp and slice fields
keep their existing pointer/slice forms.
Per review, drop the redundant "ProjectV2ViewSortBy: " prefix from the
UnmarshalJSON error messages (and wrap err2, the actual string-parse
error, in that branch), and remove the "(Required.)" notes from the
CreateProjectV2ViewRequest Name and Layout field comments.
Per review, drop the inconsistent per-field comments from ProjectV2View
so the struct matches the sibling ProjectV2 (no per-field comments), and
add an explicit json:"-" tag to ProjectV2ViewSortBy's FieldID and
Direction fields, which are (de)serialized through custom MarshalJSON /
UnmarshalJSON rather than struct tags.
Per review, remove the parenthetical (Required.)/(Optional.) annotations
from CreateProjectV2DraftItemRequest.Title/Body and the single-select
option Name comments; the value/pointer type already conveys this. The
conditional notes on AddProjectV2FieldRequest (required for single_select
/ iteration) are kept since they describe the one-of constraint.
Per the OpenAPI schema, creator, created_at, updated_at, visible_fields,
sort_by, group_by and vertical_group_by are required on the project view
response, so they should be non-pointer values without omitempty (matching
the convention that only optional fields are pointers). The scalar fields
were already converted; this does the same for the object, timestamp and
slice fields. Filter stays a pointer as it is optional.

Accessors are regenerated accordingly.
@JamBalaya56562 JamBalaya56562 force-pushed the feat/3715-projects-v2-remaining branch from 2aee530 to 10bea9e Compare June 24, 2026 00:53
Comment thread github/projects_test.go
})
}

func TestProjectV2ViewSortBy_UnmarshalJSON(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor this test in the same way as in #4323

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ac5bf32 — the success case now uses testJSONUnmarshalOnly like #4323. The error-case subtests stay manual since the helper expects a successful unmarshal.

Use the testJSONUnmarshalOnly helper for the success case of
TestProjectV2ViewSortBy_UnmarshalJSON instead of unmarshaling by hand
and asserting field by field, matching the style introduced in google#4323.
The error-case subtests stay manual since the helper expects a
successful unmarshal.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NeedsReview PR is awaiting a review before merging.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for projects v2

5 participants