Search code examples
c#restazure-devopsworkitemjson-patch

Issue Creating Azure DevOps Work Item with Microsoft.VSTS.Common.Priority and Microsoft.VSTS.Common.StackRank Fields


I'm having trouble creating a new work item in Azure DevOps using the REST API. Specifically, I want to include the Microsoft.VSTS.Common.Priority and Microsoft.VSTS.Common.StackRank fields, but my current implementation doesn't seem to be working correctly.

Here's the method I'm using to create the work item:

public WorkItemDTO CreateWorkItem(WorkItemDTO workItem, string workItemType)
{
    string tokenFormat = $"{string.Empty}:{GetTokenConfig()}";
    string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(tokenFormat));

    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri($"{DEVOPS_ORG_URL}/{GetProjectNameConfig()}/");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json-patch+json"));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);

        string uri = $"_apis/wit/workitems/${workItemType}?api-version=7.1-preview.3";

        var patchDocument = new[]
        {
            new { op = "add", path = "/fields/System.Title", value = workItem.Title },
            new { op = "add", path = "/fields/System.Description", value = workItem.Description },
            new { op = "add", path = "/fields/Microsoft.VSTS.Common.Priority", value = workItem.Priority },
            new { op = "add", path = "/fields/Microsoft.VSTS.Common.StackRank", value = workItem.BusinessValue }
        };

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

        HttpResponseMessage response = client.PostAsync(uri, content).Result;

        if (response.IsSuccessStatusCode)
        {
            string responseBody = response.Content.ReadAsStringAsync().Result;
            var createdWorkItemEntity = JsonConvert.DeserializeObject<WorkItemEntity>(responseBody);
            var createdWorkItemDTO = _mapper.Map<WorkItemDTO>(createdWorkItemEntity);
            return createdWorkItemDTO;
        }
        else
        {
            Console.WriteLine("Error creating work item. Status code: " + response.StatusCode);
            string responseBody = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine("Response body: " + responseBody);
        }
    }

    return null;
}

What I Tried:

I used a JSON patch document to include the Microsoft.VSTS.Common.Priority and Microsoft.VSTS.Common.StackRank fields. I sent a POST request to the Azure DevOps REST API with the above JSON patch document. What I Expected:

The new work item should be created with the Microsoft.VSTS.Common.Priority and Microsoft.VSTS.Common.StackRank fields set according to the values provided. What Actually Happened:

The work item is created successfully, but the Microsoft.VSTS.Common.Priority and Microsoft.VSTS.Common.StackRank fields are not populated with the provided values. The response from the API does not provide useful information about why these fields are not being set. Questions:

Are the Microsoft.VSTS.Common.Priority and Microsoft.VSTS.Common.StackRank fields correctly specified in the patch document? Could there be an issue with the API version 7.1-preview.3 or the field names? Is there something specific I might be missing in the request format or API usage?


Solution

  • I can reproduce the similar issue when using the sample code.

    The cause of the issue could be that the patchDocument content has issue. If we use the same definition as yours, the Priority and StackRank field needs string type input.

    To solve this issue, you can change to use the following format to define patchDocument:

       var patchDocument = new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchDocument();
       patchDocument.Add(
        new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
        {
            Path = "/fields/System.Description",
            Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
            Value = workItem.Description
        });
       patchDocument.Add(
        new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
        {
            Path = "/fields/System.Title",
            Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
            Value = workItem.Title
        });
       patchDocument.Add(
          new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
          {
              Path = "/fields/Microsoft.VSTS.Common.Priority",
              Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
              Value = workItem.Priority
          });
       patchDocument.Add(
          new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
          {
              Path = "/fields/Microsoft.VSTS.Common.StackRank",
              Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
              Value = workItem.BusinessValue
          });
    

    Here is the full sample:

    using Newtonsoft.Json;
    using System.Net.Http.Headers;
    using System.Text;
    
    public WorkItemDTO CreateWorkItem(WorkItemDTO workItem, string workItemType)
    {
        string tokenFormat = $"{string.Empty}:{GetTokenConfig()}";
        string credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(tokenFormat));
    
        using (var client = new HttpClient())
        {
            client.BaseAddress = new Uri($"{DEVOPS_ORG_URL}/{GetProjectNameConfig()}/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json-patch+json"));
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
    
            string uri = $"_apis/wit/workitems/${workItemType}?api-version=7.1-preview.3";
    
            var patchDocument = new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchDocument();
            patchDocument.Add(
             new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
             {
                 Path = "/fields/System.Description",
                 Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                 Value = workItem.Description
             });
            patchDocument.Add(
             new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
             {
                 Path = "/fields/System.Title",
                 Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                 Value = workItem.Title
             });
            patchDocument.Add(
               new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
               {
                   Path = "/fields/Microsoft.VSTS.Common.Priority",
                   Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                   Value = workItem.Priority
               });
            patchDocument.Add(
               new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
               {
                   Path = "/fields/Microsoft.VSTS.Common.StackRank",
                   Operation = Microsoft.VisualStudio.Services.WebApi.Patch.Operation.Add,
                   Value = workItem.BusinessValue
               });
    
            var jsonContent = JsonConvert.SerializeObject(patchDocument);
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json-patch+json");
    
            HttpResponseMessage response = client.PostAsync(uri, content).Result;
    
            if (response.IsSuccessStatusCode)
            {
                string responseBody = response.Content.ReadAsStringAsync().Result;
                var createdWorkItemEntity = JsonConvert.DeserializeObject<WorkItemEntity>(responseBody);
                var createdWorkItemDTO = _mapper.Map<WorkItemDTO>(createdWorkItemEntity);
                return createdWorkItemDTO;
            }
            else
            {
                Console.WriteLine("Error creating work item. Status code: " + response.StatusCode);
                string responseBody = response.Content.ReadAsStringAsync().Result;
                Console.WriteLine("Response body: " + responseBody);
            }
        }
    
        return null;
    }
    

    WorkItemDTO Class:

    public class WorkItemDTO
    {
        public int Id { get; set; }
        public int Rev { get; set; }
        public string Type { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string State { get; set; }
        public string Url { get; set; }
        public int Priority { get; set; }  // Add Priority property
        public double BusinessValue { get; set; }  // Add BusinessValue propert
    }
    

    Result:

    enter image description here