feat(scaffolder): working end-to-end flow with hardcoded params
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: {{cookiecutter.component_id}}
|
||||
description: {{cookiecutter.description}}
|
||||
spec:
|
||||
type: website
|
||||
lifecycle: experimental
|
||||
owner: {{cookiecutter.owner}}
|
||||
@@ -20,7 +20,6 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { RequiredTemplateValues } from '../templater';
|
||||
import { Repository, Remote, Signature, Cred } from 'nodegit';
|
||||
import gitUrlParse from 'git-url-parse';
|
||||
|
||||
export class GithubStorer implements Storer {
|
||||
private client: Octokit;
|
||||
|
||||
@@ -94,8 +94,9 @@ export async function createRouter(
|
||||
entity: template,
|
||||
values: {
|
||||
component_id: `blob${Date.now()}`,
|
||||
org: 'hojden',
|
||||
org: 'shmidt-i-test',
|
||||
description: 'test',
|
||||
owner: 'somebody',
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
|
||||
@@ -16,26 +16,25 @@
|
||||
import React, { useState } from 'react';
|
||||
import useStaleWhileRevalidate from 'swr';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { LinearProgress, Button } from '@material-ui/core';
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
useApi,
|
||||
SimpleStepper,
|
||||
SimpleStepperStep,
|
||||
Page,
|
||||
Content,
|
||||
ContentHeader,
|
||||
Header,
|
||||
Lifecycle,
|
||||
InfoCard,
|
||||
} from '@backstage/core';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { withTheme, IChangeEvent } from '@rjsf/core';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import {
|
||||
TemplateEntityV1alpha1,
|
||||
ComponentEntityV1alpha1,
|
||||
} from '@backstage/catalog-model';
|
||||
import { IChangeEvent } from '@rjsf/core';
|
||||
import { JobStatusModal } from '../JobStatusModal';
|
||||
import { scaffolderApiRef } from '../../api';
|
||||
|
||||
const Form = withTheme(MuiTheme);
|
||||
|
||||
import { MultistepJsonForm } from '../MultistepJsonForm';
|
||||
import { Job } from '../JobStatusModal/types';
|
||||
export const CreatePage = () => {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
@@ -53,6 +52,7 @@ export const CreatePage = () => {
|
||||
);
|
||||
const [formState, setFormState] = useState({});
|
||||
|
||||
const handleFormReset = () => setFormState({});
|
||||
const handleChange = (e: IChangeEvent) =>
|
||||
setFormState({ ...formState, ...e.formData });
|
||||
|
||||
@@ -67,6 +67,22 @@ export const CreatePage = () => {
|
||||
setJobId(job);
|
||||
};
|
||||
|
||||
const [entity, setEntity] = React.useState<ComponentEntityV1alpha1 | null>(
|
||||
null,
|
||||
);
|
||||
const handleCreateComplete = async (job: Job) => {
|
||||
console.log('DEBUG:', { job });
|
||||
const {
|
||||
entities: [createdEntity],
|
||||
} = await catalogApi.addLocation(
|
||||
'github',
|
||||
job.metadata.remoteUrl.replace(
|
||||
/\.git$/,
|
||||
'/blob/master/component-info.yaml',
|
||||
),
|
||||
);
|
||||
setEntity(createdEntity);
|
||||
};
|
||||
return (
|
||||
<Page>
|
||||
<Header
|
||||
@@ -79,47 +95,47 @@ export const CreatePage = () => {
|
||||
subtitle="Create new software components using standard templates"
|
||||
/>
|
||||
<Content>
|
||||
<ContentHeader title={template.metadata.title as string} />
|
||||
{jobId && <JobStatusModal jobId={jobId} onClose={handleClose} />}
|
||||
{/* <JSSONFormsStepper schemas={} */}
|
||||
<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#',
|
||||
...template?.spec?.schema,
|
||||
}}
|
||||
>
|
||||
<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',
|
||||
{jobId && (
|
||||
<JobStatusModal
|
||||
onComplete={handleCreateComplete}
|
||||
jobId={jobId}
|
||||
onClose={handleClose}
|
||||
entity={entity}
|
||||
/>
|
||||
)}
|
||||
<InfoCard title={template.metadata.title as string} noPadding>
|
||||
<MultistepJsonForm
|
||||
formData={formState}
|
||||
onChange={handleChange}
|
||||
onReset={handleFormReset}
|
||||
onFinish={handleCreate}
|
||||
steps={[
|
||||
{
|
||||
label: 'Fill in template parameters',
|
||||
schema: template.spec.schema,
|
||||
},
|
||||
{
|
||||
label: 'Choose owner and repo',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['repo', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
title: 'GitHub repository',
|
||||
description: 'Repo where to upload created component',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button hidden />
|
||||
</Form>
|
||||
</SimpleStepperStep>
|
||||
</SimpleStepper>
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</InfoCard>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
LinearProgress,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@material-ui/core';
|
||||
import { JobStage } from './JobStage';
|
||||
import { useJobPolling } from './useJobPolling';
|
||||
|
||||
import { Job } from './types';
|
||||
import { ComponentEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { Button } from '@backstage/core';
|
||||
import { entityRoute } from '@backstage/plugin-catalog';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
type Props = {
|
||||
onClose: () => void;
|
||||
onComplete: (job: Job) => void;
|
||||
jobId: string;
|
||||
entity: ComponentEntityV1alpha1 | null;
|
||||
};
|
||||
|
||||
export const JobStatusModal = ({ onClose, jobId }: Props) => {
|
||||
console.log({ jobId });
|
||||
export const JobStatusModal = ({
|
||||
onClose,
|
||||
jobId,
|
||||
onComplete,
|
||||
entity,
|
||||
}: Props) => {
|
||||
const job = useJobPolling(jobId);
|
||||
|
||||
useEffect(() => {
|
||||
if (job?.status === 'COMPLETED') onComplete(job as Job);
|
||||
}, [job]);
|
||||
return (
|
||||
<Dialog open onClose={onClose} fullWidth>
|
||||
<DialogTitle id="responsive-dialog-title">
|
||||
@@ -37,6 +52,23 @@ export const JobStatusModal = ({ onClose, jobId }: Props) => {
|
||||
))
|
||||
)}
|
||||
</DialogContent>
|
||||
{entity && (
|
||||
<DialogActions>
|
||||
<Button
|
||||
to={generatePath(entityRoute.path, {
|
||||
kind: entity.kind,
|
||||
optionalNamespaceAndName: [
|
||||
entity.metadata.namespace,
|
||||
entity.metadata.name,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(':'),
|
||||
})}
|
||||
>
|
||||
View in catalog
|
||||
</Button>
|
||||
</DialogActions>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import React, { useState } from 'react';
|
||||
import { withTheme, FormProps, IChangeEvent } from '@rjsf/core';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import {
|
||||
Stepper,
|
||||
Step,
|
||||
StepLabel,
|
||||
StepContent,
|
||||
Button,
|
||||
Paper,
|
||||
Typography,
|
||||
Box,
|
||||
} from '@material-ui/core';
|
||||
import { Content, StructuredMetadataTable } from '@backstage/core';
|
||||
|
||||
const Form = withTheme(MuiTheme);
|
||||
type Step = {
|
||||
schema: FormProps<any>['schema'];
|
||||
label: string;
|
||||
};
|
||||
type Props = {
|
||||
steps: Step[];
|
||||
formData: Record<string, any>;
|
||||
onChange: (e: IChangeEvent) => void;
|
||||
onReset: () => void;
|
||||
onFinish: () => void;
|
||||
};
|
||||
export const MultistepJsonForm = ({
|
||||
steps,
|
||||
formData,
|
||||
onChange,
|
||||
onReset,
|
||||
onFinish,
|
||||
}: Props) => {
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
|
||||
const handleReset = () => {
|
||||
setActiveStep(0);
|
||||
onReset();
|
||||
};
|
||||
const handleNext = () =>
|
||||
setActiveStep(Math.min(activeStep + 1, steps.length));
|
||||
const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0));
|
||||
return (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
{steps.map(({ label, schema }) => (
|
||||
<Step key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
<StepContent>
|
||||
<Form
|
||||
noHtml5Validate
|
||||
formData={formData}
|
||||
onChange={onChange}
|
||||
schema={schema}
|
||||
onSubmit={e => {
|
||||
if (e.errors.length === 0) handleNext();
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<Button disabled={activeStep === 0} onClick={handleBack}>
|
||||
Back
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" type="submit">
|
||||
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</StepContent>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
{activeStep === steps.length && (
|
||||
<Content>
|
||||
<Paper square elevation={0}>
|
||||
<Typography variant="h6">Review and create</Typography>
|
||||
<StructuredMetadataTable dense metadata={formData} />
|
||||
<Box mb={4} />
|
||||
<Button onClick={handleBack}>Back</Button>
|
||||
<Button onClick={handleReset}>Reset</Button>
|
||||
<Button variant="contained" color="primary" onClick={onFinish}>
|
||||
Create
|
||||
</Button>
|
||||
</Paper>
|
||||
</Content>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { MultistepJsonForm } from './MultistepJsonForm';
|
||||
Reference in New Issue
Block a user