@@ -41,7 +41,6 @@ export const GitHubIssues = ({
|
||||
itemsPerRepo = 40,
|
||||
}: Props) => {
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
const [activeFilter, setActiveFilter] = React.useState<Array<string>>([]);
|
||||
|
||||
const [issuesByRepository, setIssuesByRepository] =
|
||||
React.useState<Record<string, RepoIssues>>();
|
||||
@@ -49,62 +48,6 @@ export const GitHubIssues = ({
|
||||
const { repositories } = useEntityGitHubRepositories();
|
||||
const getIssues = useGetIssuesByRepoFromGitHub();
|
||||
|
||||
const filters = React.useMemo(
|
||||
() =>
|
||||
issuesByRepository
|
||||
? Object.keys(issuesByRepository)
|
||||
.filter(repo => issuesByRepository[repo].issues.totalCount > 0)
|
||||
.map(repo => ({
|
||||
label: `${repo} (${issuesByRepository[repo].issues.totalCount})`,
|
||||
value: repo,
|
||||
}))
|
||||
: [],
|
||||
[issuesByRepository],
|
||||
);
|
||||
|
||||
const totalIssuesInGitHub = React.useMemo(
|
||||
() =>
|
||||
issuesByRepository
|
||||
? Object.values(issuesByRepository).reduce(
|
||||
(acc, { issues: { totalCount } }) => acc + totalCount,
|
||||
0,
|
||||
)
|
||||
: 0,
|
||||
[issuesByRepository],
|
||||
);
|
||||
|
||||
const filteredRepos = React.useMemo(
|
||||
() =>
|
||||
issuesByRepository && activeFilter.length
|
||||
? activeFilter.reduce(
|
||||
(acc, val) => ({
|
||||
[val]: issuesByRepository[val],
|
||||
...acc,
|
||||
}),
|
||||
{},
|
||||
)
|
||||
: issuesByRepository,
|
||||
[issuesByRepository, activeFilter],
|
||||
);
|
||||
|
||||
const issues = React.useMemo(
|
||||
() =>
|
||||
filteredRepos
|
||||
? Object.values(filteredRepos)
|
||||
.map(({ issues: { edges } }) => edges)
|
||||
.flat()
|
||||
.sort((a, b) => {
|
||||
if (a.node.updatedAt > b.node.updatedAt) {
|
||||
return -1;
|
||||
} else if (b.node.updatedAt > a.node.updatedAt) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
: [],
|
||||
[filteredRepos],
|
||||
);
|
||||
|
||||
const fetchGitHubIssues = React.useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
const issuesByRepo = await getIssues(repositories, itemsPerRepo);
|
||||
@@ -139,11 +82,8 @@ export const GitHubIssues = ({
|
||||
{isLoading && <Progress />}
|
||||
|
||||
<IssueList
|
||||
issuesByRepository={issuesByRepository}
|
||||
itemsPerPage={itemsPerPage}
|
||||
issues={issues}
|
||||
totalIssuesInGitHub={totalIssuesInGitHub}
|
||||
setActiveFilter={setActiveFilter}
|
||||
filters={filters}
|
||||
/>
|
||||
</InfoCard>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,13 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { Box, Paper, Typography, CardActionArea } from '@material-ui/core';
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
CardActionArea,
|
||||
Link,
|
||||
} from '@material-ui/core';
|
||||
import Assignees from './Assignees';
|
||||
import { CommentsCount } from './CommentsCount';
|
||||
|
||||
@@ -58,9 +64,12 @@ export const IssueCard: FunctionComponent<Props> = (props: Props) => {
|
||||
<CardActionArea href={url} target="_blank">
|
||||
<Box padding={1}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography color="primary" variant="body2" component="h2">
|
||||
<Link
|
||||
href={`https://github.com/${repositoryName}/issues`}
|
||||
target="_blank"
|
||||
>
|
||||
{repositoryName}
|
||||
</Typography>
|
||||
</Link>
|
||||
<Assignees name={assigneeName} avatar={assigneeAvatar} />
|
||||
</Box>
|
||||
<Box>
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Select } from '@backstage/core-components';
|
||||
import { SelectedItems } from '@backstage/core-components';
|
||||
import { makeStyles, Box } from '@material-ui/core';
|
||||
import { Select, SelectedItems } from '@backstage/core-components';
|
||||
import { makeStyles, Box, Typography } from '@material-ui/core';
|
||||
|
||||
export type FilterItem = {
|
||||
label: string;
|
||||
@@ -26,6 +25,7 @@ export type FilterItem = {
|
||||
type Props = {
|
||||
items: Array<FilterItem>;
|
||||
totalIssuesInGitHub: number;
|
||||
placeholder: string;
|
||||
onChange: (active: Array<string>) => void;
|
||||
};
|
||||
|
||||
@@ -33,9 +33,9 @@ const useStyles = makeStyles(theme => ({
|
||||
filters: {
|
||||
margin: theme.spacing(0, 0, 2, 0),
|
||||
'& > div': {
|
||||
width: '600px',
|
||||
maxWidth: '800px',
|
||||
'& > div': {
|
||||
maxWidth: '600px',
|
||||
maxWidth: '800px',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -47,18 +47,22 @@ const checkSelectedItems: (
|
||||
return onChange(active as Array<string>);
|
||||
};
|
||||
|
||||
export const Filters = ({ items, totalIssuesInGitHub, onChange }: Props) => {
|
||||
export const Filters = ({ items, onChange, placeholder }: Props) => {
|
||||
const css = useStyles();
|
||||
|
||||
return (
|
||||
<Box className={css.filters}>
|
||||
<Select
|
||||
placeholder={`All repositories (${totalIssuesInGitHub})`}
|
||||
placeholder={placeholder}
|
||||
label=""
|
||||
items={items}
|
||||
multiple
|
||||
onChange={checkSelectedItems(onChange)}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
*Repositories with more Issues in GitHub than available to view in
|
||||
Backstage. To view them go to GitHub.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,29 +19,86 @@ import { Box } from '@material-ui/core';
|
||||
import { Pagination } from '@material-ui/lab';
|
||||
|
||||
import { IssueCard } from '../IssueCard';
|
||||
import { Issue } from '../../../hooks/useGetIssuesByRepoFromGitHub';
|
||||
import { Filters, FilterItem } from './Filters';
|
||||
import { RepoIssues } from '../../../hooks/useGetIssuesByRepoFromGitHub';
|
||||
import { Filters } from './Filters';
|
||||
|
||||
export type PluginMode = 'page' | 'card';
|
||||
|
||||
export type Props = {
|
||||
itemsPerPage?: number;
|
||||
issues: Array<{
|
||||
node: Issue;
|
||||
}>;
|
||||
filters: Array<FilterItem>;
|
||||
totalIssuesInGitHub: number;
|
||||
setActiveFilter: (active: Array<string>) => void;
|
||||
issuesByRepository?: Record<string, RepoIssues>;
|
||||
};
|
||||
|
||||
export const IssueList = ({
|
||||
itemsPerPage = 10,
|
||||
issues,
|
||||
filters,
|
||||
setActiveFilter,
|
||||
totalIssuesInGitHub,
|
||||
}: Props) => {
|
||||
const getIssuesCountForFilterLabel = (
|
||||
totalIssues: number,
|
||||
issuesAvailable: number,
|
||||
) =>
|
||||
`(${totalIssues} ${totalIssues === 1 ? 'Issue' : `Issues`})${
|
||||
issuesAvailable < totalIssues ? '*' : ''
|
||||
}`;
|
||||
|
||||
export const IssueList = ({ itemsPerPage = 10, issuesByRepository }: Props) => {
|
||||
const [currentPage, setCurrentPage] = React.useState(1);
|
||||
const [activeFilter, setActiveFilter] = React.useState<Array<string>>([]);
|
||||
|
||||
const filters = React.useMemo(
|
||||
() =>
|
||||
issuesByRepository
|
||||
? Object.keys(issuesByRepository)
|
||||
.filter(repo => issuesByRepository[repo].issues.totalCount > 0)
|
||||
.map(repo => ({
|
||||
label: `${repo} ${getIssuesCountForFilterLabel(
|
||||
issuesByRepository[repo].issues.totalCount,
|
||||
issuesByRepository[repo].issues.edges.length,
|
||||
)}`,
|
||||
value: repo,
|
||||
}))
|
||||
: [],
|
||||
[issuesByRepository],
|
||||
);
|
||||
|
||||
const totalIssuesInGitHub = React.useMemo(
|
||||
() =>
|
||||
issuesByRepository
|
||||
? Object.values(issuesByRepository).reduce(
|
||||
(acc, { issues: { totalCount } }) => acc + totalCount,
|
||||
0,
|
||||
)
|
||||
: 0,
|
||||
[issuesByRepository],
|
||||
);
|
||||
|
||||
const filteredRepos = React.useMemo(
|
||||
() =>
|
||||
issuesByRepository && activeFilter.length
|
||||
? activeFilter.reduce(
|
||||
(acc, val) => ({
|
||||
[val]: issuesByRepository[val],
|
||||
...acc,
|
||||
}),
|
||||
{},
|
||||
)
|
||||
: issuesByRepository,
|
||||
[issuesByRepository, activeFilter],
|
||||
);
|
||||
|
||||
const issues = React.useMemo(
|
||||
() =>
|
||||
filteredRepos
|
||||
? Object.values(filteredRepos)
|
||||
.map(({ issues: { edges } }) => edges)
|
||||
.flat()
|
||||
.sort((a, b) => {
|
||||
if (a.node.updatedAt > b.node.updatedAt) {
|
||||
return -1;
|
||||
} else if (b.node.updatedAt > a.node.updatedAt) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
: [],
|
||||
[filteredRepos],
|
||||
);
|
||||
|
||||
const displayIssues = issues.slice(
|
||||
(currentPage - 1) * itemsPerPage,
|
||||
@@ -50,11 +107,17 @@ export const IssueList = ({
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Filters
|
||||
items={filters}
|
||||
onChange={setActiveFilter}
|
||||
totalIssuesInGitHub={totalIssuesInGitHub}
|
||||
/>
|
||||
{issues.length > 0 && (
|
||||
<Filters
|
||||
placeholder={`All repositories ${getIssuesCountForFilterLabel(
|
||||
totalIssuesInGitHub,
|
||||
issues.length,
|
||||
)}`}
|
||||
items={filters}
|
||||
onChange={setActiveFilter}
|
||||
totalIssuesInGitHub={totalIssuesInGitHub}
|
||||
/>
|
||||
)}
|
||||
|
||||
{displayIssues.length > 0 ? (
|
||||
displayIssues.map(
|
||||
@@ -88,7 +151,7 @@ export const IssueList = ({
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<h1>No issues 🚀</h1>
|
||||
<h1>Hurray! No Issues 🚀</h1>
|
||||
)}
|
||||
{issues.length / itemsPerPage > 1 ? (
|
||||
<Pagination
|
||||
|
||||
@@ -27,14 +27,14 @@ describe('useGetIssuesBeRepoFromGitHub', () => {
|
||||
const Helper = () => {
|
||||
const getIssues = useGetIssuesByRepoFromGitHub();
|
||||
|
||||
getIssues(['mrwolny/yo-yo', 'mrwolny/yoyo'], 10);
|
||||
getIssues(['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], 10);
|
||||
|
||||
return <div />;
|
||||
};
|
||||
|
||||
render(<Helper />);
|
||||
|
||||
expect(mockGraphQLQuery).toHaveBeenCalled();
|
||||
expect(mockGraphQLQuery).toHaveBeenCalledTimes(1);
|
||||
expect(mockGraphQLQuery).toHaveBeenCalledWith(
|
||||
'\n' +
|
||||
' \n' +
|
||||
@@ -46,7 +46,6 @@ describe('useGetIssuesBeRepoFromGitHub', () => {
|
||||
' ) {\n' +
|
||||
' totalCount\n' +
|
||||
' edges {\n' +
|
||||
' cursor\n' +
|
||||
' node {\n' +
|
||||
' assignees(first: 10) {\n' +
|
||||
' edges {\n' +
|
||||
@@ -64,7 +63,6 @@ describe('useGetIssuesBeRepoFromGitHub', () => {
|
||||
' repository {\n' +
|
||||
' nameWithOwner\n' +
|
||||
' }\n' +
|
||||
' body\n' +
|
||||
' title\n' +
|
||||
' url\n' +
|
||||
' participants {\n' +
|
||||
@@ -90,6 +88,10 @@ describe('useGetIssuesBeRepoFromGitHub', () => {
|
||||
' yoyox: repository(name: "yoyo", owner: "mrwolny") {\n' +
|
||||
' ...issues\n' +
|
||||
' }\n' +
|
||||
' ,\n' +
|
||||
' yoyoxx: repository(name: "yo.yo", owner: "mrwolny") {\n' +
|
||||
' ...issues\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
' } \n' +
|
||||
' ',
|
||||
|
||||
@@ -36,13 +36,11 @@ type IssueAuthor = {
|
||||
};
|
||||
|
||||
export type Issue = {
|
||||
cursor: string;
|
||||
assignees: EdgesWithNodes<Assignee>;
|
||||
author: IssueAuthor;
|
||||
repository: {
|
||||
nameWithOwner: string;
|
||||
};
|
||||
body: string;
|
||||
title: string;
|
||||
url: string;
|
||||
participants: {
|
||||
@@ -80,7 +78,6 @@ const createQuery = (
|
||||
) {
|
||||
totalCount
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
assignees(first: 10) {
|
||||
edges {
|
||||
@@ -98,7 +95,6 @@ const createQuery = (
|
||||
repository {
|
||||
nameWithOwner
|
||||
}
|
||||
body
|
||||
title
|
||||
url
|
||||
participants {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Issue } from './useGetIssuesByRepoFromGitHub';
|
||||
|
||||
export type GitHubIssues = {
|
||||
getIssues: (repo?: string) => Array<Issue>;
|
||||
getIssuesTotalCountByRepo: () => Record<string, { totalCount: number }>;
|
||||
};
|
||||
Reference in New Issue
Block a user