Ejemplo n.º 1
0
        private async Task <(int created, int updated)> SaveChanges_Batch(bool commit, 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
            // BUG this code won't work if there is a relation between a new (id<0) work item and an existing one (id>0): it is an API limit

            const string ApiVersion = "api-version=4.1";

            int created = _context.Tracker.NewWorkItems.Count();
            int updated = _context.Tracker.ChangedWorkItems.Count();

            string baseUriString = _context.Client.BaseAddress.AbsoluteUri;

            BatchRequest[] batchRequests        = new BatchRequest[created + updated];
            Dictionary <string, string> headers = new Dictionary <string, string>
            {
                { "Content-Type", "application/json-patch+json" }
            };
            string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($":{_context.PersonalAccessToken}"));

            int index = 0;

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

                batchRequests[index++] = new BatchRequest
                {
                    method  = "PATCH",
                    uri     = $"/{item.TeamProject}/_apis/wit/workitems/${item.WorkItemType}?{ApiVersion}",
                    headers = headers,
                    body    = item.Changes
                              .Where(c => c.Operation != Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Test)
                              .ToArray()
                };
            }

            foreach (var item in _context.Tracker.ChangedWorkItems)
            {
                _context.Logger.WriteInfo($"Found a request to update workitem {item.Id.Value} in {item.TeamProject}");

                batchRequests[index++] = new BatchRequest
                {
                    method  = "PATCH",
                    uri     = FormattableString.Invariant($"/_apis/wit/workitems/{item.Id.Value}?{ApiVersion}"),
                    headers = headers,
                    body    = item.Changes
                              .Where(c => c.Operation != Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Test)
                              .ToArray()
                };
            }

            var    converters  = new JsonConverter[] { new JsonPatchOperationConverter() };
            string requestBody = JsonConvert.SerializeObject(batchRequests, Formatting.Indented, converters);

            _context.Logger.WriteVerbose(requestBody);

            if (commit)
            {
                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);

                    var batchRequest = new StringContent(requestBody, Encoding.UTF8, "application/json");
                    var method       = new HttpMethod("POST");

                    // send the request
                    var request = new HttpRequestMessage(method, $"{baseUriString}/_apis/wit/$batch?{ApiVersion}")
                    {
                        Content = batchRequest
                    };
                    var response = await client.SendAsync(request, cancellationToken);

                    if (response.IsSuccessStatusCode)
                    {
                        WorkItemBatchPostResponse batchResponse = await response.Content.ReadAsAsync <WorkItemBatchPostResponse>(cancellationToken);

                        string stringResponse = JsonConvert.SerializeObject(batchResponse, Formatting.Indented);
                        _context.Logger.WriteVerbose(stringResponse);
                        bool succeeded = true;
                        foreach (var batchElement in batchResponse.values)
                        {
                            if (batchElement.code != 200)
                            {
                                _context.Logger.WriteError($"Save failed: {batchElement.body}");
                                succeeded = false;
                            }
                        }

                        if (!succeeded)
                        {
                            throw new InvalidOperationException("Save failed.");
                        }
                    }
                    else
                    {
                        string stringResponse = await response.Content.ReadAsStringAsync();

                        _context.Logger.WriteError($"Save failed: {stringResponse}");
                        throw new InvalidOperationException($"Save failed: {response.ReasonPhrase}.");
                    }
                }//using
            }
            else
            {
                _context.Logger.WriteWarning($"Dry-run mode: no updates sent to Azure DevOps.");
            }//if

            return(created, updated);
        }
Ejemplo n.º 2
0
        internal async Task <WorkItemBatchPostResponse> InvokeAsync(BatchRequest[] batchRequests, CancellationToken cancellationToken)
        {
            string baseUriString = _context.Client.BaseAddress.AbsoluteUri;
            string credentials   = Convert.ToBase64String(Encoding.ASCII.GetBytes($":{_context.PersonalAccessToken}"));
            var    converters    = new JsonConverter[] { new JsonPatchOperationConverter() };

            string requestBody = JsonConvert.SerializeObject(batchRequests, Formatting.Indented, converters);

            _context.Logger.WriteVerbose($"Workitem(s) batch request:");
            _context.Logger.WriteVerbose(requestBody);

            if (_commit)
            {
                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);

                    var batchRequest = new StringContent(requestBody, Encoding.UTF8, "application/json");
                    var method       = new HttpMethod("POST");

                    // send the request
                    var request = new HttpRequestMessage(method, $"{baseUriString}/_apis/wit/$batch?{ApiVersion}")
                    {
                        Content = batchRequest
                    };
                    var response = await client.SendAsync(request, cancellationToken);

                    if (response.IsSuccessStatusCode)
                    {
                        WorkItemBatchPostResponse batchResponse = await response.Content.ReadAsAsync <WorkItemBatchPostResponse>(cancellationToken);

                        string stringResponse = JsonConvert.SerializeObject(batchResponse, Formatting.Indented);
                        _context.Logger.WriteVerbose($"Workitem(s) batch response:");
                        _context.Logger.WriteVerbose(stringResponse);

                        bool succeeded = true;
                        foreach (var batchElement in batchResponse.values)
                        {
                            if (batchElement.code != 200)
                            {
                                _context.Logger.WriteError($"Save failed: {batchElement.body}");
                                succeeded = false;
                            }
                        }

                        if (!succeeded)
                        {
                            throw new InvalidOperationException($"Save failed.");
                        }

                        return(batchResponse);
                    }
                    else
                    {
                        string stringResponse = await response.Content.ReadAsStringAsync();

                        _context.Logger.WriteError($"Save failed: {stringResponse}");
                        throw new InvalidOperationException($"Save failed: {response.ReasonPhrase}.");
                    }
                }//using
            }

            _context.Logger.WriteWarning($"Dry-run mode: no updates sent to Azure DevOps.");
            return(null);
        }
Ejemplo n.º 3
0
        private async Task <(int created, int updated)> SaveChanges_TwoPhases(bool commit)
        {
            // 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)

            const string ApiVersion             = "api-version=4.1";
            string       baseUriString          = _context.Client.BaseAddress.AbsoluteUri;
            Dictionary <string, string> headers = new Dictionary <string, string>()
            {
                { "Content-Type", "application/json-patch+json" }
            };
            string credentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes($":{_context.PersonalAccessToken}"));
            var    converters  = new JsonConverter[] { new JsonPatchOperationConverter() };

            int created = _context.Tracker.NewWorkItems.Count();
            int updated = _context.Tracker.ChangedWorkItems.Count();

            BatchRequest[] newWorkItemsBatchRequests = new BatchRequest[created];
            int            index = 0;

            foreach (var item in _context.Tracker.NewWorkItems)
            {
                _context.Logger.WriteInfo($"Found a request for a new {item.WorkItemType} workitem in {_context.ProjectName}");

                newWorkItemsBatchRequests[index++] = new BatchRequest
                {
                    method  = "PATCH",
                    uri     = $"/{_context.ProjectName}/_apis/wit/workitems/${item.WorkItemType}?{ApiVersion}",
                    headers = headers,
                    body    = item.Changes
                              .Where(c => c.Operation != Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Test)
                              // remove relations as we might incour in API failure
                              .Where(c => c.Path != "/relations/-")
                              .ToArray()
                };
            }
            string requestBody = JsonConvert.SerializeObject(newWorkItemsBatchRequests, Formatting.Indented, converters);

            _context.Logger.WriteVerbose($"New workitem(s) batch request:");
            _context.Logger.WriteVerbose(requestBody);

            if (commit)
            {
                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);

                    var batchRequest = new StringContent(requestBody, Encoding.UTF8, "application/json");
                    var method       = new HttpMethod("POST");

                    // send the request
                    var request = new HttpRequestMessage(method, $"{baseUriString}/_apis/wit/$batch?{ApiVersion}")
                    {
                        Content = batchRequest
                    };
                    var response = client.SendAsync(request).Result;

                    if (response.IsSuccessStatusCode)
                    {
                        WorkItemBatchPostResponse batchResponse = response.Content.ReadAsAsync <WorkItemBatchPostResponse>().Result;
                        string stringResponse = JsonConvert.SerializeObject(batchResponse, Formatting.Indented);
                        _context.Logger.WriteVerbose($"New workitem(s) batch response:");
                        _context.Logger.WriteVerbose(stringResponse);
                        bool succeeded = true;
                        foreach (var batchElement in batchResponse.values)
                        {
                            if (batchElement.code != 200)
                            {
                                _context.Logger.WriteError($"Save failed: {batchElement.body}");
                                succeeded = false;
                            }
                        }
                        if (!succeeded)
                        {
                            throw new ApplicationException($"Save failed.");
                        }
                        else
                        {
                            _context.Logger.WriteVerbose($"Updating work item ids...");
                            // Fix back
                            var realIds = new Dictionary <int, int>();
                            index = 0;
                            foreach (var item in _context.Tracker.NewWorkItems)
                            {
                                int oldId = item.Id.Value;
                                // the response order matches the request order
                                string  createdWorkitemJson   = batchResponse.values[index++].body;
                                dynamic createdWorkitemResult = JsonConvert.DeserializeObject(createdWorkitemJson);
                                int     newId = createdWorkitemResult.id;
                                item.ReplaceIdAndResetChanges(item.Id.Value, newId);
                                realIds.Add(oldId, newId);
                            }
                            foreach (var item in _context.Tracker.ChangedWorkItems)
                            {
                                item.RemapIdReferences(realIds);
                            }
                        }
                    }
                    else
                    {
                        string stringResponse = await response.Content.ReadAsStringAsync();

                        _context.Logger.WriteError($"Save failed: {stringResponse}");
                        throw new ApplicationException($"Save failed: {response.ReasonPhrase}.");
                    }
                }//using
            }
            else
            {
                _context.Logger.WriteWarning($"Dry-run mode: no updates sent to Azure DevOps.");
            }//if

            var batchRequests = new List <BatchRequest>();
            var allWorkItems  = _context.Tracker.NewWorkItems.Concat(_context.Tracker.ChangedWorkItems);

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

                    batchRequests.Add(new BatchRequest
                    {
                        method  = "PATCH",
                        uri     = $"/_apis/wit/workitems/{item.Id.Value}?{ApiVersion}",
                        headers = headers,
                        body    = changes.ToArray()
                    });
                }
            }

            requestBody = JsonConvert.SerializeObject(batchRequests.ToArray(), Formatting.Indented, converters);
            _context.Logger.WriteVerbose($"Update workitem(s) batch request:");
            _context.Logger.WriteVerbose(requestBody);

            if (commit)
            {
                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);

                    var batchRequest = new StringContent(requestBody, Encoding.UTF8, "application/json");
                    var method       = new HttpMethod("POST");

                    // send the request
                    var request = new HttpRequestMessage(method, $"{baseUriString}/_apis/wit/$batch?{ApiVersion}")
                    {
                        Content = batchRequest
                    };
                    var response = client.SendAsync(request).Result;

                    if (response.IsSuccessStatusCode)
                    {
                        WorkItemBatchPostResponse batchResponse = response.Content.ReadAsAsync <WorkItemBatchPostResponse>().Result;
                        string stringResponse = JsonConvert.SerializeObject(batchResponse, Formatting.Indented);
                        _context.Logger.WriteVerbose(stringResponse);
                        bool succeeded = true;
                        foreach (var batchElement in batchResponse.values)
                        {
                            if (batchElement.code != 200)
                            {
                                _context.Logger.WriteError($"Save failed: {batchElement.body}");
                                succeeded = false;
                            }
                        }
                        if (!succeeded)
                        {
                            throw new ApplicationException($"Save failed.");
                        }
                    }
                    else
                    {
                        string stringResponse = await response.Content.ReadAsStringAsync();

                        _context.Logger.WriteError($"Save failed: {stringResponse}");
                        throw new ApplicationException($"Save failed: {response.ReasonPhrase}.");
                    }
                }//using
            }
            else
            {
                _context.Logger.WriteWarning($"Dry-run mode: no updates sent to Azure DevOps.");
            }//if

            return(created, updated);
        }