UNPKG

bookr-cli

Version:

A terminal-based CLI tool to book time in Jira using the Tempo plugin by parsing your current Git branch name

163 lines 9.77 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Box, Text } from 'ink'; import { useEffect, useState } from 'react'; import { createClient } from '../api/jira-client.js'; import { createTempoClient } from '../api/tempo-client.js'; import { getCurrentBranch, getTicketFromBranch, isGitRepository } from '../utils/git.js'; import { formatTimeForDisplay, isValidTimeFormat, parseTimeToSeconds, } from '../utils/time-parser.js'; import { storeTempoWorklog } from '../utils/worklog-storage.js'; import { roundToNearest15Minutes } from '../utils/date.js'; import { ConfirmationPrompt } from './ConfirmationPrompt.js'; export const App = ({ input: _input, flags }) => { const [currentBranch, setCurrentBranch] = useState('Loading...'); const [jiraIssue, setJiraIssue] = useState(null); const [appState, setAppState] = useState('loading'); const [error, setError] = useState(null); const [timeSpent, setTimeSpent] = useState(''); useEffect(() => { async function initialize() { try { // Check if we're in a Git repository (only required if no ticket provided) if (!flags.ticket && !isGitRepository()) { setError('Not in a Git repository and no ticket provided. Please either run from a Git repository or specify a ticket: bookr PROJ-123 2h30m'); setAppState('error'); return; } // Get current Git branch (only if we need it) if (!flags.ticket) { const branch = getCurrentBranch(); setCurrentBranch(branch); } else { setCurrentBranch('N/A (ticket provided)'); } // Check if time was provided if (!flags.time) { setError('No time specified. Please provide time (e.g., "2h30m", "1h15m", "45m")'); setAppState('error'); return; } // Validate time format if (!isValidTimeFormat(flags.time)) { setError(`Invalid time format: "${flags.time}". Use formats like "2h30m", "1h15m", "45m", "2.5h"`); setAppState('invalid-time'); return; } setTimeSpent(flags.time); // Use provided ticket or extract from branch name let issueKey = null; if (flags.ticket) { // Use explicitly provided ticket issueKey = flags.ticket; } else { // Try to extract JIRA issue key from branch name const branch = getCurrentBranch(); issueKey = getTicketFromBranch(branch); } if (!issueKey) { setAppState('no-issue'); return; } try { const client = createClient(); const issue = await client.getIssue(issueKey); setJiraIssue(issue); setAppState('confirm'); } catch (jiraError) { setError(`Could not fetch JIRA issue ${issueKey}: ${jiraError instanceof Error ? jiraError.message : 'Unknown error'}`); setAppState('error'); } } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); setAppState('error'); } } initialize(); }, [flags.time, flags.ticket]); const handleConfirm = async () => { if (!jiraIssue || !timeSpent) return; setAppState('creating'); try { const jiraClient = createClient(); const tempoClient = createTempoClient(); const timeSpentSeconds = parseTimeToSeconds(timeSpent); const comment = flags.description || 'Work logged via Bookr CLI'; const user = await jiraClient.getCurrentUser(); // Calculate start time: current time minus time spent, rounded to nearest 15 minutes const now = new Date(); const startTime = new Date(now.getTime() - (timeSpentSeconds * 1000)); const roundedStartTime = roundToNearest15Minutes(startTime); // Format startDate as yyyy-MM-dd in UTC const yyyyMMdd = roundedStartTime.toISOString().slice(0, 10); // Format startTime as HH:mm:ss const startTimeFormatted = roundedStartTime.toTimeString().slice(0, 8); const createdWorklog = await tempoClient.addWorklog({ issueId: jiraIssue.id, issueKey: jiraIssue.key, timeSpentSeconds, comment, startDate: yyyyMMdd, startTime: startTimeFormatted, authorAccountId: user.accountId, }); // Store the worklog locally for potential undo storeTempoWorklog(jiraIssue, createdWorklog, comment); setAppState('success'); } catch (error) { setError(`Failed to create worklog: ${error instanceof Error ? error.message : 'Unknown error'}`); setAppState('error'); } }; const handleCancel = () => { setAppState('cancelled'); }; // Loading state if (appState === 'loading') { return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { children: "\u23F3 Loading..." }) })); } // Error state if (appState === 'error') { return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsxs(Text, { color: "red", children: ["\u274C Error: ", error] }) })); } // Invalid time format if (appState === 'invalid-time') { return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { color: "red", children: ["\u274C ", error] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "Valid time formats:" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: " \u2022 2h30m (2 hours 30 minutes)" }), _jsx(Text, { children: " \u2022 1h15m (1 hour 15 minutes)" }), _jsx(Text, { children: " \u2022 45m (45 minutes)" }), _jsx(Text, { children: " \u2022 2.5h (2.5 hours)" }), _jsx(Text, { children: " \u2022 90m (90 minutes)" })] })] })); } // No JIRA issue found if (appState === 'no-issue') { return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [flags.ticket ? (_jsx(Text, { color: "yellow", children: "\u26A0\uFE0F No JIRA issue key provided" })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "yellow", children: ["\u26A0\uFE0F No JIRA issue key found in branch \"", currentBranch, "\""] }), _jsx(Text, { color: "gray", children: "Expected format: feature/PROJ-123, bugfix/PROJ-456, etc." })] })), _jsx(Text, { color: "gray", children: "Usage: bookr [TICKET] [TIME] -m \"description\"" }), _jsx(Text, { color: "gray", children: "Examples: bookr PROJ-123 2h30m -m \"Fixed bug\"" })] }) })); } // Confirmation state if (appState === 'confirm' && jiraIssue) { return (_jsx(ConfirmationPrompt, { issue: jiraIssue, timeSpent: timeSpent, description: flags.description || undefined, onConfirm: handleConfirm, onCancel: handleCancel })); } // Creating worklog state if (appState === 'creating') { return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { children: "\u23F3 Creating worklog..." }) })); } // Success state if (appState === 'success' && jiraIssue) { // Construct JIRA issue URL const client = createClient(); const issueUrl = `${client.getBaseUrl()}/browse/${jiraIssue.key}`; // Calculate and format the start time for display (rounded to nearest 15 minutes) const timeSpentSeconds = parseTimeToSeconds(timeSpent); const now = new Date(); const startTime = new Date(now.getTime() - (timeSpentSeconds * 1000)); const roundedStartTime = roundToNearest15Minutes(startTime); const startTimeFormatted = roundedStartTime.toLocaleTimeString(); return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { color: "green", bold: true, children: "\u2705 Worklog created!" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "Issue:" }), " ", jiraIssue.key, " - ", jiraIssue.fields.summary] }), _jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "Time:" }), " ", formatTimeForDisplay(timeSpent), " (", timeSpent, ")"] }), _jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "Start Time:" }), " ", startTimeFormatted] }), flags.description && (_jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "Description:" }), " ", flags.description] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "blue", underline: true, children: ["\uD83D\uDD17 ", _jsx(Text, { color: "cyan", children: issueUrl })] }) })] })] })); } // Cancelled state if (appState === 'cancelled') { return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { color: "yellow", children: "\u274C Worklog creation cancelled" }) })); } // Fallback return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { children: "Unknown state" }) })); }; //# sourceMappingURL=App.js.map