diff --git a/github/event_types.go b/github/event_types.go index 80b0cf0485b..2ee6f2ed7a4 100644 --- a/github/event_types.go +++ b/github/event_types.go @@ -1179,15 +1179,18 @@ type FieldValue struct { // ProjectV2Item represents an item belonging to a project. type ProjectV2Item struct { - ID *int64 `json:"id,omitempty"` - NodeID *string `json:"node_id,omitempty"` - ProjectNodeID *string `json:"project_node_id,omitempty"` - ContentNodeID *string `json:"content_node_id,omitempty"` - ContentType *string `json:"content_type,omitempty"` - Creator *User `json:"creator,omitempty"` - CreatedAt *Timestamp `json:"created_at,omitempty"` - UpdatedAt *Timestamp `json:"updated_at,omitempty"` - ArchivedAt *Timestamp `json:"archived_at,omitempty"` + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + ProjectNodeID *string `json:"project_node_id,omitempty"` + ContentNodeID *string `json:"content_node_id,omitempty"` + ProjectURL *string `json:"project_url,omitempty"` + ContentType *string `json:"content_type,omitempty"` + Creator *User `json:"creator,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + ArchivedAt *Timestamp `json:"archived_at,omitempty"` + ItemURL *string `json:"item_url,omitempty"` + Fields []*ProjectV2Field `json:"fields,omitempty"` } // PublicEvent is triggered when a private repository is open sourced. diff --git a/github/github-accessors.go b/github/github-accessors.go index 6f03afa99ba..6ad78d26f05 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -15198,6 +15198,14 @@ func (n *NetworkSettingsResource) GetSubnetID() string { return *n.SubnetID } +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (n *NewProjectV2Field) GetID() int64 { + if n == nil || n.ID == nil { + return 0 + } + return *n.ID +} + // GetBase returns the Base field if it's non-nil, zero value otherwise. func (n *NewPullRequest) GetBase() string { if n == nil || n.Base == nil { @@ -18638,6 +18646,38 @@ func (p *ProjectV2Event) GetSender() *User { return p.Sender } +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetCreatedAt() Timestamp { + if p == nil || p.CreatedAt == nil { + return Timestamp{} + } + return *p.CreatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. +func (p *ProjectV2Field) GetUpdatedAt() Timestamp { + if p == nil || p.UpdatedAt == nil { + return Timestamp{} + } + return *p.UpdatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *ProjectV2FieldOption) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + // GetArchivedAt returns the ArchivedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Item) GetArchivedAt() Timestamp { if p == nil || p.ArchivedAt == nil { @@ -18686,6 +18726,14 @@ func (p *ProjectV2Item) GetID() int64 { return *p.ID } +// GetItemURL returns the ItemURL field if it's non-nil, zero value otherwise. +func (p *ProjectV2Item) GetItemURL() string { + if p == nil || p.ItemURL == nil { + return "" + } + return *p.ItemURL +} + // GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. func (p *ProjectV2Item) GetNodeID() string { if p == nil || p.NodeID == nil { @@ -18702,6 +18750,14 @@ func (p *ProjectV2Item) GetProjectNodeID() string { return *p.ProjectNodeID } +// GetProjectURL returns the ProjectURL field if it's non-nil, zero value otherwise. +func (p *ProjectV2Item) GetProjectURL() string { + if p == nil || p.ProjectURL == nil { + return "" + } + return *p.ProjectURL +} + // GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. func (p *ProjectV2Item) GetUpdatedAt() Timestamp { if p == nil || p.UpdatedAt == nil { @@ -28326,6 +28382,14 @@ func (u *UpdateEnterpriseRunnerGroupRequest) GetVisibility() string { return *u.Visibility } +// GetArchived returns the Archived field if it's non-nil, zero value otherwise. +func (u *UpdateProjectItemOptions) GetArchived() bool { + if u == nil || u.Archived == nil { + return false + } + return *u.Archived +} + // GetForce returns the Force field if it's non-nil, zero value otherwise. func (u *UpdateRef) GetForce() bool { if u == nil || u.Force == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 59282ed1f07..12b8b5e2e4b 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -19699,6 +19699,17 @@ func TestNetworkSettingsResource_GetSubnetID(tt *testing.T) { n.GetSubnetID() } +func TestNewProjectV2Field_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + n := &NewProjectV2Field{ID: &zeroValue} + n.GetID() + n = &NewProjectV2Field{} + n.GetID() + n = nil + n.GetID() +} + func TestNewPullRequest_GetBase(tt *testing.T) { tt.Parallel() var zeroValue string @@ -24180,6 +24191,50 @@ func TestProjectV2Event_GetSender(tt *testing.T) { p.GetSender() } +func TestProjectV2Field_GetCreatedAt(tt *testing.T) { + tt.Parallel() + var zeroValue Timestamp + p := &ProjectV2Field{CreatedAt: &zeroValue} + p.GetCreatedAt() + p = &ProjectV2Field{} + p.GetCreatedAt() + p = nil + p.GetCreatedAt() +} + +func TestProjectV2Field_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + p := &ProjectV2Field{ID: &zeroValue} + p.GetID() + p = &ProjectV2Field{} + p.GetID() + p = nil + p.GetID() +} + +func TestProjectV2Field_GetUpdatedAt(tt *testing.T) { + tt.Parallel() + var zeroValue Timestamp + p := &ProjectV2Field{UpdatedAt: &zeroValue} + p.GetUpdatedAt() + p = &ProjectV2Field{} + p.GetUpdatedAt() + p = nil + p.GetUpdatedAt() +} + +func TestProjectV2FieldOption_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + p := &ProjectV2FieldOption{ID: &zeroValue} + p.GetID() + p = &ProjectV2FieldOption{} + p.GetID() + p = nil + p.GetID() +} + func TestProjectV2Item_GetArchivedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -24243,6 +24298,17 @@ func TestProjectV2Item_GetID(tt *testing.T) { p.GetID() } +func TestProjectV2Item_GetItemURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Item{ItemURL: &zeroValue} + p.GetItemURL() + p = &ProjectV2Item{} + p.GetItemURL() + p = nil + p.GetItemURL() +} + func TestProjectV2Item_GetNodeID(tt *testing.T) { tt.Parallel() var zeroValue string @@ -24265,6 +24331,17 @@ func TestProjectV2Item_GetProjectNodeID(tt *testing.T) { p.GetProjectNodeID() } +func TestProjectV2Item_GetProjectURL(tt *testing.T) { + tt.Parallel() + var zeroValue string + p := &ProjectV2Item{ProjectURL: &zeroValue} + p.GetProjectURL() + p = &ProjectV2Item{} + p.GetProjectURL() + p = nil + p.GetProjectURL() +} + func TestProjectV2Item_GetUpdatedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -36466,6 +36543,17 @@ func TestUpdateEnterpriseRunnerGroupRequest_GetVisibility(tt *testing.T) { u.GetVisibility() } +func TestUpdateProjectItemOptions_GetArchived(tt *testing.T) { + tt.Parallel() + var zeroValue bool + u := &UpdateProjectItemOptions{Archived: &zeroValue} + u.GetArchived() + u = &UpdateProjectItemOptions{} + u.GetArchived() + u = nil + u.GetArchived() +} + func TestUpdateRef_GetForce(tt *testing.T) { tt.Parallel() var zeroValue bool diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index b246f0bc35a..79998b56960 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -1477,6 +1477,39 @@ func TestPreReceiveHook_String(t *testing.T) { } } +func TestProjectV2_String(t *testing.T) { + t.Parallel() + v := ProjectV2{ + ID: Ptr(int64(0)), + NodeID: Ptr(""), + Owner: &User{}, + Creator: &User{}, + Title: Ptr(""), + Description: Ptr(""), + Public: Ptr(false), + ClosedAt: &Timestamp{}, + CreatedAt: &Timestamp{}, + UpdatedAt: &Timestamp{}, + DeletedAt: &Timestamp{}, + Number: Ptr(0), + ShortDescription: Ptr(""), + DeletedBy: &User{}, + URL: Ptr(""), + HTMLURL: Ptr(""), + ColumnsURL: Ptr(""), + OwnerURL: Ptr(""), + Name: Ptr(""), + Body: Ptr(""), + State: Ptr(""), + OrganizationPermission: Ptr(""), + Private: Ptr(false), + } + want := `github.ProjectV2{ID:0, NodeID:"", Owner:github.User{}, Creator:github.User{}, Title:"", Description:"", Public:false, ClosedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, DeletedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Number:0, ShortDescription:"", DeletedBy:github.User{}, URL:"", HTMLURL:"", ColumnsURL:"", OwnerURL:"", Name:"", Body:"", State:"", OrganizationPermission:"", Private:false}` + if got := v.String(); got != want { + t.Errorf("ProjectV2.String = %v, want %v", got, want) + } +} + func TestPullRequest_String(t *testing.T) { t.Parallel() v := PullRequest{ diff --git a/github/github.go b/github/github.go index 9a1272537b9..baa11b7b893 100644 --- a/github/github.go +++ b/github/github.go @@ -218,6 +218,7 @@ type Client struct { Meta *MetaService Migrations *MigrationService Organizations *OrganizationsService + Projects *ProjectsService PullRequests *PullRequestsService RateLimit *RateLimitService Reactions *ReactionsService @@ -456,6 +457,7 @@ func (c *Client) initialize() { c.Meta = (*MetaService)(&c.common) c.Migrations = (*MigrationService)(&c.common) c.Organizations = (*OrganizationsService)(&c.common) + c.Projects = (*ProjectsService)(&c.common) c.PullRequests = (*PullRequestsService)(&c.common) c.RateLimit = (*RateLimitService)(&c.common) c.Reactions = (*ReactionsService)(&c.common) diff --git a/github/orgs_attestations.go b/github/orgs_attestations.go index d0ac123e2cf..1a7a1d5c966 100644 --- a/github/orgs_attestations.go +++ b/github/orgs_attestations.go @@ -14,7 +14,7 @@ import ( // with a given subject digest that are associated with repositories // owned by an organization. // -// GitHub API docs: https://docs.github.com/rest/orgs/orgs#list-attestations +// GitHub API docs: https://docs.github.com/rest/orgs/attestations#list-attestations // //meta:operation GET /orgs/{org}/attestations/{subject_digest} func (s *OrganizationsService) ListAttestations(ctx context.Context, org, subjectDigest string, opts *ListOptions) (*AttestationsResponse, *Response, error) { diff --git a/github/projects.go b/github/projects.go new file mode 100644 index 00000000000..178d18b54d1 --- /dev/null +++ b/github/projects.go @@ -0,0 +1,412 @@ +// Copyright 2025 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// ProjectsService handles communication with the project V2 +// methods of the GitHub API. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects +type ProjectsService service + +func (p ProjectV2) String() string { return Stringify(p) } + +// ListProjectsOptions specifies optional parameters to list projects for user / organization. +// +// Note: Pagination is powered by before/after cursor-style pagination. After the initial call, +// inspect the returned *Response. Use resp.After as the opts.After value to request +// the next page, and resp.Before as the opts.Before value to request the previous +// page. Set either Before or After for a request; if both are +// supplied GitHub API will return an error. PerPage controls the number of items +// per page (max 100 per GitHub API docs). +type ListProjectsOptions struct { + // A cursor, as given in the Link header. If specified, the query only searches for events before this cursor. + Before string `url:"before,omitempty"` + + // A cursor, as given in the Link header. If specified, the query only searches for events after this cursor. + After string `url:"after,omitempty"` + + // For paginated result sets, the number of results to include per page. + PerPage int `url:"per_page,omitempty"` + + // Q is an optional query string to limit results to projects of the specified type. + Query string `url:"q,omitempty"` +} + +// ProjectV2FieldOption represents an option for a project field of type single_select or multi_select. +// It defines the available choices that can be selected for dropdown-style fields. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields +type ProjectV2FieldOption struct { + ID *int64 `json:"id,omitempty"` // The unique identifier for this option. + Name string `json:"name,omitempty"` // The display name of the option. + Color string `json:"color,omitempty"` // The color associated with this option (e.g., "blue", "red"). + Description string `json:"description,omitempty"` // An optional description for this option. +} + +// ProjectV2Field represents a field in a GitHub Projects V2 project. +// Fields define the structure and data types for project items. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields +type ProjectV2Field struct { + ID *int64 `json:"id,omitempty"` // The unique identifier for this field. + NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field. + Name string `json:"name,omitempty"` // The display name of the field. + DataType string `json:"dataType,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select"). + URL string `json:"url,omitempty"` // The API URL for this field. + Options []*ProjectV2FieldOption `json:"options,omitempty"` // Available options for single_select and multi_select fields. + CreatedAt *Timestamp `json:"created_at,omitempty"` // The time when this field was created. + UpdatedAt *Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated. +} + +// NewProjectV2Field represents a new field to be added to a GitHub Projects V2. +type NewProjectV2Field struct { + ID *int64 `json:"id,omitempty"` // The unique identifier for this field. + Value any `json:"value,omitempty"` // The value of the field. +} + +// ListProjectsForOrg lists Projects V2 for an organization. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-organization +// +//meta:operation GET /orgs/{org}/projectsV2 +func (s *ProjectsService) ListProjectsForOrg(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2", org) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var projects []*ProjectV2 + resp, err := s.client.Do(ctx, req, &projects) + if err != nil { + return nil, resp, err + } + return projects, resp, nil +} + +// GetProjectForOrg gets a Projects V2 project for an organization by ID. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization +// +//meta:operation GET /orgs/{org}/projectsV2/{project_number} +func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectNumber int64) (*ProjectV2, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectNumber) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + project := new(ProjectV2) + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + return project, resp, nil +} + +// ListProjectsForUser lists Projects V2 for a user. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-user +// +//meta:operation GET /users/{username}/projectsV2 +func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2", username) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var projects []*ProjectV2 + resp, err := s.client.Do(ctx, req, &projects) + if err != nil { + return nil, resp, err + } + return projects, resp, nil +} + +// GetProjectForUser gets a Projects V2 project for a user by ID. +// +// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-user +// +//meta:operation GET /users/{username}/projectsV2/{project_number} +func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int64) (*ProjectV2, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectNumber) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + project := new(ProjectV2) + resp, err := s.client.Do(ctx, req, project) + if err != nil { + return nil, resp, err + } + return project, resp, nil +} + +// ListProjectFieldsForOrg lists Projects V2 for an organization. +// +// GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization +// +//meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields +func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var fields []*ProjectV2Field + resp, err := s.client.Do(ctx, req, &fields) + if err != nil { + return nil, resp, err + } + return fields, resp, nil +} + +// ListProjectItemsOptions specifies optional parameters when listing project items. +// Note: Pagination uses before/after cursor-style pagination similar to ListProjectsOptions. +// "Fields" can be used to restrict which field values are returned (by their numeric IDs). +type ListProjectItemsOptions struct { + // Embed ListProjectsOptions to reuse pagination and query parameters. + ListProjectsOptions + // Fields restricts which field values are returned by numeric field IDs. + Fields []int64 `url:"fields,omitempty,comma"` +} + +// GetProjectItemOptions specifies optional parameters when getting a project item. +type GetProjectItemOptions struct { + // Fields restricts which field values are returned by numeric field IDs. + Fields []int64 `url:"fields,omitempty,comma"` +} + +// AddProjectItemOptions represents the payload to add an item (issue or pull request) +// to a project. The Type must be either "Issue" or "PullRequest" (as per API docs) and +// ID is the numerical ID of that issue or pull request. +type AddProjectItemOptions struct { + Type string `json:"type,omitempty"` + ID int64 `json:"id,omitempty"` +} + +// UpdateProjectItemOptions represents fields that can be modified for a project item. +// Currently the REST API allows archiving/unarchiving an item (archived boolean). +// This struct can be expanded in the future as the API grows. +type UpdateProjectItemOptions struct { + // Archived indicates whether the item should be archived (true) or unarchived (false). + Archived *bool `json:"archived,omitempty"` + // Fields allows updating field values for the item. Each entry supplies a field ID and a value. + Fields []*NewProjectV2Field `json:"fields,omitempty"` +} + +// ListProjectItemsForOrg lists items for an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#list-items-for-an-organization-owned-project +// +//meta:operation GET /orgs/{org}/projectsV2/{project_number}/items +func (s *ProjectsService) ListProjectItemsForOrg(ctx context.Context, org string, projectNumber int64, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var items []*ProjectV2Item + resp, err := s.client.Do(ctx, req, &items) + if err != nil { + return nil, resp, err + } + return items, resp, nil +} + +// AddProjectItemForOrg adds an issue or pull request item to an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#add-item-to-organization-owned-project +// +//meta:operation POST /orgs/{org}/projectsV2/{project_number}/items +func (s *ProjectsService) AddProjectItemForOrg(ctx context.Context, org string, projectNumber int64, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) + req, err := s.client.NewRequest("POST", u, opts) + if err != nil { + return nil, nil, err + } + + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// GetProjectItemForOrg gets a single item from an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-an-organization-owned-project +// +//meta:operation GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) GetProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) + req, err := s.client.NewRequest("GET", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// UpdateProjectItemForOrg updates an item in an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-organization +// +//meta:operation PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) UpdateProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) + req, err := s.client.NewRequest("PATCH", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// DeleteProjectItemForOrg deletes an item from an organization owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-organization +// +//meta:operation DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) DeleteProjectItemForOrg(ctx context.Context, org string, projectNumber, itemID int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// ListProjectItemsForUser lists items for a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#list-items-for-a-user-owned-project +// +//meta:operation GET /users/{username}/projectsV2/{project_number}/items +func (s *ProjectsService) ListProjectItemsForUser(ctx context.Context, username string, projectNumber int64, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + var items []*ProjectV2Item + resp, err := s.client.Do(ctx, req, &items) + if err != nil { + return nil, resp, err + } + return items, resp, nil +} + +// AddProjectItemForUser adds an issue or pull request item to a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#add-item-to-user-owned-project +// +//meta:operation POST /users/{username}/projectsV2/{project_number}/items +func (s *ProjectsService) AddProjectItemForUser(ctx context.Context, username string, projectNumber int64, opts *AddProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) + req, err := s.client.NewRequest("POST", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// GetProjectItemForUser gets a single item from a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#get-an-item-for-a-user-owned-project +// +//meta:operation GET /users/{username}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) GetProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) + req, err := s.client.NewRequest("GET", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// UpdateProjectItemForUser updates an item in a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#update-project-item-for-user +// +//meta:operation PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) UpdateProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64, opts *UpdateProjectItemOptions) (*ProjectV2Item, *Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) + req, err := s.client.NewRequest("PATCH", u, opts) + if err != nil { + return nil, nil, err + } + item := new(ProjectV2Item) + resp, err := s.client.Do(ctx, req, item) + if err != nil { + return nil, resp, err + } + return item, resp, nil +} + +// DeleteProjectItemForUser deletes an item from a user owned project. +// +// GitHub API docs: https://docs.github.com/rest/projects/items#delete-project-item-for-user +// +//meta:operation DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} +func (s *ProjectsService) DeleteProjectItemForUser(ctx context.Context, username string, projectNumber, itemID int64) (*Response, error) { + u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/github/projects_test.go b/github/projects_test.go new file mode 100644 index 00000000000..91427ef1b4e --- /dev/null +++ b/github/projects_test.go @@ -0,0 +1,799 @@ +// Copyright 2025 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "io" + "net/http" + "testing" +) + +func TestProjectsService_ListProjectsForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + // Combined handler: supports initial test case and dual before/after validation scenario. + mux.HandleFunc("/orgs/o/projectsV2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + q := r.URL.Query() + if q.Get("before") == "b" && q.Get("after") == "a" { + fmt.Fprint(w, `[]`) + return + } + // default expectation for main part of test + testFormValues(t, r, values{"q": "alpha", "after": "2", "before": "1"}) + fmt.Fprint(w, `[{"id":1,"title":"T1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + }) + + opts := &ListProjectsOptions{Query: "alpha", After: "2", Before: "1"} + ctx := context.Background() + projects, _, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) + if err != nil { + t.Fatalf("Projects.ListProjectsForOrg returned error: %v", err) + } + if len(projects) != 1 || projects[0].GetID() != 1 || projects[0].GetTitle() != "T1" { + t.Fatalf("Projects.ListProjectsForOrg returned %+v", projects) + } + + const methodName = "ListProjectsForOrg" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectsForOrg(ctx, "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + + // still allow both set (no validation enforced) – ensure it does not error + ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectsForOrg(ctxBypass, "o", &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + t.Fatalf("unexpected error when both before/after set: %v", err) + } +} + +func TestProjectsService_GetProjectForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1,"title":"OrgProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) + }) + + ctx := context.Background() + project, _, err := client.Projects.GetProjectForOrg(ctx, "o", 1) + if err != nil { + t.Fatalf("Projects.GetProjectForOrg returned error: %v", err) + } + if project.GetID() != 1 || project.GetTitle() != "OrgProj" { + t.Fatalf("Projects.GetProjectForOrg returned %+v", project) + } + + const methodName = "GetProjectForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectForOrg(ctx, "o", 1) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_GetProjectForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/users/u/projectsV2/3", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":3,"title":"UserProj","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}`) + }) + + ctx := context.Background() + project, _, err := client.Projects.GetProjectForUser(ctx, "u", 3) + if err != nil { + t.Fatalf("Projects.GetProjectForUser returned error: %v", err) + } + if project.GetID() != 3 || project.GetTitle() != "UserProj" { + t.Fatalf("Projects.GetProjectForUser returned %+v", project) + } + + const methodName = "GetProjectForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectForUser(ctx, "u", 3) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_ListProjectFieldsForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2/1/fields", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + q := r.URL.Query() + if q.Get("before") == "b" && q.Get("after") == "a" { // bypass scenario + fmt.Fprint(w, `[]`) + return + } + testFormValues(t, r, values{"after": "2", "before": "1", "q": "text"}) + fmt.Fprint(w, `[ + { + "id": 1, + "node_id": "node_1", + "name": "Status", + "dataType": "single_select", + "url": "https://api.github.com/projects/1/fields/field1", + "options": [ + {"id": 1, "name": "Todo", "color": "blue", "description": "Tasks to be done"}, + {"id": 2, "name": "In Progress", "color": "yellow"} + ], + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + }, + { + "id": 2, + "node_id": "node_2", + "name": "Priority", + "dataType": "text", + "url": "https://api.github.com/projects/1/fields/field2", + "created_at": "2011-01-02T15:04:05Z", + "updated_at": "2012-01-02T15:04:05Z" + } + ]`) + }) + + opts := &ListProjectsOptions{Query: "text", After: "2", Before: "1"} + ctx := context.Background() + fields, _, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) + if err != nil { + t.Fatalf("Projects.ListProjectFieldsForOrg returned error: %v", err) + } + if len(fields) != 2 { + t.Fatalf("Projects.ListProjectFieldsForOrg returned %d fields, want 2", len(fields)) + } + if fields[0].ID == nil || *fields[0].ID != 1 || fields[1].ID == nil || *fields[1].ID != 2 { + t.Fatalf("unexpected field IDs: %+v", fields) + } + + const methodName = "ListProjectFieldsForOrg" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectFieldsForOrg(ctx, "\n", 1, opts) + return err + }) + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectFieldsForOrg(ctxBypass, "o", 1, &ListProjectsOptions{Before: "b", After: "a"}); err != nil { + t.Fatalf("unexpected error when both before/after set: %v", err) + } +} + +func TestProjectsService_ListProjectsForUser_pagination(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + after := q.Get("after") + before := q.Get("before") + if after == "" && before == "" { + w.Header().Set("Link", "; rel=\"next\"") + fmt.Fprint(w, `[{"id":10,"title":"UP1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + if after == "ucursor2" { + w.Header().Set("Link", "; rel=\"prev\"") + fmt.Fprint(w, `[{"id":11,"title":"UP2","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + http.Error(w, "unexpected query", http.StatusBadRequest) + }) + ctx := context.Background() + first, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) + if err != nil { + t.Fatalf("first page error: %v", err) + } + if len(first) != 1 || first[0].GetID() != 10 { + t.Fatalf("unexpected first page %+v", first) + } + if resp.After != "ucursor2" { + t.Fatalf("expected resp.After=ucursor2 got %q", resp.After) + } + opts := &ListProjectsOptions{After: resp.After} + second, resp2, err := client.Projects.ListProjectsForUser(ctx, "u", opts) + if err != nil { + t.Fatalf("second page error: %v", err) + } + if len(second) != 1 || second[0].GetID() != 11 { + t.Fatalf("unexpected second page %+v", second) + } + if resp2.Before != "ucursor2" { + t.Fatalf("expected resp2.Before=ucursor2 got %q", resp2.Before) + } +} + +func TestProjectsService_ListProjectsForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[]`) + }) + ctx := context.Background() + const methodName = "ListProjectsForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectsForUser(ctx, "u", nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + // bad options (bad username) should error + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectsForUser(ctx, "\n", nil) + return err + }) +} + +func TestProjectsService_ListProjectFieldsForOrg_pagination(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + // First page returns a Link header with rel="next" containing an after cursor + mux.HandleFunc("/orgs/o/projectsV2/1/fields", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + after := q.Get("after") + before := q.Get("before") + if after == "" && before == "" { + // first request + w.Header().Set("Link", "; rel=\"next\"") + fmt.Fprint(w, `[{"id":1,"name":"Status","dataType":"single_select","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + if after == "cursor2" { + // second request simulates a previous link + w.Header().Set("Link", "; rel=\"prev\"") + fmt.Fprint(w, `[{"id":2,"name":"Priority","dataType":"text","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + // unexpected state + http.Error(w, "unexpected query", http.StatusBadRequest) + }) + + ctx := context.Background() + first, resp, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, nil) + if err != nil { + t.Fatalf("first page error: %v", err) + } + if len(first) != 1 || first[0].ID == nil || *first[0].ID != 1 { + t.Fatalf("unexpected first page %+v", first) + } + if resp.After != "cursor2" { + t.Fatalf("expected resp.After=cursor2 got %q", resp.After) + } + + // Use resp.After as opts.After for next page + opts := &ListProjectsOptions{After: resp.After} + second, resp2, err := client.Projects.ListProjectFieldsForOrg(ctx, "o", 1, opts) + if err != nil { + t.Fatalf("second page error: %v", err) + } + if len(second) != 1 || second[0].ID == nil || *second[0].ID != 2 { + t.Fatalf("unexpected second page %+v", second) + } + if resp2.Before != "cursor2" { + t.Fatalf("expected resp2.Before=cursor2 got %q", resp2.Before) + } +} + +func TestProjectsService_ListProjectsForOrg_pagination(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + after := q.Get("after") + before := q.Get("before") + if after == "" && before == "" { + w.Header().Set("Link", "; rel=\"next\"") + fmt.Fprint(w, `[{"id":20,"title":"OP1","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + if after == "ocursor2" { + w.Header().Set("Link", "; rel=\"prev\"") + fmt.Fprint(w, `[{"id":21,"title":"OP2","created_at":"2011-01-02T15:04:05Z","updated_at":"2012-01-02T15:04:05Z"}]`) + return + } + http.Error(w, "unexpected query", http.StatusBadRequest) + }) + + ctx := context.Background() + first, resp, err := client.Projects.ListProjectsForOrg(ctx, "o", nil) + if err != nil { + t.Fatalf("first page error: %v", err) + } + if len(first) != 1 || first[0].GetID() != 20 { + t.Fatalf("unexpected first page %+v", first) + } + if resp.After != "ocursor2" { + t.Fatalf("expected resp.After=ocursor2 got %q", resp.After) + } + + opts := &ListProjectsOptions{After: resp.After} + second, resp2, err := client.Projects.ListProjectsForOrg(ctx, "o", opts) + if err != nil { + t.Fatalf("second page error: %v", err) + } + if len(second) != 1 || second[0].GetID() != 21 { + t.Fatalf("unexpected second page %+v", second) + } + if resp2.Before != "ocursor2" { + t.Fatalf("expected resp2.Before=ocursor2 got %q", resp2.Before) + } +} + +// Marshal test ensures V2 fields marshal correctly. +func TestProjectV2_Marshal(t *testing.T) { + t.Parallel() + testJSONMarshal(t, &ProjectV2{}, "{}") + + p := &ProjectV2{ + ID: Ptr(int64(10)), + Title: Ptr("Title"), + Description: Ptr("Desc"), + Public: Ptr(true), + CreatedAt: &Timestamp{referenceTime}, + UpdatedAt: &Timestamp{referenceTime}, + } + + want := `{ + "id": 10, + "title": "Title", + "description": "Desc", + "public": true, + "created_at": ` + referenceTimeStr + `, + "updated_at": ` + referenceTimeStr + ` + }` + + testJSONMarshal(t, p, want) +} + +// Marshal test ensures V2 field structures marshal correctly. +func TestProjectV2Field_Marshal(t *testing.T) { + t.Parallel() + testJSONMarshal(t, &ProjectV2Field{}, "{}") + testJSONMarshal(t, &ProjectV2FieldOption{}, "{}") + + field := &ProjectV2Field{ + ID: Ptr(int64(2)), + NodeID: "node_1", + Name: "Status", + DataType: "single_select", + URL: "https://api.github.com/projects/1/fields/field1", + Options: []*ProjectV2FieldOption{ + { + ID: Ptr(int64(1)), + Name: "Todo", + Color: "blue", + Description: "Tasks to be done", + }, + }, + CreatedAt: &Timestamp{referenceTime}, + UpdatedAt: &Timestamp{referenceTime}, + } + + want := `{ + "id": 2, + "node_id": "node_1", + "name": "Status", + "dataType": "single_select", + "url": "https://api.github.com/projects/1/fields/field1", + "options": [ + { + "id": 1, + "name": "Todo", + "color": "blue", + "description": "Tasks to be done" + } + ], + "created_at": ` + referenceTimeStr + `, + "updated_at": ` + referenceTimeStr + ` + }` + + testJSONMarshal(t, field, want) +} + +func TestProjectsService_ListProjectItemsForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2/1/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + q := r.URL.Query() + if q.Get("before") == "b" && q.Get("after") == "a" { // bypass scenario + fmt.Fprint(w, `[]`) + return + } + testFormValues(t, r, values{"after": "2", "before": "1", "per_page": "50", "fields": "10,11", "q": "status:open"}) + fmt.Fprint(w, `[{"id":17,"node_id":"PVTI_node"}]`) + }) + + opts := &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{After: "2", Before: "1", PerPage: 50, Query: "status:open"}, Fields: []int64{10, 11}} + ctx := context.Background() + items, _, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) + if err != nil { + t.Fatalf("Projects.ListProjectItemsForOrg returned error: %v", err) + } + if len(items) != 1 || items[0].GetID() != 17 { + t.Fatalf("Projects.ListProjectItemsForOrg returned %+v", items) + } + + const methodName = "ListProjectItemsForOrg" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectItemsForOrg(ctx, "\n", 1, opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectItemsForOrg(ctx, "o", 1, opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + + ctxBypass := context.WithValue(context.Background(), BypassRateLimitCheck, true) + if _, _, err = client.Projects.ListProjectItemsForOrg(ctxBypass, "o", 1, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{Before: "b", After: "a"}}); err != nil { + t.Fatalf("unexpected error when both before/after set: %v", err) + } +} + +func TestProjectsService_AddProjectItemForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/projectsV2/1/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + b, _ := io.ReadAll(r.Body) + body := string(b) + if body != `{"type":"Issue","id":99}`+"\n" { // encoder adds newline + t.Fatalf("unexpected body: %s", body) + } + fmt.Fprint(w, `{"id":99,"node_id":"PVTI_new"}`) + }) + + ctx := context.Background() + item, _, err := client.Projects.AddProjectItemForOrg(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 99}) + if err != nil { + t.Fatalf("Projects.AddProjectItemForOrg returned error: %v", err) + } + if item.GetID() != 99 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_AddProjectItemForOrg_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"id":1}`) + }) + ctx := context.Background() + const methodName = "AddProjectItemForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.AddProjectItemForOrg(ctx, "o", 1, &AddProjectItemOptions{Type: "Issue", ID: 1}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_GetProjectItemForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":17,"node_id":"PVTI_node"}`) + }) + ctx := context.Background() + opts := &GetProjectItemOptions{} + item, _, err := client.Projects.GetProjectItemForOrg(ctx, "o", 1, 17, opts) + if err != nil { + t.Fatalf("GetProjectItemForOrg error: %v", err) + } + if item.GetID() != 17 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_GetProjectItemForOrg_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":17}`) + }) + ctx := context.Background() + const methodName = "GetProjectItemForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectItemForOrg(ctx, "o", 1, 17, &GetProjectItemOptions{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_UpdateProjectItemForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + b, _ := io.ReadAll(r.Body) + body := string(b) + if body != `{"archived":true}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + fmt.Fprint(w, `{"id":17}`) + }) + archived := true + ctx := context.Background() + item, _, err := client.Projects.UpdateProjectItemForOrg(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) + if err != nil { + t.Fatalf("UpdateProjectItemForOrg error: %v", err) + } + if item.GetID() != 17 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_UpdateProjectItemForOrg_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + fmt.Fprint(w, `{"id":17}`) + }) + archived := true + ctx := context.Background() + const methodName = "UpdateProjectItemForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.UpdateProjectItemForOrg(ctx, "o", 1, 17, &UpdateProjectItemOptions{Archived: &archived}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_DeleteProjectItemForOrg(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + ctx := context.Background() + if _, err := client.Projects.DeleteProjectItemForOrg(ctx, "o", 1, 17); err != nil { + t.Fatalf("DeleteProjectItemForOrg error: %v", err) + } +} + +func TestProjectsService_DeleteProjectItemForOrg_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/orgs/o/projectsV2/1/items/17", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + ctx := context.Background() + const methodName = "DeleteProjectItemForOrg" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Projects.DeleteProjectItemForOrg(ctx, "o", 1, 17) + }) +} + +func TestProjectsService_ListProjectItemsForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"per_page": "20", "q": "type:issue"}) + fmt.Fprint(w, `[{"id":7,"node_id":"PVTI_user"}]`) + }) + ctx := context.Background() + items, _, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, &ListProjectItemsOptions{ListProjectsOptions: ListProjectsOptions{PerPage: 20, Query: "type:issue"}}) + if err != nil { + t.Fatalf("ListProjectItemsForUser error: %v", err) + } + if len(items) != 1 || items[0].GetID() != 7 { + t.Fatalf("unexpected items: %+v", items) + } +} + +func TestProjectsService_ListProjectItemsForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[]`) + }) + ctx := context.Background() + const methodName = "ListProjectItemsForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.ListProjectItemsForUser(ctx, "u", 2, nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Projects.ListProjectItemsForUser(ctx, "\n", 2, nil) + return err + }) +} + +func TestProjectsService_AddProjectItemForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + b, _ := io.ReadAll(r.Body) + body := string(b) + if body != `{"type":"PullRequest","id":123}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + fmt.Fprint(w, `{"id":123,"node_id":"PVTI_new_user"}`) + }) + ctx := context.Background() + item, _, err := client.Projects.AddProjectItemForUser(ctx, "u", 2, &AddProjectItemOptions{Type: "PullRequest", ID: 123}) + if err != nil { + t.Fatalf("AddProjectItemForUser error: %v", err) + } + if item.GetID() != 123 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_AddProjectItemForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id":5}`) + }) + ctx := context.Background() + const methodName = "AddProjectItemForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.AddProjectItemForUser(ctx, "u", 2, &AddProjectItemOptions{Type: "Issue", ID: 5}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_GetProjectItemForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":55,"node_id":"PVTI_user_item"}`) + }) + ctx := context.Background() + opts := &GetProjectItemOptions{} + item, _, err := client.Projects.GetProjectItemForUser(ctx, "u", 2, 55, opts) + if err != nil { + t.Fatalf("GetProjectItemForUser error: %v", err) + } + if item.GetID() != 55 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_GetProjectItemForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":55}`) + }) + ctx := context.Background() + const methodName = "GetProjectItemForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.GetProjectItemForUser(ctx, "u", 2, 55, &GetProjectItemOptions{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_UpdateProjectItemForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + b, _ := io.ReadAll(r.Body) + body := string(b) + if body != `{"archived":false}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + fmt.Fprint(w, `{"id":55}`) + }) + archived := false + ctx := context.Background() + item, _, err := client.Projects.UpdateProjectItemForUser(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) + if err != nil { + t.Fatalf("UpdateProjectItemForUser error: %v", err) + } + if item.GetID() != 55 { + t.Fatalf("unexpected item: %+v", item) + } +} + +func TestProjectsService_UpdateProjectItemForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + fmt.Fprint(w, `{"id":55}`) + }) + archived := false + ctx := context.Background() + const methodName = "UpdateProjectItemForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Projects.UpdateProjectItemForUser(ctx, "u", 2, 55, &UpdateProjectItemOptions{Archived: &archived}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestProjectsService_DeleteProjectItemForUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + ctx := context.Background() + if _, err := client.Projects.DeleteProjectItemForUser(ctx, "u", 2, 55); err != nil { + t.Fatalf("DeleteProjectItemForUser error: %v", err) + } +} + +func TestProjectsService_DeleteProjectItemForUser_error(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + mux.HandleFunc("/users/u/projectsV2/2/items/55", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + ctx := context.Background() + const methodName = "DeleteProjectItemForUser" + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Projects.DeleteProjectItemForUser(ctx, "u", 2, 55) + }) +} diff --git a/openapi_operations.yaml b/openapi_operations.yaml index d817be5af5a..053c19325b6 100644 --- a/openapi_operations.yaml +++ b/openapi_operations.yaml @@ -27,7 +27,7 @@ operation_overrides: documentation_url: https://docs.github.com/rest/pages/pages#request-a-github-pages-build - name: GET /repos/{owner}/{repo}/pages/builds/{build_id} documentation_url: https://docs.github.com/rest/pages/pages#get-github-pages-build -openapi_commit: 30ab35c5db4a05519ceed2e41292cdb7af182f1c +openapi_commit: 44dd20c107ca46d1dbcaf4aa522d9039e96e631c openapi_operations: - name: GET / documentation_url: https://docs.github.com/rest/meta/meta#github-api-root @@ -476,6 +476,14 @@ openapi_operations: documentation_url: https://docs.github.com/enterprise-server@3.17/rest/enterprise-admin/admin-stats#get-users-statistics openapi_files: - descriptions/ghes-3.17/ghes-3.17.json + - name: POST /enterprises/{enterprise}/access-restrictions/disable + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/enterprise-admin/enterprises#disable-access-restrictions-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/access-restrictions/enable + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/enterprise-admin/enterprises#enable-access-restrictions-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json - name: GET /enterprises/{enterprise}/actions/cache/usage documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/actions/cache#get-github-actions-cache-usage-for-an-enterprise openapi_files: @@ -827,6 +835,10 @@ openapi_operations: documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/enterprise-admin/bypass-requests#list-push-rule-bypass-requests-within-an-enterprise openapi_files: - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/bypass-requests/secret-scanning + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/secret-scanning/delegated-bypass#list-bypass-requests-for-secret-scanning-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json - name: GET /enterprises/{enterprise}/code-scanning/alerts documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/code-scanning/code-scanning#list-code-scanning-alerts-for-an-enterprise openapi_files: @@ -904,6 +916,22 @@ openapi_operations: documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#list-all-copilot-seat-assignments-for-an-enterprise openapi_files: - descriptions/ghec/ghec.json + - name: DELETE /enterprises/{enterprise}/copilot/billing/selected_enterprise_teams + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#remove-enterprise-teams-from-the-copilot-subscription-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/copilot/billing/selected_enterprise_teams + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#add-enterprise-teams-to-the-copilot-subscription-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json + - name: DELETE /enterprises/{enterprise}/copilot/billing/selected_users + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#remove-users-from-the-copilot-subscription-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/copilot/billing/selected_users + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-user-management#add-users-to-the-copilot-subscription-for-an-enterprise + openapi_files: + - descriptions/ghec/ghec.json - name: GET /enterprises/{enterprise}/copilot/metrics documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-metrics#get-copilot-metrics-for-an-enterprise openapi_files: @@ -1061,6 +1089,61 @@ openapi_operations: documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/copilot/copilot-metrics#get-copilot-metrics-for-an-enterprise-team openapi_files: - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/teams + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#list-enterprise-teams + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/teams + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#create-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#list-members-in-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/add + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#bulk-add-team-members + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/remove + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#bulk-remove-team-members + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: DELETE /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#remove-team-membership + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#get-enterprise-team-membership + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: PUT /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-team-members#add-team-member + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: DELETE /enterprises/{enterprise}/teams/{team_slug} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#delete-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /enterprises/{enterprise}/teams/{team_slug} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#get-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: PATCH /enterprises/{enterprise}/teams/{team_slug} + documentation_url: https://docs.github.com/rest/enterprise-teams/enterprise-teams#update-an-enterprise-team + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: POST /enterprises/{enterprise}/{security_product}/{enablement} documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/enterprise-admin/code-security-and-analysis#enable-or-disable-a-security-feature openapi_files: @@ -1939,8 +2022,18 @@ openapi_operations: openapi_files: - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json + - name: POST /orgs/{org}/artifacts/metadata/storage-record + documentation_url: https://docs.github.com/rest/orgs/artifact-metadata#create-artifact-metadata-storage-record + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/artifacts/{subject_digest}/metadata/storage-records + documentation_url: https://docs.github.com/rest/orgs/artifact-metadata#list-artifact-storage-records + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: POST /orgs/{org}/attestations/bulk-list - documentation_url: https://docs.github.com/rest/orgs/orgs#list-attestations-by-bulk-subject-digests + documentation_url: https://docs.github.com/rest/orgs/attestations#list-attestations-by-bulk-subject-digests openapi_files: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json @@ -1960,7 +2053,7 @@ openapi_operations: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - name: GET /orgs/{org}/attestations/{subject_digest} - documentation_url: https://docs.github.com/rest/orgs/orgs#list-attestations + documentation_url: https://docs.github.com/rest/orgs/attestations#list-attestations openapi_files: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json @@ -2877,6 +2970,51 @@ openapi_operations: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json + - name: GET /orgs/{org}/projectsV2 + documentation_url: https://docs.github.com/rest/projects/projects#list-projects-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number} + documentation_url: https://docs.github.com/rest/projects/projects#get-project-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number}/fields + documentation_url: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id} + documentation_url: https://docs.github.com/rest/projects/fields#get-project-field-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number}/items + documentation_url: https://docs.github.com/rest/projects/items#list-items-for-an-organization-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /orgs/{org}/projectsV2/{project_number}/items + documentation_url: https://docs.github.com/rest/projects/items#add-item-to-organization-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#delete-project-item-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#get-an-item-for-an-organization-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#update-project-item-for-organization + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: GET /orgs/{org}/properties/schema documentation_url: https://docs.github.com/rest/orgs/custom-properties#get-all-custom-properties-for-an-organization openapi_files: @@ -3349,29 +3487,25 @@ openapi_operations: - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json - name: DELETE /projects/columns/cards/{card_id} - documentation_url: https://docs.github.com/rest/projects-classic/cards#delete-a-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#delete-a-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: GET /projects/columns/cards/{card_id} - documentation_url: https://docs.github.com/rest/projects-classic/cards#get-a-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#get-a-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: PATCH /projects/columns/cards/{card_id} - documentation_url: https://docs.github.com/rest/projects-classic/cards#update-an-existing-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#update-an-existing-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: POST /projects/columns/cards/{card_id}/moves - documentation_url: https://docs.github.com/rest/projects-classic/cards#move-a-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#move-a-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: DELETE /projects/columns/{column_id} documentation_url: https://docs.github.com/rest/projects-classic/columns#delete-a-project-column openapi_files: @@ -3391,17 +3525,15 @@ openapi_operations: - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json - name: GET /projects/columns/{column_id}/cards - documentation_url: https://docs.github.com/rest/projects-classic/cards#list-project-cards + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#list-project-cards openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: POST /projects/columns/{column_id}/cards - documentation_url: https://docs.github.com/rest/projects-classic/cards#create-a-project-card + documentation_url: https://docs.github.com/enterprise-cloud@latest//rest/projects-classic/cards#create-a-project-card openapi_files: - - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - - descriptions/ghes-3.17/ghes-3.17.json + - descriptions/ghes-3.16/ghes-3.16.json - name: POST /projects/columns/{column_id}/moves documentation_url: https://docs.github.com/rest/projects-classic/columns#move-a-project-column openapi_files: @@ -5385,6 +5517,11 @@ openapi_operations: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json + - name: GET /repos/{owner}/{repo}/issues/{issue_number}/parent + documentation_url: https://docs.github.com/rest/issues/sub-issues#get-parent-issue + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: GET /repos/{owner}/{repo}/issues/{issue_number}/reactions documentation_url: https://docs.github.com/rest/reactions/reactions#list-reactions-for-an-issue openapi_files: @@ -7446,6 +7583,51 @@ openapi_operations: - descriptions/api.github.com/api.github.com.json - descriptions/ghec/ghec.json - descriptions/ghes-3.17/ghes-3.17.json + - name: GET /users/{username}/projectsV2 + documentation_url: https://docs.github.com/rest/projects/projects#list-projects-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number} + documentation_url: https://docs.github.com/rest/projects/projects#get-project-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number}/fields + documentation_url: https://docs.github.com/rest/projects/fields#list-project-fields-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number}/fields/{field_id} + documentation_url: https://docs.github.com/rest/projects/fields#get-project-field-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number}/items + documentation_url: https://docs.github.com/rest/projects/items#list-items-for-a-user-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: POST /users/{username}/projectsV2/{project_number}/items + documentation_url: https://docs.github.com/rest/projects/items#add-item-to-user-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#delete-project-item-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: GET /users/{username}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#get-an-item-for-a-user-owned-project + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json + - name: PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} + documentation_url: https://docs.github.com/rest/projects/items#update-project-item-for-user + openapi_files: + - descriptions/api.github.com/api.github.com.json + - descriptions/ghec/ghec.json - name: GET /users/{username}/received_events documentation_url: https://docs.github.com/rest/activity/events#list-events-received-by-the-authenticated-user openapi_files: