Skip to content

Commit 23cc79a

Browse files
Copilotpremun
andauthored
Add BAR build links to dependency PR descriptions (#5227)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: premun <7013027+premun@users.noreply.github.com> Co-authored-by: Přemek Vysoký <premek.vysoky@gmail.com> Co-authored-by: Premek Vysoky <premek.vysoky@microsoft.com>
1 parent d570883 commit 23cc79a

3 files changed

Lines changed: 238 additions & 33 deletions

File tree

src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestBuilder.cs

Lines changed: 116 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ string GenerateCodeFlowPRTitle(
5555
/// <summary>
5656
/// Generate the description for a code flow PR.
5757
/// </summary>
58-
string GenerateCodeFlowPRDescription(
58+
Task<string> GenerateCodeFlowPRDescription(
5959
SubscriptionUpdateWorkItem update,
6060
BuildDTO build,
6161
string? previousSourceCommit,
@@ -181,7 +181,7 @@ public async Task<string> CalculatePRDescriptionAndCommitUpdatesAsync(
181181
itemsToUpdate,
182182
message.ToString());
183183

184-
AppendBuildDescription(description, ref startingReferenceId, update, deps, committedFiles, build);
184+
startingReferenceId = await AppendBuildDescriptionAsync(description, startingReferenceId, update, deps, committedFiles, build);
185185
}
186186

187187
// If the coherency update wasn't combined, then
@@ -224,7 +224,7 @@ public string GenerateCodeFlowPRTitle(
224224
return GeneratePRTitle($"[{targetBranch}] Source code updates from", repoNames);
225225
}
226226

227-
public string GenerateCodeFlowPRDescription(
227+
public async Task<string> GenerateCodeFlowPRDescription(
228228
SubscriptionUpdateWorkItem update,
229229
BuildDTO build,
230230
string? previousSourceCommit,
@@ -233,7 +233,7 @@ public string GenerateCodeFlowPRDescription(
233233
string? currentDescription,
234234
bool isForwardFlow)
235235
{
236-
string description = GenerateCodeFlowPRDescriptionInternal(
236+
string description = await GenerateCodeFlowPRDescriptionInternal(
237237
update,
238238
build,
239239
previousSourceCommit,
@@ -246,7 +246,7 @@ public string GenerateCodeFlowPRDescription(
246246
return AddOrUpdateFooterInDescription(description, upstreamRepoDiffs);
247247
}
248248

249-
private static string GenerateCodeFlowPRDescriptionInternal(
249+
private async Task<string> GenerateCodeFlowPRDescriptionInternal(
250250
SubscriptionUpdateWorkItem update,
251251
BuildDTO build,
252252
string? previousSourceCommit,
@@ -263,7 +263,7 @@ private static string GenerateCodeFlowPRDescriptionInternal(
263263
> This is a codeflow update. It may contain both source code changes from [{(isForwardFlow ? "the source repo" : "the VMR")}]({update.SourceRepo}) as well as dependency updates. Learn more [here]({CodeFlowPrFaqUri}).
264264
265265
This pull request brings the following source code changes
266-
{GenerateCodeFlowDescriptionForSubscription(update.SubscriptionId, previousSourceCommit, build, update.SourceRepo, dependencyUpdates)}
266+
{await GenerateCodeFlowDescriptionForSubscription(update.SubscriptionId, previousSourceCommit, build, update.SourceRepo, dependencyUpdates)}
267267
""";
268268
}
269269
else
@@ -279,15 +279,16 @@ This pull request brings the following source code changes
279279
currentDescription.Length :
280280
endIndex + GetEndMarker(update.SubscriptionId).Length;
281281

282-
return string.Concat(
283-
currentDescription.AsSpan(0, startCutoff),
284-
GenerateCodeFlowDescriptionForSubscription(
285-
update.SubscriptionId,
286-
previousSourceCommit,
287-
build,
288-
update.SourceRepo,
289-
dependencyUpdates),
290-
currentDescription.AsSpan(endCutoff, currentDescription.Length - endCutoff));
282+
var beforeSpan = currentDescription.Substring(0, startCutoff);
283+
var afterSpan = currentDescription.Substring(endCutoff, currentDescription.Length - endCutoff);
284+
var generatedDescription = await GenerateCodeFlowDescriptionForSubscription(
285+
update.SubscriptionId,
286+
previousSourceCommit,
287+
build,
288+
update.SourceRepo,
289+
dependencyUpdates);
290+
291+
return string.Concat(beforeSpan, generatedDescription, afterSpan);
291292
}
292293
}
293294

@@ -336,15 +337,14 @@ private static string GenerateUpstreamRepoDiffs(IReadOnlyCollection<UpstreamRepo
336337
return sb.ToString();
337338
}
338339

339-
private static string GenerateCodeFlowDescriptionForSubscription(
340+
private async Task<string> GenerateCodeFlowDescriptionForSubscription(
340341
Guid subscriptionId,
341342
string? previousSourceCommit,
342343
BuildDTO build,
343344
string repoUri,
344345
List<DependencyUpdateSummary> dependencyUpdates)
345346
{
346347
string sourceDiffText = CreateSourceDiffLink(build, previousSourceCommit);
347-
348348
string dependencyUpdateBlock = CreateDependencyUpdateBlock(dependencyUpdates, repoUri);
349349
return
350350
$"""
@@ -353,7 +353,7 @@ private static string GenerateCodeFlowDescriptionForSubscription(
353353
354354
## From {build.GetRepository()}
355355
- **Subscription**: {GetSubscriptionLink(subscriptionId)}
356-
- **Build**: [{build.AzureDevOpsBuildNumber}]({build.GetBuildLink()})
356+
- **Build**: {await GetBuildLinkAsync(build, subscriptionId)}
357357
- **Date Produced**: {build.DateProduced.ToUniversalTime():MMMM d, yyyy h:mm:ss tt UTC}
358358
- **Commit**: [{build.Commit}]({GitRepoUrlUtils.GetCommitUri(build.GetRepository(), build.Commit)})
359359
- **Commit Diff**: {sourceDiffText}
@@ -557,9 +557,9 @@ private static string CompressRepeatedLinksInDescription(string description)
557557
/// Because PRs tend to be live for short periods of time, we can put more information
558558
/// in the description than the commit message without worrying that links will go stale.
559559
/// </remarks>
560-
private void AppendBuildDescription(
560+
private async Task<int> AppendBuildDescriptionAsync(
561561
StringBuilder description,
562-
ref int startingReferenceId,
562+
int startingReferenceId,
563563
SubscriptionUpdateWorkItem update,
564564
List<DependencyUpdate> deps,
565565
List<GitFile>? committedFiles,
@@ -577,7 +577,7 @@ private void AppendBuildDescription(
577577
.AppendLine(sectionStartMarker)
578578
.AppendLine($"## From {sourceRepository}")
579579
.AppendLine($"- **Subscription**: {GetSubscriptionLink(updateSubscriptionId)}")
580-
.AppendLine($"- **Build**: [{build.AzureDevOpsBuildNumber}]({build.GetBuildLink()})")
580+
.AppendLine($"- **Build**: {await GetBuildLinkAsync(build, update.SubscriptionId)}")
581581
.AppendLine($"- **Date Produced**: {build.DateProduced.ToUniversalTime():MMMM d, yyyy h:mm:ss tt UTC}")
582582
// This is duplicated from the files changed, but is easier to read here.
583583
.AppendLine($"- **Commit**: [{build.Commit}]({GitRepoUrlUtils.GetCommitUri(build.GetRepository(), build.Commit)})");
@@ -658,6 +658,7 @@ private void AppendBuildDescription(
658658
description.AppendLine();
659659

660660
startingReferenceId += changesLinks.Count;
661+
return startingReferenceId;
661662
}
662663

663664
/// <summary>
@@ -844,4 +845,98 @@ private static string GetEndMarker(Guid subscriptionId)
844845

845846
private static string GetSubscriptionLink(Guid subscriptionId)
846847
=> $"[{subscriptionId}](https://maestro.dot.net/subscriptions?search={subscriptionId})";
848+
849+
/// <summary>
850+
/// Generates a build link that to the Azure DevOps build and tries to add a BAR build link.
851+
/// </summary>
852+
/// <param name="build">The build object containing build information</param>
853+
/// <param name="subscriptionId">The subscription ID to get channel information</param>
854+
/// <returns>Enhanced build link string with BAR build details</returns>
855+
private async Task<string> GetBuildLinkAsync(BuildDTO build, Guid subscriptionId)
856+
{
857+
var originalBuildLink = $"[{build.AzureDevOpsBuildNumber}]({build.GetBuildLink()})";
858+
859+
try
860+
{
861+
int? channelId;
862+
if (build.Channels.Count == 1)
863+
{
864+
channelId = build.Channels[0].Id;
865+
}
866+
else
867+
{
868+
869+
// Get the subscription to retrieve the channel ID
870+
var subscription = await _context.Subscriptions
871+
.Where(s => s.Id == subscriptionId)
872+
.Select(s => new { s.ChannelId })
873+
.FirstOrDefaultAsync();
874+
875+
if (subscription == null)
876+
{
877+
// If the subscription is not found, return the original link
878+
return originalBuildLink;
879+
}
880+
881+
channelId = subscription.ChannelId;
882+
}
883+
884+
// Generate repository slug for BarViz URL
885+
var repoSlug = ConvertRepoUrlToSlug(build.GetRepository());
886+
if (string.IsNullOrEmpty(repoSlug))
887+
{
888+
return originalBuildLink;
889+
}
890+
891+
// Create the BarViz link
892+
var barBuildLink = $"https://maestro.dot.net/channel/{channelId}/{repoSlug}/build/{build.Id}";
893+
return $"{originalBuildLink} ([{build.Id}]({barBuildLink}))";
894+
}
895+
catch (Exception ex)
896+
{
897+
_logger.LogWarning(ex, "Failed to generate enhanced build link for build {BuildId} and subscription {SubscriptionId}", build.Id, subscriptionId);
898+
return originalBuildLink;
899+
}
900+
}
901+
902+
/// <summary>
903+
/// Converts a repository URL to a slug format used by BarViz.
904+
/// </summary>
905+
/// <param name="repoUrl">The repository URL</param>
906+
/// <returns>Repository slug in format github:org:repo or azdo:org:project:repo</returns>
907+
private static string? ConvertRepoUrlToSlug(string? repoUrl)
908+
{
909+
if (repoUrl == null)
910+
{
911+
return null;
912+
}
913+
914+
var repoType = GitRepoUrlUtils.ParseTypeFromUri(repoUrl);
915+
916+
switch (repoType)
917+
{
918+
case GitRepoType.GitHub:
919+
var (repoName, org) = GitRepoUrlUtils.GetRepoNameAndOwner(repoUrl);
920+
return $"github:{org}:{repoName}";
921+
922+
case GitRepoType.AzureDevOps:
923+
// For Azure DevOps, we need to extract the project name from the URL
924+
// Format: https://dev.azure.com/{org}/{project}/_git/{repo}
925+
const string azureDevOpsPrefix = "https://dev.azure.com/";
926+
if (repoUrl.StartsWith(azureDevOpsPrefix))
927+
{
928+
string[] urlParts = repoUrl.Substring(azureDevOpsPrefix.Length).Split('/');
929+
if (urlParts.Length >= 4 && urlParts[2] == "_git")
930+
{
931+
string orgName = urlParts[0];
932+
string projectName = urlParts[1];
933+
string repoNamePart = urlParts[3].Split('?')[0]; // Remove query parameters
934+
return $"azdo:{orgName}:{projectName}:{repoNamePart}";
935+
}
936+
}
937+
break;
938+
}
939+
940+
return null;
941+
}
847942
}

src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,7 +1216,7 @@ private async Task UpdateCodeFlowPullRequestAsync(
12161216
subscription.TargetBranch,
12171217
pullRequest.ContainedSubscriptions.Select(s => s.SourceRepo).ToList());
12181218

1219-
var description = _pullRequestBuilder.GenerateCodeFlowPRDescription(
1219+
var description = await _pullRequestBuilder.GenerateCodeFlowPRDescription(
12201220
update,
12211221
build,
12221222
previousSourceSha,
@@ -1274,7 +1274,7 @@ private async Task<InProgressPullRequest> CreateCodeFlowPullRequestAsync(
12741274
try
12751275
{
12761276
var title = _pullRequestBuilder.GenerateCodeFlowPRTitle(subscription.TargetBranch, [update.SourceRepo]);
1277-
var description = _pullRequestBuilder.GenerateCodeFlowPRDescription(
1277+
var description = await _pullRequestBuilder.GenerateCodeFlowPRDescription(
12781278
update,
12791279
build,
12801280
previousSourceSha,

0 commit comments

Comments
 (0)