Merge pull request #12875 from getndazn/feat/github-issues-board
feat: new plugin GitHub Issues
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-github-issues': minor
|
||||
---
|
||||
|
||||
New plugin for displaying GitHub Issues added
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,63 @@
|
||||
# GitHub Issues plugin
|
||||
|
||||
Welcome to the GitHub Issues plugin!
|
||||
|
||||
Based on the [well-known GitHub slug annotation](https://backstage.io/docs/features/software-catalog/well-known-annotations#githubcomproject-slug) associated with the Entity, it renders the list of Open issues in GitHub.
|
||||
|
||||
The plugin is designed to work with four Entity kinds, and it behaves a bit differently depending on that kind:
|
||||
|
||||
- Kind: Group/User: plugin renders issues from all repositories for which the Entity is the owner.
|
||||
- Kind: API/Component: plugin renders issues from only one repository assigned to the Entity
|
||||
|
||||
**Issues are sorted from the recently updated DESC order (the plugin might not render all issues from a single repo next to each other).**
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [GitHub Authentication Provider](https://backstage.io/docs/auth/github/provider)
|
||||
|
||||
## Usage
|
||||
|
||||
Install the plugin by running the following command **from your Backstage root directory**
|
||||
|
||||
`yarn --cwd packages/app add @backstage/plugin-github-issues`
|
||||
|
||||
After installation, the plugin can be used as a Card or as a Page.
|
||||
|
||||
```typescript
|
||||
import {
|
||||
GitHubIssuesCard,
|
||||
GitHubIssuesPage,
|
||||
} from '@backstage/plugin-github-issues';
|
||||
|
||||
// To use as a page Plugin needs to be wrapped in EntityLayout.Route
|
||||
const RenderGitHubIssuesPage = () => (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<EntityLayout.Route path="github-issues" title="GitHub Issues">
|
||||
<GitHubIssuesPage />
|
||||
</EntityLayout.Route>
|
||||
<EntityLayout.Route />
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
// To use as a card and make it render correctly please place it inside appropriate Grid elements
|
||||
const RenderGitHubIssuesCard = () => (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<GitHubIssuesCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<EntityLayout.Route />
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Both `GitHubIssuesPage` and `GitHubIssuesCard` provide default configuration. It is ready to use out of the box.
|
||||
However, you can configure the plugin with props:
|
||||
|
||||
- `itemsPerPage: number = 10` - Issues in the list are paginated, number of issues on a single page is controlled with this prop
|
||||
- `itemsPerRepo: number = 40` - the plugin doesn't download all Issues available on GitHub. By default, it will get at most 40 Issues - this prop controls this behaviour
|
||||
@@ -0,0 +1,33 @@
|
||||
## API Report File for "@backstage/plugin-github-issues"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
export const GitHubIssuesCard: (props: GitHubIssuesProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const GitHubIssuesPage: (props: GitHubIssuesProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const gitHubIssuesPlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
},
|
||||
{},
|
||||
{}
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type GitHubIssuesProps = {
|
||||
itemsPerPage?: number;
|
||||
itemsPerRepo?: number;
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@backstage/plugin-github-issues",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "frontend-plugin"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^1.0.3",
|
||||
"@backstage/core-components": "^0.10.1-next.0",
|
||||
"@backstage/core-plugin-api": "^1.0.5-next.0",
|
||||
"@backstage/integration": "^1.3.0-next.0",
|
||||
"@backstage/plugin-catalog-react": "^1.1.3-next.0",
|
||||
"@backstage/theme": "^0.2.15",
|
||||
"@material-ui/core": "^4.12.4",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||
"luxon": "^2.4.0",
|
||||
"octokit": "^2.0.4",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.18.1-next.0",
|
||||
"@backstage/core-app-api": "^1.0.5-next.0",
|
||||
"@backstage/dev-utils": "^1.0.5-next.0",
|
||||
"@backstage/test-utils": "^1.1.3-next.0",
|
||||
"@spotify/prettier-config": "^13.0.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/jest": "*",
|
||||
"@types/node": "*",
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"msw": "^0.44.0",
|
||||
"prettier": "^2.7.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { Box, IconButton, Typography } from '@material-ui/core';
|
||||
import { InfoCard, Progress } from '@backstage/core-components';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
|
||||
import { useEntityGitHubRepositories } from '../../hooks/useEntityGitHubRepositories';
|
||||
import {
|
||||
RepoIssues,
|
||||
useGetIssuesByRepoFromGitHub,
|
||||
} from '../../hooks/useGetIssuesByRepoFromGitHub';
|
||||
|
||||
import { IssueList } from './IssuesList';
|
||||
import { NoRepositoriesInfo } from './NoRepositoriesInfo';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type GitHubIssuesProps = {
|
||||
itemsPerPage?: number;
|
||||
itemsPerRepo?: number;
|
||||
};
|
||||
|
||||
export const GitHubIssues = (props: GitHubIssuesProps) => {
|
||||
const { itemsPerPage = 10, itemsPerRepo = 40 } = props;
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
const [issuesByRepository, setIssuesByRepository] =
|
||||
React.useState<Record<string, RepoIssues>>();
|
||||
|
||||
const { repositories } = useEntityGitHubRepositories();
|
||||
const getIssues = useGetIssuesByRepoFromGitHub();
|
||||
|
||||
const fetchGitHubIssues = React.useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
const issuesByRepo = await getIssues(repositories, itemsPerRepo);
|
||||
|
||||
setIssuesByRepository(issuesByRepo);
|
||||
setIsLoading(false);
|
||||
}, [itemsPerRepo, getIssues, repositories]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (repositories.length) {
|
||||
fetchGitHubIssues();
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [repositories.length, fetchGitHubIssues]);
|
||||
|
||||
if (!repositories.length) {
|
||||
return <NoRepositoriesInfo />;
|
||||
}
|
||||
|
||||
return (
|
||||
<InfoCard
|
||||
title={
|
||||
<Box display="flex" justifyContent="flex-start" alignItems="center">
|
||||
<Typography variant="h5">Open GitHub Issues</Typography>
|
||||
<IconButton color="secondary" onClick={fetchGitHubIssues}>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
{isLoading && <Progress />}
|
||||
|
||||
<IssueList
|
||||
issuesByRepository={issuesByRepository}
|
||||
itemsPerPage={itemsPerPage}
|
||||
/>
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Typography, Box, Avatar, makeStyles } from '@material-ui/core';
|
||||
|
||||
type AssigneesProps = {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
small: {
|
||||
width: theme.spacing(4),
|
||||
height: theme.spacing(4),
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
noAssignees: {
|
||||
height: theme.spacing(4),
|
||||
},
|
||||
}));
|
||||
|
||||
export const Assignees = (props: AssigneesProps) => {
|
||||
const { name, avatar } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
// todo: many assignees -> NUM assignees + stock images on each other
|
||||
return name ? (
|
||||
<Box display="flex" alignItems="center" marginX={1}>
|
||||
<Typography color="primary" variant="body2" component="p">
|
||||
{name}
|
||||
</Typography>
|
||||
<Avatar alt={name} src={avatar} className={classes.small} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box display="flex" alignItems="center" marginX={1}>
|
||||
<Typography
|
||||
color="primary"
|
||||
variant="body2"
|
||||
component="p"
|
||||
className={classes.noAssignees}
|
||||
>
|
||||
No assignees
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { ChatIcon } from '@backstage/core-components';
|
||||
import { Box, Badge } from '@material-ui/core';
|
||||
|
||||
type CommentsCountProps = {
|
||||
commentsCount: number;
|
||||
};
|
||||
|
||||
export const CommentsCount = (props: CommentsCountProps) => {
|
||||
const { commentsCount } = props;
|
||||
|
||||
return (
|
||||
<Box
|
||||
marginBottom={1}
|
||||
style={{ marginRight: '12px' }}
|
||||
display="flex"
|
||||
justifyContent="flex-start"
|
||||
alignSelf="flex-end"
|
||||
>
|
||||
<Badge badgeContent={commentsCount} color="primary">
|
||||
<ChatIcon />
|
||||
</Badge>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
CardActionArea,
|
||||
Link,
|
||||
} from '@material-ui/core';
|
||||
import { Assignees } from './Assignees';
|
||||
import { CommentsCount } from './CommentsCount';
|
||||
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
|
||||
type IssueCardProps = {
|
||||
title: string;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
url: string;
|
||||
authorName: string;
|
||||
assigneeName?: string;
|
||||
assigneeAvatar?: string;
|
||||
authorAvatar?: string;
|
||||
repositoryName: string;
|
||||
commentsCount: number;
|
||||
even: boolean;
|
||||
};
|
||||
|
||||
const getElapsedTime = (isoDate: string) =>
|
||||
DateTime.fromISO(isoDate).toRelative();
|
||||
|
||||
export const IssueCard = (props: IssueCardProps) => {
|
||||
const {
|
||||
title,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
url,
|
||||
assigneeName,
|
||||
assigneeAvatar,
|
||||
authorName,
|
||||
repositoryName,
|
||||
commentsCount,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Box marginBottom={1}>
|
||||
<Paper variant="outlined">
|
||||
<CardActionArea href={url} target="_blank">
|
||||
<Box padding={1}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Link
|
||||
href={`https://github.com/${repositoryName}/issues`}
|
||||
target="_blank"
|
||||
>
|
||||
{repositoryName}
|
||||
</Link>
|
||||
<Assignees name={assigneeName} avatar={assigneeAvatar} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography component="h2">
|
||||
<b>{title}</b>
|
||||
</Typography>
|
||||
</Box>
|
||||
<Divider variant="middle" />
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box marginY={1}>
|
||||
<Typography variant="body2" component="p">
|
||||
Created at: <strong>{getElapsedTime(createdAt)}</strong> by{' '}
|
||||
<strong>{authorName}</strong>
|
||||
</Typography>
|
||||
{updatedAt && (
|
||||
<Typography variant="body2" component="p">
|
||||
Last update at: <strong>{getElapsedTime(updatedAt)}</strong>
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
{commentsCount > 0 && (
|
||||
<CommentsCount commentsCount={commentsCount} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</CardActionArea>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export { IssueCard } from './IssueCard';
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Select, SelectedItems, SelectItem } from '@backstage/core-components';
|
||||
import { makeStyles, Box, Typography } from '@material-ui/core';
|
||||
|
||||
type RepositoryFiltersProps = {
|
||||
items: Array<SelectItem>;
|
||||
totalIssuesInGitHub: number;
|
||||
placeholder: string;
|
||||
onChange: (active: Array<string>) => void;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
filters: {
|
||||
margin: theme.spacing(0, 0, 2, 0),
|
||||
'& > div': {
|
||||
maxWidth: '800px',
|
||||
'& > div': {
|
||||
maxWidth: '800px',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const checkSelectedItems: (
|
||||
onChange: (active: Array<string>) => void,
|
||||
) => (active: SelectedItems) => void = onChange => active => {
|
||||
return onChange(active as Array<string>);
|
||||
};
|
||||
|
||||
export const RepositoryFilters = ({
|
||||
items,
|
||||
onChange,
|
||||
placeholder,
|
||||
}: RepositoryFiltersProps) => {
|
||||
const css = useStyles();
|
||||
|
||||
return (
|
||||
<Box className={css.filters}>
|
||||
<Select
|
||||
placeholder={placeholder}
|
||||
label=""
|
||||
items={items}
|
||||
multiple
|
||||
onChange={checkSelectedItems(onChange)}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
*Repositories with more Issues on GitHub than available to view in
|
||||
Backstage. To view them go to GitHub.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export * from './Filters';
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
import { Pagination } from '@material-ui/lab';
|
||||
|
||||
import { IssueCard } from '../IssueCard';
|
||||
import { RepoIssues } from '../../../hooks/useGetIssuesByRepoFromGitHub';
|
||||
import { RepositoryFilters } from './Filters';
|
||||
|
||||
export type PluginMode = 'page' | 'card';
|
||||
|
||||
export type IssueListProps = {
|
||||
itemsPerPage?: number;
|
||||
issuesByRepository?: Record<string, RepoIssues>;
|
||||
};
|
||||
|
||||
const getIssuesCountForFilterLabel = (
|
||||
totalIssues: number,
|
||||
issuesAvailable: number,
|
||||
) =>
|
||||
`(${totalIssues} ${totalIssues === 1 ? 'Issue' : `Issues`})${
|
||||
issuesAvailable < totalIssues ? '*' : ''
|
||||
}`;
|
||||
|
||||
export const IssueList = ({
|
||||
itemsPerPage = 10,
|
||||
issuesByRepository,
|
||||
}: IssueListProps) => {
|
||||
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,
|
||||
(currentPage - 1) * itemsPerPage + itemsPerPage,
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{issues.length > 0 && (
|
||||
<RepositoryFilters
|
||||
placeholder={`All repositories ${getIssuesCountForFilterLabel(
|
||||
totalIssuesInGitHub,
|
||||
issues.length,
|
||||
)}`}
|
||||
items={filters}
|
||||
onChange={setActiveFilter}
|
||||
totalIssuesInGitHub={totalIssuesInGitHub}
|
||||
/>
|
||||
)}
|
||||
|
||||
{displayIssues.length > 0 ? (
|
||||
displayIssues.map(
|
||||
(
|
||||
{
|
||||
node: {
|
||||
title,
|
||||
comments,
|
||||
author,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
repository,
|
||||
assignees,
|
||||
url,
|
||||
},
|
||||
},
|
||||
index,
|
||||
) => (
|
||||
<IssueCard
|
||||
even={Boolean(index % 2)}
|
||||
title={title}
|
||||
createdAt={createdAt}
|
||||
assigneeAvatar={assignees.edges[0]?.node.avatarUrl}
|
||||
assigneeName={assignees.edges[0]?.node.login}
|
||||
authorName={author.login}
|
||||
updatedAt={updatedAt}
|
||||
repositoryName={repository.nameWithOwner}
|
||||
url={url}
|
||||
commentsCount={comments.totalCount}
|
||||
/>
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<h1>Hurray! No Issues 🚀</h1>
|
||||
)}
|
||||
{issues.length / itemsPerPage > 1 ? (
|
||||
<Pagination
|
||||
count={Math.ceil(issues.length / itemsPerPage)}
|
||||
onChange={(_, page) => setCurrentPage(page)}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export * from './IssuesList';
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { EmptyState } from '@backstage/core-components';
|
||||
|
||||
export const NoRepositoriesInfo = () => {
|
||||
return (
|
||||
<EmptyState
|
||||
title="There are no GitHub repositories connected to this entity."
|
||||
missing="data"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export * from './NoRepositoriesInfo';
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export * from './GitHubIssues';
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
humanizeEntityRef,
|
||||
useEntity,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
const GITHUB_PROJECT_SLUG_ANNOTATION = 'github.com/project-slug';
|
||||
|
||||
export const getProjectNameFromEntity = (entity: Entity): string => {
|
||||
return entity?.metadata.annotations?.[GITHUB_PROJECT_SLUG_ANNOTATION] ?? '';
|
||||
};
|
||||
|
||||
export function useEntityGitHubRepositories() {
|
||||
const { entity } = useEntity();
|
||||
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const [repositories, setRepositories] = useState<string[]>([]);
|
||||
|
||||
const getRepositoriesNames = useCallback(async () => {
|
||||
if (entity.kind === 'Component' || entity.kind === 'API') {
|
||||
const entityName = getProjectNameFromEntity(entity);
|
||||
|
||||
if (entityName) {
|
||||
setRepositories([entityName]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const entitiesList = await catalogApi.getEntities({
|
||||
filter: {
|
||||
kind: ['Component', 'API'],
|
||||
'spec.owner': humanizeEntityRef(entity, { defaultKind: 'group' }),
|
||||
},
|
||||
});
|
||||
|
||||
const entitiesNames: string[] = entitiesList.items.map(componentEntity =>
|
||||
getProjectNameFromEntity(componentEntity),
|
||||
);
|
||||
|
||||
setRepositories([...new Set(entitiesNames)].filter(name => name.length));
|
||||
}, [catalogApi, entity]);
|
||||
|
||||
useEffect(() => {
|
||||
getRepositoriesNames();
|
||||
}, [getRepositoriesNames]);
|
||||
|
||||
return {
|
||||
repositories,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
const mockGraphQLQuery = jest.fn(() => ({}));
|
||||
jest.mock('./useOctokitGraphQL', () => ({
|
||||
useOctokitGraphQL: jest.fn(() => mockGraphQLQuery),
|
||||
}));
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { useGetIssuesByRepoFromGitHub } from './useGetIssuesByRepoFromGitHub';
|
||||
|
||||
describe('useGetIssuesBeRepoFromGitHub', () => {
|
||||
it('should call GitHub API with correct query with fragment for each repo', async () => {
|
||||
const Helper = () => {
|
||||
const getIssues = useGetIssuesByRepoFromGitHub();
|
||||
|
||||
getIssues(['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], 10);
|
||||
|
||||
return <div />;
|
||||
};
|
||||
|
||||
render(<Helper />);
|
||||
|
||||
expect(mockGraphQLQuery).toHaveBeenCalledTimes(1);
|
||||
expect(mockGraphQLQuery).toHaveBeenCalledWith(
|
||||
'\n' +
|
||||
' \n' +
|
||||
' fragment issues on Repository {\n' +
|
||||
' issues(\n' +
|
||||
' states: OPEN\n' +
|
||||
' first: 10\n' +
|
||||
' orderBy: { field: UPDATED_AT, direction: DESC }\n' +
|
||||
' ) {\n' +
|
||||
' totalCount\n' +
|
||||
' edges {\n' +
|
||||
' node {\n' +
|
||||
' assignees(first: 10) {\n' +
|
||||
' edges {\n' +
|
||||
' node {\n' +
|
||||
' avatarUrl\n' +
|
||||
' login\n' +
|
||||
' }\n' +
|
||||
' }\n' +
|
||||
' }\n' +
|
||||
' author {\n' +
|
||||
' login\n' +
|
||||
' avatarUrl\n' +
|
||||
' url\n' +
|
||||
' }\n' +
|
||||
' repository {\n' +
|
||||
' nameWithOwner\n' +
|
||||
' }\n' +
|
||||
' title\n' +
|
||||
' url\n' +
|
||||
' participants {\n' +
|
||||
' totalCount\n' +
|
||||
' }\n' +
|
||||
' updatedAt\n' +
|
||||
' createdAt\n' +
|
||||
' comments(last: 1) {\n' +
|
||||
' totalCount\n' +
|
||||
' }\n' +
|
||||
' }\n' +
|
||||
' }\n' +
|
||||
' }\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
'\n' +
|
||||
' query {\n' +
|
||||
' \n' +
|
||||
' yoyo: repository(name: "yo-yo", owner: "mrwolny") {\n' +
|
||||
' ...issues\n' +
|
||||
' }\n' +
|
||||
' ,\n' +
|
||||
' yoyox: repository(name: "yoyo", owner: "mrwolny") {\n' +
|
||||
' ...issues\n' +
|
||||
' }\n' +
|
||||
' ,\n' +
|
||||
' yoyoxx: repository(name: "yo.yo", owner: "mrwolny") {\n' +
|
||||
' ...issues\n' +
|
||||
' }\n' +
|
||||
' \n' +
|
||||
' } \n' +
|
||||
' ',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { useOctokitGraphQL } from './useOctokitGraphQL';
|
||||
|
||||
type Assignee = {
|
||||
avatarUrl: string;
|
||||
login: string;
|
||||
};
|
||||
|
||||
export type EdgesWithNodes<T> = {
|
||||
edges: Array<{
|
||||
node: T;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type Node<T> = {
|
||||
node: T;
|
||||
};
|
||||
|
||||
type IssueAuthor = {
|
||||
login: string;
|
||||
};
|
||||
|
||||
export type Issue = {
|
||||
assignees: EdgesWithNodes<Assignee>;
|
||||
author: IssueAuthor;
|
||||
repository: {
|
||||
nameWithOwner: string;
|
||||
};
|
||||
title: string;
|
||||
url: string;
|
||||
participants: {
|
||||
totalCount: number;
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
comments: {
|
||||
totalCount: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type RepoIssues = {
|
||||
issues: {
|
||||
totalCount: number;
|
||||
} & EdgesWithNodes<Issue>;
|
||||
};
|
||||
|
||||
export type RepoIssuesQueryResults = Record<string, RepoIssues>;
|
||||
|
||||
const createQuery = (
|
||||
repositories: Array<{
|
||||
safeName: string;
|
||||
name: string;
|
||||
owner: string;
|
||||
}>,
|
||||
itemsPerRepo: number,
|
||||
): string => {
|
||||
const fragment = `
|
||||
fragment issues on Repository {
|
||||
issues(
|
||||
states: OPEN
|
||||
first: ${itemsPerRepo}
|
||||
orderBy: { field: UPDATED_AT, direction: DESC }
|
||||
) {
|
||||
totalCount
|
||||
edges {
|
||||
node {
|
||||
assignees(first: 10) {
|
||||
edges {
|
||||
node {
|
||||
avatarUrl
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
author {
|
||||
login
|
||||
avatarUrl
|
||||
url
|
||||
}
|
||||
repository {
|
||||
nameWithOwner
|
||||
}
|
||||
title
|
||||
url
|
||||
participants {
|
||||
totalCount
|
||||
}
|
||||
updatedAt
|
||||
createdAt
|
||||
comments(last: 1) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const query = `
|
||||
${fragment}
|
||||
|
||||
query {
|
||||
${repositories.map(
|
||||
({ safeName, name, owner }) => `
|
||||
${safeName}: repository(name: "${name}", owner: "${owner}") {
|
||||
...issues
|
||||
}
|
||||
`,
|
||||
)}
|
||||
}
|
||||
`;
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
export const useGetIssuesByRepoFromGitHub = () => {
|
||||
const graphql = useOctokitGraphQL<RepoIssuesQueryResults>();
|
||||
|
||||
const fn = React.useRef(
|
||||
async (
|
||||
repos: Array<string>,
|
||||
itemsPerRepo: number,
|
||||
): Promise<Record<string, RepoIssues>> => {
|
||||
const safeNames: Array<string> = [];
|
||||
|
||||
const repositories = repos.map(repo => {
|
||||
const [owner, name] = repo.split('/');
|
||||
|
||||
const safeNameRegex = /-|\./gi;
|
||||
let safeName = name.replace(safeNameRegex, '');
|
||||
|
||||
while (safeNames.includes(safeName)) {
|
||||
safeName += 'x';
|
||||
}
|
||||
|
||||
safeNames.push(safeName);
|
||||
|
||||
return {
|
||||
safeName,
|
||||
name,
|
||||
owner,
|
||||
};
|
||||
});
|
||||
|
||||
const issuesByRepo: RepoIssuesQueryResults = await graphql(
|
||||
createQuery(repositories, itemsPerRepo),
|
||||
);
|
||||
|
||||
return repositories.reduce((acc, { safeName, name, owner }) => {
|
||||
acc[`${owner}/${name}`] = issuesByRepo[safeName];
|
||||
|
||||
return acc;
|
||||
}, {} as Record<string, RepoIssues>);
|
||||
},
|
||||
);
|
||||
|
||||
return fn.current;
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 { Octokit } from 'octokit';
|
||||
import {
|
||||
useApi,
|
||||
githubAuthApiRef,
|
||||
configApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
|
||||
let octokit: Octokit;
|
||||
|
||||
export const useOctokitGraphQL = <T>() => {
|
||||
const auth = useApi(githubAuthApiRef);
|
||||
const config = useApi(configApiRef);
|
||||
|
||||
const baseUrl = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
)[0].apiBaseUrl;
|
||||
|
||||
return (path: string, options?: any): Promise<T> =>
|
||||
auth
|
||||
.getAccessToken(['repo'])
|
||||
.then((token: string) => {
|
||||
if (!octokit) {
|
||||
octokit = new Octokit({ auth: token, ...(baseUrl && { baseUrl }) });
|
||||
}
|
||||
|
||||
return octokit;
|
||||
})
|
||||
.then(octokitInstance => {
|
||||
return octokitInstance.graphql(path, options);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export {
|
||||
gitHubIssuesPlugin,
|
||||
GitHubIssuesPage,
|
||||
GitHubIssuesCard,
|
||||
} from './plugin';
|
||||
|
||||
export type { GitHubIssuesProps } from './components/GitHubIssues';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { gitHubIssuesPlugin } from './plugin';
|
||||
|
||||
describe('github-issues', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(gitHubIssuesPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 {
|
||||
createPlugin,
|
||||
createComponentExtension,
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
/** @public */
|
||||
export const gitHubIssuesPlugin = createPlugin({
|
||||
id: 'github-issues',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const GitHubIssuesCard = gitHubIssuesPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'GitHubIssuesCard',
|
||||
component: {
|
||||
lazy: () => import('./components/GitHubIssues').then(m => m.GitHubIssues),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const GitHubIssuesPage = gitHubIssuesPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'GitHubIssuesPage',
|
||||
component: () =>
|
||||
import('./components/GitHubIssues').then(m => m.GitHubIssues),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'github-issues',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
@@ -4859,7 +4859,7 @@
|
||||
react-beautiful-dnd "^13.0.0"
|
||||
react-double-scrollbar "0.0.15"
|
||||
|
||||
"@material-ui/core@^4.11.0", "@material-ui/core@^4.11.3", "@material-ui/core@^4.12.1", "@material-ui/core@^4.12.2", "@material-ui/core@^4.9.10", "@material-ui/core@^4.9.13":
|
||||
"@material-ui/core@^4.11.0", "@material-ui/core@^4.11.3", "@material-ui/core@^4.12.1", "@material-ui/core@^4.12.2", "@material-ui/core@^4.12.4", "@material-ui/core@^4.9.10", "@material-ui/core@^4.9.13":
|
||||
version "4.12.4"
|
||||
resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz#4ac17488e8fcaf55eb6a7f5efb2a131e10138a73"
|
||||
integrity sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==
|
||||
@@ -4906,7 +4906,7 @@
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.8.0 || ^17.0.0"
|
||||
|
||||
"@material-ui/lab@^4.0.0-alpha.57", "@material-ui/lab@^4.0.0-alpha.60":
|
||||
"@material-ui/lab@^4.0.0-alpha.57", "@material-ui/lab@^4.0.0-alpha.60", "@material-ui/lab@^4.0.0-alpha.61":
|
||||
version "4.0.0-alpha.61"
|
||||
resolved "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.61.tgz#9bf8eb389c0c26c15e40933cc114d4ad85e3d978"
|
||||
integrity sha512-rSzm+XKiNUjKegj8bzt5+pygZeckNLOr+IjykH8sYdVk7dE9y2ZuUSofiMV2bJk3qU+JHwexmw+q0RyNZB9ugg==
|
||||
@@ -6270,6 +6270,11 @@
|
||||
resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-14.0.1.tgz#850a435b0defebbb3de591d83e3fc369bbc51753"
|
||||
integrity sha512-y/8on49Wtg3HvKd9A32Q7iJaOgngcSJR8hjGx/POFhJFcPRcZuTJSlhd31CF8fg78bMuzdvYH8au7FhNKjnEVw==
|
||||
|
||||
"@spotify/prettier-config@^13.0.1":
|
||||
version "13.0.1"
|
||||
resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-13.0.1.tgz#0fdceb3d4ab543259ce6adc0ec1d10e34898b812"
|
||||
integrity sha512-oVd4hjx2+y0MeUdk1l+ItwVLwlrDlvTlGwXBWMMzPYc7DLyxuxFvDfoHGkAQkrikfAgtdnzxrW6u9a8ywUqdfw==
|
||||
|
||||
"@spotify/prettier-config@^14.0.0":
|
||||
version "14.0.1"
|
||||
resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-14.0.1.tgz#a3a2342ec07693647ab76a64a623fbb79d464fa7"
|
||||
@@ -18266,6 +18271,11 @@ luxon@^1.23.x:
|
||||
resolved "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf"
|
||||
integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==
|
||||
|
||||
luxon@^2.4.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.npmjs.org/luxon/-/luxon-2.5.0.tgz#098090f67d690b247e83c090267a60b1aa8ea96c"
|
||||
integrity sha512-IDkEPB80Rb6gCAU+FEib0t4FeJ4uVOuX1CQ9GsvU3O+JAGIgu0J7sf1OarXKaKDygTZIoJyU6YdZzTFRu+YR0A==
|
||||
|
||||
luxon@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/luxon/-/luxon-3.0.1.tgz#6901111d10ad06fd267ad4e4128a84bef8a77299"
|
||||
@@ -20166,7 +20176,7 @@ octokit-plugin-create-pull-request@^3.10.0:
|
||||
dependencies:
|
||||
"@octokit/types" "^6.8.2"
|
||||
|
||||
octokit@^2.0.0:
|
||||
octokit@^2.0.0, octokit@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/octokit/-/octokit-2.0.4.tgz#cfd3adee6b775d3fa8cd8746590bed36127cc0a0"
|
||||
integrity sha512-9QvgYGzrSTGmr3koSGtbgeMgqYI20QI0Vv8Bk9y6phchk6L2aHFhcrUOIeNUPj1Z+KZnEBd6A/8faNpDFNfVjg==
|
||||
@@ -21611,7 +21621,7 @@ prettier@^1.16.4, prettier@^1.19.1:
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
|
||||
prettier@^2.2.1:
|
||||
prettier@^2.2.1, prettier@^2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
|
||||
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
|
||||
|
||||
Reference in New Issue
Block a user