Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/Dapplo.Jira.Tests/GroupTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Dapplo and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Threading.Tasks;
using Xunit;

namespace Dapplo.Jira.Tests;

public class GroupTests : TestBase
{
public GroupTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
}

[Fact]
public async Task TestSearchGroupsAsync()
{
// Search for groups
var groups = await Client.Group.SearchAsync("jira", cancellationToken: TestContext.Current.CancellationToken);
Assert.NotNull(groups);
Assert.NotNull(groups.Values);
}

[Fact]
public async Task TestSearchGroupsWithoutQueryAsync()
{
// Search for all groups
var groups = await Client.Group.SearchAsync(cancellationToken: TestContext.Current.CancellationToken);
Assert.NotNull(groups);
Assert.NotNull(groups.Values);
}
}
11 changes: 11 additions & 0 deletions src/Dapplo.Jira/Domains/IGroupDomain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Dapplo and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Dapplo.Jira.Domains;

/// <summary>
/// The marker interface for the group domain
/// </summary>
public interface IGroupDomain : IJiraDomain
{
}
26 changes: 26 additions & 0 deletions src/Dapplo.Jira/Entities/Group.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Dapplo and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Newtonsoft.Json;

namespace Dapplo.Jira.Entities;

/// <summary>
/// Group information
/// See: https://docs.atlassian.com/jira/REST/latest/#api/2/group
/// </summary>
[JsonObject]
public class Group
{
/// <summary>
/// Name of the group
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }

/// <summary>
/// Link to this item (self)
/// </summary>
[JsonProperty("self")]
public Uri Self { get; set; }
}
51 changes: 51 additions & 0 deletions src/Dapplo.Jira/Entities/GroupWithUsers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Dapplo and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Newtonsoft.Json;

namespace Dapplo.Jira.Entities;

/// <summary>
/// Group with users information
/// See: https://docs.atlassian.com/jira/REST/latest/#api/2/group
/// </summary>
[JsonObject]
public class GroupWithUsers : PageableResult
{
/// <summary>
/// Name of the group
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }

/// <summary>
/// Link to this item (self)
/// </summary>
[JsonProperty("self")]
public Uri Self { get; set; }

/// <summary>
/// Users in this group
/// </summary>
[JsonProperty("users")]
public PagedUsers Users { get; set; }

/// <summary>
/// Expand options
/// </summary>
[JsonProperty("expand")]
public string Expand { get; set; }
}

/// <summary>
/// Paged users within a group
/// </summary>
[JsonObject]
public class PagedUsers : PageableResult
{
/// <summary>
/// The list of users
/// </summary>
[JsonProperty("items")]
public IList<User> Items { get; set; }
}
233 changes: 233 additions & 0 deletions src/Dapplo.Jira/GroupDomainExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
// Copyright (c) Dapplo and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using JiraGroup = Dapplo.Jira.Entities.Group;
using JiraGroupWithUsers = Dapplo.Jira.Entities.GroupWithUsers;

namespace Dapplo.Jira;

/// <summary>
/// This holds all the group related extension methods
/// </summary>
public static class GroupDomainExtensions
{
#pragma warning disable IDE0090 // Use 'new(...)'
private static readonly LogSource Log = new LogSource();
#pragma warning restore IDE0090 // Use 'new(...)'

/// <summary>
/// Search for groups
/// See: https://docs.atlassian.com/jira/REST/latest/#api/2/group-findGroups
/// </summary>
/// <param name="jiraClient">IGroupDomain to bind the extension method to</param>
/// <param name="query">A query string used to search for groups</param>
/// <param name="excludeGroups">Comma separated list of groups to exclude from the search</param>
/// <param name="maxResults">Maximum number of results returned, default is 20</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>SearchResult with Group</returns>
public static async Task<SearchResult<JiraGroup, string>> SearchAsync(this IGroupDomain jiraClient, string query = null,
string excludeGroups = null, int maxResults = 20, CancellationToken cancellationToken = default)
{
Log.Debug().WriteLine("Searching for groups with query {0}", query);

jiraClient.Behaviour.MakeCurrent();
var searchUri = jiraClient.JiraRestUri.AppendSegments("groups", "picker");

var queryParams = new Dictionary<string, object>
{
{ "maxResults", maxResults }
};

if (!string.IsNullOrEmpty(query))
{
queryParams.Add("query", query);
}

if (!string.IsNullOrEmpty(excludeGroups))
{
queryParams.Add("exclude", excludeGroups);
}

searchUri = searchUri.ExtendQuery(queryParams);

var response = await searchUri.GetAsAsync<HttpResponse<SearchResult<JiraGroup, string>, Error>>(cancellationToken).ConfigureAwait(false);
return response.HandleErrors();
}

/// <summary>
/// Create a new group
/// See: https://docs.atlassian.com/jira/REST/latest/#api/2/group-createGroup
/// </summary>
/// <param name="jiraClient">IGroupDomain to bind the extension method to</param>
/// <param name="groupName">Name of the group to create</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Group</returns>
public static async Task<JiraGroup> CreateAsync(this IGroupDomain jiraClient, string groupName, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(groupName))
{
throw new ArgumentNullException(nameof(groupName));
}

Log.Debug().WriteLine("Creating group {0}", groupName);

var groupUri = jiraClient.JiraRestUri.AppendSegments("group");
jiraClient.Behaviour.MakeCurrent();

var groupToCreate = new JiraGroup
{
Name = groupName
};

var response = await groupUri.PostAsync<HttpResponse<JiraGroup, Error>>(groupToCreate, cancellationToken).ConfigureAwait(false);
return response.HandleErrors();
}

/// <summary>
/// Delete a group
/// See: https://docs.atlassian.com/jira/REST/latest/#api/2/group-removeGroup
/// </summary>
/// <param name="jiraClient">IGroupDomain to bind the extension method to</param>
/// <param name="groupName">Name of the group to delete</param>
/// <param name="swapGroup">Optional name of group to transfer visibility restrictions to</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Task</returns>
public static async Task DeleteAsync(this IGroupDomain jiraClient, string groupName, string swapGroup = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(groupName))
{
throw new ArgumentNullException(nameof(groupName));
}

Log.Debug().WriteLine("Deleting group {0}", groupName);

var groupUri = jiraClient.JiraRestUri.AppendSegments("group").ExtendQuery("groupname", groupName);

if (!string.IsNullOrEmpty(swapGroup))
{
groupUri = groupUri.ExtendQuery("swapGroup", swapGroup);
}

jiraClient.Behaviour.MakeCurrent();
var response = await groupUri.DeleteAsync<HttpResponse<string, Error>>(cancellationToken).ConfigureAwait(false);
response.HandleErrors();
}

/// <summary>
/// Add user to group
/// See: https://docs.atlassian.com/jira/REST/latest/#api/2/group-addUserToGroup
/// </summary>
/// <param name="jiraClient">IGroupDomain to bind the extension method to</param>
/// <param name="groupName">Name of the group</param>
/// <param name="username">Username of the user to add (deprecated, use accountId)</param>
/// <param name="accountId">AccountId of the user to add</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Group</returns>
public static async Task<JiraGroup> AddUserAsync(this IGroupDomain jiraClient, string groupName, string username = null,
string accountId = null, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(groupName))
{
throw new ArgumentNullException(nameof(groupName));
}

if (string.IsNullOrEmpty(username) && string.IsNullOrEmpty(accountId))
{
throw new ArgumentException("Either username or accountId must be provided");
}

Log.Debug().WriteLine("Adding user to group {0}", groupName);

var groupUri = jiraClient.JiraRestUri.AppendSegments("group", "user").ExtendQuery("groupname", groupName);
jiraClient.Behaviour.MakeCurrent();

var userToAdd = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(accountId))
{
userToAdd.Add("accountId", accountId);
}
else
{
userToAdd.Add("name", username);
}

var response = await groupUri.PostAsync<HttpResponse<JiraGroup, Error>>(userToAdd, cancellationToken).ConfigureAwait(false);
return response.HandleErrors();
}

/// <summary>
/// Remove user from group
/// See: https://docs.atlassian.com/jira/REST/latest/#api/2/group-removeUserFromGroup
/// </summary>
/// <param name="jiraClient">IGroupDomain to bind the extension method to</param>
/// <param name="groupName">Name of the group</param>
/// <param name="username">Username of the user to remove (deprecated, use accountId)</param>
/// <param name="accountId">AccountId of the user to remove</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Task</returns>
public static async Task RemoveUserAsync(this IGroupDomain jiraClient, string groupName, string username = null,
string accountId = null, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(groupName))
{
throw new ArgumentNullException(nameof(groupName));
}

if (string.IsNullOrEmpty(username) && string.IsNullOrEmpty(accountId))
{
throw new ArgumentException("Either username or accountId must be provided");
}

Log.Debug().WriteLine("Removing user from group {0}", groupName);

var groupUri = jiraClient.JiraRestUri.AppendSegments("group", "user").ExtendQuery("groupname", groupName);

if (!string.IsNullOrEmpty(accountId))
{
groupUri = groupUri.ExtendQuery("accountId", accountId);
}
else
{
groupUri = groupUri.ExtendQuery("username", username);
}

jiraClient.Behaviour.MakeCurrent();
var response = await groupUri.DeleteAsync<HttpResponse<string, Error>>(cancellationToken).ConfigureAwait(false);
response.HandleErrors();
}

/// <summary>
/// Get users from a group
/// See: https://docs.atlassian.com/jira/REST/latest/#api/2/group-getUsersFromGroup
/// </summary>
/// <param name="jiraClient">IGroupDomain to bind the extension method to</param>
/// <param name="groupName">Name of the group</param>
/// <param name="includeInactiveUsers">Include inactive users in the result</param>
/// <param name="startAt">Index of the first user to return (0-based)</param>
/// <param name="maxResults">Maximum number of users to return, default is 50</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>GroupWithUsers containing the users</returns>
public static async Task<JiraGroupWithUsers> GetUsersAsync(this IGroupDomain jiraClient, string groupName,
bool includeInactiveUsers = false, int startAt = 0, int maxResults = 50, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(groupName))
{
throw new ArgumentNullException(nameof(groupName));
}

Log.Debug().WriteLine("Getting users from group {0}", groupName);

jiraClient.Behaviour.MakeCurrent();
var groupUri = jiraClient.JiraRestUri.AppendSegments("group").ExtendQuery(new Dictionary<string, object>
{
{ "groupname", groupName },
{ "includeInactiveUsers", includeInactiveUsers },
{ "startAt", startAt },
{ "maxResults", maxResults }
});

var response = await groupUri.GetAsAsync<HttpResponse<JiraGroupWithUsers, Error>>(cancellationToken).ConfigureAwait(false);
return response.HandleErrors();
}
}
5 changes: 5 additions & 0 deletions src/Dapplo.Jira/IJiraClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public interface IJiraClient
/// </summary>
IGreenhopperDomain Greenhopper { get; }

/// <summary>
/// Group domain
/// </summary>
IGroupDomain Group { get; }

/// <summary>
/// Set Basic Authentication for the current client
/// </summary>
Expand Down
7 changes: 6 additions & 1 deletion src/Dapplo.Jira/JiraClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Dapplo.Jira
/// NOTE: Modernization in progress - currently using Dapplo.HttpExtensions, will migrate to RestSharp
/// </summary>
public class JiraClient : IProjectDomain, IWorkLogDomain, IUserDomain, ISessionDomain, IIssueDomain, IFilterDomain, IAttachmentDomain, IServerDomain, IAgileDomain,
IGreenhopperDomain
IGreenhopperDomain, IGroupDomain
{
private string password;
private string user;
Expand Down Expand Up @@ -177,5 +177,10 @@ public IJiraClient SetBearerAuthentication(string bearer)
/// Greenhopper domain
/// </summary>
public IGreenhopperDomain Greenhopper => this;

/// <summary>
/// Group domain
/// </summary>
public IGroupDomain Group => this;
}
}
Loading