Skip to main content
Glama
timesheetGenerator.ts6.53 kB
import { DayActivity, TimesheetEntry, GitLabActivity, CalendarEvent } from '../types/index.js'; import { formatDate, formatDayOfWeek } from './dateUtils.js'; export class TimesheetGenerator { generateTimesheet(activities: DayActivity[]): TimesheetEntry[] { return activities.map(activity => { const description = this.generateDayDescription(activity); const charCount = this.countCharacters(description); return { date: formatDate(activity.date), dayOfWeek: formatDayOfWeek(activity.date), description, wordCount: charCount, // Now contains actual character count without truncation }; }); } private generateDayDescription(activity: DayActivity): string { const parts: string[] = []; // Add meetings first if (activity.meetings.length > 0) { parts.push(this.formatMeetings(activity.meetings)); } // Add GitLab activities const gitlabDesc = this.formatGitLabActivity(activity.gitlabActivity); if (gitlabDesc) { parts.push(gitlabDesc); } let fullDescription = parts.join(' '); // Return full description - LLM will handle summarization to 255 characters return fullDescription; } private formatMeetings(meetings: CalendarEvent[]): string { if (meetings.length === 0) return ''; const meetingTitles = meetings.map(m => m.title); if (meetingTitles.length === 1) { return `Attended ${meetingTitles[0]}.`; } else if (meetingTitles.length === 2) { return `Attended ${meetingTitles[0]} and ${meetingTitles[1]}.`; } else { const lastMeeting = meetingTitles.pop(); return `Attended ${meetingTitles.join(', ')}, and ${lastMeeting}.`; } } private formatGitLabActivity(activity: GitLabActivity): string { const parts: string[] = []; // Group commits by project const commitsByProject = this.groupCommitsByProject(activity.commits); if (Object.keys(commitsByProject).length > 0) { const commitDesc = this.formatCommits(commitsByProject); if (commitDesc) parts.push(commitDesc); } // Format merge requests if (activity.mergeRequests.length > 0) { const mrDesc = this.formatMergeRequests(activity.mergeRequests); if (mrDesc) parts.push(mrDesc); } // Format issues if (activity.issues.length > 0) { const issueDesc = this.formatIssues(activity.issues); if (issueDesc) parts.push(issueDesc); } return parts.join(' '); } private groupCommitsByProject( commits: GitLabActivity['commits'] ): Record<string, Array<{ message: string; branch: string }>> { const grouped: Record<string, Array<{ message: string; branch: string }>> = {}; for (const commit of commits) { if (!grouped[commit.project]) { grouped[commit.project] = []; } grouped[commit.project].push({ message: commit.message, branch: commit.branch, }); } return grouped; } private formatCommits( commitsByProject: Record<string, Array<{ message: string; branch: string }>> ): string { const projectDescriptions: string[] = []; for (const [project, commits] of Object.entries(commitsByProject)) { if (commits.length === 1) { projectDescriptions.push(`committed "${commits[0].message}" to ${project}`); } else { const messages = commits.map(c => `"${c.message}"`).join(', '); projectDescriptions.push(`made ${commits.length} commits to ${project}: ${messages}`); } } if (projectDescriptions.length === 0) return ''; return 'Worked on ' + projectDescriptions.join('; ') + '.'; } private formatMergeRequests(mergeRequests: GitLabActivity['mergeRequests']): string { const grouped = { created: mergeRequests.filter(mr => mr.action === 'created'), reviewed: mergeRequests.filter(mr => mr.action === 'reviewed'), approved: mergeRequests.filter(mr => mr.action === 'approved'), commented: mergeRequests.filter(mr => mr.action === 'commented'), }; const parts: string[] = []; if (grouped.created.length > 0) { const titles = grouped.created.map(mr => `"${mr.title}" in ${mr.project}`); parts.push(`Created MR${grouped.created.length > 1 ? 's' : ''}: ${titles.join(', ')}`); } if (grouped.reviewed.length > 0) { const titles = grouped.reviewed.map(mr => `"${mr.title}" in ${mr.project}`); parts.push(`Reviewed MR${grouped.reviewed.length > 1 ? 's' : ''}: ${titles.join(', ')}`); } if (grouped.approved.length > 0) { const titles = grouped.approved.map(mr => `"${mr.title}" in ${mr.project}`); parts.push(`Approved MR${grouped.approved.length > 1 ? 's' : ''}: ${titles.join(', ')}`); } if (grouped.commented.length > 0) { const titles = grouped.commented.map(mr => `"${mr.title}" in ${mr.project}`); parts.push(`Commented on MR${grouped.commented.length > 1 ? 's' : ''}: ${titles.join(', ')}`); } return parts.join('. ') + (parts.length > 0 ? '.' : ''); } private formatIssues(issues: GitLabActivity['issues']): string { const parts: string[] = []; const commented = issues.filter(i => i.action === 'commented'); const statusChanged = issues.filter(i => i.action === 'status_changed'); const assigned = issues.filter(i => i.action === 'assigned'); if (commented.length > 0) { const titles = commented.map(i => `"${i.title}" in ${i.project}`); parts.push(`Commented on issue${commented.length > 1 ? 's' : ''}: ${titles.join(', ')}`); } if (statusChanged.length > 0) { const titles = statusChanged.map(i => `"${i.title}" in ${i.project}${i.details ? ` (${i.details})` : ''}`); parts.push(`Updated issue${statusChanged.length > 1 ? 's' : ''}: ${titles.join(', ')}`); } if (assigned.length > 0) { const titles = assigned.map(i => `"${i.title}" in ${i.project}`); parts.push(`Assigned to issue${assigned.length > 1 ? 's' : ''}: ${titles.join(', ')}`); } return parts.join('. ') + (parts.length > 0 ? '.' : ''); } private countCharacters(text: string): number { return text.length; } formatTimesheetOutput(entries: TimesheetEntry[]): string { let output = '# Monthly Timesheet (Raw Data)\n\n'; for (const entry of entries) { output += `## ${entry.date} (${entry.dayOfWeek})\n`; output += `${entry.description}\n`; output += `*Current length: ${entry.wordCount} characters*\n\n`; } return output; } }

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/sharadmathuratthepsi/timesheet-mcp'

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