|
| 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