From 6e4e825363c0c962f06a11a833675201d729d03a Mon Sep 17 00:00:00 2001 From: Adam Zippor Date: Thu, 26 Feb 2026 14:14:55 +0100 Subject: [PATCH 1/3] Add creation date to InProgressPullRequest --- .../DarcLib/AzureDevOpsClient.cs | 1 + .../DarcLib/GitHubClient.cs | 1 + .../DarcLib/IRemoteGitRepo.cs | 1 + .../Generated/Models/TrackedPullRequest.cs | 3 ++ .../Controllers/PullRequestController.cs | 6 ++-- .../Pages/PullRequests.razor | 5 ++++ .../Model/InProgressPullRequest.cs | 2 ++ .../PullRequestUpdater.cs | 28 +++++++++++++------ 8 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs b/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs index bb8bfa0e39..9c68a66237 100644 --- a/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs +++ b/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs @@ -2004,6 +2004,7 @@ public async Task> GetGitTreeNames(string path, stri }, UpdatedAt = DateTimeOffset.UtcNow, HeadBranchSha = pr.LastMergeSourceCommit.CommitId, + CreatedAt = pr.CreationDate, }; public async Task> ListFilesAtCommitAsync(string repoUri, string commit, string path) diff --git a/src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs b/src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs index 46f277eb6a..506bfdbad9 100644 --- a/src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs +++ b/src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs @@ -1491,6 +1491,7 @@ private static PullRequest ToDarcLibPullRequest(Octokit.PullRequest pr) Status = status, UpdatedAt = pr.UpdatedAt, HeadBranchSha = pr.Head.Sha, + CreatedAt = pr.CreatedAt, }; } diff --git a/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs b/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs index 3a48c3ba63..d35316a76b 100644 --- a/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs +++ b/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs @@ -186,4 +186,5 @@ public class PullRequest public PrStatus Status { get; set; } public DateTimeOffset UpdatedAt { get; set; } public string HeadBranchSha { get; set; } + public DateTimeOffset CreatedAt { get; set; } } diff --git a/src/ProductConstructionService/Microsoft.DotNet.ProductConstructionService.Client/Generated/Models/TrackedPullRequest.cs b/src/ProductConstructionService/Microsoft.DotNet.ProductConstructionService.Client/Generated/Models/TrackedPullRequest.cs index c51039c5b4..3f34a97430 100644 --- a/src/ProductConstructionService/Microsoft.DotNet.ProductConstructionService.Client/Generated/Models/TrackedPullRequest.cs +++ b/src/ProductConstructionService/Microsoft.DotNet.ProductConstructionService.Client/Generated/Models/TrackedPullRequest.cs @@ -48,5 +48,8 @@ public TrackedPullRequest(bool sourceEnabled, DateTimeOffset lastUpdate, DateTim [JsonProperty("nextBuildsToApply")] public Dictionary NextBuildsToApply { get; set; } + + [JsonProperty("creationDate")] + public DateTimeOffset CreationDate { get; set; } } } diff --git a/src/ProductConstructionService/ProductConstructionService.Api/Api/v2020_02_20/Controllers/PullRequestController.cs b/src/ProductConstructionService/ProductConstructionService.Api/Api/v2020_02_20/Controllers/PullRequestController.cs index f3294d6902..4843e2fc9f 100644 --- a/src/ProductConstructionService/ProductConstructionService.Api/Api/v2020_02_20/Controllers/PullRequestController.cs +++ b/src/ProductConstructionService/ProductConstructionService.Api/Api/v2020_02_20/Controllers/PullRequestController.cs @@ -239,7 +239,8 @@ [.. pr.ContainedSubscriptions csu.SubscriptionId, csu.BuildId))], pr.HeadBranch, - pr.NextBuildsToProcess); + pr.NextBuildsToProcess, + pr.CreationDate); } private record TrackedPullRequest( @@ -253,7 +254,8 @@ private record TrackedPullRequest( DateTime? NextCheck, List Updates, string HeadBranch, - Dictionary NextBuildsToApply); + Dictionary NextBuildsToApply, + DateTime CreationDate); private record PullRequestUpdate( string SourceRepository, diff --git a/src/ProductConstructionService/ProductConstructionService.BarViz/Pages/PullRequests.razor b/src/ProductConstructionService/ProductConstructionService.BarViz/Pages/PullRequests.razor index 540e7f189c..cb5369ef0d 100644 --- a/src/ProductConstructionService/ProductConstructionService.BarViz/Pages/PullRequests.razor +++ b/src/ProductConstructionService/ProductConstructionService.BarViz/Pages/PullRequests.razor @@ -67,6 +67,11 @@ @(context.TargetBranch ?? "N/A") + + + @(context.CreationDate == default ? "N/A" : (DateTime.UtcNow - context.CreationDate).ToTimeAgo()) + + @(context.LastUpdate == default ? "N/A" : (DateTime.UtcNow - context.LastUpdate).ToTimeAgo()) diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/Model/InProgressPullRequest.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/Model/InProgressPullRequest.cs index 75481768af..a6153d68b1 100644 --- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/Model/InProgressPullRequest.cs +++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/Model/InProgressPullRequest.cs @@ -53,4 +53,6 @@ public class InProgressPullRequest : DependencyFlowWorkItem public CodeFlowDirection CodeFlowDirection { get; set; } public bool BlockedFromFutureUpdates { get; set; } = false; + + public DateTime CreationDate { get; set; } } diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs index 5c7908d6a2..62b9c6c1d7 100644 --- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs +++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs @@ -180,9 +180,11 @@ public async Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, return; } - var pullRequest = await GetPullRequestStatusAsync(pr, isCodeFlow, tryingToUpdate: true); - prInfo = pullRequest.PrInfo; - switch (pullRequest.Status) + (var status, prInfo) = await GetPullRequestStatusAsync(pr, isCodeFlow, tryingToUpdate: true); + + await UpdatePullRequestCreationDateAsync(pr, prInfo.CreatedAt.UtcDateTime); + + switch (status) { case PullRequestStatus.Completed: case PullRequestStatus.Invalid: @@ -202,7 +204,7 @@ public async Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, await ScheduleUpdateForLater(pr, update, isCodeFlow); return; default: - throw new NotImplementedException($"Unknown PR status {pullRequest.Status}"); + throw new NotImplementedException($"Unknown PR status {status}"); } } @@ -267,11 +269,18 @@ public async Task CheckPullRequestAsync(PullRequestCheck pullRequestCheck) return await CheckInProgressPullRequestAsync(inProgressPr, pullRequestCheck.IsCodeFlow); } - protected virtual async Task CheckInProgressPullRequestAsync(InProgressPullRequest pullRequestCheck, bool isCodeFlow) + protected virtual async Task CheckInProgressPullRequestAsync(InProgressPullRequest pr, bool isCodeFlow) + { + _logger.LogInformation("Checking in-progress pull request {url}", pr.Url); + (var status, var prInfo) = await GetPullRequestStatusAsync(pr, isCodeFlow, tryingToUpdate: false); + await UpdatePullRequestCreationDateAsync(pr, prInfo.CreatedAt.UtcDateTime); + return status != PullRequestStatus.Invalid; + } + + private async Task UpdatePullRequestCreationDateAsync(InProgressPullRequest pr, DateTime creationDate) { - _logger.LogInformation("Checking in-progress pull request {url}", pullRequestCheck.Url); - var pr = await GetPullRequestStatusAsync(pullRequestCheck, isCodeFlow, tryingToUpdate: false); - return pr.Status != PullRequestStatus.Invalid; + pr.CreationDate = creationDate; //todo this is a temporary solution to update existing PRs, remove it + await _pullRequestState.SetAsync(pr); } protected virtual Task TagSourceRepositoryGitHubContactsIfPossibleAsync(InProgressPullRequest pr) @@ -617,6 +626,7 @@ private async Task AddDependencyFlowEventsAsync( CoherencyCheckSuccessful = false, CoherencyErrors = aggregatedCoherencyErrors.Count > 0 ? aggregatedCoherencyErrors : null, CodeFlowDirection = CodeFlowDirection.None, + CreationDate = DateTime.UtcNow, }; await SetPullRequestCheckReminder(inProgressPr, pr, isCodeFlow); @@ -671,6 +681,7 @@ private async Task AddDependencyFlowEventsAsync( CoherencyCheckSuccessful = repoDependencyUpdates.CoherencyCheckSuccessful, CoherencyErrors = aggregatedCoherencyErrors.Count > 0 ? aggregatedCoherencyErrors : null, CodeFlowDirection = CodeFlowDirection.None, + CreationDate = DateTime.UtcNow, }; if (!string.IsNullOrEmpty(pr?.Url)) @@ -1389,6 +1400,7 @@ private async Task UpdateCodeFlowPullRequestAsync( CodeFlowDirection = subscription.IsForwardFlow() ? CodeFlowDirection.ForwardFlow : CodeFlowDirection.BackFlow, + CreationDate = DateTime.UtcNow, }; await AddDependencyFlowEventsAsync( From a7293f1c236816fc56a8aa122ad8b7294cbd13df Mon Sep 17 00:00:00 2001 From: Adam Zippor Date: Fri, 27 Feb 2026 09:40:40 +0100 Subject: [PATCH 2/3] PR fixes + fix tests --- .../PullRequestUpdater.cs | 10 +++++++--- .../UpdaterTests.cs | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs index 62b9c6c1d7..451e68c643 100644 --- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs +++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs @@ -277,10 +277,14 @@ protected virtual async Task CheckInProgressPullRequestAsync(InProgressPul return status != PullRequestStatus.Invalid; } - private async Task UpdatePullRequestCreationDateAsync(InProgressPullRequest pr, DateTime creationDate) + private async Task UpdatePullRequestCreationDateAsync(InProgressPullRequest pr, DateTime creationDate) { - pr.CreationDate = creationDate; //todo this is a temporary solution to update existing PRs, remove it - await _pullRequestState.SetAsync(pr); + //todo this is a temporary solution to update existing PRs, it can be removed after all existing PRs get a creation date + if (pr.CreationDate == creationDate) + { + pr.CreationDate = creationDate; + await _pullRequestState.SetAsync(pr); + } } protected virtual Task TagSourceRepositoryGitHubContactsIfPossibleAsync(InProgressPullRequest pr) diff --git a/test/ProductConstructionService/ProductConstructionService.DependencyFlow.Tests/UpdaterTests.cs b/test/ProductConstructionService/ProductConstructionService.DependencyFlow.Tests/UpdaterTests.cs index 404820cb1b..49d382cd28 100644 --- a/test/ProductConstructionService/ProductConstructionService.DependencyFlow.Tests/UpdaterTests.cs +++ b/test/ProductConstructionService/ProductConstructionService.DependencyFlow.Tests/UpdaterTests.cs @@ -97,6 +97,7 @@ public void UpdaterTests_TearDown() pr.LastCheck = (ExpectedPRCacheState[pair.Key] as InProgressPullRequest)!.LastCheck; pr.LastUpdate = (ExpectedPRCacheState[pair.Key] as InProgressPullRequest)!.LastUpdate; pr.NextCheck = (ExpectedPRCacheState[pair.Key] as InProgressPullRequest)!.NextCheck; + pr.CreationDate = (ExpectedPRCacheState[pair.Key] as InProgressPullRequest)!.CreationDate; } } Cache.Data.Where(pair => pair.Value is InProgressPullRequest).Should().BeEquivalentTo(ExpectedPRCacheState); From cf0bb162242eed84be12d88fb80e2a55a575bcbd Mon Sep 17 00:00:00 2001 From: Adam Zippor Date: Fri, 27 Feb 2026 10:00:04 +0100 Subject: [PATCH 3/3] Rename CreatedAt -> CreationDate in darclib PR object --- src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs | 2 +- src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs | 2 +- src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs | 2 +- .../PullRequestUpdater.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs b/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs index 9c68a66237..eeda223854 100644 --- a/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs +++ b/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs @@ -2004,7 +2004,7 @@ public async Task> GetGitTreeNames(string path, stri }, UpdatedAt = DateTimeOffset.UtcNow, HeadBranchSha = pr.LastMergeSourceCommit.CommitId, - CreatedAt = pr.CreationDate, + CreationDate = pr.CreationDate, }; public async Task> ListFilesAtCommitAsync(string repoUri, string commit, string path) diff --git a/src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs b/src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs index 506bfdbad9..cbe7318dff 100644 --- a/src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs +++ b/src/Microsoft.DotNet.Darc/DarcLib/GitHubClient.cs @@ -1491,7 +1491,7 @@ private static PullRequest ToDarcLibPullRequest(Octokit.PullRequest pr) Status = status, UpdatedAt = pr.UpdatedAt, HeadBranchSha = pr.Head.Sha, - CreatedAt = pr.CreatedAt, + CreationDate = pr.CreatedAt, }; } diff --git a/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs b/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs index d35316a76b..dae8e0c37c 100644 --- a/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs +++ b/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs @@ -186,5 +186,5 @@ public class PullRequest public PrStatus Status { get; set; } public DateTimeOffset UpdatedAt { get; set; } public string HeadBranchSha { get; set; } - public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset CreationDate { get; set; } } diff --git a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs index 451e68c643..737527b805 100644 --- a/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs +++ b/src/ProductConstructionService/ProductConstructionService.DependencyFlow/PullRequestUpdater.cs @@ -182,7 +182,7 @@ public async Task ProcessPendingUpdatesAsync(SubscriptionUpdateWorkItem update, (var status, prInfo) = await GetPullRequestStatusAsync(pr, isCodeFlow, tryingToUpdate: true); - await UpdatePullRequestCreationDateAsync(pr, prInfo.CreatedAt.UtcDateTime); + await UpdatePullRequestCreationDateAsync(pr, prInfo.CreationDate.UtcDateTime); switch (status) { @@ -273,7 +273,7 @@ protected virtual async Task CheckInProgressPullRequestAsync(InProgressPul { _logger.LogInformation("Checking in-progress pull request {url}", pr.Url); (var status, var prInfo) = await GetPullRequestStatusAsync(pr, isCodeFlow, tryingToUpdate: false); - await UpdatePullRequestCreationDateAsync(pr, prInfo.CreatedAt.UtcDateTime); + await UpdatePullRequestCreationDateAsync(pr, prInfo.CreationDate.UtcDateTime); return status != PullRequestStatus.Invalid; }