Skip to content
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