azure-devops-mcp
by RyanCardin15
Verified
- src
- Services
import * as azdev from 'azure-devops-node-api';
import { WorkItemTrackingApi } from 'azure-devops-node-api/WorkItemTrackingApi';
import {
JsonPatchOperation,
Operation
} from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
import { AzureDevOpsConfig } from '../Interfaces/AzureDevOps';
import {
WorkItemByIdParams,
SearchWorkItemsParams,
RecentWorkItemsParams,
MyWorkItemsParams,
CreateWorkItemParams,
UpdateWorkItemParams,
AddWorkItemCommentParams,
UpdateWorkItemStateParams,
AssignWorkItemParams,
CreateLinkParams,
BulkWorkItemParams
} from '../Interfaces/WorkItems';
export class WorkItemService {
private connection: azdev.WebApi;
private config: AzureDevOpsConfig;
constructor(config: AzureDevOpsConfig) {
this.config = config;
this.connection = new azdev.WebApi(
config.orgUrl,
azdev.getPersonalAccessTokenHandler(config.personalAccessToken)
);
}
/**
* Get the WorkItemTracking API client
*/
private async getWorkItemTrackingApi(): Promise<WorkItemTrackingApi> {
return await this.connection.getWorkItemTrackingApi();
}
/**
* Query work items using WIQL
*/
public async listWorkItems(wiqlQuery: string): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
// Execute the WIQL query
const queryResult = await witApi.queryByWiql({
query: wiqlQuery
}, {
project: this.config.project
});
return queryResult;
} catch (error) {
console.error('Error listing work items:', error);
throw error;
}
}
/**
* Get a work item by ID
*/
public async getWorkItemById(params: WorkItemByIdParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const workItem = await witApi.getWorkItem(params.id, undefined, undefined, undefined, this.config.project);
return workItem;
} catch (error) {
console.error(`Error getting work item ${params.id}:`, error);
throw error;
}
}
/**
* Search work items using text
*/
public async searchWorkItems(params: SearchWorkItemsParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const query = `SELECT [System.Id], [System.Title], [System.State], [System.CreatedDate]
FROM WorkItems
WHERE [System.TeamProject] = @project
AND (
[System.Title] CONTAINS '${params.searchText}'
OR [System.Description] CONTAINS '${params.searchText}'
)
ORDER BY [System.CreatedDate] DESC`;
const queryResult = await witApi.queryByWiql({
query
}, {
project: this.config.project
});
return queryResult;
} catch (error) {
console.error('Error searching work items:', error);
throw error;
}
}
/**
* Get recently updated work items
*/
public async getRecentWorkItems(params: RecentWorkItemsParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const query = `SELECT [System.Id], [System.Title], [System.State], [System.ChangedDate]
FROM WorkItems
WHERE [System.TeamProject] = @project
ORDER BY [System.ChangedDate] DESC`;
const queryResult = await witApi.queryByWiql({
query
}, {
project: this.config.project
});
const top = params.top || 10;
const skip = params.skip || 0;
if (queryResult.workItems) {
queryResult.workItems = queryResult.workItems.slice(skip, skip + top);
}
return queryResult;
} catch (error) {
console.error('Error getting recent work items:', error);
throw error;
}
}
/**
* Get work items assigned to current user
*/
public async getMyWorkItems(params: MyWorkItemsParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
let stateCondition = '';
if (params.state) {
stateCondition = `AND [System.State] = '${params.state}'`;
}
const query = `SELECT [System.Id], [System.Title], [System.State], [System.CreatedDate]
FROM WorkItems
WHERE [System.TeamProject] = @project
AND [System.AssignedTo] = @me
${stateCondition}
ORDER BY [System.CreatedDate] DESC`;
const queryResult = await witApi.queryByWiql({
query
}, {
project: this.config.project
});
const top = params.top || 100;
if (queryResult.workItems) {
queryResult.workItems = queryResult.workItems.slice(0, top);
}
return queryResult;
} catch (error) {
console.error('Error getting my work items:', error);
throw error;
}
}
/**
* Create a work item
*/
public async createWorkItem(params: CreateWorkItemParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const patchDocument: JsonPatchOperation[] = [];
// Add title
patchDocument.push({
op: Operation.Add,
path: "/fields/System.Title",
value: params.title
});
// Add description if provided
if (params.description) {
patchDocument.push({
op: Operation.Add,
path: "/fields/System.Description",
value: params.description
});
}
// Add assigned to if provided
if (params.assignedTo) {
patchDocument.push({
op: Operation.Add,
path: "/fields/System.AssignedTo",
value: params.assignedTo
});
}
// Add state if provided
if (params.state) {
patchDocument.push({
op: Operation.Add,
path: "/fields/System.State",
value: params.state
});
}
// Add area path if provided
if (params.areaPath) {
patchDocument.push({
op: Operation.Add,
path: "/fields/System.AreaPath",
value: params.areaPath
});
}
// Add iteration path if provided
if (params.iterationPath) {
patchDocument.push({
op: Operation.Add,
path: "/fields/System.IterationPath",
value: params.iterationPath
});
}
// Add additional fields if provided
if (params.additionalFields) {
for (const [key, value] of Object.entries(params.additionalFields)) {
patchDocument.push({
op: Operation.Add,
path: `/fields/${key}`,
value: value
});
}
}
const workItem = await witApi.createWorkItem(
undefined,
patchDocument,
this.config.project,
params.workItemType
);
return workItem;
} catch (error) {
console.error('Error creating work item:', error);
throw error;
}
}
/**
* Update a work item
*/
public async updateWorkItem(params: UpdateWorkItemParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const patchDocument: JsonPatchOperation[] = [];
// Add fields from the params
for (const [key, value] of Object.entries(params.fields)) {
patchDocument.push({
op: Operation.Add,
path: `/fields/${key}`,
value: value
});
}
const workItem = await witApi.updateWorkItem(
undefined,
patchDocument,
params.id,
this.config.project
);
return workItem;
} catch (error) {
console.error(`Error updating work item ${params.id}:`, error);
throw error;
}
}
/**
* Add a comment to a work item
*/
public async addWorkItemComment(params: AddWorkItemCommentParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const comment = await witApi.addComment({
text: params.text
}, this.config.project, params.id);
return comment;
} catch (error) {
console.error(`Error adding comment to work item ${params.id}:`, error);
throw error;
}
}
/**
* Update work item state
*/
public async updateWorkItemState(params: UpdateWorkItemStateParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const patchDocument: JsonPatchOperation[] = [
{
op: Operation.Add,
path: "/fields/System.State",
value: params.state
}
];
// Add comment if provided
if (params.comment) {
patchDocument.push({
op: Operation.Add,
path: "/fields/System.History",
value: params.comment
});
}
const workItem = await witApi.updateWorkItem(
undefined,
patchDocument,
params.id,
this.config.project
);
return workItem;
} catch (error) {
console.error(`Error updating state for work item ${params.id}:`, error);
throw error;
}
}
/**
* Assign work item to a user
*/
public async assignWorkItem(params: AssignWorkItemParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const patchDocument: JsonPatchOperation[] = [
{
op: Operation.Add,
path: "/fields/System.AssignedTo",
value: params.assignedTo
}
];
const workItem = await witApi.updateWorkItem(
undefined,
patchDocument,
params.id,
this.config.project
);
return workItem;
} catch (error) {
console.error(`Error assigning work item ${params.id}:`, error);
throw error;
}
}
/**
* Create a link between work items
*/
public async createLink(params: CreateLinkParams): Promise<any> {
try {
const witApi = await this.getWorkItemTrackingApi();
const patchDocument: JsonPatchOperation[] = [
{
op: Operation.Add,
path: "/relations/-",
value: {
rel: params.linkType,
url: `${this.config.orgUrl}/_apis/wit/workItems/${params.targetId}`,
attributes: {
comment: params.comment || ""
}
}
}
];
const workItem = await witApi.updateWorkItem(
undefined,
patchDocument,
params.sourceId,
this.config.project
);
return workItem;
} catch (error) {
console.error(`Error creating link between work items:`, error);
throw error;
}
}
/**
* Bulk create or update work items
*/
public async bulkUpdateWorkItems(params: BulkWorkItemParams): Promise<any> {
try {
const results = [];
for (const workItemParams of params.workItems) {
if ('id' in workItemParams) {
// It's an update
const result = await this.updateWorkItem(workItemParams);
results.push(result);
} else {
// It's a create
const result = await this.createWorkItem(workItemParams);
results.push(result);
}
}
return {
count: results.length,
workItems: results
};
} catch (error) {
console.error('Error in bulk work item operation:', error);
throw error;
}
}
}