diff --git a/github/github-accessors.go b/github/github-accessors.go index ec029df2419..689d78633e9 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -5902,6 +5902,30 @@ func (c *CreateOrUpdateCustomRepoRoleOptions) GetName() string { return *c.Name } +// GetColor returns the Color field if it's non-nil, zero value otherwise. +func (c *CreateOrUpdateIssueTypesOptions) GetColor() string { + if c == nil || c.Color == nil { + return "" + } + return *c.Color +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (c *CreateOrUpdateIssueTypesOptions) GetDescription() string { + if c == nil || c.Description == nil { + return "" + } + return *c.Description +} + +// GetIsPrivate returns the IsPrivate field if it's non-nil, zero value otherwise. +func (c *CreateOrUpdateIssueTypesOptions) GetIsPrivate() bool { + if c == nil || c.IsPrivate == nil { + return false + } + return *c.IsPrivate +} + // GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise. func (c *CreateOrUpdateOrgRoleOptions) GetBaseRole() string { if c == nil || c.BaseRole == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index b2e4232950d..85647c6dd8c 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -7688,6 +7688,39 @@ func TestCreateOrUpdateCustomRepoRoleOptions_GetName(tt *testing.T) { c.GetName() } +func TestCreateOrUpdateIssueTypesOptions_GetColor(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateOrUpdateIssueTypesOptions{Color: &zeroValue} + c.GetColor() + c = &CreateOrUpdateIssueTypesOptions{} + c.GetColor() + c = nil + c.GetColor() +} + +func TestCreateOrUpdateIssueTypesOptions_GetDescription(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CreateOrUpdateIssueTypesOptions{Description: &zeroValue} + c.GetDescription() + c = &CreateOrUpdateIssueTypesOptions{} + c.GetDescription() + c = nil + c.GetDescription() +} + +func TestCreateOrUpdateIssueTypesOptions_GetIsPrivate(tt *testing.T) { + tt.Parallel() + var zeroValue bool + c := &CreateOrUpdateIssueTypesOptions{IsPrivate: &zeroValue} + c.GetIsPrivate() + c = &CreateOrUpdateIssueTypesOptions{} + c.GetIsPrivate() + c = nil + c.GetIsPrivate() +} + func TestCreateOrUpdateOrgRoleOptions_GetBaseRole(tt *testing.T) { tt.Parallel() var zeroValue string diff --git a/github/orgs_issue_types.go b/github/orgs_issue_types.go new file mode 100644 index 00000000000..73e6f8d639a --- /dev/null +++ b/github/orgs_issue_types.go @@ -0,0 +1,99 @@ +// 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" +) + +// CreateOrUpdateIssueTypesOptions represents the parameters for creating or updating an issue type. +type CreateOrUpdateIssueTypesOptions struct { + Name string `json:"name"` // Name of the issue type. (Required.) + IsEnabled bool `json:"is_enabled"` // Whether or not the issue type is enabled at the organization level. (Required.) + IsPrivate *bool `json:"is_private,omitempty"` // Whether or not the issue type is restricted to issues in private repositories. (Optional.) + Description *string `json:"description,omitempty"` // Description of the issue type. (Optional.) + Color *string `json:"color,omitempty"` // Color for the issue type. Can be one of "gray", "blue", green "orange", "red", "pink", "purple", "null". (Optional.) +} + +// ListIssueTypes lists all issue types for an organization. +// +// GitHub API docs: https://docs.github.com/rest/orgs/issue-types#list-issue-types-for-an-organization +// +//meta:operation GET /orgs/{org}/issue-types +func (s *OrganizationsService) ListIssueTypes(ctx context.Context, org string) ([]*IssueType, *Response, error) { + u := fmt.Sprintf("orgs/%v/issue-types", org) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var issueTypes []*IssueType + resp, err := s.client.Do(ctx, req, &issueTypes) + if err != nil { + return nil, resp, err + } + + return issueTypes, resp, nil +} + +// CreateIssueType creates a new issue type for an organization. +// +// GitHub API docs: https://docs.github.com/rest/orgs/issue-types#create-issue-type-for-an-organization +// +//meta:operation POST /orgs/{org}/issue-types +func (s *OrganizationsService) CreateIssueType(ctx context.Context, org string, opt *CreateOrUpdateIssueTypesOptions) (*IssueType, *Response, error) { + u := fmt.Sprintf("orgs/%v/issue-types", org) + req, err := s.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + issueType := new(IssueType) + resp, err := s.client.Do(ctx, req, issueType) + if err != nil { + return nil, resp, err + } + + return issueType, resp, nil +} + +// UpdateIssueType updates GitHub Pages for the named repo. +// +// GitHub API docs: https://docs.github.com/rest/orgs/issue-types#update-issue-type-for-an-organization +// +//meta:operation PUT /orgs/{org}/issue-types/{issue_type_id} +func (s *OrganizationsService) UpdateIssueType(ctx context.Context, org string, issueTypeID int64, opt *CreateOrUpdateIssueTypesOptions) (*IssueType, *Response, error) { + u := fmt.Sprintf("orgs/%v/issue-types/%v", org, issueTypeID) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + + issueType := new(IssueType) + resp, err := s.client.Do(ctx, req, issueType) + if err != nil { + return nil, resp, err + } + + return issueType, resp, nil +} + +// DeleteIssueType deletes an issue type for an organization. +// +// GitHub API docs: https://docs.github.com/rest/orgs/issue-types#delete-issue-type-for-an-organization +// +//meta:operation DELETE /orgs/{org}/issue-types/{issue_type_id} +func (s *OrganizationsService) DeleteIssueType(ctx context.Context, org string, issueTypeID int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/issue-types/%d", org, issueTypeID) + 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/orgs_issue_types_test.go b/github/orgs_issue_types_test.go new file mode 100644 index 00000000000..7775b4f5ef2 --- /dev/null +++ b/github/orgs_issue_types_test.go @@ -0,0 +1,238 @@ +// 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" + "encoding/json" + "fmt" + "net/http" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func TestOrganizationsService_ListIssueTypes(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/issue-types", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[ + { + "id": 410, + "node_id": "IT_kwDNAd3NAZo", + "name": "Task", + "description": "A specific piece of work", + "created_at": "2024-12-11T14:39:09Z", + "updated_at": "2024-12-11T14:39:09Z" + }, + { + "id": 411, + "node_id": "IT_kwDNAd3NAZs", + "name": "Bug", + "description": "An unexpected problem or behavior", + "created_at": "2024-12-11T14:39:09Z", + "updated_at": "2024-12-11T14:39:09Z" + } + ]`) + }) + + ctx := context.Background() + issueTypes, _, err := client.Organizations.ListIssueTypes(ctx, "o") + if err != nil { + t.Errorf("Organizations.ListIssueTypes returned error: %v", err) + } + + want := []*IssueType{ + { + ID: Ptr(int64(410)), + NodeID: Ptr("IT_kwDNAd3NAZo"), + Name: Ptr("Task"), + Description: Ptr("A specific piece of work"), + CreatedAt: Ptr(Timestamp{time.Date(2024, 12, 11, 14, 39, 9, 0, time.UTC)}), + UpdatedAt: Ptr(Timestamp{time.Date(2024, 12, 11, 14, 39, 9, 0, time.UTC)}), + }, + { + ID: Ptr(int64(411)), + NodeID: Ptr("IT_kwDNAd3NAZs"), + Name: Ptr("Bug"), + Description: Ptr("An unexpected problem or behavior"), + CreatedAt: Ptr(Timestamp{time.Date(2024, 12, 11, 14, 39, 9, 0, time.UTC)}), + UpdatedAt: Ptr(Timestamp{time.Date(2024, 12, 11, 14, 39, 9, 0, time.UTC)})}, + } + if !cmp.Equal(issueTypes, want) { + t.Errorf("Organizations.ListIssueTypes returned %+v, want %+v", issueTypes, want) + } + + const methodName = "ListIssueTypes" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Organizations.ListIssueTypes(ctx, "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Organizations.ListIssueTypes(ctx, "o") + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestOrganizationsService_CreateIssueType(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + input := &CreateOrUpdateIssueTypesOptions{ + Name: "Epic", + Description: Ptr("An issue type for a multi-week tracking of work"), + IsEnabled: true, + Color: Ptr("green"), + IsPrivate: Ptr(true), + } + + mux.HandleFunc("/orgs/o/issue-types", func(w http.ResponseWriter, r *http.Request) { + v := new(CreateOrUpdateIssueTypesOptions) + assertNilError(t, json.NewDecoder(r.Body).Decode(v)) + + testMethod(t, r, "POST") + if !cmp.Equal(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{ + "id": 410, + "node_id": "IT_kwDNAd3NAZo", + "name": "Epic", + "description": "An issue type for a multi-week tracking of work", + "created_at": "2024-12-11T14:39:09Z", + "updated_at": "2024-12-11T14:39:09Z" + }`) + }) + + ctx := context.Background() + issueType, _, err := client.Organizations.CreateIssueType(ctx, "o", input) + if err != nil { + t.Errorf("Organizations.CreateIssueType returned error: %v", err) + } + want := &IssueType{ + ID: Ptr(int64(410)), + NodeID: Ptr("IT_kwDNAd3NAZo"), + Name: Ptr("Epic"), + Description: Ptr("An issue type for a multi-week tracking of work"), + CreatedAt: Ptr(Timestamp{time.Date(2024, 12, 11, 14, 39, 9, 0, time.UTC)}), + UpdatedAt: Ptr(Timestamp{time.Date(2024, 12, 11, 14, 39, 9, 0, time.UTC)}), + } + + if !cmp.Equal(issueType, want) { + t.Errorf("Organizations.CreateIssueType returned %+v, want %+v", issueType, want) + } + + const methodName = "CreateIssueType" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Organizations.CreateIssueType(ctx, "\n", input) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Organizations.CreateIssueType(ctx, "o", input) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestOrganizationsService_UpdateIssueType(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + input := &CreateOrUpdateIssueTypesOptions{ + Name: "Epic", + Description: Ptr("An issue type for a multi-week tracking of work"), + IsEnabled: true, + Color: Ptr("green"), + IsPrivate: Ptr(true), + } + + mux.HandleFunc("/orgs/o/issue-types/410", func(w http.ResponseWriter, r *http.Request) { + v := new(CreateOrUpdateIssueTypesOptions) + assertNilError(t, json.NewDecoder(r.Body).Decode(v)) + + testMethod(t, r, "PUT") + if !cmp.Equal(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{ + "id": 410, + "node_id": "IT_kwDNAd3NAZo", + "name": "Epic", + "description": "An issue type for a multi-week tracking of work", + "created_at": "2024-12-11T14:39:09Z", + "updated_at": "2024-12-11T14:39:09Z" + }`) + }) + + ctx := context.Background() + issueType, _, err := client.Organizations.UpdateIssueType(ctx, "o", 410, input) + if err != nil { + t.Errorf("Organizations.UpdateIssueType returned error: %v", err) + } + want := &IssueType{ + ID: Ptr(int64(410)), + NodeID: Ptr("IT_kwDNAd3NAZo"), + Name: Ptr("Epic"), + Description: Ptr("An issue type for a multi-week tracking of work"), + CreatedAt: Ptr(Timestamp{time.Date(2024, 12, 11, 14, 39, 9, 0, time.UTC)}), + UpdatedAt: Ptr(Timestamp{time.Date(2024, 12, 11, 14, 39, 9, 0, time.UTC)}), + } + + if !cmp.Equal(issueType, want) { + t.Errorf("Organizations.UpdateIssueType returned %+v, want %+v", issueType, want) + } + + const methodName = "UpdateIssueType" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Organizations.UpdateIssueType(ctx, "\n", -1, input) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Organizations.UpdateIssueType(ctx, "o", 410, input) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestOrganizationsService_DeleteIssueType(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/issue-types/410", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + ctx := context.Background() + _, err := client.Organizations.DeleteIssueType(ctx, "o", 410) + if err != nil { + t.Errorf("Organizations.DeleteIssueType returned error: %v", err) + } + + const methodName = "DeleteIssueType" + testBadOptions(t, methodName, func() (err error) { + _, err = client.Organizations.DeleteIssueType(ctx, "\n", -1) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Organizations.DeleteIssueType(ctx, "o", 410) + }) +}