Merge pull request #9806 from backstage/blam/scaffolder/deprecations

🧹 some more `scaffolder` deprecations
This commit is contained in:
Ben Lambert
2022-02-25 18:21:35 +01:00
committed by GitHub
17 changed files with 202 additions and 87 deletions
+40 -27
View File
@@ -20,9 +20,9 @@ import { FieldValidation } from '@rjsf/core';
import { IconButton } from '@material-ui/core';
import { JsonObject } from '@backstage/types';
import { JSONSchema7 } from 'json-schema';
import { JsonValue } from '@backstage/types';
import { Observable } from '@backstage/types';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
@@ -238,6 +238,31 @@ export interface RepoUrlPickerUiOptions {
};
}
// Warning: (ae-missing-release-tag) "RouterProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type RouterProps = {
TemplateCardComponent?:
| ComponentType<{
template: TemplateEntityV1beta2;
}>
| undefined;
TaskPageComponent?: ComponentType<{}>;
components?: {
TemplateCardComponent?:
| ComponentType<{
template: TemplateEntityV1beta2;
}>
| undefined;
TaskPageComponent?: ComponentType<{}>;
};
groups?: Array<{
title?: string;
titleComponent?: React_2.ReactNode;
filter: (entity: Entity) => boolean;
}>;
};
// @public
export interface ScaffolderApi {
// (undocumented)
@@ -316,25 +341,7 @@ export interface ScaffolderGetIntegrationsListResponse {
// Warning: (ae-missing-release-tag) "ScaffolderPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const ScaffolderPage: ({
TemplateCardComponent,
TaskPageComponent,
groups,
}: {
TemplateCardComponent?:
| ComponentType<{
template: TemplateEntityV1beta2;
}>
| undefined;
TaskPageComponent?: ComponentType<{}> | undefined;
groups?:
| {
title?: string | undefined;
titleComponent?: ReactNode;
filter: (entity: Entity) => boolean;
}[]
| undefined;
}) => JSX.Element;
export const ScaffolderPage: (props: RouterProps) => JSX.Element;
// Warning: (ae-missing-release-tag) "scaffolderPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -357,7 +364,7 @@ export interface ScaffolderScaffoldOptions {
// (undocumented)
templateRef: string;
// (undocumented)
values: Record<string, any>;
values: Record<string, JsonValue>;
}
// Warning: (ae-missing-release-tag) "ScaffolderScaffoldResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -410,6 +417,14 @@ export type ScaffolderTaskStatus =
| 'completed'
| 'skipped';
// @public
export interface ScaffolderUseTemplateSecrets {
// @deprecated (undocumented)
setSecret: (input: Record<string, string>) => void;
// (undocumented)
setSecrets: (input: Record<string, string>) => void;
}
// @public
export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element;
@@ -420,7 +435,7 @@ export type TaskPageProps = {
// Warning: (ae-missing-release-tag) "TemplateList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public @deprecated (undocumented)
export const TemplateList: ({
TemplateCardComponent,
group,
@@ -428,7 +443,7 @@ export const TemplateList: ({
// Warning: (ae-missing-release-tag) "TemplateListProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public @deprecated (undocumented)
export type TemplateListProps = {
TemplateCardComponent?:
| ComponentType<{
@@ -436,7 +451,7 @@ export type TemplateListProps = {
}>
| undefined;
group?: {
title?: string;
title?: React_2.ReactNode;
titleComponent?: React_2.ReactNode;
filter: (entity: Entity) => boolean;
};
@@ -459,9 +474,7 @@ export type TemplateParameterSchema = {
export const TemplateTypePicker: () => JSX.Element | null;
// @public
export const useTemplateSecrets: () => {
setSecret: (input: Record<string, string>) => void;
};
export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets;
// Warnings were encountered during analysis:
//
+32 -8
View File
@@ -32,11 +32,20 @@ import {
} from '../extensions';
import { useElementFilter } from '@backstage/core-plugin-api';
type RouterProps = {
export type RouterProps = {
/** @deprecated use components.TemplateCardComponent instead */
TemplateCardComponent?:
| ComponentType<{ template: TemplateEntityV1beta2 }>
| undefined;
/** @deprecated use component.TaskPageComponent instead */
TaskPageComponent?: ComponentType<{}>;
components?: {
TemplateCardComponent?:
| ComponentType<{ template: TemplateEntityV1beta2 }>
| undefined;
TaskPageComponent?: ComponentType<{}>;
};
groups?: Array<{
title?: string;
titleComponent?: React.ReactNode;
@@ -44,13 +53,26 @@ type RouterProps = {
}>;
};
export const Router = ({
TemplateCardComponent,
TaskPageComponent,
groups,
}: RouterProps) => {
export const Router = (props: RouterProps) => {
const {
TemplateCardComponent: legacyTemplateCardComponent,
TaskPageComponent: legacyTaskPageComponent,
groups,
components = {},
} = props;
if (legacyTemplateCardComponent || legacyTaskPageComponent) {
// eslint-disable-next-line no-console
console.warn(
"DEPRECATION: 'TemplateCardComponent' and 'TaskPageComponent' are deprecated when calling the 'ScaffolderPage'. Use 'components' prop to pass these component overrides instead.",
);
}
const { TemplateCardComponent, TaskPageComponent } = components;
const outlet = useOutlet();
const TaskPageElement = TaskPageComponent || TaskPage;
const TaskPageElement =
TaskPageComponent ?? legacyTaskPageComponent ?? TaskPage;
const customFieldExtensions = useElementFilter(outlet, elements =>
elements
@@ -78,8 +100,10 @@ export const Router = ({
path="/"
element={
<ScaffolderPage
TemplateCardComponent={TemplateCardComponent}
groups={groups}
TemplateCardComponent={
TemplateCardComponent ?? legacyTemplateCardComponent
}
/>
}
/>
@@ -55,7 +55,8 @@ export type ScaffolderPageProps = {
| ComponentType<{ template: TemplateEntityV1beta2 }>
| undefined;
groups?: Array<{
title?: string;
title?: React.ReactNode;
/** @deprcated use title instead as it accepts a string and react component */
titleComponent?: React.ReactNode;
filter: (entity: Entity) => boolean;
}>;
@@ -46,9 +46,9 @@ import classNames from 'classnames';
import { DateTime, Interval } from 'luxon';
import qs from 'qs';
import React, { memo, useEffect, useMemo, useState } from 'react';
import { generatePath, useNavigate, useParams } from 'react-router';
import { useNavigate, useParams } from 'react-router';
import useInterval from 'react-use/lib/useInterval';
import { rootRouteRef } from '../../routes';
import { rootRouteRef, selectedTemplateRouteRef } from '../../routes';
import { ScaffolderTaskStatus, ScaffolderTaskOutput } from '../../types';
import { useTaskEventStream } from '../hooks/useEventStream';
import { TaskPageLinks } from './TaskPageLinks';
@@ -243,7 +243,8 @@ export type TaskPageProps = {
export const TaskPage = ({ loadingText }: TaskPageProps) => {
const classes = useStyles();
const navigate = useNavigate();
const rootLink = useRouteRef(rootRouteRef);
const rootPath = useRouteRef(rootRouteRef);
const templateRoute = useRouteRef(selectedTemplateRouteRef);
const [userSelectedStepId, setUserSelectedStepId] = useState<
string | undefined
>(undefined);
@@ -297,7 +298,7 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => {
const handleStartOver = () => {
if (!taskStream.task || !taskStream.task?.spec.templateInfo?.entityRef) {
navigate(generatePath(rootLink()));
navigate(rootPath());
return;
}
@@ -311,14 +312,9 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => {
);
navigate(
generatePath(
`${rootLink()}/templates/:templateName?${qs.stringify({
formData: JSON.stringify(formData),
})}`,
{
templateName: name,
},
),
`${templateRoute({ templateName: name })}?${qs.stringify({
formData: JSON.stringify(formData),
})}`,
);
};
@@ -41,8 +41,7 @@ import {
} from '@material-ui/core';
import WarningIcon from '@material-ui/icons/Warning';
import React from 'react';
import { generatePath } from 'react-router';
import { rootRouteRef } from '../../routes';
import { selectedTemplateRouteRef } from '../../routes';
import { FavouriteTemplate } from '../FavouriteTemplate/FavouriteTemplate';
import { Button, ItemCardHeader } from '@backstage/core-components';
@@ -141,7 +140,7 @@ const DeprecationWarning = () => {
export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => {
const backstageTheme = useTheme<BackstageTheme>();
const rootLink = useRouteRef(rootRouteRef);
const templateRoute = useRouteRef(selectedTemplateRouteRef);
const templateProps = getTemplateCardProps(template);
const ownedByRelations = getEntityRelations(
template as Entity,
@@ -152,9 +151,7 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => {
: 'other';
const theme = backstageTheme.getPageTheme({ themeId });
const classes = useStyles({ backgroundImage: theme.backgroundImage });
const href = generatePath(`${rootLink()}/templates/:templateName`, {
templateName: templateProps.name,
});
const href = templateRoute({ templateName: templateProps.name });
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi);
@@ -29,17 +29,24 @@ import { useEntityList } from '@backstage/plugin-catalog-react';
import { Typography } from '@material-ui/core';
import { TemplateCard } from '../TemplateCard';
/**
* @deprecated this type is deprecated and will be removed in a future releases, please use the TemplateCard to render your own list.
*/
export type TemplateListProps = {
TemplateCardComponent?:
| ComponentType<{ template: TemplateEntityV1beta2 }>
| undefined;
group?: {
title?: string;
title?: React.ReactNode;
/** @deprecated use title instead, can be a string or a react component */
titleComponent?: React.ReactNode;
filter: (entity: Entity) => boolean;
};
};
/**
* @deprecated this component is deprecated and will be removed in a future releases, please use the TemplateCard to render your own list.
*/
export const TemplateList = ({
TemplateCardComponent,
group,
@@ -49,11 +56,24 @@ export const TemplateList = ({
const maybeFilteredEntities = group
? entities.filter(e => group.filter(e))
: entities;
const title = group ? (
group.titleComponent || <ContentHeader title={group.title} />
) : (
<ContentHeader title="Other Templates" />
);
const titleComponent: React.ReactNode = (() => {
if (group?.titleComponent) {
// eslint-disable-next-line no-console
console.warn(
'DEPRECATED: group.titleComponent is now deprecated. Use group.title instead, it can be a string or a react component',
);
return group?.titleComponent;
}
if (group && group.title) {
if (typeof group.title === 'string') {
return <ContentHeader title={group.title} />;
}
return group.title;
}
return <ContentHeader title="Other Templates" />;
})();
if (group && maybeFilteredEntities.length === 0) {
return null;
@@ -79,7 +99,7 @@ export const TemplateList = ({
)}
<Content>
{title}
{titleComponent}
<ItemCardGrid>
{maybeFilteredEntities &&
maybeFilteredEntities?.length > 0 &&
@@ -18,13 +18,13 @@ import { LinearProgress } from '@material-ui/core';
import { FormValidation, IChangeEvent } from '@rjsf/core';
import qs from 'qs';
import React, { useCallback, useContext, useState } from 'react';
import { generatePath, Navigate, useNavigate } from 'react-router';
import { Navigate, useNavigate } from 'react-router';
import { useParams } from 'react-router-dom';
import useAsync from 'react-use/lib/useAsync';
import { scaffolderApiRef } from '../../api';
import { CustomFieldValidator, FieldExtensionOptions } from '../../extensions';
import { SecretsContext } from '../secrets/SecretsContext';
import { rootRouteRef } from '../../routes';
import { rootRouteRef, scaffolderTaskRouteRef } from '../../routes';
import { MultistepJsonForm } from '../MultistepJsonForm';
import {
@@ -117,7 +117,8 @@ export const TemplatePage = ({
const scaffolderApi = useApi(scaffolderApiRef);
const { templateName } = useParams();
const navigate = useNavigate();
const rootLink = useRouteRef(rootRouteRef);
const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef);
const rootRoute = useRouteRef(rootRouteRef);
const { schema, loading, error } = useTemplateParameterSchema(templateName);
const [formState, setFormState] = useState<Record<string, any>>(() => {
const query = qs.parse(window.location.search, {
@@ -158,16 +159,16 @@ export const TemplatePage = ({
// extra back/forward slots.
window.history?.replaceState(null, document.title, newUrl);
navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId }));
navigate(scaffolderTaskRoute({ taskId }));
};
if (error) {
errorApi.post(new Error(`Failed to load template, ${error}`));
return <Navigate to={rootLink()} />;
return <Navigate to={rootRoute()} />;
}
if (!loading && !schema) {
errorApi.post(new Error('Template was not found.'));
return <Navigate to={rootLink()} />;
return <Navigate to={rootRoute()} />;
}
const customFieldComponents = Object.fromEntries(
@@ -56,7 +56,7 @@ export const RepoUrlPicker = (
);
const integrationApi = useApi(scmIntegrationsApiRef);
const scmAuthApi = useApi(scmAuthApiRef);
const { setSecret } = useTemplateSecrets();
const { setSecrets } = useTemplateSecrets();
const allowedHosts = useMemo(
() => uiSchema?.['ui:options']?.allowedHosts ?? [],
[uiSchema],
@@ -114,7 +114,7 @@ export const RepoUrlPicker = (
// set the secret using the key provided in the the ui:options for use
// in the templating the manifest with ${{ secrets[secretsKey] }}
setSecret({ [requestUserCredentials.secretsKey]: token });
setSecrets({ [requestUserCredentials.secretsKey]: token });
},
500,
[state, uiSchema],
@@ -0,0 +1,24 @@
/*
* 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 * from './fields';
export type { RepoUrlPickerUiOptions } from './fields';
export { FavouriteTemplate } from './FavouriteTemplate';
export { TemplateList } from './TemplateList';
export type { TemplateListProps } from './TemplateList';
export { TemplateTypePicker } from './TemplateTypePicker';
export * from './secrets';
export { TaskPage } from './TaskPage';
export type { RouterProps } from './Router';
@@ -36,7 +36,7 @@ describe('SecretsContext', () => {
);
expect(result.current.context?.secrets.foo).toEqual(undefined);
act(() => result.current.hook.setSecret({ foo: 'bar' }));
act(() => result.current.hook.setSecrets({ foo: 'bar' }));
expect(result.current.context?.secrets.foo).toEqual('bar');
});
@@ -48,11 +48,21 @@ export const SecretsContextProvider = ({ children }: PropsWithChildren<{}>) => {
);
};
/**
* The return type from the useTemplateSecrets hook.
* @public
*/
export interface ScaffolderUseTemplateSecrets {
/** @deprecated use setSecrets instead */
setSecret: (input: Record<string, string>) => void;
setSecrets: (input: Record<string, string>) => void;
}
/**
* Hook to access the secrets context.
* @public
*/
export const useTemplateSecrets = () => {
export const useTemplateSecrets = (): ScaffolderUseTemplateSecrets => {
const value = useContext(SecretsContext);
if (!value) {
throw new Error(
@@ -60,14 +70,14 @@ export const useTemplateSecrets = () => {
);
}
const { setSecrets } = value;
const { setSecrets: updateSecrets } = value;
const setSecret = useCallback(
const setSecrets = useCallback(
(input: Record<string, string>) => {
setSecrets(currentSecrets => ({ ...currentSecrets, ...input }));
updateSecrets(currentSecrets => ({ ...currentSecrets, ...input }));
},
[setSecrets],
[updateSecrets],
);
return { setSecret };
return { setSecret: setSecrets, setSecrets };
};
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export { useTemplateSecrets } from './SecretsContext';
export type { ScaffolderUseTemplateSecrets } from './SecretsContext';
+3 -1
View File
@@ -48,7 +48,9 @@ export function createScaffolderFieldExtension<
};
}
export const ScaffolderFieldExtensions: React.ComponentType = () => null;
export const ScaffolderFieldExtensions: React.ComponentType =
(): JSX.Element | null => null;
attachComponentData(
ScaffolderFieldExtensions,
FIELD_EXTENSION_WRAPPER_KEY,
+1 -8
View File
@@ -55,12 +55,5 @@ export {
ScaffolderPage,
scaffolderPlugin,
} from './plugin';
export * from './components/fields';
export type { RepoUrlPickerUiOptions } from './components/fields';
export { FavouriteTemplate } from './components/FavouriteTemplate';
export { TemplateList } from './components/TemplateList';
export type { TemplateListProps } from './components/TemplateList';
export { TemplateTypePicker } from './components/TemplateTypePicker';
export * from './components/secrets';
export { TaskPage } from './components/TaskPage';
export * from './components';
export type { TaskPageProps } from './components/TaskPage';
+19
View File
@@ -16,6 +16,7 @@
import {
createExternalRouteRef,
createRouteRef,
createSubRouteRef,
} from '@backstage/core-plugin-api';
export const registerComponentRouteRef = createExternalRouteRef({
@@ -26,3 +27,21 @@ export const registerComponentRouteRef = createExternalRouteRef({
export const rootRouteRef = createRouteRef({
id: 'scaffolder',
});
export const selectedTemplateRouteRef = createSubRouteRef({
id: 'scaffolder/selected-template',
parent: rootRouteRef,
path: '/templates/:templateName',
});
export const scaffolderTaskRouteRef = createSubRouteRef({
id: 'scaffolder/task',
parent: rootRouteRef,
path: '/tasks/:taskId',
});
export const actionsRouteRef = createSubRouteRef({
id: 'scaffolder/actions',
parent: rootRouteRef,
path: '/actions',
});
+2 -2
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { JsonObject, Observable } from '@backstage/types';
import { JsonObject, JsonValue, Observable } from '@backstage/types';
import { JSONSchema7 } from 'json-schema';
export type ScaffolderTaskStatus =
@@ -82,7 +82,7 @@ export type LogEvent = {
export interface ScaffolderScaffoldOptions {
templateRef: string;
values: Record<string, any>;
values: Record<string, JsonValue>;
secrets?: Record<string, string>;
}