[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:
+1
-1
@@ -4,4 +4,4 @@
|
||||
# The last matching pattern takes precedence.
|
||||
# https://help.github.com/articles/about-codeowners/
|
||||
|
||||
* @spotify/backstage-core
|
||||
* @spotify/backstage-core
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
# Title
|
||||
Welcome to the explore plugin!
|
||||
|
||||
## Sub-section 1
|
||||
|
||||
## Sub-section 2
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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();
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user