diff --git a/docs/design.md b/docs/design.md index 7fdec77adf..bcbf0bbdc5 100644 --- a/docs/design.md +++ b/docs/design.md @@ -9,11 +9,11 @@ Backstage Open Source is a newly launched endeavor, and we’re excited to scale ### Collaborative -The Backstage Design Team is small but mighty, and we truly cherish the amazing opportunity we have to work with the Backstage Open Source community! Have an idea? A component request? Feel free to communicate with us via [Discord](https://discord.gg/PefUsZ) (*#design* channel). Collaboration trumps individual speed, and we want to work with you to make Backstage work for all of our users. +The Backstage Design Team is small but mighty, and we truly cherish the amazing opportunity we have to work with the Backstage Open Source community! Have an idea? A component request? Feel free to communicate with us via [Discord](https://discord.gg/EBHEGzX) (*#design* channel). Collaboration trumps individual speed, and we want to work with you to make Backstage work for all of our users. ### Transparent -There are a lot of exciting things coming up and we want to keep you in the loop! Keep an eye on our Milestones in GitHub to see where we’re headed. We’ll also be posting updates in the *#design* channel on [Discord](https://discord.gg/PefUsZ). Not only that, we want to keep you informed on the decisions we’ve made and why we’ve made them. +There are a lot of exciting things coming up and we want to keep you in the loop! Keep an eye on our Milestones in GitHub to see where we’re headed. We’ll also be posting updates in the *#design* channel on [Discord](https://discord.gg/EBHEGzX). Not only that, we want to keep you informed on the decisions we’ve made and why we’ve made them. ## 🛠 Our Practice @@ -48,15 +48,15 @@ This is the universal user experience that is shared amongst all Backstage users ## ⭐️ How to Contribute ### Pick up an issue! -In the beginning, most of our issues will be centered around creating universal components for our Backstage Design System and adding them to our Storybook so plugin developers can reference them. We’ll also be creating issues that are focused on building up our core Backstage user experience. We’ll be labeling our issues in GitHub with ‘design’ and/or ‘storybook’ - so feel free to browse and tackle the tasks that interest you. If you have any questions regarding an issue, you can ask them in the comments section of the issue or on [Discord](https://discord.gg/PefUsZ). We absolutely adore our external contributors and will send you virtual semlas for your contributions! +In the beginning, most of our issues will be centered around creating universal components for our Backstage Design System and adding them to our Storybook so plugin developers can reference them. We’ll also be creating issues that are focused on building up our core Backstage user experience. We’ll be labeling our issues in GitHub with ‘design’ and/or ‘storybook’ - so feel free to browse and tackle the tasks that interest you. If you have any questions regarding an issue, you can ask them in the comments section of the issue or on [Discord](https://discord.gg/EBHEGzX). We absolutely adore our external contributors and will send you virtual semlas for your contributions! ### Request a component. -Create an issue (label it design and assign it to katz95) or send us a message on [Discord](https://discord.gg/PefUsZ) (*#design* channel) with details of what the component is and its relevant use cases. Your request will be reviewed by our design team and you should hear back from us within 1-2 business days. We’ll get back to you and let you know whether your requested component will get picked up by our team as something to be added to our design system. +Create an issue (label it design and assign it to katz95) or send us a message on [Discord](https://discord.gg/EBHEGzX) (*#design* channel) with details of what the component is and its relevant use cases. Your request will be reviewed by our design team and you should hear back from us within 1-2 business days. We’ll get back to you and let you know whether your requested component will get picked up by our team as something to be added to our design system. ## ✏️ Resources **[Storybook](http://storybook.backstage.io/)** - where you can view our components. If you’d like to help build up our design system, you can also add components we’ve designed to the Storybook as well. -**[Discord](https://discord.gg/PefUsZ)** - all design questions should be directed to the *#design* channel. +**[Discord](https://discord.gg/EBHEGzX)** - all design questions should be directed to the *#design* channel. **Documentation** - Patterns (stay tuned) @@ -64,7 +64,7 @@ Create an issue (label it design and assign it to katz95) or send us a message o ## 🔮 Future ### Contributions from designers -Are you a designer at an organisation that’s implementing Backstage? A designer who’s fascinated by the developer productivity problem space? A designer who’s curious about open source design? We’d love for you to contribute. Behind the scenes, we’re setting up a few foundational elements to make sure that contributing to Backstage as a designer is easy. From styling guidelines to UX principles to Figma documents, we’ll make sure you’re equipped to chip in on this project. We’re excited to work with you! In the meantime, we’d love to hear from you on [Discord](https://discord.gg/PefUsZ). +Are you a designer at an organisation that’s implementing Backstage? A designer who’s fascinated by the developer productivity problem space? A designer who’s curious about open source design? We’d love for you to contribute. Behind the scenes, we’re setting up a few foundational elements to make sure that contributing to Backstage as a designer is easy. From styling guidelines to UX principles to Figma documents, we’ll make sure you’re equipped to chip in on this project. We’re excited to work with you! In the meantime, we’d love to hear from you on [Discord](https://discord.gg/EBHEGzX). [Back to Docs](../README.md) diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index a8c4cd3382..6854ce015b 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -20,7 +20,7 @@ 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 CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import AccountTreeIcon from '@material-ui/icons/AccountTree'; import { Sidebar, @@ -83,8 +83,8 @@ const Root: FC<{}> = ({ children }) => ( - - + + diff --git a/packages/backend-common/src/errors.ts b/packages/backend-common/src/errors.ts new file mode 100644 index 0000000000..b68dc8e3f0 --- /dev/null +++ b/packages/backend-common/src/errors.ts @@ -0,0 +1,77 @@ +/* + * 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. + */ + +/* + * A set of common business logic errors. + * + * The error handler middleware understands these and will translate them to + * well formed HTTP responses. + * + * While these are intentionally analogous to HTTP errors, they are not + * intended to be thrown by the request handling layer. In those places, please + * use e.g. the http-errors library. + */ + +class CustomErrorBase extends Error { + readonly cause?: Error; + + constructor(message?: string, cause?: Error) { + let fullMessage = message; + if (cause) { + if (fullMessage) { + fullMessage += `; caused by ${cause}`; + } else { + fullMessage = `caused by ${cause}`; + } + } + + super(fullMessage); + + Error.captureStackTrace(this, this.constructor); + + this.name = this.constructor.name; + this.cause = cause; + } +} + +/** + * The given inputs are malformed and cannot be processed. + */ +export class InputError extends CustomErrorBase {} + +/** + * The request requires authentication, which was not properly supplied. + */ +export class AuthenticationError extends CustomErrorBase {} + +/** + * The authenticated caller is not allowed to perform this request. + */ +export class NotAllowedError extends CustomErrorBase {} + +/** + * The requested resource could not be found. + * + * Note that this error usually is used to indicate that an entity with a given + * ID does not exist, rather than signalling that an entire route is missing. + */ +export class NotFoundError extends CustomErrorBase {} + +/** + * The request could not complete due to a conflict in the current state of the + * resource. + */ +export class ConflictError extends CustomErrorBase {} diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 54b9f5c40f..b2c38ab506 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -14,5 +14,6 @@ * limitations under the License. */ +export * from './errors'; export * from './logging'; export * from './middleware'; diff --git a/packages/backend-common/src/middleware/errorHandler.test.ts b/packages/backend-common/src/middleware/errorHandler.test.ts index f90794b07b..022802dff8 100644 --- a/packages/backend-common/src/middleware/errorHandler.test.ts +++ b/packages/backend-common/src/middleware/errorHandler.test.ts @@ -17,6 +17,7 @@ import express from 'express'; import createError from 'http-errors'; import request from 'supertest'; +import * as errors from '../errors'; import { errorHandler } from './errorHandler'; describe('errorHandler', () => { @@ -33,7 +34,7 @@ describe('errorHandler', () => { expect(response.text).toBe('some message'); }); - it('takes code from StatusCodeError', async () => { + it('takes code from http-errors library errors', async () => { const app = express(); app.use('/breaks', () => { throw createError(432, 'Some Message'); @@ -45,4 +46,31 @@ describe('errorHandler', () => { expect(response.status).toBe(432); expect(response.text).toContain('Some Message'); }); + + it('handles well-known error classes', async () => { + const app = express(); + app.use('/InputError', () => { + throw new errors.InputError(); + }); + app.use('/AuthenticationError', () => { + throw new errors.AuthenticationError(); + }); + app.use('/NotAllowedError', () => { + throw new errors.NotAllowedError(); + }); + app.use('/NotFoundError', () => { + throw new errors.NotFoundError(); + }); + app.use('/ConflictError', () => { + throw new errors.ConflictError(); + }); + app.use(errorHandler()); + + const r = request(app); + expect((await r.get('/InputError')).status).toBe(400); + expect((await r.get('/AuthenticationError')).status).toBe(401); + expect((await r.get('/NotAllowedError')).status).toBe(403); + expect((await r.get('/NotFoundError')).status).toBe(404); + expect((await r.get('/ConflictError')).status).toBe(409); + }); }); diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index 655d93ad91..14f6cfa8d7 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -15,12 +15,21 @@ */ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; +import * as errors from '../errors'; + +export type ErrorHandlerOptions = { + /** + * Whether error response bodies should show error stack traces or not. + * + * If not specified, by default shows stack traces only in development mode. + */ + showStackTraces?: boolean; +}; /** * Express middleware to handle errors during request processing. * - * This is commonly the second to last middleware in the chain (before the - * notFoundHandler). + * This is commonly the very last middleware in the chain. * * Its primary purpose is not to do translation of business logic exceptions, * but rather to be a gobal catch-all for uncaught "fatal" errors that are @@ -30,23 +39,32 @@ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; * * @returns An Express error request handler */ -export function errorHandler(): ErrorRequestHandler { +export function errorHandler( + options: ErrorHandlerOptions = {}, +): ErrorRequestHandler { + const showStackTraces = + options.showStackTraces ?? process.env.NODE_ENV === 'development'; + /* eslint-disable @typescript-eslint/no-unused-vars */ return ( error: Error, _request: Request, response: Response, - _next: NextFunction, + next: NextFunction, ) => { + if (response.headersSent) { + next(error); + } + const status = getStatusCode(error); - const message = error.message; + const message = showStackTraces ? error.stack : error.message; response.status(status).send(message); }; } function getStatusCode(error: Error): number { + // Look for common http library status codes const knownStatusCodeFields = ['statusCode', 'status']; - for (const field of knownStatusCodeFields) { const statusCode = (error as any)[field]; if ( @@ -59,5 +77,22 @@ function getStatusCode(error: Error): number { } } + // Handle well-known error types + switch (error.name) { + case errors.InputError.name: + return 400; + case errors.AuthenticationError.name: + return 401; + case errors.NotAllowedError.name: + return 403; + case errors.NotFoundError.name: + return 404; + case errors.ConflictError.name: + return 409; + default: + break; + } + + // Fall back to internal server error return 500; } diff --git a/packages/backend-common/tsconfig.json b/packages/backend-common/tsconfig.json index b463ac102f..6d7ca21afa 100644 --- a/packages/backend-common/tsconfig.json +++ b/packages/backend-common/tsconfig.json @@ -7,7 +7,7 @@ "sourceMap": true, "declaration": true, "strict": true, - "target": "es5", + "target": "ES2019", "module": "commonjs", "esModuleInterop": true, "types": ["node", "jest"] diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index e9fc15f1c1..f8456a0e34 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -28,21 +28,31 @@ import { notFoundHandler, requestLoggingHandler, } from '@backstage/backend-common'; -import { createRouter as inventoryRouter } from '@backstage/plugin-inventory-backend'; +import { + AggregatorInventory, + createRouter as inventoryRouter, + StaticInventory, +} from '@backstage/plugin-inventory-backend'; import { createScaffolder } from '@backstage/plugin-scaffolder-backend'; import compression from 'compression'; import cors from 'cors'; import express from 'express'; import helmet from 'helmet'; -import { testRouter } from './test'; const DEFAULT_PORT = 7000; const PORT = parseInt(process.env.PORT ?? '', 10) || DEFAULT_PORT; const logger = getRootLogger().child({ type: 'plugin' }); async function main() { - const inventory = await inventoryRouter({ logger }); - const scaffolder = createScaffolder(); + const inventory = new AggregatorInventory(); + inventory.enlist( + new StaticInventory([ + { id: 'component1' }, + { id: 'component2' }, + { id: 'component3' }, + { id: 'component4' }, + ]), + ); const app = express(); @@ -51,9 +61,8 @@ async function main() { app.use(compression()); app.use(express.json()); app.use(requestLoggingHandler()); - app.use('/test', testRouter); - app.use('/inventory', inventory); - app.use('/scaffolder', scaffolder); + app.use('/inventory', await inventoryRouter({ inventory, logger })); + app.use('/scaffolder', createScaffolder()); app.use(notFoundHandler()); app.use(errorHandler()); diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index 32f83edc9d..a05e809caa 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -10,6 +10,7 @@ "module": "ESNext", "resolveJsonModule": true, "esModuleInterop": true, + "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2019"], "types": ["node", "jest"] } } diff --git a/packages/core/package.json b/packages/core/package.json index 1d4372f15c..107cba0196 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,6 +45,7 @@ "classnames": "^2.2.6", "clsx": "^1.1.0", "lodash": "^4.17.15", + "material-table": "^1.57.2", "prop-types": "^15.7.2", "rc-progress": "^2.5.2", "react": "^16.12.0", diff --git a/packages/core/src/api/app/AppBuilder.tsx b/packages/core/src/api/app/AppBuilder.tsx index 0af21f5c8d..94d6df1b3d 100644 --- a/packages/core/src/api/app/AppBuilder.tsx +++ b/packages/core/src/api/app/AppBuilder.tsx @@ -118,7 +118,7 @@ export default class AppBuilder { {routes} ( + render={(props) => ( )} /> diff --git a/packages/core/src/components/Status/Status.stories.tsx b/packages/core/src/components/Status/Status.stories.tsx index 91ef53d7fe..0cdc38a10d 100644 --- a/packages/core/src/components/Status/Status.stories.tsx +++ b/packages/core/src/components/Status/Status.stories.tsx @@ -24,44 +24,72 @@ import { StatusRunning, StatusWarning, } from './Status'; +import SortableTable from 'components/SortableTable'; +import InfoCard from 'layout/InfoCard'; export default { title: 'Status', component: StatusOK, }; -export const statusOK = () => ( - <> - Status OK - -); -export const statusWarning = () => ( - <> - Status Warning - -); -export const statusError = () => ( - <> - Status Error - -); -export const statusFailed = () => ( - <> - Status Failed - -); -export const statusPending = () => ( - <> - Status Pending - -); -export const statusRunning = () => ( - <> - Status Running - -); -export const statusNA = () => ( - <> - Status NA - +const data = [ + { + status: , + label: 'OK', + usage: 'Deployment successful', + }, + { + status: , + label: 'Warning', + usage: 'CPU utilization at 90%', + }, + { + status: , + label: 'Error', + usage: 'Service could not be created', + }, + { + status: , + label: 'Failed', + usage: 'Build for PR #34 failed', + }, + { + status: , + label: 'Pending', + usage: 'Job is waiting', + }, + { + status: , + label: 'Running', + usage: 'Job is running', + }, + { + status: , + label: 'N/A', + usage: 'Not sure what to do', + }, +]; + +const columns = [ + { id: 'status', label: 'Status' }, + { id: 'label', label: 'Label' }, + { id: 'usage', label: 'Example usage' }, +]; + +const containerStyle = { width: 600 }; + +export const Default = () => ( +
+ + + +
); + +export const statusOK = () => ; +export const statusWarning = () => ; +export const statusError = () => ; +export const statusFailed = () => ; +export const statusPending = () => ; +export const statusRunning = () => ; +export const statusNA = () => ; diff --git a/packages/core/src/components/Status/Status.tsx b/packages/core/src/components/Status/Status.tsx index 0d02d86fb1..a232aceb62 100644 --- a/packages/core/src/components/Status/Status.tsx +++ b/packages/core/src/components/Status/Status.tsx @@ -19,7 +19,7 @@ import { BackstageTheme } from '@backstage/theme'; import classNames from 'classnames'; import React, { FC } from 'react'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ status: { width: 12, height: 12, @@ -34,6 +34,7 @@ const useStyles = makeStyles(theme => ({ backgroundColor: theme.palette.status.warning, }, error: { + // Use same for Failed status. width: '0', height: '0', borderLeft: '7px solid transparent', @@ -43,11 +44,8 @@ const useStyles = makeStyles(theme => ({ pending: { backgroundColor: theme.palette.status.pending, }, - failed: { - backgroundColor: 'rgba(245, 155, 35, 0.5)', - }, running: { - animation: 'blink 0.8s step-start 0s infinite', + animation: '$blink 0.8s step-start 0s infinite', backgroundColor: theme.palette.status.running, }, '@keyframes blink': { @@ -57,7 +55,7 @@ const useStyles = makeStyles(theme => ({ }, })); -export const StatusOK: FC<{}> = props => { +export const StatusOK: FC<{}> = (props) => { const classes = useStyles(props); return ( = props => { ); }; -export const StatusWarning: FC<{}> = props => { +export const StatusWarning: FC<{}> = (props) => { const classes = useStyles(props); return ( = props => { ); }; -export const StatusError: FC<{}> = props => { +export const StatusError: FC<{}> = (props) => { const classes = useStyles(props); return ( = props => { ); }; -export const StatusNA: FC<{}> = props => ( +export const StatusNA: FC<{}> = (props) => ( N/A ); -export const StatusPending: FC<{}> = props => { +export const StatusPending: FC<{}> = (props) => { const classes = useStyles(props); return ( = props => { ); }; -export const StatusRunning: FC<{}> = props => { +export const StatusRunning: FC<{}> = (props) => { const classes = useStyles(props); return ( = props => { ); }; -export const StatusFailed: FC<{}> = props => { +export const StatusFailed: FC<{}> = (props) => { const classes = useStyles(props); return ( diff --git a/packages/core/src/components/Table/SubvalueCell.tsx b/packages/core/src/components/Table/SubvalueCell.tsx new file mode 100644 index 0000000000..d8465b2a23 --- /dev/null +++ b/packages/core/src/components/Table/SubvalueCell.tsx @@ -0,0 +1,47 @@ +/* + * 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 { BackstageTheme } from '@backstage/theme'; +import { makeStyles } from '@material-ui/core'; + +const useSubvalueCellStyles = makeStyles(theme => ({ + value: { + marginBottom: '6px', + }, + subvalue: { + color: theme.palette.textSubtle, + fontWeight: 'normal', + }, +})); + +type SubvalueCellProps = { + value: React.ReactNode; + subvalue: React.ReactNode; +}; + +const SubvalueCell: FC = ({ value, subvalue }) => { + const classes = useSubvalueCellStyles(); + + return ( + <> +
{value}
+
{subvalue}
+ + ); +}; + +export default SubvalueCell; diff --git a/packages/core/src/components/Table/Table.stories.tsx b/packages/core/src/components/Table/Table.stories.tsx new file mode 100644 index 0000000000..b1a886ddc8 --- /dev/null +++ b/packages/core/src/components/Table/Table.stories.tsx @@ -0,0 +1,143 @@ +/* + * 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 Table, { SubvalueCell, TableColumn } from './'; + +export default { + title: 'Table', + component: Table, +}; + +const generateTestData: (number: number) => Array<{}> = (rows = 20) => { + const data: Array<{}> = []; + while (data.length <= rows) { + data.push({ + col1: `Some value ${data.length}`, + col2: `More data ${data.length}`, + subvalue: `Subvalue ${data.length}`, + number: Math.floor(Math.random() * 1000), + date: new Date(Math.random() * 10000000000000), + }); + } + + return data; +}; + +const testData100 = generateTestData(100); + +export const DefaultTable = () => { + const columns: TableColumn[] = [ + { + title: 'Column 1', + field: 'col1', + highlight: true, + }, + { + title: 'Column 2', + field: 'col2', + }, + { + title: 'Numeric value', + field: 'number', + type: 'numeric', + }, + { + title: 'A Date', + field: 'date', + type: 'date', + }, + ]; + + return ( + + ); +}; + +export const HiddenSearchTable = () => { + const columns: TableColumn[] = [ + { + title: 'Column 1', + field: 'col1', + highlight: true, + }, + { + title: 'Column 2', + field: 'col2', + }, + { + title: 'Numeric value', + field: 'number', + type: 'numeric', + }, + { + title: 'A Date', + field: 'date', + type: 'date', + }, + ]; + + return ( +
+ ); +}; + +export const SubvalueTable = () => { + const columns: TableColumn[] = [ + { + title: 'Column 1', + customFilterAndSearch: ( + query, + row: any, // Only needed if you want subvalue searchable + ) => + `${row.col1} ${row.subvalue}` + .toUpperCase() + .includes(query.toUpperCase()), + field: 'col1', + highlight: true, + render: (row: any): React.ReactNode => ( + + ), + }, + { + title: 'Column 2', + field: 'col2', + }, + { + title: 'Numeric value', + field: 'number', + type: 'numeric', + }, + { + title: 'A Date', + field: 'date', + type: 'date', + }, + ]; + + return ( +
+ ); +}; diff --git a/packages/core/src/components/Table/Table.test.tsx b/packages/core/src/components/Table/Table.test.tsx new file mode 100644 index 0000000000..f6801359eb --- /dev/null +++ b/packages/core/src/components/Table/Table.test.tsx @@ -0,0 +1,50 @@ +/* + * 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 { wrapInTestApp } from '@backstage/test-utils'; +import Table from './'; + +const minProps = { + columns: [ + { + title: 'Column 1', + field: 'col1', + }, + { + title: 'Column 2', + field: 'col2', + }, + ], + data: [ + { + col1: 'first value, first row', + col2: 'second value, first row', + }, + { + col1: 'first value, second row', + col2: 'second value, second row', + }, + ], +}; + +describe('
', () => { + it('renders without exploding', () => { + const rendered = render(wrapInTestApp(
)); + expect(rendered.getByText('second value, second row')).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/components/Table/Table.tsx b/packages/core/src/components/Table/Table.tsx new file mode 100644 index 0000000000..a9a55ae763 --- /dev/null +++ b/packages/core/src/components/Table/Table.tsx @@ -0,0 +1,192 @@ +/* + * 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, forwardRef } from 'react'; +import MTable, { + MTableCell, + MTableHeader, + MTableToolbar, + MaterialTableProps, + Options, + Column, +} from 'material-table'; +import { BackstageTheme } from '@backstage/theme'; +import { makeStyles } from '@material-ui/core'; + +// Material-table is not using the standard icons available in in material-ui. https://github.com/mbrn/material-table/issues/51 +import { + AddBox, + ArrowUpward, + Check, + ChevronLeft, + ChevronRight, + Clear, + DeleteOutline, + Edit, + FilterList, + FirstPage, + LastPage, + Remove, + SaveAlt, + Search, + ViewColumn, +} from '@material-ui/icons'; + +const tableIcons = { + Add: forwardRef((props, ref: React.Ref) => ( + + )), + Check: forwardRef((props, ref: React.Ref) => ( + + )), + Clear: forwardRef((props, ref: React.Ref) => ( + + )), + Delete: forwardRef((props, ref: React.Ref) => ( + + )), + DetailPanel: forwardRef((props, ref: React.Ref) => ( + + )), + Edit: forwardRef((props, ref: React.Ref) => ( + + )), + Export: forwardRef((props, ref: React.Ref) => ( + + )), + Filter: forwardRef((props, ref: React.Ref) => ( + + )), + FirstPage: forwardRef((props, ref: React.Ref) => ( + + )), + LastPage: forwardRef((props, ref: React.Ref) => ( + + )), + NextPage: forwardRef((props, ref: React.Ref) => ( + + )), + PreviousPage: forwardRef((props, ref: React.Ref) => ( + + )), + ResetSearch: forwardRef((props, ref: React.Ref) => ( + + )), + Search: forwardRef((props, ref: React.Ref) => ( + + )), + SortArrow: forwardRef((props, ref: React.Ref) => ( + + )), + ThirdStateCheck: forwardRef((props, ref: React.Ref) => ( + + )), + ViewColumn: forwardRef((props, ref: React.Ref) => ( + + )), +}; + +const useCellStyles = makeStyles(theme => ({ + root: { + color: theme.palette.grey[500], + padding: theme.spacing(0, 2, 0, 2.5), + height: '56px', + }, +})); + +const useHeaderStyles = makeStyles(theme => ({ + header: { + padding: theme.spacing(1, 2, 1, 2.5), + borderTop: `1px solid ${theme.palette.grey.A100}`, + borderBottom: `1px solid ${theme.palette.grey.A100}`, + color: theme.palette.textSubtle, + fontWeight: 'bold', + position: 'static', + }, +})); + +const useToolbarStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(3, 0, 2.5, 2.5), + }, + title: { + '& > h6': { + fontWeight: 'bold', + }, + }, +})); + +const convertColumns = (columns: TableColumn[]): TableColumn[] => { + return columns.map(column => { + const headerStyle: React.CSSProperties = {}; + const cellStyle: React.CSSProperties = {}; + + if (column.highlight) { + headerStyle.color = '#000000'; + cellStyle.fontWeight = 'bold'; + } + + return { + ...column, + headerStyle, + cellStyle, + }; + }); +}; + +export interface TableColumn extends Column<{}> { + highlight?: boolean; +} + +export interface TableProps extends MaterialTableProps<{}> { + columns: TableColumn[]; +} + +const Table: FC = ({ columns, options, ...props }) => { + const cellClasses = useCellStyles(); + const headerClasses = useHeaderStyles(); + const toolbarClasses = useToolbarStyles(); + + const MTColumns = convertColumns(columns); + + const defaultOptions: Options = { + headerStyle: { + textTransform: 'uppercase', + }, + }; + + return ( + ( + + ), + Header: headerProps => ( + + ), + Toolbar: toolbarProps => ( + + ), + }} + options={{ ...defaultOptions, ...options }} + columns={MTColumns} + icons={tableIcons} + {...props} + /> + ); +}; + +export default Table; diff --git a/packages/backend/src/test/index.ts b/packages/core/src/components/Table/index.ts similarity index 79% rename from packages/backend/src/test/index.ts rename to packages/core/src/components/Table/index.ts index ce3f9f8c39..e85aac3a01 100644 --- a/packages/backend/src/test/index.ts +++ b/packages/core/src/components/Table/index.ts @@ -14,10 +14,5 @@ * limitations under the License. */ -import { Router } from 'express'; - -export const testRouter = Router(); - -testRouter.get('/', async (_, res) => { - res.status(200).send('hello'); -}); +export { default, TableColumn } from './Table'; +export { default as SubvalueCell } from './SubvalueCell'; diff --git a/packages/core/src/components/TrendLine/TrendLine.stories.tsx b/packages/core/src/components/TrendLine/TrendLine.stories.tsx index fab5388c81..5263385e1c 100644 --- a/packages/core/src/components/TrendLine/TrendLine.stories.tsx +++ b/packages/core/src/components/TrendLine/TrendLine.stories.tsx @@ -24,7 +24,7 @@ export default { component: TrendLine, }; -const containerStyle = { width: 600 }; +const containerStyle = { width: 700 }; const data = [ { @@ -66,9 +66,11 @@ const columns = [ ]; export const Default = () => ( - - - +
+ + + +
); export const TrendingMix = () => ( diff --git a/packages/core/src/layout/ErrorPage/ErrorPage.tsx b/packages/core/src/layout/ErrorPage/ErrorPage.tsx index ea593dbb49..49b79ef925 100644 --- a/packages/core/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core/src/layout/ErrorPage/ErrorPage.tsx @@ -28,7 +28,7 @@ interface IErrorPageProps { }; } -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ container: { padding: theme.spacing(8), }, diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx index fe2bbfa1ce..7077167828 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.tsx @@ -27,18 +27,18 @@ import { import ErrorBoundary from 'layout/ErrorBoundary/ErrorBoundary'; import BottomLink, { Props as BottomLinkProps } from '../BottomLink'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ header: { padding: theme.spacing(2, 2, 2, 2.5), }, })); -const BoldHeader = withStyles(theme => ({ +const BoldHeader = withStyles((theme) => ({ title: { fontWeight: 700 }, subheader: { paddingTop: theme.spacing(1) }, }))(CardHeader); -const CardActionsTopRight = withStyles(theme => ({ +const CardActionsTopRight = withStyles((theme) => ({ root: { display: 'inline-block', padding: theme.spacing(8, 8, 0, 0), @@ -154,7 +154,7 @@ const InfoCard: FC = ({ if (variant) { const variants = variant.split(/[\s]+/g); - variants.forEach(name => { + variants.forEach((name) => { calculatedStyle = { ...calculatedStyle, ...VARIANT_STYLES.card[name as keyof typeof VARIANT_STYLES['card']], diff --git a/packages/storybook/.storybook/config.js b/packages/storybook/.storybook/config.js index fab204fd9f..941b02718b 100644 --- a/packages/storybook/.storybook/config.js +++ b/packages/storybook/.storybook/config.js @@ -1,14 +1,21 @@ import React from 'react'; -import { addDecorator } from '@storybook/react'; +import { addDecorator, addParameters } from '@storybook/react'; import { lightTheme, darkTheme } from '@backstage/theme'; import { CssBaseline, ThemeProvider } from '@material-ui/core'; import { useDarkMode } from 'storybook-dark-mode'; import { Content } from '@backstage/core'; -addDecorator(story => ( +addDecorator((story) => ( {story()} )); + +addParameters({ + darkMode: { + // Set the initial theme + current: 'light', + }, +}); diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index 5a3eafa532..c187dc5f56 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -11,7 +11,7 @@ module.exports = { '@storybook/addon-storysource', 'storybook-dark-mode/register', ], - webpackFinal: async config => { + webpackFinal: async (config) => { const coreSrc = path.resolve(__dirname, '../../core/src'); config.resolve.alias = { diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index 3dad461423..9e9e2945fa 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -28,7 +28,7 @@ export const lightTheme = createTheme({ error: '#CA001B', running: '#BEBEBE', pending: '#5BC0DE', - background: '#FEFEFE', + background: '#F8F8F8', }, bursts: { fontColor: '#FEFEFE', @@ -68,7 +68,7 @@ export const darkTheme = createTheme({ error: '#CA001B', running: '#BEBEBE', pending: '#5BC0DE', - background: '#FEFEFE', + background: '#282828', }, bursts: { fontColor: '#FEFEFE', diff --git a/plugins/explore/src/components/ExploreCard.tsx b/plugins/explore/src/components/ExploreCard.tsx index 9c61e9bea8..af6eca61b7 100644 --- a/plugins/explore/src/components/ExploreCard.tsx +++ b/plugins/explore/src/components/ExploreCard.tsx @@ -28,7 +28,7 @@ import { } from '@material-ui/core'; import { BackstageTheme } from '@backstage/theme'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ card: { display: 'flex', flexDirection: 'column', diff --git a/plugins/explore/src/components/ExplorePluginPage.tsx b/plugins/explore/src/components/ExplorePluginPage.tsx index 7b5fc81a30..48a0f90c32 100644 --- a/plugins/explore/src/components/ExplorePluginPage.tsx +++ b/plugins/explore/src/components/ExplorePluginPage.tsx @@ -27,7 +27,7 @@ import { import ExploreCard, { CardData } from './ExploreCard'; import { BackstageTheme } from '@backstage/theme'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ container: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, 296px)', diff --git a/plugins/graphiql/src/components/GraphiQLBrowser/GraphiQLBrowser.tsx b/plugins/graphiql/src/components/GraphiQLBrowser/GraphiQLBrowser.tsx index c5a003a496..5885183b01 100644 --- a/plugins/graphiql/src/components/GraphiQLBrowser/GraphiQLBrowser.tsx +++ b/plugins/graphiql/src/components/GraphiQLBrowser/GraphiQLBrowser.tsx @@ -24,7 +24,7 @@ import { BackstageTheme } from '@backstage/theme'; const GraphiQL = React.lazy(() => import('graphiql')); -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { height: '100%', display: 'flex', diff --git a/plugins/inventory-backend/package.json b/plugins/inventory-backend/package.json index 4b7fd0ecc7..16d510a968 100644 --- a/plugins/inventory-backend/package.json +++ b/plugins/inventory-backend/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "private": true, "scripts": { - "start": "tsc-watch --onFirstSuccess \"nodemon dist/run.js\"", + "start": "tsc-watch --onFirstSuccess \"cross-env NODE_ENV=development nodemon dist/run.js\"", "build": "tsc", "lint": "backstage-cli lint", "test": "backstage-cli test", @@ -21,6 +21,7 @@ "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", + "express-promise-router": "^3.0.3", "helmet": "^3.22.0", "morgan": "^1.10.0", "winston": "^3.2.1" diff --git a/plugins/inventory-backend/src/index.ts b/plugins/inventory-backend/src/index.ts index 7612c392a2..52b4ed9a53 100644 --- a/plugins/inventory-backend/src/index.ts +++ b/plugins/inventory-backend/src/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ +export * from './inventory'; export * from './service/router'; diff --git a/plugins/inventory-backend/src/inventory/AggregatorInventory.ts b/plugins/inventory-backend/src/inventory/AggregatorInventory.ts index 3f1ca0f5de..b768b8eb23 100644 --- a/plugins/inventory-backend/src/inventory/AggregatorInventory.ts +++ b/plugins/inventory-backend/src/inventory/AggregatorInventory.ts @@ -14,19 +14,29 @@ * limitations under the License. */ +import { NotFoundError } from '@backstage/backend-common'; import { Component, Inventory } from './types'; export class AggregatorInventory implements Inventory { inventories: Inventory[] = []; - list(): Promise> { - return Promise.all(this.inventories.map(i => i.list())).then(lists => - lists.flat(), - ); + async list(): Promise { + const lists = await Promise.all(this.inventories.map((i) => i.list())); + return lists.flat(); } - item(id: string): Promise { - return this.list().then(items => items.find(i => i.id === id)); + item(id: string): Promise { + return new Promise((resolve, reject) => { + const promises = this.inventories.map((i) => + i.item(id).then(resolve, () => { + // For now, just swallow errors in individual inventories; + // should handle partial errors better + }), + ); + Promise.all(promises).then(() => + reject(new NotFoundError(`Found no component with ID ${id}`)), + ); + }); } enlist(inventory: Inventory) { diff --git a/plugins/inventory-backend/src/inventory/StaticInventory.ts b/plugins/inventory-backend/src/inventory/StaticInventory.ts index cd4fa2b7d5..4569ae8ed2 100644 --- a/plugins/inventory-backend/src/inventory/StaticInventory.ts +++ b/plugins/inventory-backend/src/inventory/StaticInventory.ts @@ -14,16 +14,21 @@ * limitations under the License. */ +import { NotFoundError } from '@backstage/backend-common'; import { Component, Inventory } from './types'; export class StaticInventory implements Inventory { constructor(private components: Component[]) {} - list(): Promise> { - return Promise.resolve([...this.components]); + async list(): Promise { + return this.components.slice(); } - item(id: string): Promise { - return this.list().then(items => items.find(i => i.id === id)); + async item(id: string): Promise { + const item = this.components.find((i) => i.id === id); + if (!item) { + throw new NotFoundError(`Found no component with ID ${id}`); + } + return item; } } diff --git a/plugins/inventory-backend/src/inventory/types.ts b/plugins/inventory-backend/src/inventory/types.ts index 3d30b11881..863dccb291 100644 --- a/plugins/inventory-backend/src/inventory/types.ts +++ b/plugins/inventory-backend/src/inventory/types.ts @@ -19,6 +19,6 @@ export type Component = { }; export type Inventory = { - list: () => Promise; - item: (id: string) => Promise; + list(): Promise; + item(id: string): Promise; }; diff --git a/plugins/inventory-backend/src/run.ts b/plugins/inventory-backend/src/run.ts index 979827c40f..0d5c3a5c12 100644 --- a/plugins/inventory-backend/src/run.ts +++ b/plugins/inventory-backend/src/run.ts @@ -15,20 +15,20 @@ */ import { getRootLogger } from '@backstage/backend-common'; -import { startServer } from './service/server'; +import { startStandaloneServer } from './service/standaloneServer'; -startServer({ - port: process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3003, - enableCors: process.env.PLUGIN_CORS - ? Boolean(process.env.PLUGIN_CORS) - : false, - logger: getRootLogger(), -}).catch(err => { - getRootLogger().error(err); +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3003; +const enableCors = process.env.PLUGIN_CORS + ? Boolean(process.env.PLUGIN_CORS) + : false; +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch((err) => { + logger.error(err); process.exit(1); }); process.on('SIGINT', () => { - getRootLogger().info('CTRL+C pressed; exiting.'); + logger.info('CTRL+C pressed; exiting.'); process.exit(0); }); diff --git a/plugins/inventory-backend/src/service/router.ts b/plugins/inventory-backend/src/service/router.ts index afa4a543b3..a05fac08be 100644 --- a/plugins/inventory-backend/src/service/router.ts +++ b/plugins/inventory-backend/src/service/router.ts @@ -16,28 +16,21 @@ import express from 'express'; import { Logger } from 'winston'; -import { AggregatorInventory, StaticInventory } from '../inventory'; +import Router from 'express-promise-router'; +import { Inventory } from '../inventory'; export interface RouterOptions { + inventory: Inventory; logger: Logger; } export async function createRouter( options: RouterOptions, ): Promise { + const inventory = options.inventory; const logger = options.logger.child({ plugin: 'inventory' }); - const inventory = new AggregatorInventory(); - inventory.enlist( - new StaticInventory([ - { id: 'component1' }, - { id: 'component2' }, - { id: 'component3' }, - { id: 'component4' }, - ]), - ); - - const router = express.Router(); + const router = Router(); router .get('/', async (req, res) => { const components = await inventory.list(); @@ -46,11 +39,7 @@ export async function createRouter( .get('/:id', async (req, res) => { const { id } = req.params; const component = await inventory.item(id); - if (component) { - res.status(200).send(component); - } else { - res.status(404).send(); - } + res.status(200).send(component); }); const app = express(); diff --git a/plugins/inventory-backend/src/service/application.ts b/plugins/inventory-backend/src/service/standaloneApplication.ts similarity index 83% rename from plugins/inventory-backend/src/service/application.ts rename to plugins/inventory-backend/src/service/standaloneApplication.ts index e5cf151050..8f0518bb8d 100644 --- a/plugins/inventory-backend/src/service/application.ts +++ b/plugins/inventory-backend/src/service/standaloneApplication.ts @@ -24,26 +24,29 @@ import cors from 'cors'; import express from 'express'; import helmet from 'helmet'; import { Logger } from 'winston'; +import { Inventory } from '../inventory'; import { createRouter } from './router'; export interface ApplicationOptions { enableCors: boolean; + inventory: Inventory; logger: Logger; } -export async function createApplication( +export async function createStandaloneApplication( options: ApplicationOptions, ): Promise { + const { enableCors, inventory, logger } = options; const app = express(); app.use(helmet()); - if (options.enableCors) { + if (enableCors) { app.use(cors()); } app.use(compression()); app.use(express.json()); app.use(requestLoggingHandler()); - app.use('/', await createRouter({ logger: options.logger })); + app.use('/', await createRouter({ inventory, logger })); app.use(notFoundHandler()); app.use(errorHandler()); diff --git a/plugins/inventory-backend/src/service/server.ts b/plugins/inventory-backend/src/service/standaloneServer.ts similarity index 70% rename from plugins/inventory-backend/src/service/server.ts rename to plugins/inventory-backend/src/service/standaloneServer.ts index 9715a72fef..be339c5ecc 100644 --- a/plugins/inventory-backend/src/service/server.ts +++ b/plugins/inventory-backend/src/service/standaloneServer.ts @@ -16,7 +16,8 @@ import { Server } from 'http'; import { Logger } from 'winston'; -import { createApplication } from './application'; +import { AggregatorInventory, StaticInventory } from '../inventory'; +import { createStandaloneApplication } from './standaloneApplication'; export interface ServerOptions { port: number; @@ -24,12 +25,25 @@ export interface ServerOptions { logger: Logger; } -export async function startServer(options: ServerOptions): Promise { +export async function startStandaloneServer( + options: ServerOptions, +): Promise { const logger = options.logger.child({ service: 'inventory-backend' }); + const inventory = new AggregatorInventory(); + inventory.enlist( + new StaticInventory([ + { id: 'component1' }, + { id: 'component2' }, + { id: 'component3' }, + { id: 'component4' }, + ]), + ); + logger.debug('Creating application...'); - const app = await createApplication({ + const app = await createStandaloneApplication({ enableCors: options.enableCors, + inventory, logger, }); diff --git a/plugins/lighthouse/src/components/Audit/AuditRow.tsx b/plugins/lighthouse/src/components/Audit/AuditRow.tsx index c42e6a8538..71668138f4 100644 --- a/plugins/lighthouse/src/components/Audit/AuditRow.tsx +++ b/plugins/lighthouse/src/components/Audit/AuditRow.tsx @@ -14,17 +14,16 @@ * limitations under the License. */ import React, { FC } from 'react'; -import { - Link, - TableCell, - TableRow, -} from '@material-ui/core'; +import { Link, TableCell, TableRow } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { TrendLine } from '@backstage/core'; +import { Website } from '../../api'; import { - Website, -} from '../../api'; -import { formatTime, CATEGORIES, CATEGORY_LABELS, SparklinesDataByCategory } from '../../utils'; + formatTime, + CATEGORIES, + CATEGORY_LABELS, + SparklinesDataByCategory, +} from '../../utils'; import AuditStatusIcon from '../AuditStatusIcon'; const useStyles = makeStyles(theme => ({ @@ -76,9 +75,7 @@ export const AuditRow: FC<{ {website.lastAudit.status.toLowerCase()} - - {formatTime(website.lastAudit.timeCreated)} - + {formatTime(website.lastAudit.timeCreated)} ); }; diff --git a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx index 778eb71fb5..082433a550 100644 --- a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx +++ b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx @@ -23,11 +23,14 @@ import { TableRow, } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import { Website } from '../../api'; import { - Website, -} from '../../api'; -import { CATEGORIES, CATEGORY_LABELS, SparklinesDataByCategory, buildSparklinesDataForItem } from '../../utils'; -import Audit from '../Audit' + CATEGORIES, + CATEGORY_LABELS, + SparklinesDataByCategory, + buildSparklinesDataForItem, +} from '../../utils'; +import Audit from '../Audit'; const useStyles = makeStyles(theme => ({ table: { @@ -51,7 +54,7 @@ export const AuditListTable: FC<{ items: Website[] }> = ({ items }) => { () => items.reduce( (res, item) => ({ - ...res, + ...res, [item.url]: buildSparklinesDataForItem(item), }), {}, diff --git a/plugins/lighthouse/src/components/CreateAudit/index.tsx b/plugins/lighthouse/src/components/CreateAudit/index.tsx index 8ff0ce5ea8..19db33791b 100644 --- a/plugins/lighthouse/src/components/CreateAudit/index.tsx +++ b/plugins/lighthouse/src/components/CreateAudit/index.tsx @@ -84,7 +84,14 @@ const CreateAudit: FC<{}> = () => { } finally { setSubmitting(false); } - }, [url, emulatedFormFactor, lighthouseApi, setSubmitting, errorApi, history]); + }, [ + url, + emulatedFormFactor, + lighthouseApi, + setSubmitting, + errorApi, + history, + ]); return ( diff --git a/plugins/lighthouse/src/utils.ts b/plugins/lighthouse/src/utils.ts index 1546521083..cc09b52f4f 100644 --- a/plugins/lighthouse/src/utils.ts +++ b/plugins/lighthouse/src/utils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { useLocation } from 'react-router-dom'; -import {Website, Audit, LighthouseCategoryId, AuditCompleted} from './api' +import { Website, Audit, LighthouseCategoryId, AuditCompleted } from './api'; export function useQuery(): URLSearchParams { return new URLSearchParams(useLocation().search); } @@ -45,7 +45,9 @@ export const CATEGORY_LABELS: Record = { }; export type SparklinesDataByCategory = Record; -export function buildSparklinesDataForItem(item: Website): SparklinesDataByCategory { +export function buildSparklinesDataForItem( + item: Website, +): SparklinesDataByCategory { return item.audits .filter( (audit: Audit): audit is AuditCompleted => audit.status === 'COMPLETED', @@ -63,4 +65,4 @@ export function buildSparklinesDataForItem(item: Website): SparklinesDataByCateg return scores; }, {} as SparklinesDataByCategory); -} \ No newline at end of file +} diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/_document.tsx b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/_document.tsx index 210de6412c..9b01a6a74c 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/_document.tsx +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/_document.tsx @@ -20,7 +20,7 @@ export default class MyDocument extends Document { try { ctx.renderPage = () => originalRenderPage({ - enhanceApp: (App: any) => props => + enhanceApp: (App: any) => (props) => sheet.collectStyles(), }); diff --git a/plugins/scaffolder/src/components/ScaffolderPage/index.tsx b/plugins/scaffolder/src/components/ScaffolderPage/index.tsx index e6adaca099..b05b86bd92 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/index.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/index.tsx @@ -15,7 +15,16 @@ */ import React from 'react'; -import { Content, InfoCard, Header, Page, pageTheme } from '@backstage/core'; +import { + AlphaLabel, + Content, + ContentHeader, + InfoCard, + Header, + Page, + pageTheme, +} from '@backstage/core'; +import { Typography } from '@material-ui/core'; // TODO(blam): Connect to backend const STATIC_DATA = [ @@ -30,16 +39,25 @@ const STATIC_DATA = [ const ScaffolderPage: React.FC<{}> = () => { return ( -
+
+ Create a new component {' '} + + } + subtitle="Create new software components using standard templates" + /> +
- {STATIC_DATA.map(item => { + {STATIC_DATA.map((item, ix) => { return ( -

{item.description}

+ {item.description}
); })} diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index 3dfa5d0b89..31bdab2f84 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -20,6 +20,6 @@ import ScaffolderPage from './components/ScaffolderPage'; export const plugin = createPlugin({ id: 'scaffolder', register({ router }) { - router.registerRoute('/scaffolder', ScaffolderPage); + router.registerRoute('/create', ScaffolderPage); }, }); diff --git a/yarn.lock b/yarn.lock index e7fa7c2dec..fd89c86b9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -926,6 +926,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-typescript" "^7.9.0" +"@babel/runtime-corejs2@^7.4.4": + version "7.9.2" + resolved "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.9.2.tgz#f11d074ff99b9b4319b5ecf0501f12202bf2bf4d" + integrity sha512-ayjSOxuK2GaSDJFCtLgHnYjuMyIpViNujWrZo8GUpN60/n7juzJKK5yOo6RFVb0zdU9ACJFK+MsZrUnj3OmXMw== + dependencies: + core-js "^2.6.5" + regenerator-runtime "^0.13.4" + "@babel/runtime-corejs3@^7.7.4", "@babel/runtime-corejs3@^7.8.3": version "7.9.2" resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz#26fe4aa77e9f1ecef9b776559bbb8e84d34284b7" @@ -941,7 +949,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.9.2" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== @@ -1178,6 +1186,18 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@date-io/core@1.x", "@date-io/core@^1.3.13": + version "1.3.13" + resolved "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa" + integrity sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA== + +"@date-io/date-fns@^1.1.0": + version "1.3.13" + resolved "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.13.tgz#7798844041640ab393f7e21a7769a65d672f4735" + integrity sha512-yXxGzcRUPcogiMj58wVgFjc9qUYrCnnU9eLcyNbsQCmae4jPuZCDoIBR21j8ZURsM7GRtU62VOw5yNd4dDHunA== + dependencies: + "@date-io/core" "^1.3.13" + "@emotion/cache@^10.0.27": version "10.0.29" resolved "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -2468,6 +2488,18 @@ prop-types "^15.7.2" react-is "^16.8.0" +"@material-ui/pickers@^3.2.2": + version "3.2.10" + resolved "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae" + integrity sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w== + dependencies: + "@babel/runtime" "^7.6.0" + "@date-io/core" "1.x" + "@types/styled-jsx" "^2.2.8" + clsx "^1.0.2" + react-transition-group "^4.0.0" + rifm "^0.7.0" + "@material-ui/styles@^4.9.6": version "4.9.6" resolved "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.6.tgz#924a30bf7c9b91af9c8f19c12c8573b8a4ecd085" @@ -4387,6 +4419,13 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/styled-jsx@^2.2.8": + version "2.2.8" + resolved "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.8.tgz#b50d13d8a3c34036282d65194554cf186bab7234" + integrity sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg== + dependencies: + "@types/react" "*" + "@types/superagent@*": version "4.1.7" resolved "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.7.tgz#a7d92d98c490ee0f802a127fdf149b9a114f77a5" @@ -7232,7 +7271,7 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== -core-js@^2.4.0, core-js@^2.5.0: +core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5: version "2.6.11" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== @@ -7415,6 +7454,13 @@ css-blank-pseudo@^0.1.4: dependencies: postcss "^7.0.5" +css-box-model@^1.1.2: + version "1.2.0" + resolved "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.0.tgz#3a26377b4162b3200d2ede4b064ec5b6a75186d0" + integrity sha512-lri0br+jSNV0kkkiGEp9y9y3Njq2PmpqbeGWRFQJuZteZzY9iC9GZhQ8Y4WpPwM/2YocjHePxy14igJY7YKzkA== + dependencies: + tiny-invariant "^1.0.6" + css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -7851,11 +7897,21 @@ date-fns@^1.27.2: resolved "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== +date-fns@^2.0.0-alpha.27: + version "2.12.0" + resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.12.0.tgz#01754c8a2f3368fc1119cf4625c3dad8c1845ee6" + integrity sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw== + dateformat@^3.0.0: version "3.0.3" resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== +debounce@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" + integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -9188,6 +9244,15 @@ expect@^25.1.0: jest-message-util "^25.1.0" jest-regex-util "^25.1.0" +express-promise-router@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/express-promise-router/-/express-promise-router-3.0.3.tgz#5e6d22a5a3f013d71833172fe8d7ab780c3f6b70" + integrity sha1-Xm0ipaPwE9cYMxcv6NereAw/a3A= + dependencies: + is-promise "^2.1.0" + lodash.flattendeep "^4.0.0" + methods "^1.0.0" + express@^4.17.0, express@^4.17.1: version "4.17.1" resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -9294,7 +9359,7 @@ extsprintf@^1.2.0: resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^2.0.1: +fast-deep-equal@2.0.1, fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= @@ -9466,6 +9531,11 @@ file-uri-to-path@1.0.0: resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filefy@0.1.10: + version "0.1.10" + resolved "https://registry.npmjs.org/filefy/-/filefy-0.1.10.tgz#174677c8e2fa5bc39a3af0ed6fb492f16b8fbf42" + integrity sha512-VgoRVOOY1WkTpWH+KBy8zcU1G7uQTVsXqhWEgzryB9A5hg2aqCyZ6aQ/5PSzlqM5+6cnVrX6oYV0XqD3HZSnmQ== + filesize@3.6.1: version "3.6.1" resolved "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" @@ -13607,6 +13677,11 @@ lodash.escaperegexp@^4.1.2: resolved "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= +lodash.flattendeep@^4.0.0: + version "4.4.0" + resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -13956,6 +14031,22 @@ marked@^0.8.0: resolved "https://registry.npmjs.org/marked/-/marked-0.8.1.tgz#a233f39572fab15ede53a3c3be8a139bff86d2dd" integrity sha512-tZfJS8uE0zpo7xpTffwFwYRfW9AzNcdo04Qcjs+C9+oCy8MSRD2reD5iDVtYx8mtLaqsGughw/YLlcwNxAHA1g== +material-table@^1.57.2: + version "1.57.2" + resolved "https://registry.npmjs.org/material-table/-/material-table-1.57.2.tgz#8854cb4d623b294138c83a0c7ff9f2900267c81f" + integrity sha512-hiJdRTrqu8pyYwSxzmcG1TnR4KWG2gtXrFB3XL9h4ij3A68EOJmlss6VH/LXh3NLlUce1TteK6W7fGa7YcnKGg== + dependencies: + "@date-io/date-fns" "^1.1.0" + "@material-ui/pickers" "^3.2.2" + classnames "^2.2.6" + date-fns "^2.0.0-alpha.27" + debounce "^1.2.0" + fast-deep-equal "2.0.1" + filefy "0.1.10" + prop-types "^15.6.2" + react-beautiful-dnd "11.0.3" + react-double-scrollbar "0.0.15" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -14013,6 +14104,11 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" +memoize-one@^5.0.4: + version "5.1.1" + resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" + integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== + memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" @@ -14111,7 +14207,7 @@ merge@^1.2.1: resolved "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== -methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: +methods@^1.0.0, methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -17019,6 +17115,11 @@ qw@~1.0.1: resolved "https://registry.npmjs.org/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4" integrity sha1-77/cdA+a0FQwRCassYNBLMi5ltQ= +raf-schd@^4.0.0: + version "4.0.2" + resolved "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0" + integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ== + raf@^3.4.1: version "3.4.1" resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" @@ -17109,6 +17210,20 @@ react-app-polyfill@^1.0.6: regenerator-runtime "^0.13.3" whatwg-fetch "^3.0.0" +react-beautiful-dnd@11.0.3: + version "11.0.3" + resolved "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-11.0.3.tgz#5678bb3e725d8b56cb7cf57f56e952105fc4f2af" + integrity sha512-2FX2SnOlKMmfn90xUHCav7cxRWXwY7FeRa6TzdxWeX7DdP5JTvVQcsWgiOkdbJSj+J+1q1nA9QO4/HQ52D0DAA== + dependencies: + "@babel/runtime-corejs2" "^7.4.4" + css-box-model "^1.1.2" + memoize-one "^5.0.4" + raf-schd "^4.0.0" + react-redux "^7.0.3" + redux "^4.0.1" + tiny-invariant "^1.0.4" + use-memo-one "^1.1.0" + react-clientside-effect@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.2.tgz#6212fb0e07b204e714581dd51992603d1accc837" @@ -17201,6 +17316,11 @@ react-dom@16.13.1, react-dom@^16.12.0, react-dom@^16.13.1, react-dom@^16.8.3: prop-types "^15.6.2" scheduler "^0.19.1" +react-double-scrollbar@0.0.15: + version "0.0.15" + resolved "https://registry.npmjs.org/react-double-scrollbar/-/react-double-scrollbar-0.0.15.tgz#e915ab8cb3b959877075f49436debfdb04288fe4" + integrity sha1-6RWrjLO5WYdwdfSUNt6/2wQoj+Q= + react-draggable@^4.0.3: version "4.2.0" resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-4.2.0.tgz#40cc5209082ca7d613104bf6daf31372cc0e1114" @@ -17268,7 +17388,7 @@ react-inspector@^4.0.0: is-dom "^1.0.9" prop-types "^15.6.1" -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: +react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -17313,6 +17433,17 @@ react-popper@^1.3.6: typed-styles "^0.0.7" warning "^4.0.2" +react-redux@^7.0.3: + version "7.2.0" + resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" + integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + react-router-dom@5.1.2, react-router-dom@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" @@ -17445,7 +17576,7 @@ react-textarea-autosize@^7.1.0: "@babel/runtime" "^7.1.2" prop-types "^15.6.0" -react-transition-group@^4.3.0: +react-transition-group@^4.0.0, react-transition-group@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683" integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw== @@ -17749,6 +17880,14 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +redux@^4.0.1: + version "4.0.5" + resolved "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + referrer-policy@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e" @@ -18178,6 +18317,13 @@ rgba-regex@^1.0.0: resolved "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= +rifm@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be" + integrity sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ== + dependencies: + "@babel/runtime" "^7.3.1" + right-pad@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0" @@ -19671,7 +19817,7 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.1.0: +symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -19982,7 +20128,7 @@ tiny-emitter@^2.0.0: resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== -tiny-invariant@^1.0.2: +tiny-invariant@^1.0.2, tiny-invariant@^1.0.4, tiny-invariant@^1.0.6: version "1.1.0" resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== @@ -20631,6 +20777,11 @@ use-callback-ref@^1.2.1: resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.1.tgz#898759ccb9e14be6c7a860abafa3ffbd826c89bb" integrity sha512-C3nvxh0ZpaOxs9RCnWwAJ+7bJPwQI8LHF71LzbQ3BvzH5XkdtlkMadqElGevg5bYBDFip4sAnD4m06zAKebg1w== +use-memo-one@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" + integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== + use-sidecar@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.2.tgz#e72f582a75842f7de4ef8becd6235a4720ad8af6"