I'm writing an Azure DevOps extension using the azure-devops-extension-sdk in React.js. I'm trying to query our existing Product Backlog Items for one with a matching title to get the id for that PBI and update the description as I add new tasks to it.
Is this something that's possible in the Azure DevOps Extension SDK or do I need to import a different library for this interface?
I can't seem to find any documentation on how to authenticate and interface with Azure DevOps Services API. I haven't found much in regards to authentication. I've found documentation on what endpoint to use to run my WIQL query and how to format the payload, but I can't find the SDK methods to accomplish those requests.
This documentation points back to the VSS Web Extension SDK, not the current Azure DevOps Extension SDK. https://learn.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-7.2&viewFallbackFrom=azure-devops-rest-7.0
Other documentation refers to utilizing C#, but extensions are written utilizing React.js and TypeScript.
Well, someone offered an answer that has since been deleted which pointed me in the right direction.
This retrieves the access token:
async function getAuthToken() {
try {
const token = await SDK.getAccessToken();
return token;
} catch (error) {
console.error("Failed to get access token", error);
}
return "";
}
This runs a query against your work items:
async function queryWorkItems(token: string, query: string, devopsProjectName: string, devopsUrl: string) {
const response = await fetch(`${devopsUrl}/${devopsProjectName}/_apis/wit/wiql?api-version=7.2-preview.2`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ query })
});
if (!response.ok) {
throw new Error(`Error querying work items: ${response.statusText}`);
}
const result = await response.json();
return result.workItems;
}
This will pull a single work item (to get the current values like System.Description):
async function getWorkItem(token: string, workItemId: string, devopsProjectName: string, devopsUrl: string) {
const response = await fetch(`${devopsUrl}/${devopsProjectName}/_apis/wit/workitems/${workItemId}?api-version=7.2-preview.2`, {
method: 'GET',
headers: {
'Content-Type': 'application/json-patch+json',
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error(`Error getting work item # ${workItemId}: ${response.statusText}`);
}
const result = await response.json();
return result;
}
This will update an existing work item:
async function updateWorkItem(token: string, workItemId: string, description: string, newDescription: string, devopsProjectName: string, devopsUrl: string) {
const fields = [
{
op: Operation.Add,
from: "",
path: "/fields/System.Description",
value: description + newDescription
}
];
const response = await fetch(`${devopsUrl}/${devopsProjectName}/_apis/wit/workitems/${workItemId}?api-version=7.2-preview.2`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json-patch+json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(fields)
});
if (!response.ok) {
throw new Error(`Error updating work item: ${response.statusText}`);
}
const result = await response.json();
return result;
}
So, to pull all of those together, I'm using this function to find my work item, update the description, and return the id:
async function findAndUpdateWorkItemByTitle(title: string, newDescription: string, devopsProjectName: string, devopsUrl: string) {
try {
const token = await getAuthToken();
const query = `SELECT [Id], [Description] FROM workitems WHERE [System.Title] = '${title}'`;
const workItems = await queryWorkItems(token, query, devopsProjectName, devopsUrl);
if (workItems.length === 0) {
console.log("No work item found with the given title.");
return "0";
}
const workItemId: string = workItems[0].id;
var workItem = await getWorkItem(token, workItemId, devopsProjectName, devopsUrl);
const updatedWorkItem = await updateWorkItem(token, workItemId, workItem.fields["System.Description"], newDescription, devopsProjectName, devopsUrl);
return workItemId;
} catch (error) {
console.log("Could not update existing item.");
console.error("Error:", error);
}
return "0";
}
I'm still optimizing this, but this was the quick solution to get it working. Thank you to whomever posted the basis for this which pushed me in the right direction.