move TemplateWizardPage content into separate component

Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
This commit is contained in:
Paul Cowan
2022-12-08 14:46:43 +00:00
parent 2ca355e491
commit 151f659325
7 changed files with 169 additions and 79 deletions
@@ -1,4 +1,3 @@
/* eslint-disable no-console */
/*
* Copyright 2022 The Backstage Authors
*
@@ -16,10 +15,30 @@
*/
import React from 'react';
import { useEntity } from '@backstage/plugin-catalog-react';
import {
TemplateWizardContent,
useGetCustomFields,
} from '@backstage/plugin-scaffolder';
export function SecurityTab(): JSX.Element | null {
const entity = useEntity();
console.log(entity);
return <h1>Security</h1>;
interface SecurityTabProps {
customExtensionsElement: React.ReactNode;
}
export function SecurityTab(props: SecurityTabProps): JSX.Element | null {
// eslint-disable-next-line no-alert
const onComplete = async () => alert('success!!!!');
const onError = (error: Error | undefined) => (
<h2>{error?.message ?? 'Houston we have a problem.'}</h2>
);
const fieldExtensions = useGetCustomFields(props.customExtensionsElement);
return (
<TemplateWizardContent
namespace="default"
templateName="docs-template"
onComplete={onComplete}
onError={onError}
customFieldExtensions={fieldExtensions}
/>
);
}
@@ -52,7 +52,9 @@ export type NextRouterProps = {
*/
export const Router = (props: PropsWithChildren<NextRouterProps>) => {
const { components: { TemplateCardComponent } = {} } = props;
const outlet = useOutlet() || props.children;
const customFieldExtensions =
useCustomFieldExtensions<NextFieldExtensionOptions>(outlet);
const fieldExtensions = [
+1 -1
View File
@@ -13,5 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { Router } from './Router';
export { Router, useGetCustomFields } from './Router';
export type { NextRouterProps } from './Router';
@@ -0,0 +1,112 @@
/*
* Copyright 2022 The Backstage Authors
*
* 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 } from 'react';
import {
Content,
Header,
InfoCard,
MarkdownContent,
Page,
Progress,
} from '@backstage/core-components';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { useTemplateParameterSchema } from '../TemplateWizardPage/TemplateWizardPage';
import { NextFieldExtensionOptions } from '../../extensions';
import type { ErrorTransformer } from '@rjsf/utils';
import type { JsonValue } from '@backstage/types';
import { makeStyles } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import { Stepper } from '../TemplateWizardPage/Stepper';
const useStyles = makeStyles<BackstageTheme>(() => ({
markdown: {
/** to make the styles for React Markdown not leak into the description */
'& :first-child': {
marginTop: 0,
},
'& :last-child': {
marginBottom: 0,
},
},
}));
export interface TemplateWizardContentProps {
namespace: string;
templateName: string;
customFieldExtensions: NextFieldExtensionOptions<any, any>[];
transformErrors?: ErrorTransformer;
onComplete: (values: Record<string, JsonValue>) => Promise<void>;
onError(error: Error | undefined): JSX.Element | null;
}
export const TemplateWizardContent = (
props: TemplateWizardContentProps,
): JSX.Element | null => {
const styles = useStyles();
const templateRef = stringifyEntityRef({
kind: 'Template',
namespace: props.namespace,
name: props.templateName,
});
const errorApi = useApi(errorApiRef);
const { loading, manifest, error } = useTemplateParameterSchema(templateRef);
useEffect(() => {
if (error) {
errorApi.post(new Error(`Failed to load template, ${error}`));
}
}, [error, errorApi]);
if (error) {
return props.onError(error);
}
return (
<Page themeId="website">
<Header
pageTitleOverride="Create a new component"
title="Create a new component"
subtitle="Create new software components using standard templates in your organization"
/>
<Content>
{loading && <Progress />}
{manifest && (
<InfoCard
title={manifest.title}
subheader={
<MarkdownContent
className={styles.markdown}
content={manifest.description ?? 'No description'}
/>
}
noPadding
titleTypographyProps={{ component: 'h2' }}
>
<Stepper
manifest={manifest}
extensions={props.customFieldExtensions}
onComplete={props.onComplete}
transformErrors={props.transformErrors}
/>
</InfoCard>
)}
</Content>
</Page>
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2022 The Backstage Authors
*
* 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 { TemplateWizardContent } from './TemplateWizardContent';
@@ -13,20 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect } from 'react';
import {
Page,
Header,
Content,
Progress,
InfoCard,
MarkdownContent,
} from '@backstage/core-components';
import React from 'react';
import { Navigate, useNavigate } from 'react-router-dom';
import { stringifyEntityRef } from '@backstage/catalog-model';
import {
AnalyticsContext,
errorApiRef,
useApi,
useRouteRef,
useRouteRefParams,
@@ -34,37 +25,21 @@ import {
import {
scaffolderApiRef,
useTemplateSecrets,
} from '@backstage/plugin-scaffolder-react';
import useAsync from 'react-use/lib/useAsync';
import { makeStyles } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
import {
Stepper,
NextFieldExtensionOptions,
} from '@backstage/plugin-scaffolder-react';
import useAsync from 'react-use/lib/useAsync';
import { JsonValue } from '@backstage/types';
import { FormProps } from '../types';
import { nextRouteRef } from '../routes';
import { scaffolderTaskRouteRef, selectedTemplateRouteRef } from '../../routes';
import { TemplateWizardContent } from '../TemplateWizardContent/TemplateWizardContent';
type TemplateWizardPageProps = {
customFieldExtensions: NextFieldExtensionOptions<any, any>[];
FormProps?: FormProps;
};
const useStyles = makeStyles<BackstageTheme>(() => ({
markdown: {
/** to make the styles for React Markdown not leak into the description */
'& :first-child': {
marginTop: 0,
},
'& :last-child': {
marginBottom: 0,
},
},
}));
const useTemplateParameterSchema = (templateRef: string) => {
export const useTemplateParameterSchema = (templateRef: string) => {
const scaffolderApi = useApi(scaffolderApiRef);
const { value, loading, error } = useAsync(
() => scaffolderApi.getTemplateParameterSchema(templateRef),
@@ -75,7 +50,6 @@ const useTemplateParameterSchema = (templateRef: string) => {
};
export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
const styles = useStyles();
const rootRef = useRouteRef(nextRouteRef);
const taskRoute = useRouteRef(scaffolderTaskRouteRef);
const { secrets } = useTemplateSecrets();
@@ -91,9 +65,6 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
name: templateName,
});
const errorApi = useApi(errorApiRef);
const { loading, manifest, error } = useTemplateParameterSchema(templateRef);
const onComplete = async (values: Record<string, JsonValue>) => {
const { taskId } = await scaffolderApi.scaffold({
templateRef,
@@ -104,48 +75,17 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
navigate(taskRoute({ taskId }));
};
useEffect(() => {
if (error) {
errorApi.post(new Error(`Failed to load template, ${error}`));
}
}, [error, errorApi]);
if (error) {
return <Navigate to={rootRef()} />;
}
const onError = () => <Navigate to={rootRef()} />;
return (
<AnalyticsContext attributes={{ entityRef: templateRef }}>
<Page themeId="website">
<Header
pageTitleOverride="Create a new component"
title="Create a new component"
subtitle="Create new software components using standard templates in your organization"
/>
<Content>
{loading && <Progress />}
{manifest && (
<InfoCard
title={manifest.title}
subheader={
<MarkdownContent
className={styles.markdown}
content={manifest.description ?? 'No description'}
/>
}
noPadding
titleTypographyProps={{ component: 'h2' }}
>
<Stepper
manifest={manifest}
extensions={props.customFieldExtensions}
onComplete={onComplete}
FormProps={props.FormProps}
/>
</InfoCard>
)}
</Content>
</Page>
<TemplateWizardContent
namespace={namespace}
templateName={templateName}
onComplete={onComplete}
onError={onError}
customFieldExtensions={props.customFieldExtensions}
/>
</AnalyticsContext>
);
};
+1
View File
@@ -18,3 +18,4 @@ export * from './TemplateListPage';
export * from './TemplateWizardPage';
export * from './types';
export * from './routes';
export * from './TemplateWizardContent';