From ab98ceb9632411deb53291f481e4e817ef157db7 Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Fri, 23 Aug 2024 23:08:00 +0200 Subject: [PATCH 1/4] Add WorkItem support in AzureDevOps. (#13) --- .../Config/AzureDevOpsConfig.cs | 2 + .../AzureDevOps.cs | 97 ++++++++++++------- .../Dto/PullRequestResponse.cs | 2 +- DependencyUpdated/config.json | 3 +- 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/DependencyUpdated.Core/Config/AzureDevOpsConfig.cs b/DependencyUpdated.Core/Config/AzureDevOpsConfig.cs index 940a980..923da54 100644 --- a/DependencyUpdated.Core/Config/AzureDevOpsConfig.cs +++ b/DependencyUpdated.Core/Config/AzureDevOpsConfig.cs @@ -15,6 +15,8 @@ public class AzureDevOpsConfig : IValidatableObject public string? Project { get; set; } public string? Repository { get; set; } + + public int? WorkItemId { get; set; } public string TargetBranchName { get; set; } = "dev"; diff --git a/DependencyUpdated.Repositories.AzureDevOps/AzureDevOps.cs b/DependencyUpdated.Repositories.AzureDevOps/AzureDevOps.cs index 140125a..0544502 100644 --- a/DependencyUpdated.Repositories.AzureDevOps/AzureDevOps.cs +++ b/DependencyUpdated.Repositories.AzureDevOps/AzureDevOps.cs @@ -92,48 +92,73 @@ public async Task SubmitPullRequest(IReadOnlyCollection updates, s $"https://dev.azure.com/{configValue.Organization}/{configValue.Project}/_apis/git/repositories/{configValue.Repository}/pullrequests?api-version=6.0"; logger.Information("Creating new PR"); - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", - Convert.ToBase64String(Encoding.UTF8.GetBytes($":{configValue.PAT}"))); - var pr = new PullRequest(sourceBranch, targetBranch, prTitile, prDescription); + using var client = new HttpClient(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", + Convert.ToBase64String(Encoding.UTF8.GetBytes($":{configValue.PAT}"))); + var pr = new PullRequest(sourceBranch, targetBranch, prTitile, prDescription); - var jsonString = JsonSerializer.Serialize(pr); - var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); + var jsonString = JsonSerializer.Serialize(pr); + var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); - var result = await client.PostAsync(baseUrl, content); - result.EnsureSuccessStatusCode(); + var result = await client.PostAsync(baseUrl, content); + result.EnsureSuccessStatusCode(); - if (result.StatusCode == HttpStatusCode.NonAuthoritativeInformation) - { - throw new Exception("Invalid PAT token provided"); - } + if (result.StatusCode == HttpStatusCode.NonAuthoritativeInformation) + { + throw new Exception("Invalid PAT token provided"); + } - var response = await result.Content.ReadAsStringAsync(); - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - }; - var responseObject = JsonSerializer.Deserialize(response, options); - if (responseObject is null) - { - throw new Exception("Missing response from API"); - } + var response = await result.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + var responseObject = JsonSerializer.Deserialize(response, options); + if (responseObject is null) + { + throw new Exception("Missing response from API"); + } - logger.Information("New PR created {Id}", responseObject.PullRequestId); - if (configValue.AutoComplete) + logger.Information("New PR created {Id}", responseObject.PullRequestId); + if (configValue.AutoComplete) + { + logger.Information("Setting autocomplete for PR {Id}", responseObject.PullRequestId); + baseUrl = + $"https://dev.azure.com/{configValue.Organization}/{configValue.Project}/_apis/git/repositories/{configValue.Repository}/pullrequests/{responseObject.PullRequestId}?api-version=6.0"; + var autoComplete = new PullRequestUpdate(responseObject.CreatedBy, + new GitPullRequestCompletionOptions(true, false, GitPullRequestMergeStrategy.Squash)); + jsonString = JsonSerializer.Serialize(autoComplete); + content = new StringContent(jsonString, Encoding.UTF8, "application/json"); + result = await client.PatchAsync(baseUrl, content); + result.EnsureSuccessStatusCode(); + } + + if (configValue.WorkItemId.HasValue) + { + logger.Information("Setting work item {ConfigValueWorkItemId} relation to {PullRequestId}", + configValue.WorkItemId.Value, responseObject.PullRequestId); + var workItemUpdateUrl = new Uri( + $"https://dev.azure.com/{configValue.Organization}/{configValue.Project}/_apis/wit/workitems/{configValue.WorkItemId.Value}?api-version=6.0"); + var patchValue = new[] { - logger.Information("Setting autocomplete for PR {Id}", responseObject.PullRequestId); - baseUrl = - $"https://dev.azure.com/{configValue.Organization}/{configValue.Project}/_apis/git/repositories/{configValue.Repository}/pullrequests/{responseObject.PullRequestId}?api-version=6.0"; - var autoComplete = new PullRequestUpdate(responseObject.CreatedBy, - new GitPullRequestCompletionOptions(true, false, GitPullRequestMergeStrategy.Squash)); - jsonString = JsonSerializer.Serialize(autoComplete); - content = new StringContent(jsonString, Encoding.UTF8, "application/json"); - result = await client.PatchAsync(baseUrl, content); - result.EnsureSuccessStatusCode(); - } + new + { + op = "add", + path = "/relations/-", + value = new + { + rel = "ArtifactLink", + url = responseObject.ArtifactId, + attributes = new { name = "Pull Request" } + } + } + }; + + jsonString = JsonSerializer.Serialize(patchValue); + content = new StringContent(jsonString, Encoding.UTF8, "application/json-patch+json"); + result = await client.PatchAsync(workItemUpdateUrl, content); + result.EnsureSuccessStatusCode(); } } diff --git a/DependencyUpdated.Repositories.AzureDevOps/Dto/PullRequestResponse.cs b/DependencyUpdated.Repositories.AzureDevOps/Dto/PullRequestResponse.cs index fad6c58..f99eb51 100644 --- a/DependencyUpdated.Repositories.AzureDevOps/Dto/PullRequestResponse.cs +++ b/DependencyUpdated.Repositories.AzureDevOps/Dto/PullRequestResponse.cs @@ -1,3 +1,3 @@ namespace DependencyUpdated.Repositories.AzureDevOps.Dto; -public record PullRequestResponse(int PullRequestId, User CreatedBy); \ No newline at end of file +public record PullRequestResponse(int PullRequestId, User CreatedBy, string Url, string ArtifactId); \ No newline at end of file diff --git a/DependencyUpdated/config.json b/DependencyUpdated/config.json index e13882a..741f788 100644 --- a/DependencyUpdated/config.json +++ b/DependencyUpdated/config.json @@ -7,7 +7,8 @@ "PAT": "", "Organization": "", "Project": "", - "Repository": "" + "Repository": "", + "WorkItemId": null }, "PullRequestType": "AzureDevOps", "Projects": [ From 95c570161600a537507041e94cf4858501097fff Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Fri, 23 Aug 2024 23:11:31 +0200 Subject: [PATCH 2/4] Create dotnet.yml --- .github/workflows/dotnet.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/dotnet.yml diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..1f6bae7 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,27 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal From f98b098e137fb712b4836b394f98005904ed52eb Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Fri, 23 Aug 2024 23:14:03 +0200 Subject: [PATCH 3/4] Update dotnet.yml --- .github/workflows/dotnet.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 1f6bae7..922a174 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,11 +1,9 @@ # This workflow will build a .NET project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net -name: .NET +name: CI on: - push: - branches: [ "main" ] pull_request: branches: [ "main" ] From f4be2be5afa89fd6c940fe7b56bdaa17eae46004 Mon Sep 17 00:00:00 2001 From: Marcin Torba Date: Fri, 23 Aug 2024 23:14:46 +0200 Subject: [PATCH 4/4] Rename dotnet.yml to ci.yml --- .github/workflows/{dotnet.yml => ci.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{dotnet.yml => ci.yml} (100%) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/workflows/dotnet.yml rename to .github/workflows/ci.yml