public async Task Preprocess(IMigrationContext migrationContext, IBatchMigrationContext batchContext, IList <WorkItem> sourceWorkItems, IList <WorkItem> targetWorkItems) { var linkedWorkItemArtifactUrls = new HashSet <string>(); foreach (WorkItem sourceWorkItem in sourceWorkItems) { var relations = GetWorkItemLinkRelations(migrationContext, sourceWorkItem.Relations); var linkedIds = relations.Select(r => ClientHelpers.GetWorkItemIdFromApiEndpoint(r.Url)); var uris = linkedIds.Where(id => !migrationContext.SourceToTargetIds.ContainsKey(id)).Select(id => ClientHelpers.GetWorkItemApiEndpoint(migrationContext.Config.SourceConnection.Account, id)); linkedWorkItemArtifactUrls.AddRange(uris); } await linkedWorkItemArtifactUrls.Batch(Constants.BatchSize).ForEachAsync(migrationContext.Config.Parallelism, async(workItemArtifactUris, batchId) => { Logger.LogTrace(LogDestination.File, $"Finding linked work items on target for batch {batchId}"); var results = await ClientHelpers.QueryArtifactUriToGetIdsFromUris(migrationContext.TargetClient.WorkItemTrackingHttpClient, workItemArtifactUris); foreach (var result in results.ArtifactUrisQueryResult) { if (result.Value != null) { if (result.Value.Count() == 1) { var sourceId = ClientHelpers.GetWorkItemIdFromApiEndpoint(result.Key); var targetId = result.Value.First().Id; migrationContext.SourceToTargetIds[sourceId] = targetId; } } } Logger.LogTrace(LogDestination.File, $"Finished finding linked work items on target for batch {batchId}"); }); }
protected override void UpdateWorkItemMigrationStatus(IBatchMigrationContext batchContext, int sourceId, WorkItem targetWorkItem) { WorkItemMigrationState state = batchContext.WorkItemMigrationState.First(w => w.SourceId == sourceId); state.MigrationCompleted |= WorkItemMigrationState.MigrationCompletionStatus.Phase1; state.TargetId = targetWorkItem.Id.Value; }
private async Task ProcessInlineImages(IBatchMigrationContext batchContext) { foreach (WorkItem sourceWorkItem in batchContext.SourceWorkItems) { await ProcessInlineImagesForWorkItem(batchContext, sourceWorkItem); } }
private async Task ProcessInlineImagesForHtmlField(IBatchMigrationContext batchContext, int sourceWorkItemId, KeyValuePair <string, object> field) { // we are assuming that this will always be a string, so log if it is not if (!(field.Value is string)) { Logger.LogWarning(LogDestination.File, $"Unexpected value for html field {field.Key} for source work item {sourceWorkItemId}"); } string fieldHtmlContent = field.Value as string; HashSet <string> inlineImageUrls = MigrationHelpers.GetInlineImageUrlsFromField(fieldHtmlContent, this.context.SourceClient.Connection.Uri.AbsoluteUri); foreach (string inlineImageUrl in inlineImageUrls) { // There are scenarios where someone copy/pastes image from one work item to another and the // it ends up using the same inline attachment link. if (!batchContext.SourceInlineImageUrlToTargetInlineImageGuid.ContainsKey(inlineImageUrl)) { string targetGuid = await UploadInlineImageAttachmentFromSourceWorkItemToTarget(batchContext, inlineImageUrl, sourceWorkItemId, context.Config.MaxAttachmentSize); if (!String.IsNullOrEmpty(targetGuid)) { batchContext.SourceInlineImageUrlToTargetInlineImageGuid.Add(inlineImageUrl, targetGuid); } } } }
public async Task <int> ProcessAreaPaths(IBatchMigrationContext batchContext) { int modificationCount = 0; Logger.LogInformation(LogDestination.All, $"Identified {context.SourceAreaAndIterationTree.AreaPathListLookup.Count} area paths in source project."); foreach (var areaPath in context.SourceAreaAndIterationTree.AreaPathList) { string areaPathInTarget = AreaAndIterationPathTree.ReplaceLeadingProjectName(areaPath, context.Config.SourceConnection.Project, context.Config.TargetConnection.Project); // If the area path is not found in the work items we're currently processing then just ignore it. if (!batchContext.SourceWorkItems.Any(w => w.Fields.ContainsKey("System.AreaPath") && w.Fields["System.AreaPath"].ToString().ToLower().Equals(areaPath.ToLower()))) { continue; } else if (context.TargetAreaAndIterationTree.AreaPathList.Any(a => a.ToLower() == areaPathInTarget.ToLower())) { Logger.LogInformation(LogDestination.All, $"[Exists] {areaPathInTarget}."); } else { modificationCount += 1; await CreateAreaPath(areaPathInTarget); Logger.LogSuccess(LogDestination.All, $"[Created] {areaPathInTarget}."); } } Logger.LogInformation(LogDestination.All, $"Area paths synchronized."); return(modificationCount); }
protected override void BatchCompleted(IMigrationContext migrationContext, IBatchMigrationContext batchContext) { var successfullyMigrated = batchContext.WorkItemMigrationState.Where(m => m.MigrationCompleted == WorkItemMigrationState.MigrationCompletionStatus.Phase1).Select(m => new KeyValuePair <int, int>(m.SourceId, m.TargetId.Value)); // add successfully migrated workitems to main cache if (successfullyMigrated.Any()) { migrationContext.SourceToTargetIds.AddRange(successfullyMigrated); } }
public async Task Process(IBatchMigrationContext batchContext) { object identityObject = null; string identityValue = null; HashSet <string> identitiesToProcess = new HashSet <string>(StringComparer.CurrentCultureIgnoreCase); foreach (var sourceWorkItem in batchContext.SourceWorkItems) { foreach (var field in context.IdentityFields) { if (sourceWorkItem.Fields.TryGetValueIgnoringCase(field, out identityObject)) { identityValue = (string)identityObject; if (!string.IsNullOrEmpty(identityValue) && identityValue.Contains("<") && identityValue.Contains(">") && (identityValue.Contains("@"))) { // parse out email address from the combo string identityValue = identityValue.Substring(identityValue.LastIndexOf("<") + 1, identityValue.LastIndexOf(">") - identityValue.LastIndexOf("<") - 1); if (!identitiesToProcess.Contains(identityValue) && !this.context.ValidatedIdentities.Contains(identityValue) && !this.context.InvalidIdentities.Contains(identityValue)) { Logger.LogTrace(LogDestination.File, $"Found identity {identityValue} in batch {batchContext.BatchId} which has not yet been validated for the target account"); identitiesToProcess.Add(identityValue); } } } } } Logger.LogTrace(LogDestination.File, $"Adding {identitiesToProcess.Count} identities to the account for batch {batchContext.BatchId}"); foreach (var identity in identitiesToProcess) { try { await RetryHelper.RetryAsync(async() => { return(await graphClient.CreateUserAsync(new GraphUserPrincipalNameCreationContext() { PrincipalName = identity }, Groups)); }, 5); context.ValidatedIdentities.Add(identity); } catch (Exception ex) { Logger.LogWarning(LogDestination.File, ex, $"Unable to add identity {identity} to the target account for batch {batchContext.BatchId}"); context.InvalidIdentities.Add(identity); } } Logger.LogTrace(LogDestination.File, $"Completed adding {identitiesToProcess.Count} identities to the account for batch {batchContext.BatchId}"); }
private async Task ProcessInlineImagesForWorkItem(IBatchMigrationContext batchContext, WorkItem sourceWorkItem) { if (sourceWorkItem.Fields != null) { foreach (KeyValuePair <string, object> field in sourceWorkItem.Fields) { if (this.context.HtmlFieldReferenceNames.Contains(field.Key)) // field is of FieldType.Html { await ProcessInlineImagesForHtmlField(batchContext, sourceWorkItem.Id.Value, field); } } } }
public override void PrepareBatchContext(IBatchMigrationContext batchContext, IList <WorkItemMigrationState> workItemsAndStateToMigrate) { foreach (var sourceWorkItem in batchContext.SourceWorkItems) { var workItemMigrationState = workItemsAndStateToMigrate.Where(w => w.SourceId == sourceWorkItem.Id.Value).FirstOrDefault(); if (workItemMigrationState != null && workItemMigrationState.TargetId.HasValue) { batchContext.TargetIdToSourceWorkItemMapping.Add(workItemMigrationState.TargetId.Value, sourceWorkItem); } else { Logger.LogWarning(LogDestination.File, $"Expected source work item {sourceWorkItem.Id} to map to a target work item"); } } }
private async Task ProcessInlineImagesForHtmlField(IBatchMigrationContext batchContext, int sourceWorkItemId, KeyValuePair <string, object> field) { string fieldHtmlContent = (string)field.Value; // we are assuming that this will always be convertable to string HashSet <string> inlineImageUrls = MigrationHelpers.GetInlineImageUrlsFromField(fieldHtmlContent, this.context.SourceClient.Connection.Uri.AbsoluteUri); foreach (string inlineImageUrl in inlineImageUrls) { // There are scenarios where someone copy/pastes image from one work item to another and the // it ends up using the same inline attachment link. if (!batchContext.SourceInlineImageUrlToTargetInlineImageGuid.ContainsKey(inlineImageUrl)) { string targetGuid = await UploadInlineImageAttachmentFromSourceWorkItemToTarget(batchContext, inlineImageUrl, sourceWorkItemId, context.Config.MaxAttachmentSize); if (!String.IsNullOrEmpty(targetGuid)) { batchContext.SourceInlineImageUrlToTargetInlineImageGuid.Add(inlineImageUrl, targetGuid); } } } }
public async Task Process(IBatchMigrationContext batchContext) { int modificationCount = 0; await Task.WhenAll( Migrator.ReadSourceNodes(context, context.Config.SourceConnection.Project), Migrator.ReadTargetNodes(context, context.Config.TargetConnection.Project)); if (context.Config.MoveAreaPaths) { modificationCount += await ProcessAreaPaths(batchContext); } if (context.Config.MoveIterations) { modificationCount += await ProcessIterationPaths(batchContext); } if (modificationCount > 0) { await Migrator.ReadTargetNodes(context, context.Config.TargetConnection.Project); } }
public abstract int GetWorkItemsToProcessCount(IBatchMigrationContext batchContext);
public async Task Process(IBatchMigrationContext batchContext) { await ProcessInlineImages(batchContext); }
public abstract BaseWitBatchRequestGenerator GetWitBatchRequestGenerator(IMigrationContext context, IBatchMigrationContext batchContext);
public static void HandleCriticalError(WitBatchResponse witBatchResponse, IEnumerable <int> sourceIds, IBatchMigrationContext batchContext) { CriticalExceptionResponse exceptionResponse = witBatchResponse.ParseBody <CriticalExceptionResponse>(); string exceptionMessage = exceptionResponse.Value.Message; Logger.LogError(LogDestination.All, $"Exception during batch {batchContext.BatchId}: {exceptionMessage}"); MarkBatchAsFailed(batchContext, sourceIds, FailureReason.CriticalError); }
public async Task <IEnumerable <JsonPatchOperation> > Process(IMigrationContext migrationContext, IBatchMigrationContext batchContext, WorkItem sourceWorkItem, WorkItem targetWorkItem) { IList <JsonPatchOperation> jsonPatchOperations = new List <JsonPatchOperation>(); JsonPatchOperation addPostMoveTagOperation = AddPostMoveTag(migrationContext, sourceWorkItem); jsonPatchOperations.Add(addPostMoveTagOperation); return(jsonPatchOperations); }
private async Task <IList <(int SourceId, WitBatchRequest WitBatchRequest)> > GenerateWitBatchRequestsForPhase2Batch(IBatchMigrationContext batchContext, int batchId, IList <WorkItemMigrationState> workItemMigrationState, IList <WorkItem> sourceWorkItems, IList <WorkItem> targetWorkItems) { IList <(int SourceId, WitBatchRequest WitBatchRequest)> result = new List <(int SourceId, WitBatchRequest WitBatchRequest)>(); IEnumerable <IPhase2Processor> phase2Processors = ClientHelpers.GetProcessorInstances <IPhase2Processor>(context.Config); foreach (IPhase2Processor processor in phase2Processors) { Logger.LogInformation(LogDestination.File, $"Starting preprocessing of phase 2 step {processor.Name} for batch {batchId}"); await processor.Preprocess(context, batchContext, sourceWorkItems, targetWorkItems); Logger.LogInformation(LogDestination.File, $"Completed preprocessing of phase 2 step {processor.Name} for batch {batchId}"); } foreach (var sourceToTarget in batchContext.SourceWorkItemIdToTargetWorkItemIdMapping) { int sourceId = sourceToTarget.Key; int targetId = sourceToTarget.Value; WorkItem sourceWorkItem = sourceWorkItems.First(a => a.Id == sourceId); WorkItem targetWorkItem = targetWorkItems.First(a => a.Id == targetId); IList <JsonPatchOperation> jsonPatchOperations = new List <JsonPatchOperation>(); WorkItemMigrationState state = workItemMigrationState.First(a => a.SourceId == sourceId); state.RevAndPhaseStatus = GetRevAndPhaseStatus(targetWorkItem, sourceId); ISet <string> enabledPhaseStatuses = System.Linq.Enumerable.ToHashSet(phase2Processors.Where(a => a.IsEnabled(context.Config)).Select(b => b.Name)); enabledPhaseStatuses.Remove(Constants.RelationPhaseClearAllRelations); foreach (IPhase2Processor processor in phase2Processors) { IEnumerable <JsonPatchOperation> processorJsonPatchOperations = await processor.Process(context, batchContext, sourceWorkItem, targetWorkItem); jsonPatchOperations.AddRange(processorJsonPatchOperations); } jsonPatchOperations.Add(GetAddHyperlinkWithCommentOperation(targetWorkItems, state, sourceId, targetId, sourceWorkItem, enabledPhaseStatuses)); if (this.context.Config.IncludeWebLink) { var link = (ReferenceLink)sourceWorkItem.Links.Links["html"]; var addWebLinkOperation = MigrationHelpers.GetHyperlinkAddOperation(link.Href); jsonPatchOperations.Add(addWebLinkOperation); } if (jsonPatchOperations.Any()) { WitBatchRequest witBatchRequest = GenerateWitBatchRequestFromJsonPatchOperations(jsonPatchOperations, targetId); result.Add((sourceId, witBatchRequest)); } } return(result); }
public async Task Preprocess(IMigrationContext migrationContext, IBatchMigrationContext batchContext, IList <WorkItem> sourceWorkItems, IList <WorkItem> targetWorkItems) { }
protected override void BatchCompleted(IMigrationContext migrationContext, IBatchMigrationContext batchContext) { // no-op }
public override BaseWitBatchRequestGenerator GetWitBatchRequestGenerator(IMigrationContext context, IBatchMigrationContext batchContext) { return(new UpdateWitBatchRequestGenerator(context, batchContext)); }
public override int GetWorkItemsToProcessCount(IBatchMigrationContext batchContext) { return(batchContext.TargetIdToSourceWorkItemMapping.Count); }
public async Task <IEnumerable <JsonPatchOperation> > Process(IMigrationContext migrationContext, IBatchMigrationContext batchContext, WorkItem sourceWorkItem, WorkItem targetWorkItem) { IList <JsonPatchOperation> jsonPatchOperations = new List <JsonPatchOperation>(); if (sourceWorkItem.Relations == null) { return(jsonPatchOperations); } IList <WorkItemRelation> sourceWorkItemLinkRelations = GetWorkItemLinkRelations(migrationContext, sourceWorkItem.Relations); if (sourceWorkItemLinkRelations.Any()) { foreach (WorkItemRelation sourceWorkItemLinkRelation in sourceWorkItemLinkRelations) { int linkedSourceId = ClientHelpers.GetWorkItemIdFromApiEndpoint(sourceWorkItemLinkRelation.Url); int targetWorkItemId = targetWorkItem.Id.Value; int linkedTargetId; if (!migrationContext.SourceToTargetIds.TryGetValue(linkedSourceId, out linkedTargetId)) { continue; } string comment = MigrationHelpers.GetCommentFromAttributes(sourceWorkItemLinkRelation); WorkItemLink newWorkItemLink = new WorkItemLink(linkedTargetId, sourceWorkItemLinkRelation.Rel, false, false, comment, 0); JsonPatchOperation workItemLinkAddOperation = MigrationHelpers.GetWorkItemLinkAddOperation(migrationContext, newWorkItemLink); jsonPatchOperations.Add(workItemLinkAddOperation); } } return(jsonPatchOperations); }
/// <summary> /// Uploads inline image attachment from source to target and returns target inline image AttachmentReference Guid. /// </summary> /// <param name="inlineImageUrl"></param> /// <returns></returns> private async Task <string> UploadInlineImageAttachmentFromSourceWorkItemToTarget(IBatchMigrationContext batchContext, string inlineImageUrl, int sourceWorkItemId, int maxAttachmentSize) { Guid sourceGuid = MigrationHelpers.GetAttachmentUrlGuid(inlineImageUrl); string targetGuid = null; if (Guid.Empty.Equals(sourceGuid)) { Logger.LogWarning(LogDestination.File, $"Unexpected format for inline image url {inlineImageUrl} for source work item {sourceWorkItemId}"); ClientHelpers.AddFailureReasonToWorkItemMigrationState(sourceWorkItemId, FailureReason.InlineImageUrlFormatError, batchContext.WorkItemMigrationState); // just return the null guid since there is nothing we can do with an invalid url return(null); } Stream stream = null; try { Logger.LogTrace(LogDestination.File, $"Reading inline image {inlineImageUrl} for source work item {sourceWorkItemId} from the source account"); stream = await WorkItemTrackingHelpers.GetAttachmentAsync(this.context.SourceClient.WorkItemTrackingHttpClient, sourceGuid); Logger.LogTrace(LogDestination.File, $"Completed reading inline image {inlineImageUrl} for source work item {sourceWorkItemId} from the source account"); } catch (Exception e) { Logger.LogError(LogDestination.File, e, $"Unable to download inline image {inlineImageUrl} for source work item {sourceWorkItemId} from the source account"); ClientHelpers.AddFailureReasonToWorkItemMigrationState(sourceWorkItemId, FailureReason.InlineImageDownloadError, batchContext.WorkItemMigrationState); // just return the null guid since there is nothing we can do if we couldn't download the inline image return(null); } if (stream != null) { using (MemoryStream memstream = new MemoryStream()) { bool copiedStream = false; using (stream) { try { Logger.LogTrace(LogDestination.File, $"Downloading inline image {inlineImageUrl} for source work item {sourceWorkItemId} from the source account"); await ClientHelpers.CopyStreamAsync(stream, memstream); Logger.LogTrace(LogDestination.File, $"Completed downloading inline image {inlineImageUrl} for source work item {sourceWorkItemId} from the source account"); copiedStream = true; } catch (Exception e) { Logger.LogError(LogDestination.File, e, $"Unable to download inline image {inlineImageUrl} for source work item {sourceWorkItemId} from the source account"); ClientHelpers.AddFailureReasonToWorkItemMigrationState(sourceWorkItemId, FailureReason.InlineImageDownloadError, batchContext.WorkItemMigrationState); } } if (memstream.Length > maxAttachmentSize) { Logger.LogWarning(LogDestination.File, $"Inline image attachment of source work item with id {sourceWorkItemId} and url {inlineImageUrl} exceeded the maximum attachment size of {maxAttachmentSize} bytes." + $" Skipping creating the inline image attachment in target account."); return(null); } if (copiedStream) { memstream.Position = 0; //upload the attachment to target try { Logger.LogTrace(LogDestination.File, $"Uploading inline image {inlineImageUrl} for source work item {sourceWorkItemId} from the source account"); var aRef = await WorkItemTrackingHelpers.CreateAttachmentChunkedAsync(this.context.TargetClient.WorkItemTrackingHttpClient, this.context.TargetClient.Connection, memstream, this.context.Config.AttachmentUploadChunkSize); targetGuid = aRef.Id.ToString(); Logger.LogTrace(LogDestination.File, $"Completed uploading inline image {inlineImageUrl} for source work item {sourceWorkItemId} from the source account"); } catch (Exception e) { Logger.LogError(LogDestination.File, e, $"Unable to upload inline image for source work item {sourceWorkItemId} in the target account"); ClientHelpers.AddFailureReasonToWorkItemMigrationState(sourceWorkItemId, FailureReason.InlineImageUploadError, batchContext.WorkItemMigrationState); } } } } return(targetGuid); }
public async Task <IEnumerable <JsonPatchOperation> > Process(IMigrationContext migrationContext, IBatchMigrationContext batchContext, WorkItem sourceWorkItem, WorkItem targetWorkItem) { IList <JsonPatchOperation> jsonPatchOperations = new List <JsonPatchOperation>(); if (sourceWorkItem.Relations == null) { return(jsonPatchOperations); } IEnumerable <WorkItemRelation> sourceAttachmentWorkItemRelations = GetAttachmentWorkItemRelations(sourceWorkItem.Relations); if (sourceAttachmentWorkItemRelations.Any()) { foreach (WorkItemRelation sourceAttachmentWorkItemRelation in sourceAttachmentWorkItemRelations) { WorkItemRelation targetAttachmentRelation = GetAttachmentIfExistsOnTarget(targetWorkItem, sourceAttachmentWorkItemRelation); if (targetAttachmentRelation != null) // is on target { JsonPatchOperation attachmentAddOperation = MigrationHelpers.GetRelationAddOperation(targetAttachmentRelation); jsonPatchOperations.Add(attachmentAddOperation); } else // is not on target { AttachmentLink attachmentLink = await UploadAttachmentFromSourceRelation(migrationContext, batchContext, sourceWorkItem, sourceAttachmentWorkItemRelation, migrationContext.Config.MaxAttachmentSize); if (attachmentLink != null) { WorkItemRelation newAttachmentWorkItemRelation = new WorkItemRelation(); newAttachmentWorkItemRelation.Rel = sourceAttachmentWorkItemRelation.Rel; newAttachmentWorkItemRelation.Url = attachmentLink.AttachmentReference.Url; newAttachmentWorkItemRelation.Attributes = new Dictionary <string, object>(); newAttachmentWorkItemRelation.Attributes[Constants.RelationAttributeName] = attachmentLink.FileName; newAttachmentWorkItemRelation.Attributes[Constants.RelationAttributeResourceSize] = attachmentLink.ResourceSize; newAttachmentWorkItemRelation.Attributes[Constants.RelationAttributeComment] = attachmentLink.Comment; JsonPatchOperation attachmentAddOperation = MigrationHelpers.GetRelationAddOperation(newAttachmentWorkItemRelation); jsonPatchOperations.Add(attachmentAddOperation); } } } } return(jsonPatchOperations); }
public async Task <IEnumerable <JsonPatchOperation> > Process(IMigrationContext migrationContext, IBatchMigrationContext batchContext, WorkItem sourceWorkItem, WorkItem targetWorkItem) { IList <JsonPatchOperation> jsonPatchOperations = new List <JsonPatchOperation>(); IEnumerable <WorkItemRelation> sourceGitCommitLinksRelations = GetGitLinksRelationsFromWorkItem(sourceWorkItem, Constants.RelationArtifactLink, migrationContext.Config.SourceConnection.Account); if (sourceGitCommitLinksRelations.Any()) { foreach (WorkItemRelation sourceGitCommitLinkRelation in sourceGitCommitLinksRelations) { string adjustedUrl = ConvertGitCommitLinkToHyperLink(sourceWorkItem.Id.Value, sourceGitCommitLinkRelation.Url, migrationContext.Config.SourceConnection.Account); WorkItemRelation targetGitCommitHyperlinkRelation = GetGitCommitHyperlinkIfExistsOnTarget(targetWorkItem, adjustedUrl); if (targetGitCommitHyperlinkRelation != null) // is on target { JsonPatchOperation gitCommitHyperlinkAddOperation = MigrationHelpers.GetRelationAddOperation(targetGitCommitHyperlinkRelation); jsonPatchOperations.Add(gitCommitHyperlinkAddOperation); } else // is not on target { string comment = string.Empty; if (sourceGitCommitLinkRelation.Attributes.ContainsKey(Constants.RelationAttributeComment)) { comment = $"{sourceGitCommitLinkRelation.Attributes[Constants.RelationAttributeComment]}"; } string adjustedComment = $"{Constants.RelationAttributeGitCommitCommentValue}{comment}"; WorkItemRelation newGitCommitLinkRelation = new WorkItemRelation(); newGitCommitLinkRelation.Rel = Constants.Hyperlink; newGitCommitLinkRelation.Url = adjustedUrl; newGitCommitLinkRelation.Attributes = new Dictionary <string, object>(); newGitCommitLinkRelation.Attributes[Constants.RelationAttributeComment] = adjustedComment; JsonPatchOperation gitCommitHyperlinkAddOperation = MigrationHelpers.GetRelationAddOperation(newGitCommitLinkRelation); jsonPatchOperations.Add(gitCommitHyperlinkAddOperation); } } } return(jsonPatchOperations); }
public async Task Process(IBatchMigrationContext batchContext) { object identityObject = null; string identityValue = null; HashSet <string> identitiesToProcess = new HashSet <string>(StringComparer.CurrentCultureIgnoreCase); foreach (var sourceWorkItem in batchContext.SourceWorkItems) { foreach (var field in context.IdentityFields) { if (sourceWorkItem.Fields.TryGetValueIgnoringCase(field, out identityObject)) { identityValue = (string)identityObject; if (!string.IsNullOrEmpty(identityValue) && identityValue.Contains("<") && identityValue.Contains(">") && (identityValue.Contains("@"))) { // parse out email address from the combo string identityValue = identityValue.Substring(identityValue.LastIndexOf("<") + 1, identityValue.LastIndexOf(">") - identityValue.LastIndexOf("<") - 1); if (!identitiesToProcess.Contains(identityValue) && !this.context.ValidatedIdentities.Contains(identityValue) && !this.context.InvalidIdentities.Contains(identityValue)) { Logger.LogTrace(LogDestination.File, $"Found identity {identityValue} in batch {batchContext.BatchId} which has not yet been validated for the target account"); identitiesToProcess.Add(identityValue); } } } } } Logger.LogTrace(LogDestination.File, $"Adding {identitiesToProcess.Count} identities to the account for batch {batchContext.BatchId}"); foreach (var identity in identitiesToProcess) { try { var createUserResult = await RetryHelper.RetryAsync(async() => { return(await graphClient.CreateUserAsync(new GraphUserPrincipalNameCreationContext() { PrincipalName = identity })); }, 5); // using identity from createUserResult since the identity could be in a mangled format that ReadIdentities does not support var identities = await RetryHelper.RetryAsync(async() => { return(await identityHttpClient.ReadIdentitiesAsync(IdentitySearchFilter.MailAddress, createUserResult.MailAddress)); }, 5); if (identities.Count == 0) { Logger.LogWarning(LogDestination.File, $"Unable to add identity {identity} to the target account for batch {batchContext.BatchId}"); context.InvalidIdentities.Add(identity); } else { var assignResult = await RetryHelper.RetryAsync(async() => { return(await licensingHttpClient.AssignAvailableEntitlementAsync(identities[0].Id, dontNotifyUser: true)); }, 5); context.ValidatedIdentities.Add(identity); } } catch (Exception ex) { Logger.LogWarning(LogDestination.File, ex, $"Unable to add identity {identity} to the target account for batch {batchContext.BatchId}"); context.InvalidIdentities.Add(identity); } } Logger.LogTrace(LogDestination.File, $"Completed adding {identitiesToProcess.Count} identities to the account for batch {batchContext.BatchId}"); }
public async Task <IEnumerable <JsonPatchOperation> > Process(IMigrationContext migrationContext, IBatchMigrationContext batchContext, WorkItem sourceWorkItem, WorkItem targetWorkItem) { var jsonPatchOperations = new List <JsonPatchOperation>(); var attachments = await UploadAttachmentsToTarget(migrationContext, sourceWorkItem); foreach (var attachment in attachments) { JsonPatchOperation revisionHistoryAttachmentAddOperation = MigrationHelpers.GetRevisionHistoryAttachmentAddOperation(attachment, sourceWorkItem.Id.Value); jsonPatchOperations.Add(revisionHistoryAttachmentAddOperation); } return(jsonPatchOperations); }
public async Task <IEnumerable <JsonPatchOperation> > Process(IMigrationContext migrationContext, IBatchMigrationContext batchContext, WorkItem sourceWorkItem, WorkItem targetWorkItem) { IList <JsonPatchOperation> jsonPatchOperations = new List <JsonPatchOperation>(); IEnumerable <WorkItemRelation> sourceRemoteLinks = sourceWorkItem.Relations?.Where(r => RelationHelpers.IsRemoteLinkType(migrationContext, r.Rel)); if (sourceRemoteLinks != null && sourceRemoteLinks.Any()) { foreach (WorkItemRelation sourceRemoteLink in sourceRemoteLinks) { string url = ConvertRemoteLinkToHyperlink(sourceRemoteLink.Url); WorkItemRelation targetRemoteLinkHyperlinkRelation = GetHyperlinkIfExistsOnTarget(targetWorkItem, url); if (targetRemoteLinkHyperlinkRelation != null) // is on target { JsonPatchOperation remoteLinkHyperlinkAddOperation = MigrationHelpers.GetRelationAddOperation(targetRemoteLinkHyperlinkRelation); jsonPatchOperations.Add(remoteLinkHyperlinkAddOperation); } else // is not on target { string comment = string.Empty; if (sourceRemoteLink.Attributes.ContainsKey(Constants.RelationAttributeComment)) { comment = $"{sourceRemoteLink.Attributes[Constants.RelationAttributeComment]}"; } WorkItemRelation newRemoteLinkHyperlinkRelation = new WorkItemRelation(); newRemoteLinkHyperlinkRelation.Rel = Constants.Hyperlink; newRemoteLinkHyperlinkRelation.Url = url; newRemoteLinkHyperlinkRelation.Attributes = new Dictionary <string, object>(); newRemoteLinkHyperlinkRelation.Attributes[Constants.RelationAttributeComment] = comment; JsonPatchOperation remoteLinkHyperlinkAddOperation = MigrationHelpers.GetRelationAddOperation(newRemoteLinkHyperlinkRelation); jsonPatchOperations.Add(remoteLinkHyperlinkAddOperation); } } } return(jsonPatchOperations); }
/// <summary> /// Populates batchContext.WorkItems /// </summary> /// <param name="migrationContext"></param> /// <param name="workItemIds"></param> /// <param name="batchContext"></param> /// <param name="expand"></param> /// <returns></returns> public static async Task ReadSourceWorkItems(IMigrationContext migrationContext, IEnumerable <int> workItemIds, IBatchMigrationContext batchContext, WorkItemExpand?expand = WorkItemExpand.All) { batchContext.SourceWorkItems = await WorkItemTrackingHelpers.GetWorkItemsAsync(migrationContext.SourceClient.WorkItemTrackingHttpClient, workItemIds, expand : expand); }
private async Task <AttachmentLink> UploadAttachmentFromSourceRelation(IMigrationContext migrationContext, IBatchMigrationContext batchContext, WorkItem sourceWorkItem, WorkItemRelation sourceRelation, int maxAttachmentSize) { //Attachments are of type Rel = "AttachedFile" if (sourceRelation.Rel == Constants.AttachedFile) { string filename = null; string comment = null; long resourceSize = 0; //get the file name and comment if (sourceRelation.Attributes.ContainsKey(Constants.RelationAttributeName)) { filename = sourceRelation.Attributes[Constants.RelationAttributeName].ToString(); } if (sourceRelation.Attributes.ContainsKey(Constants.RelationAttributeComment)) { comment = sourceRelation.Attributes[Constants.RelationAttributeComment].ToString(); } //get the guid from the url Guid attachmentId; if (Guid.TryParse(sourceRelation.Url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Last(), out attachmentId)) { Stream stream = null; try { Logger.LogTrace(LogDestination.File, $"Reading attachment {filename} for source work item {sourceWorkItem.Id} from the source account"); stream = await WorkItemTrackingHelpers.GetAttachmentAsync(migrationContext.SourceClient.WorkItemTrackingHttpClient, attachmentId); Logger.LogTrace(LogDestination.File, $"Completed reading attachment {filename} for source work item {sourceWorkItem.Id} from the source account"); } catch (Exception e) { Logger.LogError(LogDestination.File, e, $"Unable to download attachment {filename} for source work item {sourceWorkItem.Id} from the source account"); ClientHelpers.AddFailureReasonToWorkItemMigrationState(sourceWorkItem.Id.Value, FailureReason.AttachmentDownloadError, batchContext.WorkItemMigrationState); return(null); } AttachmentReference aRef = null; using (MemoryStream memstream = new MemoryStream()) { using (stream) { try { Logger.LogTrace(LogDestination.File, $"Downloading attachment {filename} for source work item {sourceWorkItem.Id} from the source account"); await ClientHelpers.CopyStreamAsync(stream, memstream); Logger.LogTrace(LogDestination.File, $"Completed downloading attachment {filename} for source work item {sourceWorkItem.Id} from the source account"); } catch (Exception e) { Logger.LogError(LogDestination.File, e, $"Unable to read downloaded attachment {filename} for source work item {sourceWorkItem.Id} from the source account"); ClientHelpers.AddFailureReasonToWorkItemMigrationState(sourceWorkItem.Id.Value, FailureReason.AttachmentDownloadError, batchContext.WorkItemMigrationState); return(null); } } resourceSize = memstream.Length; if (resourceSize > maxAttachmentSize) { Logger.LogWarning(LogDestination.File, $"Attachment of source work item with id {sourceWorkItem.Id} and url {sourceRelation.Url} exceeded the maximum attachment size of {maxAttachmentSize} bytes." + $" Skipping creating the attachment in target account."); return(null); } memstream.Position = 0; //upload the attachment to the target try { Logger.LogTrace(LogDestination.File, $"Uploading attachment {filename} of {resourceSize} bytes for source work item {sourceWorkItem.Id} from the source account"); aRef = await WorkItemTrackingHelpers.CreateAttachmentChunkedAsync(migrationContext.TargetClient.WorkItemTrackingHttpClient, migrationContext.TargetClient.Connection, memstream, migrationContext.Config.AttachmentUploadChunkSize); Logger.LogTrace(LogDestination.File, $"Completed uploading attachment {filename} for source work item {sourceWorkItem.Id} from the source account"); } catch (Exception e) { Logger.LogError(LogDestination.File, e, $"Unable to upload attachment {filename} for source work item {sourceWorkItem.Id} to the target account"); ClientHelpers.AddFailureReasonToWorkItemMigrationState(sourceWorkItem.Id.Value, FailureReason.AttachmentUploadError, batchContext.WorkItemMigrationState); } } if (aRef != null) { return(new AttachmentLink(filename, aRef, resourceSize, comment)); } } else { Logger.LogError(LogDestination.File, $"Attachment link is incorrect for {sourceWorkItem.Id} {sourceRelation.Url}. Skipping creating the attachment in target account."); ClientHelpers.AddFailureReasonToWorkItemMigrationState(sourceWorkItem.Id.Value, FailureReason.AttachmentUploadError, batchContext.WorkItemMigrationState); } } return(null); }