Skip to main content
4 of 4
edited title
BCdotWEB
  • 11.4k
  • 2
  • 28
  • 45

Export and import work items from Azure DevOps

I'm working on an application that exports and imports work items from Azure DevOps. To import work items into Azure DevOps, I have written the following code.

Would you be able to review and make suggestions? I'm unsure whether I've used the Async-Await pattern correctly.

namespace Export_Import_AzureDevOps.Factory
{
    public class ImportFactory : IImportFactory
    {
        private ConcurrentDictionary<int, int> idMapper = new ConcurrentDictionary<int, int>();
        private readonly ILogger<ImportFactory> _logger;
        private readonly Devops _devopsConfiguration;
        private readonly IImportService<WorkItemCore> _importWorkItemService;
        private readonly IImportService<SprintCore> _importSprintService;

        public ImportFactory(ILogger<ImportFactory> logger, IConfiguration configuration, IImportService<SprintCore> importSprintService, IImportService<WorkItemCore> importWorkItemService)
        {
            _logger = logger;
            _devopsConfiguration = configuration.GetSection(nameof(Devops)).Get<Devops>();
            _importSprintService = importSprintService;
            _importWorkItemService = importWorkItemService;
        }

        private void PublishSprints(string id)
        {
            var sprintPublishURL = $"{_devopsConfiguration.Target.ProjectId}/{_devopsConfiguration.Target.TargetTeamId}/_apis/work/teamsettings/iterations?api-version={_devopsConfiguration.APIVersion}";

            var jsonPublishSprintString = $@"{{""id"":""{id}""}}";

            HttpContent publishSprintContent = new StringContent(jsonPublishSprintString, Encoding.UTF8, "application/json");

            _importSprintService.Post(sprintPublishURL, publishSprintContent).ConfigureAwait(false);
        }

        private Task CreateSprints(Sprints sprints)
        {
            var sprintCreationURL = $"{_devopsConfiguration.Target.ProjectId}/_apis/wit/classificationNodes/Iterations?api-version={_devopsConfiguration.APIVersion}";

            foreach (Sprint sprint in sprints.value)
            {
                var jsonString = JsonConvert.SerializeObject(sprint);
                HttpContent content = new StringContent(jsonString, Encoding.UTF8, "application/json");

                var result = _importWorkItemService.Post(sprintCreationURL, content).Result;

                PublishSprints(result.identifier);
            }

            return Task.CompletedTask;
        }

        private int GetCurrentIdEquivalentId(int oldId)
        {
            return idMapper[oldId];
        }

        private void TryAddIdMapper(int oldId, int newId)
        {
            if (!idMapper.ContainsKey(oldId))
            {
                idMapper.TryAdd(oldId, newId);
            }
        }

        private int FindParentId(WorkItemDetails workItemDetails)
        {
            var parentId = 0;

            if (workItemDetails.relations != null)
            {
                var parentRelation = workItemDetails.relations.Where(relation => relation.attributes.name.Equals("Parent")).FirstOrDefault();

                if (parentRelation != null)
                {
                    parentId = Int32.Parse(parentRelation.url.Split("/")[parentRelation.url.Split("/").Length - 1]);
                }
            }

            return parentId;
        }

        private void CreateWorkItem(string url, WorkItem workItem)
        {
            List<WorkItemOperation> operations = new List<WorkItemOperation>();
            var parentId = FindParentId(workItem.details);

            operations.Add(
                    new WorkItemOperation()
                    {
                        path = "/fields/System.Title",
                        value = workItem.details.fields.Title ?? string.Empty
                    }
                );
            operations.Add(
                    new WorkItemOperation()
                    {
                        path = "/fields/System.Description",
                        value = workItem.details.fields.Description ?? string.Empty
                    }
                );
            operations.Add(
                    new WorkItemOperation()
                    {
                        path = "/fields/Microsoft.VSTS.Common.AcceptanceCriteria",
                        value = workItem.details.fields.AcceptanceCriteria ?? string.Empty
                    }
                );
            operations.Add(
                    new WorkItemOperation()
                    {
                        path = "/fields/System.IterationPath",
                        value = workItem.details.fields.IterationPath.Replace(_devopsConfiguration.Source.ProjectName, _devopsConfiguration.Target.ProjectName)
                    }
                );

            if (parentId != 0)
            {
                operations.Add(
                        new WorkItemOperation()
                        {
                            path = "/relations/-",
                            value = new Relationship()
                            {
                                url = $"{_devopsConfiguration.Target.DevOpsOrgURL}{_devopsConfiguration.Target.ProjectId}/_apis/wit/workitems/{GetCurrentIdEquivalentId(parentId)}",
                                attributes = new RelationshipAttribute()
                            }
                        }
                    );
            }

            var jsonString = JsonConvert.SerializeObject(operations);
            HttpContent content = new StringContent(jsonString, Encoding.UTF8, "application/json-patch+json");

            var result = _importWorkItemService.Post(url, content).Result;

            TryAddIdMapper(workItem.id, result.id);
        }

        private Task CreateWorkItems(Dictionary<string, WorkItemQueryResult> workItemCollection)
        {
            foreach (var workItemCategory in workItemCollection.Keys)
            {
                var url = $"{_devopsConfiguration.Target.ProjectId}/_apis/wit/workitems/%24{workItemCategory}?api-version={_devopsConfiguration.APIVersion}";

                foreach (var workItem in workItemCollection[workItemCategory].workItems)
                {
                    CreateWorkItem(url, workItem);
                }
            }

            return Task.CompletedTask;
        }

        public async Task<Board> Import(IFormFile file)
        {
            string fileContent = null;

            using (var reader = new StreamReader(file.OpenReadStream()))
            {
                fileContent = reader.ReadToEnd();
            }
            var board = JsonConvert.DeserializeObject<Board>(fileContent);

            await CreateSprints(board.sprints);
            await CreateWorkItems(board.workItemCollection);

            return board;
        }
    }
}