[FEAT]: Adding Copilot for Business support (#2826)

* initial tests and implementation of Copilot for Business client API

* updated billing settings documentation

* renames and refactors - clarity and simplified

* using context to ensure license clean up

* extra documentation and used ApiOptions instead of custom class

* implemented observable clients

* Fixing convention issues

* renaming for clarity

---------

Co-authored-by: Nick Floyd <139819+nickfloyd@users.noreply.github.com>
This commit is contained in:
Dylan Morley
2024-01-02 21:57:14 +00:00
committed by GitHub
parent 48d061afd2
commit a2a4f09c24
25 changed files with 1161 additions and 2 deletions

View File

@@ -0,0 +1,23 @@
using System;
namespace Octokit.Reactive
{
/// <summary>
/// Access GitHub's Copilot for Business API.
/// </summary>
public interface IObservableCopilotClient
{
/// <summary>
/// Returns a summary of the Copilot for Business configuration for an organization. Includes a seat
/// details summary of the current billing cycle, and the mode of seat management.
/// </summary>
/// <param name="organization">the organization name to retrieve billing settings for</param>
/// <returns>A <see cref="BillingSettings"/> instance</returns>
IObservable<BillingSettings> GetSummaryForOrganization(string organization);
/// <summary>
/// For checking and managing licenses for GitHub Copilot for Business
/// </summary>
IObservableCopilotLicenseClient Licensing { get; }
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
namespace Octokit.Reactive
{
/// <summary>
/// A client for managing licenses for GitHub Copilot for Business
/// </summary>
public interface IObservableCopilotLicenseClient
{
/// <summary>
/// Removes a license for a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to remove a license from</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
IObservable<CopilotSeatAllocation> Remove(string organization, string userName);
/// <summary>
/// Removes a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to remove licenses for</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
IObservable<CopilotSeatAllocation> Remove(string organization, UserSeatAllocation userSeatAllocation);
/// <summary>
/// Assigns a license to a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to add a license to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
IObservable<CopilotSeatAllocation> Assign(string organization, string userName);
/// <summary>
/// Assigns a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to add licenses to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
IObservable<CopilotSeatAllocation> Assign(string organization, UserSeatAllocation userSeatAllocation);
/// <summary>
/// Gets all of the currently allocated licenses for an organization
/// </summary>
/// <param name="organization">The organization</param>
/// <param name="options">The api options to use when making the API call, such as paging</param>
/// <returns>A <see cref="CopilotSeats"/> instance containing the currently allocated user licenses</returns>
IObservable<CopilotSeats> GetAll(string organization, ApiOptions options);
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Reactive.Threading.Tasks;
namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Copilot for Business API.
/// Allows listing, creating, and deleting Copilot licenses.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/copilot/copilot-business?apiVersion=2022-11-28">Copilot for Business API documentation</a> for more information.
/// </remarks>
public class ObservableCopilotClient : IObservableCopilotClient
{
private readonly ICopilotClient _client;
/// <summary>
/// Instantiates a new GitHub Copilot API client.
/// </summary>
/// <param name="client"></param>
public ObservableCopilotClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, nameof(client));
_client = client.Copilot;
Licensing = new ObservableCopilotLicenseClient(client);
}
/// <summary>
/// Returns a summary of the Copilot for Business configuration for an organization. Includes a seat
/// details summary of the current billing cycle, and the mode of seat management.
/// </summary>
/// <param name="organization">the organization name to retrieve billing settings for</param>
/// <returns>A <see cref="BillingSettings"/> instance</returns>
public IObservable<BillingSettings> GetSummaryForOrganization(string organization)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
return _client.GetSummaryForOrganization(organization).ToObservable();
}
/// <summary>
/// Client for maintaining Copilot licenses for users in an organization.
/// </summary>
public IObservableCopilotLicenseClient Licensing { get; private set; }
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Reactive.Threading.Tasks;
using Octokit;
using Octokit.Reactive;
using Octokit.Reactive.Internal;
/// <summary>
/// A client for managing licenses for GitHub Copilot for Business
/// </summary>
public class ObservableCopilotLicenseClient : IObservableCopilotLicenseClient
{
private readonly ICopilotLicenseClient _client;
private readonly IConnection _connection;
public ObservableCopilotLicenseClient(IGitHubClient client)
{
_client = client.Copilot.Licensing;
_connection = client.Connection;
}
/// <summary>
/// Removes a license for a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to remove a license from</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
public IObservable<CopilotSeatAllocation> Remove(string organization, string userName)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userName, nameof(userName));
return _client.Remove(organization, userName).ToObservable();
}
/// <summary>
/// Removes a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to remove licenses for</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
public IObservable<CopilotSeatAllocation> Remove(string organization, UserSeatAllocation userSeatAllocation)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));
return _client.Remove(organization, userSeatAllocation).ToObservable();
}
/// <summary>
/// Assigns a license to a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to add a license to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
public IObservable<CopilotSeatAllocation> Assign(string organization, string userName)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userName, nameof(userName));
return _client.Assign(organization, userName).ToObservable();
}
/// <summary>
/// Assigns a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to add licenses to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
public IObservable<CopilotSeatAllocation> Assign(string organization, UserSeatAllocation userSeatAllocation)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));
return _client.Assign(organization, userSeatAllocation).ToObservable();
}
/// <summary>
/// Gets all of the currently allocated licenses for an organization
/// </summary>
/// <param name="organization">The organization</param>
/// <param name="options">Options to control page size when making API requests</param>
/// <returns>A list of <see cref="CopilotSeats"/> instance containing the currently allocated user licenses.</returns>
public IObservable<CopilotSeats> GetAll(string organization, ApiOptions options)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(options, nameof(options));
return _connection.GetAndFlattenAllPages<CopilotSeats>( ApiUrls.CopilotAllocatedLicenses(organization), options);
}
}

View File

@@ -43,5 +43,6 @@ namespace Octokit.Reactive
IObservableMetaClient Meta { get; }
IObservableActionsClient Actions { get; }
IObservableCodespacesClient Codespaces { get; }
IObservableCopilotClient Copilot { get; }
}
}

View File

@@ -58,6 +58,7 @@ namespace Octokit.Reactive
Meta = new ObservableMetaClient(gitHubClient);
Actions = new ObservableActionsClient(gitHubClient);
Codespaces = new ObservableCodespacesClient(gitHubClient);
Copilot = new ObservableCopilotClient(gitHubClient);
}
public IConnection Connection
@@ -105,8 +106,9 @@ namespace Octokit.Reactive
public IObservableRateLimitClient RateLimit { get; private set; }
public IObservableMetaClient Meta { get; private set; }
public IObservableActionsClient Actions { get; private set; }
public IObservableCodespacesClient Codespaces { get; private set; }
public IObservableCopilotClient Copilot { get; set; }
/// <summary>
/// Gets the latest API Info - this will be null if no API calls have been made
/// </summary>

View File

@@ -0,0 +1,117 @@
using System.Threading.Tasks;
using Octokit.Tests.Integration.Helpers;
using Xunit;
namespace Octokit.Tests.Integration.Clients.Copilot
{
public class CopilotClientTests
{
public class TheGetBillingSettingsMethod
{
private readonly IGitHubClient _gitHub;
public TheGetBillingSettingsMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
}
[OrganizationTest]
public async Task ReturnsBillingSettingsData()
{
var billingSettings = await _gitHub.Copilot.GetSummaryForOrganization(Helper.Organization);
Assert.NotNull(billingSettings.SeatManagementSetting);
Assert.NotNull(billingSettings.PublicCodeSuggestions);
}
}
public class TheGetAllLicensesMethod
{
private readonly IGitHubClient _gitHub;
public TheGetAllLicensesMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
}
[OrganizationTest]
public async Task ReturnsUserCopilotLicenseDetailsAsList()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var licenses = await _gitHub.Copilot.Licensing.GetAll(Helper.Organization, new ApiOptions());
Assert.True(licenses.Count > 0);
}
}
}
public class TheAddLicenseMethod
{
private readonly IGitHubClient _gitHub;
public TheAddLicenseMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
}
[OrganizationTest]
public async Task AddsLicenseForUser()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var allocation = await _gitHub.Copilot.Licensing.Assign(Helper.Organization, Helper.UserName);
Assert.True(allocation.SeatsCreated > 0);
}
}
[OrganizationTest]
public async Task AddsLicenseForUsers()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } };
var allocation = await _gitHub.Copilot.Licensing.Assign(Helper.Organization, seatAllocation);
Assert.True(allocation.SeatsCreated > 0);
}
}
}
public class TheDeleteLicenseMethod
{
private readonly IGitHubClient _gitHub;
public TheDeleteLicenseMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
}
[OrganizationTest]
public async Task RemovesLicenseForUser()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var allocation = await _gitHub.Copilot.Licensing.Remove(Helper.Organization, Helper.UserName);
Assert.True(allocation.SeatsCancelled > 0);
}
}
[OrganizationTest]
public async Task RemovesLicenseForUsers()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } };
var allocation = await _gitHub.Copilot.Licensing.Remove(Helper.Organization, seatAllocation);
Assert.True(allocation.SeatsCancelled > 0);
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace Octokit.Tests.Integration.Helpers
{
internal sealed class CopilotHelper
{
public static void RemoveUserLicense(IConnection connection, string organization, string userLogin)
{
var client = new GitHubClient(connection);
client.Copilot.Licensing.Remove(organization, userLogin).Wait(TimeSpan.FromSeconds(15));
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace Octokit.Tests.Integration.Helpers
{
internal sealed class CopilotUserLicenseContext : IDisposable
{
internal CopilotUserLicenseContext(IConnection connection, string organization, string user)
{
_connection = connection;
Organization = organization;
UserLogin = user;
}
private readonly IConnection _connection;
internal string Organization { get; }
internal string UserLogin { get; private set; }
public void Dispose()
{
CopilotHelper.RemoveUserLicense(_connection, Organization, UserLogin);
}
}
}

View File

@@ -142,6 +142,13 @@ namespace Octokit.Tests.Integration.Helpers
return new EnterpriseUserContext(client.Connection, user);
}
internal static async Task<CopilotUserLicenseContext> CreateCopilotUserLicenseContext(this IGitHubClient client, string organization, string userName)
{
await client.Copilot.Licensing.Assign(organization, userName);
return new CopilotUserLicenseContext(client.Connection, organization, userName);
}
internal static async Task<PublicKeyContext> CreatePublicKeyContext(this IGitHubClient client)
{
// Create a key

View File

@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using NSubstitute;
using Xunit;
namespace Octokit.Tests.Clients
{
public class CopilotClientTests
{
private const string orgName = "test";
public class TheGetCopilotBillingSettingsMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new CopilotClient(connection);
var expectedUri = $"orgs/{orgName}/copilot/billing";
client.GetSummaryForOrganization("test");
connection.Received().Get<BillingSettings>(Arg.Is<Uri>(u => u.ToString() == expectedUri));
}
}
public class TheGetAllCopilotLicensesMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new CopilotClient(connection);
var expectedUri = $"orgs/{orgName}/copilot/billing/seats";
client.Licensing.GetAll("test", new ApiOptions());
connection.Received().GetAll<CopilotSeats>(Arg.Is<Uri>(u => u.ToString() == expectedUri), Arg.Any<ApiOptions>());
}
}
public class TheAssignCopilotLicenseMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new CopilotClient(connection);
var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users";
client.Licensing.Assign(orgName, "copilot-user");
connection.Received().Post<CopilotSeatAllocation>(Arg.Is<Uri>(u => u.ToString() == expectedUri), Arg.Any<UserSeatAllocation>());
}
}
public class TheAssignCopilotLicensesMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new CopilotClient(connection);
var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users";
var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } };
client.Licensing.Assign(orgName, payloadData);
connection.Received().Post<CopilotSeatAllocation>(Arg.Is<Uri>(u => u.ToString() == expectedUri), payloadData);
}
}
public class TheRemoveCopilotLicenseMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new CopilotClient(connection);
var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users";
client.Licensing.Remove(orgName, "copilot-user" );
connection.Received().Delete<CopilotSeatAllocation>(Arg.Is<Uri>(u => u.ToString() == expectedUri), Arg.Any<UserSeatAllocation>());
}
}
public class TheRemoveCopilotLicensesMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new CopilotClient(connection);
var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users";
var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } };
client.Licensing.Remove(orgName, payloadData);
connection.Received().Delete<CopilotSeatAllocation>(Arg.Is<Uri>(u => u.ToString() == expectedUri), payloadData);
}
}
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(
() => new CopilotClient(null));
}
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using NSubstitute;
using Octokit.Reactive;
using Xunit;
namespace Octokit.Tests.Reactive
{
public class ObservableCopilotClientTests
{
private const string orgName = "test";
public class TheGetCopilotBillingSettingsMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var githubClient = Substitute.For<IGitHubClient>();
var client = new ObservableCopilotClient(githubClient);
client.GetSummaryForOrganization("test");
githubClient.Copilot.Received(1).GetSummaryForOrganization(orgName);
}
}
public class TheGetAllCopilotLicensesMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var endpoint = new Uri($"orgs/test/copilot/billing/seats", UriKind.Relative);
var connection = Substitute.For<IConnection>();
var gitHubClient = Substitute.For<IGitHubClient>();
gitHubClient.Connection.Returns(connection);
var client = new ObservableCopilotClient(gitHubClient);
var apiOptions = new ApiOptions() { PageSize = 50, PageCount = 10 };
client.Licensing.GetAll("test", apiOptions);
connection.Received().Get<List<CopilotSeats>>(endpoint,
Arg.Is<IDictionary<string, string>>(d => d.Count > 0));
}
}
public class TheAssignCopilotLicenseMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var githubClient = Substitute.For<IGitHubClient>();
var client = new ObservableCopilotClient(githubClient);
const string expectedUser = "copilot-user";
client.Licensing.Assign(orgName, expectedUser);
githubClient.Copilot.Licensing.Received().Assign(orgName, expectedUser);
}
}
public class TheAssignCopilotLicensesMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var githubClient = Substitute.For<IGitHubClient>();
var client = new ObservableCopilotClient(githubClient);
var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } };
client.Licensing.Assign(orgName, payloadData);
githubClient.Copilot.Licensing.Received().Assign(orgName, payloadData);
}
}
public class TheRemoveCopilotLicenseMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var githubClient = Substitute.For<IGitHubClient>();
var client = new ObservableCopilotClient(githubClient);
const string expectedUser = "copilot-user";
client.Licensing.Remove(orgName, expectedUser);
githubClient.Copilot.Licensing.Received().Remove(orgName, expectedUser);
}
}
public class TheRemoveCopilotLicensesMethod
{
[Fact]
public void RequestsCorrectUrl()
{
var githubClient = Substitute.For<IGitHubClient>();
var client = new ObservableCopilotClient(githubClient);
var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } };
client.Licensing.Remove(orgName, payloadData);
githubClient.Copilot.Licensing.Received().Remove(orgName, payloadData);
}
}
public class TheCtor
{
[Fact]
public void EnsuresNonNullArguments()
{
Assert.Throws<ArgumentNullException>(
() => new ObservableCopilotClient(null));
}
}
}
}

View File

@@ -0,0 +1,42 @@
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// A client for GitHub's Copilot for Business API.
/// Allows listing, creating, and deleting Copilot licenses.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/copilot/copilot-business?apiVersion=2022-11-28">Copilot for Business API documentation</a> for more information.
/// </remarks>
public class CopilotClient : ApiClient, ICopilotClient
{
/// <summary>
/// Instantiates a new GitHub Copilot API client.
/// </summary>
/// <param name="apiConnection"></param>
public CopilotClient(IApiConnection apiConnection) : base(apiConnection)
{
Licensing = new CopilotLicenseClient(apiConnection);
}
/// <summary>
/// Returns a summary of the Copilot for Business configuration for an organization. Includes a seat
/// details summary of the current billing cycle, and the mode of seat management.
/// </summary>
/// <param name="organization">the organization name to retrieve billing settings for</param>
/// <returns>A <see cref="BillingSettings"/> instance</returns>
[ManualRoute("GET", "/orgs/{org}/copilot/billing")]
public async Task<BillingSettings> GetSummaryForOrganization(string organization)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
return await ApiConnection.Get<BillingSettings>(ApiUrls.CopilotBillingSettings(organization));
}
/// <summary>
/// Client for maintaining Copilot licenses for users in an organization.
/// </summary>
public ICopilotLicenseClient Licensing { get; private set; }
}
}

View File

@@ -0,0 +1,107 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Octokit;
using Octokit.Models.Request.Enterprise;
/// <summary>
/// A client for managing licenses for GitHub Copilot for Business
/// </summary>
public class CopilotLicenseClient : ApiClient, ICopilotLicenseClient
{
/// <summary>
/// Initializes a new GitHub Copilot for Business License API client.
/// </summary>
/// <param name="apiConnection">An API connection</param>
public CopilotLicenseClient(IApiConnection apiConnection) : base(apiConnection)
{
}
/// <summary>
/// Removes a license for a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to remove a license from</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
[ManualRoute("DELETE", "/orgs/{org}/copilot/billing/selected_users")]
public async Task<CopilotSeatAllocation> Remove(string organization, string userName)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userName, nameof(userName));
var allocation = new UserSeatAllocation
{
SelectedUsernames = new[] { userName }
};
return await Remove(organization, allocation);
}
/// <summary>
/// Removes a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to remove licenses for</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
[ManualRoute("DELETE", "/orgs/{org}/copilot/billing/selected_users")]
public async Task<CopilotSeatAllocation> Remove(string organization, UserSeatAllocation userSeatAllocation)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));
return await ApiConnection.Delete<CopilotSeatAllocation>(ApiUrls.CopilotBillingLicense(organization), userSeatAllocation);
}
/// <summary>
/// Assigns a license to a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to add a license to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
[ManualRoute("POST", "/orgs/{org}/copilot/billing/selected_users")]
public async Task<CopilotSeatAllocation> Assign(string organization, string userName)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userName, nameof(userName));
var allocation = new UserSeatAllocation
{
SelectedUsernames = new[] { userName }
};
return await Assign(organization, allocation);
}
/// <summary>
/// Assigns a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to add licenses to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
[ManualRoute("POST", "/orgs/{org}/copilot/billing/selected_users")]
public async Task<CopilotSeatAllocation> Assign(string organization, UserSeatAllocation userSeatAllocation)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));
return await ApiConnection.Post<CopilotSeatAllocation>(ApiUrls.CopilotBillingLicense(organization), userSeatAllocation);
}
/// <summary>
/// Gets all of the currently allocated licenses for an organization
/// </summary>
/// <param name="organization">The organization</param>
/// <param name="options">Options to control page size when making API requests</param>
/// <returns>A list of <see cref="CopilotSeats"/> instance containing the currently allocated user licenses.</returns>
[ManualRoute("GET", "/orgs/{org}/copilot/billing/seats")]
public async Task<IReadOnlyList<CopilotSeats>> GetAll(string organization, ApiOptions options)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
var extendedOptions = new ApiOptionsExtended()
{
PageSize = options.PageSize
};
return await ApiConnection.GetAll<CopilotSeats>(ApiUrls.CopilotAllocatedLicenses(organization), extendedOptions);
}
}

View File

@@ -0,0 +1,23 @@
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// Access GitHub's Copilot for Business API.
/// </summary>
public interface ICopilotClient
{
/// <summary>
/// Returns a summary of the Copilot for Business configuration for an organization. Includes a seat
/// details summary of the current billing cycle, and the mode of seat management.
/// </summary>
/// <param name="organization">the organization name to retrieve billing settings for</param>
/// <returns>A <see cref="BillingSettings"/> instance</returns>
Task<BillingSettings> GetSummaryForOrganization(string organization);
/// <summary>
/// For checking and managing licenses for GitHub Copilot for Business
/// </summary>
ICopilotLicenseClient Licensing { get; }
}
}

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Octokit
{
/// <summary>
/// A client for managing licenses for GitHub Copilot for Business
/// </summary>
public interface ICopilotLicenseClient
{
/// <summary>
/// Removes a license for a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to remove a license from</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
Task<CopilotSeatAllocation> Remove(string organization, string userName);
/// <summary>
/// Removes a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to remove licenses for</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
Task<CopilotSeatAllocation> Remove(string organization, UserSeatAllocation userSeatAllocation);
/// <summary>
/// Assigns a license to a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to add a license to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
Task<CopilotSeatAllocation> Assign(string organization, string userName);
/// <summary>
/// Assigns a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to add licenses to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
Task<CopilotSeatAllocation> Assign(string organization, UserSeatAllocation userSeatAllocation);
/// <summary>
/// Gets all of the currently allocated licenses for an organization
/// </summary>
/// <param name="organization">The organization</param>
/// <param name="options">The api options to use when making the API call, such as paging</param>
/// <returns>A <see cref="CopilotSeats"/> instance containing the currently allocated user licenses</returns>
Task<IReadOnlyList<CopilotSeats>> GetAll(string organization, ApiOptions options);
}
}

View File

@@ -121,6 +121,7 @@ namespace Octokit
Meta = new MetaClient(apiConnection);
Actions = new ActionsClient(apiConnection);
Codespaces = new CodespacesClient(apiConnection);
Copilot = new CopilotClient(apiConnection);
}
/// <summary>
@@ -396,6 +397,11 @@ namespace Octokit
public ICodespacesClient Codespaces { get; private set; }
/// <summary>
/// Access GitHub's Copilot for Business API
/// </summary>
public ICopilotClient Copilot { get; private set; }
static Uri FixUpBaseUri(Uri uri)
{
Ensure.ArgumentNotNull(uri, nameof(uri));

View File

@@ -5482,6 +5482,36 @@ namespace Octokit
return "orgs/{0}/actions/runner-groups/{1}/repositories".FormatUri(org, runnerGroupId);
}
/// <summary>
/// Returns the <see cref="Uri"/> that handles adding or removing of copilot licenses for an organisation
/// </summary>
/// <param name="org">The name of the organization</param>
/// <returns>A Uri Instance</returns>
public static Uri CopilotBillingLicense(string org)
{
return $"orgs/{org}/copilot/billing/selected_users".FormatUri(org);
}
/// <summary>
/// Returns the <see cref="Uri"/> that handles reading copilot billing settings for an organization
/// </summary>
/// <param name="org">The name of the organization</param>
/// <returns>A Uri Instance</returns>
public static Uri CopilotBillingSettings(string org)
{
return $"orgs/{org}/copilot/billing".FormatUri(org);
}
/// <summary>
/// Returns the <see cref="Uri"/> that allows for searching across all licenses for an organisation
/// </summary>
/// <param name="org"></param>
/// <returns></returns>
public static Uri CopilotAllocatedLicenses(string org)
{
return $"orgs/{org}/copilot/billing/seats".FormatUri(org);
}
public static Uri Codespaces()
{
return _currentUserAllCodespaces;

View File

@@ -217,5 +217,10 @@ namespace Octokit
IEmojisClient Emojis { get; }
ICodespacesClient Codespaces { get; }
/// <summary>
/// Access to the GitHub Copilot for Business API
/// </summary>
ICopilotClient Copilot { get; }
}
}

View File

@@ -0,0 +1,47 @@
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
/// <summary>
/// The billing settings for a Copilot-enabled organization.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public partial class BillingSettings
{
public BillingSettings()
{
}
public BillingSettings(SeatBreakdown seatBreakdown, string seatManagementSetting, string publicCodeSuggestions)
{
SeatBreakdown = seatBreakdown;
SeatManagementSetting = seatManagementSetting;
PublicCodeSuggestions = publicCodeSuggestions;
}
/// <summary>
/// A summary of the current billing settings for the organization.
/// </summary>
public SeatBreakdown SeatBreakdown { get; private set; }
/// <summary>
/// A string that indicates how seats are billed for the organization.
/// </summary>
public string SeatManagementSetting { get; private set; }
/// <summary>
/// A string that indicates if public code suggestions are enabled or blocked for the organization.
/// </summary>
public string PublicCodeSuggestions { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "SeatManagementSetting: {0}, PublicCodeSuggestions: {1}", SeatManagementSetting, PublicCodeSuggestions);
}
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
/// <summary>
/// Details about a Copilot seat allocated to an organization member.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class CopilotSeat
{
public CopilotSeat()
{
}
public CopilotSeat(DateTimeOffset? createdAt, DateTimeOffset? updatedAt, string pendingCancellationDate, DateTimeOffset? lastActivityAt, string lastActivityEditor, User assignee, Team assigningTeam)
{
CreatedAt = createdAt;
UpdatedAt = updatedAt;
PendingCancellationDate = pendingCancellationDate;
LastActivityAt = lastActivityAt;
LastActivityEditor = lastActivityEditor;
Assignee = assignee;
AssigningTeam = assigningTeam;
}
/// <summary>
/// Timestamp of when the assignee was last granted access to GitHub Copilot, in ISO 8601 format
/// </summary>
public DateTimeOffset? CreatedAt { get; private set; }
/// <summary>
/// Timestamp of when the assignee's GitHub Copilot access was last updated, in ISO 8601 format.
/// </summary>
public DateTimeOffset? UpdatedAt { get; private set; }
/// <summary>
/// The pending cancellation date for the seat, in `YYYY-MM-DD` format. This will be null unless
/// the assignee's Copilot access has been canceled during the current billing cycle.
/// If the seat has been cancelled, this corresponds to the start of the organization's next billing cycle.
/// </summary>
public string PendingCancellationDate { get; private set; }
/// <summary>
/// Timestamp of user's last GitHub Copilot activity, in ISO 8601 format.
/// </summary>
public DateTimeOffset? LastActivityAt { get; private set; }
/// <summary>
/// Last editor that was used by the user for a GitHub Copilot completion.
/// </summary>
public string LastActivityEditor { get; private set; }
/// <summary>
/// The assignee that has been granted access to GitHub Copilot
/// </summary>
public User Assignee { get; private set; }
/// <summary>
/// The team that granted access to GitHub Copilot to the assignee. This will be null if the
/// user was assigned a seat individually.
/// </summary>
public Team AssigningTeam { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "User: {0}, CreatedAt: {1}", Assignee.Name, CreatedAt);
}
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
/// <summary>
/// Holds information about an API response after adding or removing seats for a Copilot-enabled organization.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class CopilotSeatAllocation
{
public CopilotSeatAllocation()
{
}
public CopilotSeatAllocation(long seatsCancelled, long seatsCreated)
{
SeatsCancelled = seatsCancelled;
SeatsCreated = seatsCreated;
}
/// <summary>
/// The total number of seat assignments removed.
/// </summary>
public long SeatsCancelled { get; private set; }
/// <summary>
/// The total number of seat assignments created.
/// </summary>
public long SeatsCreated { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "SeatsCancelled: {0}, SeatsCreated: {1}", SeatsCancelled, SeatsCreated);
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class CopilotSeats
{
public CopilotSeats()
{
}
public CopilotSeats(int totalSeats, IReadOnlyList<CopilotSeat> seats)
{
TotalSeats = totalSeats;
Seats = seats;
}
/// <summary>
/// Total number of Copilot For Business seats for the organization currently being billed
/// </summary>
public long TotalSeats { get; private set; }
/// <summary>
/// Information about a Copilot Business seat assignment for a user, team, or organization.
/// </summary>
public IReadOnlyList<CopilotSeat> Seats { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "TotalSeats: {0}", TotalSeats);
}
}
}
}

View File

@@ -0,0 +1,64 @@
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
/// <summary>
/// The breakdown of Copilot Business seats for the organization.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class SeatBreakdown
{
public SeatBreakdown()
{
}
public SeatBreakdown(long total, long addedThisCycle, long pendingInvitation, long pendingCancellation, long activeThisCycle, long inactiveThisCycle)
{
Total = total;
AddedThisCycle = addedThisCycle;
PendingInvitation = pendingInvitation;
PendingCancellation = pendingCancellation;
ActiveThisCycle = activeThisCycle;
InactiveThisCycle = inactiveThisCycle;
}
/// <summary>
/// The total number of seats being billed for the organization as of the current billing cycle.
/// </summary>
public long Total { get; private set; }
/// <summary>
/// Seats added during the current billing cycle
/// </summary>
public long AddedThisCycle { get; private set; }
/// <summary>
/// The number of seats that have been assigned to users that have not yet accepted an invitation to this organization.
/// </summary>
public long PendingInvitation { get; private set; }
/// <summary>
/// The number of seats that are pending cancellation at the end of the current billing cycle.
/// </summary>
public long PendingCancellation { get; private set; }
/// <summary>
/// The number of seats that have used Copilot during the current billing cycle.
/// </summary>
public long ActiveThisCycle { get; private set; }
/// <summary>
/// The number of seats that have not used Copilot during the current billing cycle
/// </summary>
public long InactiveThisCycle { get; private set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Total: {0}", Total);
}
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
/// <summary>
/// Holds information about user names to be added or removed from a Copilot-enabled organization.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class UserSeatAllocation
{
/// <summary>
/// One or more usernames to be added or removed from a Copilot-enabled organization.
/// </summary>
public string[] SelectedUsernames { get; set; }
internal string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "SelectedUsernames: {0}", string.Join(",", SelectedUsernames));
}
}
}
}