gitlab-activity-mcp
Version:
GitLab Activity MCP Server - Generate professional activity reports and analysis
297 lines (296 loc) • 11.5 kB
JavaScript
/**
* 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);
}