Skip to content

Commit 9d6658a

Browse files
authored
Add Copilot endpoints (#2973)
Fixes: #2938.
1 parent a354a6c commit 9d6658a

File tree

5 files changed

+1320
-0
lines changed

5 files changed

+1320
-0
lines changed

github/copilot.go

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
// Copyright 2023 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"context"
10+
"encoding/json"
11+
"fmt"
12+
)
13+
14+
// CopilotService provides access to the Copilot-related functions
15+
// in the GitHub API.
16+
//
17+
// GitHub API docs: https://docs.github.com/en/rest/copilot/
18+
type CopilotService service
19+
20+
// CopilotOrganizationDetails represents the details of an organization's Copilot for Business subscription.
21+
type CopilotOrganizationDetails struct {
22+
SeatBreakdown *CopilotSeatBreakdown `json:"seat_breakdown"`
23+
PublicCodeSuggestions string `json:"public_code_suggestions"`
24+
CopilotChat string `json:"copilot_chat"`
25+
SeatManagementSetting string `json:"seat_management_setting"`
26+
}
27+
28+
// CopilotSeatBreakdown represents the breakdown of Copilot for Business seats for the organization.
29+
type CopilotSeatBreakdown struct {
30+
Total int `json:"total"`
31+
AddedThisCycle int `json:"added_this_cycle"`
32+
PendingCancellation int `json:"pending_cancellation"`
33+
PendingInvitation int `json:"pending_invitation"`
34+
ActiveThisCycle int `json:"active_this_cycle"`
35+
InactiveThisCycle int `json:"inactive_this_cycle"`
36+
}
37+
38+
// ListCopilotSeatsResponse represents the Copilot for Business seat assignments for an organization.
39+
type ListCopilotSeatsResponse struct {
40+
TotalSeats int64 `json:"total_seats"`
41+
Seats []*CopilotSeatDetails `json:"seats"`
42+
}
43+
44+
// CopilotSeatDetails represents the details of a Copilot for Business seat.
45+
type CopilotSeatDetails struct {
46+
// Assignee can either be a User, Team, or Organization.
47+
Assignee interface{} `json:"assignee"`
48+
AssigningTeam *Team `json:"assigning_team,omitempty"`
49+
PendingCancellationDate *string `json:"pending_cancellation_date,omitempty"`
50+
LastActivityAt *Timestamp `json:"last_activity_at,omitempty"`
51+
LastActivityEditor *string `json:"last_activity_editor,omitempty"`
52+
CreatedAt *Timestamp `json:"created_at"`
53+
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
54+
}
55+
56+
// SeatAssignments represents the number of seats assigned.
57+
type SeatAssignments struct {
58+
SeatsCreated int `json:"seats_created"`
59+
}
60+
61+
// SeatCancellations represents the number of seats cancelled.
62+
type SeatCancellations struct {
63+
SeatsCancelled int `json:"seats_cancelled"`
64+
}
65+
66+
func (cp *CopilotSeatDetails) UnmarshalJSON(data []byte) error {
67+
// Using an alias to avoid infinite recursion when calling json.Unmarshal
68+
type alias CopilotSeatDetails
69+
var seatDetail alias
70+
71+
if err := json.Unmarshal(data, &seatDetail); err != nil {
72+
return err
73+
}
74+
75+
cp.AssigningTeam = seatDetail.AssigningTeam
76+
cp.PendingCancellationDate = seatDetail.PendingCancellationDate
77+
cp.LastActivityAt = seatDetail.LastActivityAt
78+
cp.LastActivityEditor = seatDetail.LastActivityEditor
79+
cp.CreatedAt = seatDetail.CreatedAt
80+
cp.UpdatedAt = seatDetail.UpdatedAt
81+
82+
switch v := seatDetail.Assignee.(type) {
83+
case map[string]interface{}:
84+
jsonData, err := json.Marshal(seatDetail.Assignee)
85+
if err != nil {
86+
return err
87+
}
88+
89+
if v["type"] == nil {
90+
return fmt.Errorf("assignee type field is not set")
91+
}
92+
93+
if t, ok := v["type"].(string); ok && t == "User" {
94+
user := &User{}
95+
if err := json.Unmarshal(jsonData, user); err != nil {
96+
return err
97+
}
98+
cp.Assignee = user
99+
} else if t, ok := v["type"].(string); ok && t == "Team" {
100+
team := &Team{}
101+
if err := json.Unmarshal(jsonData, team); err != nil {
102+
return err
103+
}
104+
cp.Assignee = team
105+
} else if t, ok := v["type"].(string); ok && t == "Organization" {
106+
organization := &Organization{}
107+
if err := json.Unmarshal(jsonData, organization); err != nil {
108+
return err
109+
}
110+
cp.Assignee = organization
111+
} else {
112+
return fmt.Errorf("unsupported assignee type %v", v["type"])
113+
}
114+
default:
115+
return fmt.Errorf("unsupported assignee type %T", v)
116+
}
117+
118+
return nil
119+
}
120+
121+
// GetUser gets the User from the CopilotSeatDetails if the assignee is a user.
122+
func (cp *CopilotSeatDetails) GetUser() (*User, bool) { u, ok := cp.Assignee.(*User); return u, ok }
123+
124+
// GetTeam gets the Team from the CopilotSeatDetails if the assignee is a team.
125+
func (cp *CopilotSeatDetails) GetTeam() (*Team, bool) { t, ok := cp.Assignee.(*Team); return t, ok }
126+
127+
// GetOrganization gets the Organization from the CopilotSeatDetails if the assignee is an organization.
128+
func (cp *CopilotSeatDetails) GetOrganization() (*Organization, bool) {
129+
o, ok := cp.Assignee.(*Organization)
130+
return o, ok
131+
}
132+
133+
// GetCopilotBilling gets Copilot for Business billing information and settings for an organization.
134+
//
135+
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-business#get-copilot-business-seat-information-and-settings-for-an-organization
136+
//
137+
//meta:operation GET /orgs/{org}/copilot/billing
138+
func (s *CopilotService) GetCopilotBilling(ctx context.Context, org string) (*CopilotOrganizationDetails, *Response, error) {
139+
u := fmt.Sprintf("orgs/%v/copilot/billing", org)
140+
141+
req, err := s.client.NewRequest("GET", u, nil)
142+
if err != nil {
143+
return nil, nil, err
144+
}
145+
146+
var copilotDetails *CopilotOrganizationDetails
147+
resp, err := s.client.Do(ctx, req, &copilotDetails)
148+
if err != nil {
149+
return nil, resp, err
150+
}
151+
152+
return copilotDetails, resp, nil
153+
}
154+
155+
// ListCopilotSeats lists Copilot for Business seat assignments for an organization.
156+
//
157+
// To paginate through all seats, populate 'Page' with the number of the last page.
158+
//
159+
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-business#list-all-copilot-business-seat-assignments-for-an-organization
160+
//
161+
//meta:operation GET /orgs/{org}/copilot/billing/seats
162+
func (s *CopilotService) ListCopilotSeats(ctx context.Context, org string, opts *ListOptions) (*ListCopilotSeatsResponse, *Response, error) {
163+
u := fmt.Sprintf("orgs/%v/copilot/billing/seats", org)
164+
165+
req, err := s.client.NewRequest("GET", u, opts)
166+
if err != nil {
167+
return nil, nil, err
168+
}
169+
170+
var copilotSeats *ListCopilotSeatsResponse
171+
resp, err := s.client.Do(ctx, req, &copilotSeats)
172+
if err != nil {
173+
return nil, resp, err
174+
}
175+
176+
return copilotSeats, resp, nil
177+
}
178+
179+
// AddCopilotTeams adds teams to the Copilot for Business subscription for an organization.
180+
//
181+
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-business#add-teams-to-the-copilot-business-subscription-for-an-organization
182+
//
183+
//meta:operation POST /orgs/{org}/copilot/billing/selected_teams
184+
func (s *CopilotService) AddCopilotTeams(ctx context.Context, org string, teamNames []string) (*SeatAssignments, *Response, error) {
185+
u := fmt.Sprintf("orgs/%v/copilot/billing/selected_teams", org)
186+
187+
body := struct {
188+
SelectedTeams []string `json:"selected_teams"`
189+
}{
190+
SelectedTeams: teamNames,
191+
}
192+
193+
req, err := s.client.NewRequest("POST", u, body)
194+
if err != nil {
195+
return nil, nil, err
196+
}
197+
198+
var seatAssignments *SeatAssignments
199+
resp, err := s.client.Do(ctx, req, &seatAssignments)
200+
if err != nil {
201+
return nil, resp, err
202+
}
203+
204+
return seatAssignments, resp, nil
205+
}
206+
207+
// RemoveCopilotTeams removes teams from the Copilot for Business subscription for an organization.
208+
//
209+
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-business#remove-teams-from-the-copilot-business-subscription-for-an-organization
210+
//
211+
//meta:operation DELETE /orgs/{org}/copilot/billing/selected_teams
212+
func (s *CopilotService) RemoveCopilotTeams(ctx context.Context, org string, teamNames []string) (*SeatCancellations, *Response, error) {
213+
u := fmt.Sprintf("orgs/%v/copilot/billing/selected_teams", org)
214+
215+
body := struct {
216+
SelectedTeams []string `json:"selected_teams"`
217+
}{
218+
SelectedTeams: teamNames,
219+
}
220+
221+
req, err := s.client.NewRequest("DELETE", u, body)
222+
if err != nil {
223+
return nil, nil, err
224+
}
225+
226+
var seatCancellations *SeatCancellations
227+
resp, err := s.client.Do(ctx, req, &seatCancellations)
228+
if err != nil {
229+
return nil, resp, err
230+
}
231+
232+
return seatCancellations, resp, nil
233+
}
234+
235+
// AddCopilotUsers adds users to the Copilot for Business subscription for an organization
236+
//
237+
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-business#add-users-to-the-copilot-business-subscription-for-an-organization
238+
//
239+
//meta:operation POST /orgs/{org}/copilot/billing/selected_users
240+
func (s *CopilotService) AddCopilotUsers(ctx context.Context, org string, users []string) (*SeatAssignments, *Response, error) {
241+
u := fmt.Sprintf("orgs/%v/copilot/billing/selected_users", org)
242+
243+
body := struct {
244+
SelectedUsers []string `json:"selected_users"`
245+
}{
246+
SelectedUsers: users,
247+
}
248+
249+
req, err := s.client.NewRequest("POST", u, body)
250+
if err != nil {
251+
return nil, nil, err
252+
}
253+
254+
var seatAssignments *SeatAssignments
255+
resp, err := s.client.Do(ctx, req, &seatAssignments)
256+
if err != nil {
257+
return nil, resp, err
258+
}
259+
260+
return seatAssignments, resp, nil
261+
}
262+
263+
// RemoveCopilotUsers removes users from the Copilot for Business subscription for an organization.
264+
//
265+
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-business#remove-users-from-the-copilot-business-subscription-for-an-organization
266+
//
267+
//meta:operation DELETE /orgs/{org}/copilot/billing/selected_users
268+
func (s *CopilotService) RemoveCopilotUsers(ctx context.Context, org string, users []string) (*SeatCancellations, *Response, error) {
269+
u := fmt.Sprintf("orgs/%v/copilot/billing/selected_users", org)
270+
271+
body := struct {
272+
SelectedUsers []string `json:"selected_users"`
273+
}{
274+
SelectedUsers: users,
275+
}
276+
277+
req, err := s.client.NewRequest("DELETE", u, body)
278+
if err != nil {
279+
return nil, nil, err
280+
}
281+
282+
var seatCancellations *SeatCancellations
283+
resp, err := s.client.Do(ctx, req, &seatCancellations)
284+
if err != nil {
285+
return nil, resp, err
286+
}
287+
288+
return seatCancellations, resp, nil
289+
}
290+
291+
// GetSeatDetails gets Copilot for Business seat assignment details for a user.
292+
//
293+
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-business#get-copilot-business-seat-assignment-details-for-a-user
294+
//
295+
//meta:operation GET /orgs/{org}/members/{username}/copilot
296+
func (s *CopilotService) GetSeatDetails(ctx context.Context, org, user string) (*CopilotSeatDetails, *Response, error) {
297+
u := fmt.Sprintf("orgs/%v/members/%v/copilot", org, user)
298+
299+
req, err := s.client.NewRequest("GET", u, nil)
300+
if err != nil {
301+
return nil, nil, err
302+
}
303+
304+
var seatDetails *CopilotSeatDetails
305+
resp, err := s.client.Do(ctx, req, &seatDetails)
306+
if err != nil {
307+
return nil, resp, err
308+
}
309+
310+
return seatDetails, resp, nil
311+
}

0 commit comments

Comments
 (0)