More work on burndown chart
This commit is contained in:
parent
ab42299b6b
commit
2998afa356
@ -5,13 +5,17 @@ import { v4 as uuidv4 } from "uuid";
|
|||||||
|
|
||||||
export default class KanbnBurndownPanel {
|
export default class KanbnBurndownPanel {
|
||||||
private static readonly viewType = "react";
|
private static readonly viewType = "react";
|
||||||
private static panels: Record<string, KanbnBurndownPanel> = {};
|
private static panels: KanbnBurndownPanel[] = [];
|
||||||
|
|
||||||
private readonly _panel: vscode.WebviewPanel;
|
private readonly _panel: vscode.WebviewPanel;
|
||||||
private readonly _extensionPath: string;
|
private readonly _extensionPath: string;
|
||||||
private readonly _workspacePath: string;
|
private readonly _workspacePath: string;
|
||||||
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
|
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[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
public static async show(
|
public static async show(
|
||||||
@ -22,51 +26,32 @@ export default class KanbnBurndownPanel {
|
|||||||
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
|
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
|
||||||
|
|
||||||
// Create a panel
|
// Create a panel
|
||||||
const panelUuid = uuidv4();
|
|
||||||
const burndownPanel = new KanbnBurndownPanel(
|
const burndownPanel = new KanbnBurndownPanel(
|
||||||
extensionPath,
|
extensionPath,
|
||||||
workspacePath,
|
workspacePath,
|
||||||
column || vscode.ViewColumn.One,
|
column || vscode.ViewColumn.One,
|
||||||
kanbn,
|
kanbn
|
||||||
panelUuid
|
|
||||||
);
|
);
|
||||||
KanbnBurndownPanel.panels[panelUuid] = burndownPanel;
|
KanbnBurndownPanel.panels.push(burndownPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async updateAll() {
|
public static async updateAll() {
|
||||||
const panels = Object.values(KanbnBurndownPanel.panels);
|
if (KanbnBurndownPanel.panels.length === 0) {
|
||||||
if (panels.length === 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const kanbn = panels[0]._kanbn;
|
const { index, tasks } = await KanbnBurndownPanel.panels[0].getKanbnIndexAndTasks();
|
||||||
let index: any;
|
KanbnBurndownPanel.panels.forEach((panel) => panel._update(index, tasks));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
extensionPath: string,
|
extensionPath: string,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
column: vscode.ViewColumn,
|
column: vscode.ViewColumn,
|
||||||
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
|
kanbn: typeof import("@basementuniverse/kanbn/src/main")
|
||||||
panelUuid: string
|
|
||||||
) {
|
) {
|
||||||
this._extensionPath = extensionPath;
|
this._extensionPath = extensionPath;
|
||||||
this._workspacePath = workspacePath;
|
this._workspacePath = workspacePath;
|
||||||
this._kanbn = kanbn;
|
this._kanbn = kanbn;
|
||||||
this._panelUuid = panelUuid;
|
|
||||||
|
|
||||||
// Create and show a new webview panel
|
// Create and show a new webview panel
|
||||||
this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, {
|
this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, {
|
||||||
@ -104,10 +89,21 @@ export default class KanbnBurndownPanel {
|
|||||||
this._panel.webview.onDidReceiveMessage(
|
this._panel.webview.onDidReceiveMessage(
|
||||||
async (message) => {
|
async (message) => {
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
|
|
||||||
// Display error message
|
// Display error message
|
||||||
case "error":
|
case "error":
|
||||||
vscode.window.showErrorMessage(message.text);
|
vscode.window.showErrorMessage(message.text);
|
||||||
return;
|
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,
|
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({
|
this._panel.webview.postMessage({
|
||||||
type: "burndown",
|
type: "burndown",
|
||||||
index,
|
index,
|
||||||
tasks,
|
tasks,
|
||||||
dateFormat: this._kanbn.getDateFormat(index),
|
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() {
|
private _getHtmlForWebview() {
|
||||||
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
|
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
|
||||||
const mainScript = manifest["main.js"];
|
const mainScript = manifest["main.js"];
|
||||||
|
@ -117,6 +117,7 @@ function App() {
|
|||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
sprints={sprints}
|
sprints={sprints}
|
||||||
burndownData={burndownData}
|
burndownData={burndownData}
|
||||||
|
dateFormat={dateFormat}
|
||||||
vscode={vscode}
|
vscode={vscode}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
113
src/Burndown.tsx
113
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 { ResponsiveContainer, LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';
|
||||||
import VSCodeApi from "./VSCodeApi";
|
import VSCodeApi from "./VSCodeApi";
|
||||||
import formatDate from 'dateformat';
|
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,
|
name: string,
|
||||||
tasks: Record<string, KanbnTask>,
|
tasks: Record<string, KanbnTask>,
|
||||||
sprints: KanbnSprint[],
|
sprints: KanbnSprint[],
|
||||||
@ -14,23 +15,71 @@ const Burndown = ({ name, tasks, sprints, burndownData, vscode }: {
|
|||||||
to: string,
|
to: string,
|
||||||
dataPoints: Array<{
|
dataPoints: Array<{
|
||||||
x: string,
|
x: string,
|
||||||
y: number
|
y: number,
|
||||||
|
count: number,
|
||||||
|
tasks: Array<{
|
||||||
|
eventType: string,
|
||||||
|
taskId: string
|
||||||
|
}>
|
||||||
}>
|
}>
|
||||||
}>
|
}>
|
||||||
},
|
},
|
||||||
|
dateFormat: string,
|
||||||
vscode: VSCodeApi
|
vscode: VSCodeApi
|
||||||
}) => {
|
}) => {
|
||||||
const [sprintMode, setSprintMode] = useState(sprints.length > 0);
|
const hasSprints = sprints.length > 0;
|
||||||
const [sprint, setSprint] = useState(null);
|
const [sprintMode, setSprintMode] = useState(hasSprints);
|
||||||
const [startDate, setStartDate] = useState(null);
|
const [sprint, setSprint] = useState(sprintMode ? burndownData.series[0].sprint.name : '');
|
||||||
const [endDate, setEndDate] = useState(null);
|
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 = [
|
const refreshBurndownData = () => {
|
||||||
{name: 'one', uv: 20},
|
vscode.postMessage({
|
||||||
{name: 'two', uv: 80},
|
command: 'kanbn.refreshBurndownData',
|
||||||
{name: 'three', uv: 45},
|
sprintMode,
|
||||||
{name: 'four', uv: 32}
|
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 (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -41,12 +90,16 @@ const Burndown = ({ name, tasks, sprints, burndownData, vscode }: {
|
|||||||
<form>
|
<form>
|
||||||
{
|
{
|
||||||
sprintMode
|
sprintMode
|
||||||
? <select className="kanbn-burndown-settings-sprint-select" onChange={e => { console.log(e); }}>
|
? <select
|
||||||
|
value={sprint}
|
||||||
|
className="kanbn-burndown-settings-sprint-select"
|
||||||
|
onChange={handleChangeSprint}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
sprints.length > 0
|
sprints.length > 0
|
||||||
? sprints.map((sprint, i) => {
|
? sprints.map(sprint => {
|
||||||
return (
|
return (
|
||||||
<option value={i}>{sprint.name}</option>
|
<option value={sprint.name}>{sprint.name}</option>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: <option disabled>No sprints</option>
|
: <option disabled>No sprints</option>
|
||||||
@ -55,31 +108,30 @@ const Burndown = ({ name, tasks, sprints, burndownData, vscode }: {
|
|||||||
: <React.Fragment>
|
: <React.Fragment>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
|
value={startDate}
|
||||||
className="kanbn-burndown-settings-input kanbn-burndown-settings-start-date"
|
className="kanbn-burndown-settings-input kanbn-burndown-settings-start-date"
|
||||||
onChange={e => { console.log(e); }}
|
onChange={handleChangeStartDate}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
|
value={endDate}
|
||||||
className="kanbn-burndown-settings-input kanbn-burndown-settings-end-date"
|
className="kanbn-burndown-settings-input kanbn-burndown-settings-end-date"
|
||||||
onChange={e => { console.log(e); }}
|
onChange={handleChangeEndDate}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
<button
|
{hasSprints && <button
|
||||||
type="button"
|
type="button"
|
||||||
className={[
|
className={[
|
||||||
'kanbn-header-button',
|
'kanbn-header-button',
|
||||||
'kanbn-burndown-settings-sprint-mode',
|
'kanbn-burndown-settings-sprint-mode',
|
||||||
sprintMode ? 'kanbn-header-button-active' : 'kanbn-header-button-inactive'
|
sprintMode ? 'kanbn-header-button-active' : 'kanbn-header-button-inactive'
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
onClick={() => {
|
onClick={handleClickSprintMode}
|
||||||
setSprintMode(true);
|
|
||||||
// update burndown chart
|
|
||||||
}}
|
|
||||||
title="View sprint burndown"
|
title="View sprint burndown"
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-rocket"></i>
|
<i className="codicon codicon-rocket"></i>
|
||||||
</button>
|
</button>}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={[
|
className={[
|
||||||
@ -87,10 +139,7 @@ const Burndown = ({ name, tasks, sprints, burndownData, vscode }: {
|
|||||||
'kanbn-burndown-settings-date-mode',
|
'kanbn-burndown-settings-date-mode',
|
||||||
sprintMode ? 'kanbn-header-button-inactive' : 'kanbn-header-button-active'
|
sprintMode ? 'kanbn-header-button-inactive' : 'kanbn-header-button-active'
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
onClick={() => {
|
onClick={handleClickDateMode}
|
||||||
setSprintMode(false);
|
|
||||||
// update burndown chart
|
|
||||||
}}
|
|
||||||
title="View date-range burndown"
|
title="View date-range burndown"
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-clock"></i>
|
<i className="codicon codicon-clock"></i>
|
||||||
@ -101,10 +150,10 @@ const Burndown = ({ name, tasks, sprints, burndownData, vscode }: {
|
|||||||
</div>
|
</div>
|
||||||
<div className="kanbn-burndown">
|
<div className="kanbn-burndown">
|
||||||
<ResponsiveContainer width="100%" height="100%" className="kanbn-burndown-chart">
|
<ResponsiveContainer width="100%" height="100%" className="kanbn-burndown-chart">
|
||||||
<LineChart data={data}>
|
<LineChart data={chartData}>
|
||||||
<Line type="monotone" dataKey="uv" stroke="#8884d8" />
|
<Line className="kanbn-burndown-line" type="monotone" dataKey="y" />
|
||||||
<CartesianGrid stroke="#ccc" strokeDasharray="5 5" />
|
<CartesianGrid className="kanbn-burndown-grid" strokeDasharray="5 5" vertical={false} />
|
||||||
<XAxis dataKey="name" />
|
<XAxis dataKey="x" type="number" domain={[chartMin, chartMax]} tickFormatter={formatXAxis} />
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip />
|
<Tooltip />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
|
@ -537,3 +537,11 @@ body.vscode-dark .kanbn-burndown-settings-input[type="date"]::-webkit-calendar-p
|
|||||||
height: 85vh;
|
height: 85vh;
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown-grid {
|
||||||
|
stroke: var(--vscode-activityBar-inactiveForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown-line path {
|
||||||
|
stroke: #3c7;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user