diff --git a/ext-src/KanbnBurndownPanel.ts b/ext-src/KanbnBurndownPanel.ts index df7d34a..53d7c39 100644 --- a/ext-src/KanbnBurndownPanel.ts +++ b/ext-src/KanbnBurndownPanel.ts @@ -5,13 +5,17 @@ import { v4 as uuidv4 } from "uuid"; export default class KanbnBurndownPanel { private static readonly viewType = "react"; - private static panels: Record = {}; + private static panels: KanbnBurndownPanel[] = []; private readonly _panel: vscode.WebviewPanel; private readonly _extensionPath: string; private readonly _workspacePath: string; private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main"); - private readonly _panelUuid: string; + private sprintMode: boolean = true; + private sprint: string = ''; + private startDate: string = ''; + private endDate: string = ''; + private assigned: string = ''; private _disposables: vscode.Disposable[] = []; public static async show( @@ -22,51 +26,32 @@ export default class KanbnBurndownPanel { const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; // Create a panel - const panelUuid = uuidv4(); const burndownPanel = new KanbnBurndownPanel( extensionPath, workspacePath, column || vscode.ViewColumn.One, - kanbn, - panelUuid + kanbn ); - KanbnBurndownPanel.panels[panelUuid] = burndownPanel; + KanbnBurndownPanel.panels.push(burndownPanel); } public static async updateAll() { - const panels = Object.values(KanbnBurndownPanel.panels); - if (panels.length === 0) { + if (KanbnBurndownPanel.panels.length === 0) { return; } - const kanbn = panels[0]._kanbn; - let index: any; - try { - index = await kanbn.getIndex(); - } catch (error) { - vscode.window.showErrorMessage(error instanceof Error ? error.message : error); - return; - } - let tasks: any[]; - try { - tasks = (await kanbn.loadAllTrackedTasks(index)).map((task) => kanbn.hydrateTask(index, task)); - } catch (error) { - vscode.window.showErrorMessage(error instanceof Error ? error.message : error); - return; - } - panels.forEach((panel) => panel._update(index, tasks)); + const { index, tasks } = await KanbnBurndownPanel.panels[0].getKanbnIndexAndTasks(); + KanbnBurndownPanel.panels.forEach((panel) => panel._update(index, tasks)); } private constructor( extensionPath: string, workspacePath: string, column: vscode.ViewColumn, - kanbn: typeof import("@basementuniverse/kanbn/src/main"), - panelUuid: string + kanbn: typeof import("@basementuniverse/kanbn/src/main") ) { this._extensionPath = extensionPath; this._workspacePath = workspacePath; this._kanbn = kanbn; - this._panelUuid = panelUuid; // Create and show a new webview panel this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, { @@ -104,10 +89,21 @@ export default class KanbnBurndownPanel { this._panel.webview.onDidReceiveMessage( async (message) => { switch (message.command) { + // Display error message case "error": vscode.window.showErrorMessage(message.text); return; + + // Refresh the kanbn chart + case 'kanbn.refreshBurndownData': + this.sprintMode = message.sprintMode; + this.sprint = message.sprint; + this.startDate = message.startDate; + this.endDate = message.endDate; + this.assigned = message.assigned; + KanbnBurndownPanel.updateAll(); + return; } }, null, @@ -125,15 +121,46 @@ export default class KanbnBurndownPanel { } } - private async _update(index: any, tasks: any[]) { + private async _update(index: any|null, tasks: any[]|null) { + if (!index && !tasks) { + ({ index, tasks } = await this.getKanbnIndexAndTasks()); + } this._panel.webview.postMessage({ type: "burndown", index, tasks, dateFormat: this._kanbn.getDateFormat(index), + burndownData: await this._kanbn.burndown( + (this.sprintMode && this.sprint) ? [this.sprint] : null, + (!this.sprintMode && this.startDate && this.endDate) + ? [ + new Date(Date.parse(this.startDate)), + new Date(Date.parse(this.endDate)) + ] + : null, + this.assigned || null + ) }); } + private async getKanbnIndexAndTasks(): Promise<{ index: any|null, tasks: any[]|null }> { + let index: any; + try { + index = await this._kanbn.getIndex(); + } catch (error) { + vscode.window.showErrorMessage(error instanceof Error ? error.message : error); + return { index: null, tasks: null }; + } + let tasks: any[]; + try { + tasks = (await this._kanbn.loadAllTrackedTasks(index)).map((task) => this._kanbn.hydrateTask(index, task)); + } catch (error) { + vscode.window.showErrorMessage(error instanceof Error ? error.message : error); + return { index: null, tasks: null }; + } + return { index, tasks }; + } + private _getHtmlForWebview() { const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json")); const mainScript = manifest["main.js"]; diff --git a/src/App.tsx b/src/App.tsx index debd8ab..93dec7e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -117,6 +117,7 @@ function App() { tasks={tasks} sprints={sprints} burndownData={burndownData} + dateFormat={dateFormat} vscode={vscode} /> } diff --git a/src/Burndown.tsx b/src/Burndown.tsx index 304b6eb..080104d 100644 --- a/src/Burndown.tsx +++ b/src/Burndown.tsx @@ -1,9 +1,10 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { ResponsiveContainer, LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts'; import VSCodeApi from "./VSCodeApi"; import formatDate from 'dateformat'; +import dateFormat from 'dateformat'; -const Burndown = ({ name, tasks, sprints, burndownData, vscode }: { +const Burndown = ({ name, tasks, sprints, burndownData, dateFormat, vscode }: { name: string, tasks: Record, sprints: KanbnSprint[], @@ -14,23 +15,71 @@ const Burndown = ({ name, tasks, sprints, burndownData, vscode }: { to: string, dataPoints: Array<{ x: string, - y: number + y: number, + count: number, + tasks: Array<{ + eventType: string, + taskId: string + }> }> }> }, + dateFormat: string, vscode: VSCodeApi }) => { - const [sprintMode, setSprintMode] = useState(sprints.length > 0); - const [sprint, setSprint] = useState(null); - const [startDate, setStartDate] = useState(null); - const [endDate, setEndDate] = useState(null); + const hasSprints = sprints.length > 0; + const [sprintMode, setSprintMode] = useState(hasSprints); + const [sprint, setSprint] = useState(sprintMode ? burndownData.series[0].sprint.name : ''); + const [startDate, setStartDate] = useState(sprintMode ? '' : formatDate(burndownData.series[0].from, 'yyyy-mm-dd')); + const [endDate, setEndDate] = useState(sprintMode ? '' : formatDate(burndownData.series[0].to, 'yyyy-mm-dd')); + const [assigned, setAssigned] = useState(''); - const data = [ - {name: 'one', uv: 20}, - {name: 'two', uv: 80}, - {name: 'three', uv: 45}, - {name: 'four', uv: 32} - ]; + const refreshBurndownData = () => { + vscode.postMessage({ + command: 'kanbn.refreshBurndownData', + sprintMode, + sprint, + startDate, + endDate, + assigned + }); + }; + + const handleChangeSprint = ({ target: { value }}) => { + setSprint(value); + refreshBurndownData(); + }; + + const handleChangeStartDate = ({ target: { value }}) => { + setStartDate(value); + refreshBurndownData(); + }; + + const handleChangeEndDate = ({ target: { value }}) => { + setEndDate(value); + refreshBurndownData(); + }; + + const handleClickSprintMode = () => { + setSprintMode(true); + refreshBurndownData(); + }; + + const handleClickDateMode = () => { + setSprintMode(false); + refreshBurndownData(); + }; + + const chartMin = Date.parse(burndownData.series[0].from); + const chartMax = Date.parse(burndownData.series[0].to); + const chartData = burndownData.series[0].dataPoints.map(dataPoint => ({ + x: Date.parse(dataPoint.x), + y: dataPoint.y, + 'Active tasks': dataPoint.count, + 'Activity': dataPoint.tasks.map(task => `${task.eventType} ${task.taskId}`).join('\n') + })); + + const formatXAxis = date => formatDate(date, dateFormat); return ( @@ -41,12 +90,16 @@ const Burndown = ({ name, tasks, sprints, burndownData, vscode }: {
{ sprintMode - ? { sprints.length > 0 - ? sprints.map((sprint, i) => { + ? sprints.map(sprint => { return ( - + ); }) : @@ -55,31 +108,30 @@ const Burndown = ({ name, tasks, sprints, burndownData, vscode }: { : { console.log(e); }} + onChange={handleChangeStartDate} /> { console.log(e); }} + onChange={handleChangeEndDate} /> } - + }