Skip to main content
Glama
yjiace

AlibabaCloud DevOps MCP Server

by yjiace
workitem.ts19.1 kB
import {RecordType, string, TypeOf, z, ZodString} from "zod"; import {buildUrl, yunxiaoRequest} from "../../common/utils.js"; import { WorkItemSchema, FilterConditionSchema, ConditionsSchema, WorkItemType, WorkItemTypeDetail, WorkItemTypeFieldConfig, WorkItemWorkflow, UpdateWorkItemField } from "./types.js"; import { ProjectInfoSchema } from "./types.js"; import { ListWorkItemCommentsParams } from "./types.js"; import { getCurrentUserFunc } from "../organization/organization.js"; export async function getWorkItemFunc( organizationId: string, workItemId: string ): Promise<z.infer<typeof WorkItemSchema>> { 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: string, category: string, spaceId: string, subject?: string, status?: string, createdAfter?: string, createdBefore?: string, updatedAfter?: string, updatedBefore?: string, creator?: string, assignedTo?: string, advancedConditions?: string, orderBy: string = "gmtCreate", includeDetails: boolean = false // 新增参数:是否自动补充缺失的description等详细信息 ): Promise<z.infer<typeof WorkItemSchema>[]> { // 处理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: Record<string, any> = { 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: string, workItems: z.infer<typeof WorkItemSchema>[], batchSize: number = 10, // 每批处理10个 maxItems: number = 100 // 最多处理100个 ): Promise<Map<string, string | null>> { const descriptionMap = new Map<string, string | null>(); // 限制处理数量 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: string = 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: { subject?: string; status?: string; createdAfter?: string; createdBefore?: string; updatedAfter?: string; updatedBefore?: string; creator?: string; assignedTo?: string; advancedConditions?: string; }): string | undefined { if (args.advancedConditions) { return args.advancedConditions; } const filterConditions: z.infer<typeof FilterConditionSchema>[] = []; 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: z.infer<typeof ConditionsSchema> = { conditionGroups: [filterConditions], }; return JSON.stringify(conditions); } export async function createWorkItemFunc( organizationId: string, assignedTo: string, spaceId: string, subject: string, workitemTypeId: string, customFieldValues?: RecordType<string, string> | undefined, description?: string | undefined, labels?: string[], parentId?: string | undefined, participants?: string[] | undefined, sprint?: string | undefined, trackers?: string[] | undefined, verifier?: string | undefined, versions?: string[] | undefined ): Promise<z.infer<typeof WorkItemSchema>> { const url = `/oapi/v1/projex/organizations/${organizationId}/workitems`; const payload: Record<string, any> = { 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 updateWorkItemFunc( organizationId: string, workItemId: string, updateWorkItemFields: UpdateWorkItemField ): Promise<void> { const url = `/oapi/v1/projex/organizations/${organizationId}/workitems/${workItemId}`; // 构建请求体,将自定义字段合并到主对象中 const requestBody: Record<string, any> = {}; // 复制所有标准字段 if (updateWorkItemFields.subject !== undefined) { requestBody.subject = updateWorkItemFields.subject; } if (updateWorkItemFields.description !== undefined) { requestBody.description = updateWorkItemFields.description; } if (updateWorkItemFields.status !== undefined) { requestBody.status = updateWorkItemFields.status; } if (updateWorkItemFields.assignedTo !== undefined) { requestBody.assignedTo = updateWorkItemFields.assignedTo; } if (updateWorkItemFields.priority !== undefined) { requestBody.priority = updateWorkItemFields.priority; } if (updateWorkItemFields.labels !== undefined) { requestBody.labels = updateWorkItemFields.labels; } if (updateWorkItemFields.sprint !== undefined) { requestBody.sprint = updateWorkItemFields.sprint; } if (updateWorkItemFields.trackers !== undefined) { requestBody.trackers = updateWorkItemFields.trackers; } if (updateWorkItemFields.verifier !== undefined) { requestBody.verifier = updateWorkItemFields.verifier; } if (updateWorkItemFields.participants !== undefined) { requestBody.participants = updateWorkItemFields.participants; } if (updateWorkItemFields.versions !== undefined) { requestBody.versions = updateWorkItemFields.versions; } // 处理自定义字段 if (updateWorkItemFields.customFieldValues !== undefined) { // 将自定义字段合并到请求体中 Object.entries(updateWorkItemFields.customFieldValues).forEach(([fieldId, value]) => { requestBody[fieldId] = value; }); } const response = await yunxiaoRequest(url, { method: "PUT", body: requestBody, }); } export async function getWorkItemTypesFunc( organizationId: string, id: string, // 项目唯一标识 category: string // 工作项类型,可选值为 Req,Bug,Task 等。 ): Promise<WorkItemType[]> { const url = `/oapi/v1/projex/organizations/${organizationId}/projects/${id}/workitemTypes?category=${encodeURIComponent(category)}`; const response = await yunxiaoRequest(url, { method: "GET", }); return response as WorkItemType[]; } /** * 列出所有工作项类型 * @param organizationId 企业ID * @returns 工作项类型列表 */ export async function listAllWorkItemTypesFunc( organizationId: string ): Promise<WorkItemTypeDetail[]> { const url = `/oapi/v1/projex/organizations/${organizationId}/workitemTypes`; const response = await yunxiaoRequest(url, { method: "GET", }); // 确保返回的是数组格式 if (Array.isArray(response)) { return response as WorkItemTypeDetail[]; } // 如果响应中包含result字段,则返回result中的数据 if (response && typeof response === 'object' && 'result' in response && Array.isArray(response.result)) { return response.result as WorkItemTypeDetail[]; } // 其他情况返回空数组 return []; } /** * 列出工作项类型 * @param organizationId 企业ID * @param spaceIdentifier 项目唯一标识 * @param category 工作项类型分类(可选) * @returns 工作项类型列表 */ export async function listWorkItemTypesFunc( organizationId: string, spaceIdentifier: string, category?: string ): Promise<WorkItemTypeDetail[]> { 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 as WorkItemTypeDetail[]; } // 如果响应中包含result字段,则返回result中的数据 if (response && typeof response === 'object' && 'result' in response && Array.isArray(response.result)) { return response.result as WorkItemTypeDetail[]; } // 其他情况返回空数组 return []; } /** * 获取工作项类型详情 * @param organizationId 企业ID * @param spaceIdentifier 项目唯一标识 * @param id 工作项类型ID * @returns 工作项类型详情 */ export async function getWorkItemTypeFunc( organizationId: string, id: string ): Promise<WorkItemTypeDetail> { 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 as WorkItemTypeDetail; } // 否则直接返回响应 return response as WorkItemTypeDetail; } /** * 列出工作项关联的工作项类型 * @param organizationId 企业ID * @param spaceIdentifier 项目唯一标识 * @param workItemTypeId 工作项ID * @param relationType 关联类型 (BLOCK, RELATE, DUPLICATE, CHILD) * @returns 关联的工作项类型列表 */ export async function listWorkItemRelationWorkItemTypesFunc( organizationId: string, workItemTypeId: string, relationType?: string ): Promise<WorkItemTypeDetail[]> { const url = `/oapi/v1/projex/organizations/${organizationId}/workitemTypes/${workItemTypeId}/relationWorkitemTypes`; const queryParams: Record<string, string | number | undefined> = {}; if (relationType != null) { queryParams.relationType = relationType; } let finalUrl = buildUrl(url, queryParams); const response = await yunxiaoRequest(finalUrl, { method: "GET", }); // 确保返回的是数组格式 if (Array.isArray(response)) { return response as WorkItemTypeDetail[]; } // 如果响应中包含result字段,则返回result中的数据 if (response && typeof response === 'object' && 'result' in response && Array.isArray(response.result)) { return response.result as WorkItemTypeDetail[]; } // 其他情况返回空数组 return []; } /** * 获取工作项类型字段配置 * @param organizationId 企业ID * @param projectId 项目唯一标识 * @param workItemTypeId 工作项类型ID * @returns 工作项类型字段配置 */ export async function getWorkItemTypeFieldConfigFunc( organizationId: string, projectId: string, workItemTypeId: string ): Promise<WorkItemTypeFieldConfig> { 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 as WorkItemTypeFieldConfig; } // 否则直接返回响应 return response as WorkItemTypeFieldConfig; } /** * 获取工作项工作流 * @param organizationId 企业ID * @param projectId 项目唯一标识 * @param workItemTypeId 工作项类型ID * @returns 工作项工作流信息 */ export async function getWorkItemWorkflowFunc( organizationId: string, projectId: string, workItemTypeId: string ): Promise<WorkItemWorkflow> { 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 as WorkItemWorkflow; } // 否则直接返回响应 return response as WorkItemWorkflow; } /** * 列出工作项评论 * @param organizationId 企业ID * @param workItemId 工作项ID * @param page 页码 * @param perPage 每页条数 * @returns 工作项评论列表 */ export async function listWorkItemCommentsFunc( organizationId: string, workItemId: string, page: number = 1, perPage: number = 20 ): Promise<any[]> { 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: string, workItemId: string, content: string ): Promise<any> { 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; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yjiace/alibabacloud-devops-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server