@tosin2013/kanbn
Version:
A CLI Kanban board with AI-powered task management features
398 lines (380 loc) • 8.57 kB
JavaScript
const kanbnModule = require('../main');
const utility = require('../utility');
const inquirer = require('inquirer');
inquirer.registerPrompt('recursive', require('inquirer-recursive'));
const sorterFields = [
{
name: 'Id',
field: 'id',
options: [
'--id'
],
filterable: true
},
{
name: 'Name',
field: 'name',
options: [
'--name',
'-n'
],
filterable: true
},
{
name: 'Description',
field: 'description',
options: [
'--desc',
'-d'
],
filterable: true
},
{
name: 'Sub-tasks',
field: 'subTasks',
options: [
'--sub-task',
'-s'
],
filterable: true
},
{
name: 'Count sub-tasks',
field: 'countSubTasks',
options: [
'--count-sub-tasks'
],
filterable: false
},
{
name: 'Tags',
field: 'tags',
options: [
'--tag',
'-t'
],
filterable: true
},
{
name: 'Count tags',
field: 'countTags',
options: [
'--count-tags'
],
filterable: false
},
{
name: 'Relations',
field: 'relations',
options: [
'--relation',
'-r'
],
filterable: true
},
{
name: 'Count relations',
field: 'countRelations',
options: [
'--count-relations'
],
filterable: false
},
{
name: 'Comments',
field: 'comments',
options: [
'--comment'
],
filterable: true
},
{
name: 'Count comments',
field: 'countComments',
options: [
'--count-comments'
],
filterable: false
},
{
name: 'Created date',
field: 'created',
options: [
'--created'
],
filterable: false
},
{
name: 'Updated date',
field: 'updated',
options: [
'--updated'
],
filterable: false
},
{
name: 'Started date',
field: 'started',
options: [
'--started'
],
filterable: false
},
{
name: 'Completed date',
field: 'completed',
options: [
'--completed'
],
filterable: false
},
{
name: 'Due date',
field: 'due',
options: [
'--due'
],
filterable: false
},
{
name: 'Workload',
field: 'workload',
options: [
'--workload',
'-w'
],
filterable: false
},
{
name: 'Progress',
field: 'progress',
options: [
'--progress'
],
filterable: false
},
{
name: 'Assigned user',
field: 'assigned',
options: [
'--assigned'
],
filterable: true
}
];
/**
* Sort a column interactively
* @param {string} columnName
* @param {string[]} columnNames
* @param {object[]} sorters
* @return {Promise<any>}
*/
async function interactive(columnName, columnNames, sorters) {
const sorterNameToField = Object.fromEntries(sorterFields.map(sorterField => [sorterField.name, sorterField.field]));
return await inquirer.prompt([
{
type: 'rawlist',
name: 'column',
message: 'Which column do you want to sort?',
default: columnName,
choices: columnNames
},
{
type: 'recursive',
name: 'sorters',
message: 'Sort by another field?',
default: true,
autoStart: !sorters.length,
prompts: [
{
type: 'list',
name: 'field',
message: 'Field:',
default: 'Name',
choices: sorterFields.map(sorterField => sorterField.name),
filter: (value, answers) => sorterNameToField[value]
},
{
type: 'confirm',
name: 'addFilter',
message: 'Filter this field?',
default: false,
when: answers => (
sorterFields
.filter(sorterField => sorterField.filterable)
.map(sorterField => sorterField.field)
.indexOf(answers.field) !== -1
)
},
{
type: 'input',
name: 'filter',
message: 'Filter field:',
when: answers => answers.addFilter,
validate: value => {
if (!value) {
return 'Filter cannot be empty';
}
return true;
}
},
{
type: 'list',
name: 'order',
message: 'Which order?',
default: 'Ascending',
choices: [
'Ascending',
'Descending'
],
filter: (value, answers) => ({
'Ascending': 'ascending',
'Descending': 'descending'
})[value]
}
]
}
]);
}
/**
* Sort a column
* @param {string} columnName
* @param {object[]} sorters
* @param {boolean} save
* @param {object} kanbnInstance The Kanbn instance to use
*/
function sortColumn(columnName, sorters, save, kanbnInstance) {
kanbnInstance
.sort(columnName, sorters, save)
.then(() => {
console.log(`Column "${columnName}" sorted`);
})
.catch(error => {
utility.error(error);
});
}
module.exports = async (args, argv) => {
const kanbn = kanbnModule();
// Sortable fields and aliases
const sortOptions = Object.fromEntries(utility.zip(
sorterFields.map(sorterField => sorterField.options).flat(),
sorterFields.map(sorterField => (new Array(sorterField.options.length)).fill(sorterField.field)).flat()
));
// Sorting order and aliases
const orderOptions = {
'--ascending': 'ascending',
'-a': 'ascending',
'--descending': 'descending',
'-z': 'descending'
};
// Skip these options
const skipOptions = [
'--interactive',
'-i',
'--save'
];
// Get the column that we're sorting
argv = argv.slice(3);
const columnName = args._.length > 1 ? args._[1] : null;
// Column name must be defined if not sorting interactively
if (columnName === null && !args.interactive) {
utility.error('No column name specified\nTry running {b}kanbn sort "column"{b} or {b}kanbn sort -i{b}');
return;
}
// Get the index and make sure it has some columns
let index;
try {
index = await kanbn.getIndex();
} catch (error) {
utility.error(error);
return;
}
const columnNames = Object.keys(index.columns);
if (!columnNames.length) {
utility.error('No columns defined in the index\nTry running {b}kanbn init -c "column name"{b}');
return;
}
// Add custom fields to sort fields
if ('customFields' in index.options) {
for (let customField of index.options.customFields) {
sorterFields.push({
name: customField.name,
field: customField.name,
options: [
`--${customField.name}`
],
filterable: customField.type === 'string'
});
}
}
// If a column name is defined, make sure it exists in the index
if (columnName !== null) {
if (columnNames.indexOf(columnName) === -1) {
utility.error(`Column "${columnName}" doesn't exist`);
return;
}
argv.shift();
}
// Get the default sorting order
let defaultOrder = "ascending";
if (argv[0] in orderOptions) {
defaultOrder = orderOptions[argv[0]];
argv.shift();
}
// Create a list of fields to sort by
const sorters = [];
let currentSorter = null, expectingFilter = false;
for (let arg of argv) {
if (skipOptions.indexOf(arg) !== -1) {
continue;
}
if (arg in sortOptions) {
if (currentSorter !== null) {
sorters.push(currentSorter);
}
currentSorter = {
field: sortOptions[arg],
filter: '',
order: defaultOrder
};
expectingFilter = true;
} else if (currentSorter && currentSorter.field) {
if (arg in orderOptions) {
currentSorter.order = orderOptions[arg];
expectingFilter = false;
}
if (expectingFilter) {
currentSorter.filter = arg;
expectingFilter = false;
}
}
}
if (currentSorter && currentSorter.field) {
sorters.push(currentSorter);
}
// Build sorters interactively
if (args.interactive) {
interactive(columnName, columnNames, sorters)
.then(answers => {
inquirer
.prompt({
type: 'confirm',
name: 'save',
message: 'Save sort settings?',
default: args.save
})
.then(saveAnswer => {
sortColumn(answers.column, answers.sorters, saveAnswer.save, kanbn);
})
.catch(error => {
utility.error(error);
});
})
.catch(error => {
utility.error(error);
});
// Otherwise sort a column non-interactively
} else {
sortColumn(columnName, sorters, args.save, kanbn);
}
};