refactor: apply second round of review suggestions

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2025-06-03 09:07:32 +02:00
parent 3c59ece2e0
commit 8ee146cea9
30 changed files with 260 additions and 265 deletions
+2 -2
View File
@@ -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`).
-6
View File
@@ -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.
+1
View File
@@ -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.
-6
View File
@@ -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.
+1
View File
@@ -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.
@@ -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
+2 -2
View File
@@ -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';
+12
View File
@@ -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',
{}
>;
};
}>;
@@ -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<IconLinkVerticalProps, 'color'>;
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 }));
},
});
-1
View File
@@ -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:^",
+14
View File
@@ -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',
+3 -3
View File
@@ -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<InternalAboutCardProps, 'variant'>;
export type AboutCardProps = {
variant?: InfoCardVariants;
};
// @public (undocumented)
export function AboutContent(props: AboutContentProps): JSX_2.Element;
+7 -4
View File
@@ -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];
}
@@ -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: <DocsIcon />,
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: <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<InternalAboutCardProps, 'variant'>;
/**
* Exported publicly via the EntityAboutCard
*
+1 -13
View File
@@ -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<string, JsonValue>,
) => [Record<string, any>, Dispatch<SetStateAction<Record<string, any>>>];
// @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;
-2
View File
@@ -17,5 +17,3 @@
export * from './next';
export { scaffolderReactTranslationRef } from './translation';
export { useScaffolderTemplateIconLinkProps } from './hooks/useScaffolderTemplateIconLinkProps';
+7 -4
View File
@@ -48,10 +48,6 @@ const _default: FrontendPlugin<
},
{
registerComponent: ExternalRouteRef<undefined>;
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',
@@ -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,
+3 -13
View File
@@ -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: [
-7
View File
@@ -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
*/
-2
View File
@@ -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",
@@ -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<TAddonProps = {}> = {
component: ComponentType<TAddonProps>;
};
// @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)
```
-2
View File
@@ -26,8 +26,6 @@ import {
createExtensionDataRef,
} from '@backstage/frontend-plugin-api';
export { useTechdocsReaderIconLinkProps } from './hooks';
/** @alpha */
export type { TechDocsAddonOptions, TechDocsAddonLocations } from './types';
@@ -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: <DocsIcon />,
href: buildTechDocsURL(entity, viewTechdocLink),
};
}
/**
* Hook for use within TechDocs addons that provides access to the underlying
+2 -2
View File
@@ -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"
+8 -17
View File
@@ -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<undefined>;
},
{
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)
```
@@ -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: <DocsIcon />,
href: buildTechDocsURL(entity, viewTechdocLink),
};
}
@@ -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(<m.TechDocsIndexPage />),
),
},
@@ -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(
<TechDocsReaderRouter>
<TechDocsReaderLayout />
@@ -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,
}),
});
+1 -11
View File
@@ -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',
});
-3
View File
@@ -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"