From 43f363bc1a60390197fa43cc98b4b64b382511db Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Wed, 6 Dec 2023 12:59:27 +0000 Subject: [PATCH 1/8] initial tests and implementation of Copilot for Business client API --- .../Clients/Copilot/CopilotClientTests.cs | 101 ++++++++++++++++ .../Helpers/CopilotHelper.cs | 13 ++ .../Helpers/CopilotUserLicenseContext.cs | 24 ++++ .../Helpers/GithubClientExtensions.cs | 7 ++ .../Clients/Copilot/CopilotClientTests.cs | 114 ++++++++++++++++++ Octokit/Clients/Copilot/CopilotClient.cs | 35 ++++++ .../Clients/Copilot/CopilotLicenseClient.cs | 73 +++++++++++ .../Clients/Copilot/CopilotSeatsRequest.cs | 6 + Octokit/Clients/Copilot/ICopilotClient.cs | 22 ++++ .../Clients/Copilot/ICopilotLicenseClient.cs | 50 ++++++++ Octokit/GitHubClient.cs | 6 + Octokit/Helpers/ApiUrls.cs | 32 ++++- Octokit/IGitHubClient.cs | 5 + .../Response/Copilot/CopilotApiOptions.cs | 30 +++++ .../Models/Response/Copilot/CopilotSeat.cs | 34 ++++++ .../Response/Copilot/CopilotSeatAllocation.cs | 17 +++ .../Models/Response/Copilot/CopilotSeats.cs | 9 ++ Octokit/Models/Response/Copilot/Properties.cs | 7 ++ .../Models/Response/Copilot/SeatBreakdown.cs | 38 ++++++ .../Models/Response/Copilot/SeatsCreated.cs | 7 ++ .../Response/Copilot/UserSeatAllocation.cs | 7 ++ 21 files changed, 636 insertions(+), 1 deletion(-) create mode 100644 Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs create mode 100644 Octokit.Tests.Integration/Helpers/CopilotHelper.cs create mode 100644 Octokit.Tests.Integration/Helpers/CopilotUserLicenseContext.cs create mode 100644 Octokit.Tests/Clients/Copilot/CopilotClientTests.cs create mode 100644 Octokit/Clients/Copilot/CopilotClient.cs create mode 100644 Octokit/Clients/Copilot/CopilotLicenseClient.cs create mode 100644 Octokit/Clients/Copilot/CopilotSeatsRequest.cs create mode 100644 Octokit/Clients/Copilot/ICopilotClient.cs create mode 100644 Octokit/Clients/Copilot/ICopilotLicenseClient.cs create mode 100644 Octokit/Models/Response/Copilot/CopilotApiOptions.cs create mode 100644 Octokit/Models/Response/Copilot/CopilotSeat.cs create mode 100644 Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs create mode 100644 Octokit/Models/Response/Copilot/CopilotSeats.cs create mode 100644 Octokit/Models/Response/Copilot/Properties.cs create mode 100644 Octokit/Models/Response/Copilot/SeatBreakdown.cs create mode 100644 Octokit/Models/Response/Copilot/SeatsCreated.cs create mode 100644 Octokit/Models/Response/Copilot/UserSeatAllocation.cs diff --git a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs new file mode 100644 index 0000000000..f168ba0c30 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs @@ -0,0 +1,101 @@ +using System.Threading.Tasks; +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.Get(Helper.Organization); + + Assert.True(string.IsNullOrEmpty(billingSettings.SeatManagementSetting)); + Assert.True(string.IsNullOrEmpty(billingSettings.PublicCodeSuggestions)); + } + } + + public class TheGetAllLicensesMethod + { + private readonly IGitHubClient _gitHub; + + public TheGetAllLicensesMethod() + { + _gitHub = Helper.GetAuthenticatedClient(); + } + + [OrganizationTest] + public async Task ReturnsUserCopilotLicenseDetailsAsList() + { + var licenses = await _gitHub.Copilot.License.GetAll(Helper.Organization, new CopilotSeatsRequest(), new CopilotApiOptions()); + + Assert.True(licenses.Count > 0); + } + } + + public class TheAddLicenseMethod + { + private readonly IGitHubClient _gitHub; + + public TheAddLicenseMethod() + { + _gitHub = Helper.GetAuthenticatedClient(); + } + + [OrganizationTest] + public async Task AddsLicenseForUser() + { + var allocation = await _gitHub.Copilot.License.Add(Helper.Organization, ""); + + Assert.True(allocation.SeatsCreated > 0); + } + + [OrganizationTest] + public async Task AddsLicenseForUsers() + { + var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { "" } }; + + var allocation = await _gitHub.Copilot.License.Add(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() + { + var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, ""); + + Assert.True(allocation.SeatsCancelled > 0); + } + + [OrganizationTest] + public async Task RemovesLicenseForUsers() + { + var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { "" } }; + + var allocation = await _gitHub.Copilot.License.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..01a9a2ac9f --- /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.License.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..fbec19d98a 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.License.Add(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..954f7538d8 --- /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.Get("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.License.GetAll("test", new CopilotSeatsRequest(), new CopilotApiOptions()); + + connection.Received().GetAll(Arg.Is(u => u.ToString() == expectedUri), Arg.Any>(), null, Arg.Any()); + } + } + + public class TheAddCopilotLicenseMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new CopilotClient(connection); + var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users"; + + client.License.Add(orgName, "copilot-user"); + + connection.Received().Post(Arg.Is(u => u.ToString() == expectedUri), Arg.Any()); + } + } + + public class TheAddCopilotLicensesMethod + { + [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.License.Add(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.License.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.License.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/Clients/Copilot/CopilotClient.cs b/Octokit/Clients/Copilot/CopilotClient.cs new file mode 100644 index 0000000000..93d7a61d30 --- /dev/null +++ b/Octokit/Clients/Copilot/CopilotClient.cs @@ -0,0 +1,35 @@ +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) + { + License = new CopilotLicenseClient(apiConnection); + } + + public async Task Get(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 License { 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..4423a7ebc8 --- /dev/null +++ b/Octokit/Clients/Copilot/CopilotLicenseClient.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Octokit; +using Octokit.Models.Request.Enterprise; + +public class CopilotLicenseClient : ApiClient, ICopilotLicenseClient +{ + public CopilotLicenseClient(IApiConnection apiConnection) : base(apiConnection) + { + } + + 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); + } + + 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); + } + + public async Task Add(string organization, string userName) + { + Ensure.ArgumentNotNull(organization, nameof(organization)); + Ensure.ArgumentNotNull(userName, nameof(userName)); + + var allocation = new UserSeatAllocation + { + SelectedUsernames = new[] { userName } + }; + + return await Add(organization, allocation); + } + + public async Task Add(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 + /// Any parameters to include on the API call + /// Options to control page size when making API requests + /// A list of instance containing the currently allocated user licenses. + public async Task> GetAll(string organization, CopilotSeatsRequest request, CopilotApiOptions copilotApiOptions) + { + Ensure.ArgumentNotNull(organization, nameof(organization)); + + ApiOptionsExtended options = new ApiOptionsExtended() + { + PageSize = copilotApiOptions.PageSize + }; + + return await ApiConnection.GetAll(ApiUrls.CopilotAllocatedLicenses(organization), + request.ToParametersDictionary(), null, options); + } +} diff --git a/Octokit/Clients/Copilot/CopilotSeatsRequest.cs b/Octokit/Clients/Copilot/CopilotSeatsRequest.cs new file mode 100644 index 0000000000..9b1e64f532 --- /dev/null +++ b/Octokit/Clients/Copilot/CopilotSeatsRequest.cs @@ -0,0 +1,6 @@ +using Octokit; + +public class CopilotSeatsRequest : RequestParameters +{ + +} diff --git a/Octokit/Clients/Copilot/ICopilotClient.cs b/Octokit/Clients/Copilot/ICopilotClient.cs new file mode 100644 index 0000000000..9b17a6d52a --- /dev/null +++ b/Octokit/Clients/Copilot/ICopilotClient.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// Access GitHub's Copilot for Business API. + /// + public interface ICopilotClient + { + /// + /// Returns the top level billing settings for an organization. + /// + /// the organization name to retrieve billing settings for + /// A instance + Task Get(string organization); + + /// + /// For checking and managing licenses for GitHub Copilot for Business + /// + ICopilotLicenseClient License { get; } + } +} diff --git a/Octokit/Clients/Copilot/ICopilotLicenseClient.cs b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs new file mode 100644 index 0000000000..d20c7f7b94 --- /dev/null +++ b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs @@ -0,0 +1,50 @@ +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); + + /// + /// Adds a license for a user + /// + /// The organization name + /// The github users profile name to add a license to + /// A instance with results + Task Add(string organization, string userName); + + /// + /// Adds 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 Add(string organization, UserSeatAllocation userSeatAllocation); + + /// + /// Gets all of the currently allocated licenses for an organization + /// + /// The organization + /// A instance containing the currently allocated user licenses + Task> GetAll(string organization, CopilotSeatsRequest request, CopilotApiOptions copilotApiOptions); + } +} 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/CopilotApiOptions.cs b/Octokit/Models/Response/Copilot/CopilotApiOptions.cs new file mode 100644 index 0000000000..0e4be0704c --- /dev/null +++ b/Octokit/Models/Response/Copilot/CopilotApiOptions.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace Octokit +{ + + public class CopilotApiOptions + { + /// + /// Specify the number of results to return for each page + /// + /// + /// Results returned may be less than this total if you reach the final page of results + /// + public int PageSize { get; set; } = 100; + + internal string DebuggerDisplay + { + get + { + var values = new List + { + "PageSize: " + PageSize + }; + + return String.Join(", ", values); + } + } + } +} \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/CopilotSeat.cs b/Octokit/Models/Response/Copilot/CopilotSeat.cs new file mode 100644 index 0000000000..acb6ff9204 --- /dev/null +++ b/Octokit/Models/Response/Copilot/CopilotSeat.cs @@ -0,0 +1,34 @@ +using System; +using Octokit; + +namespace Octokit +{ + public class CopilotSeat + { + public CopilotSeat() + { + } + + public CopilotSeat(DateTimeOffset? createdAt, DateTimeOffset? updatedAt, string pendingCancellationDate, DateTimeOffset? lastActivityAt, string lastActivityEditor, User assignee) + { + CreatedAt = createdAt; + UpdatedAt = updatedAt; + PendingCancellationDate = pendingCancellationDate; + LastActivityAt = lastActivityAt; + LastActivityEditor = lastActivityEditor; + Assignee = assignee; + } + + public DateTimeOffset? CreatedAt { get; set; } + + public DateTimeOffset? UpdatedAt { get; set; } + + public string PendingCancellationDate { get; set; } + + public DateTimeOffset? LastActivityAt { get; set; } + + public string LastActivityEditor { get; set; } + + public User Assignee { get; set; } + } +} \ 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..ff58165f01 --- /dev/null +++ b/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs @@ -0,0 +1,17 @@ +namespace Octokit +{ + public class CopilotSeatAllocation + { + public string Type { get; set; } + + public string Description { get; set; } + + public Properties Properties { get; set; } + + public string[] SeatAllocationResponseRequired { get; set; } + + public long SeatsCancelled { get; set; } + + public long SeatsCreated { get; set; } + } +} \ 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..9e4f4b46f1 --- /dev/null +++ b/Octokit/Models/Response/Copilot/CopilotSeats.cs @@ -0,0 +1,9 @@ +namespace Octokit +{ + public class CopilotSeats + { + public long TotalSeats { get; set; } + + public CopilotSeat[] Seats { get; set; } + } +} \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/Properties.cs b/Octokit/Models/Response/Copilot/Properties.cs new file mode 100644 index 0000000000..66a3d8d06f --- /dev/null +++ b/Octokit/Models/Response/Copilot/Properties.cs @@ -0,0 +1,7 @@ +namespace Octokit +{ + public class Properties + { + public SeatsCreated SeatsCreated { get; set; } + } +} \ 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..fee99058ec --- /dev/null +++ b/Octokit/Models/Response/Copilot/SeatBreakdown.cs @@ -0,0 +1,38 @@ +namespace Octokit +{ + /// + /// The breakdown of Copilot Business seats for the organization. + /// + public class SeatBreakdown + { + /// + /// The total number of seats being billed for the organization as of the current billing cycle. + /// + public long Total { get; set; } + + /// + /// Seats added during the current billing cycle + /// + public long AddedThisCycle { get; 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; set; } + + /// + /// The number of seats that are pending cancellation at the end of the current billing cycle. + /// + public long PendingCancellation { get; set; } + + /// + /// The number of seats that have used Copilot during the current billing cycle. + /// + public long ActiveThisCycle { get; set; } + + /// + /// The number of seats that have not used Copilot during the current billing cycle + /// + public long InactiveThisCycle { get; set; } + } +} \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/SeatsCreated.cs b/Octokit/Models/Response/Copilot/SeatsCreated.cs new file mode 100644 index 0000000000..8ba3fb8b41 --- /dev/null +++ b/Octokit/Models/Response/Copilot/SeatsCreated.cs @@ -0,0 +1,7 @@ +namespace Octokit +{ + public class SeatsCreated + { + public string Type { get; set; } + } +} \ 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..c21830fb79 --- /dev/null +++ b/Octokit/Models/Response/Copilot/UserSeatAllocation.cs @@ -0,0 +1,7 @@ +namespace Octokit +{ + public class UserSeatAllocation + { + public string[] SelectedUsernames { get; set; } + } +} \ No newline at end of file From 41a513c043d8ffd161a7842e2e9610ffeab31282 Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Wed, 6 Dec 2023 13:01:23 +0000 Subject: [PATCH 2/8] updated billing settings documentation --- .../Response/Copilot/BillingSettings.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Octokit/Models/Response/Copilot/BillingSettings.cs diff --git a/Octokit/Models/Response/Copilot/BillingSettings.cs b/Octokit/Models/Response/Copilot/BillingSettings.cs new file mode 100644 index 0000000000..fbc1f0233c --- /dev/null +++ b/Octokit/Models/Response/Copilot/BillingSettings.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; + +namespace Octokit +{ + /// + /// The billing settings for a Copilot-enabled organization. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public partial class BillingSettings + { + /// + /// A summary of the current billing settings for the organization. + /// + public SeatBreakdown SeatBreakdown { get; set; } + + /// + /// A string that indicates how seats are billed for the organization. + /// + public string SeatManagementSetting { get; set; } + + /// + /// A string that indicates if public code suggestions are enabled or blocked for the organization. + /// + public string PublicCodeSuggestions { get; set; } + } +} + From 6dc4785ad26208baa6c6e142e940525d7b50d94e Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Wed, 6 Dec 2023 13:23:28 +0000 Subject: [PATCH 3/8] renames and refactors - clarity and simplified --- .../Clients/Copilot/CopilotClientTests.cs | 12 ++++++------ .../Helpers/GithubClientExtensions.cs | 2 +- Octokit.Tests/Clients/Copilot/CopilotClientTests.cs | 10 +++++----- Octokit/Clients/Copilot/CopilotLicenseClient.cs | 12 +++++------- Octokit/Clients/Copilot/CopilotSeatsRequest.cs | 6 ------ Octokit/Clients/Copilot/ICopilotLicenseClient.cs | 11 ++++++----- 6 files changed, 23 insertions(+), 30 deletions(-) delete mode 100644 Octokit/Clients/Copilot/CopilotSeatsRequest.cs diff --git a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs index f168ba0c30..d3ae49273b 100644 --- a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs +++ b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs @@ -36,7 +36,7 @@ public TheGetAllLicensesMethod() [OrganizationTest] public async Task ReturnsUserCopilotLicenseDetailsAsList() { - var licenses = await _gitHub.Copilot.License.GetAll(Helper.Organization, new CopilotSeatsRequest(), new CopilotApiOptions()); + var licenses = await _gitHub.Copilot.License.GetAll(Helper.Organization, new CopilotApiOptions()); Assert.True(licenses.Count > 0); } @@ -54,7 +54,7 @@ public TheAddLicenseMethod() [OrganizationTest] public async Task AddsLicenseForUser() { - var allocation = await _gitHub.Copilot.License.Add(Helper.Organization, ""); + var allocation = await _gitHub.Copilot.License.Assign(Helper.Organization, Helper.UserName); Assert.True(allocation.SeatsCreated > 0); } @@ -62,9 +62,9 @@ public async Task AddsLicenseForUser() [OrganizationTest] public async Task AddsLicenseForUsers() { - var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { "" } }; + var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } }; - var allocation = await _gitHub.Copilot.License.Add(Helper.Organization, seatAllocation); + var allocation = await _gitHub.Copilot.License.Assign(Helper.Organization, seatAllocation); Assert.True(allocation.SeatsCreated > 0); } @@ -82,7 +82,7 @@ public TheDeleteLicenseMethod() [OrganizationTest] public async Task RemovesLicenseForUser() { - var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, ""); + var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, Helper.UserName); Assert.True(allocation.SeatsCancelled > 0); } @@ -90,7 +90,7 @@ public async Task RemovesLicenseForUser() [OrganizationTest] public async Task RemovesLicenseForUsers() { - var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { "" } }; + var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } }; var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, seatAllocation); diff --git a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs index fbec19d98a..005354c3e1 100644 --- a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs +++ b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs @@ -144,7 +144,7 @@ internal static async Task CreateEnterpriseUserContext(th internal static async Task CreateCopilotUserLicenseContext(this IGitHubClient client, string organization, string userName) { - await client.Copilot.License.Add(organization, userName); + await client.Copilot.License.Assign(organization, userName); return new CopilotUserLicenseContext(client.Connection, organization, userName); } diff --git a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs index 954f7538d8..d3d6fec099 100644 --- a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs +++ b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs @@ -33,13 +33,13 @@ public void RequestsCorrectUrl() var client = new CopilotClient(connection); var expectedUri = $"orgs/{orgName}/copilot/billing/seats"; - client.License.GetAll("test", new CopilotSeatsRequest(), new CopilotApiOptions()); + client.License.GetAll("test", new CopilotApiOptions()); connection.Received().GetAll(Arg.Is(u => u.ToString() == expectedUri), Arg.Any>(), null, Arg.Any()); } } - public class TheAddCopilotLicenseMethod + public class TheAssignCopilotLicenseMethod { [Fact] public void RequestsCorrectUrl() @@ -48,13 +48,13 @@ public void RequestsCorrectUrl() var client = new CopilotClient(connection); var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users"; - client.License.Add(orgName, "copilot-user"); + client.License.Assign(orgName, "copilot-user"); connection.Received().Post(Arg.Is(u => u.ToString() == expectedUri), Arg.Any()); } } - public class TheAddCopilotLicensesMethod + public class TheAssignCopilotLicensesMethod { [Fact] public void RequestsCorrectUrl() @@ -64,7 +64,7 @@ public void RequestsCorrectUrl() var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users"; var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } }; - client.License.Add(orgName, payloadData); + client.License.Assign(orgName, payloadData); connection.Received().Post(Arg.Is(u => u.ToString() == expectedUri), payloadData); } diff --git a/Octokit/Clients/Copilot/CopilotLicenseClient.cs b/Octokit/Clients/Copilot/CopilotLicenseClient.cs index 4423a7ebc8..f8fd3d7f89 100644 --- a/Octokit/Clients/Copilot/CopilotLicenseClient.cs +++ b/Octokit/Clients/Copilot/CopilotLicenseClient.cs @@ -30,7 +30,7 @@ public async Task Remove(string organization, UserSeatAll return await ApiConnection.Delete(ApiUrls.CopilotBillingLicense(organization), userSeatAllocation); } - public async Task Add(string organization, string userName) + public async Task Assign(string organization, string userName) { Ensure.ArgumentNotNull(organization, nameof(organization)); Ensure.ArgumentNotNull(userName, nameof(userName)); @@ -40,10 +40,10 @@ public async Task Add(string organization, string userNam SelectedUsernames = new[] { userName } }; - return await Add(organization, allocation); + return await Assign(organization, allocation); } - public async Task Add(string organization, UserSeatAllocation userSeatAllocation) + public async Task Assign(string organization, UserSeatAllocation userSeatAllocation) { Ensure.ArgumentNotNull(organization, nameof(organization)); Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation)); @@ -55,10 +55,9 @@ public async Task Add(string organization, UserSeatAlloca /// Gets all of the currently allocated licenses for an organization /// /// The organization - /// Any parameters to include on the API call /// Options to control page size when making API requests /// A list of instance containing the currently allocated user licenses. - public async Task> GetAll(string organization, CopilotSeatsRequest request, CopilotApiOptions copilotApiOptions) + public async Task> GetAll(string organization, CopilotApiOptions copilotApiOptions) { Ensure.ArgumentNotNull(organization, nameof(organization)); @@ -67,7 +66,6 @@ public async Task> GetAll(string organization, Copil PageSize = copilotApiOptions.PageSize }; - return await ApiConnection.GetAll(ApiUrls.CopilotAllocatedLicenses(organization), - request.ToParametersDictionary(), null, options); + return await ApiConnection.GetAll(ApiUrls.CopilotAllocatedLicenses(organization), options); } } diff --git a/Octokit/Clients/Copilot/CopilotSeatsRequest.cs b/Octokit/Clients/Copilot/CopilotSeatsRequest.cs deleted file mode 100644 index 9b1e64f532..0000000000 --- a/Octokit/Clients/Copilot/CopilotSeatsRequest.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Octokit; - -public class CopilotSeatsRequest : RequestParameters -{ - -} diff --git a/Octokit/Clients/Copilot/ICopilotLicenseClient.cs b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs index d20c7f7b94..e05d8750ac 100644 --- a/Octokit/Clients/Copilot/ICopilotLicenseClient.cs +++ b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs @@ -25,26 +25,27 @@ public interface ICopilotLicenseClient Task Remove(string organization, UserSeatAllocation userSeatAllocation); /// - /// Adds a license for a user + /// Assigns a license to a user /// /// The organization name /// The github users profile name to add a license to /// A instance with results - Task Add(string organization, string userName); + Task Assign(string organization, string userName); /// - /// Adds a license for one or many users + /// 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 Add(string organization, UserSeatAllocation userSeatAllocation); + 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, CopilotSeatsRequest request, CopilotApiOptions copilotApiOptions); + Task> GetAll(string organization, CopilotApiOptions copilotApiOptions); } } From dffc4dac64ec6e577139c3c0cbf376fe6deb91a1 Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Wed, 6 Dec 2023 13:48:00 +0000 Subject: [PATCH 4/8] using context to ensure license clean up --- .../Clients/Copilot/CopilotClientTests.cs | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs index d3ae49273b..78dc88719c 100644 --- a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs +++ b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Octokit.Tests.Integration.Helpers; using Xunit; namespace Octokit.Tests.Integration.Clients.Copilot @@ -19,8 +20,8 @@ public async Task ReturnsBillingSettingsData() { var billingSettings = await _gitHub.Copilot.Get(Helper.Organization); - Assert.True(string.IsNullOrEmpty(billingSettings.SeatManagementSetting)); - Assert.True(string.IsNullOrEmpty(billingSettings.PublicCodeSuggestions)); + Assert.NotNull(billingSettings.SeatManagementSetting); + Assert.NotNull(billingSettings.PublicCodeSuggestions); } } @@ -36,9 +37,12 @@ public TheGetAllLicensesMethod() [OrganizationTest] public async Task ReturnsUserCopilotLicenseDetailsAsList() { - var licenses = await _gitHub.Copilot.License.GetAll(Helper.Organization, new CopilotApiOptions()); + using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) + { + var licenses = await _gitHub.Copilot.License.GetAll(Helper.Organization, new CopilotApiOptions()); - Assert.True(licenses.Count > 0); + Assert.True(licenses.Count > 0); + } } } @@ -54,19 +58,25 @@ public TheAddLicenseMethod() [OrganizationTest] public async Task AddsLicenseForUser() { - var allocation = await _gitHub.Copilot.License.Assign(Helper.Organization, Helper.UserName); + using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) + { + var allocation = await _gitHub.Copilot.License.Assign(Helper.Organization, Helper.UserName); - Assert.True(allocation.SeatsCreated > 0); + Assert.True(allocation.SeatsCreated > 0); + } } [OrganizationTest] public async Task AddsLicenseForUsers() { - var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } }; + using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) + { + var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } }; - var allocation = await _gitHub.Copilot.License.Assign(Helper.Organization, seatAllocation); + var allocation = await _gitHub.Copilot.License.Assign(Helper.Organization, seatAllocation); - Assert.True(allocation.SeatsCreated > 0); + Assert.True(allocation.SeatsCreated > 0); + } } } @@ -82,19 +92,25 @@ public TheDeleteLicenseMethod() [OrganizationTest] public async Task RemovesLicenseForUser() { - var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, Helper.UserName); + using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) + { + var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, Helper.UserName); - Assert.True(allocation.SeatsCancelled > 0); + Assert.True(allocation.SeatsCancelled > 0); + } } [OrganizationTest] public async Task RemovesLicenseForUsers() { - var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } }; + using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) + { + var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } }; - var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, seatAllocation); + var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, seatAllocation); - Assert.True(allocation.SeatsCancelled > 0); + Assert.True(allocation.SeatsCancelled > 0); + } } } } From d3da74be19610377b5ac408f4c9d301eaeb5bae2 Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Wed, 6 Dec 2023 16:32:49 +0000 Subject: [PATCH 5/8] extra documentation and used ApiOptions instead of custom class --- .../Clients/Copilot/CopilotClientTests.cs | 2 +- .../Clients/Copilot/CopilotClientTests.cs | 2 +- .../Clients/Copilot/CopilotLicenseClient.cs | 6 ++-- .../Clients/Copilot/ICopilotLicenseClient.cs | 2 +- .../Response/Copilot/BillingSettings.cs | 6 ++++ .../Response/Copilot/CopilotApiOptions.cs | 30 ------------------- .../Models/Response/Copilot/CopilotSeat.cs | 30 ++++++++++++++++++- .../Response/Copilot/CopilotSeatAllocation.cs | 18 ++++++----- .../Models/Response/Copilot/CopilotSeats.cs | 6 ++++ Octokit/Models/Response/Copilot/Properties.cs | 7 ----- .../Models/Response/Copilot/SeatsCreated.cs | 7 ----- .../Response/Copilot/UserSeatAllocation.cs | 6 ++++ 12 files changed, 63 insertions(+), 59 deletions(-) delete mode 100644 Octokit/Models/Response/Copilot/CopilotApiOptions.cs delete mode 100644 Octokit/Models/Response/Copilot/Properties.cs delete mode 100644 Octokit/Models/Response/Copilot/SeatsCreated.cs diff --git a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs index 78dc88719c..3ac18df6b7 100644 --- a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs +++ b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs @@ -39,7 +39,7 @@ public async Task ReturnsUserCopilotLicenseDetailsAsList() { using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) { - var licenses = await _gitHub.Copilot.License.GetAll(Helper.Organization, new CopilotApiOptions()); + var licenses = await _gitHub.Copilot.License.GetAll(Helper.Organization, new ApiOptions()); Assert.True(licenses.Count > 0); } diff --git a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs index d3d6fec099..338f67130a 100644 --- a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs +++ b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs @@ -33,7 +33,7 @@ public void RequestsCorrectUrl() var client = new CopilotClient(connection); var expectedUri = $"orgs/{orgName}/copilot/billing/seats"; - client.License.GetAll("test", new CopilotApiOptions()); + client.License.GetAll("test", new ApiOptions()); connection.Received().GetAll(Arg.Is(u => u.ToString() == expectedUri), Arg.Any>(), null, Arg.Any()); } diff --git a/Octokit/Clients/Copilot/CopilotLicenseClient.cs b/Octokit/Clients/Copilot/CopilotLicenseClient.cs index f8fd3d7f89..342789a95b 100644 --- a/Octokit/Clients/Copilot/CopilotLicenseClient.cs +++ b/Octokit/Clients/Copilot/CopilotLicenseClient.cs @@ -8,7 +8,7 @@ public class CopilotLicenseClient : ApiClient, ICopilotLicenseClient public CopilotLicenseClient(IApiConnection apiConnection) : base(apiConnection) { } - + public async Task Remove(string organization, string userName) { Ensure.ArgumentNotNull(organization, nameof(organization)); @@ -42,7 +42,7 @@ public async Task Assign(string organization, string user return await Assign(organization, allocation); } - + public async Task Assign(string organization, UserSeatAllocation userSeatAllocation) { Ensure.ArgumentNotNull(organization, nameof(organization)); @@ -57,7 +57,7 @@ public async Task Assign(string organization, UserSeatAll /// The organization /// Options to control page size when making API requests /// A list of instance containing the currently allocated user licenses. - public async Task> GetAll(string organization, CopilotApiOptions copilotApiOptions) + public async Task> GetAll(string organization, ApiOptions copilotApiOptions) { Ensure.ArgumentNotNull(organization, nameof(organization)); diff --git a/Octokit/Clients/Copilot/ICopilotLicenseClient.cs b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs index e05d8750ac..689fb85a0e 100644 --- a/Octokit/Clients/Copilot/ICopilotLicenseClient.cs +++ b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs @@ -46,6 +46,6 @@ public interface ICopilotLicenseClient /// 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, CopilotApiOptions copilotApiOptions); + Task> GetAll(string organization, ApiOptions copilotApiOptions); } } diff --git a/Octokit/Models/Response/Copilot/BillingSettings.cs b/Octokit/Models/Response/Copilot/BillingSettings.cs index fbc1f0233c..7231e2c5f1 100644 --- a/Octokit/Models/Response/Copilot/BillingSettings.cs +++ b/Octokit/Models/Response/Copilot/BillingSettings.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Globalization; namespace Octokit { @@ -22,6 +23,11 @@ public partial class BillingSettings /// A string that indicates if public code suggestions are enabled or blocked for the organization. /// public string PublicCodeSuggestions { get; set; } + + internal string DebuggerDisplay + { + get { return string.Format(CultureInfo.InvariantCulture, "SeatManagementSetting: {0}, PublicCodeSuggestions: {1}", SeatManagementSetting, PublicCodeSuggestions); } + } } } diff --git a/Octokit/Models/Response/Copilot/CopilotApiOptions.cs b/Octokit/Models/Response/Copilot/CopilotApiOptions.cs deleted file mode 100644 index 0e4be0704c..0000000000 --- a/Octokit/Models/Response/Copilot/CopilotApiOptions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Octokit -{ - - public class CopilotApiOptions - { - /// - /// Specify the number of results to return for each page - /// - /// - /// Results returned may be less than this total if you reach the final page of results - /// - public int PageSize { get; set; } = 100; - - internal string DebuggerDisplay - { - get - { - var values = new List - { - "PageSize: " + PageSize - }; - - return String.Join(", ", values); - } - } - } -} \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/CopilotSeat.cs b/Octokit/Models/Response/Copilot/CopilotSeat.cs index acb6ff9204..167ce21f16 100644 --- a/Octokit/Models/Response/Copilot/CopilotSeat.cs +++ b/Octokit/Models/Response/Copilot/CopilotSeat.cs @@ -1,8 +1,10 @@ using System; -using Octokit; namespace Octokit { + /// + /// Details about a Copilot seat allocated to an organization member. + /// public class CopilotSeat { public CopilotSeat() @@ -19,16 +21,42 @@ public CopilotSeat(DateTimeOffset? createdAt, DateTimeOffset? updatedAt, string Assignee = assignee; } + /// + /// Timestamp of when the assignee was last granted access to GitHub Copilot, in ISO 8601 format + /// public DateTimeOffset? CreatedAt { get; set; } + /// + /// Timestamp of when the assignee's GitHub Copilot access was last updated, in ISO 8601 format. + /// public DateTimeOffset? UpdatedAt { get; 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; set; } + /// + /// Timestamp of user's last GitHub Copilot activity, in ISO 8601 format. + /// public DateTimeOffset? LastActivityAt { get; set; } + /// + /// Last editor that was used by the user for a GitHub Copilot completion. + /// public string LastActivityEditor { get; set; } + /// + /// The assignee that has been granted access to GitHub Copilot + /// public User Assignee { get; 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; set; } } } \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs b/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs index ff58165f01..2c24d8fb62 100644 --- a/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs +++ b/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs @@ -1,17 +1,19 @@ namespace Octokit { + + /// + /// Holds information about an API response after adding or removing seats for a Copilot-enabled organization. + /// public class CopilotSeatAllocation { - public string Type { get; set; } - - public string Description { get; set; } - - public Properties Properties { get; set; } - - public string[] SeatAllocationResponseRequired { get; set; } - + /// + /// The total number of seat assignments removed. + /// public long SeatsCancelled { get; set; } + /// + /// The total number of seat assignments created. + /// public long SeatsCreated { get; set; } } } \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/CopilotSeats.cs b/Octokit/Models/Response/Copilot/CopilotSeats.cs index 9e4f4b46f1..1ddad647e3 100644 --- a/Octokit/Models/Response/Copilot/CopilotSeats.cs +++ b/Octokit/Models/Response/Copilot/CopilotSeats.cs @@ -2,8 +2,14 @@ namespace Octokit { public class CopilotSeats { + /// + /// Total number of Copilot For Business seats for the organization currently being billed + /// public long TotalSeats { get; set; } + /// + /// Information about a Copilot Business seat assignment for a user, team, or organization. + /// public CopilotSeat[] Seats { get; set; } } } \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/Properties.cs b/Octokit/Models/Response/Copilot/Properties.cs deleted file mode 100644 index 66a3d8d06f..0000000000 --- a/Octokit/Models/Response/Copilot/Properties.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Octokit -{ - public class Properties - { - public SeatsCreated SeatsCreated { get; set; } - } -} \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/SeatsCreated.cs b/Octokit/Models/Response/Copilot/SeatsCreated.cs deleted file mode 100644 index 8ba3fb8b41..0000000000 --- a/Octokit/Models/Response/Copilot/SeatsCreated.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Octokit -{ - public class SeatsCreated - { - public string Type { get; set; } - } -} \ No newline at end of file diff --git a/Octokit/Models/Response/Copilot/UserSeatAllocation.cs b/Octokit/Models/Response/Copilot/UserSeatAllocation.cs index c21830fb79..57f00dcd55 100644 --- a/Octokit/Models/Response/Copilot/UserSeatAllocation.cs +++ b/Octokit/Models/Response/Copilot/UserSeatAllocation.cs @@ -1,7 +1,13 @@ namespace Octokit { + /// + /// Holds information about user names to be added or removed from a Copilot-enabled organization. + /// public class UserSeatAllocation { + /// + /// One or more usernames to be added or removed from a Copilot-enabled organization. + /// public string[] SelectedUsernames { get; set; } } } \ No newline at end of file From 79990141da0a0d72b4b13449bb32cad2ebb5ad39 Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Thu, 7 Dec 2023 12:31:55 +0000 Subject: [PATCH 6/8] implemented observable clients --- .../Copilot/IObservableCopilotClient.cs | 22 ++++ .../IObservableCopilotLicenseClient.cs | 51 ++++++++ .../Copilot/ObservableCopilotClient.cs | 46 ++++++++ .../Copilot/ObservableCopilotLicenseClient.cs | 89 ++++++++++++++ .../Clients/Copilot/CopilotClientTests.cs | 2 +- .../Copilot/ObservableCopilotClientTests.cs | 111 ++++++++++++++++++ .../Clients/Copilot/CopilotLicenseClient.cs | 31 +++++ 7 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs create mode 100644 Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs create mode 100644 Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs create mode 100644 Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs create mode 100644 Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs diff --git a/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs b/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs new file mode 100644 index 0000000000..3b7085b760 --- /dev/null +++ b/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs @@ -0,0 +1,22 @@ +using System; + +namespace Octokit.Reactive +{ + /// + /// Access GitHub's Copilot for Business API. + /// + public interface IObservableCopilotClient + { + /// + /// Returns the top level billing settings for an organization. + /// + /// the organization name to retrieve billing settings for + /// A instance + IObservable Get(string organization); + + /// + /// For checking and managing licenses for GitHub Copilot for Business + /// + IObservableCopilotLicenseClient License { get; } + } +} diff --git a/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs b/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs new file mode 100644 index 0000000000..66ee474e42 --- /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 copilotApiOptions); + } +} diff --git a/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs b/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs new file mode 100644 index 0000000000..8a33451de7 --- /dev/null +++ b/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs @@ -0,0 +1,46 @@ +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; + License = new ObservableCopilotLicenseClient(client); + } + + /// + /// Returns the top level billing settings for an organization. + /// + /// the organization name to retrieve billing settings for + /// A instance + public IObservable Get(string organization) + { + Ensure.ArgumentNotNull(organization, nameof(organization)); + + return _client.Get(organization).ToObservable(); + } + + /// + /// Client for maintaining Copilot licenses for users in an organization. + /// + public IObservableCopilotLicenseClient License { 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..cc1ac17483 --- /dev/null +++ b/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Threading.Tasks; +using Octokit; +using Octokit.Models.Request.Enterprise; +using Octokit.Reactive; + +/// +/// A client for managing licenses for GitHub Copilot for Business +/// +public class ObservableCopilotLicenseClient : IObservableCopilotLicenseClient +{ + private readonly ICopilotLicenseClient _client; + + public ObservableCopilotLicenseClient(IGitHubClient client) + { + _client = client.Copilot.License; + } + + /// + /// 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 copilotApiOptions) + { + Ensure.ArgumentNotNull(organization, nameof(organization)); + Ensure.ArgumentNotNull(copilotApiOptions, nameof(copilotApiOptions)); + + return _client.GetAll(organization, copilotApiOptions).ToObservable(); + } +} diff --git a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs index 338f67130a..16f9c1eb4c 100644 --- a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs +++ b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs @@ -35,7 +35,7 @@ public void RequestsCorrectUrl() var expectedUri = $"orgs/{orgName}/copilot/billing/seats"; client.License.GetAll("test", new ApiOptions()); - connection.Received().GetAll(Arg.Is(u => u.ToString() == expectedUri), Arg.Any>(), null, Arg.Any()); + connection.Received().GetAll(Arg.Is(u => u.ToString() == expectedUri), Arg.Any()); } } diff --git a/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs b/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs new file mode 100644 index 0000000000..8de11b0456 --- /dev/null +++ b/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs @@ -0,0 +1,111 @@ +using System; +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.Get("test"); + + githubClient.Copilot.Received(1).Get(orgName); + } + } + + public class TheGetAllCopilotLicensesMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var githubClient = Substitute.For(); + var client = new ObservableCopilotClient(githubClient); + var apiOptions = new ApiOptions(); + + client.License.GetAll("test", apiOptions); + + githubClient.Copilot.License.Received().GetAll(orgName, apiOptions); + } + } + + public class TheAssignCopilotLicenseMethod + { + [Fact] + public void RequestsCorrectUrl() + { + var githubClient = Substitute.For(); + var client = new ObservableCopilotClient(githubClient); + const string expectedUser = "copilot-user"; + + client.License.Assign(orgName, expectedUser); + + githubClient.Copilot.License.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.License.Assign(orgName, payloadData); + + githubClient.Copilot.License.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.License.Remove(orgName, expectedUser); + + githubClient.Copilot.License.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.License.Remove(orgName, payloadData); + + githubClient.Copilot.License.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/CopilotLicenseClient.cs b/Octokit/Clients/Copilot/CopilotLicenseClient.cs index 342789a95b..7d6b478518 100644 --- a/Octokit/Clients/Copilot/CopilotLicenseClient.cs +++ b/Octokit/Clients/Copilot/CopilotLicenseClient.cs @@ -3,12 +3,25 @@ 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 public async Task Remove(string organization, string userName) { Ensure.ArgumentNotNull(organization, nameof(organization)); @@ -22,6 +35,12 @@ public async Task Remove(string organization, string user 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 public async Task Remove(string organization, UserSeatAllocation userSeatAllocation) { Ensure.ArgumentNotNull(organization, nameof(organization)); @@ -30,6 +49,12 @@ public async Task Remove(string organization, UserSeatAll 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 public async Task Assign(string organization, string userName) { Ensure.ArgumentNotNull(organization, nameof(organization)); @@ -43,6 +68,12 @@ public async Task Assign(string organization, string user 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 public async Task Assign(string organization, UserSeatAllocation userSeatAllocation) { Ensure.ArgumentNotNull(organization, nameof(organization)); From a3cb10c49fda23165351329030c077f1f830b183 Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Sat, 9 Dec 2023 09:56:07 +0000 Subject: [PATCH 7/8] Fixing convention issues --- .../IObservableCopilotLicenseClient.cs | 4 +- .../Copilot/ObservableCopilotLicenseClient.cs | 13 +++--- Octokit.Reactive/IObservableGitHubClient.cs | 1 + Octokit.Reactive/ObservableGitHubClient.cs | 4 +- .../Copilot/ObservableCopilotClientTests.cs | 15 ++++--- Octokit/Clients/Copilot/CopilotClient.cs | 6 +++ .../Clients/Copilot/CopilotLicenseClient.cs | 15 ++++--- .../Clients/Copilot/ICopilotLicenseClient.cs | 4 +- .../Response/Copilot/BillingSettings.cs | 24 ++++++++--- .../Models/Response/Copilot/CopilotSeat.cs | 28 +++++++++---- .../Response/Copilot/CopilotSeatAllocation.cs | 26 +++++++++++- .../Models/Response/Copilot/CopilotSeats.cs | 28 ++++++++++++- .../Models/Response/Copilot/SeatBreakdown.cs | 40 +++++++++++++++---- .../Response/Copilot/UserSeatAllocation.cs | 14 ++++++- 14 files changed, 177 insertions(+), 45 deletions(-) diff --git a/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs b/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs index 66ee474e42..333bd93f29 100644 --- a/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs +++ b/Octokit.Reactive/Clients/Copilot/IObservableCopilotLicenseClient.cs @@ -44,8 +44,8 @@ public interface IObservableCopilotLicenseClient /// 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 + /// 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 copilotApiOptions); + IObservable GetAll(string organization, ApiOptions options); } } diff --git a/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs b/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs index cc1ac17483..3467d3a3a0 100644 --- a/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs +++ b/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs @@ -4,6 +4,7 @@ using Octokit; using Octokit.Models.Request.Enterprise; using Octokit.Reactive; +using Octokit.Reactive.Internal; /// /// A client for managing licenses for GitHub Copilot for Business @@ -11,10 +12,12 @@ public class ObservableCopilotLicenseClient : IObservableCopilotLicenseClient { private readonly ICopilotLicenseClient _client; + private readonly IConnection _connection; public ObservableCopilotLicenseClient(IGitHubClient client) { _client = client.Copilot.License; + _connection = client.Connection; } /// @@ -77,13 +80,13 @@ public IObservable Assign(string organization, UserSeatAl /// Gets all of the currently allocated licenses for an organization /// /// The organization - /// Options to control page size when making API requests + /// 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 copilotApiOptions) + public IObservable GetAll(string organization, ApiOptions options) { Ensure.ArgumentNotNull(organization, nameof(organization)); - Ensure.ArgumentNotNull(copilotApiOptions, nameof(copilotApiOptions)); - - return _client.GetAll(organization, copilotApiOptions).ToObservable(); + 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/Reactive/Copilot/ObservableCopilotClientTests.cs b/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs index 8de11b0456..12460d2a9a 100644 --- a/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs +++ b/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NSubstitute; using Octokit.Reactive; using Xunit; @@ -28,13 +29,17 @@ public class TheGetAllCopilotLicensesMethod [Fact] public void RequestsCorrectUrl() { - var githubClient = Substitute.For(); - var client = new ObservableCopilotClient(githubClient); - var apiOptions = new ApiOptions(); - + 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.License.GetAll("test", apiOptions); - githubClient.Copilot.License.Received().GetAll(orgName, apiOptions); + connection.Received().Get>(endpoint, + Arg.Is>(d => d.Count > 0)); } } diff --git a/Octokit/Clients/Copilot/CopilotClient.cs b/Octokit/Clients/Copilot/CopilotClient.cs index 93d7a61d30..6574c0e412 100644 --- a/Octokit/Clients/Copilot/CopilotClient.cs +++ b/Octokit/Clients/Copilot/CopilotClient.cs @@ -20,6 +20,12 @@ public CopilotClient(IApiConnection apiConnection) : base(apiConnection) License = new CopilotLicenseClient(apiConnection); } + /// + /// Returns the top level billing settings for an organization. + /// + /// the organization name to retrieve billing settings for + /// A instance + [ManualRoute("GET", "/orgs/{org}/copilot/billing")] public async Task Get(string organization) { Ensure.ArgumentNotNull(organization, nameof(organization)); diff --git a/Octokit/Clients/Copilot/CopilotLicenseClient.cs b/Octokit/Clients/Copilot/CopilotLicenseClient.cs index 7d6b478518..aaf61977aa 100644 --- a/Octokit/Clients/Copilot/CopilotLicenseClient.cs +++ b/Octokit/Clients/Copilot/CopilotLicenseClient.cs @@ -22,6 +22,7 @@ public CopilotLicenseClient(IApiConnection apiConnection) : base(apiConnection) /// 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)); @@ -41,6 +42,7 @@ public async Task Remove(string organization, string user /// 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)); @@ -55,6 +57,7 @@ public async Task Remove(string organization, UserSeatAll /// 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)); @@ -74,6 +77,7 @@ public async Task Assign(string organization, string user /// 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)); @@ -86,17 +90,18 @@ public async Task Assign(string organization, UserSeatAll /// Gets all of the currently allocated licenses for an organization /// /// The organization - /// Options to control page size when making API requests + /// Options to control page size when making API requests /// A list of instance containing the currently allocated user licenses. - public async Task> GetAll(string organization, ApiOptions copilotApiOptions) + [ManualRoute("GET", "/orgs/{org}/copilot/billing/seats")] + public async Task> GetAll(string organization, ApiOptions options) { Ensure.ArgumentNotNull(organization, nameof(organization)); - ApiOptionsExtended options = new ApiOptionsExtended() + var extendedOptions = new ApiOptionsExtended() { - PageSize = copilotApiOptions.PageSize + PageSize = options.PageSize }; - return await ApiConnection.GetAll(ApiUrls.CopilotAllocatedLicenses(organization), options); + return await ApiConnection.GetAll(ApiUrls.CopilotAllocatedLicenses(organization), extendedOptions); } } diff --git a/Octokit/Clients/Copilot/ICopilotLicenseClient.cs b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs index 689fb85a0e..8c6ed2aebe 100644 --- a/Octokit/Clients/Copilot/ICopilotLicenseClient.cs +++ b/Octokit/Clients/Copilot/ICopilotLicenseClient.cs @@ -44,8 +44,8 @@ public interface ICopilotLicenseClient /// 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 + /// 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 copilotApiOptions); + Task> GetAll(string organization, ApiOptions options); } } diff --git a/Octokit/Models/Response/Copilot/BillingSettings.cs b/Octokit/Models/Response/Copilot/BillingSettings.cs index 7231e2c5f1..458e20ce40 100644 --- a/Octokit/Models/Response/Copilot/BillingSettings.cs +++ b/Octokit/Models/Response/Copilot/BillingSettings.cs @@ -9,24 +9,38 @@ namespace Octokit [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; set; } - + public SeatBreakdown SeatBreakdown { get; private set; } + /// /// A string that indicates how seats are billed for the organization. /// - public string SeatManagementSetting { get; set; } + 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; set; } + public string PublicCodeSuggestions { get; private set; } internal string DebuggerDisplay { - get { return string.Format(CultureInfo.InvariantCulture, "SeatManagementSetting: {0}, PublicCodeSuggestions: {1}", SeatManagementSetting, PublicCodeSuggestions); } + 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 index 167ce21f16..3be9cb673b 100644 --- a/Octokit/Models/Response/Copilot/CopilotSeat.cs +++ b/Octokit/Models/Response/Copilot/CopilotSeat.cs @@ -1,17 +1,20 @@ 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) + public CopilotSeat(DateTimeOffset? createdAt, DateTimeOffset? updatedAt, string pendingCancellationDate, DateTimeOffset? lastActivityAt, string lastActivityEditor, User assignee, Team assigningTeam) { CreatedAt = createdAt; UpdatedAt = updatedAt; @@ -19,44 +22,53 @@ public CopilotSeat(DateTimeOffset? createdAt, DateTimeOffset? updatedAt, string 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; set; } + 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; set; } + 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; set; } + public string PendingCancellationDate { get; private set; } /// /// Timestamp of user's last GitHub Copilot activity, in ISO 8601 format. /// - public DateTimeOffset? LastActivityAt { get; set; } + public DateTimeOffset? LastActivityAt { get; private set; } /// /// Last editor that was used by the user for a GitHub Copilot completion. /// - public string LastActivityEditor { get; set; } + public string LastActivityEditor { get; private set; } /// /// The assignee that has been granted access to GitHub Copilot /// - public User Assignee { get; set; } + 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; set; } + 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 index 2c24d8fb62..726f9898f1 100644 --- a/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs +++ b/Octokit/Models/Response/Copilot/CopilotSeatAllocation.cs @@ -1,19 +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; set; } + public long SeatsCancelled { get; private set; } /// /// The total number of seat assignments created. /// - public long SeatsCreated { get; set; } + 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 index 1ddad647e3..97a9ab3043 100644 --- a/Octokit/Models/Response/Copilot/CopilotSeats.cs +++ b/Octokit/Models/Response/Copilot/CopilotSeats.cs @@ -1,15 +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; set; } + public long TotalSeats { get; private set; } /// /// Information about a Copilot Business seat assignment for a user, team, or organization. /// - public CopilotSeat[] Seats { get; set; } + + 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 index fee99058ec..a8f40732f0 100644 --- a/Octokit/Models/Response/Copilot/SeatBreakdown.cs +++ b/Octokit/Models/Response/Copilot/SeatBreakdown.cs @@ -1,38 +1,64 @@ -namespace Octokit +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; set; } + public long Total { get; private set; } /// /// Seats added during the current billing cycle /// - public long AddedThisCycle { get; set; } + 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; set; } + 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; set; } + public long PendingCancellation { get; private set; } /// /// The number of seats that have used Copilot during the current billing cycle. /// - public long ActiveThisCycle { get; set; } + public long ActiveThisCycle { get; private set; } /// /// The number of seats that have not used Copilot during the current billing cycle /// - public long InactiveThisCycle { get; set; } + 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 index 57f00dcd55..07805b4c11 100644 --- a/Octokit/Models/Response/Copilot/UserSeatAllocation.cs +++ b/Octokit/Models/Response/Copilot/UserSeatAllocation.cs @@ -1,13 +1,25 @@ -namespace Octokit +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 From 0138e28a261d4cf90936bba18844283f811d1a6a Mon Sep 17 00:00:00 2001 From: Dylan Morley Date: Sat, 9 Dec 2023 10:42:35 +0000 Subject: [PATCH 8/8] renaming for clarity --- .../Copilot/IObservableCopilotClient.cs | 7 +++--- .../Copilot/ObservableCopilotClient.cs | 11 +++++----- .../Copilot/ObservableCopilotLicenseClient.cs | 4 +--- .../Clients/Copilot/CopilotClientTests.cs | 12 +++++----- .../Helpers/CopilotHelper.cs | 2 +- .../Helpers/GithubClientExtensions.cs | 2 +- .../Clients/Copilot/CopilotClientTests.cs | 12 +++++----- .../Copilot/ObservableCopilotClientTests.cs | 22 +++++++++---------- Octokit/Clients/Copilot/CopilotClient.cs | 9 ++++---- Octokit/Clients/Copilot/ICopilotClient.cs | 7 +++--- 10 files changed, 45 insertions(+), 43 deletions(-) diff --git a/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs b/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs index 3b7085b760..6366ccdcab 100644 --- a/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs +++ b/Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs @@ -8,15 +8,16 @@ namespace Octokit.Reactive public interface IObservableCopilotClient { /// - /// Returns the top level billing settings for an organization. + /// 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 Get(string organization); + IObservable GetSummaryForOrganization(string organization); /// /// For checking and managing licenses for GitHub Copilot for Business /// - IObservableCopilotLicenseClient License { get; } + IObservableCopilotLicenseClient Licensing { get; } } } diff --git a/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs b/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs index 8a33451de7..bd27fb3a37 100644 --- a/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs +++ b/Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs @@ -23,24 +23,25 @@ public ObservableCopilotClient(IGitHubClient client) Ensure.ArgumentNotNull(client, nameof(client)); _client = client.Copilot; - License = new ObservableCopilotLicenseClient(client); + Licensing = new ObservableCopilotLicenseClient(client); } /// - /// Returns the top level billing settings for an organization. + /// 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 Get(string organization) + public IObservable GetSummaryForOrganization(string organization) { Ensure.ArgumentNotNull(organization, nameof(organization)); - return _client.Get(organization).ToObservable(); + return _client.GetSummaryForOrganization(organization).ToObservable(); } /// /// Client for maintaining Copilot licenses for users in an organization. /// - public IObservableCopilotLicenseClient License { get; private set; } + 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 index 3467d3a3a0..ead9377e91 100644 --- a/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs +++ b/Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Reactive.Threading.Tasks; using Octokit; -using Octokit.Models.Request.Enterprise; using Octokit.Reactive; using Octokit.Reactive.Internal; @@ -16,7 +14,7 @@ public class ObservableCopilotLicenseClient : IObservableCopilotLicenseClient public ObservableCopilotLicenseClient(IGitHubClient client) { - _client = client.Copilot.License; + _client = client.Copilot.Licensing; _connection = client.Connection; } diff --git a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs index 3ac18df6b7..2a42d0fc81 100644 --- a/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs +++ b/Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs @@ -18,7 +18,7 @@ public TheGetBillingSettingsMethod() [OrganizationTest] public async Task ReturnsBillingSettingsData() { - var billingSettings = await _gitHub.Copilot.Get(Helper.Organization); + var billingSettings = await _gitHub.Copilot.GetSummaryForOrganization(Helper.Organization); Assert.NotNull(billingSettings.SeatManagementSetting); Assert.NotNull(billingSettings.PublicCodeSuggestions); @@ -39,7 +39,7 @@ public async Task ReturnsUserCopilotLicenseDetailsAsList() { using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) { - var licenses = await _gitHub.Copilot.License.GetAll(Helper.Organization, new ApiOptions()); + var licenses = await _gitHub.Copilot.Licensing.GetAll(Helper.Organization, new ApiOptions()); Assert.True(licenses.Count > 0); } @@ -60,7 +60,7 @@ public async Task AddsLicenseForUser() { using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) { - var allocation = await _gitHub.Copilot.License.Assign(Helper.Organization, Helper.UserName); + var allocation = await _gitHub.Copilot.Licensing.Assign(Helper.Organization, Helper.UserName); Assert.True(allocation.SeatsCreated > 0); } @@ -73,7 +73,7 @@ public async Task AddsLicenseForUsers() { var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } }; - var allocation = await _gitHub.Copilot.License.Assign(Helper.Organization, seatAllocation); + var allocation = await _gitHub.Copilot.Licensing.Assign(Helper.Organization, seatAllocation); Assert.True(allocation.SeatsCreated > 0); } @@ -94,7 +94,7 @@ public async Task RemovesLicenseForUser() { using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName)) { - var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, Helper.UserName); + var allocation = await _gitHub.Copilot.Licensing.Remove(Helper.Organization, Helper.UserName); Assert.True(allocation.SeatsCancelled > 0); } @@ -107,7 +107,7 @@ public async Task RemovesLicenseForUsers() { var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } }; - var allocation = await _gitHub.Copilot.License.Remove(Helper.Organization, seatAllocation); + 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 index 01a9a2ac9f..6c18153c2a 100644 --- a/Octokit.Tests.Integration/Helpers/CopilotHelper.cs +++ b/Octokit.Tests.Integration/Helpers/CopilotHelper.cs @@ -7,7 +7,7 @@ internal sealed class CopilotHelper public static void RemoveUserLicense(IConnection connection, string organization, string userLogin) { var client = new GitHubClient(connection); - client.Copilot.License.Remove(organization, userLogin).Wait(TimeSpan.FromSeconds(15)); + client.Copilot.Licensing.Remove(organization, userLogin).Wait(TimeSpan.FromSeconds(15)); } } } diff --git a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs index 005354c3e1..37e5e9599e 100644 --- a/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs +++ b/Octokit.Tests.Integration/Helpers/GithubClientExtensions.cs @@ -144,7 +144,7 @@ internal static async Task CreateEnterpriseUserContext(th internal static async Task CreateCopilotUserLicenseContext(this IGitHubClient client, string organization, string userName) { - await client.Copilot.License.Assign(organization, userName); + await client.Copilot.Licensing.Assign(organization, userName); return new CopilotUserLicenseContext(client.Connection, organization, userName); } diff --git a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs index 16f9c1eb4c..bac7083935 100644 --- a/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs +++ b/Octokit.Tests/Clients/Copilot/CopilotClientTests.cs @@ -18,7 +18,7 @@ public void RequestsCorrectUrl() var client = new CopilotClient(connection); var expectedUri = $"orgs/{orgName}/copilot/billing"; - client.Get("test"); + client.GetSummaryForOrganization("test"); connection.Received().Get(Arg.Is(u => u.ToString() == expectedUri)); } @@ -33,7 +33,7 @@ public void RequestsCorrectUrl() var client = new CopilotClient(connection); var expectedUri = $"orgs/{orgName}/copilot/billing/seats"; - client.License.GetAll("test", new ApiOptions()); + client.Licensing.GetAll("test", new ApiOptions()); connection.Received().GetAll(Arg.Is(u => u.ToString() == expectedUri), Arg.Any()); } @@ -48,7 +48,7 @@ public void RequestsCorrectUrl() var client = new CopilotClient(connection); var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users"; - client.License.Assign(orgName, "copilot-user"); + client.Licensing.Assign(orgName, "copilot-user"); connection.Received().Post(Arg.Is(u => u.ToString() == expectedUri), Arg.Any()); } @@ -64,7 +64,7 @@ public void RequestsCorrectUrl() var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users"; var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } }; - client.License.Assign(orgName, payloadData); + client.Licensing.Assign(orgName, payloadData); connection.Received().Post(Arg.Is(u => u.ToString() == expectedUri), payloadData); } @@ -79,7 +79,7 @@ public void RequestsCorrectUrl() var client = new CopilotClient(connection); var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users"; - client.License.Remove(orgName, "copilot-user" ); + client.Licensing.Remove(orgName, "copilot-user" ); connection.Received().Delete(Arg.Is(u => u.ToString() == expectedUri), Arg.Any()); } @@ -95,7 +95,7 @@ public void RequestsCorrectUrl() var expectedUri = $"orgs/{orgName}/copilot/billing/selected_users"; var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } }; - client.License.Remove(orgName, payloadData); + client.Licensing.Remove(orgName, payloadData); connection.Received().Delete(Arg.Is(u => u.ToString() == expectedUri), payloadData); } diff --git a/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs b/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs index 12460d2a9a..078633bb50 100644 --- a/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs +++ b/Octokit.Tests/Reactive/Copilot/ObservableCopilotClientTests.cs @@ -18,9 +18,9 @@ public void RequestsCorrectUrl() var githubClient = Substitute.For(); var client = new ObservableCopilotClient(githubClient); - client.Get("test"); + client.GetSummaryForOrganization("test"); - githubClient.Copilot.Received(1).Get(orgName); + githubClient.Copilot.Received(1).GetSummaryForOrganization(orgName); } } @@ -36,7 +36,7 @@ public void RequestsCorrectUrl() var client = new ObservableCopilotClient(gitHubClient); var apiOptions = new ApiOptions() { PageSize = 50, PageCount = 10 }; - client.License.GetAll("test", apiOptions); + client.Licensing.GetAll("test", apiOptions); connection.Received().Get>(endpoint, Arg.Is>(d => d.Count > 0)); @@ -52,9 +52,9 @@ public void RequestsCorrectUrl() var client = new ObservableCopilotClient(githubClient); const string expectedUser = "copilot-user"; - client.License.Assign(orgName, expectedUser); + client.Licensing.Assign(orgName, expectedUser); - githubClient.Copilot.License.Received().Assign(orgName, expectedUser); + githubClient.Copilot.Licensing.Received().Assign(orgName, expectedUser); } } @@ -67,9 +67,9 @@ public void RequestsCorrectUrl() var client = new ObservableCopilotClient(githubClient); var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } }; - client.License.Assign(orgName, payloadData); + client.Licensing.Assign(orgName, payloadData); - githubClient.Copilot.License.Received().Assign(orgName, payloadData); + githubClient.Copilot.Licensing.Received().Assign(orgName, payloadData); } } @@ -82,9 +82,9 @@ public void RequestsCorrectUrl() var client = new ObservableCopilotClient(githubClient); const string expectedUser = "copilot-user"; - client.License.Remove(orgName, expectedUser); + client.Licensing.Remove(orgName, expectedUser); - githubClient.Copilot.License.Received().Remove(orgName, expectedUser); + githubClient.Copilot.Licensing.Received().Remove(orgName, expectedUser); } } @@ -97,9 +97,9 @@ public void RequestsCorrectUrl() var client = new ObservableCopilotClient(githubClient); var payloadData = new UserSeatAllocation() { SelectedUsernames = new[] { "copilot-user" } }; - client.License.Remove(orgName, payloadData); + client.Licensing.Remove(orgName, payloadData); - githubClient.Copilot.License.Received().Remove(orgName, payloadData); + githubClient.Copilot.Licensing.Received().Remove(orgName, payloadData); } } diff --git a/Octokit/Clients/Copilot/CopilotClient.cs b/Octokit/Clients/Copilot/CopilotClient.cs index 6574c0e412..30c167ac8a 100644 --- a/Octokit/Clients/Copilot/CopilotClient.cs +++ b/Octokit/Clients/Copilot/CopilotClient.cs @@ -17,16 +17,17 @@ public class CopilotClient : ApiClient, ICopilotClient /// public CopilotClient(IApiConnection apiConnection) : base(apiConnection) { - License = new CopilotLicenseClient(apiConnection); + Licensing = new CopilotLicenseClient(apiConnection); } /// - /// Returns the top level billing settings for an organization. + /// 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 Get(string organization) + public async Task GetSummaryForOrganization(string organization) { Ensure.ArgumentNotNull(organization, nameof(organization)); @@ -36,6 +37,6 @@ public async Task Get(string organization) /// /// Client for maintaining Copilot licenses for users in an organization. /// - public ICopilotLicenseClient License { get; private set; } + public ICopilotLicenseClient Licensing { get; private set; } } } \ No newline at end of file diff --git a/Octokit/Clients/Copilot/ICopilotClient.cs b/Octokit/Clients/Copilot/ICopilotClient.cs index 9b17a6d52a..dd72ba7704 100644 --- a/Octokit/Clients/Copilot/ICopilotClient.cs +++ b/Octokit/Clients/Copilot/ICopilotClient.cs @@ -8,15 +8,16 @@ namespace Octokit public interface ICopilotClient { /// - /// Returns the top level billing settings for an organization. + /// 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 Get(string organization); + Task GetSummaryForOrganization(string organization); /// /// For checking and managing licenses for GitHub Copilot for Business /// - ICopilotLicenseClient License { get; } + ICopilotLicenseClient Licensing { get; } } }