Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[FEAT]: Adding Copilot for Business support #2826

Merged
merged 11 commits into from
Jan 2, 2024
23 changes: 23 additions & 0 deletions Octokit.Reactive/Clients/Copilot/IObservableCopilotClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

namespace Octokit.Reactive
{
/// <summary>
/// Access GitHub's Copilot for Business API.
/// </summary>
public interface IObservableCopilotClient
{
/// <summary>
/// Returns a summary of the Copilot for Business configuration for an organization. Includes a seat
/// details summary of the current billing cycle, and the mode of seat management.
/// </summary>
/// <param name="organization">the organization name to retrieve billing settings for</param>
/// <returns>A <see cref="BillingSettings"/> instance</returns>
IObservable<BillingSettings> GetSummaryForOrganization(string organization);

/// <summary>
/// For checking and managing licenses for GitHub Copilot for Business
/// </summary>
IObservableCopilotLicenseClient Licensing { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;

namespace Octokit.Reactive
{
/// <summary>
/// A client for managing licenses for GitHub Copilot for Business
/// </summary>
public interface IObservableCopilotLicenseClient
{
/// <summary>
/// Removes a license for a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to remove a license from</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
IObservable<CopilotSeatAllocation> Remove(string organization, string userName);

/// <summary>
/// Removes a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to remove licenses for</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
IObservable<CopilotSeatAllocation> Remove(string organization, UserSeatAllocation userSeatAllocation);

/// <summary>
/// Assigns a license to a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to add a license to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
IObservable<CopilotSeatAllocation> Assign(string organization, string userName);

/// <summary>
/// Assigns a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to add licenses to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
IObservable<CopilotSeatAllocation> Assign(string organization, UserSeatAllocation userSeatAllocation);

/// <summary>
/// Gets all of the currently allocated licenses for an organization
/// </summary>
/// <param name="organization">The organization</param>
/// <param name="options">The api options to use when making the API call, such as paging</param>
/// <returns>A <see cref="CopilotSeats"/> instance containing the currently allocated user licenses</returns>
IObservable<CopilotSeats> GetAll(string organization, ApiOptions options);
}
}
47 changes: 47 additions & 0 deletions Octokit.Reactive/Clients/Copilot/ObservableCopilotClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Reactive.Threading.Tasks;

namespace Octokit.Reactive
{
/// <summary>
/// A client for GitHub's Copilot for Business API.
/// Allows listing, creating, and deleting Copilot licenses.
/// </summary>
/// <remarks>
/// See the <a href="https://docs.github.com/en/rest/copilot/copilot-business?apiVersion=2022-11-28">Copilot for Business API documentation</a> for more information.
/// </remarks>
public class ObservableCopilotClient : IObservableCopilotClient
{
private readonly ICopilotClient _client;

/// <summary>
/// Instantiates a new GitHub Copilot API client.
/// </summary>
/// <param name="client"></param>
public ObservableCopilotClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, nameof(client));

_client = client.Copilot;
Licensing = new ObservableCopilotLicenseClient(client);
}

/// <summary>
/// Returns a summary of the Copilot for Business configuration for an organization. Includes a seat
/// details summary of the current billing cycle, and the mode of seat management.
/// </summary>
/// <param name="organization">the organization name to retrieve billing settings for</param>
/// <returns>A <see cref="BillingSettings"/> instance</returns>
public IObservable<BillingSettings> GetSummaryForOrganization(string organization)
{
Ensure.ArgumentNotNull(organization, nameof(organization));

return _client.GetSummaryForOrganization(organization).ToObservable();
}

/// <summary>
/// Client for maintaining Copilot licenses for users in an organization.
/// </summary>
public IObservableCopilotLicenseClient Licensing { get; private set; }
}
}
90 changes: 90 additions & 0 deletions Octokit.Reactive/Clients/Copilot/ObservableCopilotLicenseClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Reactive.Threading.Tasks;
using Octokit;
using Octokit.Reactive;
using Octokit.Reactive.Internal;

/// <summary>
/// A client for managing licenses for GitHub Copilot for Business
/// </summary>
public class ObservableCopilotLicenseClient : IObservableCopilotLicenseClient
{
private readonly ICopilotLicenseClient _client;
private readonly IConnection _connection;

public ObservableCopilotLicenseClient(IGitHubClient client)
{
_client = client.Copilot.Licensing;
_connection = client.Connection;
}

/// <summary>
/// Removes a license for a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to remove a license from</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
public IObservable<CopilotSeatAllocation> Remove(string organization, string userName)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userName, nameof(userName));

return _client.Remove(organization, userName).ToObservable();
}

/// <summary>
/// Removes a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to remove licenses for</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
public IObservable<CopilotSeatAllocation> Remove(string organization, UserSeatAllocation userSeatAllocation)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));

return _client.Remove(organization, userSeatAllocation).ToObservable();
}

/// <summary>
/// Assigns a license to a user
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userName">The github users profile name to add a license to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
public IObservable<CopilotSeatAllocation> Assign(string organization, string userName)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userName, nameof(userName));

return _client.Assign(organization, userName).ToObservable();
}

/// <summary>
/// Assigns a license for one or many users
/// </summary>
/// <param name="organization">The organization name</param>
/// <param name="userSeatAllocation">A <see cref="UserSeatAllocation"/> instance, containing the names of the user(s) to add licenses to</param>
/// <returns>A <see cref="CopilotSeatAllocation"/> instance with results</returns>
public IObservable<CopilotSeatAllocation> Assign(string organization, UserSeatAllocation userSeatAllocation)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(userSeatAllocation, nameof(userSeatAllocation));

return _client.Assign(organization, userSeatAllocation).ToObservable();
}

/// <summary>
/// Gets all of the currently allocated licenses for an organization
/// </summary>
/// <param name="organization">The organization</param>
/// <param name="options">Options to control page size when making API requests</param>
/// <returns>A list of <see cref="CopilotSeats"/> instance containing the currently allocated user licenses.</returns>
public IObservable<CopilotSeats> GetAll(string organization, ApiOptions options)
{
Ensure.ArgumentNotNull(organization, nameof(organization));
Ensure.ArgumentNotNull(options, nameof(options));

return _connection.GetAndFlattenAllPages<CopilotSeats>( ApiUrls.CopilotAllocatedLicenses(organization), options);
}
}
1 change: 1 addition & 0 deletions Octokit.Reactive/IObservableGitHubClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,6 @@ public interface IObservableGitHubClient : IApiInfoProvider
IObservableMetaClient Meta { get; }
IObservableActionsClient Actions { get; }
IObservableCodespacesClient Codespaces { get; }
IObservableCopilotClient Copilot { get; }
}
}
4 changes: 3 additions & 1 deletion Octokit.Reactive/ObservableGitHubClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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; }

/// <summary>
/// Gets the latest API Info - this will be null if no API calls have been made
/// </summary>
Expand Down
117 changes: 117 additions & 0 deletions Octokit.Tests.Integration/Clients/Copilot/CopilotClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System.Threading.Tasks;
using Octokit.Tests.Integration.Helpers;
using Xunit;

namespace Octokit.Tests.Integration.Clients.Copilot
{
public class CopilotClientTests
{
public class TheGetBillingSettingsMethod
{
private readonly IGitHubClient _gitHub;

public TheGetBillingSettingsMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
}

[OrganizationTest]
public async Task ReturnsBillingSettingsData()
{
var billingSettings = await _gitHub.Copilot.GetSummaryForOrganization(Helper.Organization);

Assert.NotNull(billingSettings.SeatManagementSetting);
Assert.NotNull(billingSettings.PublicCodeSuggestions);
}
}

public class TheGetAllLicensesMethod
{
private readonly IGitHubClient _gitHub;

public TheGetAllLicensesMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
}

[OrganizationTest]
public async Task ReturnsUserCopilotLicenseDetailsAsList()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var licenses = await _gitHub.Copilot.Licensing.GetAll(Helper.Organization, new ApiOptions());

Assert.True(licenses.Count > 0);
}
}
}

public class TheAddLicenseMethod
{
private readonly IGitHubClient _gitHub;

public TheAddLicenseMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
}

[OrganizationTest]
public async Task AddsLicenseForUser()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var allocation = await _gitHub.Copilot.Licensing.Assign(Helper.Organization, Helper.UserName);

Assert.True(allocation.SeatsCreated > 0);
}
}

[OrganizationTest]
public async Task AddsLicenseForUsers()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } };

var allocation = await _gitHub.Copilot.Licensing.Assign(Helper.Organization, seatAllocation);

Assert.True(allocation.SeatsCreated > 0);
}
}
}

public class TheDeleteLicenseMethod
{
private readonly IGitHubClient _gitHub;

public TheDeleteLicenseMethod()
{
_gitHub = Helper.GetAuthenticatedClient();
}

[OrganizationTest]
public async Task RemovesLicenseForUser()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var allocation = await _gitHub.Copilot.Licensing.Remove(Helper.Organization, Helper.UserName);

Assert.True(allocation.SeatsCancelled > 0);
}
}

[OrganizationTest]
public async Task RemovesLicenseForUsers()
{
using (var context = await _gitHub.CreateCopilotUserLicenseContext(Helper.Organization, Helper.UserName))
{
var seatAllocation = new UserSeatAllocation() { SelectedUsernames = new[] { Helper.UserName } };

var allocation = await _gitHub.Copilot.Licensing.Remove(Helper.Organization, seatAllocation);

Assert.True(allocation.SeatsCancelled > 0);
}
}
}
}
}
13 changes: 13 additions & 0 deletions Octokit.Tests.Integration/Helpers/CopilotHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Octokit.Tests.Integration.Helpers
{
internal sealed class CopilotHelper
{
public static void RemoveUserLicense(IConnection connection, string organization, string userLogin)
{
var client = new GitHubClient(connection);
client.Copilot.Licensing.Remove(organization, userLogin).Wait(TimeSpan.FromSeconds(15));
}
}
}
24 changes: 24 additions & 0 deletions Octokit.Tests.Integration/Helpers/CopilotUserLicenseContext.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading
Loading