fix(scaffolder): typefixes
This commit is contained in:
@@ -18,5 +18,5 @@ export * from './entity';
|
||||
export { EntityPolicies } from './EntityPolicies';
|
||||
export * from './kinds';
|
||||
export * from './location';
|
||||
export type { EntityPolicy } from './types';
|
||||
export type { EntityPolicy, JSONSchema } from './types';
|
||||
export * from './validation';
|
||||
|
||||
@@ -39,6 +39,22 @@ describe('JobProcessor', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: './template',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -50,6 +50,22 @@ describe('GitHubPreparer', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: './template',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -41,6 +41,22 @@ describe('Helpers', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: './template',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -72,6 +88,22 @@ describe('Helpers', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: './template',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -101,6 +133,22 @@ describe('Helpers', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: './template',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -131,6 +179,22 @@ describe('Helpers', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: './template',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -159,6 +223,22 @@ describe('Helpers', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: './template',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -37,6 +37,22 @@ describe('Preparers', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: '.',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
it('should throw an error when the preparer for the source location is not registered', () => {
|
||||
@@ -74,6 +90,22 @@ describe('Preparers', () => {
|
||||
spec: {
|
||||
type: 'cookiecutter',
|
||||
path: '.',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -14,22 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import Router from 'express-promise-router';
|
||||
import express from 'express';
|
||||
import {
|
||||
PreparerBuilder,
|
||||
TemplaterBase,
|
||||
JobProcessor,
|
||||
RequiredTemplateValues,
|
||||
} from '../scaffolder';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import Docker from 'dockerode';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { StageContext } from '../scaffolder/jobs/types';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { GithubStorer } from '../scaffolder/stages/store/github';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import Docker from 'dockerode';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
JobProcessor,
|
||||
PreparerBuilder,
|
||||
RequiredTemplateValues,
|
||||
TemplaterBase,
|
||||
} from '../scaffolder';
|
||||
import { StageContext } from '../scaffolder/jobs/types';
|
||||
import { GithubStorer } from '../scaffolder/stages/store/github';
|
||||
|
||||
export interface RouterOptions {
|
||||
preparers: PreparerBuilder;
|
||||
templater: TemplaterBase;
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
Header,
|
||||
Lifecycle,
|
||||
InfoCard,
|
||||
errorApiRef,
|
||||
} from '@backstage/core';
|
||||
import {
|
||||
TemplateEntityV1alpha1,
|
||||
@@ -36,6 +37,7 @@ import { scaffolderApiRef } from '../../api';
|
||||
import { MultistepJsonForm } from '../MultistepJsonForm';
|
||||
import { Job } from '../JobStatusModal/types';
|
||||
export const CreatePage = () => {
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
const { templateName } = useParams();
|
||||
@@ -68,16 +70,24 @@ export const CreatePage = () => {
|
||||
null,
|
||||
);
|
||||
const handleCreateComplete = async (job: Job) => {
|
||||
const componentYaml = job.metadata.remoteUrl?.replace(
|
||||
/\.git$/,
|
||||
'/blob/master/component-info.yaml',
|
||||
);
|
||||
if (!componentYaml) {
|
||||
errorApi.post(
|
||||
new Error(
|
||||
`Failed to find component-info.yaml file in ${job.metadata.remoteUrl}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
entities: [createdEntity],
|
||||
} = await catalogApi.addLocation(
|
||||
'github',
|
||||
job.metadata.remoteUrl.replace(
|
||||
/\.git$/,
|
||||
'/blob/master/component-info.yaml',
|
||||
),
|
||||
);
|
||||
setEntity(createdEntity);
|
||||
} = await catalogApi.addLocation('github', componentYaml);
|
||||
|
||||
setEntity(createdEntity as ComponentEntityV1alpha1);
|
||||
};
|
||||
|
||||
if (!template && isValidating) return <LinearProgress />;
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
import { Writable } from 'stream';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export type Job = {
|
||||
id: string;
|
||||
metadata: {
|
||||
entity: any;
|
||||
values: any;
|
||||
remoteUrl?: string;
|
||||
};
|
||||
status: 'PENDING' | 'STARTED' | 'COMPLETE' | 'FAILED';
|
||||
status: 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED';
|
||||
stages: Stage[];
|
||||
logStream?: Writable;
|
||||
logger?: Logger;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
export type Stage = {
|
||||
name: string;
|
||||
log: string[];
|
||||
status: 'PENDING' | 'STARTED' | 'COMPLETE' | 'FAILED';
|
||||
status: 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED';
|
||||
startedAt: string;
|
||||
finishedAt?: string;
|
||||
endedAt?: string;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
/*
|
||||
* 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 { useState, useEffect } from 'react';
|
||||
import { Job } from './types';
|
||||
import { useApi } from '@backstage/core';
|
||||
import { scaffolderApiRef } from '../../api';
|
||||
|
||||
const DEFAULT_POLLING_INTERVAL = 1000;
|
||||
const poll = (thunk: () => Promise<void>, ms: number) => {
|
||||
let shouldStop = false;
|
||||
(async () => {
|
||||
@@ -17,12 +33,15 @@ const poll = (thunk: () => Promise<void>, ms: number) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useJobPolling = (jobId: string | null) => {
|
||||
export const useJobPolling = (
|
||||
jobId: string | null,
|
||||
pollingInterval = DEFAULT_POLLING_INTERVAL,
|
||||
) => {
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
const [job, setJob] = useState<Job | null>(null);
|
||||
|
||||
const [job, setJob] = useState<Job | void>(undefined);
|
||||
useEffect(() => {
|
||||
if (!jobId) return;
|
||||
if (!jobId) return () => {};
|
||||
const stopPolling = poll(async () => {
|
||||
const nextJobState = await scaffolderApi.getJob(jobId);
|
||||
if (
|
||||
@@ -32,10 +51,11 @@ export const useJobPolling = (jobId: string | null) => {
|
||||
stopPolling();
|
||||
}
|
||||
setJob(nextJobState);
|
||||
}, 500);
|
||||
}, pollingInterval);
|
||||
return () => {
|
||||
stopPolling();
|
||||
};
|
||||
}, [jobId, setJob]);
|
||||
}, [jobId, setJob, scaffolderApi, pollingInterval]);
|
||||
|
||||
return job;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { withTheme, FormProps, IChangeEvent } from '@rjsf/core';
|
||||
import { withTheme, IChangeEvent, FormProps } from '@rjsf/core';
|
||||
import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import {
|
||||
Stepper,
|
||||
@@ -12,10 +27,11 @@ import {
|
||||
Box,
|
||||
} from '@material-ui/core';
|
||||
import { Content, StructuredMetadataTable } from '@backstage/core';
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
|
||||
const Form = withTheme(MuiTheme);
|
||||
type Step = {
|
||||
schema: FormProps<any>['schema'];
|
||||
schema: JSONSchema;
|
||||
label: string;
|
||||
};
|
||||
type Props = {
|
||||
@@ -52,7 +68,7 @@ export const MultistepJsonForm = ({
|
||||
noHtml5Validate
|
||||
formData={formData}
|
||||
onChange={onChange}
|
||||
schema={schema}
|
||||
schema={schema as FormProps<any>['schema']}
|
||||
onSubmit={e => {
|
||||
if (e.errors.length === 0) handleNext();
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user