diff --git a/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs b/src/Microsoft.DotNet.Darc/DarcLib/AzureDevOpsClient.cs index bb8bfa0e39..eeda223854 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, + 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 46f277eb6a..cbe7318dff 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, + CreationDate = pr.CreatedAt, }; } diff --git a/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs b/src/Microsoft.DotNet.Darc/DarcLib/IRemoteGitRepo.cs index 3a48c3ba63..dae8e0c37c 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 CreationDate { 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..737527b805 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.CreationDate.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,22 @@ 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}", pullRequestCheck.Url); - var pr = await GetPullRequestStatusAsync(pullRequestCheck, isCodeFlow, tryingToUpdate: false); - return pr.Status != PullRequestStatus.Invalid; + _logger.LogInformation("Checking in-progress pull request {url}", pr.Url); + (var status, var prInfo) = await GetPullRequestStatusAsync(pr, isCodeFlow, tryingToUpdate: false); + await UpdatePullRequestCreationDateAsync(pr, prInfo.CreationDate.UtcDateTime); + return status != PullRequestStatus.Invalid; + } + + private async Task UpdatePullRequestCreationDateAsync(InProgressPullRequest pr, DateTime creationDate) + { + //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) @@ -617,6 +630,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 +685,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 +1404,7 @@ private async Task UpdateCodeFlowPullRequestAsync( CodeFlowDirection = subscription.IsForwardFlow() ? CodeFlowDirection.ForwardFlow : CodeFlowDirection.BackFlow, + CreationDate = DateTime.UtcNow, }; await AddDependencyFlowEventsAsync( 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);