diff --git a/.changeset/kind-pianos-burn.md b/.changeset/kind-pianos-burn.md index 078ab6846e..1fb09aeb83 100644 --- a/.changeset/kind-pianos-burn.md +++ b/.changeset/kind-pianos-burn.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder-react': major --- -Re-home some of the common types, components, hooks and `(route|api)Ref`'s for the `@backstage/plugin-scaffolder` to this package for easy re-use across things that want to interact with the `scaffolder`. +Re-home some of the common types, components, hooks and `scaffolderApiRef` for the `@backstage/plugin-scaffolder` to this package for easy re-use across things that want to interact with the `scaffolder`. diff --git a/plugins/scaffolder-react/src/api/ref.ts b/plugins/scaffolder-react/src/api/ref.ts index 3dee426b09..c4b7853556 100644 --- a/plugins/scaffolder-react/src/api/ref.ts +++ b/plugins/scaffolder-react/src/api/ref.ts @@ -16,8 +16,13 @@ import { createApiRef } from '@backstage/core-plugin-api'; import { ScaffolderApi } from './types'; +import { getOrCreateGlobalSingleton } from '@backstage/version-bridge'; /** @public */ -export const scaffolderApiRef = createApiRef({ - id: 'plugin.scaffolder.service', -}); +export const scaffolderApiRef = getOrCreateGlobalSingleton( + 'scaffolder:scaffolder-api-ref', + () => + createApiRef({ + id: 'plugin.scaffolder.service', + }), +); diff --git a/plugins/scaffolder-react/src/extensions/index.tsx b/plugins/scaffolder-react/src/extensions/index.tsx index 94bf8bad14..33442f6697 100644 --- a/plugins/scaffolder-react/src/extensions/index.tsx +++ b/plugins/scaffolder-react/src/extensions/index.tsx @@ -22,17 +22,7 @@ import { FieldExtensionComponentProps, } from './types'; import { Extension, attachComponentData } from '@backstage/core-plugin-api'; - -/** - * The key used to store the field extension data for the `` component - * @public - */ -export const FIELD_EXTENSION_WRAPPER_KEY = 'scaffolder.extensions.wrapper.v1'; -/** - * The key used to store the field extension data for any `` component - * @public - */ -export const FIELD_EXTENSION_KEY = 'scaffolder.extensions.field.v1'; +import { FIELD_EXTENSION_KEY, FIELD_EXTENSION_WRAPPER_KEY } from './keys'; /** * A type used to wrap up the FieldExtension to embed the ReturnValue and the InputProps diff --git a/plugins/scaffolder-react/src/extensions/keys.ts b/plugins/scaffolder-react/src/extensions/keys.ts new file mode 100644 index 0000000000..f035e9c1c2 --- /dev/null +++ b/plugins/scaffolder-react/src/extensions/keys.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 const FIELD_EXTENSION_WRAPPER_KEY = 'scaffolder.extensions.wrapper.v1'; +/** + * The key used to store the field extension data for any `` component + */ +export const FIELD_EXTENSION_KEY = 'scaffolder.extensions.field.v1'; diff --git a/plugins/scaffolder-react/src/hooks/index.ts b/plugins/scaffolder-react/src/hooks/index.ts new file mode 100644 index 0000000000..022fe7c38c --- /dev/null +++ b/plugins/scaffolder-react/src/hooks/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 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 { useCustomFieldExtensions } from './useCustomFieldExtensions'; diff --git a/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts b/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts new file mode 100644 index 0000000000..15300956c6 --- /dev/null +++ b/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2023 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 { useElementFilter } from '@backstage/core-plugin-api'; +import { FieldExtensionOptions } from '../extensions'; +import { + FIELD_EXTENSION_KEY, + FIELD_EXTENSION_WRAPPER_KEY, +} from '../extensions/keys'; +import { NextFieldExtensionOptions } from '../next'; + +/** + * Hook that returns all custom field extensions from the current outlet. + * @public + */ +export const useCustomFieldExtensions = (outlet: React.ReactNode) => { + return useElementFilter(outlet, elements => + elements + .selectByComponentData({ + key: FIELD_EXTENSION_WRAPPER_KEY, + }) + .findComponentData({ + key: FIELD_EXTENSION_KEY, + }), + ); +}; diff --git a/plugins/scaffolder-react/src/index.ts b/plugins/scaffolder-react/src/index.ts index a2336a5ca5..0b053e6bf4 100644 --- a/plugins/scaffolder-react/src/index.ts +++ b/plugins/scaffolder-react/src/index.ts @@ -18,5 +18,6 @@ export * from './extensions'; export * from './types'; export * from './secrets'; export * from './api'; +export * from './hooks'; export * from './next'; diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 287342007b..564e12bcae 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -30,10 +30,8 @@ import { useRouteRefParams, } from '@backstage/core-plugin-api'; import { - FIELD_EXTENSION_KEY, - FIELD_EXTENSION_WRAPPER_KEY, - FieldExtensionOptions, SecretsContextProvider, + useCustomFieldExtensions, } from '@backstage/plugin-scaffolder-react'; import { ListTasksPage } from './ListTasksPage'; import { LayoutOptions, LAYOUTS_KEY, LAYOUTS_WRAPPER_KEY } from '../layouts'; @@ -94,16 +92,7 @@ export const Router = (props: RouterProps) => { const outlet = useOutlet(); const TaskPageElement = TaskPageComponent ?? TaskPage; - const customFieldExtensions = useElementFilter(outlet, elements => - elements - .selectByComponentData({ - key: FIELD_EXTENSION_WRAPPER_KEY, - }) - .findComponentData({ - key: FIELD_EXTENSION_KEY, - }), - ); - + const customFieldExtensions = useCustomFieldExtensions(outlet); const fieldExtensions = [ ...customFieldExtensions, ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter( @@ -114,6 +103,7 @@ export const Router = (props: RouterProps) => { ), ]; + // todo(blam): this should also be moved to a hook in -react const customLayouts = useElementFilter(outlet, elements => elements .selectByComponentData({ diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index a9785e6aa6..33c2b40f01 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -18,14 +18,11 @@ import { Routes, Route, useOutlet } from 'react-router-dom'; import { TemplateListPage } from '../TemplateListPage'; import { TemplateWizardPage } from '../TemplateWizardPage'; import { - FIELD_EXTENSION_WRAPPER_KEY, - FIELD_EXTENSION_KEY, NextFieldExtensionOptions, - FieldExtensionOptions, SecretsContextProvider, + useCustomFieldExtensions, } from '@backstage/plugin-scaffolder-react'; -import { useElementFilter } from '@backstage/core-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups'; import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../../extensions/default'; @@ -55,20 +52,8 @@ export type NextRouterProps = { */ export const Router = (props: PropsWithChildren) => { const { components: { TemplateCardComponent } = {} } = props; - const outlet = useOutlet() || props.children; - - // todo(blam): move this out into a `useFieldExtensions` hook exported by -react - const customFieldExtensions = useElementFilter(outlet, elements => - elements - .selectByComponentData({ - key: FIELD_EXTENSION_WRAPPER_KEY, - }) - .findComponentData({ - key: FIELD_EXTENSION_KEY, - }), - ); - + const customFieldExtensions = useCustomFieldExtensions(outlet); const fieldExtensions = [ ...customFieldExtensions, ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(