Skip to content

Commit

Permalink
feat: update dashboard configuration instructutions
Browse files Browse the repository at this point in the history
  • Loading branch information
prototypicalpro committed Oct 27, 2020
1 parent 0d9b8ea commit 4a50d55
Show file tree
Hide file tree
Showing 5 changed files with 543 additions and 406 deletions.
180 changes: 29 additions & 151 deletions nerdlets/maintainer-dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { client } from './graphql/ApolloClientInstance';
import * as humanizeDuration from 'humanize-duration';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, {
Expand All @@ -11,16 +10,9 @@ import filterFactory, {
multiSelectFilter
} from 'react-bootstrap-table2-filter';
import {
Card,
CardHeader,
CardBody,
HeadingText,
Spinner,
Grid,
GridItem,
Stack,
StackItem,
Spacing,
Table,
TableHeader,
TableHeaderCell,
Expand All @@ -30,150 +22,12 @@ import {
Tabs,
TabsItem,
BillboardChart,
BlockText,
Icon,
Modal,
TextField,
Link,
Select,
SelectItem,
UserStorageMutation
Tooltip
} from 'nr1';
import { getGithubData } from './githubData';
import { IssueLabel } from './issueLabel';
import PullRequestLogo from './img/git-pull-request-16.svg';
import IssueLogo from './img/issue-opened-16.svg';

class IssueTable extends React.PureComponent {
static propTypes = {
items: PropTypes.arrayOf(PropTypes.object)
};

constructor(props) {
super(props);
}

render() {
const sortCaret = order => {
let type;
if (order === 'asc') type = Icon.TYPE.INTERFACE__ARROW__ARROW_TOP;
else if (order === 'desc')
type = Icon.TYPE.INTERFACE__ARROW__ARROW_BOTTOM;
else type = Icon.TYPE.INTERFACE__ARROW__ARROW_VERTICAL;
return (
<Button
sizeType={Button.SIZE_TYPE.SMALL}
type={Button.TYPE.PLAIN}
iconType={type}
style={{ float: 'right' }}
/>
);
};

const allLabels = Array.from(
new Map(
this.props.items.flatMap(i => i.labels.nodes.map(l => [l.name, l]))
).values()
);

const columns = [
{
dataField: '__typename',
text: 'Type',
sort: true,
sortCaret,
formatter: cell => (
<img
src={cell === 'Issue' ? IssueLogo : PullRequestLogo}
style={{ marginRight: '40px' }}
/>
),
filter: selectFilter({
options: {
Issue: 'Issue',
PullRequest: 'Pull Request'
}
})
},
{
dataField: 'url',
text: 'Link',
formatter: (cell, row) => (
<Button
type={Button.TYPE.NORMAL}
iconType={Button.ICON_TYPE.INTERFACE__OPERATIONS__EXTERNAL_LINK}
onClick={() => window.open(cell, '_blank')}
>
#{row.number}
</Button>
)
},
{
dataField: 'repository.name',
text: 'Repository',
sort: true,
sortCaret,
filter: textFilter()
},
{
dataField: 'createdAt',
text: 'Open',
sort: true,
sortValue: cell => Date.now() - new Date(cell).getTime(),
sortCaret,
type: 'date',
filter: dateFilter({}),
formatter: cell =>
humanizeDuration(Date.now() - new Date(cell).getTime(), {
largest: 1
})
},
{
dataField: 'author.login',
text: 'User',
sort: true,
sortCaret,
filter: textFilter()
},
{
dataField: 'labels.nodes',
text: 'Labels',
filter: multiSelectFilter({
comparator: Comparator.LIKE,
options: allLabels.reduce((a, { name }) => {
a[name] = name;
return a;
}, {}),
withoutEmptyOption: true
}),
filterValue: cell => cell.map(({ name }) => name),
formatter: cell =>
cell.map(({ name, color }) => (
<IssueLabel key={name} name={name} color={color} />
))
},
{
dataField: 'title',
text: 'Title',
filter: textFilter()
}
];

return (
<>
<BootstrapTable
keyField="id"
data={this.props.items}
columns={columns}
defaultSorted={[{ dataField: 'createdAt', order: 'asc' }]}
rowStyle={{ whiteSpace: 'nowrap' }}
headerClasses="ospo-tableheader"
filter={filterFactory()}
/>
</>
);
}
}
import { IssueTable } from './issueTable';

export default class Dashboard extends React.Component {
static propTypes = {
Expand All @@ -200,7 +54,7 @@ export default class Dashboard extends React.Component {
this.setState(
await getGithubData(this.props.client, {
scanRepos: this.props.scanRepos,
companyUsers: this.props.companyUsers.concat(this.props.ignoreUsers), // TODO: ignore users by adding them to the company users?
companyUsers: this.props.companyUsers.concat(this.props.ignoreUsers),
ignoreLabels: this.props.ignoreLabels,
staleTime: this.props.staleTime
})
Expand Down Expand Up @@ -272,10 +126,34 @@ export default class Dashboard extends React.Component {
</StackItem>
<StackItem>
<Tabs default="new">
<TabsItem label="New Items" value="new">
<TabsItem
label={
<span>
New Items{' '}
<Tooltip text="Issues or PRs which have not received a response from an employee.">
<Icon type={Icon.TYPE.INTERFACE__INFO__HELP} />
</Tooltip>
</span>
}
value="new"
>
<IssueTable items={this.state.newSearchItems} />
</TabsItem>
<TabsItem label="Stale Items" value="stale">
<TabsItem
label={
<span>
Stale Items{' '}
<Tooltip
text={`Issues or PRs which have received a response from an employee, but have not received an employee follow up for longer than ${humanizeDuration(
this.props.staleTime
)}.`}
>
<Icon type={Icon.TYPE.INTERFACE__INFO__HELP} />
</Tooltip>
</span>
}
value="stale"
>
<IssueTable items={this.state.staleSearchItems} />
</TabsItem>
</Tabs>
Expand Down
155 changes: 1 addition & 154 deletions nerdlets/maintainer-dashboard/githubData.js
Original file line number Diff line number Diff line change
@@ -1,157 +1,4 @@
import gql from 'graphql-tag';

// TODO: split query into search + virtualized table to improve caching
const ISSUE_FRAGMENT = gql`
fragment GetIssueInfo on Issue {
id
title
author {
login
url
}
repository {
name
url
}
labels(first: 100, orderBy: { field: NAME, direction: ASC }) {
nodes {
name
color
}
}
number
url
createdAt
}
`;

const PR_FRAGMENT = gql`
fragment GetPRInfo on PullRequest {
id
title
author {
login
url
}
repository {
name
url
}
labels(first: 100, orderBy: { field: NAME, direction: ASC }) {
nodes {
name
color
}
}
number
url
createdAt
}
`;

const SEARCH_NEW_ITEMS_QUERY = gql`
query SearchResults($query: String!) {
search(query: $query, type: ISSUE, first: 100) {
nodes {
__typename
...GetIssueInfo
...GetPRInfo
}
issueCount
}
rateLimit {
limit
cost
remaining
resetAt
}
}
${PR_FRAGMENT}
${ISSUE_FRAGMENT}
`;

const SEARCH_STALE_ITEMS_QUERY = gql`
query SearchResults(
$queryDefStale: String!
$queryMaybeStale: String!
$timeSince: DateTime!
) {
definitelyStale: search(query: $queryDefStale, type: ISSUE, first: 100) {
issueCount
nodes {
__typename
...GetIssueInfo
...GetPRInfo
}
}
maybeStale: search(query: $queryMaybeStale, type: ISSUE, first: 100) {
issueCount
nodes {
__typename
...GetIssueInfo
...GetPRInfo
... on Issue {
timelineItems(since: $timeSince, last: 100) {
nodes {
... on Comment {
id
author {
login
}
updatedAt
}
}
}
}
... on PullRequest {
timelineItems(since: $timeSince, last: 100) {
nodes {
... on Comment {
id
author {
login
}
updatedAt
}
}
}
}
}
}
rateLimit {
limit
cost
remaining
resetAt
}
}
${PR_FRAGMENT}
${ISSUE_FRAGMENT}
`;

function makeNewSearch(users, repos, ignoreLabels) {
return `${repos.map(r => `repo:${r}`).join(' ')} ${users
.map(u => `-author:${u} -commenter:${u}`)
.join(' ')} ${ignoreLabels.map(l => `-label:${l}`).join(' ')} is:open`;
}

function makeDefStaleSearch(users, repos, ignoreLabels, date) {
return `${repos.map(r => `repo:${r}`).join(' ')} ${users
.map(u => `-author:${u} commenter:${u}`)
.join(' ')} ${ignoreLabels
.map(l => `-label:${l}`)
.join(' ')} is:open updated:<=${date.toISOString()}`;
}

function makeMaybeStaleSearch(users, repos, ignoreLabels, date) {
return `${repos.map(r => `repo:${r}`).join(' ')} ${users
.map(u => `-author:${u} commenter:${u}`)
.join(' ')} ${ignoreLabels
.map(l => `-label:${l}`)
.join(
' '
)} is:open updated:>${date.toISOString()} created:<=${date.toISOString()}`;
}
import { SEARCH_NEW_ITEMS_QUERY,SEARCH_STALE_ITEMS_QUERY, makeDefStaleSearch, makeMaybeStaleSearch, makeNewSearch } from './graphql/GithubQueries';

/**
* Run a GraphQL query to get information about new and stale items for a given set of repositories.
Expand Down
Loading

0 comments on commit 4a50d55

Please sign in to comment.