Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Additional entry point for review comments #2084

Merged
merged 32 commits into from
Apr 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0679453
Add vertical ellipses for context menu
kuychaco Apr 18, 2019
4bdaace
Lift logic for opening ReviewItem up to IssueishSearchesController
kuychaco Apr 18, 2019
7cc3fab
Pass down onOpenReviews callback as prop to IssueishListView
kuychaco Apr 18, 2019
3e8a6c4
Add menu with "See reviews" item
kuychaco Apr 18, 2019
ec08555
Make "See reviews" button call passed down prop
kuychaco Apr 18, 2019
7ee6f11
:fire: unused openReviews in IssueishListController
kuychaco Apr 18, 2019
2aad757
Add menu item for opening on GitHub
kuychaco Apr 18, 2019
2944122
Move test for opening review from IssuishListController to IssueishSe…
kuychaco Apr 19, 2019
7552148
Fix failing IsssueisListView test
kuychaco Apr 19, 2019
1aedec4
:shirt:
kuychaco Apr 19, 2019
9afa981
fix icon vertical centering
Apr 19, 2019
c6a8138
actually fix the icon
Apr 19, 2019
5387610
Istanbul ignore electron menu logic
kuychaco Apr 19, 2019
515f91c
Move logic to open issue on GitHub from view to controller
kuychaco Apr 22, 2019
2e47609
Add `open-issueish-in-browser` event and pass url instead of issueish
kuychaco Apr 23, 2019
65e95e3
Move `showActionsMenu` to IssueishListController
kuychaco Apr 23, 2019
2432b84
:fire: unnecessary imports
kuychaco Apr 23, 2019
d74436a
Add prop types
kuychaco Apr 23, 2019
47312af
:fire: unused prop types
kuychaco Apr 23, 2019
005883e
Test that `showActionsMenu` handler is called when octicon is clicked
kuychaco Apr 23, 2019
7ea7c39
Add test for `openOnGitHub`
kuychaco Apr 23, 2019
8d113d1
Add test for `open-issueish-in-browser` event
kuychaco Apr 23, 2019
9c5adda
:fire: todo comment
kuychaco Apr 23, 2019
0646872
Make click handler bound class method
kuychaco Apr 23, 2019
ea18395
Oops, actually pass down `showActionsMenu` to the view
kuychaco Apr 23, 2019
7f7565c
Oops, showActionsMenu needs to be an arrow function to bind `this`
kuychaco Apr 23, 2019
f119527
Fix parameters for `showActionsMenu`
kuychaco Apr 23, 2019
dc25f9d
Use `istanbul ignore next` correctly
kuychaco Apr 23, 2019
717f3e1
Okay, _actually_ istanbul-ignore method this time
kuychaco Apr 23, 2019
ff20567
Update test/controllers/issueish-list-controller.test.js
vanessayuenn Apr 25, 2019
62b2b89
Use strictEqual instead of equal
kuychaco Apr 25, 2019
cff5a35
Empty commit to kick off CI after setup got messed up
kuychaco Apr 26, 2019
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
5 changes: 2 additions & 3 deletions lib/containers/current-pull-request-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export default class CurrentPullRequestContainer extends React.Component {
pushInProgress: PropTypes.bool.isRequired,

workspace: PropTypes.object.isRequired,
workingDirectory: PropTypes.string.isRequired,

// Actions
onOpenIssueish: PropTypes.func.isRequired,
onOpenReviews: PropTypes.func.isRequired,
onCreatePr: PropTypes.func.isRequired,
}

Expand Down Expand Up @@ -147,7 +147,6 @@ export default class CurrentPullRequestContainer extends React.Component {
results={associatedPullRequests.nodes}
isLoading={false}
workspace={this.props.workspace}
workingDirectory={this.props.workingDirectory}
endpoint={this.props.endpoint}
resultFilter={issueish => issueish.getHeadRepositoryID() === this.props.repository.id}
{...this.controllerProps()}
Expand Down Expand Up @@ -176,9 +175,9 @@ export default class CurrentPullRequestContainer extends React.Component {
return {
title: 'Checked out pull request',
onOpenIssueish: this.props.onOpenIssueish,
onOpenReviews: this.props.onOpenReviews,
emptyComponent: this.renderEmptyTile,
needReviewsButton: true,
workingDirectory: this.props.workingDirectory,
};
}
}
2 changes: 2 additions & 0 deletions lib/containers/issueish-search-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default class IssueishSearchContainer extends React.Component {
// Action methods
onOpenIssueish: PropTypes.func.isRequired,
onOpenSearch: PropTypes.func.isRequired,
onOpenReviews: PropTypes.func.isRequired,
}

static defaultProps = {
Expand Down Expand Up @@ -116,6 +117,7 @@ export default class IssueishSearchContainer extends React.Component {
title: this.props.search.getName(),

onOpenIssueish: this.props.onOpenIssueish,
onOpenReviews: this.props.onOpenReviews,
onOpenMore: () => this.props.onOpenSearch(this.props.search),
};
}
Expand Down
47 changes: 30 additions & 17 deletions lib/controllers/issueish-list-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {graphql, createFragmentContainer} from 'react-relay';
import {EndpointPropType} from '../prop-types';
import IssueishListView from '../views/issueish-list-view';
import Issueish from '../models/issueish';
import ReviewsItem from '../items/reviews-item';
import {shell, remote} from 'electron';
const {Menu, MenuItem} = remote;
import {addEvent} from '../reporter-proxy';

const StatePropType = PropTypes.oneOf(['EXPECTED', 'PENDING', 'SUCCESS', 'ERROR', 'FAILURE']);
Expand Down Expand Up @@ -51,12 +52,12 @@ export class BareIssueishListController extends React.Component {

resultFilter: PropTypes.func,
onOpenIssueish: PropTypes.func.isRequired,
onOpenReviews: PropTypes.func.isRequired,
onOpenMore: PropTypes.func,

emptyComponent: PropTypes.func,
workspace: PropTypes.object,
endpoint: EndpointPropType,
workingDirectory: PropTypes.string,
needReviewsButton: PropTypes.bool,
};

Expand Down Expand Up @@ -90,21 +91,31 @@ export class BareIssueishListController extends React.Component {
return null;
}

openReviews = async () => {
/* istanbul ignore next */
if (this.props.results.length < 1) {
return;
}
const result = this.props.results[0];
const uri = ReviewsItem.buildURI({
host: this.props.endpoint.getHost(),
owner: result.repository.owner.login,
repo: result.repository.name,
number: result.number,
workdir: this.props.workingDirectory,
openOnGitHub = url => {
return new Promise((resolve, reject) => {
shell.openExternal(url, {}, err => {
if (err) { reject(err); } else {
resolve();
addEvent('open-issueish-in-browser', {package: 'github', component: this.constructor.name});
}
});
});
await this.props.workspace.open(uri);
addEvent('open-reviews-tab', {package: 'github', from: this.constructor.name});
}

showActionsMenu = /* istanbul ignore next */ issueish => {
const menu = new Menu();

menu.append(new MenuItem({
label: 'See reviews',
click: () => this.props.onOpenReviews(issueish),
}));

menu.append(new MenuItem({
label: 'Open on GitHub',
click: () => this.openOnGitHub(issueish.getGitHubURL()),
}));

menu.popup(remote.getCurrentWindow());
}

render() {
Expand All @@ -118,7 +129,9 @@ export class BareIssueishListController extends React.Component {
needReviewsButton={this.props.needReviewsButton}
onIssueishClick={this.props.onOpenIssueish}
onMoreClick={this.props.onOpenMore}
openReviews={this.openReviews}
openReviews={this.props.onOpenReviews}
openOnGitHub={this.openOnGitHub}
showActionsMenu={this.showActionsMenu}
emptyComponent={this.props.emptyComponent}
/>
);
Expand Down
16 changes: 16 additions & 0 deletions lib/controllers/issueish-searches-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Search from '../models/search';
import IssueishSearchContainer from '../containers/issueish-search-container';
import CurrentPullRequestContainer from '../containers/current-pull-request-container';
import IssueishDetailItem from '../items/issueish-detail-item';
import ReviewsItem from '../items/reviews-item';
import {addEvent} from '../reporter-proxy';

export default class IssueishSearchesController extends React.Component {
Expand Down Expand Up @@ -68,6 +69,7 @@ export default class IssueishSearchesController extends React.Component {
workspace={this.props.workspace}
workingDirectory={this.props.workingDirectory}
onOpenIssueish={this.onOpenIssueish}
onOpenReviews={this.onOpenReviews}
onCreatePr={this.props.onCreatePr}
/>
{this.state.searches.map(search => (
Expand All @@ -81,12 +83,26 @@ export default class IssueishSearchesController extends React.Component {

onOpenIssueish={this.onOpenIssueish}
onOpenSearch={this.onOpenSearch}
onOpenReviews={this.onOpenReviews}
/>
))}
</div>
);
}

onOpenReviews = issueish => {
const uri = ReviewsItem.buildURI({
host: this.props.endpoint.getHost(),
owner: this.props.remote.getOwner(),
repo: this.props.remote.getRepo(),
number: issueish.getNumber(),
workdir: this.props.workingDirectory,
});
return this.props.workspace.open(uri).then(() => {
addEvent('open-reviews-tab', {package: 'github', from: this.constructor.name});
});
}

onOpenIssueish = issueish => {
return this.props.workspace.open(
IssueishDetailItem.buildURI({
Expand Down
19 changes: 16 additions & 3 deletions lib/views/issueish-list-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export default class IssueishListView extends React.Component {
needReviewsButton: PropTypes.bool,
onIssueishClick: PropTypes.func.isRequired,
onMoreClick: PropTypes.func,
openReviews: PropTypes.func,
openReviews: PropTypes.func.isRequired,
openOnGitHub: PropTypes.func.isRequired,
showActionsMenu: PropTypes.func.isRequired,

emptyComponent: PropTypes.func,
error: PropTypes.object,
Expand Down Expand Up @@ -56,7 +58,7 @@ export default class IssueishListView extends React.Component {
}

renderReviewsButton = () => {
if (!this.props.needReviewsButton || this.props.total < 1) {
if (!this.props.needReviewsButton || this.props.issueishes.length < 1) {
return null;
}
return (
Expand All @@ -70,7 +72,7 @@ export default class IssueishListView extends React.Component {

openReviews = e => {
e.stopPropagation();
this.props.openReviews();
this.props.openReviews(this.props.issueishes[0]);
}

renderIssueish(issueish) {
Expand All @@ -94,10 +96,21 @@ export default class IssueishListView extends React.Component {
displayStyle="short"
className="github-IssueishList-item github-IssueishList-item--age"
/>
<Octicon icon="ellipses"
className="github-IssueishList-item github-IssueishList-item--menu"
onClick={event => this.showActionsMenu(event, issueish)}
/>
</Fragment>
);
}

showActionsMenu(event, issueish) {
event.preventDefault();
event.stopPropagation();

this.props.showActionsMenu(issueish);
}

renderStatusSummary(statusCounts) {
if (['success', 'failure', 'pending'].every(kind => statusCounts[kind] === 0)) {
return <Octicon className="github-IssueishList-item github-IssueishList-item--status" icon="dash" />;
Expand Down
5 changes: 5 additions & 0 deletions styles/issueish-list-view.less
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@
text-align: center;
}

&--menu {
color: @text-color-subtle;
line-height: 1;
}

}

&-more {
Expand Down
50 changes: 34 additions & 16 deletions test/controllers/issueish-list-controller.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import {shallow} from 'enzyme';
import {shell} from 'electron';

import {createPullRequestResult} from '../fixtures/factories/pull-request-result';
import Issueish from '../../lib/models/issueish';
import {BareIssueishListController} from '../../lib/controllers/issueish-list-controller';
import {getEndpoint} from '../../lib/models/endpoint';
import * as reporterProxy from '../../lib/reporter-proxy';

describe('IssueishListController', function() {
Expand Down Expand Up @@ -85,22 +85,40 @@ describe('IssueishListController', function() {
assert.deepEqual(view.prop('issueishes').map(issueish => issueish.getNumber()), [11, 13, 12]);
});

it('opens reviews', async function() {
const atomEnv = global.buildAtomEnvironment();
sinon.stub(atomEnv.workspace, 'open').resolves();
sinon.stub(reporterProxy, 'addEvent');
const pr = createPullRequestResult({number: 1337});
Object.assign(pr.repository, {owner: {login: 'owner'}, name: 'repo'});
const wrapper = shallow(buildApp({
workspace: atomEnv.workspace,
endpoint: getEndpoint('github.com'),
results: [pr],
}));
describe('openOnGitHub', function() {
const url = 'https://github.com/atom/github/pull/2084';

it('calls shell.openExternal with specified url', async function() {
const wrapper = shallow(buildApp());
sinon.stub(shell, 'openExternal').callsArg(2);

await wrapper.instance().openOnGitHub(url);
assert.isTrue(shell.openExternal.calledWith(url));
});

await wrapper.find('IssueishListView').prop('openReviews')();
assert.isTrue(atomEnv.workspace.open.called);
assert.isTrue(reporterProxy.addEvent.calledWith('open-reviews-tab', {package: 'github', from: 'BareIssueishListController'}));
it('fires `open-issueish-in-browser` event upon success', async function() {
const wrapper = shallow(buildApp());
sinon.stub(shell, 'openExternal').callsArg(2);
sinon.stub(reporterProxy, 'addEvent');

atomEnv.destroy();
await wrapper.instance().openOnGitHub(url);
assert.strictEqual(reporterProxy.addEvent.callCount, 1);

await assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-browser', {package: 'github', component: 'BareIssueishListController'}));
});

it('handles error when openOnGitHub fails', async function() {
const wrapper = shallow(buildApp());
sinon.stub(shell, 'openExternal').callsArgWith(2, new Error('oh noes'));
sinon.stub(reporterProxy, 'addEvent');

try {
await wrapper.instance().openOnGitHub(url);
} catch (err) {
assert.strictEqual(err.message, 'oh noes');
}
assert.strictEqual(reporterProxy.addEvent.callCount, 0);
});
});

});
25 changes: 25 additions & 0 deletions test/controllers/issueish-searches-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,29 @@ describe('IssueishSearchesController', function() {
);
assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-pane', {package: 'github', from: 'issueish-list'}));
});

it('passes a handler to open reviews and reports an event', async function() {
sinon.spy(atomEnv.workspace, 'open');

const wrapper = shallow(buildApp());
const container = wrapper.find('IssueishSearchContainer').at(0);

const issueish = new Issueish({
number: 2084,
url: 'https://github.com/atom/github/pull/2084',
author: {login: 'kuychaco', avatarUrl: 'https://avatars3.githubusercontent.com/u/7910250?v=4'},
createdAt: '2019-04-01T10:00:00',
repository: {id: '0'},
commits: {nodes: []},
});

sinon.stub(reporterProxy, 'addEvent');
await container.prop('onOpenReviews')(issueish);
assert.isTrue(
atomEnv.workspace.open.calledWith(
`atom-github://reviews/github.com/atom/github/2084?workdir=${encodeURIComponent(__dirname)}`,
),
);
assert.isTrue(reporterProxy.addEvent.calledWith('open-reviews-tab', {package: 'github', from: 'IssueishSearchesController'}));
});
});
16 changes: 15 additions & 1 deletion test/views/issueish-list-view.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,25 @@ describe('IssueishListView', function() {
wrapper.find('.github-IssueishList-more a').simulate('click');
assert.isTrue(onMoreClick.called);
});

it('calls its `showActionsMenu` handler when the menu icon is clicked', function() {
const issueishes = [allGreen, mixed, allRed];
const showActionsMenu = sinon.stub();
const wrapper = mount(buildApp({
isLoading: false,
total: 3,
issueishes,
showActionsMenu,
}));

wrapper.find('Octicon.github-IssueishList-item--menu').at(1).simulate('click');
assert.isTrue(showActionsMenu.calledWith(mixed));
});
});

it('renders review button only if needed', function() {
const openReviews = sinon.spy();
const wrapper = mount(buildApp({total: 1, openReviews}));
const wrapper = mount(buildApp({issueishes: [allGreen], openReviews}));
assert.isFalse(wrapper.find('.github-IssueishList-openReviewsButton').exists());

wrapper.setProps({needReviewsButton: true});
Expand Down