fix(scaffolder): typefixes

This commit is contained in:
Ivan Shmidt
2020-07-01 19:15:06 +02:00
parent 34399824a6
commit 227a4af561
10 changed files with 240 additions and 39 deletions
+1 -1
View File
@@ -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();
}}