Add task archive and restore
This commit is contained in:
		| @ -1,3 +1,8 @@ | |||||||
|  | # 0.10.0 | ||||||
|  |  | ||||||
|  | * Added task archive, tasks can now be sent to the archive folder and restored from the archive folder | ||||||
|  | * Fixed debounced updates on burndown chart | ||||||
|  |  | ||||||
| # 0.9.3 | # 0.9.3 | ||||||
|  |  | ||||||
| * Fixed performance issue where React app would keep adding event listeners every time the board was re-rendered | * Fixed performance issue where React app would keep adding event listeners every time the board was re-rendered | ||||||
|  | |||||||
| @ -54,6 +54,7 @@ The following commands are available: | |||||||
| - `Kanbn: Open board` will open open the Kanbn board. | - `Kanbn: Open board` will open open the Kanbn board. | ||||||
| - `Kanbn: Open burndown chart` will open a burndown chart. | - `Kanbn: Open burndown chart` will open a burndown chart. | ||||||
| - `Kanbn: Add task` will open the task editor. | - `Kanbn: Add task` will open the task editor. | ||||||
|  | - `Kanbn: Restore task` will restore tasks from the archive. | ||||||
|  |  | ||||||
| ## Configuration settings | ## Configuration settings | ||||||
|  |  | ||||||
|  | |||||||
| @ -91,6 +91,7 @@ Various Codicon icons have been used in this extension. Check [here](https://cod | |||||||
| - `kanbn-task-editor-comment-text` | - `kanbn-task-editor-comment-text` | ||||||
| - `kanbn-task-editor-column-right` | - `kanbn-task-editor-column-right` | ||||||
| - `kanbn-task-editor-button-submit` | - `kanbn-task-editor-button-submit` | ||||||
|  | - `kanbn-task-editor-button-archive` | ||||||
| - `kanbn-task-editor-field-column` | - `kanbn-task-editor-field-column` | ||||||
| - `kanbn-task-editor-field-assigned` | - `kanbn-task-editor-field-assigned` | ||||||
| - `kanbn-task-editor-field-started` | - `kanbn-task-editor-field-started` | ||||||
|  | |||||||
| @ -135,7 +135,7 @@ export default class KanbnBurndownPanel { | |||||||
|             this.sprint = message.sprint; |             this.sprint = message.sprint; | ||||||
|             this.startDate = message.startDate; |             this.startDate = message.startDate; | ||||||
|             this.endDate = message.endDate; |             this.endDate = message.endDate; | ||||||
|           KanbnBurndownPanel.update(); |             KanbnBurndownPanel.update(); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|  | |||||||
| @ -197,6 +197,16 @@ export default class KanbnTaskPanel { | |||||||
|                 } |                 } | ||||||
|               }); |               }); | ||||||
|             return; |             return; | ||||||
|  |  | ||||||
|  |           // Archive a task and close the webview panel | ||||||
|  |           case 'kanbn.archive': | ||||||
|  |             await this._kanbn.archiveTask(message.taskId); | ||||||
|  |             KanbnTaskPanel.panels[message.panelUuid].dispose(); | ||||||
|  |             delete KanbnTaskPanel.panels[message.panelUuid]; | ||||||
|  |             if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) { | ||||||
|  |               vscode.window.showInformationMessage(`Archived task '${message.taskData.name}'.`); | ||||||
|  |             } | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       null, |       null, | ||||||
|  | |||||||
| @ -136,6 +136,68 @@ export async function activate(context: vscode.ExtensionContext) { | |||||||
|     }) |     }) | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   // Register a command to restore a task from the archive. | ||||||
|  |   context.subscriptions.push( | ||||||
|  |     vscode.commands.registerCommand("kanbn.restoreTask", async () => { | ||||||
|  |       // If no workspace folder is opened, we can't restore tasks from the archive | ||||||
|  |       if (vscode.workspace.workspaceFolders === undefined) { | ||||||
|  |         vscode.window.showErrorMessage("You need to open a workspace before restoring tasks from the archive."); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Set the node process directory and import kanbn | ||||||
|  |       process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); | ||||||
|  |       const kanbn = await import("@basementuniverse/kanbn/src/main"); | ||||||
|  |  | ||||||
|  |       // Get a list of archived tasks | ||||||
|  |       let archivedTasks: string[] = []; | ||||||
|  |       try { | ||||||
|  |         archivedTasks = await kanbn.listArchivedTasks(); | ||||||
|  |       } catch (e) {} | ||||||
|  |       if (archivedTasks.length === 0) { | ||||||
|  |         vscode.window.showInformationMessage("There are no archived tasks to restore."); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // Prompt for a selection of tasks to restore | ||||||
|  |       const restoreTaskIds = await vscode.window.showQuickPick( | ||||||
|  |         archivedTasks, | ||||||
|  |         { | ||||||
|  |           placeHolder: 'Select tasks to restore...', | ||||||
|  |           canPickMany: true, | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |       if (restoreTaskIds !== undefined && restoreTaskIds.length > 0) { | ||||||
|  |  | ||||||
|  |         // Load index | ||||||
|  |         const index = await kanbn.getIndex(); | ||||||
|  |  | ||||||
|  |         // Prompt for a column to restore the tasks into | ||||||
|  |         const restoreColumn = await vscode.window.showQuickPick( | ||||||
|  |           [ | ||||||
|  |             'None (use original)', | ||||||
|  |             ...Object.keys(index.columns) | ||||||
|  |           ], | ||||||
|  |           { | ||||||
|  |             canPickMany: false | ||||||
|  |           } | ||||||
|  |         ); | ||||||
|  |         if (restoreColumn !== undefined) { | ||||||
|  |           for (let restoreTaskId of restoreTaskIds) { | ||||||
|  |             await kanbn.restoreTask(restoreTaskId, restoreColumn === 'None (use original)' ? null : restoreColumn); | ||||||
|  |           } | ||||||
|  |           KanbnBoardPanel.update(); | ||||||
|  |           kanbnStatusBarItem.update(); | ||||||
|  |           if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) { | ||||||
|  |             vscode.window.showInformationMessage( | ||||||
|  |               `Restored ${restoreTaskIds.length} task${restoreTaskIds.length === 1 ? '' : 's'}.` | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   // If a workspace folder is open, add a status bar item and start watching for file changes |   // If a workspace folder is open, add a status bar item and start watching for file changes | ||||||
|   if (vscode.workspace.workspaceFolders !== undefined) { |   if (vscode.workspace.workspaceFolders !== undefined) { | ||||||
|     // Set the node process directory and import kanbn |     // Set the node process directory and import kanbn | ||||||
|  | |||||||
							
								
								
									
										2202
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2202
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -59,6 +59,11 @@ | |||||||
|         "command": "kanbn.burndown", |         "command": "kanbn.burndown", | ||||||
|         "title": "Open burndown chart", |         "title": "Open burndown chart", | ||||||
|         "category": "Kanbn" |         "category": "Kanbn" | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "command": "kanbn.restoreTask", | ||||||
|  |         "title": "Restore task", | ||||||
|  |         "category": "Kanbn" | ||||||
|       } |       } | ||||||
|     ], |     ], | ||||||
|     "configuration": { |     "configuration": { | ||||||
| @ -88,7 +93,7 @@ | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@basementuniverse/kanbn": "^0.8.2", |     "@basementuniverse/kanbn": "file:~/Projects/kanbn", | ||||||
|     "dateformat": "^4.5.1", |     "dateformat": "^4.5.1", | ||||||
|     "formik": "^2.2.6", |     "formik": "^2.2.6", | ||||||
|     "git-user-name": "^2.0.0", |     "git-user-name": "^2.0.0", | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import React, { useState, useEffect } from 'react'; | import React, { useState, useRef } 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'; | ||||||
| @ -40,44 +40,96 @@ const Burndown = ({ name, sprints, burndownData, dateFormat, vscode }: { | |||||||
|       : formatDate(burndownData.series[0].to, 'yyyy-mm-dd') |       : formatDate(burndownData.series[0].to, 'yyyy-mm-dd') | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   const refreshBurndownData = debounce(500, settings => { |   const refreshBurndownData = useRef(debounce(500, settings => { | ||||||
|     vscode.postMessage({ |     vscode.postMessage({ | ||||||
|       command: 'kanbn.refreshBurndownData', |       command: 'kanbn.refreshBurndownData', | ||||||
|       ...Object.assign( |       ...settings | ||||||
|  |     }); | ||||||
|  |   })).current; | ||||||
|  |  | ||||||
|  |   const handleChangeSprint = ({ target: { value }}) => { | ||||||
|  |     setSprint(value); | ||||||
|  |     refreshBurndownData( | ||||||
|  |       Object.assign( | ||||||
|         { |         { | ||||||
|           sprintMode, |           sprintMode, | ||||||
|           sprint, |           sprint, | ||||||
|           startDate, |           startDate, | ||||||
|           endDate |           endDate | ||||||
|         }, |         }, | ||||||
|         settings |         { | ||||||
|  |           sprint: value | ||||||
|  |         } | ||||||
|       ) |       ) | ||||||
|     }); |     ); | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const handleChangeSprint = ({ target: { value }}) => { |  | ||||||
|     setSprint(value); |  | ||||||
|     refreshBurndownData({ sprint: value }); |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleChangeStartDate = ({ target: { value }}) => { |   const handleChangeStartDate = ({ target: { value }}) => { | ||||||
|     setStartDate(value); |     setStartDate(value); | ||||||
|     refreshBurndownData({ startDate: value }); |     refreshBurndownData( | ||||||
|  |       Object.assign( | ||||||
|  |         { | ||||||
|  |           sprintMode, | ||||||
|  |           sprint, | ||||||
|  |           startDate, | ||||||
|  |           endDate | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           startDate: value | ||||||
|  |         } | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleChangeEndDate = ({ target: { value }}) => { |   const handleChangeEndDate = ({ target: { value }}) => { | ||||||
|     setEndDate(value); |     setEndDate(value); | ||||||
|     refreshBurndownData({ endDate: value }); |     refreshBurndownData( | ||||||
|  |       Object.assign( | ||||||
|  |         { | ||||||
|  |           sprintMode, | ||||||
|  |           sprint, | ||||||
|  |           startDate, | ||||||
|  |           endDate | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           endDate: value | ||||||
|  |         } | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleClickSprintMode = () => { |   const handleClickSprintMode = () => { | ||||||
|     setSprintMode(true); |     setSprintMode(true); | ||||||
|     refreshBurndownData({ sprintMode: true }); |     refreshBurndownData( | ||||||
|  |       Object.assign( | ||||||
|  |         { | ||||||
|  |           sprintMode, | ||||||
|  |           sprint, | ||||||
|  |           startDate, | ||||||
|  |           endDate | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           sprintMode: true | ||||||
|  |         } | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleClickDateMode = () => { |   const handleClickDateMode = () => { | ||||||
|     setSprintMode(false); |     setSprintMode(false); | ||||||
|     refreshBurndownData({ sprintMode: false }); |     refreshBurndownData( | ||||||
|  |       Object.assign( | ||||||
|  |         { | ||||||
|  |           sprintMode, | ||||||
|  |           sprint, | ||||||
|  |           startDate, | ||||||
|  |           endDate | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           sprintMode: false | ||||||
|  |         } | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const chartData = burndownData.series.length > 0 |   const chartData = burndownData.series.length > 0 | ||||||
|  | |||||||
| @ -107,6 +107,16 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui | |||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   // Called when the archive task button is clicked | ||||||
|  |   const handleArchiveTask = values => { | ||||||
|  |     vscode.postMessage({ | ||||||
|  |       command: 'kanbn.archive', | ||||||
|  |       taskId: task!.id, | ||||||
|  |       taskData: values, | ||||||
|  |       panelUuid | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Check if a task's due date is in the past |   // Check if a task's due date is in the past | ||||||
|   const checkOverdue = (values: { metadata: { due?: string } }) => { |   const checkOverdue = (values: { metadata: { due?: string } }) => { | ||||||
|     if ('due' in values.metadata && values.metadata.due !== undefined) { |     if ('due' in values.metadata && values.metadata.due !== undefined) { | ||||||
| @ -487,6 +497,16 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, panelUui | |||||||
|                     > |                     > | ||||||
|                       <i className="codicon codicon-trash"></i>Delete |                       <i className="codicon codicon-trash"></i>Delete | ||||||
|                     </button>} |                     </button>} | ||||||
|  |                     {editing && <button | ||||||
|  |                       type="button" | ||||||
|  |                       className="kanbn-task-editor-button kanbn-task-editor-button-archive" | ||||||
|  |                       title="Archive task" | ||||||
|  |                       onClick={() => { | ||||||
|  |                         handleArchiveTask(values); | ||||||
|  |                       }} | ||||||
|  |                     > | ||||||
|  |                       <i className="codicon codicon-archive"></i>Archive | ||||||
|  |                     </button>} | ||||||
|                     <button |                     <button | ||||||
|                       type="submit" |                       type="submit" | ||||||
|                       className="kanbn-task-editor-button kanbn-task-editor-button-submit" |                       className="kanbn-task-editor-button kanbn-task-editor-button-submit" | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user