diff --git a/webapp/__mocks__/github-api.js b/webapp/__mocks__/github-api.js new file mode 100644 index 0000000..6173af6 --- /dev/null +++ b/webapp/__mocks__/github-api.js @@ -0,0 +1,88 @@ +class GitHub { + getIssues(user, repo) { + return new Issues(user, repo); + } + + search() { + return new Search(); + } +} + +class Issues { + constructor(user, repo) { + this._user = user; + this._repo = repo; + this._calls = {}; + } + + _getIssue(number) { + if (this._calls[number]) { + throw new Error( + 'duplicate call for github.com/' + + this._user + '/' + this._repo + '#' + number + ); + } + this._calls[number] = true; + if (number > 100) { + return Promise.reject(new Error( + 'error making request GET https://api.github.com/repos/' + + this._user + '/' + this._repo + '/issues/' + number + )); + } + + var dependencies = function (number) { + switch (number) { + case 1: + return ['#10']; + case 3: + return ['#2', 'd3/d3#4356', 'gitlab.com/foo/bar#234']; + case 5: + return ['#3']; + case 7: + return ['#3']; + case 10: + return ['#3', '#7', '#5']; + case 20: + return ['foo/#3']; /* will be skipped */ + default: + return []; + } + }; + + return { + body: dependencies(number).map(function (dep) { + return 'depends on ' + dep; + }).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}`, + state: number < 10 ? 'open' : 'closed', + title: 'Some title for ' + number, + user: { + login: 'author' + number, + }, + }; + } + + getIssue(number) { + return Promise.resolve({data: this._getIssue(number)}); + } + + listIssues() { + return Promise.resolve({ + data: [ + this._getIssue(1), + this._getIssue(2), + ] + }); + } +} + +export class Search { + forIssues(options) { + var issues = new Issues('jbenet', 'depviz'); + return issues.listIssues(); + } +} + +export default GitHub; diff --git a/webapp/src/App.js b/webapp/src/App.js index b88b0a4..65fbf23 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -2,52 +2,32 @@ import React, { Component } from 'react'; import { Router, IndexRoute, Route, hashHistory } from 'react-router' import './App.css'; import DepGraph from './DepGraph'; -import GetDummyHostNode, { CanonicalDummyHostKey } from './DummyHost'; -import GetGitHubNode, { CanonicalGitHubKey, GetGitHubRepoNodes } from './GitHub'; -import GetNode, { Canonicalizers, Getters, CanonicalKey } from './GetNode'; +import GetDummyHostNodes, { CanonicalDummyHostKey } from './DummyHost'; +import GetGitHubNodes, { CanonicalGitHubKey } from './GitHub'; +import GetNodes, { Canonicalizers, Getters, CanonicalKey } from './GetNodes'; import Home from './Home'; import Layout, { HeaderHeight } from './Layout'; +Canonicalizers['dummy'] = CanonicalDummyHostKey; +var dummyGetter = new GetDummyHostNodes(); +Getters['dummy'] = dummyGetter.GetNodes.bind(dummyGetter); + +Canonicalizers['github.com'] = CanonicalGitHubKey; +Getters['github.com'] = GetGitHubNodes; + export class DepGraphView extends Component { render() { - var attributes = {}; - const key = this.props.params.splat; - const host = key.split('/', 1)[0]; - if (host === 'github.com') { - var match = /^github.com\/([^\/#]+)\/([^\/#]+)?$/.exec(key); - if (match === null) { - attributes.slugs = [key]; - } else { - var user = match[1]; - var repo = match[2]; - attributes.getInitialNodes = function (pushNodes) { - return GetGitHubRepoNodes(user, repo, pushNodes); - }; - } - } else { - attributes.slugs = [key]; - } return + getNodes={GetNodes} canonicalKey={CanonicalKey} + slugs={[this.props.params.splat]} /> } } function enterGraphView(nextState, replace) { const splat = nextState.params.splat; - var canonicalKey; - try { - canonicalKey = CanonicalKey(splat); - } catch (error) { - var match; - match = /^github.com\/([^\/#]+)\/([^\/#]+)?$/.exec(splat); - if (match === null) { - throw error; - } - canonicalKey = splat; - } + const canonicalKey = CanonicalKey(splat); const canonicalPath = canonicalKey.replace(/#/g, '/'); if (splat === canonicalKey || splat === canonicalPath) { return; @@ -57,10 +37,6 @@ function enterGraphView(nextState, replace) { class App extends Component { render() { - Canonicalizers['dummy'] = CanonicalDummyHostKey; - Canonicalizers['github.com'] = CanonicalGitHubKey; - Getters['dummy'] = GetDummyHostNode; - Getters['github.com'] = GetGitHubNode; return (
diff --git a/webapp/src/App.test.js b/webapp/src/App.test.js index 5c52dd8..899ec3e 100644 --- a/webapp/src/App.test.js +++ b/webapp/src/App.test.js @@ -1,35 +1,3 @@ -jest.mock('github-api', () => { - class GitHub { - getIssues(user, repo) { - return new Issues(user, repo); - } - } - - class Issues { - constructor(user, repo) { - this._user = user; - this._repo = repo; - } - - getIssue(number) { - return Promise.resolve({ - data: { - body: '', - html_url: 'https://github.com/' + - this._user + '/' + this._repo + '/issues/' + number, - state: number < 10 ? 'open' : 'closed', - title: 'Some title for ' + number, - user: { - login: 'author' + number, - }, - } - }); - } - } - - return jest.fn(() => new GitHub()); -}); - import React from 'react'; import ReactDOM from 'react-dom'; import { hashHistory } from 'react-router' @@ -40,10 +8,10 @@ it('home page renders without crashing', () => { ReactDOM.render(, div); }); -it('DepGraphView renders without crashing', () => { +it('issue view renders without crashing', () => { const div = document.createElement('div'); ReactDOM.render( - , + , div ); }); @@ -54,10 +22,26 @@ it('entering graph view normalizes non-canonical paths', () => { , div, function () { - hashHistory.push('/http/dummy/jbenet/depviz/issues/1'); + hashHistory.push('/http/dummy/jbenet/depviz/issues/31'); expect( hashHistory.getCurrentLocation().pathname - ).toBe('/http/dummy/jbenet/depviz/1'); + ).toBe('/http/dummy/jbenet/depviz/31'); }, ); }); + +it('repo view renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render( + , + div + ); +}); + +it('user view renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render( + , + div + ); +}); diff --git a/webapp/src/DepGraph.js b/webapp/src/DepGraph.js index fc92747..7073707 100644 --- a/webapp/src/DepGraph.js +++ b/webapp/src/DepGraph.js @@ -13,22 +13,19 @@ class DepGraph extends PureComponent { componentDidMount() { this.nodes(); - if (this.props.getInitialNodes) { - this.props.getInitialNodes(this.pushNodes.bind(this)); - } } nodes() { - if (!this.props.getNode) { - throw new Error('getNode unset'); + if (!this.props.getNodes) { + throw new Error('getNodes unset'); } if (!this.props.canonicalKey) { throw new Error('canonicalKey unset'); } - for (var index in (this.props.slugs || [])) { + for (var index in this.props.slugs) { if (true) { var key = this.props.slugs[index]; - this.getNode(key); + this.getNodes(key); } } } @@ -56,7 +53,7 @@ class DepGraph extends PureComponent { for (var i in parents) { if (true) { var relatedKey = parents[i]; - this.getNode(relatedKey); + this.getNodes(relatedKey); } } } @@ -64,16 +61,14 @@ class DepGraph extends PureComponent { } } - getNode(key) { + getNodes(key) { var _this = this; key = this.props.canonicalKey(key); this.setState(function (prevState) { if (prevState.nodes[key] || prevState.pending[key]) { return prevState; } - _this.props.getNode(key).then(function (node) { - _this.pushNodes([node]); - }); + _this.props.getNodes(key, _this.pushNodes.bind(_this)); var pending = {...prevState.pending}; pending[key] = true; return {...prevState, pending: pending}; @@ -82,17 +77,14 @@ class DepGraph extends PureComponent { /* Properties: * - * * slugs, (optional) roots for the issue graph. An array of - * strings, like: + * * slugs, roots for the issue graph. An array of strings, like: * ['github.com/jbenet/depviz#1', 'gitlab.com/foo/bar#123'] - * * getInitialNodes(pushNodes), (optional) a callback for resolving - * initial nodes. pushNodes takes an array of nodes and may be - * called multiple times. * * width, the width of the graph viewport in pixels. * * height, the height of the graph viewport in pixels. * * canonicalKey(key) -> key, a callback for canonicalizing node * names. - * * getNode(key) -> Node, a callback for resolving nodes. + * * getNodes(key, pushNodes) -> [Node, ...], a callback for + * resolving nodes. */ render() { var _this = this; diff --git a/webapp/src/DepGraph.test.js b/webapp/src/DepGraph.test.js index 457e916..4aeec6c 100644 --- a/webapp/src/DepGraph.test.js +++ b/webapp/src/DepGraph.test.js @@ -1,11 +1,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import DepGraph from './DepGraph'; -import GetDummyHostNode, { CanonicalDummyHostKey } from './DummyHost'; -import GetNode, { Canonicalizers, Getters, CanonicalKey } from './GetNode'; +import GetDummyHostNodes, { CanonicalDummyHostKey } from './DummyHost'; +import GetNodes, { Canonicalizers, Getters, CanonicalKey } from './GetNodes'; Canonicalizers['dummy'] = CanonicalDummyHostKey; -Getters['dummy'] = GetDummyHostNode; +var dummyGetter = new GetDummyHostNodes(); +Getters['dummy'] = dummyGetter.GetNodes.bind(dummyGetter); it('renders without crashing', () => { const div = document.createElement('div'); @@ -14,18 +15,18 @@ it('renders without crashing', () => { slugs={[ 'dummy/jbenet/depviz#1', 'dummy/jbenet/depviz#6']} - getNode={GetNode} canonicalKey={CanonicalKey} />, + getNodes={GetNodes} canonicalKey={CanonicalKey} />, div, ); }); -it('missing getNode crashes', () => { +it('missing getNodes crashes', () => { const div = document.createElement('div'); expect(() => ReactDOM.render( , div, - )).toThrowError('getNode unset'); + )).toThrowError('getNodes unset'); }); it('missing canonicalKey crashes', () => { @@ -33,7 +34,7 @@ it('missing canonicalKey crashes', () => { expect(() => ReactDOM.render( , + getNodes={GetNodes} />, div, )).toThrowError('canonicalKey unset'); }); @@ -43,7 +44,7 @@ it('example.com host crashes', () => { expect(() => ReactDOM.render( , + getNodes={GetNodes} canonicalKey={CanonicalKey} />, div )).toThrowError('unrecognized key host: example.com/jbenet/depviz#7'); }); @@ -53,7 +54,7 @@ it('missing issue crashes', () => { expect(() => ReactDOM.render( , + getNodes={GetNodes} canonicalKey={CanonicalKey} />, div )).toThrowError( 'error making request GET https://example.com/jbenet/depviz/issue/999999999' @@ -65,17 +66,22 @@ it('invalid key crashes', () => { expect(() => ReactDOM.render( , + getNodes={GetNodes} canonicalKey={CanonicalKey} />, div )).toThrowError('unrecognized dummy key: dummy/jbenet/depviz#-1'); }); it('does not duplicate requests for loaded nodes', () => { const div = document.createElement('div'); + var dummyGetter = new GetDummyHostNodes(); ReactDOM.render( , + slugs={[ + 'dummy/jbenet/depviz#30', + 'dummy/jbenet/depviz#30', + ]} + getNodes={dummyGetter.GetNodes.bind(dummyGetter)} + canonicalKey={CanonicalKey} />, div ); }); diff --git a/webapp/src/DummyHost.js b/webapp/src/DummyHost.js index a879d1e..7515cc7 100644 --- a/webapp/src/DummyHost.js +++ b/webapp/src/DummyHost.js @@ -1,7 +1,5 @@ import DepCard from './DepCard'; -var calls = {}; - export function CanonicalDummyHostKey(key) { var match = /^dummy\/([^\/#]+)\/([^\/#]+)(\/|\/issues\/|)(#?)([0-9]+)$/.exec(key); if (!match) { @@ -20,68 +18,80 @@ export function CanonicalDummyHostKey(key) { } /* Dummy host getter for testing. */ -function GetDummyHostNode(key) { - var match = /^dummy\/(.*)\/(.*)#([0-9]*)$/.exec(key); - if (!match) { - throw new Error('unrecognized dummy key: ' + key); +class GetDummyHostNodes { + constructor() { + this.calls = {}; } - var user = match[1]; - var repo = match[2]; - var number = parseInt(match[3], 10); - if (calls[key]) { - //throw new Error('duplicate call for ' + key); - } - calls[key] = true; - if (number > 100) { - throw new Error( - 'error making request GET https://example.com/' + - user + '/' + repo + '/issue/' + number - ); - } + GetNodes(key, pushNodes) { + var match = /^dummy\/(.*)\/(.*)#([0-9]*)$/.exec(key); + if (!match) { + throw new Error('unrecognized dummy key: ' + key); + } + var user = match[1]; + var repo = match[2]; + var number = parseInt(match[3], 10); - var dependencies = function (number) { - switch (number) { - case 1: - return ['dummy/jbenet/depviz#10']; - case 3: - return [ - 'dummy/jbenet/depviz#2', 'dummy/d3/d3#4356', 'gitlab.com/foo/bar#234']; - case 5: - return ['dummy/jbenet/depviz#3']; - case 7: - return ['dummy/jbenet/depviz#3']; - case 10: - return [ + if (this.calls[key]) { + //throw new Error('duplicate call for ' + key); + console.log('duplicate call for ' + key); + } + this.calls[key] = true; + if (number > 100) { + throw new Error( + 'error making request GET https://example.com/' + + user + '/' + repo + '/issue/' + number + ); + } + + var dependencies = function (number) { + switch (number) { + case 1: + return ['dummy/jbenet/depviz#10']; + case 3: + return [ + 'dummy/jbenet/depviz#2', + 'dummy/d3/d3#4356', + 'gitlab.com/foo/bar#234']; + case 5: + return ['dummy/jbenet/depviz#3']; + case 7: + return ['dummy/jbenet/depviz#3']; + case 10: + return [ 'dummy/jbenet/depviz#3', 'dummy/jbenet/depviz#7', 'dummy/jbenet/depviz#5']; - default: - return []; - } - }; + default: + return []; + } + }; - var done = function (number) { - switch (number) { - case 2: - case 3: - case 6: - return true; - default: - return false; - } - }; + var done = function (number) { + switch (number) { + case 2: + case 3: + case 6: + return true; + default: + return false; + } + }; + + var node = new DepCard({ + slug: key, + host: 'github.com', /* because we need a logo */ + title: 'dummy ' + key, + href: 'https://example.com/' + user + '/' + repo + '/issue/' + number, + done: done(number), + dependencies: dependencies(number), + related: [], + user: 'author' + number, + }) - return Promise.resolve(new DepCard({ - slug: key, - host: 'github.com', /* because we need a logo */ - title: 'dummy ' + key, - href: 'https://example.com/' + user + '/' + repo + '/issue/' + number, - done: done(number), - dependencies: dependencies(number), - related: [], - user: 'author' + number, - })); + pushNodes([node]); + return Promise.resolve([node]); + } } -export default GetDummyHostNode; +export default GetDummyHostNodes; diff --git a/webapp/src/DummyHost.test.js b/webapp/src/DummyHost.test.js index bea6910..c6bdf0e 100644 --- a/webapp/src/DummyHost.test.js +++ b/webapp/src/DummyHost.test.js @@ -1,4 +1,6 @@ -import GetDummyHostNode, { CanonicalDummyHostKey } from './DummyHost'; +import GetDummyHostNodes, { CanonicalDummyHostKey } from './DummyHost'; + +var dummyGetter = new GetDummyHostNodes(); it('canonical key found for hash path', () => { expect( @@ -8,8 +10,8 @@ it('canonical key found for hash path', () => { it('canonical key found for slash path', () => { expect( - CanonicalDummyHostKey('dummy/jbenet/depviz/1') - ).toBe('dummy/jbenet/depviz#1'); + CanonicalDummyHostKey('dummy/jbenet/depviz/10') + ).toBe('dummy/jbenet/depviz#10'); }); it('spacer hash key fails canonicalization', () => { @@ -20,6 +22,27 @@ it('spacer hash key fails canonicalization', () => { it('example.com host crashes', () => { expect(() => - GetDummyHostNode('example.com/jbenet/depviz#1') + dummyGetter.GetNodes( + 'example.com/jbenet/depviz#1', function () {}) ).toThrowError('unrecognized dummy key: example.com/jbenet/depviz#1'); }); + +it('dummy key fetches without crashing', () => { + var nodes = []; + function pushNodes(newNodes) { + for (var index in newNodes) { + if (true) { + nodes.push(newNodes[index]); + } + } + } + return dummyGetter.GetNodes( + 'dummy/jbenet/depviz#3', pushNodes + ).then(function () { + return dummyGetter.GetNodes('dummy/jbenet/depviz#5', pushNodes); + }).then(function () { + return dummyGetter.GetNodes('dummy/jbenet/depviz#7', pushNodes); + }).then(function () { + return dummyGetter.GetNodes('dummy/jbenet/depviz#10', pushNodes); + }); +}); diff --git a/webapp/src/GetNode.js b/webapp/src/GetNodes.js similarity index 54% rename from webapp/src/GetNode.js rename to webapp/src/GetNodes.js index 96c6391..88e3796 100644 --- a/webapp/src/GetNode.js +++ b/webapp/src/GetNodes.js @@ -1,21 +1,21 @@ export var Canonicalizers = {}; export var Getters = {}; -function handle(key, handlers) { +function handler(key, handlers) { const host = key.split('/', 1)[0]; const handler = handlers[host]; if (!handler) { throw new Error('unrecognized key host: ' + key); } - return handler(key); + return handler; } export function CanonicalKey(key) { - return handle(key, Canonicalizers); + return handler(key, Canonicalizers)(key); } -function GetNode(key) { - return handle(key, Getters); +function GetNodes(key, pushNodes) { + return handler(key, Getters)(key, pushNodes); } -export default GetNode; +export default GetNodes; diff --git a/webapp/src/GitHub.js b/webapp/src/GitHub.js index 92b997e..c50b5ca 100644 --- a/webapp/src/GitHub.js +++ b/webapp/src/GitHub.js @@ -3,21 +3,39 @@ import DepCard from './DepCard'; const gh = new GitHub(); /* unauthenticated client */ -export function CanonicalGitHubKey(key) { - var match = /^github\.com\/([^\/#]+)\/([^\/#]+)(\/|\/issues\/|\/pull\/|)(#?)([0-9]+)$/.exec(key); +function parseKey(key) { + var match = /^github\.com\/([^\/#]+)(\/?)([^\/#]*)(\/|\/issues\/|\/pull\/|)(#?)([0-9]*)$/.exec(key); if (!match) { throw new Error('unrecognized GitHub key: ' + key); } - var user = match[1]; - var repo = match[2]; - var spacer = match[3]; - var hash = match[4] - var number = parseInt(match[5], 10); - if (spacer && hash) { + var data = {user: match[1]}; + var spacer1 = match[2]; + if (match[3]) { + data.repo = match[3]; + } + var spacer2 = match[4]; + var hash = match[5] + if (match[6]) { + data.number = parseInt(match[6], 10); + } + if ((!spacer1 && data.repo) || + (!data.repo && (spacer2 || hash)) || + (spacer2 && hash)) { throw new Error('unrecognized GitHub key: ' + key); } + return data; +} - return 'github.com/' + user + '/' + repo + '#' + number; +export function CanonicalGitHubKey(key) { + var data = parseKey(key); + key = 'github.com/' + data.user; + if (data.repo) { + key += '/' + data.repo; + } + if (data.number) { + key += '#' + data.number; + } + return key; } function nodeFromIssue(issue) { @@ -27,11 +45,6 @@ function nodeFromIssue(issue) { // FIXME: look for related too var match; match = /^.*\/([^\/]+)\/([^\/]+)$/.exec(issue.repository_url); - if (match === null) { - throw new Error( - 'unrecognized repository URL format: ' + issue.repository_url - ); - } var issueUser = match[1]; var issueRepo = match[2]; var key = 'github.com/' + issueUser + '/' + issueRepo + '#' + issue.number; @@ -65,31 +78,32 @@ function nodeFromIssue(issue) { }); } -function GetGitHubNode(key) { - var match = /^github\.com\/([^\/]*)\/([^\/]*)#([0-9]*)$/.exec(key); - if (!match) { - throw new Error('unrecognized GitHub key: ' + key); +function GetGitHubNodes(key, pushNodes) { + var data = parseKey(key); + if (data.repo === undefined) { + return gh.search().forIssues({ + q: `assignee:${data.user} is:open`, + }).then(function (issues) { + var nodes = issues.data.map(nodeFromIssue); + pushNodes(nodes); + }); } - var user = match[1]; - var repo = match[2]; - var number = parseInt(match[3], 10); - return gh.getIssues( - user, repo - ).getIssue( - number - ).then(function (issue) { - return nodeFromIssue(issue.data); - }); -} - -export function GetGitHubRepoNodes(user, repo, pushNodes) { - gh.getIssues( - user, repo + if (data.number === undefined) { + return gh.getIssues( + data.user, data.repo ).listIssues( ).then(function (issues) { var nodes = issues.data.map(nodeFromIssue); pushNodes(nodes); }); } + return gh.getIssues( + data.user, data.repo + ).getIssue( + data.number + ).then(function (issue) { + pushNodes([nodeFromIssue(issue.data)]); + }); +} -export default GetGitHubNode; +export default GetGitHubNodes; diff --git a/webapp/src/GitHub.test.js b/webapp/src/GitHub.test.js index c39297b..bdb7c52 100644 --- a/webapp/src/GitHub.test.js +++ b/webapp/src/GitHub.test.js @@ -1,72 +1,4 @@ -jest.mock('github-api', () => { - class GitHub { - getIssues(user, repo) { - return new Issues(user, repo); - } - } - - class Issues { - constructor(user, repo) { - this._user = user; - this._repo = repo; - this._calls = {}; - } - - getIssue(number) { - if (this._calls[number]) { - throw new Error( - 'duplicate call for github.com/' + - this._user + '/' + this._repo + '#' + number - ); - } - this._calls[number] = true; - if (number > 100) { - return Promise.reject(new Error( - 'error making request GET https://api.github.com/repos/' + - this._user + '/' + this._repo + '/issues/' + number - )); - } - - var dependencies = function (number) { - switch (number) { - case 1: - return ['#10']; - case 3: - return ['#2', 'd3/d3#4356', 'gitlab.com/foo/bar#234']; - case 5: - return ['#3']; - case 7: - return ['#3']; - case 10: - return ['#3', '#7', '#5']; - case 20: - return ['foo/#3']; /* will be skipped */ - default: - return []; - } - }; - - return Promise.resolve({ - data: { - body: dependencies(number).map(function (dep) { - return 'depends on ' + dep; - }).join('\n') + '\n', - html_url: 'https://github.com/' + - this._user + '/' + this._repo + '/issues/' + number, - state: number < 10 ? 'open' : 'closed', - title: 'Some title for ' + number, - user: { - login: 'author' + number, - }, - } - }); - } - } - - return jest.fn(() => new GitHub()); -}); - -import GetGitHubNode, { CanonicalGitHubKey } from './GitHub'; +import GetGitHubNodes, { CanonicalGitHubKey } from './GitHub'; it('canonical key found for hash path', () => { expect( @@ -111,51 +43,88 @@ it('example.com host fails canonicalization', () => { }); it('example.com host fails node lookup', () => { + var pushNode = jest.fn(); expect(() => - GetGitHubNode('example.com/jbenet/depviz#1') + GetGitHubNodes('example.com/jbenet/depviz#1', pushNode) ).toThrowError('unrecognized GitHub key: example.com/jbenet/depviz#1'); }); it('foo/#3 reference is skipped without crashing', () => { - return new Promise( - function (resolve, reject) { - GetGitHubNode('github.com/jbenet/depviz#20').then( - function (node) { - if (node.parents().length) { - reject(new Error( - 'mock github.com/jbenet/depviz#20 should have no parents' - )); - return; - } - resolve(); - }, - reject, - ); + 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].parents().length).toBe(0); + resolve(); + }).catch(reject); + }); }); it('long and short references are understood', () => { - return new Promise( - function (resolve, reject) { - GetGitHubNode('github.com/jbenet/depviz#3').then( - function (node) { - if (node.parents().length !== 3) { - reject(new Error( - 'mock github.com/jbenet/depviz#3 should have three parents' - )); - return; - } - if (node.parents()[0] !== 'github.com/jbenet/depviz#2') { - reject(new Error( - 'short reference not expanded in github.com/jbenet/depviz#3' - )); - return; - } - resolve(); - }, - reject, - ); + 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#3', pushNodes + ).then(function() { + expect(nodes.length).toBe(1); + var parents = nodes[0].parents(); + expect(parents.length).toBe(3); + expect(parents[0]).toBe('github.com/jbenet/depviz#2'); + resolve(); + }).catch(reject); + }); +}); + +it('repository keys are understood', () => { + 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', pushNodes + ).then(function() { + expect(nodes.length).toBe(2); + resolve(); + }).catch(reject); + }); +}); + +it('user keys are understood', () => { + 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', pushNodes + ).then(function() { + expect(nodes.length).toBe(2); + resolve(); + }).catch(reject); + }); }); diff --git a/webapp/src/Home.css b/webapp/src/Home.css index 401d09c..f9ca202 100644 --- a/webapp/src/Home.css +++ b/webapp/src/Home.css @@ -8,3 +8,7 @@ vertical-align: middle; padding-right: 0.5em; } + +.Home h2 { + font-size: large; +} diff --git a/webapp/src/Home.js b/webapp/src/Home.js index 1457ba5..499ec6e 100644 --- a/webapp/src/Home.js +++ b/webapp/src/Home.js @@ -1,4 +1,5 @@ import React, { PureComponent } from 'react'; +import { Link } from 'react-router' import github from './logo/github.svg'; import Jump from './Jump'; import './Home.css'; @@ -7,6 +8,75 @@ class Home extends PureComponent { render() { return
+ +

Identifiers

+ +

+ The jump bar above supports the following patterns: +

+ +
+
+ + github.com/user + +
+
+

+ Listing all the open issues assigned to that user and + their open dependencies. For example,  + + github.com/jbenet + . +

+
+
+ + github.com/user/repo + +
+
+

+ Listing all the open issues in that repository and their + open dependencies. For example,  + + github.com/jbenet/depviz + . +

+
+
+ + github.com/user/repo#number + +
+
+ + github.com/user/repo/number + +
+
+ + github.com/user/repo/issues/number + +
+
+ + github.com/user/repo/pull/number + +
+
+

+ Listing the referenced issue and all of its open + dependencies. For example,  + + github.com/jbenet/depviz#1 + . +

+
+
+ +

Dependencies

+

Link issues to each other with a “depends on” relationship. Support for issue trackers is pluggable, and depviz currently @@ -34,6 +104,9 @@ class Home extends PureComponent { + +

Development

+

Contributions welcome! Fork depviz on GitHub!