Querying and Updating Work Items in Azure DevOps

A couple of weeks after migrating various projects from Jira to Azure DevOps (ADO) one of the teams told me that they were missing some data. They had suddenly realised that in the past they had created (and populated) custom Jira fields which had been forgotten when we migrated across. Could I update their historic and in-flight ADO work items with the missing data?

I previously used the Solidify migration tool to download Jira issues into JSON files and then push them into ADO as new work items. The tool is intended for one-off migrations – you cannot use it to update existing and in-use work items.

So I needed to figure out how to bring the data across myself...

Extracting and manipulating the data from Jira


Firstly, I queried Jira for the IDs of the required issues, plus the columns containing the missing data. I exported this to CSV.

Then, in Excel, I concatenated the missing data into a single field. This is so that I could insert it as a single statement at the beginning of the ADO work item description. I then saved the data with two columns, ID and Statement, again as a CSV.

Updating the ADO work items using the REST API


In C# I then looped over the rows in the CSV file (using CSVHelper).

I needed to: query the work item using the Jira ID (helpfully the Soldify tool puts it into the work item's title). Then retrieve the work item's description. Then update the description.

ADO provides an API method to query work items:

POST https://dev.azure.com/[yourOrganisation]/[yourProject]/[yourTeam]/_apis/wit/wiql?api-version=6.0

(NB – for ADO REST Calls your Personal Access Token (PAT) must be encoded and passed in the header)

The method expects a WIQL (Work Item Query Language) JSON payload. I created a C# WiqlQuery object to represent the JSON, generated using https://json2csharp.com. Then I used the following to retrieve the ID of the work item:
var wiqlQuery = new WiqlQuery
{
    // there are square brackets around the Jira ID in the ADO title
    query = "Select [System.Id] From WorkItems Where [System.Title] Contains '["
 + jiraId + "]'"
};
 
var query = JsonConvert.SerializeObject(wiqlQuery);
The JSON response returned by the API was also converted into a generated C# object:
var wiqlResult = JsonConvert.DeserializeObject<WiqlResult>(response);
 
// pull out the work item ID
return wiqlResult.workItems[0].id;
With the work item ID I was now able to get the full details of the work item. It's worth noting that Description is not a top level field – it lives in the Fields collection. You need to specifically request it, like so:

GET https://dev.azure.com/[yourOrganisation]/_apis/wit/workitems/[WorkItemId]?fields=System.Description&api-version=6.0

Again I converted the JSON response into a generated C# object:
var workItem = JsonConvert.DeserializeObject<AdoWorkItem>(response);
 
// pull out the description, if there is one
return workItem.fields.SystemDescription ?? string.Empty;
To update the description I used the same API URL, but with a different HTTP method

PATCH https://dev.azure.com/[yourOrganisation]/_apis/wit/workitems/[WorkItemId]?api-version=6.0

The content to be sent contains Path (the field to update), Value and Operation
// pass in a collection of fields to update
var workItemFields = new List<AdoWorkItemField>
{
    new AdoWorkItemField
    {
        // replace the existing description with the Jira data
        // plus any existing description
        op = "replace",
        path = "/fields/System.Description",
        value = jiraStatement + "\n" + workItemDescription
    }
};
 
var query = JsonConvert.SerializeObject(workItemFields);
And that's it. I queried the work item ID from ADO using the Jira ID. I retrieved the description for the work item. I then added the Jira data to the beginning. The teams were very grateful!

The trickiest part? Figuring out that I needed to retrieve the work item's Description from its Fields collection.

Happy coding.

Comments