Пример #1
0
        private async Task VerifyRelationTypesExistsOnTarget(IValidationContext context)
        {
            var sourceRelationTypes = await WorkItemTrackingHelpers.GetRelationTypesAsync(context.SourceClient.WorkItemTrackingHttpClient);

            var targetRelationTypes = await WorkItemTrackingHelpers.GetRelationTypesAsync(context.TargetClient.WorkItemTrackingHttpClient);

            foreach (var relationType in sourceRelationTypes)
            {
                //retrieve relations which are of type workitemlink defined by attribute kvp {"usage", "workitemlink"}
                //exclude remote link types because they need to be converted into hyperlinks
                if (IsWorkItemLinkType(relationType))
                {
                    if (RelationHelpers.IsRemoteLinkType(relationType))
                    {
                        context.RemoteLinkRelationTypes.Add(relationType.ReferenceName);
                    }
                    else
                    {
                        if (TargetHasRelationType(relationType, targetRelationTypes))
                        {
                            context.ValidatedWorkItemLinkRelationTypes.Add(relationType.ReferenceName);
                        }
                        else
                        {
                            Logger.LogWarning(LogDestination.File, $"Target: Relation type {relationType.ReferenceName} does not exist");
                        }
                    }
                }
            }
        }
Пример #2
0
        private async Task PopulateWorkItemMigrationState()
        {
            //dictionary of target workitem id to source id - these workitems have been migrated before
            var existingWorkItems    = ValidationContext.WorkItemsMigrationState.Where(wi => wi.MigrationState == WorkItemMigrationState.State.Existing);
            var totalNumberOfBatches = ClientHelpers.GetBatchCount(existingWorkItems.Count(), Constants.BatchSize);

            await existingWorkItems.Batch(Constants.BatchSize).ForEachAsync(ValidationContext.Config.Parallelism, async(batchWorkItemMigrationState, batchId) =>
            {
                var stopwatch = Stopwatch.StartNew();
                Logger.LogInformation(LogDestination.File, $"{Name} batch {batchId} of {totalNumberOfBatches}: Started");

                Dictionary <int, WorkItemMigrationState> targetToWorkItemMigrationState = batchWorkItemMigrationState.ToDictionary(k => k.TargetId.Value, v => v);

                //read the target work items
                IList <WorkItem> targetWorkItems = await WorkItemTrackingHelpers.GetWorkItemsAsync(ValidationContext.TargetClient.WorkItemTrackingHttpClient, batchWorkItemMigrationState.Select(a => a.TargetId.Value).ToList(), expand: WorkItemExpand.Relations);

                IDictionary <int, WorkItemRelation> targetIdToHyperlinkToSourceRelationMapping = GetTargetIdToHyperlinkToSourceRelationMapping(targetWorkItems, targetToWorkItemMigrationState);

                ProcessUpdatedSourceWorkItems(targetWorkItems, targetToWorkItemMigrationState, targetIdToHyperlinkToSourceRelationMapping);

                StoreWorkItemBatchRelationInformationOnContext(targetWorkItems, targetToWorkItemMigrationState, targetIdToHyperlinkToSourceRelationMapping);

                stopwatch.Stop();
                Logger.LogInformation(LogDestination.File, $"{Name} batch {batchId} of {totalNumberOfBatches}: Completed in {stopwatch.Elapsed.TotalSeconds}s");
            });
        }
Пример #3
0
        private async Task VerifyRelationTypesExistsOnTarget(IValidationContext context)
        {
            var sourceRelationTypes = await WorkItemTrackingHelpers.GetRelationTypesAsync(context.SourceClient.WorkItemTrackingHttpClient);

            var targetRelationTypes = await WorkItemTrackingHelpers.GetRelationTypesAsync(context.TargetClient.WorkItemTrackingHttpClient);

            var targetRelationNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            foreach (var relationType in sourceRelationTypes)
            {
                //retrieve relations which are of type workitemlink defined by attribute kvp {"usage", "workitemlink"}
                if (relationType.Attributes.ContainsKeyIgnoringCase(Constants.WorkItemLinkAttributeKey) &&
                    String.Equals(relationType.Attributes[Constants.WorkItemLinkAttributeKey].ToString(), Constants.WorkItemLinkAttributeValue, StringComparison.OrdinalIgnoreCase))
                {
                    if (TargetHasRelationType(relationType, targetRelationTypes))
                    {
                        context.ValidatedWorkItemLinkRelationTypes.Add(relationType.ReferenceName);
                    }
                    else
                    {
                        Logger.LogWarning(LogDestination.File, $"Target: Relation type {relationType.ReferenceName} does not exist");
                    }
                }
            }
        }
Пример #4
0
        /// <summary>
        /// Populates migrationContext.TargetAreaAndIterationTree, migrationContext.TargetAreaPaths, and migrationContext.TargetIterationPaths
        /// </summary>
        /// <param name="migrationContext"></param>
        /// <param name="projectId"></param>
        /// <returns></returns>
        public static async Task ReadTargetNodes(IMigrationContext migrationContext, string projectId)
        {
            var nodes = await WorkItemTrackingHelpers.GetClassificationNodes(migrationContext.TargetClient.WorkItemTrackingHttpClient, projectId);

            migrationContext.TargetAreaAndIterationTree = new AreaAndIterationPathTree(nodes);
            migrationContext.TargetAreaPaths            = migrationContext.TargetAreaAndIterationTree.AreaPathList;
            migrationContext.TargetIterationPaths       = migrationContext.TargetAreaAndIterationTree.IterationPathList;
        }
Пример #5
0
 public async virtual Task CreateIterationPath(string iterationPath, WorkItemClassificationNode sourceIterationNode)
 {
     await WorkItemTrackingHelpers.CreateIterationAsync(
         context.TargetClient.WorkItemTrackingHttpClient,
         context.Config.TargetConnection.Project,
         iterationPath,
         sourceIterationNode.Attributes == null?null : (DateTime?)sourceIterationNode.Attributes?["startDate"],
         sourceIterationNode.Attributes == null?null : (DateTime?)sourceIterationNode.Attributes["finishDate"]);
 }
Пример #6
0
        private async Task <RevisionHistoryAttachments> GetWorkItemUpdates(IMigrationContext migrationContext, WorkItem sourceWorkItem)
        {
            IList <RevisionHistoryAttachments> revisionHistoryAttachments = new List <RevisionHistoryAttachments>();
            var wiUpdates = await WorkItemTrackingHelpers.GetWorkItemUpdatesAsync(migrationContext.SourceClient.WorkItemTrackingHttpClient, sourceWorkItem.Id.Value);

            return(new RevisionHistoryAttachments {
                Workitem = sourceWorkItem, Updates = wiUpdates
            });
        }
Пример #7
0
        private async Task MigratePhase2()
        {
            Logger.LogInformation("Starting migration phase 2");

            IEnumerable <WorkItemMigrationState> successfulWorkItemMigrationStates;

            // when skip existing config flag is on and this work item was existing, continue to next work item.
            if (context.Config.SkipExisting)
            {
                successfulWorkItemMigrationStates = context.WorkItemsMigrationState.Where(a => a.MigrationState == WorkItemMigrationState.State.Create);
            }
            else
            {
                // allow any Create, OR Existing with UpdatePhase2
                successfulWorkItemMigrationStates = context.WorkItemsMigrationState.Where(a => a.MigrationState == WorkItemMigrationState.State.Create || (a.MigrationState == WorkItemMigrationState.State.Existing && a.Requirement.HasFlag(WorkItemMigrationState.RequirementForExisting.UpdatePhase2)));
            }

            var phase2WorkItemsToUpdateCount = successfulWorkItemMigrationStates.Count();
            var totalNumberOfBatches         = ClientHelpers.GetBatchCount(phase2WorkItemsToUpdateCount, Constants.BatchSize);

            if (phase2WorkItemsToUpdateCount == 0)
            {
                Logger.LogInformation(LogDestination.File, "No work items to process for phase 2");
                return;
            }

            await successfulWorkItemMigrationStates.Batch(Constants.BatchSize).ForEachAsync(context.Config.Parallelism, async(workItemMigrationStateBatch, batchId) =>
            {
                Logger.LogTrace(LogDestination.File, $"Reading Phase 2 source and target work items for batch {batchId} of {totalNumberOfBatches}");
                // make web call to get source and target work items
                IList <WorkItem> sourceWorkItemsInBatch = await WorkItemTrackingHelpers.GetWorkItemsAsync(context.SourceClient.WorkItemTrackingHttpClient, workItemMigrationStateBatch.Select(a => a.SourceId).ToList(), expand: WorkItemExpand.All);
                IList <WorkItem> targetWorkItemsInBatch = await WorkItemTrackingHelpers.GetWorkItemsAsync(context.TargetClient.WorkItemTrackingHttpClient, workItemMigrationStateBatch.Select(a => a.TargetId.Value).ToList(), expand: WorkItemExpand.Relations);

                IBatchMigrationContext batchContext = new BatchMigrationContext(batchId, workItemMigrationStateBatch);
                batchContext.SourceWorkItemIdToTargetWorkItemIdMapping = workItemMigrationStateBatch.ToDictionary(key => key.SourceId, value => value.TargetId.Value);

                foreach (var sourceWorkItem in sourceWorkItemsInBatch)
                {
                    int targetId = Migrator.GetTargetId(sourceWorkItem.Id.Value, workItemMigrationStateBatch);
                    batchContext.TargetIdToSourceWorkItemMapping.Add(targetId, sourceWorkItem);
                }

                Logger.LogTrace(LogDestination.File, $"Generating Phase 2 json patch operations for batch {batchId} of {totalNumberOfBatches}");
                var sourceIdToWitBatchRequests = await GenerateWitBatchRequestsForPhase2Batch(batchContext, batchId, workItemMigrationStateBatch, sourceWorkItemsInBatch, targetWorkItemsInBatch);

                Logger.LogTrace(LogDestination.File, $"Saving Phase 2 json patch operations for batch {batchId} of {totalNumberOfBatches}");

                var phase2ApiWrapper = new Phase2ApiWrapper();
                await phase2ApiWrapper.ExecuteWitBatchRequests(sourceIdToWitBatchRequests, context, batchContext);

                Logger.LogTrace(LogDestination.File, $"Completed Phase 2 for batch {batchId} of {totalNumberOfBatches}");
            });

            Logger.LogInformation("Completed migration phase 2");
        }
Пример #8
0
        public async static Task CheckConnection(WorkItemClientConnection client, string project, int requestedPermission)
        {
            Logger.LogInformation($"Checking security permissions for {client.Connection.AuthorizedIdentity.DisplayName} in {project}");
            bool hasPermission = false;

            SecurityHttpClient         securityHttpClient = null;
            WorkItemClassificationNode result             = null;

            try
            {
                securityHttpClient = client.Connection.GetClient <SecurityHttpClient>();
                result             = await WorkItemTrackingHelpers.GetClassificationNode(client.WorkItemTrackingHttpClient, project, TreeStructureGroup.Areas);
            }
            catch (Exception e) when(e.InnerException is VssUnauthorizedException)
            {
                throw new ValidationException(client.Connection.Uri.ToString(), (VssUnauthorizedException)e.InnerException);
            }
            catch (Exception e)
            {
                throw new ValidationException("An unexpected error occurred while reading the classification nodes to validate project permissions", e);
            }

            //construct the token by appending the id
            string token = $"vstfs:///Classification/Node/{result.Identifier}";

            //WORK_ITEM guid is hardcoded below
            //securityNameSpaceId for WORK_ITEM is 83e28ad4-2d72-4ceb-97b0-c7726d5502c3
            try
            {
                hasPermission = await securityHttpClient.HasPermissionAsync(
                    new Guid("83e28ad4-2d72-4ceb-97b0-c7726d5502c3"),
                    token,
                    requestedPermission,
                    false);
            }
            catch (Exception e)
            {
                throw new ValidationException($"An unexpected error occurred while trying to check permissions for project {project}", e);
            }

            if (hasPermission)
            {
                Logger.LogSuccess(LogDestination.All, $"Verified security permissions for {project} project");
            }
            else
            {
                throw new ValidationException($"You do not have the necessary security permissions for {project}, work item {(requestedPermission == WritePermission ? "write" : "read")} permissions are required.");
            }
        }
Пример #9
0
        private async static Task CheckPermission(WorkItemClientConnection client, string project, Guid securityNamespace, int requestedPermission)
        {
            Logger.LogInformation($"Checking security permissions for {client.Connection.AuthorizedIdentity.DisplayName} in {project}");
            bool hasPermission = false;

            SecurityHttpClient         securityHttpClient = null;
            WorkItemClassificationNode result             = null;

            try
            {
                securityHttpClient = client.Connection.GetClient <SecurityHttpClient>();
                result             = await WorkItemTrackingHelpers.GetClassificationNode(client.WorkItemTrackingHttpClient, project, TreeStructureGroup.Areas);
            }
            catch (Exception e) when(e.InnerException is VssUnauthorizedException)
            {
                throw new ValidationException(client.Connection.Uri.ToString(), (VssUnauthorizedException)e.InnerException);
            }
            catch (Exception e)
            {
                throw new ValidationException("An unexpected error occurred while reading the classification nodes to validate project permissions", e);
            }

            //construct the token by appending the id
            string token = $"vstfs:///Classification/Node/{result.Identifier}";

            try
            {
                hasPermission = await securityHttpClient.HasPermissionAsync(
                    securityNamespace,
                    token,
                    requestedPermission,
                    false);
            }
            catch (Exception e)
            {
                throw new ValidationException($"An unexpected error occurred while trying to check permissions for project {project} in namespace {securityNamespace}", e);
            }

            if (hasPermission)
            {
                Logger.LogSuccess(LogDestination.All, $"Verified security permissions for {project} project");
            }
            else
            {
                throw new ValidationException($"You do not have the necessary security permissions for {project}, work item permission: {requestedPermission} is required.");
            }
        }
Пример #10
0
        private async Task MigratePhase3()
        {
            IEnumerable <IPhase3Processor> phase3Processors = ClientHelpers.GetProcessorInstances <IPhase3Processor>(context.Config);

            if (phase3Processors != null && !phase3Processors.Any())
            {
                // nothing to do if no phase 3 processors are enabled
                return;
            }

            // Phase1 or Phase2 have completed, and FailureReason == None
            IEnumerable <WorkItemMigrationState> successfullyMigratedWorkItemMigrationStates = context.WorkItemsMigrationState.Where(w => (w.MigrationCompleted.HasFlag(WorkItemMigrationState.MigrationCompletionStatus.Phase1) || w.MigrationCompleted.HasFlag(WorkItemMigrationState.MigrationCompletionStatus.Phase2)) && w.FailureReason == FailureReason.None);
            var phase3WorkItemsToUpdateCount = successfullyMigratedWorkItemMigrationStates.Count();
            var totalNumberOfBatches         = ClientHelpers.GetBatchCount(phase3WorkItemsToUpdateCount, Constants.BatchSize);

            if (phase3WorkItemsToUpdateCount == 0)
            {
                return;
            }

            await successfullyMigratedWorkItemMigrationStates.Batch(Constants.BatchSize).ForEachAsync(context.Config.Parallelism, async(workItemMigrationStateBatch, batchId) =>
            {
                IBatchMigrationContext batchContext = new BatchMigrationContext(batchId, workItemMigrationStateBatch);
                IList <(int SourceId, WitBatchRequest WitBatchRequest)> sourceIdToWitBatchRequests = new List <(int SourceId, WitBatchRequest WitBatchRequest)>();
                IList <WorkItem> sourceWorkItemsInBatch = await WorkItemTrackingHelpers.GetWorkItemsAsync(context.SourceClient.WorkItemTrackingHttpClient, workItemMigrationStateBatch.Select(a => a.SourceId).ToList(), expand: WorkItemExpand.All);

                foreach (WorkItem sourceWorkItem in sourceWorkItemsInBatch)
                {
                    IList <JsonPatchOperation> jsonPatchOperations = new List <JsonPatchOperation>();
                    foreach (IPhase3Processor processor in phase3Processors)
                    {
                        IEnumerable <JsonPatchOperation> processorJsonPatchOperations = await processor.Process(context, null, sourceWorkItem, null);
                        jsonPatchOperations.AddRange(processorJsonPatchOperations);
                    }

                    if (jsonPatchOperations.Any())
                    {
                        WitBatchRequest witBatchRequest = GenerateWitBatchRequestFromJsonPatchOperations(jsonPatchOperations, sourceWorkItem.Id.Value);
                        sourceIdToWitBatchRequests.Add((sourceWorkItem.Id.Value, witBatchRequest));
                    }
                }

                var phase3ApiWrapper = new Phase3ApiWrapper();
                await phase3ApiWrapper.ExecuteWitBatchRequests(sourceIdToWitBatchRequests, context, batchContext);
            });
        }
        private async Task VerifyQueryExistsAndIsValid(IValidationContext context)
        {
            Logger.LogInformation(LogDestination.File, "Checking if the migration query exists in the source project");
            QueryHierarchyItem query;

            try
            {
                query = await WorkItemTrackingHelpers.GetQueryAsync(context.SourceClient.WorkItemTrackingHttpClient, context.Config.SourceConnection.Project, context.Config.Query);
            }
            catch (Exception e)
            {
                throw new ValidationException("Unable to read the migration query", e);
            }

            if (query.QueryType != QueryType.Flat)
            {
                throw new ValidationException("Only flat queries are supported for migration");
            }
        }
Пример #12
0
        private async Task <AttachmentReference> UploadAttachmentsToTarget(IMigrationContext migrationContext, WorkItem sourceWorkItem)
        {
            RevisionHistoryAttachments revisionHistoryAttachmentsItem = await GetWorkItemUpdates(migrationContext, sourceWorkItem);

            string attachment = JsonConvert.SerializeObject(revisionHistoryAttachmentsItem.Updates);
            AttachmentReference aRef;

            using (MemoryStream stream = new MemoryStream())
            {
                var stringBytes = System.Text.Encoding.UTF8.GetBytes(attachment);
                await stream.WriteAsync(stringBytes, 0, stringBytes.Length);

                stream.Position = 0;
                //upload the attachment to the target for each workitem
                aRef = await WorkItemTrackingHelpers.CreateAttachmentAsync(migrationContext.TargetClient.WorkItemTrackingHttpClient, stream);
            }

            return(aRef);
        }
        private async Task RunQuery(IValidationContext context)
        {
            Logger.LogInformation(LogDestination.File, "Running the migration query in the source project");

            try
            {
                var workItemUris = await WorkItemTrackingHelpers.GetWorkItemIdAndReferenceLinksAsync(
                    context.SourceClient.WorkItemTrackingHttpClient,
                    context.Config.SourceConnection.Project,
                    context.Config.Query,
                    context.Config.TargetPostMoveTag,
                    context.Config.QueryPageSize - 1 /* Have to subtract -1 from the page size due to a bug in how query interprets page size */);

                context.WorkItemIdsUris = new ConcurrentDictionary <int, string>(workItemUris);
            }
            catch (Exception e)
            {
                throw new ValidationException("Unable to run the migration query", e);
            }
        }
        public async Task Prepare(IValidationContext context)
        {
            context.RequestedFields.Add(FieldNames.AreaPath);
            context.RequestedFields.Add(FieldNames.IterationPath);

            Logger.LogInformation("Reading the area and iteration paths from the source and target accounts");

            try
            {
                var classificationNodes = await WorkItemTrackingHelpers.GetClassificationNodes(context.TargetClient.WorkItemTrackingHttpClient, context.Config.TargetConnection.Project);

                var nodes = new AreaAndIterationPathTree(classificationNodes);
                context.TargetAreaPaths      = nodes.AreaPathList;
                context.TargetIterationPaths = nodes.IterationPathList;
            }
            catch (Exception e)
            {
                throw new ValidationException("Unable to read the classification nodes on the source", e);
            }
        }
Пример #15
0
        private async Task <IList <AttachmentLink> > UploadAttachmentsToTarget(IMigrationContext migrationContext, WorkItem sourceWorkItem)
        {
            var attachmentLinks = new List <AttachmentLink>();
            int updateLimit     = migrationContext.Config.MoveHistoryLimit;
            int updateCount     = 0;

            while (updateCount < updateLimit)
            {
                var updates = await GetWorkItemUpdates(migrationContext, sourceWorkItem, skip : updateCount);

                string attachmentContent = JsonConvert.SerializeObject(updates);
                AttachmentReference attachmentReference;
                using (MemoryStream stream = new MemoryStream())
                {
                    var stringBytes = System.Text.Encoding.UTF8.GetBytes(attachmentContent);
                    await stream.WriteAsync(stringBytes, 0, stringBytes.Length);

                    stream.Position = 0;
                    //upload the attachment to the target for each batch of workitem updates
                    attachmentReference = await WorkItemTrackingHelpers.CreateAttachmentAsync(migrationContext.TargetClient.WorkItemTrackingHttpClient, stream);

                    attachmentLinks.Add(
                        new AttachmentLink(
                            $"{Constants.WorkItemHistory}-{sourceWorkItem.Id}-{updateCount}.json",
                            attachmentReference,
                            stringBytes.Length,
                            comment: $"Update range from {updateCount} to {updateCount + updates.Count}"));
                }

                updateCount += updates.Count;

                // if we got less than a page size, that means we're on the last
                // page and shouldn't try and read another page.
                if (updates.Count < Constants.PageSize)
                {
                    break;
                }
            }

            return(attachmentLinks);
        }
        private async Task ValidateWorkItemMetadata()
        {
            Logger.LogInformation("Starting work item metadata validation");

            var validators = ClientHelpers.GetInstances <IWorkItemValidator>();

            foreach (var validator in validators)
            {
                await validator.Prepare(context);
            }

            var totalNumberOfBatches = ClientHelpers.GetBatchCount(context.WorkItemIdsUris.Count, Constants.BatchSize);

            await context.WorkItemIdsUris.Keys.Batch(Constants.BatchSize).ForEachAsync(context.Config.Parallelism, async(workItemIds, batchId) =>
            {
                var stopwatch = Stopwatch.StartNew();
                Logger.LogInformation(LogDestination.File, $"Work item metadata validation batch {batchId} of {totalNumberOfBatches}: Starting");

                var workItems = await WorkItemTrackingHelpers.GetWorkItemsAsync(
                    context.SourceClient.WorkItemTrackingHttpClient,
                    workItemIds,
                    context.RequestedFields);

                foreach (var validator in validators)
                {
                    Logger.LogInformation(LogDestination.File, $"Work item metadata validation batch {batchId} of {totalNumberOfBatches}: {validator.Name}");
                    foreach (var workItem in workItems)
                    {
                        await validator.Validate(context, workItem);
                    }
                }

                stopwatch.Stop();

                Logger.LogInformation(LogDestination.File, $"Work item metadata validation batch {batchId} of {totalNumberOfBatches}: Completed in {stopwatch.Elapsed.TotalSeconds}s");
            });

            Logger.LogInformation("Completed work item metadata validation");
        }
        private async static Task CheckCssPermission(WorkItemClientConnection client, string project, int requestedPermission)
        {
            Logger.LogInformation($"Checking css security permissions for {client.Connection.AuthorizedIdentity.DisplayName} in {project}");

            SecurityHttpClient         securityHttpClient = null;
            WorkItemClassificationNode result             = null;

            try
            {
                securityHttpClient = client.Connection.GetClient <SecurityHttpClient>();
                result             = await WorkItemTrackingHelpers.GetClassificationNode(client.WorkItemTrackingHttpClient, project, TreeStructureGroup.Areas);
            }
            catch (Exception e) when(e.InnerException is VssUnauthorizedException)
            {
                throw new ValidationException(client.Connection.Uri.ToString(), (VssUnauthorizedException)e.InnerException);
            }
            catch (Exception e)
            {
                throw new ValidationException("An unexpected error occurred while reading the classification nodes to validate project permissions", e);
            }

            await HasPermission(securityHttpClient, project, $"vstfs:///Classification/Node/{result.Identifier}", CssSecurityNamespace, requestedPermission);
        }
Пример #18
0
        public void ParseQueryForPaging_InjectsPostMoveTagWithWhereClause()
        {
            var query = WorkItemTrackingHelpers.ParseQueryForPaging("SELECT * FROM WorkItems WHERE System.Id = 1 order BY System.Id", "Migrated");

            Assert.AreEqual("SELECT * FROM WorkItems WHERE (System.Id = 1) AND System.Tags NOT CONTAINS 'Migrated'", query);
        }
Пример #19
0
        public void GetPageableQuery_NoWhereClause()
        {
            var query = WorkItemTrackingHelpers.GetPageableQuery("SELECT * FROM WorkItems", 1, 1);

            Assert.AreEqual("SELECT * FROM WorkItems WHERE ((System.Watermark > 1) OR (System.Watermark = 1 AND System.Id > 1)) ORDER BY System.Watermark, System.Id", query);
        }
Пример #20
0
 /// <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);
 }
Пример #21
0
        /// <summary>
        /// Populates migrationContext.SourceAreaAndIterationTree
        /// </summary>
        /// <param name="migrationContext"></param>
        /// <param name="projectId"></param>
        /// <returns></returns>
        public static async Task ReadSourceNodes(IMigrationContext migrationContext, string projectId)
        {
            var nodes = await WorkItemTrackingHelpers.GetClassificationNodes(migrationContext.SourceClient.WorkItemTrackingHttpClient, projectId);

            migrationContext.SourceAreaAndIterationTree = new AreaAndIterationPathTree(nodes);
        }
Пример #22
0
 private async Task <IList <WorkItemUpdate> > GetWorkItemUpdates(IMigrationContext migrationContext, WorkItem sourceWorkItem, int skip = 0)
 {
     return(await WorkItemTrackingHelpers.GetWorkItemUpdatesAsync(migrationContext.SourceClient.WorkItemTrackingHttpClient, sourceWorkItem.Id.Value, skip));
 }
Пример #23
0
        public void ParseQueryForPaging_StripsOrderByClause()
        {
            var query = WorkItemTrackingHelpers.ParseQueryForPaging("SELECT * FROM WorkItems order BY System.Id", null);

            Assert.AreEqual("SELECT * FROM WorkItems", query);
        }
        public async Task Prepare(IValidationContext context)
        {
            Logger.LogInformation("Reading the work item types from the source and target accounts");

            // When reading the work item, we need the type field for this validator
            context.RequestedFields.Add(FieldNames.WorkItemType);

            try
            {
                // We need all fields to validate the field types
                var sourceFields = (await WorkItemTrackingHelpers.GetFields(context.SourceClient.WorkItemTrackingHttpClient)).ToDictionary(key => key.ReferenceName);
                context.SourceFields = new ConcurrentDictionary <string, WorkItemField>(sourceFields, StringComparer.OrdinalIgnoreCase);
            }
            catch (Exception e)
            {
                throw new ValidationException("Unable to read fields on the source", e);
            }

            try
            {
                // We need all fields to validate the field types
                var targetFields = (await WorkItemTrackingHelpers.GetFields(context.TargetClient.WorkItemTrackingHttpClient)).ToDictionary(key => key.ReferenceName);
                context.TargetFields   = new ConcurrentDictionary <string, WorkItemField>(targetFields, StringComparer.OrdinalIgnoreCase);
                context.IdentityFields = new HashSet <string>(targetFields.Where(f => f.Value.IsIdentity).Select(f => f.Key), StringComparer.OrdinalIgnoreCase);
            }
            catch (Exception e)
            {
                throw new ValidationException("Unable to read fields on the target", e);
            }

            // handle condition of activated/not activated:
            ValidateFieldsMapping(context);

            try
            {
                var workItemTypes = await WorkItemTrackingHelpers.GetWorkItemTypes(context.SourceClient.WorkItemTrackingHttpClient, context.Config.SourceConnection.Project);

                foreach (var workItemType in workItemTypes)
                {
                    context.SourceTypesAndFields[workItemType.Name] = new HashSet <string>(workItemType.Fields.Select(f => f.ReferenceName), StringComparer.OrdinalIgnoreCase);
                }
            }
            catch (Exception e)
            {
                throw new ValidationException("Unable to read work item types on the source", e);
            }

            try
            {
                var workItemTypes = await WorkItemTrackingHelpers.GetWorkItemTypes(context.TargetClient.WorkItemTrackingHttpClient, context.Config.TargetConnection.Project);

                foreach (var workItemType in workItemTypes)
                {
                    context.TargetTypesAndFields[workItemType.Name] = new HashSet <string>(workItemType.Fields.Select(f => f.ReferenceName), StringComparer.OrdinalIgnoreCase);
                }
            }
            catch (Exception e)
            {
                throw new ValidationException("Unable to read work item types on the target", e);
            }

            // A very edge case, but we should fail this scenario.
            if (context.SourceTypesAndFields.Count == 0 || context.TargetTypesAndFields.Count == 0)
            {
                throw new ValidationException("Source or target does not have any work item types");
            }
        }
Пример #25
0
        public void ParseQueryForPaging_NoOrderByClause()
        {
            var parsedQuery = WorkItemTrackingHelpers.ParseQueryForPaging("SELECT * FROM WorkItems", null);

            Assert.AreEqual("SELECT * FROM WorkItems", parsedQuery);
        }
Пример #26
0
 public async virtual Task CreateAreaPath(string areaPathInTarget)
 {
     await WorkItemTrackingHelpers.CreateAreaPathAsync(context.TargetClient.WorkItemTrackingHttpClient, context.Config.TargetConnection.Project, areaPathInTarget);
 }
        /// <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);
        }
        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);
        }