UNPKG

@tosin2013/kanbn

Version:

A CLI Kanban board with AI-powered task management features

186 lines (168 loc) 6.42 kB
const kanbnModule = require('../main'); const utility = require('../utility'); const asciichart = require('asciichart'); const term = require('terminal-kit').terminal; const chrono = require('chrono-node'); const formatDate = require('dateformat'); module.exports = async args => { try { const kanbn = kanbnModule(); // Make sure kanbn has been initialised if (!await kanbn.initialised()) { throw new Error('Kanbn has not been initialised in this folder\nTry running: kanbn init'); } const index = await kanbn.getIndex(); // Get sprint numbers or names let sprints = null; if (args.sprint) { sprints = utility.arrayArg(args.sprint) .flatMap(s => s.split(',')) .map(s => s.trim()) .filter(s => s.length > 0) .map(s => { const sprintNumber = parseInt(s); return isNaN(sprintNumber) ? s : sprintNumber; }); // Verify sprint columns exist for (const sprint of sprints) { const sprintName = typeof sprint === 'number' ? `Sprint ${sprint}` : sprint; if (!index.columns[sprintName]) { throw new Error(`Sprint "${sprintName}" not found`); } } } // Get dates let dates = null; if (args.date) { dates = utility.arrayArg(args.date); if (dates.length) { for (let i = 0; i < dates.length; i++) { const dateValue = chrono.parseDate(dates[i]); if (dateValue === null) { throw new Error(`Unable to parse date: "${dates[i]}"`); } dates[i] = dateValue; } } } // Get assigned let assigned = null; if (args.assigned) { assigned = utility.strArg(args.assigned); } // Get columns let columns = null; if (args.column) { columns = utility.arrayArg(args.column); // Verify columns exist for (const column of columns) { if (!index.columns[column]) { throw new Error(`Column "${column}" not found`); } } } // Get normalisation mode let normalise = null; if (args.normalise) { if (args.normalise === '0') { throw new Error('Normalisation value cannot be zero'); } normalise = args.normalise.toLowerCase(); if (!['days', 'hours', 'minutes', 'seconds'].includes(normalise)) { normalise = 'auto'; } } // Get burndown data const data = await kanbn.burndown(sprints, dates, assigned, columns, normalise); if (args.json) { // Format data for JSON output if (!data.series || data.series.length === 0 || !data.series[0].dataPoints || data.series[0].dataPoints.length === 0) { // Return empty data if no data points are available return { total: 0, completed: 0, data: [] }; } const result = { total: data.series[0].total || 0, completed: (data.series[0].total || 0) - (data.series[0].dataPoints[0]?.y || 0), data: data.series[0].dataPoints.map(point => ({ date: point.x, remaining: point.y })) }; return result; } // Render chart for console output const PADDING = ' '; // Ensure we have a valid terminal width, with a reasonable fallback const termWidth = term.width || 80; // Default to 80 if term.width is undefined const width = Math.max(10, termWidth - (PADDING.length + 1)); // Ensure minimum width of 10 const plots = []; for (const s of data.series) { // Ensure we have data points before creating a plot if (!s.dataPoints || s.dataPoints.length === 0) { console.log('No data points available for the selected time period.'); return ''; } const plot = []; // Ensure the time range is valid const timeRange = s.to.getTime() - s.from.getTime(); if (timeRange <= 0) { // If time range is invalid, create a flat line with the current workload // Ensure we don't create an array that's too large const safeWidth = Math.min(width, 1000); // Cap at 1000 points to prevent memory issues for (let i = 0; i < safeWidth; i++) { plot.push(s.dataPoints[0].y); } } else { // Calculate points for the plot const delta = Math.max(1, Math.floor(timeRange / width)); // Ensure we don't create an array that's too large const safeWidth = Math.min(width, 1000); // Cap at 1000 points to prevent memory issues for (let i = 0; i < safeWidth; i++) { const targetTime = s.from.getTime() + i * delta; const dataPoint = s.dataPoints.find(d => d.x.getTime() >= targetTime) || s.dataPoints[s.dataPoints.length - 1]; plot.push(dataPoint.y); } } plots.push(plot); } const dateFormat = kanbn.getDateFormat(index); console.log(`${formatDate(data.series[0].from, dateFormat)} to ${formatDate(data.series[0].to, dateFormat)}:`); // Check if we have enough data to plot if (plots.length === 0 || plots[0].length === 0) { console.log(' 0┼───────────────────────────────────────────────────────────────────────────────────── '); } else { try { console.log(asciichart.plot( plots, { offset: 2, height: 10, padding: PADDING, format: x => (PADDING + x.toFixed(0)).slice(-PADDING.length), colors: [ asciichart.default, asciichart.green, asciichart.blue, asciichart.red ] } )); } catch (chartError) { // Fallback to a simple line if chart generation fails console.log(' 0┼───────────────────────────────────────────────────────────────────────────────────── '); console.log(' (Not enough data to generate a meaningful chart)'); } } return ''; } catch (error) { if (args.json) { throw error; } utility.error(error); return ''; } };