diff --git a/src/GitLabApiClient/GitLabClient.cs b/src/GitLabApiClient/GitLabClient.cs
index 0259842e..84d51ffd 100644
--- a/src/GitLabApiClient/GitLabClient.cs
+++ b/src/GitLabApiClient/GitLabClient.cs
@@ -36,13 +36,13 @@ public GitLabClient(string hostUrl, string authenticationToken = "")
authenticationToken);
var projectQueryBuilder = new ProjectsQueryBuilder();
- var projectIssuesQueryBuilder = new ProjectIssuesQueryBuilder();
var projectIssueNotesQueryBuilder = new ProjectIssueNotesQueryBuilder();
var issuesQueryBuilder = new IssuesQueryBuilder();
var mergeRequestsQueryBuilder = new MergeRequestsQueryBuilder();
var projectMilestonesQueryBuilder = new MilestonesQueryBuilder();
var projectMergeRequestsQueryBuilder = new ProjectMergeRequestsQueryBuilder();
var groupsQueryBuilder = new GroupsQueryBuilder();
+ var groupLabelsQueryBuilder = new GroupLabelsQueryBuilder();
var projectsGroupsQueryBuilder = new ProjectsGroupQueryBuilder();
var branchQueryBuilder = new BranchQueryBuilder();
var releaseQueryBuilder = new ReleaseQueryBuilder();
@@ -52,12 +52,12 @@ public GitLabClient(string hostUrl, string authenticationToken = "")
var pipelineQueryBuilder = new PipelineQueryBuilder();
var treeQueryBuilder = new TreeQueryBuilder();
- Issues = new IssuesClient(_httpFacade, issuesQueryBuilder, projectIssuesQueryBuilder, projectIssueNotesQueryBuilder);
+ Issues = new IssuesClient(_httpFacade, issuesQueryBuilder, projectIssueNotesQueryBuilder);
Uploads = new UploadsClient(_httpFacade);
MergeRequests = new MergeRequestsClient(_httpFacade, mergeRequestsQueryBuilder, projectMergeRequestsQueryBuilder);
Projects = new ProjectsClient(_httpFacade, projectQueryBuilder, projectMilestonesQueryBuilder);
Users = new UsersClient(_httpFacade);
- Groups = new GroupsClient(_httpFacade, groupsQueryBuilder, projectsGroupsQueryBuilder, projectMilestonesQueryBuilder);
+ Groups = new GroupsClient(_httpFacade, groupsQueryBuilder, projectsGroupsQueryBuilder, projectMilestonesQueryBuilder, groupLabelsQueryBuilder);
Branches = new BranchClient(_httpFacade, branchQueryBuilder);
Releases = new ReleaseClient(_httpFacade, releaseQueryBuilder);
Tags = new TagClient(_httpFacade, tagQueryBuilder);
diff --git a/src/GitLabApiClient/GroupsClient.cs b/src/GitLabApiClient/GroupsClient.cs
index 9a0fc584..3f157078 100644
--- a/src/GitLabApiClient/GroupsClient.cs
+++ b/src/GitLabApiClient/GroupsClient.cs
@@ -26,24 +26,27 @@ public sealed class GroupsClient
private readonly GroupsQueryBuilder _queryBuilder;
private readonly ProjectsGroupQueryBuilder _projectsQueryBuilder;
private readonly MilestonesQueryBuilder _queryMilestonesBuilder;
+ private readonly GroupLabelsQueryBuilder _queryGroupLabelBuilder;
internal GroupsClient(
GitLabHttpFacade httpFacade,
GroupsQueryBuilder queryBuilder,
ProjectsGroupQueryBuilder projectsQueryBuilder,
- MilestonesQueryBuilder queryMilestonesBuilder)
+ MilestonesQueryBuilder queryMilestonesBuilder,
+ GroupLabelsQueryBuilder queryGroupLabelBuilder)
{
_httpFacade = httpFacade;
_queryBuilder = queryBuilder;
_projectsQueryBuilder = projectsQueryBuilder;
_queryMilestonesBuilder = queryMilestonesBuilder;
+ _queryGroupLabelBuilder = queryGroupLabelBuilder;
}
///
/// Get all details of a group.
/// This endpoint can be accessed without authentication if the group is publicly accessible.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
public async Task GetAsync(GroupId groupId) =>
await _httpFacade.Get($"groups/{groupId}");
@@ -51,7 +54,7 @@ public async Task GetAsync(GroupId groupId) =>
/// Get all subgroups of a group.
/// This endpoint can be accessed without authentication if the group is publicly accessible.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
public async Task> GetSubgroupsAsync(GroupId groupId) =>
await _httpFacade.GetPagedList($"groups/{groupId}/subgroups");
@@ -80,7 +83,7 @@ public async Task> GetAsync(Action options = nu
/// Get a list of projects in this group.
/// When accessed without authentication, only public projects are returned.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// Groups projects retrieval options.
/// Issues satisfying options.
public async Task> GetProjectsAsync(GroupId groupId, Action options = null)
@@ -95,7 +98,7 @@ public async Task> GetProjectsAsync(GroupId groupId, Action
/// Get a list of members in this group.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// A query string to search for members.
/// Group members satisfying options.
public async Task> GetMembersAsync(GroupId groupId, string search = null)
@@ -113,7 +116,7 @@ public async Task> GetMembersAsync(GroupId groupId, string search
///
/// Get a list of all members (including inherited) in this group.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// A query string to search for members.
/// Group members satisfying options.
public async Task> GetAllMembersAsync(GroupId groupId, string search = null)
@@ -131,7 +134,7 @@ public async Task> GetAllMembersAsync(GroupId groupId, string sear
///
/// Adds a member to the group.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// Create milestone request.
/// Newly created milestone.
public async Task CreateMilestoneAsync(GroupId groupId, CreateGroupMilestoneRequest request)
@@ -143,7 +146,7 @@ public async Task CreateMilestoneAsync(GroupId groupId, CreateGroupMi
///
/// Get a list of milestones in this group.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// Query options.
public async Task> GetMilestonesAsync(GroupId groupId, Action options = null)
{
@@ -157,7 +160,7 @@ public async Task> GetMilestonesAsync(GroupId groupId, Action
/// Retrieves a group milestone by its id.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// Id of the milestone.
public async Task GetMilestoneAsync(GroupId groupId, int milestoneId) =>
await _httpFacade.Get($"groups/{groupId}/milestones/{milestoneId}");
@@ -174,7 +177,7 @@ public async Task CreateAsync(CreateGroupRequest request) =>
///
/// Adds a user to a group.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// Add group member request.
/// Newly created membership.
public async Task AddMemberAsync(GroupId groupId, AddGroupMemberRequest request)
@@ -186,7 +189,7 @@ public async Task AddMemberAsync(GroupId groupId, AddGroupMemberRequest
///
/// Updates a user's group membership.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// The user ID of the member.
/// Update group member request.
/// Updated membership.
@@ -199,7 +202,7 @@ public async Task UpdateMemberAsync(GroupId groupId, int userId, AddGrou
///
/// Removes a user as a member of the group.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// The user ID of the member.
public async Task RemoveMemberAsync(GroupId groupId, int userId) =>
await _httpFacade.Delete($"groups/{groupId}/members/{userId}");
@@ -207,7 +210,7 @@ public async Task RemoveMemberAsync(GroupId groupId, int userId) =>
///
/// Transfer a project to the Group namespace. Available only for admin
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// The ID, path or of the project.
/// The newly updated group.
public async Task TransferAsync(GroupId groupId, ProjectId projectId) =>
@@ -218,7 +221,7 @@ public async Task TransferAsync(GroupId groupId, ProjectId projectId) =>
/// Only available to group owners and administrators.
///
/// The updated group.
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// Update group request.
public async Task UpdateAsync(GroupId groupId, UpdateGroupRequest request) =>
await _httpFacade.Put($"groups/{groupId}", request);
@@ -226,7 +229,7 @@ public async Task UpdateAsync(GroupId groupId, UpdateGroupRequest request
///
/// Updates an existing group milestone.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// The ID of the group's milestone.
/// Update milestone request.
/// Newly modified milestone.
@@ -240,14 +243,14 @@ public async Task UpdateMilestoneAsync(GroupId groupId, int milestone
/// Removes group with all projects inside.
/// Only available to group owners and administrators.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
public async Task DeleteAsync(GroupId groupId) =>
await _httpFacade.Delete($"groups/{groupId}");
///
/// Deletes a group milestone. Only for user with developer access to the group.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// The ID of the group's milestone.
public async Task DeleteMilestoneAsync(GroupId groupId, int milestoneId) =>
await _httpFacade.Delete($"groups/{groupId}/milestones/{milestoneId}");
@@ -256,14 +259,14 @@ public async Task DeleteMilestoneAsync(GroupId groupId, int milestoneId) =>
/// Syncs the group with its linked LDAP group.
/// Only available to group owners and administrators.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
public async Task SyncLdapAsync(GroupId groupId) =>
await _httpFacade.Post($"groups/{groupId}/ldap_sync");
///
/// Creates LDAP group link.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// Create LDAP group link request.
public async Task CreateLdapLinkAsync(GroupId groupId, CreateLdapGroupLinkRequest request) =>
await _httpFacade.Post($"groups/{groupId}/ldap_group_links", request);
@@ -271,7 +274,7 @@ public async Task CreateLdapLinkAsync(GroupId groupId, CreateLdapGroupLinkReques
///
/// Deletes a LDAP group link.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// The CN of a LDAP group
public async Task DeleteLdapLinkAsync(GroupId groupId, string cn) =>
await _httpFacade.Delete($"groups/{groupId}/ldap_group_links/{cn}");
@@ -280,10 +283,58 @@ public async Task DeleteLdapLinkAsync(GroupId groupId, string cn) =>
///
/// Deletes a LDAP group link for a specific LDAP provider.
///
- /// The ID, path or of the project.
+ /// The ID, path or of the group.
/// Name of a LDAP provider
/// The CN of a LDAP group
public async Task DeleteProviderLdapLinkAsync(GroupId groupId, string provider, string cn) =>
await _httpFacade.Delete($"groups/{groupId}/ldap_group_links/{provider}/{cn}");
+
+
+ ///
+ /// Get all labels for a given group.
+ ///
+ /// The ID, path or of the group.
+ /// Query options
+ public async Task> GetLabelsAsync(GroupId groupId,
+ Action options = null)
+ {
+ var labelOptions = new GroupLabelsQueryOptions();
+ options?.Invoke(labelOptions);
+
+ string url = _queryGroupLabelBuilder.Build($"groups/{groupId}/labels", labelOptions);
+ return await _httpFacade.GetPagedList(url);
+ }
+
+ ///
+ /// Creates new group label.
+ ///
+ /// The ID, path or of the group.
+ /// Create label request.
+ /// Newly created label.
+ public async Task CreateLabelAsync(GroupId groupId, CreateGroupLabelRequest request)
+ {
+ Guard.NotNull(request, nameof(request));
+ return await _httpFacade.Post($"groups/{groupId}/labels", request);
+ }
+
+ ///
+ /// Updates an existing label with new name or new color. At least one parameter is required, to update the label.
+ ///
+ /// The ID, path or of the group.
+ /// Update label request.
+ /// Newly modified label.
+ public async Task UpdateLabelAsync(GroupId groupId, UpdateGroupLabelRequest request)
+ {
+ Guard.NotNull(request, nameof(request));
+ return await _httpFacade.Put($"groups/{groupId}/labels", request);
+ }
+
+ ///
+ /// Deletes group labels.
+ ///
+ /// The ID, path or of the group.
+ /// Name of the label.
+ public async Task DeleteLabelAsync(GroupId groupId, string name) =>
+ await _httpFacade.Delete($"groups/{groupId}/labels?name={name}");
}
}
diff --git a/src/GitLabApiClient/Internal/Queries/GroupLabelsQueryBuilder.cs b/src/GitLabApiClient/Internal/Queries/GroupLabelsQueryBuilder.cs
new file mode 100644
index 00000000..0126eabe
--- /dev/null
+++ b/src/GitLabApiClient/Internal/Queries/GroupLabelsQueryBuilder.cs
@@ -0,0 +1,25 @@
+using GitLabApiClient.Models.Groups.Requests;
+
+namespace GitLabApiClient.Internal.Queries
+{
+ internal class GroupLabelsQueryBuilder : QueryBuilder
+ {
+ #region Overrides of QueryBuilder
+
+ ///
+ protected override void BuildCore(GroupLabelsQueryOptions options)
+ {
+ if (options.WithCounts)
+ {
+ Add("with_counts", true);
+ }
+
+ if (!options.IncludeAncestorGroups)
+ {
+ Add("include_ancestor_groups", false);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/GitLabApiClient/Internal/Queries/IssuesQueryBuilder.cs b/src/GitLabApiClient/Internal/Queries/IssuesQueryBuilder.cs
index 21c05fbe..96748f7f 100644
--- a/src/GitLabApiClient/Internal/Queries/IssuesQueryBuilder.cs
+++ b/src/GitLabApiClient/Internal/Queries/IssuesQueryBuilder.cs
@@ -39,6 +39,21 @@ protected override void BuildCore(IssuesQueryOptions options)
if (!options.Filter.IsNullOrEmpty())
Add("search", options.Filter);
+
+ if (options.IsConfidential)
+ Add("confidential", true);
+
+ if (options.CreatedBefore.HasValue)
+ Add("created_before", options.CreatedBefore.Value);
+
+ if (options.CreatedAfter.HasValue)
+ Add("created_after", options.CreatedAfter.Value);
+
+ if (options.UpdatedBefore.HasValue)
+ Add("updated_before", options.UpdatedBefore.Value);
+
+ if (options.UpdatedAfter.HasValue)
+ Add("updated_after", options.UpdatedAfter.Value);
}
private static string GetStateQueryValue(IssueState state)
diff --git a/src/GitLabApiClient/Internal/Queries/ProjectIssuesQueryBuilder.cs b/src/GitLabApiClient/Internal/Queries/ProjectIssuesQueryBuilder.cs
deleted file mode 100644
index 79957163..00000000
--- a/src/GitLabApiClient/Internal/Queries/ProjectIssuesQueryBuilder.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using GitLabApiClient.Models.Issues.Requests;
-
-namespace GitLabApiClient.Internal.Queries
-{
- internal sealed class ProjectIssuesQueryBuilder : IssuesQueryBuilder
- {
- protected override void BuildCore(IssuesQueryOptions options)
- {
- if (!(options is ProjectIssuesQueryOptions projectIssuesQueryOptions))
- {
- base.BuildCore(options);
- return;
- }
-
- base.BuildCore(options);
-
- if (projectIssuesQueryOptions.CreatedAfter.HasValue)
- Add("created_after", projectIssuesQueryOptions.CreatedAfter.Value);
-
- if (projectIssuesQueryOptions.CreatedBefore.HasValue)
- Add("created_before", projectIssuesQueryOptions.CreatedBefore.Value);
- }
- }
-}
diff --git a/src/GitLabApiClient/IssuesClient.cs b/src/GitLabApiClient/IssuesClient.cs
index 3814b63a..5ac8cf84 100644
--- a/src/GitLabApiClient/IssuesClient.cs
+++ b/src/GitLabApiClient/IssuesClient.cs
@@ -5,6 +5,7 @@
using GitLabApiClient.Internal.Http;
using GitLabApiClient.Internal.Paths;
using GitLabApiClient.Internal.Queries;
+using GitLabApiClient.Models.Groups.Responses;
using GitLabApiClient.Models.Issues.Requests;
using GitLabApiClient.Models.Issues.Responses;
using GitLabApiClient.Models.Notes.Requests;
@@ -23,21 +24,76 @@ public sealed class IssuesClient
{
private readonly GitLabHttpFacade _httpFacade;
private readonly IssuesQueryBuilder _queryBuilder;
- private readonly ProjectIssuesQueryBuilder _projectIssuesQueryBuilder;
private readonly ProjectIssueNotesQueryBuilder _projectIssueNotesQueryBuilder;
internal IssuesClient(
GitLabHttpFacade httpFacade,
IssuesQueryBuilder queryBuilder,
- ProjectIssuesQueryBuilder projectIssuesQueryBuilder,
ProjectIssueNotesQueryBuilder projectIssueNotesQueryBuilder)
{
_httpFacade = httpFacade;
_queryBuilder = queryBuilder;
- _projectIssuesQueryBuilder = projectIssuesQueryBuilder;
_projectIssueNotesQueryBuilder = projectIssueNotesQueryBuilder;
}
+ ///
+ /// Retrieves issues.
+ /// By default retrieves opened issues from all users. The more specific setting win (if both project and group are set, only project issues will be retrieved).
+ ///
+ ///
+ ///
+ /// /* Get all issues */
+ /// var client = new GitLabClient("https://gitlab.com", "PRIVATE-TOKEN");
+ /// var allIssues = await client.Issues.GetAllAsync();
+ ///
+ ///
+ /// /* Get project issues */
+ /// var client = new GitLabClient("https://gitlab.com", "PRIVATE-TOKEN");
+ /// string projectPath = "dev/group/project-1";
+ /// var allIssues = await client.Issues.GetAllAsync(projectId: projectPath);
+ /// // OR
+ /// int projectId = 55;
+ /// var allIssues = await client.Issues.GetAllAsync(projectId: projectId);
+ /// // OR - Group ID is skipped, project ID is more specific
+ /// int projectId = 55;
+ /// int groupId = 181;
+ /// var allIssues = await client.Issues.GetAllAsync(projectId: projectId, groupId: groupId);
+ ///
+ ///
+ /// /* Get group issues */
+ /// var client = new GitLabClient("https://gitlab.com", "PRIVATE-TOKEN");
+ /// string groupPath = "dev/group1/subgroup-1";
+ /// var allIssues = await client.Issues.GetAllAsync(groupId: groupPath);
+ /// // OR
+ /// int groupId = 55;
+ /// var allIssues = await client.Issues.GetAllAsync(groupId: groupId);
+ ///
+ ///
+ /// The ID, path or of the project.
+ /// The ID, path or of the group.
+ /// Issues retrieval options.
+ /// Issues satisfying options.
+ public async Task> GetAllAsync(ProjectId projectId = null, GroupId groupId = null,
+ Action options = null)
+ {
+ var queryOptions = new IssuesQueryOptions();
+ options?.Invoke(queryOptions);
+
+ string path = "issues";
+ if (projectId != null)
+ {
+ path = $"projects/{projectId}/issues";
+ }
+ else if (groupId != null)
+ {
+ path = $"groups/{groupId}/issues";
+ }
+
+ string url = _queryBuilder.Build(path, queryOptions);
+
+ return await _httpFacade.GetPagedList(url);
+ }
+
///
/// Retrieves project issue.
///
@@ -51,14 +107,9 @@ public async Task GetAsync(ProjectId projectId, int issueId) =>
/// The ID, path or of the project.
/// Issues retrieval options.
/// Issues satisfying options.
- public async Task> GetAsync(ProjectId projectId, Action options = null)
- {
- var queryOptions = new ProjectIssuesQueryOptions();
- options?.Invoke(queryOptions);
-
- string url = _projectIssuesQueryBuilder.Build($"projects/{projectId}/issues", queryOptions);
- return await _httpFacade.GetPagedList(url);
- }
+ [Obsolete("Use GetAllAsync instead")]
+ public Task> GetAsync(ProjectId projectId, Action options = null) =>
+ GetAllAsync(projectId: projectId, options: options);
///
/// Retrieves issues from all projects.
@@ -66,14 +117,9 @@ public async Task> GetAsync(ProjectId projectId, Action
/// Issues retrieval options.
/// Issues satisfying options.
- public async Task> GetAsync(Action options = null)
- {
- var queryOptions = new IssuesQueryOptions();
- options?.Invoke(queryOptions);
-
- string url = _queryBuilder.Build("issues", queryOptions);
- return await _httpFacade.GetPagedList(url);
- }
+ [Obsolete("Use GetAllAsync instead")]
+ public Task> GetAsync(Action options = null) =>
+ GetAllAsync(options: options);
///
/// Retrieves project issue note.
diff --git a/src/GitLabApiClient/Models/Groups/Requests/CreateGroupLabelRequest.cs b/src/GitLabApiClient/Models/Groups/Requests/CreateGroupLabelRequest.cs
new file mode 100644
index 00000000..78d6b1aa
--- /dev/null
+++ b/src/GitLabApiClient/Models/Groups/Requests/CreateGroupLabelRequest.cs
@@ -0,0 +1,39 @@
+using GitLabApiClient.Internal.Utilities;
+using Newtonsoft.Json;
+
+namespace GitLabApiClient.Models.Groups.Requests
+{
+ ///
+ /// Used to create labels in a group.
+ ///
+ public sealed class CreateGroupLabelRequest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Name of the label.
+ public CreateGroupLabelRequest(string name)
+ {
+ Guard.NotEmpty(name, nameof(name));
+ Name = name;
+ }
+
+ ///
+ /// The name of the label.
+ ///
+ [JsonProperty("name")]
+ public string Name { get; private set; }
+
+ ///
+ /// The color of the label given in 6-digit hex notation with leading ‘#’ sign (e.g. #FFAABB) or one of the CSS color names.
+ ///
+ [JsonProperty("color")]
+ public string Color { get; set; }
+
+ ///
+ /// The description of the label.
+ ///
+ [JsonProperty("description")]
+ public string Description { get; set; }
+ }
+}
diff --git a/src/GitLabApiClient/Models/Groups/Requests/GroupLabelsQueryOptions.cs b/src/GitLabApiClient/Models/Groups/Requests/GroupLabelsQueryOptions.cs
new file mode 100644
index 00000000..c50503c8
--- /dev/null
+++ b/src/GitLabApiClient/Models/Groups/Requests/GroupLabelsQueryOptions.cs
@@ -0,0 +1,22 @@
+namespace GitLabApiClient.Models.Groups.Requests
+{
+ ///
+ /// Options for group label list
+ ///
+ public sealed class GroupLabelsQueryOptions
+ {
+ internal GroupLabelsQueryOptions()
+ {
+ }
+
+ ///
+ /// Whether or not to include issue and merge request counts. Defaults to `false`. (Introduced in GitLab 12.2)
+ ///
+ public bool WithCounts { get; set; } = false;
+
+ ///
+ /// Include ancestor groups. Defaults to `true`.
+ ///
+ public bool IncludeAncestorGroups { get; set; } = true;
+ }
+}
diff --git a/src/GitLabApiClient/Models/Groups/Requests/UpdateGroupLabelRequest.cs b/src/GitLabApiClient/Models/Groups/Requests/UpdateGroupLabelRequest.cs
new file mode 100644
index 00000000..68b7476d
--- /dev/null
+++ b/src/GitLabApiClient/Models/Groups/Requests/UpdateGroupLabelRequest.cs
@@ -0,0 +1,69 @@
+using GitLabApiClient.Internal.Utilities;
+using Newtonsoft.Json;
+
+namespace GitLabApiClient.Models.Groups.Requests
+{
+ ///
+ /// Updates an existing label with new name or new color. At least one parameter is required, to update the label.
+ ///
+ public sealed class UpdateGroupLabelRequest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ /// Old name of the label.
+ /// The new name of the label.
+ ///
+ public static UpdateGroupLabelRequest FromNewName(string name, string newName)
+ {
+ Guard.NotEmpty(name, nameof(name));
+ Guard.NotEmpty(newName, nameof(newName));
+ return new UpdateGroupLabelRequest
+ {
+ Name = name,
+ NewName = newName
+ };
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Old name of the label.
+ /// The color of the label given in 6-digit hex notation with leading ‘#’ sign (e.g. #FFAABB) or one of the CSS color names.
+ ///
+ public static UpdateGroupLabelRequest FromColor(string name, string color)
+ {
+ Guard.NotEmpty(name, nameof(name));
+ Guard.NotEmpty(color, nameof(color));
+ return new UpdateGroupLabelRequest
+ {
+ Name = name,
+ Color = color
+ };
+ }
+
+ private UpdateGroupLabelRequest() { }
+
+ ///
+ /// The name of the existing label.
+ ///
+ [JsonProperty("name")]
+ public string Name { get; private set; }
+
+ ///
+ /// The new name of the label
+ ///
+ [JsonProperty("new_name")]
+ public string NewName { get; private set; }
+
+ ///
+ /// The color of the label given in 6-digit hex notation with leading ‘#’ sign (e.g. #FFAABB) or one of the CSS color names.
+ ///
+ [JsonProperty("color")]
+ public string Color { get; set; }
+
+ ///
+ /// The description of the label.
+ ///
+ [JsonProperty("description")]
+ public string Description { get; set; }
+ }
+}
diff --git a/src/GitLabApiClient/Models/Groups/Responses/GroupLabel.cs b/src/GitLabApiClient/Models/Groups/Responses/GroupLabel.cs
new file mode 100644
index 00000000..66166a67
--- /dev/null
+++ b/src/GitLabApiClient/Models/Groups/Responses/GroupLabel.cs
@@ -0,0 +1,31 @@
+using Newtonsoft.Json;
+
+namespace GitLabApiClient.Models.Groups.Responses
+{
+ public sealed class GroupLabel
+ {
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("color")]
+ public string Color { get; set; }
+
+ [JsonProperty("description")]
+ public string Description { get; set; }
+
+ [JsonProperty("open_issues_count")]
+ public int OpenIssuesCount { get; set; }
+
+ [JsonProperty("closed_issues_count")]
+ public int ClosedIssuesCount { get; set; }
+
+ [JsonProperty("open_merge_requests_count")]
+ public int OpenMergeRequestsCount { get; set; }
+
+ [JsonProperty("subscribed")]
+ public bool Subscribed { get; set; }
+ }
+}
diff --git a/src/GitLabApiClient/Models/Issues/Requests/IssuesQueryOptions.cs b/src/GitLabApiClient/Models/Issues/Requests/IssuesQueryOptions.cs
index 87c79a7f..7a24c0cf 100644
--- a/src/GitLabApiClient/Models/Issues/Requests/IssuesQueryOptions.cs
+++ b/src/GitLabApiClient/Models/Issues/Requests/IssuesQueryOptions.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using GitLabApiClient.Models.Issues.Responses;
@@ -54,7 +55,7 @@ internal IssuesQueryOptions() { }
public IssuesOrder Order { get; set; }
///
- /// Specifies project sort order. Default is desending.
+ /// Specifies project sort order. Default is descending.
///
public SortOrder SortOrder { get; set; }
@@ -62,5 +63,30 @@ internal IssuesQueryOptions() { }
/// Search issues against their title and description
///
public string Filter { get; set; }
+
+ ///
+ /// Return issues created after the given time (inclusive)
+ ///
+ public DateTime? CreatedAfter { get; set; }
+
+ ///
+ /// Return issues created before the given time (inclusive)
+ ///
+ public DateTime? CreatedBefore { get; set; }
+
+ ///
+ /// Return issues updated on or after the given time
+ ///
+ public DateTime? UpdatedAfter { get; set; }
+
+ ///
+ /// Return issues updated on or before the given time
+ ///
+ public DateTime? UpdatedBefore { get; set; }
+
+ ///
+ /// Filter confidential or public issues.
+ ///
+ public bool IsConfidential { get; set; } = false;
}
}
diff --git a/src/GitLabApiClient/Models/Issues/Requests/ProjectIssuesQueryOptions.cs b/src/GitLabApiClient/Models/Issues/Requests/ProjectIssuesQueryOptions.cs
index 06b1d754..6f5f6bf6 100644
--- a/src/GitLabApiClient/Models/Issues/Requests/ProjectIssuesQueryOptions.cs
+++ b/src/GitLabApiClient/Models/Issues/Requests/ProjectIssuesQueryOptions.cs
@@ -1,5 +1,4 @@
using System;
-using GitLabApiClient.Internal.Utilities;
namespace GitLabApiClient.Models.Issues.Requests
{
@@ -7,6 +6,7 @@ namespace GitLabApiClient.Models.Issues.Requests
///
/// Options for project issues listing
///
+ [Obsolete("Use IssuesQueryOptions instead")]
public sealed class ProjectIssuesQueryOptions : IssuesQueryOptions
{
///
@@ -15,15 +15,5 @@ public sealed class ProjectIssuesQueryOptions : IssuesQueryOptions
internal ProjectIssuesQueryOptions()
{
}
-
- ///
- /// Return issues created after the given time (inclusive)
- ///
- public DateTime? CreatedAfter { get; set; }
-
- ///
- /// Return issues created before the given time (inclusive)
- ///
- public DateTime? CreatedBefore { get; set; }
}
}
diff --git a/test/GitLabApiClient.Test/GroupsClientTest.cs b/test/GitLabApiClient.Test/GroupsClientTest.cs
index f2951e14..140a515e 100644
--- a/test/GitLabApiClient.Test/GroupsClientTest.cs
+++ b/test/GitLabApiClient.Test/GroupsClientTest.cs
@@ -4,8 +4,10 @@
using FluentAssertions;
using GitLabApiClient.Internal.Queries;
using GitLabApiClient.Models.Groups.Requests;
+using GitLabApiClient.Models.Groups.Responses;
using GitLabApiClient.Models.Milestones.Requests;
using GitLabApiClient.Models.Milestones.Responses;
+using GitLabApiClient.Test.Utilities;
using Xunit;
using static GitLabApiClient.Test.Utilities.GitLabApiHelper;
@@ -22,7 +24,8 @@ public class GroupsClientTest
GetFacade(),
new GroupsQueryBuilder(),
new ProjectsGroupQueryBuilder(),
- new MilestonesQueryBuilder());
+ new MilestonesQueryBuilder(),
+ new GroupLabelsQueryBuilder());
[Fact]
public async Task GroupCanBeRetrievedByGroupId()
@@ -149,6 +152,31 @@ public async Task CreatedGroupCanBeUpdated()
updateGroupResponse.RequestAccessEnabled.Should().BeFalse();
}
+ [Fact]
+ public async Task CreatedGroupLabelCanBeUpdated()
+ {
+ //arrange
+ var createdLabel = await _sut.CreateLabelAsync(GitLabApiHelper.TestGroupId, new CreateGroupLabelRequest("Label 1")
+ {
+ Color = "#FFFFFF",
+ Description = "description1"
+ });
+
+ //act
+ var updateRequest = UpdateGroupLabelRequest.FromNewName(createdLabel.Name, "Label 11");
+ updateRequest.Color = "#000000";
+ updateRequest.Description = "description11";
+
+ var updatedLabel = await _sut.UpdateLabelAsync(GitLabApiHelper.TestGroupId, updateRequest);
+ await _sut.DeleteLabelAsync(GitLabApiHelper.TestGroupId, updatedLabel.Name);
+
+ //assert
+ updatedLabel.Should().Match(l =>
+ l.Name == "Label 11" &&
+ l.Color == "#000000" &&
+ l.Description == "description11");
+ }
+
[Fact]
public async Task CreatedGroupMilestoneCanBeUpdated()
{
diff --git a/test/GitLabApiClient.Test/Internal/Queries/GroupLabelsQueryBuilderTest.cs b/test/GitLabApiClient.Test/Internal/Queries/GroupLabelsQueryBuilderTest.cs
new file mode 100644
index 00000000..69175d6d
--- /dev/null
+++ b/test/GitLabApiClient.Test/Internal/Queries/GroupLabelsQueryBuilderTest.cs
@@ -0,0 +1,28 @@
+using FluentAssertions;
+using GitLabApiClient.Internal.Queries;
+using GitLabApiClient.Models.Groups.Requests;
+using Xunit;
+
+namespace GitLabApiClient.Test.Internal.Queries
+{
+ public class GroupLabelsQueryBuilderTest
+ {
+ [Fact]
+ public void NonDefaultQueryBuilt()
+ {
+ var sut = new GroupLabelsQueryBuilder();
+
+ string query = sut.Build(
+ "https://gitlab.com/api/v4/groups/123/labels",
+ new GroupLabelsQueryOptions
+ {
+ WithCounts = true,
+ IncludeAncestorGroups = false
+ });
+
+ query.Should().Be("https://gitlab.com/api/v4/groups/123/labels?" +
+ "with_counts=true&" +
+ "include_ancestor_groups=false");
+ }
+ }
+}
diff --git a/test/GitLabApiClient.Test/Internal/Queries/IssuesQueryBuilderTest.cs b/test/GitLabApiClient.Test/Internal/Queries/IssuesQueryBuilderTest.cs
index 3f21b4ae..a1002d85 100644
--- a/test/GitLabApiClient.Test/Internal/Queries/IssuesQueryBuilderTest.cs
+++ b/test/GitLabApiClient.Test/Internal/Queries/IssuesQueryBuilderTest.cs
@@ -1,3 +1,4 @@
+using System;
using FluentAssertions;
using GitLabApiClient.Internal.Queries;
using GitLabApiClient.Models;
@@ -27,10 +28,15 @@ public void NonDefaultQueryBuilt()
IssueIds = { 3, 4 },
Order = IssuesOrder.UpdatedAt,
SortOrder = SortOrder.Ascending,
- Filter = "filter"
+ Filter = "filter",
+ CreatedAfter = new DateTime(1991, 11, 11, 1, 1, 1),
+ CreatedBefore = new DateTime(1991, 12, 12, 2, 2, 2),
+ UpdatedAfter = new DateTime(1991, 4, 4, 4, 4, 4),
+ UpdatedBefore = new DateTime(1991, 5, 5, 5, 5, 5),
+ IsConfidential = true
});
- query.Should().Be("https://gitlab.com/api/v4/issues?" +
+ query.Should().BeEquivalentTo("https://gitlab.com/api/v4/issues?" +
"state=opened&" +
"labels=label1%2Clabel2&" +
"milestone=milestone1&" +
@@ -40,7 +46,12 @@ public void NonDefaultQueryBuilt()
"iids%5B%5D=3&iids%5B%5D=4&" +
"order_by=updated_at&" +
"sort=asc&" +
- "search=filter");
+ "search=filter&" +
+ "confidential=true&" +
+ "created_before=1991-12-12T02%3a02%3a02.0000000&" +
+ "created_after=1991-11-11T01%3a01%3a01.0000000&" +
+ "updated_before=1991-05-05T05%3a05%3a05.0000000&" +
+ "updated_after=1991-04-04T04%3a04%3a04.0000000");
}
}
}
diff --git a/test/GitLabApiClient.Test/Internal/Queries/ProjectIssuesQueryBuilderTest.cs b/test/GitLabApiClient.Test/Internal/Queries/ProjectIssuesQueryBuilderTest.cs
deleted file mode 100644
index e89e4f79..00000000
--- a/test/GitLabApiClient.Test/Internal/Queries/ProjectIssuesQueryBuilderTest.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using FluentAssertions;
-using GitLabApiClient.Internal.Queries;
-using GitLabApiClient.Models;
-using GitLabApiClient.Models.Issues.Requests;
-using GitLabApiClient.Models.Issues.Responses;
-using Xunit;
-
-namespace GitLabApiClient.Test.Internal.Queries
-{
- public class ProjectIssuesQueryBuilderTest
- {
- [Fact]
- public void NonDefaultQueryBuilt()
- {
- var sut = new ProjectIssuesQueryBuilder();
-
- string query = sut.Build(
- "https://gitlab.com/api/v4/issues",
- new ProjectIssuesQueryOptions()
- {
- State = IssueState.Opened,
- Labels = { "label1", "label2" },
- MilestoneTitle = "milestone1",
- Scope = Scope.All,
- AuthorId = 1,
- AssigneeId = 2,
- IssueIds = { 3, 4 },
- Order = IssuesOrder.UpdatedAt,
- SortOrder = SortOrder.Ascending,
- Filter = "filter",
- CreatedAfter = new DateTime(1991, 11, 11, 1, 1, 1),
- CreatedBefore = new DateTime(1991, 12, 12, 2, 2, 2)
- });
-
- query.Should().Be("https://gitlab.com/api/v4/issues?" +
- "state=opened&" +
- "labels=label1%2Clabel2&" +
- "milestone=milestone1&" +
- "scope=all&" +
- "author_id=1&" +
- "assignee_id=2&" +
- "iids%5B%5D=3&iids%5B%5D=4&" +
- "order_by=updated_at&" +
- "sort=asc&" +
- "search=filter&" +
- "created_after=1991-11-11T01%3A01%3A01.0000000&" +
- "created_before=1991-12-12T02%3A02%3A02.0000000");
- }
- }
-}
diff --git a/test/GitLabApiClient.Test/IssuesClientTest.cs b/test/GitLabApiClient.Test/IssuesClientTest.cs
index 8f23eceb..8f321a69 100644
--- a/test/GitLabApiClient.Test/IssuesClientTest.cs
+++ b/test/GitLabApiClient.Test/IssuesClientTest.cs
@@ -19,7 +19,7 @@ namespace GitLabApiClient.Test
public class IssuesClientTest
{
private readonly IssuesClient _sut = new IssuesClient(
- GetFacade(), new IssuesQueryBuilder(), new ProjectIssuesQueryBuilder(), new ProjectIssueNotesQueryBuilder());
+ GetFacade(), new IssuesQueryBuilder(), new ProjectIssueNotesQueryBuilder());
[Fact]
public async Task CreatedIssueCanBeUpdated()
@@ -80,7 +80,7 @@ public async Task CreatedIssueCanBeListedFromProject()
await _sut.CreateAsync(TestProjectTextId, new CreateIssueRequest(title));
//act
- var listedIssues = await _sut.GetAsync(TestProjectTextId, o => o.Filter = title);
+ var listedIssues = await _sut.GetAllAsync(projectId: TestProjectTextId, options: o => o.Filter = title);
//assert
listedIssues.Single().Should().Match(i =>
@@ -105,8 +105,8 @@ public async Task CreatedIssueCanBeRetrieved()
//act
var issueById = await _sut.GetAsync(TestProjectId, issue.Iid);
- var issueByProjectId = (await _sut.GetAsync(o => o.IssueIds = new[] { issue.Iid })).FirstOrDefault(i => i.Title == title);
- var ownedIssue = (await _sut.GetAsync(o => o.Scope = Scope.CreatedByMe)).FirstOrDefault(i => i.Title == title);
+ var issueByProjectId = (await _sut.GetAllAsync(options: o => o.IssueIds = new[] { issue.Iid })).FirstOrDefault(i => i.Title == title);
+ var ownedIssue = (await _sut.GetAllAsync(options: o => o.Scope = Scope.CreatedByMe)).FirstOrDefault(i => i.Title == title);
//assert
issue.Should().Match(i =>