This post outlines what worked for me to update an Azure Dev Ops (ADO) Boards Work Item. I initially tried the following prior to settling on typed-rest-client:

  • azure-devops-node-api - This looked promising but ultimately I could not get it to work for updating work items. Judging by the silence in my Stackoverflow question it seems updating Work Items via azure-devops-node-api is not something anyone has tried?
  • azure-devops-extension-api - This one seemed promising but has no README nor examples. It was too deep a rabbit hole to go down right now.

Thus I settled on typed-rest-client which seemed relatively straight forward to use.

I’ll show some snippets of how I fetched a Work Item and then how to update a custom field (or any field really). For my use case I just created a toy type to store just what I was interested in rather than try and pull down an official type from the monster azure-devops* repos above (🐇). If I find a more “official” way of doing it then I’ll update this post.

Step 0 - Minimal Types for Representing Work Item

Given the labynth of types azure-devops-node-api and azure-devops-extension-api have I decided to just make my own as follows:

export interface ADOWorkItemLite {
  readonly id: number,
  readonly fields: ADOWorkItemFields
}

export interface ADOWorkItemFields {
  "Custom.SomeField": string // Can also be another, non-custom field
}

Fetching a Work Item

// Note: I can probably get the PAT handler from type-rest-client as well
import * as ado from 'azure-devops-node-api'
import * as restClient from 'typed-rest-client'

const authHandler = ado.getPersonalAccessTokenHandler(process.env.PAT)
const client: restClient.RestClient = new restClient
  .RestClient('example-user-agent', 'https://dev.azure.com', [authHandler])

/**
 * Where:
 * myorg - Your organisation name
 * project - Your URL escaped project name
 * 1234 - The ADO ticket being requested
 * 7.0 - The ADO API version (7.0 as of March 2023)
*/
const location = '/myorg/project/_apis/wit/workitems/1234?api-version=7.0'

return client.get<ADOWorkItemLite>(location).then(response => {
    const workItem = response.result as ADOWorkItemLite
    return workItem
})

Updating a Work Item Field

For this I introduced a new type, the HTTP PATCH update payload:

export interface ADOWorkItemUpdatePayload {
  /**
   * What we're updating, usually "add"
   */
  op: string,

  /**
   * The Work Item path e.g. /fields/SomeField
   */
  path: string,

  /**
   * The value given to the item on the path
   */
  value: string
}

Then the initial parts are the same as getting a work item above but, after that, we apply the update:

import * as ado from 'azure-devops-node-api'
import * as restClient from 'typed-rest-client'

const authHandler = ado.getPersonalAccessTokenHandler(process.env.PAT)
const client: restClient.RestClient = new restClient
  .RestClient('example-user-agent', 'https://dev.azure.com', [authHandler])
const location = '/myorg/project/_apis/wit/workitems/1234?api-version=7.0'

const patchContents: ADOWorkItemUpdatePayload = {
  op: "add",
  path: "/fields/Custom.SomeField",
  value: updatedWorkItem.fields['Custom.SomeField']
}

return client.update<ADOWorkItemUpdatePayload>(location, [patchContents], {
  additionalHeaders: {
    "Content-Type": 'application/json-patch+json'
  }
}).then(results => {
  console.log(`${results.statusCode} - Updated work item ${updatedWorkItem.id}`)
})

For me, this worked. As mentioned I should use the auth handler from type-rest-client to remove the dependency on azure-devops-node-api.