Using EmptyState in Github Actions Plugin (#2680)

* display EmptyState when no CI/CD is avialable

* change padding and title size

* add EmptyState to gitHubActionPlugin

* fix test failing

* add MissingAnnotationEmptyState

* add MissingAnnotationEmptyState to storybook

* add highlightedNumbers feature to Codesnippet

* use the new component in github actions and update README

* remove entity from MissingAnnotationEmptyState

* remove proptypes from Codesnippet

Co-authored-by: Samira Mokaram <samiram@spotify.com>
This commit is contained in:
samiramkr
2020-10-04 16:23:53 +02:00
committed by GitHub
parent bbc7ec8a29
commit 1518741f67
10 changed files with 165 additions and 47 deletions
@@ -47,8 +47,8 @@ import {
useEntity,
} from '@backstage/plugin-catalog';
import { Entity } from '@backstage/catalog-model';
import { Grid } from '@material-ui/core';
import { WarningPanel } from '@backstage/core';
import { Button, Grid } from '@material-ui/core';
import { EmptyState } from '@backstage/core';
const CICDSwitcher = ({ entity }: { entity: Entity }) => {
// This component is just an example of how you can implement your company's logic in entity page.
@@ -66,10 +66,20 @@ const CICDSwitcher = ({ entity }: { entity: Entity }) => {
return <TravisCIRouter entity={entity} />;
default:
return (
<WarningPanel title="CI/CD switcher:">
No CI/CD is available for this entity. Check corresponding
annotations!
</WarningPanel>
<EmptyState
title="No CI/CD available for this entity"
missing="info"
description="You need to add an annotation to your component if you want to enable CI/CD for it. You can read more about annotations in Backstage by clicking the button below."
action={
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/features/software-catalog/well-known-annotations"
>
Read more
</Button>
}
/>
);
}
};
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React, { FC } from 'react';
import PropTypes from 'prop-types';
import React from 'react';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { docco, dark } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
import { useTheme } from '@material-ui/core';
@@ -27,19 +26,16 @@ type Props = {
language: string;
showLineNumbers?: boolean;
showCopyCodeButton?: boolean;
highlightedNumbers?: number[];
};
const defaultProps = {
showLineNumbers: false,
showCopyCodeButton: false,
};
export const CodeSnippet: FC<Props> = props => {
const { text, language, showLineNumbers, showCopyCodeButton } = {
...defaultProps,
...props,
};
export const CodeSnippet = ({
text,
language,
showLineNumbers = false,
showCopyCodeButton = false,
highlightedNumbers,
}: Props) => {
const theme = useTheme<BackstageTheme>();
const mode = theme.palette.type === 'dark' ? dark : docco;
@@ -49,6 +45,12 @@ export const CodeSnippet: FC<Props> = props => {
language={language}
style={mode}
showLineNumbers={showLineNumbers}
wrapLines
lineProps={(lineNumber: number) =>
highlightedNumbers?.includes(lineNumber)
? { style: { backgroundColor: '#e6ffed' } }
: {}
}
>
{text}
</SyntaxHighlighter>
@@ -60,11 +62,3 @@ export const CodeSnippet: FC<Props> = props => {
</div>
);
};
// Type check for the JS files using this core component
CodeSnippet.propTypes = {
text: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
showLineNumbers: PropTypes.bool,
showCopyCodeButton: PropTypes.bool,
};
@@ -17,6 +17,7 @@
import React from 'react';
import { EmptyState } from './EmptyState';
import { Button } from '@material-ui/core';
import { MissingAnnotationEmptyState } from './MissingAnnotationEmptyState';
export default {
title: 'EmptyState',
@@ -25,7 +26,7 @@ export default {
const containerStyle = { width: '100%', height: '100vh' };
export const MissingAnnotation = () => (
export const Field = () => (
<div style={containerStyle}>
<EmptyState
missing="field"
@@ -35,7 +36,7 @@ export const MissingAnnotation = () => (
</div>
);
export const NoInformation = () => (
export const Info = () => (
<div style={containerStyle}>
<EmptyState
missing="info"
@@ -45,7 +46,7 @@ export const NoInformation = () => (
</div>
);
export const CreateComponent = () => (
export const Content = () => (
<div style={containerStyle}>
<EmptyState
missing="content"
@@ -55,7 +56,7 @@ export const CreateComponent = () => (
</div>
);
export const NoBuild = () => (
export const Data = () => (
<div style={containerStyle}>
<EmptyState
missing="data"
@@ -79,3 +80,9 @@ export const WithAction = () => (
/>
</div>
);
export const MissingAnnotation = () => (
<div style={containerStyle}>
<MissingAnnotationEmptyState annotation="backstage.io/example" />
</div>
);
@@ -22,7 +22,7 @@ import Background from './assets/Background.svg';
const useStyles = makeStyles(theme => ({
root: {
backgroundColor: theme.palette.background.default,
padding: theme.spacing(10, 0, 0, 0),
padding: theme.spacing(2, 0, 0, 0),
},
action: {
marginTop: theme.spacing(2),
@@ -56,7 +56,7 @@ export const EmptyState = ({ title, description, missing, action }: Props) => {
>
<Grid item container direction="column" xs={12} md={6}>
<Grid item>
<Typography variant="h3">{title}</Typography>
<Typography variant="h5">{title}</Typography>
</Grid>
<Grid item>
<Typography variant="body1">{description}</Typography>
@@ -0,0 +1,69 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { Button, Typography } from '@material-ui/core';
import { EmptyState } from './EmptyState';
import { CodeSnippet } from '../CodeSnippet';
const COMPONENT_YAML = `# Example
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage
description: backstage.io
annotations:
ANNOTATION: value
spec:
type: website
lifecycle: production
owner: guest
`;
type Props = {
annotation: string;
};
export const MissingAnnotationEmptyState = ({ annotation }: Props) => {
return (
<EmptyState
missing="field"
title="Missing Annotation"
description={`The "${annotation}" annotation is missing. You need to add the annotation to your component if you want to enable this tool for it.`}
action={
<>
<Typography variant="body1">
Add the annotation to your component YAML as per the highlighted
example below:
</Typography>
<CodeSnippet
text={COMPONENT_YAML.replace('ANNOTATION', annotation)}
language="yaml"
showLineNumbers
highlightedNumbers={[7, 8]}
/>
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/features/software-catalog/well-known-annotations"
>
Read more
</Button>
</>
}
/>
);
};
@@ -15,3 +15,4 @@
*/
export { EmptyState } from './EmptyState';
export { MissingAnnotationEmptyState } from './MissingAnnotationEmptyState';
+1 -1
View File
@@ -11,7 +11,7 @@ TBD
### Generic Requirements
1. Provide OAuth credentials:
1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with callback URL set to `https://localhost:3000/auth/github`.
1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with callback URL set to `http://localhost:7000/api/auth/github`.
2. Take Client ID and Client Secret from the newly created app's settings page and put them into `AUTH_GITHUB_CLIENT_ID` and `AUTH_GITHUB_CLIENT_SECRET` env variables.
2. Annotate your component with a correct GitHub Actions repository and owner:
@@ -18,9 +18,9 @@ import { errorApiRef, useApi } from '@backstage/core-api';
import { GITHUB_ACTIONS_ANNOTATION } from '../useProjectName';
import { useWorkflowRuns } from '../useWorkflowRuns';
import React, { useEffect } from 'react';
import { Table } from '@backstage/core';
import { EmptyState, Table } from '@backstage/core';
import { WorkflowRunStatus } from '../WorkflowRunStatus';
import { Card, Link, TableContainer } from '@material-ui/core';
import { Button, Card, Link, TableContainer } from '@material-ui/core';
import { generatePath, Link as RouterLink } from 'react-router-dom';
const firstLine = (message: string): string => message.split('\n')[0];
@@ -54,7 +54,22 @@ export const RecentWorkflowRunsCard = ({
}
}, [error, errorApi]);
return (
return !runs.length ? (
<EmptyState
missing="data"
title="No Workflow Data"
description="This component has Github Actions enabled, but no data was found. Have you created any Workflows? Click the button below to create a new Workflow."
action={
<Button
variant="contained"
color="primary"
href={`https://github.com/${owner}/${repo}/actions/new`}
>
Create new Workflow
</Button>
}
/>
) : (
<TableContainer component={Card}>
<Table
title="Recent Workflow Runs"
@@ -20,18 +20,14 @@ import { rootRouteRef, buildRouteRef } from '../plugin';
import { WorkflowRunDetails } from './WorkflowRunDetails';
import { WorkflowRunsTable } from './WorkflowRunsTable';
import { GITHUB_ACTIONS_ANNOTATION } from './useProjectName';
import { WarningPanel } from '@backstage/core';
import { MissingAnnotationEmptyState } from '@backstage/core';
export const isPluginApplicableToEntity = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION]);
export const Router = ({ entity }: { entity: Entity }) =>
// TODO(shmidt-i): move warning to a separate standardized component
!isPluginApplicableToEntity(entity) ? (
<WarningPanel title="GitHubActions plugin:">
<pre>{GITHUB_ACTIONS_ANNOTATION}</pre> annotation is missing on the
entity.
</WarningPanel>
<MissingAnnotationEmptyState annotation={GITHUB_ACTIONS_ANNOTATION} />
) : (
<Routes>
<Route
@@ -14,11 +14,18 @@
* limitations under the License.
*/
import React, { FC } from 'react';
import { Link, Typography, Box, IconButton, Tooltip } from '@material-ui/core';
import {
Link,
Typography,
Box,
IconButton,
Tooltip,
Button,
} from '@material-ui/core';
import RetryIcon from '@material-ui/icons/Replay';
import GitHubIcon from '@material-ui/icons/GitHub';
import { Link as RouterLink, generatePath } from 'react-router-dom';
import { Table, TableColumn } from '@backstage/core';
import { EmptyState, Table, TableColumn } from '@backstage/core';
import { useWorkflowRuns } from '../useWorkflowRuns';
import { WorkflowRunStatus } from '../WorkflowRunStatus';
import SyncIcon from '@material-ui/icons/Sync';
@@ -156,15 +163,34 @@ export const WorkflowRunsTable = ({
}) => {
const { value: projectName, loading } = useProjectName(entity);
const [owner, repo] = (projectName ?? '/').split('/');
const [tableProps, { retry, setPage, setPageSize }] = useWorkflowRuns({
const [
{ runs, ...tableProps },
{ retry, setPage, setPageSize },
] = useWorkflowRuns({
owner,
repo,
branch,
});
return (
return !runs ? (
<EmptyState
missing="data"
title="No Workflow Data"
description="This component has Github Actions enabled, but no data was found. Have you created any Workflows? Click the button below to create a new Workflow."
action={
<Button
variant="contained"
color="primary"
href={`https://github.com/${projectName}/actions/new`}
>
Create new Workflow
</Button>
}
/>
) : (
<WorkflowRunsTableView
{...tableProps}
runs={runs}
loading={loading || tableProps.loading}
retry={retry}
onChangePageSize={setPageSize}