chore: more moving out into the other packages

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-12-22 16:53:34 +01:00
parent 5f0fadc610
commit 33f069c805
47 changed files with 139 additions and 96 deletions
+4 -1
View File
@@ -38,6 +38,8 @@
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/theme": "workspace:^",
"@backstage/types": "workspace:^",
"@backstage/version-bridge": "workspace:^",
@@ -56,6 +58,7 @@
"json-schema-library": "^7.3.9",
"lodash": "^4.17.21",
"qs": "^6.9.4",
"zen-observable": "^0.10.0",
"zod": "~3.18.0",
"zod-to-json-schema": "~3.18.0"
},
@@ -67,8 +70,8 @@
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/plugin-catalog": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
@@ -15,12 +15,6 @@
*/
import { ApiHolder } from '@backstage/core-plugin-api';
import { FieldValidation, FieldProps } from '@rjsf/core';
import {
UIOptionsType,
FieldProps as FieldPropsV5,
UiSchema as UiSchemaV5,
FieldValidation as FieldValidationV5,
} from '@rjsf/utils';
import { JSONSchema7 } from 'json-schema';
/**
+1 -1
View File
@@ -16,5 +16,5 @@
export * from './routes';
export * from './extensions';
export * from './next';
export * from './types';
@@ -17,7 +17,7 @@
import React from 'react';
import { ReviewState } from './ReviewState';
import { render } from '@testing-library/react';
import { ParsedTemplateSchema } from './useTemplateSchema';
import { ParsedTemplateSchema } from '../../hooks/useTemplateSchema';
describe('ReviewState', () => {
it('should render the text as normal with no options', () => {
@@ -16,14 +16,18 @@
import React from 'react';
import { StructuredMetadataTable } from '@backstage/core-components';
import { JsonObject } from '@backstage/types';
import { ParsedTemplateSchema } from './useTemplateSchema';
import { Draft07 as JSONSchema } from 'json-schema-library';
import { ParsedTemplateSchema } from '../../hooks/useTemplateSchema';
interface ReviewStateProps {
schemas: ParsedTemplateSchema[];
formState: JsonObject;
}
/**
* The ReviewState component used by the Stepper
* @alpha
*/
export const ReviewState = (props: ReviewStateProps) => {
const reviewData = Object.fromEntries(
Object.entries(props.formState).map(([key, value]) => {
@@ -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 { ReviewState } from './ReviewState';
@@ -33,7 +33,7 @@ import { NextFieldExtensionOptions } from '../../extensions';
import { TemplateParameterSchema } from '../../../types';
import { createAsyncValidators } from './createAsyncValidators';
import { useTemplateSchema } from '../../hooks/useTemplateSchema';
import { ReviewState } from './ReviewState';
import { ReviewState } from '../ReviewState';
import validator from '@rjsf/validator-ajv6';
import { selectedTemplateRouteRef } from '../../../routes';
import { useFormData } from '../../hooks/useFormData';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { JsonObject } from '@backstage/types';
import { NextCustomFieldValidator } from '../../../extensions';
import { NextCustomFieldValidator } from '../../extensions';
import { createAsyncValidators } from './createAsyncValidators';
describe('createAsyncValidators', () => {
@@ -17,9 +17,9 @@
import { FieldValidation } from '@rjsf/utils';
import { JsonObject } from '@backstage/types';
import { ApiHolder } from '@backstage/core-plugin-api';
import { NextCustomFieldValidator } from '../../../extensions';
import { Draft07 as JSONSchema } from 'json-schema-library';
import { createFieldValidation } from './schema';
import { createFieldValidation } from '../../lib';
import { NextCustomFieldValidator } from '../../extensions';
export const createAsyncValidators = (
rootSchema: JsonObject,
@@ -0,0 +1,188 @@
/*
* 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 from 'react';
import { fireEvent, render } from '@testing-library/react';
import { CardHeader } from './CardHeader';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import {
MockStorageApi,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
import { starredEntitiesApiRef } from '@backstage/plugin-catalog-react';
import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
import Observable from 'zen-observable';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
describe('CardHeader', () => {
it('should select the correct theme from the theme provider from the header', () => {
// Can't really test what we want here.
// But we can check that we call the getPage theme with the right type of template at least.
const mockTheme = {
...lightTheme,
getPageTheme: jest.fn(lightTheme.getPageTheme),
};
render(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<ThemeProvider theme={mockTheme}>
<CardHeader
template={{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob' },
spec: {
steps: [],
type: 'service',
},
}}
/>
</ThemeProvider>
</TestApiProvider>,
);
expect(mockTheme.getPageTheme).toHaveBeenCalledWith({ themeId: 'service' });
});
it('should render the type', async () => {
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<CardHeader
template={{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob' },
spec: {
steps: [],
type: 'service',
},
}}
/>
</TestApiProvider>,
);
expect(getByText('service')).toBeInTheDocument();
});
it('should enable favoriting of the entity', async () => {
const starredEntitiesApi = {
starredEntitie$: () => new Observable(() => {}),
toggleStarred: jest.fn(async () => {}),
};
const mockTemplate: TemplateEntityV1beta3 = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob' },
spec: {
steps: [],
type: 'service',
},
};
const { getByRole } = await renderInTestApp(
<TestApiProvider apis={[[starredEntitiesApiRef, starredEntitiesApi]]}>
<CardHeader template={mockTemplate} />
</TestApiProvider>,
);
const favorite = getByRole('button', { name: 'favorite' });
await fireEvent.click(favorite);
expect(starredEntitiesApi.toggleStarred).toHaveBeenCalledWith(
stringifyEntityRef(mockTemplate),
);
});
it('should render the name of the entity', async () => {
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<CardHeader
template={{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob' },
spec: {
steps: [],
type: 'service',
},
}}
/>
</TestApiProvider>,
);
expect(getByText('bob')).toBeInTheDocument();
});
it('should render the title of the entity in favor of the name if it is provided', async () => {
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<CardHeader
template={{
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob', title: 'Iamtitle' },
spec: {
steps: [],
type: 'service',
},
}}
/>
</TestApiProvider>,
);
expect(getByText('Iamtitle')).toBeInTheDocument();
});
});
@@ -0,0 +1,76 @@
/*
* 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 from 'react';
import { makeStyles, useTheme } from '@material-ui/core';
import { ItemCardHeader } from '@backstage/core-components';
import { BackstageTheme } from '@backstage/theme';
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import { FavoriteEntity } from '@backstage/plugin-catalog-react';
const useStyles = makeStyles<BackstageTheme, { cardBackgroundImage: string }>(
() => ({
header: {
backgroundImage: ({ cardBackgroundImage }) => cardBackgroundImage,
},
subtitleWrapper: {
display: 'flex',
justifyContent: 'space-between',
},
}),
);
/**
* Props for the CardHeader component
*/
export interface CardHeaderProps {
template: TemplateEntityV1beta3;
}
/**
* The Card Header with the background for the TemplateCard.
*/
export const CardHeader = (props: CardHeaderProps) => {
const {
template: {
metadata: { title, name },
spec: { type },
},
} = props;
const { getPageTheme } = useTheme<BackstageTheme>();
const themeForType = getPageTheme({ themeId: type });
const styles = useStyles({
cardBackgroundImage: themeForType.backgroundImage,
});
const SubtitleComponent = (
<div className={styles.subtitleWrapper}>
<div>{type}</div>
<div>
<FavoriteEntity entity={props.template} style={{ padding: 0 }} />
</div>
</div>
);
return (
<ItemCardHeader
title={title ?? name}
subtitle={SubtitleComponent}
classes={{ root: styles.header }}
/>
);
};
@@ -0,0 +1,46 @@
/*
* 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 { IconComponent } from '@backstage/core-plugin-api';
import { BackstageTheme } from '@backstage/theme';
import { Link } from '@backstage/core-components';
import { makeStyles } from '@material-ui/core';
import React from 'react';
interface CardLinkProps {
icon: IconComponent;
text: string;
url: string;
}
const useStyles = makeStyles<BackstageTheme>(() => ({
linkText: {
display: 'inline-flex',
alignItems: 'center',
},
}));
export const CardLink = ({ icon: Icon, text, url }: CardLinkProps) => {
const styles = useStyles();
return (
<div className={styles.linkText}>
<Icon fontSize="small" />
<Link style={{ marginLeft: '8px' }} to={url}>
{text || url}
</Link>
</div>
);
};
@@ -0,0 +1,239 @@
/*
* 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 { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
import {
entityRouteRef,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
import {
MockStorageApi,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
import { TemplateCard } from './TemplateCard';
import React from 'react';
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
import { nextRouteRef } from '../../../routes';
describe('TemplateCard', () => {
it('should render the card title', async () => {
const mockTemplate: TemplateEntityV1beta3 = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob' },
spec: {
steps: [],
type: 'service',
},
};
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<TemplateCard template={mockTemplate} />
</TestApiProvider>,
{ mountedRoutes: { '/': nextRouteRef } },
);
expect(getByText('bob')).toBeInTheDocument();
});
it('should render the description as markdown', async () => {
const mockTemplate: TemplateEntityV1beta3 = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob', description: 'hello **test**' },
spec: {
steps: [],
type: 'service',
},
};
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<TemplateCard template={mockTemplate} />
</TestApiProvider>,
{ mountedRoutes: { '/': nextRouteRef } },
);
const description = getByText('hello');
expect(description.querySelector('strong')).toBeInTheDocument();
});
it('should render no descroption if none is provided through the template', async () => {
const mockTemplate: TemplateEntityV1beta3 = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob' },
spec: {
steps: [],
type: 'service',
},
};
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<TemplateCard template={mockTemplate} />
</TestApiProvider>,
{ mountedRoutes: { '/': nextRouteRef } },
);
expect(getByText('No description')).toBeInTheDocument();
});
it('should render the tags', async () => {
const mockTemplate: TemplateEntityV1beta3 = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob', tags: ['cpp', 'react'] },
spec: {
steps: [],
type: 'service',
},
};
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<TemplateCard template={mockTemplate} />
</TestApiProvider>,
{ mountedRoutes: { '/': nextRouteRef } },
);
for (const tag of mockTemplate.metadata.tags!) {
expect(getByText(tag)).toBeInTheDocument();
}
});
it('should render a link to the owner', async () => {
const mockTemplate: TemplateEntityV1beta3 = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob', tags: ['cpp', 'react'] },
spec: {
steps: [],
type: 'service',
},
relations: [
{
targetRef: 'group:default/my-test-user',
type: RELATION_OWNED_BY,
},
],
};
const { getByRole } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<TemplateCard template={mockTemplate} />
</TestApiProvider>,
{
mountedRoutes: {
'/': nextRouteRef,
'/catalog/:kind/:namespace/:name': entityRouteRef,
},
},
);
expect(getByRole('link', { name: 'my-test-user' })).toBeInTheDocument();
expect(getByRole('link', { name: 'my-test-user' })).toHaveAttribute(
'href',
'/catalog/group/default/my-test-user',
);
});
it('should render the choose button to navigate to the selected template', async () => {
const mockTemplate: TemplateEntityV1beta3 = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: { name: 'bob', tags: ['cpp', 'react'] },
spec: {
steps: [],
type: 'service',
},
};
const { getByRole } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<TemplateCard template={mockTemplate} />
</TestApiProvider>,
{
mountedRoutes: {
'/': nextRouteRef,
'/catalog/:kind/:namespace/:name': entityRouteRef,
},
},
);
expect(getByRole('button', { name: 'Choose' })).toBeInTheDocument();
expect(getByRole('button', { name: 'Choose' })).toHaveAttribute(
'href',
'/templates/default/bob',
);
});
});
@@ -0,0 +1,214 @@
/*
* 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 {
DEFAULT_NAMESPACE,
parseEntityRef,
RELATION_OWNED_BY,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Button, MarkdownContent, UserIcon } from '@backstage/core-components';
import { IconComponent, useApp, useRouteRef } from '@backstage/core-plugin-api';
import {
EntityRefLinks,
getEntityRelations,
} from '@backstage/plugin-catalog-react';
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import { BackstageTheme } from '@backstage/theme';
import {
Box,
Card,
CardActions,
CardContent,
Chip,
Divider,
Grid,
makeStyles,
} from '@material-ui/core';
import LanguageIcon from '@material-ui/icons/Language';
import React from 'react';
import {
nextSelectedTemplateRouteRef,
viewTechDocRouteRef,
} from '../../../routes';
import { CardHeader } from './CardHeader';
import { CardLink } from './CardLink';
const useStyles = makeStyles<BackstageTheme>(theme => ({
box: {
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
'-webkit-line-clamp': 10,
'-webkit-box-orient': 'vertical',
},
markdown: {
/** to make the styles for React Markdown not leak into the description */
'& :first-child': {
margin: 0,
},
},
label: {
color: theme.palette.text.secondary,
textTransform: 'uppercase',
fontWeight: 'bold',
letterSpacing: 0.5,
lineHeight: 1,
fontSize: '0.75rem',
},
footer: {
display: 'flex',
justifyContent: 'space-between',
flex: 1,
alignItems: 'center',
},
ownedBy: {
display: 'flex',
alignItems: 'center',
flex: 1,
color: theme.palette.link,
},
}));
/**
* The Props for the Template Card component
* @alpha
*/
export interface TemplateCardProps {
template: TemplateEntityV1beta3;
deprecated?: boolean;
}
/**
* The Template Card component that is rendered in a list for each template
* @alpha
*/
export const TemplateCard = (props: TemplateCardProps) => {
const { template } = props;
const styles = useStyles();
const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
const templateRoute = useRouteRef(nextSelectedTemplateRouteRef);
const { name, namespace } = parseEntityRef(
stringifyEntityRef(props.template),
);
const href = templateRoute({
templateName: name,
namespace: namespace,
});
const app = useApp();
const iconResolver = (key?: string): IconComponent =>
key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon;
// TechDocs Link
const viewTechDoc = useRouteRef(viewTechDocRouteRef);
const viewTechDocsAnnotation =
template.metadata.annotations?.['backstage.io/techdocs-ref'];
const viewTechDocsLink =
!!viewTechDocsAnnotation &&
!!viewTechDoc &&
viewTechDoc({
namespace: template.metadata.namespace || DEFAULT_NAMESPACE,
kind: template.kind,
name: template.metadata.name,
});
return (
<Card>
<CardHeader template={template} />
<CardContent>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box className={styles.box}>
<MarkdownContent
className={styles.markdown}
content={template.metadata.description ?? 'No description'}
/>
</Box>
</Grid>
{(template.metadata.tags?.length ?? 0) > 0 && (
<>
<Grid item xs={12}>
<Divider />
</Grid>
<Grid item xs={12}>
<Grid container spacing={2}>
{template.metadata.tags?.map(tag => (
<Grid item>
<Chip
style={{ margin: 0 }}
size="small"
label={tag}
key={tag}
/>
</Grid>
))}
</Grid>
</Grid>
</>
)}
{(!!viewTechDocsLink || template.metadata.links?.length) && (
<>
<Grid item xs={12}>
<Divider />
</Grid>
<Grid item xs={12}>
<Grid container spacing={2}>
{viewTechDocsLink && (
<Grid className={styles.linkText} item xs={6}>
<CardLink
icon={iconResolver('docs')}
text="View TechDocs"
url={viewTechDocsLink}
/>
</Grid>
)}
{template.metadata.links?.map(({ url, icon, title }) => (
<Grid className={styles.linkText} item xs={6}>
<CardLink
icon={iconResolver(icon)}
text={title || url}
url={url}
/>
</Grid>
))}
</Grid>
</Grid>
</>
)}
</Grid>
</CardContent>
<CardActions style={{ padding: '16px' }}>
<div className={styles.footer}>
<div className={styles.ownedBy}>
{ownedByRelations.length > 0 && (
<>
<UserIcon fontSize="small" />
<EntityRefLinks
style={{ marginLeft: '8px' }}
entityRefs={ownedByRelations}
defaultKind="Group"
/>
</>
)}
</div>
<Button size="small" variant="outlined" color="primary" to={href}>
Choose
</Button>
</div>
</CardActions>
</Card>
);
};
@@ -0,0 +1,17 @@
/*
* 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 { TemplateCard } from './TemplateCard';
export type { TemplateCardProps } from './TemplateCard';
@@ -13,4 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { Stepper } from './Stepper';
export * from './Stepper';
export * from './TemplateCard';
export * from './ReviewState';
@@ -21,10 +21,7 @@ import {
} from './types';
import { Extension, attachComponentData } from '@backstage/core-plugin-api';
import { UIOptionsType } from '@rjsf/utils';
import { FieldExtensionComponent } from '../../extensions';
export const FIELD_EXTENSION_WRAPPER_KEY = 'scaffolder.extensions.wrapper.v1';
export const FIELD_EXTENSION_KEY = 'scaffolder.extensions.field.v1';
import { FieldExtensionComponent, FIELD_EXTENSION_KEY } from '../../extensions';
/**
* Method for creating field extensions that can be used in the scaffolder
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TemplateParameterSchema } from '../../../types';
import { useTemplateSchema } from './useTemplateSchema';
import { renderHook } from '@testing-library/react-hooks';
import { TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { featureFlagsApiRef } from '@backstage/core-plugin-api';
import { TemplateParameterSchema } from '../../types';
describe('useTemplateSchema', () => {
it('should generate the correct schema', () => {
@@ -16,8 +16,8 @@
import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api';
import { JsonObject } from '@backstage/types';
import { UiSchema } from '@rjsf/utils';
import { TemplateParameterSchema } from '../../../types';
import { extractSchemaFromStep } from './schema';
import { TemplateParameterSchema } from '../../types';
import { extractSchemaFromStep } from '../lib';
export interface ParsedTemplateSchema {
uiSchema: UiSchema;
@@ -26,6 +26,7 @@ export interface ParsedTemplateSchema {
title: string;
description?: string;
}
export const useTemplateSchema = (
manifest: TemplateParameterSchema,
): { steps: ParsedTemplateSchema[] } => {
@@ -15,3 +15,5 @@
*/
export * from './components';
export * from './extensions';
export * from './types';
export * from './lib';
@@ -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 { extractSchemaFromStep, createFieldValidation } from './schema';
@@ -0,0 +1,26 @@
/*
* 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 type { FormProps as SchemaFormProps } from '@rjsf/core-v5';
/**
* Any `@rjsf/core` form properties that are publicly exposed to the `NextScaffolderpage`
*
* @alpha
*/
export type FormProps = Pick<
SchemaFormProps,
'transformErrors' | 'noHtml5Validate'
>;
+18
View File
@@ -13,3 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { JsonObject } from '@backstage/types';
/**
* The shape of each entry of parameters which gets rendered
* as a separate step in the wizard input
*
* @public
*/
export type TemplateParameterSchema = {
title: string;
description?: string;
steps: Array<{
title: string;
description?: string;
schema: JsonObject;
}>;
};