diff --git a/.editorconfig b/.editorconfig index af10f470..dc8e66ff 100644 --- a/.editorconfig +++ b/.editorconfig @@ -73,10 +73,10 @@ csharp_style_var_for_built_in_types = true : suggestion csharp_style_var_when_type_is_apparent = true : warning # Expression-Bodied members -csharp_style_expression_bodied_accessors = true : suggestion -csharp_style_expression_bodied_indexers = true : suggestion -csharp_style_expression_bodied_operators = true : suggestion -csharp_style_expression_bodied_properties = true : suggestion +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion # Explicitly disabled due to difference in coding style between source and tests #csharp_style_expression_bodied_constructors = true : warning #csharp_style_expression_bodied_methods = true : warning @@ -101,7 +101,7 @@ csharp_style_conditional_delegate_call = true : warning csharp_style_throw_expression = true : warning # Code block preferences -csharp_prefer_braces = when_multiline : suggestion +csharp_prefer_braces = when_multiline:suggestion ## Formatting conventions # Dotnet formatting settings: @@ -141,6 +141,16 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false # Wrapping options csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +dotnet_diagnostic.SA1507.severity = error ## Naming conventions [*.{cs,vb}] @@ -159,7 +169,7 @@ dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case # Constants are PascalCase dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants -dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style +dotnet_naming_rule.constants_should_be_pascal_case.style = non_private_static_field_style dotnet_naming_symbols.constants.applicable_kinds = field, local dotnet_naming_symbols.constants.required_modifiers = const @@ -198,7 +208,7 @@ dotnet_naming_style.camel_case_style.capitalization = camel_case # Local functions are PascalCase dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions -dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style +dotnet_naming_rule.local_functions_should_be_pascal_case.style = non_private_static_field_style dotnet_naming_symbols.local_functions.applicable_kinds = local_function dotnet_naming_style.local_function_style.capitalization = pascal_case @@ -216,7 +226,7 @@ dotnet_naming_symbols.type_parameter_symbol.applicable_accessibilities = * # By default, name items with PascalCase dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members -dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.members_should_be_pascal_case.style = non_private_static_field_style dotnet_naming_symbols.all_members.applicable_kinds = * @@ -373,3 +383,6 @@ dotnet_diagnostic.SA1636.severity = none # SA1649: File name should match first type name dotnet_diagnostic.SA1649.severity = none +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +end_of_line = crlf diff --git a/NGitLab.Mock/Clients/IssueClient.cs b/NGitLab.Mock/Clients/IssueClient.cs index d38712b2..bc1a342b 100644 --- a/NGitLab.Mock/Clients/IssueClient.cs +++ b/NGitLab.Mock/Clients/IssueClient.cs @@ -302,6 +302,16 @@ public Models.Issue Get(int projectId, int issueId) return GetById(issueId); } + public GitLabCollectionResponse LinkedToAsync(int projectId, int issueId) + { + throw new NotImplementedException(); + } + + public bool CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId) + { + throw new NotImplementedException(); + } + public Models.Issue GetById(int issueId) { using (Context.BeginOperationScope()) diff --git a/NGitLab.Tests/IssueTests.cs b/NGitLab.Tests/IssueTests.cs index b82e1113..cb975f6d 100644 --- a/NGitLab.Tests/IssueTests.cs +++ b/NGitLab.Tests/IssueTests.cs @@ -293,5 +293,23 @@ public async Task Test_get_new_and_updated_issue_with_duedate() Assert.AreEqual(updatedDueDate, updatedIssue.DueDate); } + + [Test] + [NGitLabRetry] + public async Task Test_get_linked_issue() + { + using var context = await GitLabTestContext.CreateAsync(); + var project = context.CreateProject(); + var issuesClient = context.Client.Issues; + + var issue1 = await issuesClient.CreateAsync(new IssueCreate { ProjectId = project.Id, Title = "title1" }); + var issue2 = await issuesClient.CreateAsync(new IssueCreate { ProjectId = project.Id, Title = "title2", Description = "related to #1" }); + var linked = issuesClient.CreateLinkBetweenIssues(project.Id, issue1.IssueId, project.Id, issue2.IssueId); + Assert.IsTrue(linked, "Expected true for create Link between issues"); + var issues = issuesClient.LinkedToAsync(project.Id, issue1.IssueId).ToList(); + + // for now, no API to link issues so not links exist but API should not throw + Assert.AreEqual(1, issues.Count, "Expected 1. Got {0}", issues.Count); + } } } diff --git a/NGitLab/IIssueClient.cs b/NGitLab/IIssueClient.cs index 12e23ce9..a5145f6b 100644 --- a/NGitLab/IIssueClient.cs +++ b/NGitLab/IIssueClient.cs @@ -134,6 +134,23 @@ public interface IIssueClient /// The list of MR that are related this issue. IEnumerable RelatedTo(int projectId, int issueIid); + /// + /// Get all Issues that are linked to a particular issue of particular project. + /// + /// The project id. + /// The id of the issue in the project's scope. + /// The list of Issues linked to this issue. + GitLabCollectionResponse LinkedToAsync(int projectId, int issueId); + + /// + /// Create links between Issues. + /// + /// The project id. + /// The id of the issue in the project's scope. + /// The target project id. + /// The target id of the issue to link to. + bool CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId); + GitLabCollectionResponse RelatedToAsync(int projectId, int issueIid); /// diff --git a/NGitLab/Impl/IssueClient.cs b/NGitLab/Impl/IssueClient.cs index 4a1bb103..7835988e 100644 --- a/NGitLab/Impl/IssueClient.cs +++ b/NGitLab/Impl/IssueClient.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,8 @@ public class IssueClient : IIssueClient { private const string IssuesUrl = "/issues"; private const string IssueByIdUrl = "/issues/{0}"; + private const string LinkedIssuesByIdUrl = "/projects/{0}/issues/{1}/links"; + private const string CreateLinkBetweenIssuesUrl = "/projects/{0}/issues/{1}/links?target_project_id={2}&target_issue_iid={3}"; private const string GroupIssuesUrl = "/groups/{0}/issues"; private const string ProjectIssuesUrl = "/projects/{0}/issues"; private const string SingleIssueUrl = "/projects/{0}/issues/{1}"; @@ -145,6 +148,25 @@ public IEnumerable RelatedTo(int projectId, int issueIid) return _api.Get().GetAll(string.Format(CultureInfo.InvariantCulture, RelatedToUrl, projectId, issueIid)); } + public GitLabCollectionResponse LinkedToAsync(int projectId, int issueId) + { + return _api.Get().GetAllAsync(string.Format(CultureInfo.InvariantCulture, LinkedIssuesByIdUrl, projectId, issueId)); + } + + public bool CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, + int targetIssueId) + { + try + { + _api.Post().Execute(string.Format(CultureInfo.InvariantCulture, CreateLinkBetweenIssuesUrl, sourceProjectId, sourceIssueId, targetProjectId, targetIssueId)); + return true; + } + catch (Exception) + { + return false; + } + } + public GitLabCollectionResponse RelatedToAsync(int projectId, int issueIid) { return _api.Get().GetAllAsync(string.Format(CultureInfo.InvariantCulture, RelatedToUrl, projectId, issueIid)); diff --git a/NGitLab/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI.Unshipped.txt index b6740306..82e0304d 100644 --- a/NGitLab/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI.Unshipped.txt @@ -249,6 +249,7 @@ NGitLab.IIssueClient.ClosedBy(int projectId, int issueIid) -> System.Collections NGitLab.IIssueClient.ClosedByAsync(int projectId, int issueIid) -> NGitLab.GitLabCollectionResponse NGitLab.IIssueClient.Create(NGitLab.Models.IssueCreate issueCreate) -> NGitLab.Models.Issue NGitLab.IIssueClient.CreateAsync(NGitLab.Models.IssueCreate issueCreate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +NGitLab.IIssueClient.CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId) -> bool NGitLab.IIssueClient.Edit(NGitLab.Models.IssueEdit issueEdit) -> NGitLab.Models.Issue NGitLab.IIssueClient.EditAsync(NGitLab.Models.IssueEdit issueEdit, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task NGitLab.IIssueClient.ForGroupsAsync(int groupId) -> NGitLab.GitLabCollectionResponse @@ -263,6 +264,7 @@ NGitLab.IIssueClient.GetAsync(int projectId, NGitLab.Models.IssueQuery query) -> NGitLab.IIssueClient.GetAsync(NGitLab.Models.IssueQuery query) -> NGitLab.GitLabCollectionResponse NGitLab.IIssueClient.GetById(int issueId) -> NGitLab.Models.Issue NGitLab.IIssueClient.GetByIdAsync(int issueId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +NGitLab.IIssueClient.LinkedToAsync(int projectId, int issueId) -> NGitLab.GitLabCollectionResponse NGitLab.IIssueClient.Owned.get -> System.Collections.Generic.IEnumerable NGitLab.IIssueClient.RelatedTo(int projectId, int issueIid) -> System.Collections.Generic.IEnumerable NGitLab.IIssueClient.RelatedToAsync(int projectId, int issueIid) -> NGitLab.GitLabCollectionResponse @@ -498,6 +500,7 @@ NGitLab.Impl.IssueClient.ClosedBy(int projectId, int issueIid) -> System.Collect NGitLab.Impl.IssueClient.ClosedByAsync(int projectId, int issueIid) -> NGitLab.GitLabCollectionResponse NGitLab.Impl.IssueClient.Create(NGitLab.Models.IssueCreate issueCreate) -> NGitLab.Models.Issue NGitLab.Impl.IssueClient.CreateAsync(NGitLab.Models.IssueCreate issueCreate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +NGitLab.Impl.IssueClient.CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId) -> bool NGitLab.Impl.IssueClient.Edit(NGitLab.Models.IssueEdit issueEdit) -> NGitLab.Models.Issue NGitLab.Impl.IssueClient.EditAsync(NGitLab.Models.IssueEdit issueEdit, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task NGitLab.Impl.IssueClient.ForGroupsAsync(int groupId) -> NGitLab.GitLabCollectionResponse @@ -513,6 +516,7 @@ NGitLab.Impl.IssueClient.GetAsync(NGitLab.Models.IssueQuery query) -> NGitLab.Gi NGitLab.Impl.IssueClient.GetById(int issueId) -> NGitLab.Models.Issue NGitLab.Impl.IssueClient.GetByIdAsync(int issueId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task NGitLab.Impl.IssueClient.IssueClient(NGitLab.Impl.API api) -> void +NGitLab.Impl.IssueClient.LinkedToAsync(int projectId, int issueId) -> NGitLab.GitLabCollectionResponse NGitLab.Impl.IssueClient.Owned.get -> System.Collections.Generic.IEnumerable NGitLab.Impl.IssueClient.RelatedTo(int projectId, int issueIid) -> System.Collections.Generic.IEnumerable NGitLab.Impl.IssueClient.RelatedToAsync(int projectId, int issueIid) -> NGitLab.GitLabCollectionResponse