reactjsazure-devops-extensions

RESTful WIQL Interface for Azure DevOps Extension SDK


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.


Solution

  • 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.