Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Copilot endpoints #2973

Merged
merged 40 commits into from Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
aaa5b5b
Add Copilot Endpoints
o-sama Oct 24, 2023
67e0a68
clean up unmarshal func
o-sama Oct 24, 2023
3e32bab
Add coverage and adddress linting issues
o-sama Oct 24, 2023
8dddf5f
undo wrong file committed
o-sama Oct 24, 2023
0864167
Add json unmarshal tests for seat details
o-sama Oct 24, 2023
9d5be67
update seat details unmarshal tests
o-sama Oct 24, 2023
3e27021
Apply suggestions from code review
o-sama Oct 24, 2023
568617d
code review comments
o-sama Oct 24, 2023
19339c2
Add coverage for Assignee helper methods
o-sama Oct 25, 2023
4f78d68
Add coverage for Assignee helper methods error cases
o-sama Oct 25, 2023
2c90d0c
Add requested test case
o-sama Oct 25, 2023
e01ef53
Fix unmarshal test cases
o-sama Oct 25, 2023
99fe9ba
Update tests and time types
o-sama Oct 25, 2023
f2d3230
Apply suggestions from code review
o-sama Oct 26, 2023
2ab8aca
Address review comments
o-sama Oct 26, 2023
b0f05fe
Merge branch 'master' into add-copilot-endpoints
o-sama Oct 26, 2023
63276a8
Address review comments
o-sama Nov 17, 2023
d3fa200
Merge branch 'master' into add-copilot-endpoints
o-sama Nov 17, 2023
78f4db0
Add operations metadata
o-sama Nov 17, 2023
da71a5a
Update generated code
o-sama Nov 17, 2023
766902b
Add default branch to repository edit event (#2995)
caseyduquettesc Nov 22, 2023
76779c0
Add `Draft` to `Issue` type (#2997)
caseyduquettesc Nov 23, 2023
baf2515
Fix secondary rate limits URL (#3001)
Letiste Nov 27, 2023
2adca97
Bump golang.org/x/net from 0.18.0 to 0.19.0 in /scrape (#3003)
dependabot[bot] Nov 27, 2023
b0181bb
Implement global security advisories API (#2993)
cpanato Nov 29, 2023
c7a9ad7
Change `PushEvent.Pusher` type to `CommitAuthor` (#2999)
caseyduquettesc Nov 30, 2023
56a8c95
Bump version of go-github to v57.0.0 (#3009)
gmlewis Dec 1, 2023
5390049
Bump go-github from v56 to v57 in /scrape (#3010)
gmlewis Dec 1, 2023
9f70f1f
Update metadata (#3012)
WillAbides Dec 2, 2023
5e63691
Fix broken CreateOrUpdateRepoCustomPropertyValues (#3023)
peter-aglen Dec 8, 2023
cec367c
Bump actions/setup-go from 4 to 5 (#3027)
dependabot[bot] Dec 11, 2023
1080dff
Add scanning validity checks (#3026)
tomasz-adam-skrzypczak Dec 14, 2023
27decbb
Add Referrer field to AuditEntry (#3032)
ngoduykhanh Dec 15, 2023
f5b837b
Add code_search and dependency_snapshots for RateLimits (#3019)
rufusnufus Dec 15, 2023
f53e74d
Support temporary private fork creation via API (#3025)
Kiyo510 Dec 15, 2023
0053173
Escape package names to support names which include a slash (#3002)
bn4t Dec 16, 2023
25e042b
Don't update httpClient passed to NewClient (#3011)
WillAbides Dec 16, 2023
6e03d4e
Add GetAllCustomPropertyValues for repositories (#3020)
liaodaniel Dec 17, 2023
428a0da
Remove ambiguous fields from AuditEntry (#3017)
WillAbides Dec 18, 2023
36bc1f4
Merge branch 'master' into add-copilot-endpoints
gmlewis Dec 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
263 changes: 263 additions & 0 deletions github/copilot.go
@@ -0,0 +1,263 @@
// Copyright 2023 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"
)

// CopilotService provides access to the Copilot-related functions
// in the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/copilot/
type CopilotService service

// OrganizationCopilotDetails represents the details of an organization's Copilot for Business supbscription.
o-sama marked this conversation as resolved.
Show resolved Hide resolved
type OrganizationCopilotDetails struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is called copilot-organization-details in github's schema, so CopilotOrganizationDetails might be a better name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do 🙂

SeatBreakdown *SeatBreakdown `json:"seat_breakdown"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
SeatBreakdown *SeatBreakdown `json:"seat_breakdown"`
SeatBreakdown *SeatBreakdown `json:"seat_breakdown,omitempty"`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll have to check whether or not we should be omitting empty, and if we do, how we should be handling that. The reason I bring this up is I've noticed in repo rulesets there's a case where bypass_actors should be an empty list of pointers instead of nil or omitted otherwise you lose the ability to unset actors when updating the ruleset. (I plan to open a PR to address that specific example in the next couple of days btw).

PublicCodeSuggestions string `json:"public_code_suggestions"`
CopilotChat string `json:"copilot_chat"`
SeatManagementSetting string `json:"seat_management_setting"`
}

// SeatBreakdown represents the breakdown of Copilot for Business seats for the organization.
type SeatBreakdown struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

CopilotSeatBreakdown

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto

Total int64 `json:"total"`
AddedThisCycle int64 `json:"added_this_cycle"`
PendingCancellation int64 `json:"pending_cancellation"`
PendingInvitation int64 `json:"pending_invitation"`
ActiveThisCycle int64 `json:"active_this_cycle"`
InactiveThisCycle int64 `json:"inactive_this_cycle"`
o-sama marked this conversation as resolved.
Show resolved Hide resolved
}

type CopilotSeats struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing godoc-style comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto

Copy link
Contributor

Choose a reason for hiding this comment

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

This might be better named ListCopilotSeatsResponse because it's only used as a response.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto

TotalSeats int64 `json:"total_seats"`
Seats []CopilotSeatDetails `json:"seats"`
o-sama marked this conversation as resolved.
Show resolved Hide resolved
}

// CopilotSeatDetails represents the details of a Copilot for Business seat.
// Assignee can either be a User, Team, or Organization.
type CopilotSeatDetails struct {
Assignee interface{} `json:"assignee"`
Copy link
Contributor

Choose a reason for hiding this comment

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

It may be ok to use *User here. There are a few other places where we use *User and let users distinguish using the Type field. @gmlewis will know what the preferred method is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel like this might be a better option since we're explicitly setting the correct type for the field, but both approaches leave type-checking to the end user.

There are minor differences as far as I'm aware between the types, e.g. Team has no Login field, but has a Name field, which both User and Organization have. Login is used for the username in the case of a user, whereas Name is used for team name.

I'll leave you guys with these thoughts and will be happy to make changes based on what you think.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good point about Team being different. I think this might be the best way to handle it after all.

o-sama marked this conversation as resolved.
Show resolved Hide resolved
AssigningTeam *Team `json:"assigning_team,omitempty"`
PendingCancellationDate *string `json:"pending_cancellation_date,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

type should be *Timestamp instead that it works in similar way than other time fields.

LastActivityAt *string `json:"last_activity_at,omitempty"`
LastActivityEditor *string `json:"last_activity_editor,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at,omitempty"`
}

// SelectedTeams represents the teams selected for the Copilot for Business subscription.
type SelectedTeams struct {
SelectedTeams []string `json:"selected_teams"`
}

// SelectedUsers represents the users selected for the Copilot for Business subscription.
type SelectedUsers struct {
SelectedUsers []string `json:"selected_users"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

These can be removed and replaced with []string in the methods that take them as an argument. When you do that, please name the variable something like teamNames so that it is clear it takes names and not ids.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto


// SeatAssignments represents the number of seats assigned.
type SeatAssignments struct {
SeatsCreated int64 `json:"seats_created"`
o-sama marked this conversation as resolved.
Show resolved Hide resolved
}

// SeatCancellations represents the number of seats cancelled.
type SeatCancellations struct {
SeatsCancelled int64 `json:"seats_cancelled"`
o-sama marked this conversation as resolved.
Show resolved Hide resolved
}

Copy link
Contributor

Choose a reason for hiding this comment

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

These should have "Copilot" in the name to avoid confusion with any other type of seat in the GitHub API.

I also think that because these are used as responses, it might be better to split these into four structs: AddCopilotTeamsResponse, RemoveCopilotTeamsResponse, AddCopilotUsersResponse and RemoveCopilotUsersResponse.

/cc @gmlewis for thoughts

Copy link

@gauraw10 gauraw10 Oct 24, 2023

Choose a reason for hiding this comment

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

I don't know, its right place to add this comment here, But may be could we have GetCopilotSeatInfoForUser to get the info that user already onboarded before assigning the seat.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@WillAbides I'll add the "Copilot" bit in the names. About the structs, the "add" and "remove" structs are identical for users and teams, respectively, because the response schemas are identical.

func (cp *CopilotSeatDetails) UnmarshalJSON(data []byte) error {
type Alias CopilotSeatDetails
var seatDetail Alias
o-sama marked this conversation as resolved.
Show resolved Hide resolved

if err := json.Unmarshal(data, &seatDetail); err != nil {
return err
}

cp.AssigningTeam = seatDetail.AssigningTeam
cp.PendingCancellationDate = seatDetail.PendingCancellationDate
cp.LastActivityAt = seatDetail.LastActivityAt
cp.LastActivityEditor = seatDetail.LastActivityEditor
cp.CreatedAt = seatDetail.CreatedAt
cp.UpdatedAt = seatDetail.UpdatedAt

switch v := seatDetail.Assignee.(type) {
case map[string]interface{}:
jsonData, err := json.Marshal(seatDetail.Assignee)
if err != nil {
return err
}
if v["type"].(string) == "User" {
user := &User{}
if err := json.Unmarshal(jsonData, user); err != nil {
return err
}
cp.Assignee = user
} else if v["type"].(string) == "Team" {
team := &Team{}
if err := json.Unmarshal(jsonData, team); err != nil {
return err
}
cp.Assignee = team
} else if v["type"].(string) == "Organization" {
organization := &Organization{}
if err := json.Unmarshal(jsonData, organization); err != nil {
return err
}
cp.Assignee = organization
} else {
return fmt.Errorf("unsupported assignee type %s", v["type"].(string))
}
default:
return fmt.Errorf("unsupported assignee type %T", v)
}

return nil
}

// GetCopilotBilling Gets Copilot for Business seat information and settings for an organization
o-sama marked this conversation as resolved.
Show resolved Hide resolved
//
// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-for-business#get-copilot-for-business-seat-information-and-settings-for-an-organization
func (s *CopilotService) GetCopilotBilling(ctx context.Context, org string) (*OrganizationCopilotDetails, *Response, error) {
u := fmt.Sprintf("orgs/%v/copilot/billing", org)

req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var copilotDetails *OrganizationCopilotDetails
resp, err := s.client.Do(ctx, req, &copilotDetails)
if err != nil {
return nil, resp, err
}

return copilotDetails, resp, nil
}

// ListCopilotSeats Gets Copilot for Business seat assignments for an organization
o-sama marked this conversation as resolved.
Show resolved Hide resolved
//
// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-for-business#list-all-copilot-for-business-seat-assignments-for-an-organization
func (s *CopilotService) ListCopilotSeats(ctx context.Context, org string) (*CopilotSeats, *Response, error) {
u := fmt.Sprintf("orgs/%v/copilot/billing/seats", org)

req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var copilotSeats *CopilotSeats
resp, err := s.client.Do(ctx, req, &copilotSeats)

Choose a reason for hiding this comment

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

How are we handling pagination here for more than 100 users?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will be adding support for this, thanks for the callout 🙏

Copy link
Contributor

Choose a reason for hiding this comment

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

pagination works in current version

if err != nil {
return nil, resp, err
}

return copilotSeats, resp, nil
}

// AddCopilotTeams Adds teams to the Copilot for Business subscription for an organization
o-sama marked this conversation as resolved.
Show resolved Hide resolved
//
// https://docs.github.com/en/rest/copilot/copilot-for-business#add-teams-to-the-copilot-for-business-subscription-for-an-organization
o-sama marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// https://docs.github.com/en/rest/copilot/copilot-for-business#add-teams-to-the-copilot-for-business-subscription-for-an-organization
// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-for-business#add-teams-to-the-copilot-for-business-subscription-for-an-organization

func (s *CopilotService) AddCopilotTeams(ctx context.Context, org string, teams SelectedTeams) (*SeatAssignments, *Response, error) {
u := fmt.Sprintf("orgs/%v/copilot/billing/selected_teams", org)

req, err := s.client.NewRequest("PUT", u, teams)
o-sama marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, nil, err
}

var seatAssignments *SeatAssignments
resp, err := s.client.Do(ctx, req, &seatAssignments)
if err != nil {
return nil, resp, err
}

return seatAssignments, resp, nil
}

// RemoveCopilotTeams Removes teams from the Copilot for Business subscription for an organization
o-sama marked this conversation as resolved.
Show resolved Hide resolved
//
// https://docs.github.com/en/rest/copilot/copilot-for-business#remove-teams-from-the-copilot-for-business-subscription-for-an-organization
o-sama marked this conversation as resolved.
Show resolved Hide resolved

o-sama marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please delete the blank line between the godoc and the method.

Suggested change
// https://docs.github.com/en/rest/copilot/copilot-for-business#remove-teams-from-the-copilot-for-business-subscription-for-an-organization
// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-for-business#remove-teams-from-the-copilot-for-business-subscription-for-an-organization

func (s *CopilotService) RemoveCopilotTeams(ctx context.Context, org string, teams SelectedTeams) (*SeatCancellations, *Response, error) {
u := fmt.Sprintf("orgs/%v/copilot/billing/selected_teams", org)

req, err := s.client.NewRequest("DELETE", u, teams)
if err != nil {
return nil, nil, err
}

var SeatCancellations *SeatCancellations
Copy link
Contributor

Choose a reason for hiding this comment

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

The var name should be downcased "seatCancellations"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do, my bad there

resp, err := s.client.Do(ctx, req, &SeatCancellations)
if err != nil {
return nil, resp, err
}

return SeatCancellations, resp, nil
}

// AddCopilotUsers Adds users to the Copilot for Business subscription for an organization
//
// https://docs.github.com/en/rest/copilot/copilot-for-business#add-users-to-the-copilot-for-business-subscription-for-an-organization
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// AddCopilotUsers Adds users to the Copilot for Business subscription for an organization
//
// https://docs.github.com/en/rest/copilot/copilot-for-business#add-users-to-the-copilot-for-business-subscription-for-an-organization
// AddCopilotUsers sdds users to the Copilot for Business subscription for an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-for-business#add-users-to-the-copilot-for-business-subscription-for-an-organization

o-sama marked this conversation as resolved.
Show resolved Hide resolved
func (s *CopilotService) AddCopilotUsers(ctx context.Context, org string, users SelectedUsers) (*SeatAssignments, *Response, error) {
u := fmt.Sprintf("orgs/%v/copilot/billing/selected_users", org)

req, err := s.client.NewRequest("PUT", u, users)
o-sama marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, nil, err
}

var seatAssignments *SeatAssignments
resp, err := s.client.Do(ctx, req, &seatAssignments)
if err != nil {
return nil, resp, err
}

return seatAssignments, resp, nil
}

// RemoveCopilotUsers Removes users from the Copilot for Business subscription for an organization
//
// https://docs.github.com/en/rest/copilot/copilot-for-business#remove-users-from-the-copilot-for-business-subscription-for-an-organization
o-sama marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// https://docs.github.com/en/rest/copilot/copilot-for-business#remove-users-from-the-copilot-for-business-subscription-for-an-organization
// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-for-business#remove-users-from-the-copilot-for-business-subscription-for-an-organization

func (s *CopilotService) RemoveCopilotUsers(ctx context.Context, org string, users SelectedUsers) (*SeatCancellations, *Response, error) {
u := fmt.Sprintf("orgs/%v/copilot/billing/selected_users", org)

req, err := s.client.NewRequest("DELETE", u, users)
if err != nil {
return nil, nil, err
}

var SeatCancellations *SeatCancellations
Copy link
Contributor

Choose a reason for hiding this comment

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

Downcase seatCancellations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ditto

resp, err := s.client.Do(ctx, req, &SeatCancellations)
if err != nil {
return nil, resp, err
}

return SeatCancellations, resp, nil
}

// GetSeatDetails Gets Copilot for Business seat assignment details for a user
//
// https://docs.github.com/en/rest/copilot/copilot-for-business#get-copilot-for-business-seat-assignment-details-for-a-user
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// https://docs.github.com/en/rest/copilot/copilot-for-business#get-copilot-for-business-seat-assignment-details-for-a-user
// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-for-business#get-copilot-for-business-seat-assignment-details-for-a-user

func (s *CopilotService) GetSeatDetails(ctx context.Context, org string, user string) (*CopilotSeatDetails, *Response, error) {
o-sama marked this conversation as resolved.
Show resolved Hide resolved
u := fmt.Sprintf("orgs/%v/members/%v/copilot", org, user)

req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var seatDetails *CopilotSeatDetails
resp, err := s.client.Do(ctx, req, &seatDetails)
if err != nil {
return nil, resp, err
}

return seatDetails, resp, nil
}