@orengrinker/jira-mcp-server
Version:
A comprehensive Model Context Protocol server for Jira integration with issue management, board operations, time tracking, and project management capabilities
154 lines (130 loc) • 5.25 kB
text/typescript
import { JiraApiClient } from '../jiraApiClient.js';
import { ToolResult, WorklogRequest } from '../types/index.js';
import { Logger } from '../utils/logger.js';
import { formatMarkdownTable } from '../utils/formatters.js';
export class WorklogService {
private logger: Logger;
constructor(private apiClient: JiraApiClient) {
this.logger = new Logger('WorklogService');
}
async addWorklog(params: WorklogRequest): Promise<ToolResult> {
try {
this.logger.debug(`Adding worklog to issue: ${params.issueKey}`, params);
const result = await this.apiClient.addWorklog(
params.issueKey,
params.timeSpent,
params.comment,
params.startDate
);
return {
content: [
{
type: 'text',
text: `# ✅ Work Log Added Successfully!
**Issue**: ${params.issueKey}
**Time Spent**: ${params.timeSpent}
**Work Log ID**: ${result.id}
**Started**: ${new Date(result.started).toLocaleString()}
${params.comment ? `**Comment**: ${params.comment}` : ''}
## Work Details
- **Author**: ${result.author.displayName}
- **Created**: ${new Date(result.created).toLocaleString()}
- **Time in Seconds**: ${result.timeSpentSeconds}
## Quick Actions
- View all worklogs: Use \`get_worklogs\` with issueKey: ${params.issueKey}
- View issue details: Use \`get_issue_details\` with issueKey: ${params.issueKey}
- Add another worklog: Use \`add_worklog\` again
## Time Format Examples
- Hours: "2h", "4h 30m"
- Days: "1d", "2d 4h"
- Minutes: "30m", "1h 15m"
- Mixed: "1w 2d 3h 30m"`,
},
],
};
} catch (error) {
this.logger.error(`Failed to add worklog to ${params.issueKey}:`, error);
throw new Error(`Failed to add work log: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async getWorklogs(issueKey: string): Promise<ToolResult> {
try {
this.logger.debug(`Fetching worklogs for issue: ${issueKey}`);
const response = await this.apiClient.getWorklogs(issueKey);
const worklogs = response.worklogs || [];
if (worklogs.length === 0) {
return {
content: [
{
type: 'text',
text: `# ⏱️ Work Logs for ${issueKey}
No work logs found for this issue.
## Quick Actions
- Add work log: Use \`add_worklog\` with issueKey: ${issueKey}
- View issue details: Use \`get_issue_details\` with issueKey: ${issueKey}`,
},
],
};
}
const tableData = worklogs.map((worklog: any) => [
worklog.author.displayName,
worklog.timeSpent,
new Date(worklog.started).toLocaleDateString(),
new Date(worklog.created).toLocaleDateString(),
worklog.comment ? 'Yes' : 'No',
]);
const markdownTable = formatMarkdownTable(
['Author', 'Time Spent', 'Work Date', 'Logged Date', 'Has Comment'],
tableData
);
const totalSeconds = worklogs.reduce((sum: number, w: any) => sum + w.timeSpentSeconds, 0);
const totalTime = this.formatDuration(totalSeconds);
const uniqueAuthors = [...new Set(worklogs.map((w: any) => w.author.displayName))];
return {
content: [
{
type: 'text',
text: `# ⏱️ Work Logs for ${issueKey}
**Total Time Logged**: ${totalTime}
**Total Entries**: ${worklogs.length}
**Contributors**: ${uniqueAuthors.length}
${markdownTable}
## Summary
- **Total Time (seconds)**: ${totalSeconds}
- **Contributors**: ${uniqueAuthors.join(', ')}
- **Latest Entry**: ${new Date(Math.max(...worklogs.map((w: any) => new Date(w.created).getTime()))).toLocaleString()}
- **Earliest Entry**: ${new Date(Math.min(...worklogs.map((w: any) => new Date(w.created).getTime()))).toLocaleString()}
## Quick Actions
- Add more work: Use \`add_worklog\` with issueKey: ${issueKey}
- View issue details: Use \`get_issue_details\` with issueKey: ${issueKey}
### Recent Work Log Details
${worklogs.slice(-3).map((w: any) => `
**${w.author.displayName}** - ${w.timeSpent} (${new Date(w.started).toLocaleDateString()})
${w.comment ? `Comment: ${typeof w.comment === 'string' ? w.comment : 'Rich text comment'}` : 'No comment'}
`).join('\n')}`,
},
],
};
} catch (error) {
this.logger.error(`Failed to get worklogs for ${issueKey}:`, error);
throw new Error(`Failed to retrieve work logs: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
private formatDuration(seconds: number): string {
if (seconds < 60) {
return `${seconds}s`;
} else if (seconds < 3600) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
} else if (seconds < 86400) {
const hours = Math.floor(seconds / 3600);
const remainingMinutes = Math.floor((seconds % 3600) / 60);
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
} else {
const days = Math.floor(seconds / 86400);
const remainingHours = Math.floor((seconds % 86400) / 3600);
return remainingHours > 0 ? `${days}d ${remainingHours}h` : `${days}d`;
}
}
}