feat(scaffolder): mocked fe flow

This commit is contained in:
Ivan Shmidt
2020-06-29 11:42:17 +02:00
parent 71c65d9610
commit 3f3409ac4c
18 changed files with 668 additions and 28 deletions
+1 -1
View File
@@ -33,7 +33,7 @@
"version": "1.0.0",
"devDependencies": {
"@spotify/eslint-config-oss": "^1.0.1",
"@spotify/prettier-config": "^7.0.0",
"@spotify/prettier-config": "^8.0.0",
"husky": "^4.2.3",
"lerna": "^3.20.2",
"lint-staged": "^10.1.0",
+14 -2
View File
@@ -51,6 +51,10 @@ import {
graphQlBrowseApiRef,
GraphQLEndpoints,
} from '@backstage/plugin-graphiql';
import {
scaffolderApiRef,
ScaffolderApi,
} from '@backstage/plugin-scaffolder/src/api';
export const apis = (config: ConfigApi) => {
// eslint-disable-next-line no-console
@@ -113,8 +117,16 @@ export const apis = (config: ConfigApi) => {
builder.add(
catalogApiRef,
new CatalogClient({
apiOrigin: 'http://localhost:3000',
basePath: '/catalog/api',
apiOrigin: 'http://localhost:7000',
basePath: '/catalog',
}),
);
builder.add(
scaffolderApiRef,
new ScaffolderApi({
apiOrigin: 'http://localhost:7000',
basePath: '/scaffolder/v1',
}),
);
@@ -11,3 +11,8 @@ spec:
processor: cookiecutter
type: website
path: '.'
parameters:
component_name:
title: Component name
type: string
description: Name of the component
+2
View File
@@ -28,6 +28,8 @@
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@rjsf/core": "^2.1.0",
"@rjsf/material-ui": "^2.1.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-alpha.5",
+52
View File
@@ -0,0 +1,52 @@
/*
* 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 { createApiRef } from '@backstage/core';
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
export const scaffolderApiRef = createApiRef<ScaffolderApi>({
id: 'plugin.scaffolder.service',
description: 'Used to make requests towards the scaffolder backend',
});
export class ScaffolderApi {
private apiOrigin: string;
private basePath: string;
constructor({
apiOrigin,
basePath,
}: {
apiOrigin: string;
basePath: string;
}) {
this.apiOrigin = apiOrigin;
this.basePath = basePath;
}
async scaffold(
template: TemplateEntityV1alpha1,
values: Record<string, any>,
) {
const url = `${this.apiOrigin}${this.basePath}/jobs`;
const jobId = await fetch(url, {
method: 'POST',
body: JSON.stringify({ template, values }),
}).then(x => x.json());
return jobId;
}
}
@@ -0,0 +1,111 @@
import React, { useState } from 'react';
import useStaleWhileRevalidate from 'swr';
import { useParams } from 'react-router-dom';
import { LinearProgress, Button } from '@material-ui/core';
import { catalogApiRef } from '@backstage/plugin-catalog';
import {
useApi,
SimpleStepper,
SimpleStepperStep,
Page,
Content,
ContentHeader,
Header,
Lifecycle,
} from '@backstage/core';
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
import { withTheme, IChangeEvent } from '@rjsf/core';
import { Theme as MuiTheme } from '@rjsf/material-ui';
import { JobStatusModal } from '../JobStatusModal';
import { scaffolderApiRef } from '../../api';
const Form = withTheme(MuiTheme);
export const CreatePage = () => {
const catalogApi = useApi(catalogApiRef);
const scaffolderApi = useApi(scaffolderApiRef);
const { templateName } = useParams();
const {
data: [template] = [] as TemplateEntityV1alpha1[],
isValidating,
} = useStaleWhileRevalidate(
`templates/${templateName}`,
async () =>
(catalogApi.getEntities({
kind: 'Template',
'metadata.name': templateName,
}) as any) as Promise<TemplateEntityV1alpha1[]>,
);
const [formState, setFormState] = useState({});
const handleChange = (e: IChangeEvent) =>
setFormState({ ...formState, ...e.formData });
const [jobId, setJobId] = useState<string | null>(null);
const handleClose = () => setJobId(null);
if (!template && isValidating) return <LinearProgress />;
if (!template || !template?.spec?.parameters) return null;
const handleCreate = async () => {
const job = await scaffolderApi.scaffold(template, formState);
setJobId(job);
};
return (
<Page>
<Header
pageTitleOverride="Create a new component"
title={
<>
Create a new component <Lifecycle alpha shorthand />
</>
}
subtitle="Create new software components using standard templates"
/>
<Content>
<ContentHeader
title={template.metadata.title as string}
></ContentHeader>
{jobId && <JobStatusModal jobId={jobId} onClose={handleClose} />}
<SimpleStepper
onStepChange={(_prevStep, nextStep) => {
if (nextStep === 2) {
handleCreate();
}
}}
>
<SimpleStepperStep title="Configure your component">
<Form
formData={formState}
onChange={handleChange}
schema={{
$schema: 'http://json-schema.org/draft-07/schema#',
properties: template?.spec?.parameters,
}}
>
<Button hidden />
</Form>
</SimpleStepperStep>
<SimpleStepperStep title="Choose repository">
<Form
formData={formState}
onChange={handleChange}
schema={{
$schema: 'http://json-schema.org/draft-07/schema#',
properties: {
repo: {
type: 'string',
description:
'Path to the repo where to upload created component',
},
},
}}
>
<Button hidden />
</Form>
</SimpleStepperStep>
</SimpleStepper>
</Content>
</Page>
);
};
@@ -0,0 +1 @@
export { CreatePage } from './CreatePage';
@@ -0,0 +1,149 @@
/*
* 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, { useEffect, useState, Suspense } from 'react';
import {
ExpansionPanel,
ExpansionPanelSummary,
Typography,
ExpansionPanelDetails,
LinearProgress,
} from '@material-ui/core';
import moment from 'moment';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { makeStyles } from '@material-ui/core/styles';
import { Job } from './types';
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
moment.relativeTimeThreshold('ss', 0);
const useStyles = makeStyles(theme => ({
expansionPanelDetails: {
padding: 0,
},
button: {
order: -1,
marginRight: 0,
marginLeft: '-20px',
},
neutral: {},
failed: {
position: 'relative',
'&:after': {
pointerEvents: 'none',
content: '""',
position: 'absolute',
top: 0,
right: 0,
left: 0,
bottom: 0,
boxShadow: `inset 4px 0px 0px ${theme.palette.error.main}`,
},
},
running: {
position: 'relative',
'&:after': {
pointerEvents: 'none',
content: '""',
position: 'absolute',
top: 0,
right: 0,
left: 0,
bottom: 0,
boxShadow: `inset 4px 0px 0px ${theme.palette.info.main}`,
},
},
cardContent: {
backgroundColor: theme.palette.background.default,
},
success: {
position: 'relative',
'&:after': {
pointerEvents: 'none',
content: '""',
position: 'absolute',
top: 0,
right: 0,
left: 0,
bottom: 0,
boxShadow: `inset 4px 0px 0px ${theme.palette.success.main}`,
},
},
}));
type Props = {
name: string;
className?: string;
log: string[];
startedAt: string;
finishedAt?: string;
status?: Job['status'];
};
export const JobStage = ({
finishedAt,
startedAt,
name,
log,
status,
}: Props) => {
const classes = useStyles();
const [expanded, setExpanded] = useState(false);
useEffect(() => {
if (status === 'FAILED') setExpanded(true);
}, [status === 'FAILED', setExpanded]);
const timeElapsed = moment
.duration(moment(finishedAt ?? moment()).diff(moment(startedAt)))
.humanize();
return (
<ExpansionPanel
TransitionProps={{ unmountOnExit: true }}
className={
status === 'COMPLETE'
? classes.success
: status === 'FAILED'
? classes.failed
: classes.neutral
}
expanded={expanded}
onChange={(_, newState) => setExpanded(newState)}
>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
IconButtonProps={{
className: classes.button,
}}
>
<Typography variant="button">
{name} ({timeElapsed})
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.expansionPanelDetails}>
{log.length === 0 ? (
'Nothing here...'
) : (
<Suspense fallback={<LinearProgress />}>
<div style={{ height: '20vh', width: '100%' }}>
<LazyLog text={log.join('\n')} extraLines={1} enableSearch />
</div>
</Suspense>
)}
</ExpansionPanelDetails>
</ExpansionPanel>
);
};
@@ -0,0 +1,41 @@
import React from 'react';
import {
Dialog,
LinearProgress,
DialogTitle,
DialogContent,
} from '@material-ui/core';
import { JobStage } from './JobStage';
import { useJob } from './jobMocks';
type Props = {
onClose: () => void;
jobId: string;
};
export const JobStatusModal = ({ onClose, jobId }: Props) => {
const job = useJob(jobId);
return (
<Dialog open onClose={onClose} fullWidth>
<DialogTitle id="responsive-dialog-title">
Creating component...
</DialogTitle>
<DialogContent>
{!job ? (
<LinearProgress />
) : (
(job?.stages ?? []).map(step => (
<JobStage
log={step.log}
name={step.name}
key={step.name}
startedAt={step.startedAt}
finishedAt={step.finishedAt}
status={step.status}
/>
))
)}
</DialogContent>
</Dialog>
);
};
@@ -0,0 +1 @@
export { JobStatusModal } from './JobStatusModal';
@@ -0,0 +1,78 @@
import { useMemo, useState, useEffect } from 'react';
import { Job } from './types';
function* emulatePoll() {
const now = () => new Date().toString();
const job: Job = {
id: '132536-42362-4253532',
metadata: { entity: {}, values: {} },
status: 'STARTED',
stages: [
{
name: 'created',
startedAt: now(),
log: [
'Job id #rw-tstywe-tdsy was successfully created and placed in the queue',
],
status: 'STARTED',
},
],
};
let newTime = now();
job.stages[0].finishedAt = newTime;
job.stages.push({
startedAt: newTime,
name: 'preparing',
log: ['preparing blahblah', 'some other stuuff'],
status: 'COMPLETE',
});
yield job;
newTime = now();
job.stages[1].finishedAt = newTime;
job.stages.push({
startedAt: newTime,
name: 'templating',
log: ['templating blahblah', 'some other stuuff'],
status: 'COMPLETE',
});
yield job;
newTime = now();
job.stages[2].finishedAt = newTime;
job.stages.push({
startedAt: newTime,
name: 'pushing',
log: ['pushing blahblah', 'some other stuuff'],
status: 'STARTED',
});
yield job;
yield job;
job.stages[3].status = 'FAILED';
job.stages[3].log.push('ERROR OCCURED');
while (true) yield job;
}
export const useJob = (jobId: string | null) => {
const apiMock = useMemo(() => emulatePoll(), [jobId]);
const [job, setJob] = useState<Job | void>(undefined);
useEffect(() => {
if (!jobId) return;
const nextJobState = apiMock.next().value as Job;
setJob({ ...nextJobState });
const intervalId = setInterval(() => {
const nextJobState = apiMock.next().value as Job;
if (nextJobState?.status === 'FAILED') {
clearInterval(intervalId);
}
setJob({ ...nextJobState });
}, 3000);
return () => {
clearInterval(intervalId);
};
}, [jobId, setJob]);
return job;
};
@@ -0,0 +1,23 @@
import { Writable } from 'stream';
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
export type Job = {
id: string;
metadata: {
entity: any;
values: any;
};
status: 'PENDING' | 'STARTED' | 'COMPLETE' | 'FAILED';
stages: Stage[];
logStream?: Writable;
logger?: Logger;
error?: Error;
};
export type Stage = {
name: string;
log: string[];
status: 'PENDING' | 'STARTED' | 'COMPLETE' | 'FAILED';
startedAt: string;
finishedAt?: string;
};
@@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { FC } from 'react';
import { Button, Card, Chip, Typography, makeStyles } from '@material-ui/core';
import React from 'react';
import { generatePath } from 'react-router-dom';
import { Button } from '@backstage/core';
import { Card, Chip, Typography, makeStyles } from '@material-ui/core';
import { createTemplateRoute } from '../../routes';
const useStyles = makeStyles(theme => ({
header: {
@@ -42,14 +45,17 @@ type TemplateCardProps = {
tags: string[];
title: string;
type: string;
name: string;
};
const TemplateCard: FC<TemplateCardProps> = ({
const TemplateCard = ({
description,
tags,
title,
type,
}) => {
name,
}: TemplateCardProps) => {
const classes = useStyles();
const href = generatePath(createTemplateRoute.path, { templateName: name });
return (
<Card>
@@ -65,7 +71,9 @@ const TemplateCard: FC<TemplateCardProps> = ({
{description}
</Typography>
<div className={classes.footer}>
<Button color="primary">Choose</Button>
<Button color="primary" variant="contained" to={href}>
Choose
</Button>
</div>
</div>
</Card>
@@ -35,11 +35,11 @@ import {
LinearProgress,
} from '@material-ui/core';
import { Link as RouterLink } from 'react-router-dom';
import TemplateCard from '../TemplateCard';
import TemplateCard from './TemplateCard';
import useStaleWhileRevalidate from 'swr';
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
const ScaffolderPage: React.FC<{}> = () => {
export const ScaffolderPage: React.FC<{}> = () => {
const catalogApi = useApi(catalogApiRef);
const errorApi = useApi(errorApiRef);
@@ -98,6 +98,7 @@ const ScaffolderPage: React.FC<{}> = () => {
<Grid item xs={12} sm={6} md={3}>
<TemplateCard
key={template.metadata.uid}
name={template.metadata.name}
title={`${
(template.metadata.title || template.metadata.name) ?? ''
}`}
@@ -113,5 +114,3 @@ const ScaffolderPage: React.FC<{}> = () => {
</Page>
);
};
export default ScaffolderPage;
+2 -1
View File
@@ -14,4 +14,5 @@
* limitations under the License.
*/
export { plugin, rootRoute } from './plugin';
export { plugin } from './plugin';
export { rootRoute, createTemplateRoute } from './routes';
+5 -8
View File
@@ -14,18 +14,15 @@
* limitations under the License.
*/
import { createPlugin, createRouteRef } from '@backstage/core';
import ScaffolderPage from './components/ScaffolderPage';
export const rootRoute = createRouteRef({
icon: () => null,
path: '/create',
title: 'Create entity',
});
import { createPlugin } from '@backstage/core';
import { ScaffolderPage } from './components/ScaffolderPage';
import { CreatePage } from './components/CreatePage';
import { rootRoute, createTemplateRoute } from './routes';
export const plugin = createPlugin({
id: 'scaffolder',
register({ router }) {
router.addRoute(rootRoute, ScaffolderPage);
router.addRoute(createTemplateRoute, CreatePage);
},
});
+12
View File
@@ -0,0 +1,12 @@
import { createRouteRef } from '@backstage/core';
export const rootRoute = createRouteRef({
icon: () => null,
path: '/create',
title: 'Create new entity',
});
export const createTemplateRoute = createRouteRef({
icon: () => null,
path: '/create/:templateName',
title: 'Entity creation',
});
+155 -7
View File
@@ -876,6 +876,14 @@
core-js "^2.6.5"
regenerator-runtime "^0.13.4"
"@babel/runtime-corejs2@^7.8.7":
version "7.10.3"
resolved "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.10.3.tgz#81bc99a96bfcb6db3f0dcf73fdc577cc554d341b"
integrity sha512-enKvnR/kKFbZFgXYo165wtSA5OeiTlgsnU4jV3vpKRhfWUJjLS6dfVcjIPeRcgJbgEgdgu0I+UyBWqu6c0GumQ==
dependencies:
core-js "^2.6.5"
regenerator-runtime "^0.13.4"
"@babel/runtime-corejs3@^7.10.2":
version "7.10.3"
resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz#931ed6941d3954924a7aa967ee440e60c507b91a"
@@ -2454,6 +2462,28 @@
prop-types "^15.6.1"
react-lifecycles-compat "^3.0.4"
"@rjsf/core@^2.1.0":
version "2.1.0"
resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.1.0.tgz#00130c89959850cc90224fd14c82feaecc2b9dc8"
integrity sha512-2A65RZ3I/RVVNfJUTYUkFs4snX02GHzql6o/KcLBBoG8G8+gr9ZeIi3+JEbe2mOEN02rIPnyJtBkl6C7QajyAw==
dependencies:
"@babel/runtime-corejs2" "^7.8.7"
"@types/json-schema" "^7.0.4"
ajv "^6.7.0"
core-js "^2.5.7"
json-schema-merge-allof "^0.6.0"
jsonpointer "^4.0.1"
lodash "^4.17.15"
prop-types "^15.7.2"
react-app-polyfill "^1.0.4"
react-is "^16.9.0"
shortid "^2.2.14"
"@rjsf/material-ui@^2.1.0":
version "2.1.0"
resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.1.0.tgz#a361b125af3a383b7f671634b8d254345f9f9311"
integrity sha512-9mMttnPNP6GiP7BtZGimYcYsbWwjyviqg/PD8oxrkEtZykULXOIdC3WDMJ3nPSym8RvZsgSnB8bajCpa5iSYQQ==
"@rollup/plugin-commonjs@^13.0.0":
version "13.0.0"
resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec"
@@ -2561,10 +2591,10 @@
eslint-plugin-react "^7.12.4"
eslint-plugin-react-hooks "^4.0.0"
"@spotify/prettier-config@^7.0.0":
version "7.0.0"
resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-7.0.0.tgz#47750979d1282197295108b6958360660a955c16"
integrity sha512-lIMcx/2oDqTtW84iHKkRJe+8U6HK6GPwWH5sJp9UEHcDpdXomOQYvwcGXy2I2zwPQQ14gYYE6nEJuSnnYqsYRw==
"@spotify/prettier-config@^8.0.0":
version "8.0.0"
resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-8.0.0.tgz#8b6c2bd579ddc54887155a0721fe04e96c89f7f2"
integrity sha512-so8w32ZV42CHWxOEXcBtbNO/hLXFrQNXVmhfzhUI6dVB9cq2xjRaiqu8GjFj8LvKbWpPj+S+KwTIS4aDVWqrFQ==
"@spotify/web-scripts-utils@^7.0.0":
version "7.0.0"
@@ -3653,6 +3683,11 @@
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
"@types/json-schema@^7.0.4":
version "7.0.5"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -4519,7 +4554,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.5.5:
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.5.5, ajv@^6.7.0:
version "6.12.2"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
@@ -4810,7 +4845,7 @@ arrify@^1.0.1:
resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
asap@^2.0.0, asap@~2.0.3:
asap@^2.0.0, asap@~2.0.3, asap@~2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@@ -6380,6 +6415,25 @@ compression@^1.7.4:
safe-buffer "5.1.2"
vary "~1.1.2"
compute-gcd@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.0.tgz#fc1ede5b65001e950226502f46543863e4fea10e"
integrity sha1-/B7eW2UAHpUCJlAvRlQ4Y+T+oQ4=
dependencies:
validate.io-array "^1.0.3"
validate.io-function "^1.0.2"
validate.io-integer-array "^1.0.0"
compute-lcm@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.0.tgz#abd96d040b41b0a166f89944b5c8b7c511e21ad5"
integrity sha1-q9ltBAtBsKFm+JlEtci3xRHiGtU=
dependencies:
compute-gcd "^1.2.0"
validate.io-array "^1.0.3"
validate.io-function "^1.0.2"
validate.io-integer-array "^1.0.0"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -6629,7 +6683,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.6.5:
core-js@^2.4.0, core-js@^2.5.7, 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==
@@ -6639,6 +6693,11 @@ core-js@^3.0.1, core-js@^3.0.4:
resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==
core-js@^3.5.0:
version "3.6.5"
resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -11644,6 +11703,22 @@ json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-bet
resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
json-schema-compare@^0.2.2:
version "0.2.2"
resolved "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz#dd601508335a90c7f4cfadb6b2e397225c908e56"
integrity sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==
dependencies:
lodash "^4.17.4"
json-schema-merge-allof@^0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.6.0.tgz#64d48820fec26b228db837475ce3338936bf59a5"
integrity sha512-LEw4VMQVRceOPLuGRWcxW5orTTiR9ZAtqTAe4rQUjNADTeR81bezBVFa0MqIwp0YmHIM1KkhSjZM7o+IQhaPbQ==
dependencies:
compute-lcm "^1.1.0"
json-schema-compare "^0.2.2"
lodash "^4.17.4"
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -11718,6 +11793,11 @@ jsonparse@^1.2.0:
resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
jsonpointer@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk=
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -13087,6 +13167,11 @@ nano-css@^5.2.1:
stacktrace-js "^2.0.0"
stylis "3.5.0"
nanoid@^2.1.0:
version "2.1.11"
resolved "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -15008,6 +15093,13 @@ promise.series@^0.2.0:
resolved "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd"
integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=
promise@^8.0.3:
version "8.1.0"
resolved "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e"
integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==
dependencies:
asap "~2.0.6"
promisify-node@~0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/promisify-node/-/promisify-node-0.3.0.tgz#b4b55acf90faa7d2b8b90ca396899086c03060cf"
@@ -15218,6 +15310,13 @@ raf-schd@^4.0.0:
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"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
dependencies:
performance-now "^2.1.0"
ramda@0.26.1, ramda@^0.26:
version "0.26.1"
resolved "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
@@ -15296,6 +15395,18 @@ rc@^1.2.7, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-app-polyfill@^1.0.4:
version "1.0.6"
resolved "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz#890f8d7f2842ce6073f030b117de9130a5f385f0"
integrity sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g==
dependencies:
core-js "^3.5.0"
object-assign "^4.1.1"
promise "^8.0.3"
raf "^3.4.1"
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"
@@ -16799,6 +16910,13 @@ shellwords@^0.1.1:
resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
shortid@^2.2.14:
version "2.2.15"
resolved "https://registry.npmjs.org/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122"
integrity sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw==
dependencies:
nanoid "^2.1.0"
side-channel@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947"
@@ -18800,6 +18918,36 @@ validate-npm-package-name@^3.0.0:
dependencies:
builtins "^1.0.3"
validate.io-array@^1.0.3:
version "1.0.6"
resolved "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz#5b5a2cafd8f8b85abb2f886ba153f2d93a27774d"
integrity sha1-W1osr9j4uFq7L4hroVPy2Tond00=
validate.io-function@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz#343a19802ed3b1968269c780e558e93411c0bad7"
integrity sha1-NDoZgC7TsZaCaceA5VjpNBHAutc=
validate.io-integer-array@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz#2cabde033293a6bcbe063feafe91eaf46b13a089"
integrity sha1-LKveAzKTpry+Bj/q/pHq9GsToIk=
dependencies:
validate.io-array "^1.0.3"
validate.io-integer "^1.0.4"
validate.io-integer@^1.0.4:
version "1.0.5"
resolved "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz#168496480b95be2247ec443f2233de4f89878068"
integrity sha1-FoSWSAuVviJH7EQ/IjPeT4mHgGg=
dependencies:
validate.io-number "^1.0.3"
validate.io-number@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz#f63ffeda248bf28a67a8d48e0e3b461a1665baf8"
integrity sha1-9j/+2iSL8opnqNSODjtGGhZluvg=
value-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"