Merge branch 'master' into eide/lighthouse-new-routing-api

This commit is contained in:
Marcus Eide
2020-09-09 15:58:47 +02:00
52 changed files with 460 additions and 533 deletions
+1 -1
View File
@@ -26,7 +26,7 @@
"@backstage/theme": "^0.1.1-alpha.21",
"@kyma-project/asyncapi-react": "^0.11.0",
"@material-icons/font": "^1.0.2",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
+1 -1
View File
@@ -29,7 +29,7 @@
"@backstage/plugin-sentry": "^0.1.1-alpha.21",
"@backstage/plugin-techdocs": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"moment": "^2.26.0",
+1 -1
View File
@@ -25,7 +25,7 @@
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"circleci-api": "^4.0.0",
@@ -13,18 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect, useState, FC, Suspense } from 'react';
import {
ExpansionPanel,
ExpansionPanelSummary,
Typography,
ExpansionPanelDetails,
Accordion,
AccordionDetails,
AccordionSummary,
LinearProgress,
Typography,
} from '@material-ui/core';
import moment from 'moment';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { makeStyles } from '@material-ui/core/styles';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { BuildStepAction } from 'circleci-api';
import moment from 'moment';
import React, { FC, Suspense, useEffect, useState } from 'react';
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
moment.relativeTimeThreshold('ss', 0);
@@ -66,11 +67,8 @@ export const ActionOutput: FC<{
)
.humanize();
return (
<ExpansionPanel
TransitionProps={{ unmountOnExit: true }}
className={className}
>
<ExpansionPanelSummary
<Accordion TransitionProps={{ unmountOnExit: true }} className={className}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
@@ -81,8 +79,8 @@ export const ActionOutput: FC<{
<Typography variant="button">
{name} ({timeElapsed})
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.expansionPanelDetails}>
</AccordionSummary>
<AccordionDetails className={classes.expansionPanelDetails}>
{messages.length === 0 ? (
'Nothing here...'
) : (
@@ -92,7 +90,7 @@ export const ActionOutput: FC<{
</div>
</Suspense>
)}
</ExpansionPanelDetails>
</ExpansionPanel>
</AccordionDetails>
</Accordion>
);
};
+1 -1
View File
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"classnames": "^2.2.6",
+1 -1
View File
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
+2 -2
View File
@@ -27,11 +27,11 @@
"@backstage/core-api": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@octokit/rest": "^18.0.0",
"@octokit/types": "^5.0.1",
"@octokit/types": "^5.4.1",
"moment": "^2.27.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
@@ -13,37 +13,38 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { useWorkflowRunsDetails } from './useWorkflowRunsDetails';
import { useWorkflowRunJobs } from './useWorkflowRunJobs';
import { useProjectName } from '../useProjectName';
import {
makeStyles,
Box,
TableRow,
TableCell,
ListItemText,
ExpansionPanel,
ExpansionPanelSummary,
Typography,
ExpansionPanelDetails,
TableContainer,
Table,
Paper,
TableBody,
LinearProgress,
CircularProgress,
Theme,
Breadcrumbs,
Link as MaterialLink,
} from '@material-ui/core';
import { Jobs, Job, Step } from '../../api';
import moment from 'moment';
import { WorkflowRunStatus } from '../WorkflowRunStatus';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import ExternalLinkIcon from '@material-ui/icons/Launch';
import { Entity } from '@backstage/catalog-model';
import { Link } from '@backstage/core';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Breadcrumbs,
CircularProgress,
LinearProgress,
Link as MaterialLink,
ListItemText,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
Theme,
Typography,
} from '@material-ui/core';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import ExternalLinkIcon from '@material-ui/icons/Launch';
import moment from 'moment';
import React from 'react';
import { Job, Jobs, Step } from '../../api';
import { useProjectName } from '../useProjectName';
import { WorkflowRunStatus } from '../WorkflowRunStatus';
import { useWorkflowRunJobs } from './useWorkflowRunJobs';
import { useWorkflowRunsDetails } from './useWorkflowRunsDetails';
const useStyles = makeStyles<Theme>(theme => ({
root: {
@@ -113,11 +114,8 @@ const StepView = ({ step }: { step: Step }) => {
const JobListItem = ({ job, className }: { job: Job; className: string }) => {
const classes = useStyles();
return (
<ExpansionPanel
TransitionProps={{ unmountOnExit: true }}
className={className}
>
<ExpansionPanelSummary
<Accordion TransitionProps={{ unmountOnExit: true }} className={className}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
@@ -128,8 +126,8 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => {
<Typography variant="button">
{job.name} ({getElapsedTime(job.started_at, job.completed_at)})
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.expansionPanelDetails}>
</AccordionSummary>
<AccordionDetails className={classes.expansionPanelDetails}>
<TableContainer>
<Table>
{job.steps.map((step: Step) => (
@@ -137,8 +135,8 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => {
))}
</Table>
</TableContainer>
</ExpansionPanelDetails>
</ExpansionPanel>
</AccordionDetails>
</Accordion>
);
};
+1 -1
View File
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
+1 -1
View File
@@ -33,7 +33,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"graphiql": "^1.0.0-alpha.10",
+1 -1
View File
@@ -24,7 +24,7 @@
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"jenkins": "^0.28.0",
@@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect, FC } from 'react';
import {
ExpansionPanel,
ExpansionPanelSummary,
Accordion,
AccordionDetails,
AccordionSummary,
Typography,
ExpansionPanelDetails,
} from '@material-ui/core';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { makeStyles } from '@material-ui/core/styles';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import React, { FC, useEffect } from 'react';
const useStyles = makeStyles({
expansionPanelDetails: {
@@ -45,11 +46,8 @@ export const ActionOutput: FC<{
useEffect(() => {}, [url]);
return (
<ExpansionPanel
TransitionProps={{ unmountOnExit: true }}
className={className}
>
<ExpansionPanelSummary
<Accordion TransitionProps={{ unmountOnExit: true }} className={className}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
@@ -58,10 +56,10 @@ export const ActionOutput: FC<{
}}
>
<Typography variant="button">{name}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.expansionPanelDetails}>
</AccordionSummary>
<AccordionDetails className={classes.expansionPanelDetails}>
Nothing here...
</ExpansionPanelDetails>
</ExpansionPanel>
</AccordionDetails>
</Accordion>
);
};
+1 -1
View File
@@ -24,7 +24,7 @@
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
+1 -1
View File
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
+1 -1
View File
@@ -25,7 +25,7 @@
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
@@ -16,10 +16,15 @@
import React from 'react';
import { render, cleanup } from '@testing-library/react';
import RegisterComponentPage from './RegisterComponentPage';
import { RegisterComponentPage } from './RegisterComponentPage';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { errorApiRef, ApiProvider, ApiRegistry } from '@backstage/core';
import {
errorApiRef,
ApiProvider,
ApiRegistry,
createRouteRef,
} from '@backstage/core';
import { catalogApiRef } from '@backstage/plugin-catalog';
import { MemoryRouter } from 'react-router-dom';
@@ -45,7 +50,12 @@ const setup = () => ({
])}
>
<ThemeProvider theme={lightTheme}>
<RegisterComponentPage />
<RegisterComponentPage
catalogRouteRef={createRouteRef({
path: '/catalog',
title: 'Service Catalog',
})}
/>
</ThemeProvider>
</ApiProvider>
</MemoryRouter>,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { FC, useState } from 'react';
import React, { useState } from 'react';
import { Grid, makeStyles } from '@material-ui/core';
import {
InfoCard,
@@ -26,6 +26,7 @@ import {
Header,
SupportButton,
ContentHeader,
RouteRef,
} from '@backstage/core';
import RegisterComponentForm from '../RegisterComponentForm';
import { catalogApiRef } from '@backstage/plugin-catalog';
@@ -54,7 +55,11 @@ const FormStates = {
} as const;
type ValuesOf<T> = T extends Record<any, infer V> ? V : never;
const RegisterComponentPage: FC<{}> = () => {
export const RegisterComponentPage = ({
catalogRouteRef,
}: {
catalogRouteRef: RouteRef;
}) => {
const classes = useStyles();
const catalogApi = useApi(catalogApiRef);
const [formState, setFormState] = useState<ValuesOf<typeof FormStates>>(
@@ -130,10 +135,9 @@ const RegisterComponentPage: FC<{}> = () => {
entities={result.data!.entities}
onClose={() => setFormState(FormStates.Idle)}
classes={{ paper: classes.dialogPaper }}
catalogRouteRef={catalogRouteRef}
/>
)}
</Page>
);
};
export default RegisterComponentPage;
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { default } from './RegisterComponentPage';
export { RegisterComponentPage } from './RegisterComponentPage';
@@ -20,6 +20,7 @@ import { cleanup, render } from '@testing-library/react';
import React, { ComponentProps } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RegisterComponentResultDialog } from './RegisterComponentResultDialog';
import { createRouteRef } from '@backstage/core';
const setup = (
props?: Partial<ComponentProps<typeof RegisterComponentResultDialog>>,
@@ -30,6 +31,10 @@ const setup = (
<RegisterComponentResultDialog
onClose={() => {}}
entities={[]}
catalogRouteRef={createRouteRef({
path: '/catalog',
title: 'Service Catalog',
})}
{...props}
/>
</ThemeProvider>
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { FC } from 'react';
import React from 'react';
import {
Dialog,
DialogTitle,
@@ -28,25 +28,50 @@ import {
Button,
} from '@material-ui/core';
import { Entity } from '@backstage/catalog-model';
import { StructuredMetadataTable } from '@backstage/core';
import { generatePath } from 'react-router';
import {
entityRoute,
rootRoute as catalogRootRoute,
} from '@backstage/plugin-catalog';
import { StructuredMetadataTable, RouteRef } from '@backstage/core';
import { generatePath, resolvePath } from 'react-router';
import { entityRoute } from '@backstage/plugin-catalog';
import { Link as RouterLink } from 'react-router-dom';
type Props = {
onClose: () => void;
classes?: Record<string, string>;
entities: Entity[];
catalogRouteRef: RouteRef;
};
export const RegisterComponentResultDialog: FC<Props> = ({
const getEntityCatalogPath = ({
entity,
catalogRouteRef,
}: {
entity: Entity;
catalogRouteRef: RouteRef;
}) => {
const optionalNamespaceAndName = [
entity.metadata.namespace,
entity.metadata.name,
]
.filter(Boolean)
.join(':');
const relativeEntityPathInsideCatalog = generatePath(entityRoute.path, {
optionalNamespaceAndName,
kind: entity.kind,
});
const resolvedAbsolutePath = resolvePath(
relativeEntityPathInsideCatalog,
catalogRouteRef.path,
)?.pathname;
return resolvedAbsolutePath;
};
export const RegisterComponentResultDialog = ({
onClose,
classes,
entities,
}) => (
catalogRouteRef,
}: Props) => (
<Dialog open onClose={onClose} classes={classes}>
<DialogTitle>Component Registration Result</DialogTitle>
<DialogContent>
@@ -55,17 +80,7 @@ export const RegisterComponentResultDialog: FC<Props> = ({
</DialogContentText>
<List>
{entities.map((entity: any, index: number) => {
const entityPath = generatePath(entityRoute.path, {
optionalNamespaceAndName: [
entity.metadata.namespace,
entity.metadata.name,
]
.filter(Boolean)
.join(':'),
kind: entity.kind,
selectedTabId: 'overview',
});
const entityPath = getEntityCatalogPath({ entity, catalogRouteRef });
return (
<React.Fragment
key={`${entity.metadata.namespace}-${entity.metadata.name}`}
@@ -91,7 +106,11 @@ export const RegisterComponentResultDialog: FC<Props> = ({
</List>
</DialogContent>
<DialogActions>
<Button component={RouterLink} to={catalogRootRoute.path} color="default">
<Button
component={RouterLink}
to={`/${catalogRouteRef.path}`}
color="default"
>
To Catalog
</Button>
</DialogActions>
@@ -0,0 +1,29 @@
/*
* 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 { Route, Routes } from 'react-router';
import { RegisterComponentPage } from './RegisterComponentPage';
import { RouteRef } from '@backstage/core';
// As we don't know which path the catalog's router mounted on
// We need to inject this from the app
export const Router = ({ catalogRouteRef }: { catalogRouteRef: RouteRef }) => (
<Routes>
<Route
element={<RegisterComponentPage catalogRouteRef={catalogRouteRef} />}
/>
</Routes>
);
+2 -1
View File
@@ -14,4 +14,5 @@
* limitations under the License.
*/
export { plugin, rootRoute } from './plugin';
export { plugin } from './plugin';
export { Router } from './components/Router';
+1 -11
View File
@@ -14,18 +14,8 @@
* limitations under the License.
*/
import { createPlugin, createRouteRef } from '@backstage/core';
import RegisterComponentPage from './components/RegisterComponentPage';
export const rootRoute = createRouteRef({
icon: () => null,
path: '/register-component',
title: 'Register component',
});
import { createPlugin } from '@backstage/core';
export const plugin = createPlugin({
id: 'register-component',
register({ router }) {
router.addRoute(rootRoute, RegisterComponentPage);
},
});
+1 -1
View File
@@ -25,7 +25,7 @@
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"lodash": "^4.17.15",
+1 -1
View File
@@ -45,7 +45,7 @@
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@octokit/types": "^5.0.1",
"@octokit/types": "^5.4.1",
"@types/fs-extra": "^9.0.1",
"@types/git-url-parse": "^9.0.0",
"@types/nodegit": "0.26.8",
@@ -5,7 +5,7 @@ metadata:
title: Documentation Template
description: Create a new standalone documentation project
tags:
- experimental
- recommended
- techdocs
- mkdocs
spec:
+1 -1
View File
@@ -25,7 +25,7 @@
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@rjsf/core": "^2.1.0",
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
ExpansionPanel,
ExpansionPanelDetails,
ExpansionPanelSummary,
LinearProgress,
Typography,
} from '@material-ui/core';
@@ -98,7 +99,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
: null;
return (
<ExpansionPanel
<Accordion
TransitionProps={{ unmountOnExit: true }}
className={cn(
classes.expansionPanel,
@@ -108,7 +109,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
expanded={expanded}
onChange={(_, newState) => setExpanded(newState)}
>
<ExpansionPanelSummary
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
@@ -119,8 +120,8 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
<Typography variant="button">
{name} {timeElapsed && `(${timeElapsed})`}
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.expansionPanelDetails}>
</AccordionSummary>
<AccordionDetails className={classes.expansionPanelDetails}>
{log.length === 0 ? (
<Box px={4}>No logs available for this step</Box>
) : (
@@ -130,7 +131,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
</div>
</Suspense>
)}
</ExpansionPanelDetails>
</ExpansionPanel>
</AccordionDetails>
</Accordion>
);
};
+1 -1
View File
@@ -24,7 +24,7 @@
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "^16.9",
+1 -1
View File
@@ -24,7 +24,7 @@
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"color": "^3.1.2",
+1 -1
View File
@@ -28,7 +28,7 @@
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "^16.9",
+1 -1
View File
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.9.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",