diff --git a/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs b/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs
new file mode 100644
index 0000000000..6366ccdcab
--- /dev/null
+++ b/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Octokit.Reactive
+{
+ ///
+ /// Access GitHub's Copilot for Business API.
+ ///
+ public interface IObservableCopilotClient
+ {
+ ///
+ /// 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.
+ ///
+ /// the organization name to retrieve billing settings for
+ /// A instance
+ IObservable GetSummaryForOrganization(string organization);
+
+ ///
+ /// For checking and managing licenses for GitHub Copilot for Business
+ ///
+ IObservableCopilotLicenseClient Licensing { get; }
+ }
+}
diff --git a/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs b/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs
new file mode 100644
index 0000000000..333bd93f29
--- /dev/null
+++ b/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+
+namespace Octokit.Reactive
+{
+ ///
+ /// A client for managing licenses for GitHub Copilot for Business
+ ///
+ public interface IObservableCopilotLicenseClient
+ {
+ ///
+ /// Removes a license for a user
+ ///
+ /// The organization name
+ /// The github users profile name to remove a license from
+ /// A instance with results
+ IObservable Remove(string organization, string userName);
+
+ ///
+ /// Removes a license for one or many users
+ ///
+ /// The organization name
+ /// A instance, containing the names of the user(s) to remove licenses for
+ /// A instance with results
+ IObservable Remove(string organization, UserSeatAllocation userSeatAllocation);
+
+ ///
+ /// Assigns a license to a user
+ ///
+ /// The organization name
+ /// The github users profile name to add a license to
+ /// A instance with results
+ IObservable Assign(string organization, string userName);
+
+ ///
+ /// Assigns a license for one or many users
+ ///
+ /// The organization name
+ /// A instance, containing the names of the user(s) to add licenses to
+ /// A instance with results
+ IObservable Assign(string organization, UserSeatAllocation userSeatAllocation);
+
+ ///
+ /// Gets all of the currently allocated licenses for an organization
+ ///
+ /// The organization
+ /// The api options to use when making the API call, such as paging
+ /// A instance containing the currently allocated user licenses
+ IObservable GetAll(string organization, ApiOptions options);
+ }
+}
diff --git a/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs b/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs
new file mode 100644
index 0000000000..bd27fb3a37
--- /dev/null
+++ b/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Reactive.Threading.Tasks;
+
+namespace Octokit.Reactive
+{
+ ///
+ /// A client for GitHub's Copilot for Business API.
+ /// Allows listing, creating, and deleting Copilot licenses.
+ ///
+ ///
+ /// See the Copilot for Business API documentation for more information.
+ ///
+ public class ObservableCopilotClient : IObservableCopilotClient
+ {
+ private readonly ICopilotClient _client;
+
+ ///
+ /// Instantiates a new GitHub Copilot API client.
+ ///
+ ///
+ public ObservableCopilotClient(IGitHubClient client)
+ {
+ Ensure.ArgumentNotNull(client, nameof(client));
+
+ _client = client.Copilot;
+ Licensing = new ObservableCopilotLicenseClient(client);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// the organization name to retrieve billing settings for
+ /// A instance
+ public IObservable GetSummaryForOrganization(string organization)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+
+ return _client.GetSummaryForOrganization(organization).ToObservable();
+ }
+
+ ///
+ /// Client for maintaining Copilot licenses for users in an organization.
+ ///
+ public IObservableCopilotLicenseClient Licensing { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs b/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs
new file mode 100644
index 0000000000..ead9377e91
--- /dev/null
+++ b/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Reactive.Threading.Tasks;
+using Octokit;
+using Octokit.Reactive;
+using Octokit.Reactive.Internal;
+
+///
+/// A client for managing licenses for GitHub Copilot for Business
+///
+public class ObservableCopilotLicenseClient : IObservableCopilotLicenseClient
+{
+ private readonly ICopilotLicenseClient _client;
+ private readonly IConnection _connection;
+
+ public ObservableCopilotLicenseClient(IGitHubClient client)
+ {
+ _client = client.Copilot.Licensing;
+ _connection = client.Connection;
+ }
+
+ ///
+ /// Removes a license for a user
+ ///
+ /// The organization name
+ /// The github users profile name to remove a license from
+ /// A instance with results
+ public IObservable Remove(string organization, string userName)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+ Ensure.ArgumentNotNull(userName, nameof(userName));
+
+ return _client.Remove(organization, userName).ToObservable();
+ }
+
+ ///
+ /// Removes a license for one or many users
+ ///
+ /// The organization name
+ /// A instance, containing the names of the user(s) to remove licenses for
+ /// A instance with results
+ public IObservable Remove(string organization, UserSeatAllocation userSeatAllocation)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+ Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));
+
+ return _client.Remove(organization, userSeatAllocation).ToObservable();
+ }
+
+ ///
+ /// Assigns a license to a user
+ ///
+ /// The organization name
+ /// The github users profile name to add a license to
+ /// A instance with results
+ public IObservable Assign(string organization, string userName)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+ Ensure.ArgumentNotNull(userName, nameof(userName));
+
+ return _client.Assign(organization, userName).ToObservable();
+ }
+
+ ///
+ /// Assigns a license for one or many users
+ ///
+ /// The organization name
+ /// A instance, containing the names of the user(s) to add licenses to
+ /// A instance with results
+ public IObservable Assign(string organization, UserSeatAllocation userSeatAllocation)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+ Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));
+
+ return _client.Assign(organization, userSeatAllocation).ToObservable();
+ }
+
+ ///
+ /// Gets all of the currently allocated licenses for an organization
+ ///
+ /// The organization
+ /// Options to control page size when making API requests
+ /// A list of instance containing the currently allocated user licenses.
+ public IObservable GetAll(string organization, ApiOptions options)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+ Ensure.ArgumentNotNull(options, nameof(options));
+
+ return _connection.GetAndFlattenAllPages( ApiUrls.CopilotAllocatedLicenses(organization), options);
+ }
+}
diff --git a/Octokit.Reactive/IObservableGitHubClient.cs b/Octokit.Reactive/IObservableGitHubClient.cs
index bc610f7fca..68fb81a6a5 100644
--- a/Octokit.Reactive/IObservableGitHubClient.cs
+++ b/Octokit.Reactive/IObservableGitHubClient.cs
@@ -43,5 +43,6 @@ public interface IObservableGitHubClient : IApiInfoProvider
IObservableMetaClient Meta { get; }
IObservableActionsClient Actions { get; }
IObservableCodespacesClient Codespaces { get; }
+ IObservableCopilotClient Copilot { get; }
}
}
diff --git a/Octokit.Reactive/ObservableGitHubClient.cs b/Octokit.Reactive/ObservableGitHubClient.cs
index 7b8da221a3..2713ad12ce 100644
--- a/Octokit.Reactive/ObservableGitHubClient.cs
+++ b/Octokit.Reactive/ObservableGitHubClient.cs
@@ -58,6 +58,7 @@ public ObservableGitHubClient(IGitHubClient gitHubClient)
Meta = new ObservableMetaClient(gitHubClient);
Actions = new ObservableActionsClient(gitHubClient);
Codespaces = new ObservableCodespacesClient(gitHubClient);
+ Copilot = new ObservableCopilotClient(gitHubClient);
}
public IConnection Connection
@@ -105,8 +106,9 @@ public void SetRequestTimeout(TimeSpan timeout)
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; }
+
///
/// Gets the latest API Info - this will be null if no API calls have been made
///
diff --git a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs
new file mode 100644
index 0000000000..2a42d0fc81
--- /dev/null
+++ b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs
@@ -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);
+ }
+ }
+ }
+ }
+}
diff --git a/Octokit.Tests.Integration/Helpers/CopilotHelper.cs b/Octokit.Tests.Integration/Helpers/CopilotHelper.cs
new file mode 100644
index 0000000000..6c18153c2a
--- /dev/null
+++ b/Octokit.Tests.Integration/Helpers/CopilotHelper.cs
@@ -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));
+ }
+ }
+}
diff --git a/Octokit.Tests.Integration/Helpers/CopilotUserLicenseContext.cs b/Octokit.Tests.Integration/Helpers/CopilotUserLicenseContext.cs
new file mode 100644
index 0000000000..a6e7488984
--- /dev/null
+++ b/Octokit.Tests.Integration/Helpers/CopilotUserLicenseContext.cs
@@ -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);
+ }
+ }
+}
diff --git a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs
index 941aeb9371..37e5e9599e 100644
--- a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs
+++ b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs
@@ -141,6 +141,13 @@ internal static async Task CreateEnterpriseUserContext(th
return new EnterpriseUserContext(client.Connection, user);
}
+
+ internal static async Task 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 CreatePublicKeyContext(this IGitHubClient client)
{
diff --git a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs
new file mode 100644
index 0000000000..bac7083935
--- /dev/null
+++ b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs
@@ -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();
+ var client = new CopilotClient(connection);
+
+ var expectedUri = $"orgs/{orgName}/copilot/billing";
+ client.GetSummaryForOrganization("test");
+
+ connection.Received().Get(Arg.Is(u => u.ToString() == expectedUri));
+ }
+ }
+
+ public class TheGetAllCopilotLicensesMethod
+ {
+ [Fact]
+ public void RequestsCorrectUrl()
+ {
+ var connection = Substitute.For();
+ var client = new CopilotClient(connection);
+
+ var expectedUri = $"orgs/{orgName}/copilot/billing/seats";
+ client.Licensing.GetAll("test", new ApiOptions());
+
+ connection.Received().GetAll(Arg.Is(u => u.ToString() == expectedUri), Arg.Any());
+ }
+ }
+
+ public class TheAssignCopilotLicenseMethod
+ {
+ [Fact]
+ public void RequestsCorrectUrl()
+ {
+ var connection = Substitute.For();
+ var client = new CopilotClient(connection);
+ var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users";
+
+ client.Licensing.Assign(orgName, "copilot-user");
+
+ connection.Received().Post(Arg.Is(u => u.ToString() == expectedUri), Arg.Any());
+ }
+ }
+
+ public class TheAssignCopilotLicensesMethod
+ {
+ [Fact]
+ public void RequestsCorrectUrl()
+ {
+ var connection = Substitute.For();
+ 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(Arg.Is(u => u.ToString() == expectedUri), payloadData);
+ }
+ }
+
+ public class TheRemoveCopilotLicenseMethod
+ {
+ [Fact]
+ public void RequestsCorrectUrl()
+ {
+ var connection = Substitute.For();
+ var client = new CopilotClient(connection);
+ var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users";
+
+ client.Licensing.Remove(orgName, "copilot-user" );
+
+ connection.Received().Delete(Arg.Is(u => u.ToString() == expectedUri), Arg.Any());
+ }
+ }
+
+ public class TheRemoveCopilotLicensesMethod
+ {
+ [Fact]
+ public void RequestsCorrectUrl()
+ {
+ var connection = Substitute.For();
+ 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(Arg.Is(u => u.ToString() == expectedUri), payloadData);
+ }
+ }
+
+ public class TheCtor
+ {
+ [Fact]
+ public void EnsuresNonNullArguments()
+ {
+ Assert.Throws(
+ () => new CopilotClient(null));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs b/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs
new file mode 100644
index 0000000000..078633bb50
--- /dev/null
+++ b/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs
@@ -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();
+ 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();
+ var gitHubClient = Substitute.For();
+ 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>(endpoint,
+ Arg.Is>(d => d.Count > 0));
+ }
+ }
+
+ public class TheAssignCopilotLicenseMethod
+ {
+ [Fact]
+ public void RequestsCorrectUrl()
+ {
+ var githubClient = Substitute.For();
+ 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();
+ 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();
+ 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();
+ 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(
+ () => new ObservableCopilotClient(null));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Octokit/Clients/Copilot/CopilotClient.cs b/Octokit/Clients/Copilot/CopilotClient.cs
new file mode 100644
index 0000000000..30c167ac8a
--- /dev/null
+++ b/Octokit/Clients/Copilot/CopilotClient.cs
@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+
+namespace Octokit
+{
+ ///
+ /// A client for GitHub's Copilot for Business API.
+ /// Allows listing, creating, and deleting Copilot licenses.
+ ///
+ ///
+ /// See the Copilot for Business API documentation for more information.
+ ///
+ public class CopilotClient : ApiClient, ICopilotClient
+ {
+ ///
+ /// Instantiates a new GitHub Copilot API client.
+ ///
+ ///
+ public CopilotClient(IApiConnection apiConnection) : base(apiConnection)
+ {
+ Licensing = new CopilotLicenseClient(apiConnection);
+ }
+
+ ///
+ /// 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.
+ ///
+ /// the organization name to retrieve billing settings for
+ /// A instance
+ [ManualRoute("GET", "/orgs/{org}/copilot/billing")]
+ public async Task GetSummaryForOrganization(string organization)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+
+ return await ApiConnection.Get(ApiUrls.CopilotBillingSettings(organization));
+ }
+
+ ///
+ /// Client for maintaining Copilot licenses for users in an organization.
+ ///
+ public ICopilotLicenseClient Licensing { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Octokit/Clients/Copilot/CopilotLicenseClient.cs b/Octokit/Clients/Copilot/CopilotLicenseClient.cs
new file mode 100644
index 0000000000..aaf61977aa
--- /dev/null
+++ b/Octokit/Clients/Copilot/CopilotLicenseClient.cs
@@ -0,0 +1,107 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Octokit;
+using Octokit.Models.Request.Enterprise;
+
+///
+/// A client for managing licenses for GitHub Copilot for Business
+///
+public class CopilotLicenseClient : ApiClient, ICopilotLicenseClient
+{
+ ///
+ /// Initializes a new GitHub Copilot for Business License API client.
+ ///
+ /// An API connection
+ public CopilotLicenseClient(IApiConnection apiConnection) : base(apiConnection)
+ {
+ }
+
+ ///
+ /// Removes a license for a user
+ ///
+ /// The organization name
+ /// The github users profile name to remove a license from
+ /// A instance with results
+ [ManualRoute("DELETE", "/orgs/{org}/copilot/billing/selected_users")]
+ public async Task 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);
+ }
+
+ ///
+ /// Removes a license for one or many users
+ ///
+ /// The organization name
+ /// A instance, containing the names of the user(s) to remove licenses for
+ /// A instance with results
+ [ManualRoute("DELETE", "/orgs/{org}/copilot/billing/selected_users")]
+ public async Task Remove(string organization, UserSeatAllocation userSeatAllocation)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+ Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));
+
+ return await ApiConnection.Delete(ApiUrls.CopilotBillingLicense(organization), userSeatAllocation);
+ }
+
+ ///
+ /// Assigns a license to a user
+ ///
+ /// The organization name
+ /// The github users profile name to add a license to
+ /// A instance with results
+ [ManualRoute("POST", "/orgs/{org}/copilot/billing/selected_users")]
+ public async Task 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);
+ }
+
+ ///
+ /// Assigns a license for one or many users
+ ///
+ /// The organization name
+ /// A instance, containing the names of the user(s) to add licenses to
+ /// A instance with results
+ [ManualRoute("POST", "/orgs/{org}/copilot/billing/selected_users")]
+ public async Task Assign(string organization, UserSeatAllocation userSeatAllocation)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+ Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));
+
+ return await ApiConnection.Post(ApiUrls.CopilotBillingLicense(organization), userSeatAllocation);
+ }
+
+ ///
+ /// Gets all of the currently allocated licenses for an organization
+ ///
+ /// The organization
+ /// Options to control page size when making API requests
+ /// A list of instance containing the currently allocated user licenses.
+ [ManualRoute("GET", "/orgs/{org}/copilot/billing/seats")]
+ public async Task> GetAll(string organization, ApiOptions options)
+ {
+ Ensure.ArgumentNotNull(organization, nameof(organization));
+
+ var extendedOptions = new ApiOptionsExtended()
+ {
+ PageSize = options.PageSize
+ };
+
+ return await ApiConnection.GetAll(ApiUrls.CopilotAllocatedLicenses(organization), extendedOptions);
+ }
+}
diff --git a/Octokit/Clients/Copilot/ICopilotClient.cs b/Octokit/Clients/Copilot/ICopilotClient.cs
new file mode 100644
index 0000000000..dd72ba7704
--- /dev/null
+++ b/Octokit/Clients/Copilot/ICopilotClient.cs
@@ -0,0 +1,23 @@
+using System.Threading.Tasks;
+
+namespace Octokit
+{
+ ///
+ /// Access GitHub's Copilot for Business API.
+ ///
+ public interface ICopilotClient
+ {
+ ///
+ /// 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.
+ ///
+ /// the organization name to retrieve billing settings for
+ /// A instance
+ Task GetSummaryForOrganization(string organization);
+
+ ///
+ /// For checking and managing licenses for GitHub Copilot for Business
+ ///
+ ICopilotLicenseClient Licensing { get; }
+ }
+}
diff --git a/Octokit/Clients/Copilot/ICopilotLicenseClient.cs b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs
new file mode 100644
index 0000000000..8c6ed2aebe
--- /dev/null
+++ b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Octokit
+{
+ ///
+ /// A client for managing licenses for GitHub Copilot for Business
+ ///
+ public interface ICopilotLicenseClient
+ {
+ ///
+ /// Removes a license for a user
+ ///
+ /// The organization name
+ /// The github users profile name to remove a license from
+ /// A instance with results
+ Task Remove(string organization, string userName);
+
+ ///
+ /// Removes a license for one or many users
+ ///
+ /// The organization name
+ /// A instance, containing the names of the user(s) to remove licenses for
+ /// A instance with results
+ Task Remove(string organization, UserSeatAllocation userSeatAllocation);
+
+ ///
+ /// Assigns a license to a user
+ ///
+ /// The organization name
+ /// The github users profile name to add a license to
+ /// A instance with results
+ Task Assign(string organization, string userName);
+
+ ///
+ /// Assigns a license for one or many users
+ ///
+ /// The organization name
+ /// A instance, containing the names of the user(s) to add licenses to
+ /// A instance with results
+ Task Assign(string organization, UserSeatAllocation userSeatAllocation);
+
+ ///
+ /// Gets all of the currently allocated licenses for an organization
+ ///
+ /// The organization
+ /// The api options to use when making the API call, such as paging
+ /// A instance containing the currently allocated user licenses
+ Task> GetAll(string organization, ApiOptions options);
+ }
+}
diff --git a/Octokit/GitHubClient.cs b/Octokit/GitHubClient.cs
index 2a839b3b1f..0839d4c400 100644
--- a/Octokit/GitHubClient.cs
+++ b/Octokit/GitHubClient.cs
@@ -121,6 +121,7 @@ public GitHubClient(IConnection connection)
Meta = new MetaClient(apiConnection);
Actions = new ActionsClient(apiConnection);
Codespaces = new CodespacesClient(apiConnection);
+ Copilot = new CopilotClient(apiConnection);
}
///
@@ -395,6 +396,11 @@ public Uri BaseAddress
public IActionsClient Actions { get; private set; }
public ICodespacesClient Codespaces { get; private set; }
+
+ ///
+ /// Access GitHub's Copilot for Business API
+ ///
+ public ICopilotClient Copilot { get; private set; }
static Uri FixUpBaseUri(Uri uri)
{
diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs
index 41cb5ca958..98722edd20 100644
--- a/Octokit/Helpers/ApiUrls.cs
+++ b/Octokit/Helpers/ApiUrls.cs
@@ -5481,7 +5481,37 @@ public static Uri ActionsListOrganizationRunnerGroupRepositories(string org, lon
{
return "orgs/{0}/actions/runner-groups/{1}/repositories".FormatUri(org, runnerGroupId);
}
-
+
+ ///
+ /// Returns the that handles adding or removing of copilot licenses for an organisation
+ ///
+ /// The name of the organization
+ /// A Uri Instance
+ public static Uri CopilotBillingLicense(string org)
+ {
+ return $"orgs/{org}/copilot/billing/selected_users".FormatUri(org);
+ }
+
+ ///
+ /// Returns the that handles reading copilot billing settings for an organization
+ ///
+ /// The name of the organization
+ /// A Uri Instance
+ public static Uri CopilotBillingSettings(string org)
+ {
+ return $"orgs/{org}/copilot/billing".FormatUri(org);
+ }
+
+ ///
+ /// Returns the that allows for searching across all licenses for an organisation
+ ///
+ ///
+ ///
+ public static Uri CopilotAllocatedLicenses(string org)
+ {
+ return $"orgs/{org}/copilot/billing/seats".FormatUri(org);
+ }
+
public static Uri Codespaces()
{
return _currentUserAllCodespaces;
diff --git a/Octokit/IGitHubClient.cs b/Octokit/IGitHubClient.cs
index 0fe8d3d3af..91ef11b772 100644
--- a/Octokit/IGitHubClient.cs
+++ b/Octokit/IGitHubClient.cs
@@ -217,5 +217,10 @@ public interface IGitHubClient : IApiInfoProvider
IEmojisClient Emojis { get; }
ICodespacesClient Codespaces { get; }
+
+ ///
+ /// Access to the GitHub Copilot for Business API
+ ///
+ ICopilotClient Copilot { get; }
}
}
diff --git a/Octokit/Models/Response/Copilot/BillingSettings.cs b/Octokit/Models/Response/Copilot/BillingSettings.cs
new file mode 100644
index 0000000000..458e20ce40
--- /dev/null
+++ b/Octokit/Models/Response/Copilot/BillingSettings.cs
@@ -0,0 +1,47 @@
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Octokit
+{
+ ///
+ /// The billing settings for a Copilot-enabled organization.
+ ///
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ public partial class BillingSettings
+ {
+ public BillingSettings()
+ {
+ }
+
+ public BillingSettings(SeatBreakdown seatBreakdown, string seatManagementSetting, string publicCodeSuggestions)
+ {
+ SeatBreakdown = seatBreakdown;
+ SeatManagementSetting = seatManagementSetting;
+ PublicCodeSuggestions = publicCodeSuggestions;
+ }
+
+ ///
+ /// A summary of the current billing settings for the organization.
+ ///
+ public SeatBreakdown SeatBreakdown { get; private set; }
+
+ ///
+ /// A string that indicates how seats are billed for the organization.
+ ///
+ public string SeatManagementSetting { get; private set; }
+
+ ///
+ /// A string that indicates if public code suggestions are enabled or blocked for the organization.
+ ///
+ public string PublicCodeSuggestions { get; private set; }
+
+ internal string DebuggerDisplay
+ {
+ get
+ {
+ return string.Format(CultureInfo.InvariantCulture, "SeatManagementSetting: {0}, PublicCodeSuggestions: {1}", SeatManagementSetting, PublicCodeSuggestions);
+ }
+ }
+ }
+}
+
diff --git a/Octokit/Models/Response/Copilot/CopilotSeat.cs b/Octokit/Models/Response/Copilot/CopilotSeat.cs
new file mode 100644
index 0000000000..3be9cb673b
--- /dev/null
+++ b/Octokit/Models/Response/Copilot/CopilotSeat.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Octokit
+{
+ ///
+ /// Details about a Copilot seat allocated to an organization member.
+ ///
+ [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;
+ }
+
+ ///
+ /// Timestamp of when the assignee was last granted access to GitHub Copilot, in ISO 8601 format
+ ///
+ public DateTimeOffset? CreatedAt { get; private set; }
+
+ ///
+ /// Timestamp of when the assignee's GitHub Copilot access was last updated, in ISO 8601 format.
+ ///
+ public DateTimeOffset? UpdatedAt { get; private set; }
+
+ ///
+ /// 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.
+ ///
+ public string PendingCancellationDate { get; private set; }
+
+ ///
+ /// Timestamp of user's last GitHub Copilot activity, in ISO 8601 format.
+ ///
+ public DateTimeOffset? LastActivityAt { get; private set; }
+
+ ///
+ /// Last editor that was used by the user for a GitHub Copilot completion.
+ ///
+ public string LastActivityEditor { get; private set; }
+
+ ///
+ /// The assignee that has been granted access to GitHub Copilot
+ ///
+ public User Assignee { get; private set; }
+
+ ///
+ /// The team that granted access to GitHub Copilot to the assignee. This will be null if the
+ /// user was assigned a seat individually.
+ ///
+ public Team AssigningTeam { get; private set; }
+
+ internal string DebuggerDisplay
+ {
+ get
+ {
+ return string.Format(CultureInfo.InvariantCulture, "User: {0}, CreatedAt: {1}", Assignee.Name, CreatedAt);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs b/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs
new file mode 100644
index 0000000000..726f9898f1
--- /dev/null
+++ b/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs
@@ -0,0 +1,41 @@
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Octokit
+{
+
+ ///
+ /// Holds information about an API response after adding or removing seats for a Copilot-enabled organization.
+ ///
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ public class CopilotSeatAllocation
+ {
+ public CopilotSeatAllocation()
+ {
+ }
+
+ public CopilotSeatAllocation(long seatsCancelled, long seatsCreated)
+ {
+ SeatsCancelled = seatsCancelled;
+ SeatsCreated = seatsCreated;
+ }
+
+ ///
+ /// The total number of seat assignments removed.
+ ///
+ public long SeatsCancelled { get; private set; }
+
+ ///
+ /// The total number of seat assignments created.
+ ///
+ public long SeatsCreated { get; private set; }
+
+ internal string DebuggerDisplay
+ {
+ get
+ {
+ return string.Format(CultureInfo.InvariantCulture, "SeatsCancelled: {0}, SeatsCreated: {1}", SeatsCancelled, SeatsCreated);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Octokit/Models/Response/Copilot/CopilotSeats.cs b/Octokit/Models/Response/Copilot/CopilotSeats.cs
new file mode 100644
index 0000000000..97a9ab3043
--- /dev/null
+++ b/Octokit/Models/Response/Copilot/CopilotSeats.cs
@@ -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 seats)
+ {
+ TotalSeats = totalSeats;
+ Seats = seats;
+ }
+
+ ///
+ /// Total number of Copilot For Business seats for the organization currently being billed
+ ///
+ public long TotalSeats { get; private set; }
+
+ ///
+ /// Information about a Copilot Business seat assignment for a user, team, or organization.
+ ///
+
+ public IReadOnlyList Seats { get; private set; }
+
+ internal string DebuggerDisplay
+ {
+ get
+ {
+ return string.Format(CultureInfo.InvariantCulture, "TotalSeats: {0}", TotalSeats);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Octokit/Models/Response/Copilot/SeatBreakdown.cs b/Octokit/Models/Response/Copilot/SeatBreakdown.cs
new file mode 100644
index 0000000000..a8f40732f0
--- /dev/null
+++ b/Octokit/Models/Response/Copilot/SeatBreakdown.cs
@@ -0,0 +1,64 @@
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Octokit
+{
+ ///
+ /// The breakdown of Copilot Business seats for the organization.
+ ///
+ [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;
+ }
+
+ ///
+ /// The total number of seats being billed for the organization as of the current billing cycle.
+ ///
+ public long Total { get; private set; }
+
+ ///
+ /// Seats added during the current billing cycle
+ ///
+ public long AddedThisCycle { get; private set; }
+
+ ///
+ /// The number of seats that have been assigned to users that have not yet accepted an invitation to this organization.
+ ///
+ public long PendingInvitation { get; private set; }
+
+ ///
+ /// The number of seats that are pending cancellation at the end of the current billing cycle.
+ ///
+ public long PendingCancellation { get; private set; }
+
+ ///
+ /// The number of seats that have used Copilot during the current billing cycle.
+ ///
+ public long ActiveThisCycle { get; private set; }
+
+ ///
+ /// The number of seats that have not used Copilot during the current billing cycle
+ ///
+ public long InactiveThisCycle { get; private set; }
+
+ internal string DebuggerDisplay
+ {
+ get
+ {
+ return string.Format(CultureInfo.InvariantCulture, "Total: {0}", Total);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Octokit/Models/Response/Copilot/UserSeatAllocation.cs b/Octokit/Models/Response/Copilot/UserSeatAllocation.cs
new file mode 100644
index 0000000000..07805b4c11
--- /dev/null
+++ b/Octokit/Models/Response/Copilot/UserSeatAllocation.cs
@@ -0,0 +1,25 @@
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Octokit
+{
+ ///
+ /// Holds information about user names to be added or removed from a Copilot-enabled organization.
+ ///
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ public class UserSeatAllocation
+ {
+ ///
+ /// One or more usernames to be added or removed from a Copilot-enabled organization.
+ ///
+ public string[] SelectedUsernames { get; set; }
+
+ internal string DebuggerDisplay
+ {
+ get
+ {
+ return string.Format(CultureInfo.InvariantCulture, "SelectedUsernames: {0}", string.Join(",", SelectedUsernames));
+ }
+ }
+ }
+}
\ No newline at end of file