Add Pr URL in subscription outcome table#6395
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a Pull Request URL field to recorded subscription outcomes, exposes it via the PCS API/client model, and displays it as a dedicated “Pull Request” column in the BarViz subscription trigger history table (rather than embedding the URL in the outcome message text).
Changes:
- Persist
PrUrlonSubscriptionOutcome(EF model + migration) and populate it when recording outcomes by reading the tracked PR state from Redis. - Extend API and generated client
SubscriptionTriggerOutcomemodels to includeprUrl. - Update BarViz UI to show a Pull Request link column; normalize several outcome messages to remove inline URLs.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs | Removes unused subscription lookup during update processing. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionTriggerProcessor.cs | Improves failure message when no matching subscription/build is found. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/SubscriptionUpdateOutcomeRecorder.cs | Adds Redis lookup to capture PR URL and store it on outcomes. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdaters/PullRequestUpdater.cs | Removes PR URL from certain outcome messages now that it’s stored separately. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdaters/DependencyPullRequestUpdater.cs | Updates outcome messages to be URL-free / more generic. |
| src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdaters/CodeFlowPullRequestUpdater.cs | Updates outcome messages to be URL-free / more generic. |
| src/ProductConstructionService/ProductConstructionService.BarViz/Pages/SubscriptionTriggerHistory.razor | Adds “Pull Request” column linking to PrUrl. |
| src/ProductConstructionService/ProductConstructionService.Api/Api/v2020_02_20/Models/SubscriptionTriggerOutcome.cs | Adds PrUrl to API response model. |
| src/ProductConstructionService/Microsoft.DotNet.ProductConstructionService.Client/Generated/Models/SubscriptionTriggerOutcome.cs | Regenerates client model to include prUrl. |
| src/Maestro/Maestro.Data/Models/SubscriptionOutcome.cs | Adds PrUrl to EF entity. |
| src/Maestro/Maestro.Data/Migrations/BuildAssetRegistryContextModelSnapshot.cs | Updates snapshot to include PrUrl column. |
| src/Maestro/Maestro.Data/Migrations/20260610160212_AddSubscriptionOutcomeRepoAndPrUrls.cs | Adds migration introducing SubscriptionOutcomes.PrUrl column. |
This reverts commit 4708ea5.
| return new SubscriptionUpdateResult( | ||
| $"Skipping codeflow update because an update with a newer build {pr.NextBuildsToProcess} has already been queued." | ||
| + (pr != null ? $"PR url: {GitRepoUrlUtils.TurnApiUrlToWebsite(pr.Url)}" : ""), | ||
| $"Skipping codeflow update because an update with a newer build {pr.NextBuildsToProcess.Values.First().ToString() ?? "(N/A)"} has already been queued", | ||
| SubscriptionOutcomeType.NoUpdate); |
premun
left a comment
There was a problem hiding this comment.
I think my original question stands - why wouldn't it be enough in those 3 places?
| { | ||
| oldPrUrl = pr.Url; | ||
| await _stateManager.ClearAllStateAsync(isCodeFlow: true, clearPendingUpdates: true); | ||
| _outcomeRecorder.SetPullRequestUrl(null); |
There was a problem hiding this comment.
Isn't it better to not to do this? The URL will get overwritten later. And if we blow up before then, don't we want to have that old PR tied to that exception?
There was a problem hiding this comment.
I think it's misleading to put the URL in that case.
When we clear the PR state and start a codeflow on a new branch, failures won't have anything to do with the old PR, right? it would be misleading to put the URL there with the old codeflows and whatever user commits or comments might be there
| { | ||
| oldPrUrl = pr.Url; | ||
| await _stateManager.ClearAllStateAsync(isCodeFlow: true, clearPendingUpdates: true); | ||
| _outcomeRecorder.SetPullRequestUrl(null); |
There was a problem hiding this comment.
Same here, we will create a new PR and override this. And if we fail before then, let's log the old URI?
| _commentCollector.AddComment(PullRequestCommentBuilder.BuildOppositeCodeflowMergedNotification(), CommentType.Warning); | ||
| pr.BlockedFromFutureUpdates = true; | ||
| await _stateManager.SetInProgressPullRequestAsync(pr); | ||
| _outcomeRecorder.SetPullRequestUrl(pr.Url); |
There was a problem hiding this comment.
Why do we need this one? Wouldn't it already be set from when we created/fetched the PR info?
There was a problem hiding this comment.
Yep there were a few cases where copilot only added it because of the interaction with the state manager even though it wasn't needed (i.e. we only call the state manager to persist the pr's new "blocked" state or something like that). removed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 17 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- src/Maestro/Maestro.Data/Migrations/20260615142342_AddSubscriptionOutcomePrUrl.Designer.cs: Generated file
Comments suppressed due to low confidence (1)
src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdaters/PullRequestUpdater.cs:427
pr.NextBuildsToProcess.Values.First()can throw when the dictionary is empty, and even when non-empty it may show the wrong build id (it ignores which subscription key was matched). You already have the queued build id in theTryGetValue(..., out int buildId)result; use that for both logging and the outcome message to avoid exceptions and incorrect output.
$"Skipping codeflow update because an update with a newer build {pr.NextBuildsToProcess.Values.First().ToString() ?? "(N/A)"} has already been queued",
SubscriptionOutcomeType.NoUpdate);
}
(var status, prInfo) = await GetPullRequestStatusAsync(pr, isCodeFlow, tryingToUpdate: true);
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 17 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- src/Maestro/Maestro.Data/Migrations/20260615142342_AddSubscriptionOutcomePrUrl.Designer.cs: Generated file
Comments suppressed due to low confidence (1)
src/ProductConstructionService/ProductConstructionService.DependencyFlow/WorkItemProcessors/SubscriptionUpdateProcessor.cs:44
- This processor no longer validates that the subscription exists before dispatching to the updater. If the subscription was deleted while a work item is queued,
DependencyPullRequestUpdatercurrently throws anInvalidOperationException(retriable) when it can’t load the subscription, causing unnecessary retries. Consider keeping the early non-retriable validation here (as before) so deleted subscriptions fail fast and don’t churn the queue.
var build = await _sqlClient.GetBuildAsync(workItem.BuildId)
?? throw new NonRetriableException($"Build with buildId {workItem.BuildId} not found in the DB.");
var updater = _updaterFactory.CreatePullRequestUpdater(
PullRequestUpdaterId.Parse(
workItem.UpdaterId,
workItem.SubscriptionType == SubscriptionType.DependenciesAndSources));
| return new SubscriptionUpdateResult( | ||
| $"Skipping codeflow update because an update with a newer build {pr.NextBuildsToProcess} has already been queued." | ||
| + (pr != null ? $"PR url: {GitRepoUrlUtils.TurnApiUrlToWebsite(pr.Url)}" : ""), | ||
| $"Skipping codeflow update because an update with a newer build {pr.NextBuildsToProcess.Values.First().ToString() ?? "(N/A)"} has already been queued", | ||
| SubscriptionOutcomeType.NoUpdate); |
#6385