public async Task Create(AzureDevOpsWorkItem azureDevOpsWorkItem, CancellationToken cancellationToken)
        {
            azureDevOpsWorkItem.Files ??= new File[0];

            var attachments = new List <AttachmentReference>(azureDevOpsWorkItem.Files.Length);

            foreach (var file in azureDevOpsWorkItem.Files)
            {
                if (file.Data.CanSeek)
                {
                    file.Data.Seek(0, SeekOrigin.Begin);
                }

                var requestAttachment = await WorkItemTrackingHttpClient.CreateAttachmentAsync(
                    file.Data,
                    fileName : file.Name,
                    project : azureDevOpsWorkItem.Project,
                    cancellationToken : cancellationToken);

                attachments.Add(requestAttachment);
                await file.Data.DisposeAsync();
            }

            var jsonPatchDocument = new JsonPatchDocument();

            jsonPatchDocument.AddRange(azureDevOpsWorkItem.GetFields().Select(x => new JsonPatchOperation
            {
                Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                Path      = x.field,
                From      = null,
                Value     = x.value
            }));

            jsonPatchDocument.AddRange(attachments.Select(x => new JsonPatchOperation
            {
                Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                Path      = "/relations/-",
                From      = null,
                Value     = new
                {
                    Rel = "AttachedFile",
                    x.Url
                }
            }));
            Options.Value.OnBeforeSend?.Invoke(ServiceProvider, azureDevOpsWorkItem);

            var workItem = await WorkItemTrackingHttpClient.CreateWorkItemAsync(jsonPatchDocument, azureDevOpsWorkItem.Project, azureDevOpsWorkItem.Type, cancellationToken : cancellationToken);

            azureDevOpsWorkItem.Url = new Uri(workItem.Url);

            Options.Value.OnAfterSend?.Invoke(ServiceProvider, azureDevOpsWorkItem);
        }
        public static JsonPatchDocument ToJsonPatchDocument(this IEnumerable <JsonPatchItem> valuePair)
        {
            JsonPatchDocument jsonPatchDocument = new JsonPatchDocument();

            jsonPatchDocument.AddRange(valuePair.Select(item => new JsonPatchOperation()
            {
                Operation = Operation.Add,
                Path      = item.Path,
                Value     = item.Value
            }));

            return(jsonPatchDocument);
        }
        public JsonPatchDocument ToJsonPatchDocument()
        {
            _jsonPatchDocument.AddRange(
                from property in this.GetType().GetProperties()
                let attributePath = property.GetFieldPath()
                                    where attributePath != null
                                    let propertyValue = property.GetPropertyValue(this)
                                                        where propertyValue != null
                                                        select new JsonPatchOperation
            {
                Operation = Operation.Add,
                Path      = attributePath,
                Value     = propertyValue
            });

            return(_jsonPatchDocument);
        }
Ejemplo n.º 4
0
        private async Task InternalRemoveRelationshipAsync(WorkItem owner, IEnumerable <WorkItem> relatedItems, CancellationToken cancellationToken)
        {
            if (owner is null)
            {
                throw new ArgumentNullException(nameof(owner));
            }
            if (owner.IsNew)
            {
                throw new ArgumentException("Cannot remove related items from new WorkItem.", nameof(owner));
            }
            if (relatedItems?.Any(wi => wi.IsNew) == true)
            {
                throw new ArgumentException("Cannot remove NEW related items from a WorkItem.", nameof(owner));
            }

            var operations = new List <JsonPatchOperation>();
            var items      = (await InternalGetRelatedIdsAsync(owner.Id.Value, null, asOf: null, cancellationToken))
                             .Where(rid => rid.RelatedId.HasValue).Select(rid => rid.RelatedId.Value).ToList();

            foreach (var item in relatedItems)
            {
                int index = items.IndexOf(item.Id.Value);
                if (index >= 0)
                {
                    operations.Add(new JsonPatchOperation
                    {
                        Operation = Operation.Remove,
                        Path      = $"/relations/{index}"
                    });
                }
            }

            if (operations.Count > 0)
            {
                var patchDocument = new JsonPatchDocument();
                patchDocument.AddRange(operations);
                var wit = await UpdateAsync(owner.Id.Value, patchDocument, validateOnly : false, bypassRules : false,
                                            supressNotifications : null, WorkItemExpand.None, cancellationToken)
                          .ConfigureAwait(false);

                log?.WriteLine(LogLevel.PatchDocument, $"WorkItem {owner.Id} updated from Rev {owner.Revision} to {wit.Rev}");
                owner.Initialize(wit);
            }
        }
Ejemplo n.º 5
0
        // Patch changed items
        public Task PatchItems(List <WorkItem> items)
        {
            var requests = new List <Task <Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem> >();


            items.ForEach(item =>
            {
                if (item.hours > 0)
                {
                    var estimate  = float.Parse(item.trackerWorkitem.Fields["Microsoft.VSTS.CMMI.Estimate"].ToString());
                    var remaining = float.Parse(item.trackerWorkitem.Fields["Microsoft.VSTS.Scheduling.RemainingWork"].ToString());
                    var completed = float.Parse(item.trackerWorkitem.Fields["Microsoft.VSTS.Scheduling.CompletedWork"].ToString());

                    JsonPatchDocument patchDocument = new JsonPatchDocument();
                    patchDocument.AddRange(new List <JsonPatchOperation>()
                    {
                        new JsonPatchOperation()
                        {
                            Operation = Operation.Add,
                            Path      = "/fields/Microsoft.VSTS.Scheduling.RemainingWork",
                            Value     = remaining - item.hours
                        },
                        new JsonPatchOperation()
                        {
                            Operation = Operation.Add,
                            Path      = "/fields/Microsoft.VSTS.Scheduling.CompletedWork",
                            Value     = completed + item.hours
                        }
                    });
                    requests.Add(wiClient.UpdateWorkItemAsync(patchDocument, item.Id));

                    item.trackerWorkitem.Fields["Microsoft.VSTS.Scheduling.RemainingWork"] = remaining - item.hours;
                    item.trackerWorkitem.Fields["Microsoft.VSTS.Scheduling.CompletedWork"] = completed + item.hours;
                }
            });
            return(Task.WhenAll(requests));
        }
Ejemplo n.º 6
0
        private WitBatchRequest GenerateWitBatchRequestFromJsonPatchOperations(IList <JsonPatchOperation> jsonPatchOperations, int workItemId)
        {
            Dictionary <string, string> headers = new Dictionary <string, string>();

            headers.Add("Content-Type", "application/json-patch+json");

            JsonPatchDocument jsonPatchDocument = new JsonPatchDocument();

            jsonPatchDocument.AddRange(jsonPatchOperations);

            WitBatchRequest witBatchRequest = null;

            if (jsonPatchDocument.Any())
            {
                witBatchRequest = new WitBatchRequest();
                string json = JsonConvert.SerializeObject(jsonPatchDocument);
                witBatchRequest.Method  = "PATCH";
                witBatchRequest.Headers = headers;
                witBatchRequest.Uri     = $"/_apis/wit/workItems/{workItemId}?{this.queryString}";
                witBatchRequest.Body    = json;
            }

            return(witBatchRequest);
        }
Ejemplo n.º 7
0
        //TODO no error handling here? SaveChanges_Batch has at least the DryRun support and error handling
        //TODO Improve complex handling with ReplaceIdAndResetChanges and RemapIdReferences
        private async Task <(int created, int updated)> SaveChanges_TwoPhases(bool commit, bool impersonate, CancellationToken cancellationToken)
        {
            // see https://github.com/redarrowlabs/vsts-restapi-samplecode/blob/master/VSTSRestApiSamples/WorkItemTracking/Batch.cs
            // and https://docs.microsoft.com/en-us/rest/api/vsts/wit/workitembatchupdate?view=vsts-rest-4.1
            // The workitembatchupdate API has a huge limit:
            // it fails adding a relation between a new (id<0) work item and an existing one (id>0)

            var workItems = _context.Tracker.GetChangedWorkItems();
            int created   = workItems.Created.Length;
            int updated   = workItems.Updated.Length + workItems.Deleted.Length + workItems.Restored.Length;

            //TODO strange handling, better would be a redesign here: Add links as new Objects and do not create changes when they occur but when accessed to Changes property
            var batchRequests = new List <WitBatchRequest>();

            foreach (var item in workItems.Created)
            {
                _context.Logger.WriteInfo($"Found a request for a new {item.WorkItemType} workitem in {item.TeamProject}");

                //TODO HACK better something like this: _context.Tracker.NewWorkItems.Where(wi => !wi.Relations.HasAdds(toNewItems: true))
                var changesWithoutRelation = item.Changes
                                             .Where(c => c.Operation != Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Test)
                                             // remove relations as we might incour in API failure
                                             .Where(c => !string.Equals(c.Path, "/relations/-", StringComparison.Ordinal));
                var document = new JsonPatchDocument();
                document.AddRange(changesWithoutRelation);

                var request = _clients.WitClient.CreateWorkItemBatchRequest(_context.ProjectName,
                                                                            item.WorkItemType,
                                                                            document,
                                                                            bypassRules: impersonate,
                                                                            suppressNotifications: false);
                batchRequests.Add(request);
            }

            if (commit)
            {
                var batchResponses = await ExecuteBatchRequest(batchRequests, cancellationToken);

                UpdateIdsInRelations(batchResponses);

                await RestoreAndDelete(workItems.Restored, workItems.Deleted, cancellationToken);
            }
            else
            {
                _context.Logger.WriteWarning($"Dry-run mode: no updates sent to Azure DevOps.");
            }

            batchRequests.Clear();
            var allWorkItems = workItems.Created.Concat(workItems.Updated).Concat(workItems.Restored);

            foreach (var item in allWorkItems)
            {
                var changes = item.Changes
                              .Where(c => c.Operation != Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Test);
                if (!changes.Any())
                {
                    continue;
                }
                _context.Logger.WriteInfo($"Found a request to update workitem {item.Id} in {_context.ProjectName}");

                var request = _clients.WitClient.CreateWorkItemBatchRequest(item.Id,
                                                                            item.Changes,
                                                                            bypassRules: impersonate,
                                                                            suppressNotifications: false);
                batchRequests.Add(request);
            }

            if (commit)
            {
                _ = await ExecuteBatchRequest(batchRequests, cancellationToken);
            }
            else
            {
                _context.Logger.WriteWarning($"Dry-run mode: no updates sent to Azure DevOps.");
            }

            return(created, updated);
        }
Ejemplo n.º 8
0
        private async Task CreateWorkItem(string type, Issue jiraIssue, string parentKey, string title, string description, string state, params JsonPatchOperation[] fields)
        {
            var patchDocument = new JsonPatchDocument
            {
                new JsonPatchOperation {
                    Path = "/fields/System.State", Value = state
                },
                new JsonPatchOperation {
                    Path = "/fields/System.CreatedBy", Value = MapUser(jiraIssue.ReporterUser.DisplayName)
                },
                new JsonPatchOperation {
                    Path = "/fields/System.CreatedDate", Value = jiraIssue.Created.Value.ToUniversalTime()
                },
                new JsonPatchOperation {
                    Path = "/fields/System.ChangedBy", Value = MapUser(jiraIssue.ReporterUser.DisplayName)
                },
                new JsonPatchOperation {
                    Path = "/fields/System.ChangedDate", Value = jiraIssue.Created.Value.ToUniversalTime()
                },
                new JsonPatchOperation {
                    Path = "/fields/System.Title", Value = title
                },
                new JsonPatchOperation {
                    Path = "/fields/System.Description", Value = description
                },
                new JsonPatchOperation {
                    Path = "/fields/Custom.JiraID", Value = jiraIssue.Key.Value
                },
                new JsonPatchOperation {
                    Path = "/fields/Microsoft.VSTS.Common.Priority", Value = MapPriority(jiraIssue.Priority)
                }
            };

            if (jiraIssue.AdditionalFields["customfield_10301"] != null)
            {
                patchDocument.Add(new JsonPatchOperation {
                    Path = "/fields/Custom.TestURL", Value = jiraIssue.AdditionalFields["customfield_10301"]?.ToString()
                });
            }

            if (parentKey != null)
            {
                patchDocument.Add(new JsonPatchOperation {
                    Path = "/relations/-", Value = new WorkItemRelation {
                        Rel = "System.LinkTypes.Hierarchy-Reverse", Url = $"https://ciappdev.visualstudio.com/_apis/wit/workItems/{parentKey}"
                    }
                });
            }

            if (jiraIssue.AssigneeUser != null)
            {
                patchDocument.Add(new JsonPatchOperation {
                    Path = "/fields/System.AssignedTo", Value = MapUser(jiraIssue.AssigneeUser.DisplayName)
                });
            }

            patchDocument.Add(new JsonPatchOperation {
                Path = "/fields/System.Tags", Value = _jiraProjectAbbrev
            });

            var attachments = await _jiraClient.Issues.GetAttachmentsAsync(jiraIssue.JiraIdentifier).ConfigureAwait(false);

            if (attachments != null)
            {
                foreach (var attachment in attachments)
                {
                    var bytes = attachment.DownloadData();

                    using var stream = new MemoryStream(bytes);
                    var uploaded = await _witClient.CreateAttachmentAsync(stream, _devOpsProjectName, fileName : attachment.FileName).ConfigureAwait(false);

                    patchDocument.Add(new JsonPatchOperation {
                        Path = "/relations/-", Value = new WorkItemRelation {
                            Rel = "AttachedFile", Url = uploaded.Url
                        }
                    });
                }
            }

            var all = patchDocument.Concat(fields).Where(p => p.Value != null).ToList();

            patchDocument = new JsonPatchDocument();

            patchDocument.AddRange(all);

            try
            {
                var workItem = await _witClient.CreateWorkItemAsync(patchDocument, _devOpsProjectName, type, bypassRules : true).ConfigureAwait(false);

                await CreateComments(workItem.Id.Value, jiraIssue).ConfigureAwait(false);

                Console.WriteLine($"Added {type}: {jiraIssue.Key} {title}");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
Ejemplo n.º 9
0
        private static async Task CreateWorkItem(
            WorkItemTrackingHttpClient client,
            string type,
            Issue jira,
            string parentKey,
            string title,
            string description,
            string state,
            params JsonPatchOperation[] fields)
        {
            // Short-circuit if we've already processed this item.
            if (Migrated.ContainsKey(jira.Key.Value))
            {
                return;
            }

            var vsts = new JsonPatchDocument
            {
                new JsonPatchOperation
                {
                    Path  = "/fields/System.State",
                    Value = state
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.CreatedBy",
                    Value = ResolveUser(jira.Reporter)
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.CreatedDate",
                    Value = jira.Created.Value.ToUniversalTime()
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.ChangedBy",
                    Value = ResolveUser(jira.Reporter)
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.ChangedDate",
                    Value = jira.Created.Value.ToUniversalTime()
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.Title",
                    Value = title
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.Description",
                    Value = description
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/Microsoft.VSTS.Common.Priority",
                    Value = ResolvePriority(jira.Priority)
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/Microsoft.VSTS.Common.ClosedDate",
                    Value = jira.ResolutionDate
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/Microsoft.VSTS.Scheduling.FinishDate",
                    Value = jira.ResolutionDate
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/Microsoft.VSTS.Common.ResolvedDate",
                    Value = jira.ResolutionDate
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/Microsoft.VSTS.Common.ResolvedReason",
                    Value = jira.Resolution?.Name
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.IterationPath",
                    Value = ResolveIteration(jira.CustomFields["Sprint"]?.Values, config["AzureDevOps:Project"])
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/Microsoft.VSTS.Scheduling.StoryPoints",
                    Value = jira.CustomFields["Story Points"]?.Values[0]
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/Microsoft.VSTS.Scheduling.Effort",
                    Value = jira.CustomFields["Story Points"]?.Values[0]
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.AreaPath",
                    Value = ResolveAreaPath(jira.CustomFields["DC Team"]?.Values[0], config["AzureDevOps:Project"])
                },
                new JsonPatchOperation
                {
                    Path  = "/fields/System.AssignedTo",
                    Value = ResolveUser(jira.Assignee)
                }
            }
            ;

            if (parentKey != null)
            {
                vsts.Add(new JsonPatchOperation
                {
                    Path  = "/relations/-",
                    Value = new WorkItemRelation
                    {
                        Rel = "System.LinkTypes.Hierarchy-Reverse",
                        Url = $"https://{config["AzureDevOps:Url"]}/_apis/wit/workItems/{Migrated[parentKey]}"
                    }
                });
            }

            if (jira.Labels.Any())
            {
                vsts.Add(new JsonPatchOperation
                {
                    Path  = "/fields/System.Tags",
                    Value = jira.Labels.Aggregate("", (l, r) => $"{l};{r}").Trim(';', ' ')
                });
            }

            foreach (var attachment in await jira.GetAttachmentsAsync())
            {
                var path = Path.GetTempFileName();
                attachment.Download(path);

                await using var stream = new MemoryStream(await File.ReadAllBytesAsync(path));

                var uploaded = await client.CreateAttachmentAsync(stream, config["AzureDevOps:Project"], fileName : attachment.FileName);

                vsts.Add(new JsonPatchOperation
                {
                    Path  = "/relations/-",
                    Value = new WorkItemRelation
                    {
                        Rel = "AttachedFile",
                        Url = uploaded.Url
                    }
                });

                File.Delete(path);
            }

            var all = vsts.Concat(fields)
                      .Where(p => p.Value != null)
                      .ToList();

            vsts = new JsonPatchDocument();
            vsts.AddRange(all);
            var workItem = await client.CreateWorkItemAsync(vsts, config["AzureDevOps:Project"], type, bypassRules : true);

            AddMigrated(jira.Key.Value, workItem.Id.Value);

            await CreateComments(client, workItem.Id.Value, jira);

            Console.WriteLine($"Added {type}: {jira.Key}{title}");
        }
Ejemplo n.º 10
0
        public JsonPatchDocument BuildJsonPatchDocument(TestCase testCase)
        {
            var jsonPatchDocument   = new JsonPatchDocument();
            var jsonPatchOperations = new List <JsonPatchOperation>();

            TestBaseHelper helper   = new TestBaseHelper();
            ITestBase      testBase = helper.Create();

            testCase.Steps.ForEach(x =>
            {
                ITestStep testStep      = testBase.CreateTestStep();
                testStep.Title          = x.Action ?? string.Empty;
                testStep.ExpectedResult = x.ExpectedResult ?? string.Empty;
                testStep.Description    = x.Action ?? string.Empty;

                testBase.Actions.Add(testStep);
            });

            var properties = typeof(TestCase).GetProperties();

            foreach (var propertyInfo in properties.Where(x => x.Name != "Steps"))
            {
                object[] attrs = propertyInfo.GetCustomAttributes(true);
                foreach (object attr in attrs)
                {
                    AzDevFieldReferenceAttribute azDevFieldReferenceAttribute = attr as AzDevFieldReferenceAttribute;
                    if (azDevFieldReferenceAttribute != null)
                    {
                        var data = testCase.GetType().GetProperty(propertyInfo.Name).GetValue(testCase, null);
                        if (propertyInfo.Name.Contains("AttachmentReferences"))
                        {
                            var castedData = data as List <AttachmentReference>;
                            castedData.ForEach(x =>
                            {
                                jsonPatchOperations.Add(new JsonPatchOperation
                                {
                                    Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                                    Path      = azDevFieldReferenceAttribute.AzDevFieldReference,
                                    Value     = new { rel = "AttachedFile", url = x.Url }
                                });
                            });
                        }
                        else
                        {
                            if (!string.IsNullOrWhiteSpace(data as string))
                            {
                                jsonPatchOperations.Add(new JsonPatchOperation
                                {
                                    Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                                    Path      = azDevFieldReferenceAttribute.AzDevFieldReference,
                                    Value     = data
                                });
                            }
                        }
                    }
                }
            }

            jsonPatchDocument.AddRange(jsonPatchOperations);
            jsonPatchDocument = testBase.SaveActions(jsonPatchDocument);
            return(jsonPatchDocument);
        }