Skip to content

Instantly share code, notes, and snippets.

@moosh3
Last active December 20, 2024 17:15
Show Gist options
  • Save moosh3/6f96b5380fd9e5c43e6ab1f5419bd70a to your computer and use it in GitHub Desktop.
Save moosh3/6f96b5380fd9e5c43e6ab1f5419bd70a to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
class JiraClient {
constructor(baseUrl, email, apiToken) {
this.baseUrl = baseUrl;
this.auth = Buffer.from(`${email}:${apiToken}`).toString('base64');
}
async getTicket(ticketId) {
try {
const response = await fetch(`${this.baseUrl}/rest/api/3/issue/${ticketId}`, {
headers: {
'Authorization': `Basic ${this.auth}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch ticket: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`Error fetching ticket ${ticketId}:`, error);
throw error;
}
}
}
class JiraSummarizer {
constructor(jiraClient) {
this.jiraClient = jiraClient;
}
async generateTicketSummary(ticketId) {
try {
const ticket = await this.jiraClient.getTicket(ticketId);
return {
key: ticket.key,
summary: ticket.fields.summary,
status: ticket.fields.status.name,
priority: ticket.fields.priority.name,
assignee: ticket.fields.assignee?.displayName || 'Unassigned',
created: new Date(ticket.fields.created).toLocaleDateString(),
updated: new Date(ticket.fields.updated).toLocaleDateString(),
description: this.summarizeDescription(ticket.fields.description),
comments: this.summarizeComments(ticket.fields.comment)
};
} catch (error) {
console.error('Error generating ticket summary:', error);
throw error;
}
}
summarizeDescription(description) {
if (!description) return 'No description provided';
// Extract main points from description
const sentences = description.split(/[.!?]+/);
return sentences.slice(0, 3).join('. ') + (sentences.length > 3 ? '...' : '');
}
summarizeComments(comments) {
if (!comments?.comments?.length) return 'No comments';
return comments.comments.slice(-3).map(comment => ({
author: comment.author.displayName,
created: new Date(comment.created).toLocaleDateString(),
body: this.summarizeDescription(comment.body)
}));
}
}
// Check for required environment variables
const requiredEnvVars = ['JIRA_URL', 'JIRA_EMAIL', 'JIRA_API_TOKEN'];
const missingEnvVars = requiredEnvVars.filter(varName => !process.env[varName]);
if (missingEnvVars.length > 0) {
console.error('Error: Missing required environment variables:', missingEnvVars.join(', '));
console.error('Please set these environment variables before running the script.');
console.error('Example:');
console.error('export JIRA_URL="https://your-domain.atlassian.net"');
console.error('export JIRA_EMAIL="[email protected]"');
console.error('export JIRA_API_TOKEN="your-api-token"');
process.exit(1);
}
// Get ticket ID from command line argument
const ticketId = process.argv[2];
if (!ticketId) {
console.error('Error: Please provide a ticket ID');
console.error('Usage: ./jira.js TICKET-123');
process.exit(1);
}
// Initialize the client and summarizer
const jiraClient = new JiraClient(
process.env.JIRA_URL,
process.env.JIRA_EMAIL,
process.env.JIRA_API_TOKEN
);
const summarizer = new JiraSummarizer(jiraClient);
// Get and display the ticket summary
async function main() {
try {
const summary = await summarizer.generateTicketSummary(ticketId);
console.log('\nTicket Summary:');
console.log('==============');
console.log(`Key: ${summary.key}`);
console.log(`Summary: ${summary.summary}`);
console.log(`Status: ${summary.status}`);
console.log(`Priority: ${summary.priority}`);
console.log(`Assignee: ${summary.assignee}`);
console.log(`Created: ${summary.created}`);
console.log(`Updated: ${summary.updated}`);
console.log('\nDescription:');
console.log(summary.description);
console.log('\nRecent Comments:');
if (Array.isArray(summary.comments)) {
summary.comments.forEach(comment => {
console.log(`\n${comment.author} (${comment.created}):`);
console.log(comment.body);
});
} else {
console.log(summary.comments);
}
} catch (error) {
console.error('Failed to get ticket summary:', error);
process.exit(1);
}
}
main();
class JiraClient {
constructor(baseUrl, email, apiToken) {
this.baseUrl = baseUrl;
this.auth = Buffer.from(`${email}:${apiToken}`).toString('base64');
}
async getTicket(ticketId) {
try {
const response = await fetch(`${this.baseUrl}/rest/api/3/issue/${ticketId}`, {
headers: {
'Authorization': `Basic ${this.auth}`,
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch ticket: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`Error fetching ticket ${ticketId}:`, error);
throw error;
}
}
async searchTickets(jql) {
try {
const response = await fetch(`${this.baseUrl}/rest/api/3/search`, {
method: 'POST',
headers: {
'Authorization': `Basic ${this.auth}`,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
jql,
maxResults: 50
})
});
if (!response.ok) {
throw new Error(`Failed to search tickets: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Error searching tickets:', error);
throw error;
}
}
async getProjectSummary(projectKey) {
try {
const jql = `project = ${projectKey} ORDER BY updated DESC`;
const tickets = await this.searchTickets(jql);
const summary = {
totalIssues: tickets.total,
statusBreakdown: {},
priorityBreakdown: {},
recentUpdates: []
};
tickets.issues.forEach(issue => {
// Count issues by status
const status = issue.fields.status.name;
summary.statusBreakdown[status] = (summary.statusBreakdown[status] || 0) + 1;
// Count issues by priority
const priority = issue.fields.priority.name;
summary.priorityBreakdown[priority] = (summary.priorityBreakdown[priority] || 0) + 1;
// Track recent updates
if (summary.recentUpdates.length < 5) {
summary.recentUpdates.push({
key: issue.key,
summary: issue.fields.summary,
status,
updated: issue.fields.updated
});
}
});
return summary;
} catch (error) {
console.error(`Error getting project summary for ${projectKey}:`, error);
throw error;
}
}
}
class JiraSummarizer {
constructor(jiraClient) {
this.jiraClient = jiraClient;
}
async generateTicketSummary(ticketId) {
try {
const ticket = await this.jiraClient.getTicket(ticketId);
return {
key: ticket.key,
summary: ticket.fields.summary,
status: ticket.fields.status.name,
priority: ticket.fields.priority.name,
assignee: ticket.fields.assignee?.displayName || 'Unassigned',
created: new Date(ticket.fields.created).toLocaleDateString(),
updated: new Date(ticket.fields.updated).toLocaleDateString(),
description: this.summarizeDescription(ticket.fields.description),
comments: this.summarizeComments(ticket.fields.comment)
};
} catch (error) {
console.error('Error generating ticket summary:', error);
throw error;
}
}
summarizeDescription(description) {
if (!description) return 'No description provided';
// Extract main points from description
// This is a simple implementation - could be enhanced with NLP
const sentences = description.split(/[.!?]+/);
return sentences.slice(0, 3).join('. ') + (sentences.length > 3 ? '...' : '');
}
summarizeComments(comments) {
if (!comments?.comments?.length) return 'No comments';
return comments.comments.slice(-3).map(comment => ({
author: comment.author.displayName,
created: new Date(comment.created).toLocaleDateString(),
body: this.summarizeDescription(comment.body)
}));
}
async generateProjectStatus(projectKey) {
try {
const summary = await this.jiraClient.getProjectSummary(projectKey);
return {
overview: `Project ${projectKey} has ${summary.totalIssues} total issues`,
status: this.formatBreakdown(summary.statusBreakdown),
priority: this.formatBreakdown(summary.priorityBreakdown),
recentActivity: summary.recentUpdates.map(update =>
`${update.key}: ${update.summary} (${update.status})`
).join('\n')
};
} catch (error) {
console.error('Error generating project status:', error);
throw error;
}
}
formatBreakdown(breakdown) {
return Object.entries(breakdown)
.map(([key, value]) => `${key}: ${value}`)
.join(', ');
}
}
// Example usage:
const jiraClient = new JiraClient(
'https://your-domain.atlassian.net',
'[email protected]',
'your-api-token'
);
const summarizer = new JiraSummarizer(jiraClient);
// Get ticket summary
async function getTicketSummary(ticketId) {
try {
const summary = await summarizer.generateTicketSummary(ticketId);
console.log('Ticket Summary:', JSON.stringify(summary, null, 2));
} catch (error) {
console.error('Failed to get ticket summary:', error);
}
}
// Get project status
async function getProjectStatus(projectKey) {
try {
const status = await summarizer.generateProjectStatus(projectKey);
console.log('Project Status:', JSON.stringify(status, null, 2));
} catch (error) {
console.error('Failed to get project status:', error);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment