Skip to content

Commit

Permalink
Add review filters functionality
Browse files Browse the repository at this point in the history
Closes #82
  • Loading branch information
li-boxuan committed Jul 30, 2018
1 parent 93c842b commit 9eae168
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 7 deletions.
18 changes: 18 additions & 0 deletions src/components/app/filter-dropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@ class FilterDropdown extends Component {
return (<FilterCategory noSearch items={items}/>);
};

renderReviews = () => {
const filters = getFilters();
const {reviews} = filters.getState();

const items = ['my-reviews', 'reviews-under-my-pr', 'my-meta-reviews', 'others'].map((review) => {
return {
text: review,
isSelected: reviews.indexOf(review) >= 0,
toggleHref: filters.toggleReviews(review).url(),
};
});

return (<FilterCategory noSearch items={items}/>);
};

render() {
const {milestones, labels} = this.props;

Expand Down Expand Up @@ -294,6 +309,9 @@ class FilterDropdown extends Component {
<BS.Panel className='filter-category' header='Types' eventKey='5'>
{this.renderTypes()}
</BS.Panel>
<BS.Panel className='filter-category' header='Reviews' eventKey='6'>
{this.renderReviews()}
</BS.Panel>
</BS.Accordion>
</BS.Panel>
);
Expand Down
21 changes: 20 additions & 1 deletion src/components/repo-kanban.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {ListUnorderedIcon} from 'react-octicons';
import {getFilters} from '../route-utils';
import {filterKanbanLabels} from '../lib/columns';
import {UNCATEGORIZED_NAME} from '../helpers';
import Client from '../github-client';
import IssueStore from '../issue-store';
import {filterCards} from '../issue-store';
import SettingsStore from '../settings-store';
Expand Down Expand Up @@ -91,11 +92,28 @@ function ReviewColumn(props) {
}

class KanbanRepo extends Component {
state = {login: null};

componentDidMount() {
const repoTitle = titlecaps(this.props.repoInfos[0].repoName);
document.title = `${repoTitle} Kanban Board`;
Client.on('changeToken', this.onChangeToken);
this.onChangeToken();
}

componentWillUnmount() {
Client.off('changeToken', this.onChangeToken);
}

onChangeToken = () => {
CurrentUserStore.fetchUser()
.then((info) => {
this.setState({login: info.login});
}).catch(() => {
this.setState({login: null});
});
};

render() {
const {columnData, cards, repoInfos} = this.props;

Expand All @@ -107,6 +125,7 @@ class KanbanRepo extends Component {
comment.repoOwner = card.repoOwner;
comment.repoName = card.repoName;
comment.number = card.number;
comment.prAuthor = card.issue.user ? card.issue.user.login : null;
return comment;
});
} else {
Expand All @@ -115,7 +134,7 @@ class KanbanRepo extends Component {
}));

// filter and sort review comments
const sortedReviews = FilterStore.filterAndSortReviews(reviews);
const sortedReviews = FilterStore.filterAndSortReviews(reviews, this.state.login);

// Get the primary repoOwner and repoName
const [primaryRepo] = repoInfos;
Expand Down
9 changes: 5 additions & 4 deletions src/filter-store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'underscore';
import {EventEmitter} from 'events';

import {getFilters, filterReviewsByFilter} from './route-utils';
import SettingsStore from './settings-store';
// import {filterCards} from './issue-store';

Expand Down Expand Up @@ -171,10 +172,10 @@ class Store extends EventEmitter {
return sortedCards;
}

filterAndSortReviews(reviews) {
// Sort the reviews by `lastEditedAt` and then
// by `createdAt` (if `lastEditedAt` does not exist)
let sortedReviews = reviews.map(review => {
filterAndSortReviews(reviews, login) {
const filter = getFilters();
const filteredReviews = filterReviewsByFilter(reviews, filter, login);
const sortedReviews = filteredReviews.map(review => {
review.parsedUpdatedAt = Date.parse(review.updatedAt);
return review;
}).sort((a, b) => {
Expand Down
1 change: 1 addition & 0 deletions src/github-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class Client extends EventEmitter {
constructor() {
super();
this.LOW_RATE_LIMIT = 60;
this.setMaxListeners(20);
}
// Used for checking if we should retreive ALL Issues or just open ones
canCacheLots() { return this.hasCredentials() /*&& !!cacheHandler._db*/; }
Expand Down
59 changes: 57 additions & 2 deletions src/route-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function addParams(options, key, vals, defaults) {

// Generate a URL based on various filters and whatnot
// `/r/:repoStr(/m/:milestonesStr)(/t/:tagsStr)(/u/:user)(/x/:columnRegExp)/:name(/:startShas)(/:endShas)
export function buildRoute(name, {repoInfos, milestoneTitles, tagNames, columnLabels, userName, columnRegExp, routeSegmentName, states, types}={}, ...otherFields) {
export function buildRoute(name, {repoInfos, milestoneTitles, tagNames, columnLabels, userName, columnRegExp, routeSegmentName, states, types, reviews}={}, ...otherFields) {
repoInfos = repoInfos || [];
milestoneTitles = milestoneTitles || [];
tagNames = tagNames || [];
Expand All @@ -67,6 +67,7 @@ export function buildRoute(name, {repoInfos, milestoneTitles, tagNames, columnLa
addParams(options, 'u', userName);
addParams(options, 's', states, DEFAULTS.states); // include the defaults so the URL is cleaner
addParams(options, 't', types, DEFAULTS.types);
addParams(options, 'r', reviews, DEFAULTS.reviews);
if (columnRegExp) {
const re = columnRegExp.toString();
// Strip off the wrapping `/` marks
Expand Down Expand Up @@ -118,6 +119,7 @@ export function parseRoute({params, routes, location}) {
let userName;
let states;
let types;
let reviews;
let columnRegExp;

// TODO: remove these fallbacks once URL's are updated.
Expand All @@ -130,9 +132,10 @@ export function parseRoute({params, routes, location}) {
if (query.u) { userName = query.u; }
if (query.s) { states = parseArray(query.s); }
if (query.t) { types = parseArray(query.t); }
if (query.r) { reviews = parseArray(query.r); }
if (query.x) { columnRegExp = new RegExp(query.x); }

return {repoInfos, milestoneTitles, tagNames, columnLabels, userName, states, types, columnRegExp, routeSegmentName};
return {repoInfos, milestoneTitles, tagNames, columnLabels, userName, states, types, reviews, columnRegExp, routeSegmentName};
}

class FilterState {
Expand Down Expand Up @@ -190,6 +193,9 @@ class FilterState {
toggleType(type) {
return this._toggleKey('types', type);
}
toggleReviews(reviews) {
return this._toggleKey('reviews', reviews);
}
// setUser(user)
// clearUser()
toggleUserName(name) {
Expand Down Expand Up @@ -220,6 +226,7 @@ const DEFAULTS = {
tagNames: [],
states: ['open'],
types: ['issue', 'pull-request'],
reviews: ['my-reviews', 'reviews-under-my-pr', 'my-meta-reviews', 'others'],
columnLabels: [],
columnRegExp: undefined
};
Expand Down Expand Up @@ -350,3 +357,51 @@ export function filterCardsByFilter(cards, filter) {
return true;
});
}

// Filters the list of reviews by the criteria set in the URL.
// Note this happens after `issues/prs` get filtered. A review is
// just a part of a pull request, so this would only take effect
// if its corresponding issue is not filtered out.
// Used by FilterStore.filterAndSortReviews()
export function filterReviewsByFilter(reviews, filter, user) {
filter = filter || getFilters();
const {reviews: reviewOptions} = filter.getState();

let myReviews, reviewsUnderMyPr, myMetaReviews, others;
for (const reviewOption of reviewOptions) {
switch (reviewOption) {
case 'my-reviews':
myReviews = true;
break;
case 'reviews-under-my-pr':
reviewsUnderMyPr = true;
break;
case 'my-meta-reviews':
myMetaReviews = true;
break;
case 'others':
others = true;
break;
default:
throw new Error('Review filter is invalid!');
}
}

return reviews.filter(review => {
const isMyReview = review.author && review.author.login && review.author.login === user;
const isReviewUnderMyPr = review.prAuthor === user;
const hasMyMetaReview = review.reactions && review.reactions.some(reaction => {
return reaction.user && reaction.user.login && reaction.user.login === user;
});

// This is main filter that is needed. It should be default not to show the
// user their own comments, unless they explicitly request it.
if (!myReviews && isMyReview) return false;

if (myReviews && isMyReview) return true;
if (reviewsUnderMyPr && isReviewUnderMyPr) return true;
if (myMetaReviews && hasMyMetaReview) return true;
if (others && !isMyReview && !isReviewUnderMyPr && !hasMyMetaReview) return true;
return false;
});
}

0 comments on commit 9eae168

Please sign in to comment.