diff --git a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs index 3355c9ed9..de648385e 100644 --- a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs +++ b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs @@ -876,6 +876,7 @@ private WorkItemData ReplayRevisions(List revisionsToMigrate, Work targetWorkItem.ToWorkItem().History = history.ToString(); } targetWorkItem.SaveToAzureDevOps(); + PatchClosedDate(sourceWorkItem, targetWorkItem); CommonTools.Attachment.CleanUpAfterSave(); TraceWriteLine(LogEventLevel.Information, "...Saved as {TargetWorkItemId}", new Dictionary { { "TargetWorkItemId", targetWorkItem.Id } }); @@ -961,6 +962,65 @@ private void CheckClosedDateIsValid(WorkItemData sourceWorkItem, WorkItemData ta } } + private void PatchClosedDate(WorkItemData sourceWorkItem, WorkItemData targetWorkItem) + { + var targetItem = targetWorkItem.ToWorkItem(); + var state = targetItem.Fields["System.State"].Value?.ToString(); + if (!(state == "Closed" || state == "Done")) + { + return; + } + + object srcClosedDate = null; + if (sourceWorkItem.ToWorkItem().Fields.Contains("Microsoft.VSTS.Common.ClosedDate")) + { + srcClosedDate = sourceWorkItem.ToWorkItem().Fields["Microsoft.VSTS.Common.ClosedDate"].Value; + } + if (srcClosedDate == null && sourceWorkItem.ToWorkItem().Fields.Contains("System.ClosedDate")) + { + srcClosedDate = sourceWorkItem.ToWorkItem().Fields["System.ClosedDate"].Value; + } + if (srcClosedDate == null) + { + return; + } + + try + { + ValidatePatTokenRequirement(); + Uri collectionUri = Target.Options.Collection; + string token = Target.Options.Authentication.AccessToken; + VssConnection connection = new(collectionUri, new VssBasicCredential(string.Empty, token)); + WorkItemTrackingHttpClient workItemTrackingClient = connection.GetClient(); + JsonPatchDocument patchDocument = []; + + var closedDateFieldRef = targetItem.Fields.Contains("Microsoft.VSTS.Common.ClosedDate") + ? "Microsoft.VSTS.Common.ClosedDate" + : (targetItem.Fields.Contains("System.ClosedDate") ? "System.ClosedDate" : null); + + if (closedDateFieldRef == null) + { + Log.LogWarning("Cannot patch ClosedDate: no appropriate field on target."); + return; + } + + patchDocument.Add(new JsonPatchOperation + { + Operation = Operation.Add, + Path = $"/fields/{closedDateFieldRef}", + Value = srcClosedDate + }); + + int id = int.Parse(targetWorkItem.Id); + var result = workItemTrackingClient.UpdateWorkItemAsync(patchDocument, id, bypassRules: true).Result; + Log.LogInformation("Patched ClosedDate for work item {id}.", id); + } + catch (Exception ex) + { + Log.LogWarning(ex, "Failed to patch ClosedDate for {id}.", targetWorkItem.Id); + } + } + private bool SkipRevisionWithInvalidIterationPath(WorkItemData targetWorkItemData) { if (!Options.SkipRevisionWithInvalidIterationPath)