From 25fadc4b861891462d9b09db9ae393715afa1cd7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Nov 2016 11:11:09 -0800 Subject: [PATCH] DepCard: Add tasks, tasksCompleted, and a progress bar The GitHub feature is described in [1]. I'm scraping it with a regexp, since they don't seem to export parsed task information via the API. [1]: https://help.github.com/articles/about-task-lists/ --- webapp/__mocks__/github-api.js | 11 ++++--- webapp/src/DepCard.js | 58 ++++++++++++++++++++++++++-------- webapp/src/DepCard.test.js | 1 - webapp/src/DummyHost.js | 2 ++ webapp/src/GitHub.js | 16 ++++++++++ webapp/src/GitHub.test.js | 21 ++++++++++++ 6 files changed, 91 insertions(+), 18 deletions(-) diff --git a/webapp/__mocks__/github-api.js b/webapp/__mocks__/github-api.js index 6173af6..20e61f0 100644 --- a/webapp/__mocks__/github-api.js +++ b/webapp/__mocks__/github-api.js @@ -48,11 +48,14 @@ class Issues { return []; } }; - + var bodyLines = dependencies(number).map(function (dep) { + return 'depends on ' + dep; + }); + bodyLines.push(''); + bodyLines.push('- [ ] an uncompleted task'); + bodyLines.push('- [x] a completed task'); return { - body: dependencies(number).map(function (dep) { - return 'depends on ' + dep; - }).join('\n') + '\n', + body: bodyLines.join('\n') + '\n', html_url: `https://github.com/${this._user}/${this._repo}/issues/${number}`, number: number, repository_url: `https://api.github.com/repos/${this._user}/${this._repo}`, diff --git a/webapp/src/DepCard.js b/webapp/src/DepCard.js index d5bffa9..80bfb49 100644 --- a/webapp/src/DepCard.js +++ b/webapp/src/DepCard.js @@ -56,23 +56,31 @@ class DepCard extends PureComponent { dependencies={this.dependencyCount()} related={this.relatedCount()} dependents={this.dependentCount(nodes || {})} - done={this.props.done} /> + done={this.props.done} + tasks={this.props.tasks} + tasksCompleted={this.props.tasksCompleted} + user={this.props.user} /> } render() { var width = 15; var height = 3; + var radius = 0.5; var color = Neutral; var style = { fill: color, - fillOpacity: '0.1', + fillOpacity: 0.1, stroke: this.props.done ? Green : Red, strokeWidth: 0.2, }; var backgroundStyle = { fill: 'white', - stroke: 'white', - strokeWidth: 0.1, + stroke: 'none', + }; + var taskStyle = { + fill: Green, + fillOpacity: 0.1, + stroke: 'none', }; var logo, host; if (this.props.host === 'asana.com') { @@ -87,31 +95,55 @@ class DepCard extends PureComponent { } else { throw new Error('unrecognized host: ' + this.props.host); } + var taskRatio = 1; + if (this.props.tasks) { + taskRatio = this.props.tasksCompleted / this.props.tasks; + } else { + taskStyle.fill = 'white'; + } + var left = this.props.cx - width/2; + var right = this.props.cx + width/2; + var leftCenter = left + radius; + var rightTask = left + width * taskRatio; + var top = this.props.cy - height/2; + var bottom = this.props.cy + height/2; + var topCenter = top + radius; + var bottomCenter = bottom - radius; + var taskPath = [ + `M ${left} ${topCenter}`, + `A ${radius} ${radius} 0 0 1 ${leftCenter} ${top}`, + `L ${rightTask} ${top}`, + `L ${rightTask} ${bottom}`, + `L ${leftCenter} ${bottom}`, + `A ${radius} ${radius} 0 0 1 ${left} ${bottomCenter}`, + `Z`, + ]; return - - + x={left} y={top} width={width} height={height} + rx={radius} ry={radius} style={backgroundStyle}> + {this.props.slug.replace(/^[^\/]*\//, '')} + + { const svg = document.createElement('svg'); ReactDOM.render( diff --git a/webapp/src/DummyHost.js b/webapp/src/DummyHost.js index 2254490..c7d7ae0 100644 --- a/webapp/src/DummyHost.js +++ b/webapp/src/DummyHost.js @@ -97,6 +97,8 @@ class GetDummyHostNodes { done: done(data.number), dependencies: dependencies(data.number), related: [], + tasks: Math.max(10, data.number), + tasksCompleted: data.number, user: 'author' + data.number, }) } diff --git a/webapp/src/GitHub.js b/webapp/src/GitHub.js index c50b5ca..f75184d 100644 --- a/webapp/src/GitHub.js +++ b/webapp/src/GitHub.js @@ -66,6 +66,20 @@ function nodeFromIssue(issue) { var relatedKey = 'github.com/' + user + '/' + repo + '#' + number; dependencies.push(relatedKey); } + var tasks = 0; + var tasksCompleted = 0; + regexp = /^[^[]*\[([ x])].*$/gm; + for (;;) { + match = regexp.exec(issue.body); + if (match === null) { + break; + } + var check = match[1]; + if (check === 'x') { + tasksCompleted += 1; + } + tasks += 1; + } return new DepCard({ slug: key, host: 'github.com', @@ -74,6 +88,8 @@ function nodeFromIssue(issue) { done: issue.state !== 'open', dependencies: dependencies, related: related, + tasks: tasks, + tasksCompleted: tasksCompleted, user: issue.user.login, }); } diff --git a/webapp/src/GitHub.test.js b/webapp/src/GitHub.test.js index bdb7c52..a345f2b 100644 --- a/webapp/src/GitHub.test.js +++ b/webapp/src/GitHub.test.js @@ -91,6 +91,27 @@ it('long and short references are understood', () => { }); }); +it('tasks are counted', () => { + var nodes = []; + function pushNodes(newNodes) { + for (var index in newNodes) { + if (true) { + nodes.push(newNodes[index]); + } + } + } + return new Promise(function (resolve, reject) { + GetGitHubNodes( + 'github.com/jbenet/depviz#20', pushNodes + ).then(function () { + expect(nodes.length).toBe(1); + expect(nodes[0].props.tasks).toBe(2); + expect(nodes[0].props.tasksCompleted).toBe(1); + resolve(); + }).catch(reject); + }); +}); + it('repository keys are understood', () => { var nodes = []; function pushNodes(newNodes) {