UNPKG

gitlab-activity-mcp

Version:

GitLab Activity MCP Server - Generate professional activity reports and analysis

297 lines (296 loc) 11.5 kB
/** * Markdown格式化器 * 生成包含链接和时间的GitLab活动报告 */ import { formatDate } from './Date.js'; export class MarkdownFormatter { /** * 格式化GitLab活动为Markdown报告 */ formatActivities(filterResult, timeRange, options = {}) { const { showStatistics = true, groupByProject = true, groupByType = true, showMatchReasons = false, showDetailedTime = true, maxDescriptionLength = 200, title = 'GitLab活动报告', timeRangeDescription, } = options; const { activities, matchReasons, statistics } = filterResult; const sections = []; // 1. 标题和时间范围 sections.push(this.formatHeader(title, timeRange, timeRangeDescription)); // 2. 统计信息 if (showStatistics && activities.length > 0) { sections.push(this.formatStatistics(statistics)); } // 3. 活动详情 if (activities.length > 0) { if (groupByProject) { sections.push(this.formatActivitiesByProject(activities, matchReasons, { showMatchReasons, showDetailedTime, maxDescriptionLength, })); } else if (groupByType) { sections.push(this.formatActivitiesByType(activities, matchReasons, { showMatchReasons, showDetailedTime, maxDescriptionLength, })); } else { sections.push(this.formatActivitiesList(activities, matchReasons, { showMatchReasons, showDetailedTime, maxDescriptionLength, })); } } else { sections.push('## 📝 活动详情\n\n*在指定的时间范围内没有找到匹配的活动。*'); } // 4. 脚注 sections.push(this.formatFooter()); return sections.join('\n\n'); } /** * 格式化报告头部 */ formatHeader(title, timeRange, customDescription) { const timeDesc = customDescription || this.formatTimeRange(timeRange); return `# ${title}\n\n**时间范围**: ${timeDesc}\n**生成时间**: ${formatDate(new Date(), 'yyyy年MM月dd日 HH:mm:ss')}`; } /** * 格式化统计信息 */ formatStatistics(statistics) { const sections = ['## 📊 统计信息']; sections.push(`**总计**: ${statistics.total} 个活动`); // 按GitLab类型统计 if (Object.keys(statistics.byType).length > 0) { sections.push('### 📋 按类型分布'); const typeItems = Object.entries(statistics.byType) .sort(([, a], [, b]) => b - a) .map(([type, count]) => `- **${this.getTypeDisplayName(type)}**: ${count} 个`) .join('\n'); sections.push(typeItems); } // 按项目统计 if (Object.keys(statistics.byProject).length > 0) { sections.push('### 🏗️ 按项目分布'); const projectItems = Object.entries(statistics.byProject) .sort(([, a], [, b]) => b - a) .slice(0, 10) // 只显示前10个项目 .map(([project, count]) => `- **${project}**: ${count} 个`) .join('\n'); sections.push(projectItems); } return sections.join('\n\n'); } /** * 按项目分组格式化活动 */ formatActivitiesByProject(activities, matchReasons, options) { const sections = ['## 📝 活动详情']; // 按项目分组 const byProject = this.groupByField(activities, 'projectName'); for (const [projectName, projectActivities] of Object.entries(byProject)) { sections.push(`### 🏗️ ${projectName} (${projectActivities.length} 个活动)`); // 按时间排序 const sortedActivities = projectActivities.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); for (const activity of sortedActivities) { sections.push(this.formatSingleActivity(activity, matchReasons.get(activity.id) || [], options)); } } return sections.join('\n\n'); } /** * 按类型分组格式化活动 */ formatActivitiesByType(activities, matchReasons, options) { const sections = ['## 📝 活动详情']; // 按GitLab类型分组 const byType = this.groupByField(activities, 'type'); for (const [type, typeActivities] of Object.entries(byType)) { sections.push(`### ${this.getTypeIcon(type)} ${this.getTypeDisplayName(type)} (${typeActivities.length} 个)`); // 按时间排序 const sortedActivities = typeActivities.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); for (const activity of sortedActivities) { sections.push(this.formatSingleActivity(activity, matchReasons.get(activity.id) || [], options)); } } return sections.join('\n\n'); } /** * 列表格式化活动 */ formatActivitiesList(activities, matchReasons, options) { const sections = ['## 📝 活动详情']; // 按时间排序 const sortedActivities = activities.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); for (const activity of sortedActivities) { sections.push(this.formatSingleActivity(activity, matchReasons.get(activity.id) || [], options)); } return sections.join('\n\n'); } /** * 格式化单个活动 */ formatSingleActivity(activity, reasons, options) { const { showMatchReasons, showDetailedTime, maxDescriptionLength } = options; const sections = []; // 标题行 const typeIcon = this.getTypeIcon(activity.type); const title = `#### ${typeIcon} ${activity.title}`; sections.push(title); // 基本信息 const info = []; info.push(`**项目**: ${activity.projectName}`); info.push(`**类型**: ${this.getTypeDisplayName(activity.type)}`); info.push(`**作者**: ${activity.author}`); if (showDetailedTime) { info.push(`**创建时间**: ${formatDate(activity.createdAt, 'yyyy年MM月dd日 HH:mm:ss')}`); if (activity.updatedAt && activity.updatedAt.getTime() !== activity.createdAt.getTime()) { info.push(`**更新时间**: ${formatDate(activity.updatedAt, 'yyyy年MM月dd日 HH:mm:ss')}`); } } else { info.push(`**时间**: ${formatDate(activity.createdAt, 'MM月dd日 HH:mm')}`); } if (activity.state) { info.push(`**状态**: ${this.formatState(activity.state)}`); } sections.push(info.join(' | ')); // 描述 if (activity.description) { let description = activity.description.trim(); if (description.length > maxDescriptionLength) { description = description.substring(0, maxDescriptionLength) + '...'; } sections.push(`**描述**: ${description}`); } // 标签 if (activity.labels && activity.labels.length > 0) { const labelText = activity.labels.map((label) => `\`${label}\``).join(' '); sections.push(`**标签**: ${labelText}`); } // 链接 sections.push(`**链接**: [查看详情](${activity.webUrl})`); // 匹配原因 if (showMatchReasons && reasons.length > 0) { const reasonText = reasons.map((reason) => `- ${reason}`).join('\n'); sections.push(`**匹配原因**:\n${reasonText}`); } return sections.join('\n\n'); } /** * 格式化脚注 */ formatFooter() { return `---\n\n*本报告由 GitLab Activity MCP 自动生成*`; } /** * 格式化时间范围 */ formatTimeRange(timeRange) { const { start, end } = timeRange; if (this.isSameDay(start, end)) { return formatDate(start, 'yyyy年MM月dd日 (E)'); } else { return `${formatDate(start, 'yyyy年MM月dd日')}${formatDate(end, 'yyyy年MM月dd日')}`; } } /** * 获取类型图标 */ getTypeIcon(type) { const icons = { commit: '📝', issue: '🐛', merge_request: '🔀', pipeline: '🚀', }; return icons[type] || '📄'; } /** * 获取类型显示名称 */ getTypeDisplayName(type) { const names = { commit: '提交', issue: '问题', merge_request: '合并请求', pipeline: '流水线', }; return names[type] || type; } /** * 格式化状态 */ formatState(state) { const stateMap = { opened: '🟢 打开', closed: '🔴 关闭', merged: '🟣 已合并', success: '✅ 成功', failed: '❌ 失败', running: '🔄 运行中', pending: '⏳ 等待中', canceled: '⏹️ 已取消', }; return stateMap[state] || state; } /** * 按字段分组 */ groupByField(items, field) { return items.reduce((groups, item) => { const key = String(item[field]); if (!groups[key]) { groups[key] = []; } groups[key].push(item); return groups; }, {}); } /** * 检查是否为同一天 */ isSameDay(date1, date2) { return formatDate(date1, 'yyyy-MM-dd') === formatDate(date2, 'yyyy-MM-dd'); } /** * 生成简短摘要 */ generateSummary(filterResult, timeRange) { const { activities, statistics } = filterResult; if (activities.length === 0) { return `在${this.formatTimeRange(timeRange)}期间没有找到匹配的活动。`; } const parts = []; parts.push(`在${this.formatTimeRange(timeRange)}期间`); parts.push(`共有 ${statistics.total} 个活动`); // 添加主要活动类型 const topTypes = Object.entries(statistics.byType) .sort(([, a], [, b]) => b - a) .slice(0, 2) .map(([type, count]) => `${count}${this.getTypeDisplayName(type)}`); if (topTypes.length > 0) { parts.push(`包括 ${topTypes.join('、')}`); } // 添加主要项目 const topProjects = Object.entries(statistics.byProject) .sort(([, a], [, b]) => b - a) .slice(0, 2) .map(([project]) => project); if (topProjects.length > 0) { parts.push(`主要涉及 ${topProjects.join('、')} 等项目`); } return parts.join(',') + '。'; } } // 导出单例实例 export const markdownFormatter = new MarkdownFormatter(); // 导出便捷函数 export function formatActivitiesMarkdown(filterResult, timeRange, options = {}) { return markdownFormatter.formatActivities(filterResult, timeRange, options); } export function generateActivitySummary(filterResult, timeRange) { return markdownFormatter.generateSummary(filterResult, timeRange); }