From 8ee146cea9f26f4a43f8fae0621761a85a01d7fe Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 3 Jun 2025 09:07:32 +0200 Subject: [PATCH] refactor: apply second round of review suggestions Signed-off-by: Camila Belo --- .changeset/large-baboons-prove.md | 4 +- .changeset/large-crabs-joke.md | 6 - .changeset/loose-ants-learn.md | 1 + .changeset/petite-pots-lead.md | 6 - .changeset/rare-rules-move.md | 1 + .../search/declarative-integration.md | 2 +- plugins/catalog-import/report-alpha.api.md | 4 +- plugins/catalog-react/report-alpha.api.md | 12 ++ .../blueprints/EntityIconLinkBlueprint.tsx | 61 ++++---- plugins/catalog/package.json | 1 - plugins/catalog/report-alpha.api.md | 14 ++ plugins/catalog/report.api.md | 6 +- plugins/catalog/src/alpha/entityCards.tsx | 11 +- .../src/components/AboutCard/AboutCard.tsx | 133 +++++++++++++----- plugins/scaffolder-react/report-alpha.api.md | 14 +- plugins/scaffolder-react/src/alpha.ts | 2 - plugins/scaffolder/report-alpha.api.md | 11 +- .../useScaffolderTemplateIconLinkProps.tsx | 26 ++-- plugins/scaffolder/src/alpha/plugin.tsx | 16 +-- plugins/scaffolder/src/routes.ts | 7 - plugins/techdocs-react/package.json | 2 - plugins/techdocs-react/report-alpha.api.md | 14 -- plugins/techdocs-react/src/alpha.ts | 2 - .../src/{hooks.tsx => hooks.ts} | 38 ----- plugins/techdocs/package.json | 4 +- plugins/techdocs/report-alpha.api.md | 25 ++-- .../hooks/useTechdocsReaderIconLinkProps.tsx | 51 +++++++ .../src/{alpha.tsx => alpha/index.tsx} | 36 ++--- plugins/techdocs/src/routes.ts | 12 +- yarn.lock | 3 - 30 files changed, 260 insertions(+), 265 deletions(-) delete mode 100644 .changeset/large-crabs-joke.md delete mode 100644 .changeset/petite-pots-lead.md rename plugins/{scaffolder-react/src => scaffolder/src/alpha}/hooks/useScaffolderTemplateIconLinkProps.tsx (71%) rename plugins/techdocs-react/src/{hooks.tsx => hooks.ts} (76%) create mode 100644 plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx rename plugins/techdocs/src/{alpha.tsx => alpha/index.tsx} (87%) diff --git a/.changeset/large-baboons-prove.md b/.changeset/large-baboons-prove.md index 18949dd976..072d57de4c 100644 --- a/.changeset/large-baboons-prove.md +++ b/.changeset/large-baboons-prove.md @@ -4,6 +4,6 @@ Add support to customize the about card icon links via `EntityIconLinkBlueprint` and provide a default catalog view catalog source, launch scaffolder template and read techdocs docs icon links extensions. -**BREAKING** +**BREAKING ALPHA** -The `Scaffolder` launch template and `TechDocs` read documentation icons have been extracted from the default `Catalog` about card links and are now provided respectively by the `Scaffolder` and `TechDocs` plugins in the new frontend system. If you are trying out the new frontend system and are using a translation for these link titles other than the default, you should now translate them using the scaffolder translation reference or the TechDocs translation reference (the translation keys are still the same, `aboutCard.viewTechdocs` and `aboutCard.launchTemplate`). +The `Scaffolder` launch template and `TechDocs` read documentation icons have been extracted from the default `Catalog` about card links and are now provided respectively by the `Scaffolder` and `TechDocs` plugins in the new frontend system. It means that they will not be available unless you install the `TechDocs` and `Scaffolder` plugins. Also If you are using translation for these icon link titles other than the default, you should now translate them using the scaffolder translation reference or the TechDocs translation reference (the translation keys are still the same, `aboutCard.viewTechdocs` and `aboutCard.launchTemplate`). diff --git a/.changeset/large-crabs-joke.md b/.changeset/large-crabs-joke.md deleted file mode 100644 index c8e021d09c..0000000000 --- a/.changeset/large-crabs-joke.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': minor ---- - -Export a new hook to share how to get the "Launch Template" icon link properties. The function is currently used by the alpha `Scaffolder` plugin and the legacy `Catalog` plugin. -This hook should only be used internally and temporarily until the legacy frontend system is deprecated. diff --git a/.changeset/loose-ants-learn.md b/.changeset/loose-ants-learn.md index 728bdc8942..29f357966c 100644 --- a/.changeset/loose-ants-learn.md +++ b/.changeset/loose-ants-learn.md @@ -2,4 +2,5 @@ '@backstage/plugin-techdocs': minor --- +**New Frontend System Only:** The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. diff --git a/.changeset/petite-pots-lead.md b/.changeset/petite-pots-lead.md deleted file mode 100644 index 7ce331f5b5..0000000000 --- a/.changeset/petite-pots-lead.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-techdocs-react': minor ---- - -Export a new hook to share how to get the "View TechDocs" icon link properties. The function is currently used by the alpha `Techdocs` plugin and the legacy `Catalog` plugin. -This hook should only be used internally and temporarily until the legacy frontend system is deprecated. diff --git a/.changeset/rare-rules-move.md b/.changeset/rare-rules-move.md index 9786bc1104..6abc778941 100644 --- a/.changeset/rare-rules-move.md +++ b/.changeset/rare-rules-move.md @@ -2,4 +2,5 @@ '@backstage/plugin-scaffolder': minor --- +**New Frontend System Only:** The `Scaffolder` plugin is now responsible for providing an entity icon link extension to launch templates from the catalog entity page. diff --git a/docs/features/search/declarative-integration.md b/docs/features/search/declarative-integration.md index c331d810ef..b8f1c6ce5c 100644 --- a/docs/features/search/declarative-integration.md +++ b/docs/features/search/declarative-integration.md @@ -182,7 +182,7 @@ export default createPlugin({ }) ``` -Here is the `plugins/techdocs/alpha.tsx` final version, and you can also take a look at the [actual implementation](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/alpha.tsx) of a custom `TechDocs` search result item: +Here is the `plugins/techdocs/alpha/index.tsx` final version, and you can also take a look at the [actual implementation](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/alpha/index.tsx) of a custom `TechDocs` search result item: ```tsx // plugins/techdocs/alpha.tsx diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index 90716bc442..d4dab4549a 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -63,15 +63,15 @@ export const catalogImportTranslationRef: TranslationRef< readonly 'stepInitAnalyzeUrl.error.url': 'Must start with http:// or https://.'; readonly 'stepInitAnalyzeUrl.error.repository': "Couldn't generate entities for your repository"; readonly 'stepInitAnalyzeUrl.error.locations': 'There are no entities at this location'; - readonly 'stepInitAnalyzeUrl.nextButtonText': 'Analyze'; readonly 'stepInitAnalyzeUrl.urlHelperText': 'Enter the full path to your entity file to start tracking your component'; + readonly 'stepInitAnalyzeUrl.nextButtonText': 'Analyze'; readonly 'stepPrepareCreatePullRequest.nextButtonText': 'Create PR'; readonly 'stepPrepareCreatePullRequest.previewPr.title': 'Preview Pull Request'; readonly 'stepPrepareCreatePullRequest.previewPr.subheader': 'Create a new Pull Request'; readonly 'stepPrepareCreatePullRequest.previewCatalogInfo.title': 'Preview Entities'; - readonly 'stepPrepareSelectLocations.nextButtonText': 'Review'; readonly 'stepPrepareSelectLocations.locations.description': 'Select one or more locations that are present in your git repository:'; readonly 'stepPrepareSelectLocations.locations.selectAll': 'Select All'; + readonly 'stepPrepareSelectLocations.nextButtonText': 'Review'; readonly 'stepPrepareSelectLocations.existingLocations.description': 'These locations already exist in the catalog:'; readonly 'stepReviewLocation.refresh': 'Refresh'; readonly 'stepReviewLocation.import': 'Import'; diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index e4be5ae299..36a93a8a5e 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -407,6 +407,13 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{ optional: true; } > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > | ConfigurableExtensionDataRef< () => IconLinkVerticalProps, 'entity-icon-link-props', @@ -434,6 +441,11 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{ 'catalog.entity-filter-function', {} >; + filterExpression: ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + {} + >; }; }>; diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx index fce281b70f..11c9a2b805 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx @@ -20,14 +20,15 @@ import { createExtensionDataRef, } from '@backstage/frontend-plugin-api'; -import { - EntityPredicate, - entityPredicateToFilterFunction, -} from '../predicates'; +import { EntityPredicate } from '../predicates'; import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; -import { entityFilterFunctionDataRef } from './extensionData'; +import { + entityFilterExpressionDataRef, + entityFilterFunctionDataRef, +} from './extensionData'; import { Entity } from '@backstage/catalog-model'; +import { resolveEntityFilterData } from './resolveEntityFilterData'; const entityIconLinkPropsDataRef = createExtensionDataRef< () => IconLinkVerticalProps @@ -39,10 +40,15 @@ const entityIconLinkPropsDataRef = createExtensionDataRef< export const EntityIconLinkBlueprint = createExtensionBlueprint({ kind: 'entity-icon-link', attachTo: { id: 'entity-card:catalog/about', input: 'iconLinks' }, - output: [entityIconLinkPropsDataRef, entityFilterFunctionDataRef.optional()], + output: [ + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), + entityIconLinkPropsDataRef, + ], dataRefs: { useProps: entityIconLinkPropsDataRef, filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, }, config: { schema: { @@ -56,33 +62,22 @@ export const EntityIconLinkBlueprint = createExtensionBlueprint({ useProps: () => Omit; filter?: EntityPredicate | ((entity: Entity) => boolean); }, - { config }, + { config, node }, ) { - yield entityIconLinkPropsDataRef(() => ({ - ...params.useProps(), - ...Object.entries(config).reduce((rest, [key, value]) => { - // Only include properties that are defined in the config - // to avoid overriding defaults with undefined values - if (value !== undefined) { - return { - ...rest, - [key]: value, - }; - } - return rest; - }, {}), - })); - - if (config.filter) { - yield entityFilterFunctionDataRef( - entityPredicateToFilterFunction(config.filter), - ); - } else if (typeof params.filter === 'function') { - yield entityFilterFunctionDataRef(params.filter); - } else if (params.filter) { - yield entityFilterFunctionDataRef( - entityPredicateToFilterFunction(params.filter), - ); - } + const { filter, useProps } = params; + yield* resolveEntityFilterData(filter, config, node); + // Only include properties that are defined in the config + // to avoid overriding defaults with undefined values + const configProps = Object.entries(config).reduce( + (rest, [key, value]) => + value !== undefined + ? { + ...rest, + [key]: value, + } + : rest, + {}, + ); + yield entityIconLinkPropsDataRef(() => ({ ...useProps(), ...configProps })); }, }); diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 29c4eb134b..1e78d40306 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -70,7 +70,6 @@ "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", - "@backstage/plugin-scaffolder-react": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/plugin-search-react": "workspace:^", "@backstage/plugin-techdocs-common": "workspace:^", diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index d7a7be0778..4be914a69d 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -377,6 +377,13 @@ const _default: FrontendPlugin< optional: true; } > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > | ConfigurableExtensionDataRef< () => IconLinkVerticalProps, 'entity-icon-link-props', @@ -964,6 +971,13 @@ const _default: FrontendPlugin< optional: true; } > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > | ConfigurableExtensionDataRef< () => IconLinkVerticalProps, 'entity-icon-link-props', diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 1b9b4dfa8c..52b13e1c43 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -40,10 +40,10 @@ import { TableProps } from '@backstage/core-components'; import { TabProps } from '@material-ui/core/Tab'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; -// Warning: (ae-forgotten-export) The symbol "InternalAboutCardProps" needs to be exported by the entry point index.d.ts -// // @public -export type AboutCardProps = Pick; +export type AboutCardProps = { + variant?: InfoCardVariants; +}; // @public (undocumented) export function AboutContent(props: AboutContentProps): JSX_2.Element; diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index 6533bde3a1..3913f8b2b7 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -25,13 +25,15 @@ import { IconLinkVerticalProps, } from '@backstage/core-components'; import { useEntity } from '@backstage/plugin-catalog-react'; +import { buildFilterFn } from './filter/FilterWrapper'; export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ name: 'about', inputs: { iconLinks: createExtensionInput([ - EntityIconLinkBlueprint.dataRefs.useProps, EntityIconLinkBlueprint.dataRefs.filterFunction.optional(), + EntityIconLinkBlueprint.dataRefs.filterExpression.optional(), + EntityIconLinkBlueprint.dataRefs.useProps, ]), }, factory(originalFactory, { inputs }) { @@ -41,9 +43,10 @@ export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ // call them in a component function to avoid breaking the rules of hooks. const links = inputs.iconLinks.reduce((rest, iconLink) => { const props = iconLink.get(EntityIconLinkBlueprint.dataRefs.useProps)(); - const filter = - iconLink.get(EntityIconLinkBlueprint.dataRefs.filterFunction) ?? - (() => true); + const filter = buildFilterFn( + iconLink.get(EntityIconLinkBlueprint.dataRefs.filterFunction), + iconLink.get(EntityIconLinkBlueprint.dataRefs.filterExpression), + ); if (filter(entity)) { return [...rest, props]; } diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index e5d6bbeef2..fd1c05ba5f 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -13,52 +13,70 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - ANNOTATION_EDIT_URL, - ANNOTATION_LOCATION, - stringifyEntityRef, -} from '@backstage/catalog-model'; + +import { useCallback } from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import CardHeader from '@material-ui/core/CardHeader'; import Divider from '@material-ui/core/Divider'; import IconButton from '@material-ui/core/IconButton'; -import { makeStyles } from '@material-ui/core/styles'; +import CachedIcon from '@material-ui/icons/Cached'; +import EditIcon from '@material-ui/icons/Edit'; +import DocsIcon from '@material-ui/icons/Description'; +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; + import { AppIcon, HeaderIconLinkRow, + IconLinkVerticalProps, InfoCardVariants, Link, } from '@backstage/core-components'; -import { useCallback } from 'react'; +import { + alertApiRef, + errorApiRef, + useApp, + useApi, + useRouteRef, +} from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + import { ScmIntegrationIcon, scmIntegrationsApiRef, } from '@backstage/integration-react'; + import { - alertApiRef, - errorApiRef, - useApi, - useRouteRef, -} from '@backstage/core-plugin-api'; + DEFAULT_NAMESPACE, + ANNOTATION_EDIT_URL, + ANNOTATION_LOCATION, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { catalogApiRef, getEntitySourceLocation, useEntity, } from '@backstage/plugin-catalog-react'; -import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; - -import { AboutContent } from './AboutContent'; -import CachedIcon from '@material-ui/icons/Cached'; -import EditIcon from '@material-ui/icons/Edit'; import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; -import { useSourceTemplateCompoundEntityRef } from './hooks'; -import { catalogTranslationRef } from '../../alpha/translation'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { useTechdocsReaderIconLinkProps } from '@backstage/plugin-techdocs-react/alpha'; -import { useScaffolderTemplateIconLinkProps } from '@backstage/plugin-scaffolder-react/alpha'; + +import { + TECHDOCS_ANNOTATION, + TECHDOCS_EXTERNAL_ANNOTATION, +} from '@backstage/plugin-techdocs-common'; +import { buildTechDocsURL } from '@backstage/plugin-techdocs-react'; + import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; + +import { usePermission } from '@backstage/plugin-permission-react'; + +import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; +import { catalogTranslationRef } from '../../alpha/translation'; +import { useSourceTemplateCompoundEntityRef } from './hooks'; +import { AboutContent } from './AboutContent'; export function useCatalogSourceIconLinkProps() { const { entity } = useEntity(); @@ -76,17 +94,55 @@ export function useCatalogSourceIconLinkProps() { }; } +// TODO: This hook is duplicated from the TechDocs plugin for backwards compatibility +// Remove it when the the legacy frontend system support is dropped. +function useTechdocsReaderIconLinkProps(): IconLinkVerticalProps { + const { entity } = useEntity(); + const viewTechdocLink = useRouteRef(viewTechDocRouteRef); + const { t } = useTranslationRef(catalogTranslationRef); + + return { + label: t('aboutCard.viewTechdocs'), + disabled: + !( + entity.metadata.annotations?.[TECHDOCS_ANNOTATION] || + entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] + ) || !viewTechdocLink, + icon: , + href: buildTechDocsURL(entity, viewTechdocLink), + }; +} + +// TODO: This hook is duplicated from the Scaffolder plugin for backwards compatibility +// Remove it when the the legacy frontend system support is dropped. +function useScaffolderTemplateIconLinkProps(): IconLinkVerticalProps { + const app = useApp(); + const { entity } = useEntity(); + const templateRoute = useRouteRef(createFromTemplateRouteRef); + const { t } = useTranslationRef(catalogTranslationRef); + const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon; + const { allowed: canCreateTemplateTask } = usePermission({ + permission: taskCreatePermission, + }); + + return { + label: t('aboutCard.launchTemplate'), + icon: , + disabled: !templateRoute || !canCreateTemplateTask, + href: + templateRoute && + templateRoute({ + templateName: entity.metadata.name, + namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, + }), + }; +} + function DefaultAboutCardSubheader() { const { entity } = useEntity(); const catalogSourceIconLink = useCatalogSourceIconLinkProps(); - const techdocsreaderIconLink = useTechdocsReaderIconLinkProps({ - translationRef: catalogTranslationRef, - externalRouteRef: viewTechDocRouteRef, - }); - const scaffolderTemplateIconLink = useScaffolderTemplateIconLinkProps({ - translationRef: catalogTranslationRef, - externalRouteRef: createFromTemplateRouteRef, - }); + const techdocsreaderIconLink = useTechdocsReaderIconLinkProps(); + const scaffolderTemplateIconLink = useScaffolderTemplateIconLinkProps(); const links = [catalogSourceIconLink, techdocsreaderIconLink]; if (isTemplateEntityV1beta3(entity)) { @@ -116,8 +172,16 @@ const useStyles = makeStyles({ }, }); -export interface InternalAboutCardProps { +/** + * Props for {@link EntityAboutCard}. + * + * @public + */ +export type AboutCardProps = { variant?: InfoCardVariants; +}; + +export interface InternalAboutCardProps extends AboutCardProps { subheader?: JSX.Element; } @@ -217,13 +281,6 @@ export function InternalAboutCard(props: InternalAboutCardProps) { ); } -/** - * Props for {@link EntityAboutCard}. - * - * @public - */ -export type AboutCardProps = Pick; - /** * Exported publicly via the EntityAboutCard * diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index 3a1e330614..2014bff9be 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -14,7 +14,6 @@ import { Dispatch } from 'react'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import { FieldSchema } from '@backstage/plugin-scaffolder-react'; @@ -345,8 +344,8 @@ export const scaffolderReactTranslationRef: TranslationRef< readonly 'stepper.backButtonText': 'Back'; readonly 'stepper.createButtonText': 'Create'; readonly 'stepper.reviewButtonText': 'Review'; - readonly 'stepper.stepIndexLabel': 'Step {{index, number}}'; readonly 'stepper.nextButtonText': 'Next'; + readonly 'stepper.stepIndexLabel': 'Step {{index, number}}'; readonly 'templateCategoryPicker.title': 'Categories'; readonly 'templateCard.noDescription': 'No description'; readonly 'templateCard.chooseButtonText': 'Choose'; @@ -485,17 +484,6 @@ export const useFormDataFromQuery: ( initialState?: Record, ) => [Record, Dispatch>>]; -// @alpha (undocumented) -export function useScaffolderTemplateIconLinkProps(options: { - translationRef: TranslationRef; - externalRouteRef: ExternalRouteRef; -}): { - label: string; - icon: JSX_2.Element; - disabled: boolean; - href: string | undefined; -}; - // @alpha (undocumented) export const useTemplateParameterSchema: (templateRef: string) => { manifest?: TemplateParameterSchema; diff --git a/plugins/scaffolder-react/src/alpha.ts b/plugins/scaffolder-react/src/alpha.ts index 1b4935c522..e18c758b3d 100644 --- a/plugins/scaffolder-react/src/alpha.ts +++ b/plugins/scaffolder-react/src/alpha.ts @@ -17,5 +17,3 @@ export * from './next'; export { scaffolderReactTranslationRef } from './translation'; - -export { useScaffolderTemplateIconLinkProps } from './hooks/useScaffolderTemplateIconLinkProps'; diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index 05e94a446a..54acadd561 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -48,10 +48,6 @@ const _default: FrontendPlugin< }, { registerComponent: ExternalRouteRef; - createFromTemplate: ExternalRouteRef<{ - namespace: string; - templateName: string; - }>; viewTechDoc: ExternalRouteRef<{ name: string; kind: string; @@ -149,6 +145,13 @@ const _default: FrontendPlugin< optional: true; } > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > | ConfigurableExtensionDataRef< () => IconLinkVerticalProps, 'entity-icon-link-props', diff --git a/plugins/scaffolder-react/src/hooks/useScaffolderTemplateIconLinkProps.tsx b/plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx similarity index 71% rename from plugins/scaffolder-react/src/hooks/useScaffolderTemplateIconLinkProps.tsx rename to plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx index b59f858ce9..87754468fa 100644 --- a/plugins/scaffolder-react/src/hooks/useScaffolderTemplateIconLinkProps.tsx +++ b/plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx @@ -15,31 +15,25 @@ */ import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; -import { - ExternalRouteRef, - useApp, - useRouteRef, -} from '@backstage/core-plugin-api'; +import { useApp, useRouteRef } from '@backstage/core-plugin-api'; import { useEntity } from '@backstage/plugin-catalog-react'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { usePermission } from '@backstage/plugin-permission-react'; -import { - TranslationRef, - useTranslationRef, -} from '@backstage/core-plugin-api/alpha'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; +import { selectedTemplateRouteRef } from '../../routes'; + +// Note: If you update this hook, please also update the "useScaffolderTemplateIconLinkProps" hook +// in the "plugins/catalog/src/components/AboutCard/AboutCard.tsx" file /** @alpha */ -export function useScaffolderTemplateIconLinkProps(options: { - translationRef: TranslationRef; - externalRouteRef: ExternalRouteRef; -}) { - const { translationRef, externalRouteRef } = options; +export function useScaffolderTemplateIconLinkProps() { const app = useApp(); const { entity } = useEntity(); - const templateRoute = useRouteRef(externalRouteRef); - const { t } = useTranslationRef(translationRef); + const templateRoute = useRouteRef(selectedTemplateRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon; const { allowed: canCreateTemplateTask } = usePermission({ permission: taskCreatePermission, diff --git a/plugins/scaffolder/src/alpha/plugin.tsx b/plugins/scaffolder/src/alpha/plugin.tsx index 56f7343978..a414c61da5 100644 --- a/plugins/scaffolder/src/alpha/plugin.tsx +++ b/plugins/scaffolder/src/alpha/plugin.tsx @@ -26,7 +26,6 @@ import { selectedTemplateRouteRef, templatingExtensionsRouteRef, viewTechDocRouteRef, - createFromTemplateRouteRef, } from '../routes'; import { repoUrlPickerFormField, @@ -35,25 +34,17 @@ import { scaffolderApi, } from './extensions'; import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { - formFieldsApi, - useScaffolderTemplateIconLinkProps, -} from '@backstage/plugin-scaffolder-react/alpha'; +import { formFieldsApi } from '@backstage/plugin-scaffolder-react/alpha'; import { formDecoratorsApi } from './api'; import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha'; -import { scaffolderTranslationRef } from '../translation'; +import { useScaffolderTemplateIconLinkProps } from './hooks/useScaffolderTemplateIconLinkProps'; /** @alpha */ const scaffolderEntityIconLink = EntityIconLinkBlueprint.make({ name: 'launch-template', params: { filter: isTemplateEntityV1beta3, - useProps: () => { - return useScaffolderTemplateIconLinkProps({ - translationRef: scaffolderTranslationRef, - externalRouteRef: createFromTemplateRouteRef, - }); - }, + useProps: useScaffolderTemplateIconLinkProps, }, }); @@ -72,7 +63,6 @@ export default createFrontendPlugin({ }), externalRoutes: convertLegacyRouteRefs({ registerComponent: registerComponentRouteRef, - createFromTemplate: createFromTemplateRouteRef, viewTechDoc: viewTechDocRouteRef, }), extensions: [ diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 0a36089928..2e336757b8 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -32,13 +32,6 @@ export const viewTechDocRouteRef = createExternalRouteRef({ defaultTarget: 'techdocs.docRoot', }); -export const createFromTemplateRouteRef = createExternalRouteRef({ - id: 'create-from-template', - optional: true, - params: ['namespace', 'templateName'], - defaultTarget: 'scaffolder.selectedTemplate', -}); - /** * @public */ diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 774b3e4dae..618af87698 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -63,11 +63,9 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", - "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-techdocs-common": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", "@material-ui/styles": "^4.11.0", "jss": "~10.10.0", "lodash": "^4.17.21", diff --git a/plugins/techdocs-react/report-alpha.api.md b/plugins/techdocs-react/report-alpha.api.md index f4f4e612ed..d916d1fcb0 100644 --- a/plugins/techdocs-react/report-alpha.api.md +++ b/plugins/techdocs-react/report-alpha.api.md @@ -6,9 +6,6 @@ import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/core-plugin-api'; -import { JSX as JSX_2 } from 'react/jsx-runtime'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha export const AddonBlueprint: ExtensionBlueprint<{ @@ -62,16 +59,5 @@ export type TechDocsAddonOptions = { component: ComponentType; }; -// @alpha (undocumented) -export function useTechdocsReaderIconLinkProps(options: { - translationRef: TranslationRef; - externalRouteRef: ExternalRouteRef; -}): { - label: string; - disabled: boolean; - icon: JSX_2.Element; - href: string | undefined; -}; - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/techdocs-react/src/alpha.ts b/plugins/techdocs-react/src/alpha.ts index 2cf7878047..83c0444753 100644 --- a/plugins/techdocs-react/src/alpha.ts +++ b/plugins/techdocs-react/src/alpha.ts @@ -26,8 +26,6 @@ import { createExtensionDataRef, } from '@backstage/frontend-plugin-api'; -export { useTechdocsReaderIconLinkProps } from './hooks'; - /** @alpha */ export type { TechDocsAddonOptions, TechDocsAddonLocations } from './types'; diff --git a/plugins/techdocs-react/src/hooks.tsx b/plugins/techdocs-react/src/hooks.ts similarity index 76% rename from plugins/techdocs-react/src/hooks.tsx rename to plugins/techdocs-react/src/hooks.ts index b8819a345e..1203622cb2 100644 --- a/plugins/techdocs-react/src/hooks.tsx +++ b/plugins/techdocs-react/src/hooks.ts @@ -15,45 +15,7 @@ */ import { useEffect, useMemo, useState } from 'react'; import debounce from 'lodash/debounce'; -import DocsIcon from '@material-ui/icons/Description'; - -import { ExternalRouteRef, useRouteRef } from '@backstage/core-plugin-api'; -import { - TranslationRef, - useTranslationRef, -} from '@backstage/core-plugin-api/alpha'; - -import { - TECHDOCS_ANNOTATION, - TECHDOCS_EXTERNAL_ANNOTATION, -} from '@backstage/plugin-techdocs-common'; - -import { useEntity } from '@backstage/plugin-catalog-react'; - import { useTechDocsReaderPage } from './context'; -import { buildTechDocsURL } from './helpers'; - -/** @alpha */ -export function useTechdocsReaderIconLinkProps(options: { - translationRef: TranslationRef; - externalRouteRef: ExternalRouteRef; -}) { - const { translationRef, externalRouteRef } = options; - const { entity } = useEntity(); - const viewTechdocLink = useRouteRef(externalRouteRef); - const { t } = useTranslationRef(translationRef); - - return { - label: t('aboutCard.viewTechdocs'), - disabled: - !( - entity.metadata.annotations?.[TECHDOCS_ANNOTATION] || - entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] - ) || !viewTechdocLink, - icon: , - href: buildTechDocsURL(entity, viewTechdocLink), - }; -} /** * Hook for use within TechDocs addons that provides access to the underlying diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index f688fb9503..c79dadca05 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -30,7 +30,7 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.tsx", + "./alpha": "./src/alpha/index.tsx", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -38,7 +38,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.tsx" + "src/alpha/index.tsx" ], "package.json": [ "package.json" diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md index 69e98a4c33..ce1d10a00c 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -12,7 +12,6 @@ import { Entity } from '@backstage/catalog-model'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; @@ -22,7 +21,6 @@ import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-rea import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; import { SearchResultListItemBlueprintParams } from '@backstage/plugin-search-react/alpha'; import { TechDocsAddonOptions } from '@backstage/plugin-techdocs-react'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) const _default: FrontendPlugin< @@ -35,13 +33,7 @@ const _default: FrontendPlugin< }>; entityContent: RouteRef; }, - { - viewTechDoc: ExternalRouteRef<{ - name: string; - kind: string; - namespace: string; - }>; - }, + {}, { 'api:techdocs': ExtensionDefinition<{ kind: 'api'; @@ -203,6 +195,13 @@ const _default: FrontendPlugin< optional: true; } > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > | ConfigurableExtensionDataRef< () => IconLinkVerticalProps, 'entity-icon-link-props', @@ -381,13 +380,5 @@ export const techDocsSearchResultListItemExtension: ExtensionDefinition<{ params: SearchResultListItemBlueprintParams; }>; -// @alpha (undocumented) -export const techdocsTranslationRef: TranslationRef< - 'techdocs', - { - readonly 'aboutCard.viewTechdocs': 'View TechDocs'; - } ->; - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx b/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx new file mode 100644 index 0000000000..0cd2cf9ca1 --- /dev/null +++ b/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2025 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 DocsIcon from '@material-ui/icons/Description'; + +import { useRouteRef } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { + TECHDOCS_ANNOTATION, + TECHDOCS_EXTERNAL_ANNOTATION, +} from '@backstage/plugin-techdocs-common'; +import { buildTechDocsURL } from '@backstage/plugin-techdocs-react'; + +import { useEntity } from '@backstage/plugin-catalog-react'; + +import { techdocsTranslationRef } from '../../translation'; +import { rootDocsRouteRef } from '../../routes'; + +// Note: If you update this hook, please also update the "useTechdocsReaderIconLinkProps" hook +// in the "plugins/catalog/src/components/AboutCard/AboutCard.tsx" file +/** @alpha */ +export function useTechdocsReaderIconLinkProps() { + const { entity } = useEntity(); + const viewTechdocLink = useRouteRef(rootDocsRouteRef); + const { t } = useTranslationRef(techdocsTranslationRef); + + return { + label: t('aboutCard.viewTechdocs'), + disabled: + !( + entity.metadata.annotations?.[TECHDOCS_ANNOTATION] || + entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] + ) || !viewTechdocLink, + icon: , + href: buildTechDocsURL(entity, viewTechdocLink), + }; +} diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha/index.tsx similarity index 87% rename from plugins/techdocs/src/alpha.tsx rename to plugins/techdocs/src/alpha/index.tsx index 37cdb64ead..44205a480a 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha/index.tsx @@ -41,38 +41,27 @@ import { } from '@backstage/plugin-catalog-react/alpha'; import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha'; -import { TechDocsClient, TechDocsStorageClient } from './client'; +import { TechDocsClient, TechDocsStorageClient } from '../client'; import { rootCatalogDocsRouteRef, rootDocsRouteRef, rootRouteRef, - viewTechDocRouteRef, -} from './routes'; -import { TechDocsReaderLayout } from './reader'; -import { - attachTechDocsAddonComponentData, - useTechdocsReaderIconLinkProps, -} from '@backstage/plugin-techdocs-react/alpha'; +} from '../routes'; +import { TechDocsReaderLayout } from '../reader'; +import { attachTechDocsAddonComponentData } from '@backstage/plugin-techdocs-react/alpha'; import { TechDocsAddons, techdocsApiRef, techdocsStorageApiRef, } from '@backstage/plugin-techdocs-react'; -import { techdocsTranslationRef } from './translation'; - -export { techdocsTranslationRef } from './translation'; +import { useTechdocsReaderIconLinkProps } from './hooks/useTechdocsReaderIconLinkProps'; /** @alpha */ const techdocsEntityIconLink = EntityIconLinkBlueprint.make({ name: 'read-docs', params: { - useProps: () => { - return useTechdocsReaderIconLinkProps({ - translationRef: techdocsTranslationRef, - externalRouteRef: viewTechDocRouteRef, - }); - }, + useProps: useTechdocsReaderIconLinkProps, }, }); @@ -133,7 +122,7 @@ export const techDocsSearchResultListItemExtension = predicate: result => result.type === 'techdocs', component: async () => { const { TechDocsSearchResultListItem } = await import( - './search/components/TechDocsSearchResultListItem' + '../search/components/TechDocsSearchResultListItem' ); return props => compatWrapper( @@ -154,7 +143,7 @@ const techDocsPage = PageBlueprint.make({ defaultPath: '/docs', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => - import('./home/components/TechDocsIndexPage').then(m => + import('../home/components/TechDocsIndexPage').then(m => compatWrapper(), ), }, @@ -182,7 +171,7 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({ defaultPath: '/docs/:namespace/:kind/:name', routeRef: convertLegacyRouteRef(rootDocsRouteRef), loader: async () => - await import('./Router').then(({ TechDocsReaderRouter }) => { + await import('../Router').then(({ TechDocsReaderRouter }) => { return compatWrapper( @@ -217,7 +206,7 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ defaultTitle: 'TechDocs', routeRef: convertLegacyRouteRef(rootCatalogDocsRouteRef), loader: () => - import('./Router').then(({ EmbeddedDocsRouter }) => { + import('../Router').then(({ EmbeddedDocsRouter }) => { const addons = context.inputs.addons.map(output => { const options = output.get(AddonBlueprint.dataRefs.addon); const Addon = options.component; @@ -260,7 +249,7 @@ const techDocsNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'techdocs', - info: { packageJson: () => import('../package.json') }, + info: { packageJson: () => import('../../package.json') }, extensions: [ techDocsClientApi, techDocsStorageApi, @@ -277,7 +266,4 @@ export default createFrontendPlugin({ docRoot: rootDocsRouteRef, entityContent: rootCatalogDocsRouteRef, }), - externalRoutes: convertLegacyRouteRefs({ - viewTechDoc: viewTechDocRouteRef, - }), }); diff --git a/plugins/techdocs/src/routes.ts b/plugins/techdocs/src/routes.ts index 69f76a1627..e781550899 100644 --- a/plugins/techdocs/src/routes.ts +++ b/plugins/techdocs/src/routes.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - createRouteRef, - createExternalRouteRef, -} from '@backstage/core-plugin-api'; +import { createRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ id: 'techdocs:index-page', @@ -31,10 +28,3 @@ export const rootDocsRouteRef = createRouteRef({ export const rootCatalogDocsRouteRef = createRouteRef({ id: 'techdocs:catalog-reader-view', }); - -export const viewTechDocRouteRef = createExternalRouteRef({ - id: 'view-techdoc', - optional: true, - params: ['namespace', 'kind', 'name'], - defaultTarget: 'techdocs.docRoot', -}); diff --git a/yarn.lock b/yarn.lock index a7ca1e5817..ab61b20c89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6378,7 +6378,6 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" - "@backstage/plugin-scaffolder-react": "workspace:^" "@backstage/plugin-search-common": "workspace:^" "@backstage/plugin-search-react": "workspace:^" "@backstage/plugin-techdocs-common": "workspace:^" @@ -8532,13 +8531,11 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" - "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-techdocs-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" "@material-ui/styles": "npm:^4.11.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0"