[Plugin] Explore features in Backstage (#696)

* Explore plugin

* Fixed styles and props

* Real conent

* Add Lighthouse and fix tests

* Add menu item

* Cleanup

* Change wording

* Update package.json

* Remove dead code

* Update yarn.lock

* Revert change to CODEOWNERS

* Reset yarn.lock to master

* Fix comments
This commit is contained in:
Stefan Ålund
2020-05-03 14:30:51 +02:00
committed by GitHub
parent 8a16cf7067
commit 7472e9a5c2
15 changed files with 464 additions and 2 deletions
+1 -1
View File
@@ -4,4 +4,4 @@
# The last matching pattern takes precedence.
# https://help.github.com/articles/about-codeowners/
* @spotify/backstage-core
* @spotify/backstage-core
+1
View File
@@ -20,6 +20,7 @@
"dependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/plugin-explore": "^0.1.1-alpha.4",
"@backstage/plugin-home-page": "^0.1.1-alpha.4",
"@backstage/plugin-inventory": "^0.1.1-alpha.4",
"@backstage/plugin-lighthouse": "^0.1.1-alpha.4",
@@ -18,6 +18,7 @@ import React, { FC, useContext } from 'react';
import PropTypes from 'prop-types';
import { Link, makeStyles, Typography } from '@material-ui/core';
import HomeIcon from '@material-ui/icons/Home';
import ExploreIcon from '@material-ui/icons/Explore';
import AccountCircle from '@material-ui/icons/AccountCircle';
import CreateComponentIcon from '@material-ui/icons/Create';
import AccountTreeIcon from '@material-ui/icons/AccountTree';
@@ -81,7 +82,9 @@ const Root: FC<{}> = ({ children }) => (
<SidebarSpacer />
<SidebarDivider />
<SidebarItem icon={HomeIcon} to="/" text="Home" />
<SidebarItem icon={ExploreIcon} to="/explore" text="Explore" />
<SidebarItem icon={CreateComponentIcon} to="/scaffolder" text="Create" />
<SidebarDivider />
<SidebarItem icon={AccountTreeIcon} to="/inventory" text="Inventory" />
<SidebarItem icon={AccountCircle} to="/login" text="Login" />
<SidebarDivider />
+1 -1
View File
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { plugin as HomePagePlugin } from '@backstage/plugin-home-page';
export { plugin as WelcomePlugin } from '@backstage/plugin-welcome';
export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse';
export { plugin as InventoryPlugin } from '@backstage/plugin-inventory';
export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder';
export { plugin as TechRadar } from '@backstage/plugin-tech-radar';
export { plugin as Explore } from '@backstage/plugin-explore';
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+6
View File
@@ -0,0 +1,6 @@
# Title
Welcome to the explore plugin!
## Sub-section 1
## Sub-section 2
+39
View File
@@ -0,0 +1,39 @@
{
"name": "@backstage/plugin-explore",
"version": "0.1.1-alpha.4",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts",
"license": "Apache-2.0",
"private": true,
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"classnames": "^2.2.6",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"files": [
"dist"
]
}
@@ -0,0 +1,93 @@
/*
* 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 { render } from '@testing-library/react';
import { wrapInThemedTestApp } from '@backstage/test-utils';
import ExploreCard from './ExploreCard';
const minProps = {
card: {
title: 'Title',
description: 'Something something',
url: 'http://spotify.com/',
image: 'https://developer.spotify.com/assets/WebAPI_intro.png',
tags: ['tag1', 'tag2'],
},
};
describe('<ExploreCard />', () => {
it('renders without exploding', () => {
const { getByText } = render(
wrapInThemedTestApp(<ExploreCard {...minProps} />),
);
expect(getByText('Explore')).toBeInTheDocument();
});
it('renders props correctly', () => {
const { getByText } = render(
wrapInThemedTestApp(<ExploreCard {...minProps} />),
);
expect(getByText(minProps.card.title)).toBeInTheDocument();
expect(getByText(minProps.card.description)).toBeInTheDocument();
});
it('should link out', () => {
const rendered = render(wrapInThemedTestApp(<ExploreCard {...minProps} />));
const anchor = rendered.container.querySelector('a');
expect(anchor.href).toBe(minProps.card.url);
});
it('renders default description when missing', () => {
const propsWithoutDescription = {
card: {
card: {
title: 'Title',
url: 'http://spotify.com/',
image: 'https://developer.spotify.com/assets/WebAPI_intro.png',
},
},
};
const { getByText } = render(
wrapInThemedTestApp(<ExploreCard {...propsWithoutDescription} />),
);
expect(getByText('Description missing')).toBeInTheDocument();
});
it('renders lifecycle correctly', () => {
const propsWithLifecycle = {
card: {
title: 'Title',
url: 'http://spotify.com/',
image: 'https://developer.spotify.com/assets/WebAPI_intro.png',
lifecycle: 'GA',
},
};
const { queryByText } = render(
wrapInThemedTestApp(<ExploreCard {...propsWithLifecycle} />),
);
expect(queryByText('GA')).not.toBeInTheDocument();
});
it('renders tags correctly', () => {
const { getByText } = render(
wrapInThemedTestApp(<ExploreCard {...minProps} />),
);
expect(getByText(minProps.card.tags[0])).toBeInTheDocument();
expect(getByText(minProps.card.tags[1])).toBeInTheDocument();
});
});
@@ -0,0 +1,134 @@
/*
* 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, { FC } from 'react';
import classNames from 'classnames';
import {
Button,
Card,
CardActions,
CardContent,
CardMedia,
Chip,
Typography,
makeStyles,
} from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
const useStyles = makeStyles<BackstageTheme>(theme => ({
card: {
display: 'flex',
flexDirection: 'column',
},
cardActions: {
flexGrow: 1,
alignItems: 'flex-end',
},
media: {
height: 128,
},
mediaContain: {
backgroundSize: 'contain',
},
lifecycle: {
lineHeight: '0.8em',
color: 'white',
},
ga: {
backgroundColor: theme.palette.status.ok,
},
alpha: {
backgroundColor: theme.palette.status.error,
},
beta: {
backgroundColor: theme.palette.status.warning,
},
domains: {
position: 'relative',
top: theme.spacing(2),
},
spaceBetween: {
justifyContent: 'space-between',
},
}));
export type CardData = {
title: string;
description: string;
url: string;
image: string;
tags?: string[];
lifecycle?: string;
newsTag?: string;
};
type Props = {
card: CardData;
objectFit?: 'cover' | 'contain';
};
const ExploreCard: FC<Props> = ({ card, objectFit }) => {
const classes = useStyles();
const { title, description, url, image, lifecycle, newsTag, tags } = card;
return (
<Card key={title} className={classes.card}>
<CardMedia
image={image}
title={title}
className={classNames(classes.media, {
[classes.mediaContain]: objectFit === 'contain',
})}
/>
<CardContent>
<Typography gutterBottom variant="h5">
{title}{' '}
{lifecycle && lifecycle.toLowerCase() !== 'ga' && (
<Chip
label={lifecycle}
className={classNames(
classes.lifecycle,
classes[lifecycle.toLowerCase()],
)}
/>
)}
</Typography>
<Typography paragraph>
{description || 'Description missing'}
</Typography>
{tags && (
<div className={classes.domains}>
{tags.map((item, idx) => (
<Chip key={idx} label={item} />
))}
</div>
)}
</CardContent>
<CardActions
className={classNames(classes.cardActions, {
[classes.spaceBetween]: newsTag,
})}
>
<Button size="small" color="primary" href={url} disabled={!url}>
Explore
</Button>
</CardActions>
</Card>
);
};
export default ExploreCard;
@@ -0,0 +1,93 @@
/*
* 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, { FC } from 'react';
import { makeStyles, Typography } from '@material-ui/core';
import {
Content,
ContentHeader,
Header,
Page,
pageTheme,
SupportButton,
} from '@backstage/core';
import ExploreCard, { CardData } from './ExploreCard';
import { BackstageTheme } from '@backstage/theme';
const useStyles = makeStyles<BackstageTheme>(theme => ({
container: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, 296px)',
gridGap: theme.spacing(3),
marginBottom: theme.spacing(6),
},
}));
const toolsCards = [
{
title: 'Lighthouse',
description:
"Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website.",
url: '/lighthouse',
image:
'https://raw.githubusercontent.com/GoogleChrome/lighthouse/8b3d7f052b2e64dd857e741d7395647f487697e7/assets/lighthouse-logo.png',
tags: ['web', 'seo', 'accessibility', 'performance'],
},
{
title: 'Tech Radar',
description:
'Tech Radar is a list of technologies, complemented by an assessment result, called ring assignment.',
url: '/tech-radar',
image:
'https://storage.googleapis.com/wf-blogs-engineering-media/2018/09/fe13bb32-wf-tech-radar-hero-1024x597.png',
tags: ['standards', 'landscape'],
},
{
title: 'GraphiQL',
description:
'Integrates GraphiQL as a tool to browse GraphiQL endpoints inside Backstage.',
url: 'graphiql',
image:
'https://camo.githubusercontent.com/517398c3fbe0687d3d4dcbe05da82970b882e75a/68747470733a2f2f64337676366c703535716a6171632e636c6f756466726f6e742e6e65742f6974656d732f33413061324e314c3346324f304c3377326e316a2f477261706869514c382e706e673f582d436c6f75644170702d56697369746f722d49643d3433363432',
tags: ['graphql', 'dev'],
},
];
const ExplorePluginPage: FC<{}> = () => {
const classes = useStyles();
return (
<Page theme={pageTheme.home}>
<Header
title="Explore"
subtitle="Tools and services available in Backstage"
/>
<Content>
<ContentHeader title="Tools">
<SupportButton>
<Typography>Explore tools available in Backstage</Typography>
</SupportButton>
</ContentHeader>
<div className={classes.container}>
{toolsCards.map((card: CardData, ix: any) => (
<ExploreCard card={card} key={ix} />
))}
</div>
</Content>
</Page>
);
};
export default ExplorePluginPage;
+17
View File
@@ -0,0 +1,17 @@
/*
* 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.
*/
export { plugin } from './plugin';
+23
View File
@@ -0,0 +1,23 @@
/*
* 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 { plugin } from './plugin';
describe('explore', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
});
});
+25
View File
@@ -0,0 +1,25 @@
/*
* 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 { createPlugin } from '@backstage/core';
import ExplorePluginPage from './components/ExplorePluginPage';
export const plugin = createPlugin({
id: 'explore',
register({ router }) {
router.registerRoute('/explore', ExplorePluginPage);
},
});
+18
View File
@@ -0,0 +1,18 @@
/*
* 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 '@testing-library/jest-dom/extend-expect';
require('jest-fetch-mock').enableMocks();
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
}