Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop interactive mode #12

Merged
merged 4 commits into from
Jun 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,19 @@ As [recommended by GitHub](https://github.com/blog/180-local-github-config), Gad

### Commands

In your projet repository, just enter `gad`.

| Command | Description |
|---|---|
| __sprint__ | Show the state of the current sprint |
| __sprints__ | Show the state of all sprints |
| __backlog__ | Show the state of the backlog |
| __review__ | Display PullRequest that are awaiting your review |
| __changelog__ | Generate a markdown changelog of the current sprint |
| __estimate__ | Show stories that are missing estimation |
| __status__ | Show the status of the repository |
| __help__ | Show list of commands |
| __exit__ | Quit the dashboard |
In your projet repository, just enter `gad [command] (options)`.

| Command | Description | Options |
|---|---|---|
| __sprint__ | Show the state of the current sprint | __sprint__ `-s=-1` Show the previous sprint |
| __sprints__ | Show the state of all sprints | __limit__ `-l=2` limit the number of sprint to display |
| __backlog__ | Show the state of the backlog | |
| __review__ | Display PullRequest that are awaiting your review | |
| __changelog__ | Generate a markdown changelog of the current sprint | __all__ `--all` include open issues in the changelog. __sprint__ `-s=-2` Show the changelog from two sprints ago |
| __estimate__ | Show stories that are missing estimation | |
| __status__ | Show the status of the repository | |
| __help__ | Show list of commands | |
| __exit__ | Quit the dashboard | |

### Options

Expand Down
10 changes: 8 additions & 2 deletions gad.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

const { execSync } = require('child_process');
const { homedir } = require('os');
const minimist = require('minimist');
const GithubAgileDashboard = require('./src/GithubAgileDashboard');
const { red } = require('./src/Util/colors');

function lookup(command) { try { return execSync(command).toString().trim(); } catch (error) { return ''; } }
function remote(url) { return (new RegExp('[email protected]:(.+)\\/(.+)\\.git', 'ig').exec(url) || new Array(3).fill(null)).slice(1); }

process.on('uncaughtException', function onError(error) { console.error(red(error.message)); process.exit(1); });

const [defaultOwner, defaultRepo] = remote(lookup('git -C . config --get remote.origin.url'));
const { owner, repo, user, password, cacheDir, _: commands} = require('minimist')(process.argv.slice(2), {
const { owner, repo, user, password, cacheDir, _: command} = minimist(process.argv.slice(2), {
stopEarly: true,
default: {
owner: defaultOwner,
repo: defaultRepo,
Expand All @@ -26,4 +32,4 @@ const { owner, repo, user, password, cacheDir, _: commands} = require('minimist'
}
});

module.exports = new (require('./src/GithubAgileDashboard'))(owner, repo, user, password, cacheDir, commands);
module.exports = new GithubAgileDashboard(owner, repo, user, password, cacheDir, command.join(' '));
44 changes: 33 additions & 11 deletions src/CLI.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const EventEmitter = require('events');
const Readline = require('readline');
const minimist = require('minimist');

class CLI extends EventEmitter {
/**
Expand All @@ -9,14 +10,14 @@ class CLI extends EventEmitter {

/**
* @param {String} prompt
* @param {Array} commandStack command stack
* @param {Array} commandStack Command stack
*/
constructor(prompt = '', commandStack = []) {
super();

const { stdin, stdout } = process;

this.commands = new Set();
this.commands = new Map();
this.readline = Readline.createInterface({ input: stdin, output: stdout, prompt });
this.commandStack = commandStack;
this.ready = false;
Expand All @@ -37,15 +38,31 @@ class CLI extends EventEmitter {
*
* @param {String} name
* @param {Function} callback
* @param {Object} options
*/
on(name, callback) {
on(name, callback, options = {}) {
super.on(name, callback);

if (CLI.RESERVED.indexOf(name) < 0) {
this.commands.add(name);
this.commands.set(name, { default: options, alias: this.getAlias(options) });
}
}

/**
* Create alias automatically
*
* @param {Object} options
*
* @return {Object}
*/
getAlias(options) {
const alias = {};

Object.keys(options).forEach(option => alias[option[0]] = option);

return alias;
}

/**
* Mark as ready
*/
Expand All @@ -59,7 +76,8 @@ class CLI extends EventEmitter {
let line;

while (line = this.commandStack.shift()) {
this.readline.write(`${line}\r\n`);
this.readline.prompt();
this.readline.write(`${line.trim()}\r\n`);
}
}
}
Expand All @@ -77,8 +95,8 @@ class CLI extends EventEmitter {
* Display command result
*/
result(message) {
this.write(typeof message === 'string' ? message : message.join('\r\n'));
this.readline.prompt();
this.write('\r\n' + (typeof message === 'string' ? message : message.join('\r\n')) + '\r\n');
setImmediate(this.close);
}

/**
Expand All @@ -96,11 +114,13 @@ class CLI extends EventEmitter {
* @return {String}
*/
getCommand(input) {
if (this.commands.has(input.trim())) {
return input;
const [ command, ...options] = input.split(' ');

if (!this.commands.has(command)) {
return { command: 'unknown' };
}

return 'unknown';
return { command, options: minimist(options, this.commands.get(command)) };
}

/**
Expand All @@ -118,7 +138,9 @@ class CLI extends EventEmitter {
* @param {String} line
*/
onLine(line) {
this.emit(this.getCommand(line) || 'unknown');
const { command, options } = this.getCommand(line);

this.emit(command || 'unknown', options);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Display/BurnDownChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class BurnDownChart {
display() {
const { dayWidth, gutter } = this.constructor;
const burnDown = this.getBurnDown(this.milestone);
const labelLength = Array.from(burnDown.keys()).reduce((max, label) => Math.max(label.length, max), 0);
const labelLength = Math.max(Array.from(burnDown.keys()).reduce((max, label) => Math.max(label.length, max), 0), 1);
const maxPoints = Math.ceil(this.milestone.points);
const pointsPerDay = maxPoints / burnDown.size;
const lines = [
Expand Down
32 changes: 23 additions & 9 deletions src/GitHub/Milestone.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Issue = require('./Issue');
const DateUtil = require('../Util/DateUtil');
const BurnDownChart = require('../Display/BurnDownChart');
const DateUtil = require('../Util/DateUtil');
const { green, yellow } = require('../Util/colors');

class Milestone {
/**
Expand Down Expand Up @@ -144,7 +145,16 @@ class Milestone {
displaySprint() {
const { title, length, done, todo, inProgress, readyToReview, progress, points } = this;

return `${title} ・ 📉 ${(progress * 100).toFixed(2)}% ・ 📫 ${todo}pts ・ 🚧 ${inProgress}pts ・ 🔍 ${readyToReview}pts ・ ✅ ${done}pts ・ (${length} stories ・ ${points}pts)`;
return [
title,
`${green(length)} stories`,
`${yellow(points)} points`,
`📉 ${(progress * 100).toFixed(2)}%`,
`📫 ${todo}pts`,
`🚧 ${inProgress}pts`,
`🔍 ${readyToReview}pts`,
`✅ ${done}pts`,
].join(' ・ ');
}

/**
Expand All @@ -155,7 +165,7 @@ class Milestone {
displayBacklog() {
const { title, length, points } = this;

return `${title} ・ 📇 ${length} stories ・ ${points} points`;
return `${title} ・ 📇 ${green(length)} stories ・ ${yellow(points)} points`;
}

/**
Expand All @@ -170,14 +180,18 @@ class Milestone {
/**
* Returns a changelog of the sprint
*
* @param {Boolean} all Display all issue (and not just those that are done)
*
* @return {Array}
*/
displayChangelog() {
return ['## Changelog:'].concat(
this.getIssueByStatus('done')
.sort(Issue.sortByPoint)
.map(issue => `- ${issue.title}`)
);
displayChangelog(all = false) {
const issues = all ? this.issues : this.getIssueByStatus('done');

return [`# ${this.title}`, '## Changelog '].concat(
issues
.sort(Issue.sortByPoint)
.map(issue => `- ${issue.title}`)
).join('\r\n');
}

}
Expand Down
31 changes: 27 additions & 4 deletions src/GitHub/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,36 @@ class Project {
}

/**
* Get current milestone
* Get sprint by index
*
* @param {Number} index If null: current sprint. If negative: previous sprint from current. If position: number of the sprint.
* @param {Date} date
*
* @return {Milestone}
*/
getCurrentMilestone(date = DateUtil.day()) {
return this.getSprints().find(milestone => milestone.isCurrent(date));
getSprint(number = null, date = DateUtil.day()) {
const sprints = this.getSprints();

if (!number) {
return sprints.find(milestone => milestone.isCurrent(date));
}

if (number > 0) {
if (typeof sprints[number - 1] === 'undefined') {
throw new Error(`No sprint ${number}: only ${sprints.length} sprints found.`);
}

return sprints[number - 1];
} else {
const date = DateUtil.day();
const current = sprints.findIndex(milestone => milestone.isCurrent(date));

if (typeof sprints[current + number] === 'undefined') {
throw new Error(`No sprint ${number}.`);
}

return sprints[current + number];
}
}

/**
Expand All @@ -40,7 +62,7 @@ class Project {
* @return {Milestone[]}
*/
getSprints() {
return this.milestones.sort(Milestone.sort).filter(milestone => !milestone.isBacklog());
return this.milestones.filter(milestone => !milestone.isBacklog());
}

/**
Expand Down Expand Up @@ -82,6 +104,7 @@ class Project {
load(issues) {
issues.filter(data => typeof data.pull_request === 'undefined').forEach(this.loadIssue);
issues.filter(data => typeof data.pull_request !== 'undefined').forEach(this.loadPullRequest);
this.milestones.sort(Milestone.sort);
this.getSprints().forEach((milestone, index, sprints) => milestone.previous = sprints[index - 1] || null);
}

Expand Down
Loading