@iflow-mcp/alibabacloud-devops-mcp-server
Version:
MCP Server for using the alibabacloud-devops API: allows AI assistants to directly participate in development collaboration, helping teams optimize development processes and improve efficiency.
446 lines (445 loc) • 15.9 kB
JavaScript
import { buildUrl, yunxiaoRequest } from "../../common/utils.js";
import { WorkItemSchema } from "../../common/types.js";
import { getCurrentUserFunc } from "../organization/organization.js";
export async function getWorkItemFunc(organizationId, workItemId) {
const url = `/oapi/v1/projex/organizations/${organizationId}/workitems/${workItemId}`;
const response = await yunxiaoRequest(url, {
method: "GET",
});
return WorkItemSchema.parse(response);
}
export async function searchWorkitemsFunc(organizationId, category, spaceId, subject, status, createdAfter, createdBefore, updatedAfter, updatedBefore, creator, assignedTo, advancedConditions, orderBy = "gmtCreate", includeDetails = false // 新增参数:是否自动补充缺失的description等详细信息
) {
// 处理assignedTo为"self"的情况,自动获取当前用户ID
let finalAssignedTo = assignedTo;
let finalCreator = creator;
if (assignedTo === "self" || creator === "self") {
try {
const currentUser = await getCurrentUserFunc();
if (currentUser.id) {
if (assignedTo === "self") {
finalAssignedTo = currentUser.id;
}
if (creator === "self") {
finalCreator = currentUser.id;
}
}
else {
finalAssignedTo = assignedTo;
finalCreator = creator;
}
}
catch (error) {
finalAssignedTo = assignedTo;
finalCreator = creator;
}
}
const url = `/oapi/v1/projex/organizations/${organizationId}/workitems:search`;
const payload = {
category: category,
spaceId: spaceId,
};
const conditions = buildWorkitemConditions({
subject,
status,
createdAfter,
createdBefore,
updatedAfter,
updatedBefore,
creator: finalCreator,
assignedTo: finalAssignedTo,
advancedConditions
});
if (conditions) {
payload.conditions = conditions;
}
payload.orderBy = orderBy;
const response = await yunxiaoRequest(url, {
method: "POST",
body: payload,
});
if (!Array.isArray(response)) {
return [];
}
const workItems = response.map(workitem => WorkItemSchema.parse(workitem));
// 如果需要补充详细信息,使用分批并发方式获取
if (includeDetails) {
const itemsNeedingDetails = workItems.filter(item => item.id.length > 0 &&
(item.description === null || item.description === undefined || item.description === ""));
if (itemsNeedingDetails.length > 0) {
// 分批并发获取详情
const descriptionMap = await batchGetWorkItemDetails(organizationId, itemsNeedingDetails);
// 更新workItems中的description
return workItems.map(item => {
if (descriptionMap.has(item.id)) {
return {
...item,
description: descriptionMap.get(item.id) || item.description
};
}
return item;
});
}
}
return workItems;
}
// 分批并发获取工作项详情
async function batchGetWorkItemDetails(organizationId, workItems, batchSize = 10, // 每批处理10个
maxItems = 100 // 最多处理100个
) {
const descriptionMap = new Map();
// 限制处理数量
const limitedItems = workItems.slice(0, maxItems);
// 分批处理
for (let i = 0; i < limitedItems.length; i += batchSize) {
const batch = limitedItems.slice(i, i + batchSize);
// 批次内并发执行
const batchResults = await Promise.allSettled(batch.map(async (item) => {
// 再次检查item.id是否为有效字符串
if (typeof item.id !== 'string' || item.id.length === 0) {
return {
id: item.id || 'unknown',
description: null,
success: false
};
}
const itemId = item.id;
try {
const detailedItem = await getWorkItemFunc(organizationId, itemId);
return {
id: itemId,
description: detailedItem.description,
success: true
};
}
catch (error) {
return {
id: itemId,
description: null,
success: false
};
}
}));
// 处理批次结果
batchResults.forEach((result) => {
if (result.status === 'fulfilled') {
// 确保description类型正确,将undefined转换为null
const description = result.value.description === undefined ? null : result.value.description;
descriptionMap.set(result.value.id, description);
}
});
}
return descriptionMap;
}
function buildWorkitemConditions(args) {
if (args.advancedConditions) {
return args.advancedConditions;
}
const filterConditions = [];
if (args.subject) {
filterConditions.push({
className: "string",
fieldIdentifier: "subject",
format: "input",
operator: "CONTAINS",
toValue: null,
value: [args.subject],
});
}
if (args.status) {
const statusValues = args.status.split(",");
const values = statusValues.map(v => v.trim());
filterConditions.push({
className: "status",
fieldIdentifier: "status",
format: "list",
operator: "CONTAINS",
toValue: null,
value: values,
});
}
if (args.createdAfter) {
const createdBefore = args.createdBefore ? `${args.createdBefore} 23:59:59` : null;
filterConditions.push({
className: "dateTime",
fieldIdentifier: "gmtCreate",
format: "input",
operator: "BETWEEN",
toValue: createdBefore,
value: [`${args.createdAfter} 00:00:00`],
});
}
if (args.updatedAfter) {
const updatedBefore = args.updatedBefore ? `${args.updatedBefore} 23:59:59` : null;
filterConditions.push({
className: "dateTime",
fieldIdentifier: "gmtModified",
format: "input",
operator: "BETWEEN",
toValue: updatedBefore,
value: [`${args.updatedAfter} 00:00:00`],
});
}
if (args.creator) {
const creatorValues = args.creator.split(",");
const values = creatorValues.map(v => v.trim());
filterConditions.push({
className: "user",
fieldIdentifier: "creator",
format: "list",
operator: "CONTAINS",
toValue: null,
value: values,
});
}
if (args.assignedTo) {
const assignedToValues = args.assignedTo.split(",");
const values = assignedToValues.map(v => v.trim());
filterConditions.push({
className: "user",
fieldIdentifier: "assignedTo",
format: "list",
operator: "CONTAINS",
toValue: null,
value: values,
});
}
if (filterConditions.length === 0) {
return undefined;
}
const conditions = {
conditionGroups: [filterConditions],
};
return JSON.stringify(conditions);
}
export async function createWorkItemFunc(organizationId, assignedTo, spaceId, subject, workitemTypeId, customFieldValues, description, labels, parentId, participants, sprint, trackers, verifier, versions) {
const url = `/oapi/v1/projex/organizations/${organizationId}/workitems`;
const payload = {
assignedTo,
spaceId,
subject,
workitemTypeId
};
if (customFieldValues) {
payload.customFieldValues = customFieldValues;
}
if (description !== undefined) {
payload.description = description;
}
if (labels && labels.length > 0) {
payload.labels = labels;
}
if (parentId !== undefined) {
payload.parentId = parentId;
}
if (participants && participants.length > 0) {
payload.participants = participants;
}
if (sprint !== undefined) {
payload.sprint = sprint;
}
if (trackers && trackers.length > 0) {
payload.trackers = trackers;
}
if (verifier !== undefined) {
payload.verifier = verifier;
}
if (versions && versions.length > 0) {
payload.versions = versions;
}
const response = await yunxiaoRequest(url, {
method: "POST",
body: payload,
});
return WorkItemSchema.parse(response);
}
export async function getWorkItemTypesFunc(organizationId, id, // 项目唯一标识
category // 工作项类型,可选值为 Req,Bug,Task 等。
) {
const url = `/oapi/v1/projex/organizations/${organizationId}/projects/${id}/workitemTypes?category=${encodeURIComponent(category)}`;
const response = await yunxiaoRequest(url, {
method: "GET",
});
return response;
}
/**
* 列出所有工作项类型
* @param organizationId 企业ID
* @returns 工作项类型列表
*/
export async function listAllWorkItemTypesFunc(organizationId) {
const url = `/oapi/v1/projex/organizations/${organizationId}/workitemTypes`;
const response = await yunxiaoRequest(url, {
method: "GET",
});
// 确保返回的是数组格式
if (Array.isArray(response)) {
return response;
}
// 如果响应中包含result字段,则返回result中的数据
if (response && typeof response === 'object' && 'result' in response && Array.isArray(response.result)) {
return response.result;
}
// 其他情况返回空数组
return [];
}
/**
* 列出工作项类型
* @param organizationId 企业ID
* @param spaceIdentifier 项目唯一标识
* @param category 工作项类型分类(可选)
* @returns 工作项类型列表
*/
export async function listWorkItemTypesFunc(organizationId, spaceIdentifier, category) {
let url = `/oapi/v1/projex/organizations/${organizationId}/projects/${spaceIdentifier}/workitemTypes`;
// 如果提供了category参数,则添加到URL中
if (category) {
url += `?category=${encodeURIComponent(category)}`;
}
const response = await yunxiaoRequest(url, {
method: "GET",
});
// 确保返回的是数组格式
if (Array.isArray(response)) {
return response;
}
// 如果响应中包含result字段,则返回result中的数据
if (response && typeof response === 'object' && 'result' in response && Array.isArray(response.result)) {
return response.result;
}
// 其他情况返回空数组
return [];
}
/**
* 获取工作项类型详情
* @param organizationId 企业ID
* @param spaceIdentifier 项目唯一标识
* @param id 工作项类型ID
* @returns 工作项类型详情
*/
export async function getWorkItemTypeFunc(organizationId, id) {
const url = `/oapi/v1/projex/organizations/${organizationId}/workitemTypes/${id}`;
const response = await yunxiaoRequest(url, {
method: "GET",
});
// 如果响应中包含result字段,则返回result中的数据
if (response && typeof response === 'object' && 'result' in response) {
return response.result;
}
// 否则直接返回响应
return response;
}
/**
* 列出工作项关联的工作项类型
* @param organizationId 企业ID
* @param spaceIdentifier 项目唯一标识
* @param workItemTypeId 工作项ID
* @param relationType 关联类型 (BLOCK, RELATE, DUPLICATE, CHILD)
* @returns 关联的工作项类型列表
*/
export async function listWorkItemRelationWorkItemTypesFunc(organizationId, workItemTypeId, relationType) {
const url = `/oapi/v1/projex/organizations/${organizationId}/workitemTypes/${workItemTypeId}/relationWorkitemTypes`;
const queryParams = {};
if (relationType != null) {
queryParams.relationType = relationType;
}
let finalUrl = buildUrl(url, queryParams);
const response = await yunxiaoRequest(finalUrl, {
method: "GET",
});
// 确保返回的是数组格式
if (Array.isArray(response)) {
return response;
}
// 如果响应中包含result字段,则返回result中的数据
if (response && typeof response === 'object' && 'result' in response && Array.isArray(response.result)) {
return response.result;
}
// 其他情况返回空数组
return [];
}
/**
* 获取工作项类型字段配置
* @param organizationId 企业ID
* @param projectId 项目唯一标识
* @param workItemTypeId 工作项类型ID
* @returns 工作项类型字段配置
*/
export async function getWorkItemTypeFieldConfigFunc(organizationId, projectId, workItemTypeId) {
const url = `/oapi/v1/projex/organizations/${organizationId}/projects/${projectId}/workitemTypes/${workItemTypeId}/fields`;
const response = await yunxiaoRequest(url, {
method: "GET",
});
// 如果响应中包含result字段,则返回result中的数据
if (response && typeof response === 'object' && 'result' in response) {
return response.result;
}
// 否则直接返回响应
return response;
}
/**
* 获取工作项工作流
* @param organizationId 企业ID
* @param projectId 项目唯一标识
* @param workItemTypeId 工作项类型ID
* @returns 工作项工作流信息
*/
export async function getWorkItemWorkflowFunc(organizationId, projectId, workItemTypeId) {
const url = `/oapi/v1/projex/organizations/${organizationId}/projects/${projectId}/workitemTypes/${workItemTypeId}/workflows`;
const response = await yunxiaoRequest(url, {
method: "GET",
});
// 如果响应中包含result字段,则返回result中的数据
if (response && typeof response === 'object' && 'result' in response) {
return response.result;
}
// 否则直接返回响应
return response;
}
/**
* 列出工作项评论
* @param organizationId 企业ID
* @param workItemId 工作项ID
* @param page 页码
* @param perPage 每页条数
* @returns 工作项评论列表
*/
export async function listWorkItemCommentsFunc(organizationId, workItemId, page = 1, perPage = 20) {
const url = `/oapi/v1/projex/organizations/${organizationId}/workitems/${workItemId}/comments?page=${page}&perPage=${perPage}`;
const response = await yunxiaoRequest(url, {
method: "GET",
});
// 确保返回的是数组格式
if (Array.isArray(response)) {
return response;
}
// 如果响应中包含result字段,则返回result中的数据
if (response && typeof response === 'object' && 'result' in response && Array.isArray(response.result)) {
return response.result;
}
// 其他情况返回空数组
return [];
}
/**
* 创建工作项评论
* @param organizationId 企业ID
* @param workItemId 工作项ID
* @param content 评论内容
* @returns 创建的评论信息
*/
export async function createWorkItemCommentFunc(organizationId, workItemId, content) {
const url = `/oapi/v1/projex/organizations/${organizationId}/workitems/${workItemId}/comments`;
const payload = {
content: content
};
const response = await yunxiaoRequest(url, {
method: "POST",
body: payload,
});
// 如果响应中包含result字段,则返回result中的数据
if (response && typeof response === 'object' && 'result' in response) {
return response.result;
}
// 否则直接返回响应
return response;
}