private WorkItemData ReplayRevisions(List <RevisionItem> revisionsToMigrate, WorkItemData sourceWorkItem, WorkItemData targetWorkItem) { try { var skipToFinalRevisedWorkItemType = _config.SkipToFinalRevisedWorkItemType; var finalDestType = revisionsToMigrate.Last().Type; if (skipToFinalRevisedWorkItemType && Engine.TypeDefinitionMaps.Items.ContainsKey(finalDestType)) { finalDestType = Engine.TypeDefinitionMaps.Items[finalDestType].Map(); } //If work item hasn't been created yet, create a shell if (targetWorkItem == null) { var targetType = revisionsToMigrate.First().Type; if (Engine.TypeDefinitionMaps.Items.ContainsKey(targetType)) { targetType = Engine.TypeDefinitionMaps.Items[targetType].Map(); } targetWorkItem = CreateWorkItem_Shell(Engine.Target.WorkItems.Project, sourceWorkItem, skipToFinalRevisedWorkItemType ? finalDestType : targetType); } if (_config.AttachRevisionHistory) { revisionManager.AttachSourceRevisionHistoryJsonToTarget(sourceWorkItem, targetWorkItem); } foreach (var revision in revisionsToMigrate) { var currentRevisionWorkItem = sourceWorkItem.GetRevision(revision.Number); TraceWriteLine(LogEventLevel.Information, " Processing Revision [{RevisionNumber}]", new Dictionary <string, object>() { { "RevisionNumber", revision.Number } }); // Decide on WIT var destType = currentRevisionWorkItem.Type; if (Engine.TypeDefinitionMaps.Items.ContainsKey(destType)) { destType = Engine.TypeDefinitionMaps.Items[destType].Map(); } PopulateWorkItem(currentRevisionWorkItem, targetWorkItem, destType); // Todo: Ensure all field maps use WorkItemData.Fields to apply a correct mapping Engine.FieldMaps.ApplyFieldMappings(currentRevisionWorkItem, targetWorkItem); // Todo: Think about an "UpdateChangedBy" flag as this is expensive! (2s/WI instead of 1,5s when writing "Migration") var changedBy = targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value.ToString(); targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value = revision.Fields["System.ChangedBy"].Value; targetWorkItem.ToWorkItem().Fields["System.History"].Value = revision.Fields["System.History"].Value; var reflectedUri = (TfsReflectedWorkItemId)Engine.Source.WorkItems.CreateReflectedWorkItemId(sourceWorkItem); if (!targetWorkItem.ToWorkItem().Fields.Contains(Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName)) { var ex = new InvalidOperationException("ReflectedWorkItemIDField Field Missing"); Log.LogError(ex, " The WorkItemType {WorkItemType} does not have a Field called {ReflectedWorkItemID}", targetWorkItem.Type, Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName); throw ex; } targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value = reflectedUri.ToString(); ProcessHTMLFieldAttachements(targetWorkItem); ProcessWorkItemEmbeddedLinks(sourceWorkItem, targetWorkItem); targetWorkItem.SaveToAzureDevOps(); TraceWriteLine(LogEventLevel.Information, " Saved TargetWorkItem {TargetWorkItemId}. Replayed revision {RevisionNumber} of {RevisionsToMigrateCount}", new Dictionary <string, object>() { { "TargetWorkItemId", targetWorkItem.Id }, { "RevisionNumber", revision.Number }, { "RevisionsToMigrateCount", revisionsToMigrate.Count } }); // Change this back to the original value as this object is mutated, and this value is needed elsewhere. targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value = changedBy; } if (targetWorkItem != null) { ProcessWorkItemAttachments(sourceWorkItem, targetWorkItem, false); if (!string.IsNullOrEmpty(targetWorkItem.Id)) { ProcessWorkItemLinks(Engine.Source.WorkItems, Engine.Target.WorkItems, sourceWorkItem, targetWorkItem); } if (_config.GenerateMigrationComment) { var reflectedUri = targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value; var history = new StringBuilder(); history.Append( $"This work item was migrated from a different project or organization. You can find the old version at <a href=\"{reflectedUri}\">{reflectedUri}</a>."); targetWorkItem.ToWorkItem().History = history.ToString(); } targetWorkItem.SaveToAzureDevOps(); attachmentEnricher.CleanUpAfterSave(); TraceWriteLine(LogEventLevel.Information, "...Saved as {TargetWorkItemId}", new Dictionary <string, object> { { "TargetWorkItemId", targetWorkItem.Id } }); } } catch (Exception ex) { TraceWriteLine(LogEventLevel.Information, "...FAILED to Save"); Log.LogInformation("==============================================================="); if (targetWorkItem != null) { foreach (Field f in targetWorkItem.ToWorkItem().Fields) { TraceWriteLine(LogEventLevel.Information, "{FieldReferenceName} ({FieldName}) | {FieldValue}", new Dictionary <string, object>() { { "FieldReferenceName", f.ReferenceName }, { "FieldName", f.Name }, { "FieldValue", f.Value } }); } } Log.LogInformation("==============================================================="); Log.LogError(ex.ToString(), ex); Log.LogInformation("==============================================================="); } return(targetWorkItem); }
private WorkItemData ReplayRevisions(List <RevisionItem> revisionsToMigrate, WorkItemData sourceWorkItem, WorkItemData targetWorkItem, int current) { try { var skipToFinalRevisedWorkItemType = _config.SkipToFinalRevisedWorkItemType; string finalDestType = revisionsToMigrate.Last().Type; if (skipToFinalRevisedWorkItemType && Engine.TypeDefinitionMaps.Items.ContainsKey(finalDestType)) { finalDestType = Engine.TypeDefinitionMaps.Items[finalDestType].Map(); } //If work item hasn't been created yet, create a shell if (targetWorkItem == null) { string targetType = revisionsToMigrate.First().Type; if (Engine.TypeDefinitionMaps.Items.ContainsKey(targetType)) { targetType = Engine.TypeDefinitionMaps.Items[targetType].Map(); } targetWorkItem = CreateWorkItem_Shell(Engine.Target.WorkItems.Project, sourceWorkItem, skipToFinalRevisedWorkItemType ? finalDestType : targetType); } if (_config.CollapseRevisions) { var data = revisionsToMigrate.Select(rev => { var revWi = sourceWorkItem.GetRevision(rev.Number); return(new { revWi.Id, Rev = revWi.Rev, RevisedDate = revWi.ChangedDate, revWi.Fields }); }); var fileData = JsonConvert.SerializeObject(data, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.None }); var filePath = Path.Combine(Path.GetTempPath(), $"{sourceWorkItem.Id}_PreMigrationHistory.json"); // todo: Delete this file after (!) WorkItem has been saved File.WriteAllText(filePath, fileData); targetWorkItem.ToWorkItem().Attachments.Add(new Attachment(filePath, "History has been consolidated into the attached file.")); revisionsToMigrate = revisionsToMigrate.GetRange(revisionsToMigrate.Count - 1, 1); TraceWriteLine(LogEventLevel.Information, " Attached a consolidated set of {RevisionCount} revisions.", new Dictionary <string, object>() { { "RevisionCount", data.Count() } }); } foreach (var revision in revisionsToMigrate) { var currentRevisionWorkItem = sourceWorkItem.GetRevision(revision.Number); TraceWriteLine(LogEventLevel.Information, " Processing Revision [{RevisionNumber}]", new Dictionary <string, object>() { { "RevisionNumber", revision.Number } }); // Decide on WIT string destType = currentRevisionWorkItem.Type; if (Engine.TypeDefinitionMaps.Items.ContainsKey(destType)) { destType = Engine.TypeDefinitionMaps.Items[destType].Map(); } WorkItemTypeChange(targetWorkItem, skipToFinalRevisedWorkItemType, finalDestType, revision, currentRevisionWorkItem, destType); PopulateWorkItem(currentRevisionWorkItem, targetWorkItem, destType); // Todo: Ensure all field maps use WorkItemData.Fields to apply a correct mapping Engine.FieldMaps.ApplyFieldMappings(currentRevisionWorkItem, targetWorkItem); // Todo: Think about an "UpdateChangedBy" flag as this is expensive! (2s/WI instead of 1,5s when writing "Migration") targetWorkItem.ToWorkItem().Fields["System.ChangedBy"].Value = currentRevisionWorkItem.Fields["System.ChangedBy"]; targetWorkItem.ToWorkItem().Fields["System.History"].Value = currentRevisionWorkItem.Fields["System.History"]; //Debug.WriteLine("Discussion:" + currentRevisionWorkItem.Revisions[revision.Index].Fields["System.History"].Value); TfsReflectedWorkItemId reflectedUri = (TfsReflectedWorkItemId)Engine.Source.WorkItems.CreateReflectedWorkItemId(sourceWorkItem); if (!targetWorkItem.ToWorkItem().Fields.Contains(Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName)) { var ex = new InvalidOperationException("ReflectedWorkItemIDField Field Missing"); Log.LogError(ex, " The WorkItemType {WorkItemType} does not have a Field called {ReflectedWorkItemID}", targetWorkItem.Type, Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName); throw ex; } targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value = reflectedUri.ToString(); targetWorkItem.SaveToAzureDevOps(); TraceWriteLine(LogEventLevel.Information, " Saved TargetWorkItem {TargetWorkItemId}. Replayed revision {RevisionNumber} of {RevisionsToMigrateCount}", new Dictionary <string, object>() { { "TargetWorkItemId", targetWorkItem.Id }, { "RevisionNumber", revision.Number }, { "RevisionsToMigrateCount", revisionsToMigrate.Count } }); } if (targetWorkItem != null) { ProcessWorkItemAttachments(sourceWorkItem, targetWorkItem, false); if (!string.IsNullOrEmpty(targetWorkItem.Id)) { ProcessWorkItemLinks(Engine.Source.WorkItems, Engine.Target.WorkItems, sourceWorkItem, targetWorkItem); } if (_config.GenerateMigrationComment) { var reflectedUri = targetWorkItem.ToWorkItem().Fields[Engine.Target.Config.AsTeamProjectConfig().ReflectedWorkItemIDFieldName].Value; var history = new StringBuilder(); history.Append( $"This work item was migrated from a different project or organization. You can find the old version at <a href=\"{reflectedUri}\">{reflectedUri}</a>."); targetWorkItem.ToWorkItem().History = history.ToString(); } targetWorkItem.SaveToAzureDevOps(); attachmentEnricher.CleanUpAfterSave(); TraceWriteLine(LogEventLevel.Information, "...Saved as {TargetWorkItemId}", new Dictionary <string, object> { { "TargetWorkItemId", targetWorkItem.Id } }); } } catch (Exception ex) { TraceWriteLine(LogEventLevel.Information, "...FAILED to Save"); Log.LogInformation("==============================================================="); if (targetWorkItem != null) { foreach (Field f in targetWorkItem.ToWorkItem().Fields) { TraceWriteLine(LogEventLevel.Information, "{FieldReferenceName} ({FieldName}) | {FieldValue}", new Dictionary <string, object>() { { "FieldReferenceName", f.ReferenceName }, { "FieldName", f.Name }, { "FieldValue", f.Value } }); } } Log.LogInformation("==============================================================="); Log.LogError(ex.ToString(), ex); Log.LogInformation("==============================================================="); } return(targetWorkItem); }