feat: create entity icon link extensions
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': minor
|
||||
---
|
||||
|
||||
The `IconLinkVertical` component now includes an optional hide property. When set to `true`, this property completely hides the icon element.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': minor
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
---
|
||||
|
||||
Introduces a new `EntityIconLinkBlueprint` that customizes the `About` card icon links on the `Catalog` entity page.
|
||||
|
||||
The blueprint currently accepts the following `params`:
|
||||
|
||||
| Name | Description | Type | Default Value |
|
||||
| ---------- | --------------------------------------------------- | -------------------------- | ------------- |
|
||||
| `icon` | The icon to display. | `JSX.Element` | N/A |
|
||||
| `label` | The label for the element. | `string` | N/A |
|
||||
| `title` | The title for the element. | `string` | N/A |
|
||||
| `color` | The color of the element. | `'primary' \| 'secondary'` | `primary` |
|
||||
| `disabled` | Whether the element is disabled. | `boolean` | `false` |
|
||||
| `href` | The URL to navigate to when the element is clicked. | `string` | N/A |
|
||||
| `onClick` | A function to call when the element is clicked. | `() => void` | N/A |
|
||||
| `hidden` | Whether the element is hidden. | `boolean` | `false` |
|
||||
|
||||
Here is an usage example:
|
||||
|
||||
```tsx
|
||||
import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha';
|
||||
//...
|
||||
|
||||
// Defining the icon link properties using an object
|
||||
EntityIconLinkBlueprint.make({
|
||||
name: 'my-icon-link',
|
||||
params: {
|
||||
props: {
|
||||
label: 'My Icon Link Label',
|
||||
icon: <MyIconLinkIcon />,
|
||||
href: '/my-plugin',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```tsx
|
||||
import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha';
|
||||
//...
|
||||
|
||||
// Defining the icon link properties using a function
|
||||
EntityIconLinkBlueprint.make({
|
||||
name: 'my-icon-link',
|
||||
params: {
|
||||
props: function useMyIconLinkProps() {
|
||||
// Use a function when you would like use a hook for defining the
|
||||
// icon link props on runtime
|
||||
const { t } = useTranslationRef(myIconLinkTranslationRef);
|
||||
return {
|
||||
label: t('myIconLink.label'),
|
||||
icon: <MyIconLinkIcon />,
|
||||
href: '/my-plugin',
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Additionally, the `app-config.yaml` file allows you to override some of the default icon link parameters, including `label`, `title`, `color`, `href`, `disabled`, and `hidden` values. Here's how to set them:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
extensions:
|
||||
- entity-icon-link:my-plugin/my-icon-link:
|
||||
config:
|
||||
label: 'My Custom Icon Link label'
|
||||
```
|
||||
@@ -582,7 +582,8 @@ export function IconLinkVertical({
|
||||
label,
|
||||
onClick,
|
||||
title,
|
||||
}: IconLinkVerticalProps): JSX_2.Element;
|
||||
hidden,
|
||||
}: IconLinkVerticalProps): JSX_2.Element | null;
|
||||
|
||||
// @public (undocumented)
|
||||
export type IconLinkVerticalClassKey =
|
||||
@@ -603,6 +604,7 @@ export type IconLinkVerticalProps = {
|
||||
label: string;
|
||||
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||
title?: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -29,6 +29,7 @@ export type IconLinkVerticalProps = {
|
||||
label: string;
|
||||
onClick?: MouseEventHandler<HTMLAnchorElement>;
|
||||
title?: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
@@ -75,9 +76,14 @@ export function IconLinkVertical({
|
||||
label,
|
||||
onClick,
|
||||
title,
|
||||
hidden,
|
||||
}: IconLinkVerticalProps) {
|
||||
const classes = useIconStyles();
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
return (
|
||||
<Box title={title} className={classnames(classes.link, classes.disabled)}>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ExtensionBlueprint } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { IconLinkVerticalProps } from '@backstage/core-components';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
@@ -390,6 +391,44 @@ export const EntityHeaderBlueprint: ExtensionBlueprint<{
|
||||
};
|
||||
}>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const EntityIconLinkBlueprint: ExtensionBlueprint<{
|
||||
kind: 'entity-icon-link';
|
||||
name: undefined;
|
||||
params: {
|
||||
props: IconLinkVerticalProps | (() => IconLinkVerticalProps);
|
||||
};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
() => IconLinkVerticalProps,
|
||||
'entity-icon-link-props',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
config: {
|
||||
label: string | undefined;
|
||||
title: string | undefined;
|
||||
color: 'primary' | 'secondary' | undefined;
|
||||
href: string | undefined;
|
||||
hidden: boolean | undefined;
|
||||
disabled: boolean | undefined;
|
||||
};
|
||||
configInput: {
|
||||
color?: 'primary' | 'secondary' | undefined;
|
||||
hidden?: boolean | undefined;
|
||||
label?: string | undefined;
|
||||
title?: string | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
href?: string | undefined;
|
||||
};
|
||||
dataRefs: {
|
||||
props: ConfigurableExtensionDataRef<
|
||||
() => IconLinkVerticalProps,
|
||||
'entity-icon-link-props',
|
||||
{}
|
||||
>;
|
||||
};
|
||||
}>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type EntityPredicate =
|
||||
| EntityPredicateExpression
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 { IconLinkVerticalProps } from '@backstage/core-components';
|
||||
import {
|
||||
createExtensionBlueprint,
|
||||
createExtensionDataRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
const entityIconLinkPropsDataRef = createExtensionDataRef<
|
||||
() => IconLinkVerticalProps
|
||||
>().with({
|
||||
id: 'entity-icon-link-props',
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export const EntityIconLinkBlueprint = createExtensionBlueprint({
|
||||
kind: 'entity-icon-link',
|
||||
attachTo: { id: 'entity-card:catalog/about', input: 'iconLinks' },
|
||||
output: [entityIconLinkPropsDataRef],
|
||||
dataRefs: {
|
||||
props: entityIconLinkPropsDataRef,
|
||||
},
|
||||
config: {
|
||||
schema: {
|
||||
label: z => z.string().optional(),
|
||||
title: z => z.string().optional(),
|
||||
color: z => z.enum(['primary', 'secondary']).optional(),
|
||||
href: z => z.string().optional(),
|
||||
hidden: z => z.boolean().optional(),
|
||||
disabled: z => z.boolean().optional(),
|
||||
},
|
||||
},
|
||||
*factory(
|
||||
params: {
|
||||
props: IconLinkVerticalProps | (() => IconLinkVerticalProps);
|
||||
},
|
||||
{ config },
|
||||
) {
|
||||
yield entityIconLinkPropsDataRef(() => ({
|
||||
...(typeof params.props === 'function' ? params.props() : params.props),
|
||||
...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,
|
||||
// Removing the "onClick" handler if href is provided prevents the handler
|
||||
// from redirecting to a different page
|
||||
...(key === 'href' ? { onClick: undefined } : {}),
|
||||
};
|
||||
}
|
||||
return rest;
|
||||
}, {}),
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -28,3 +28,4 @@ export {
|
||||
type EntityContextMenuItemParams,
|
||||
type UseProps,
|
||||
} from './EntityContextMenuItemBlueprint';
|
||||
export { EntityIconLinkBlueprint } from './EntityIconLinkBlueprint';
|
||||
|
||||
@@ -18,6 +18,7 @@ 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';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { RouteRef } from '@backstage/frontend-plugin-api';
|
||||
import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha';
|
||||
@@ -336,8 +337,6 @@ const _default: FrontendPlugin<
|
||||
};
|
||||
}>;
|
||||
'entity-card:catalog/about': ExtensionDefinition<{
|
||||
kind: 'entity-card';
|
||||
name: 'about';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
type: 'content' | 'summary' | 'info' | undefined;
|
||||
@@ -369,7 +368,21 @@ const _default: FrontendPlugin<
|
||||
optional: true;
|
||||
}
|
||||
>;
|
||||
inputs: {};
|
||||
inputs: {
|
||||
iconLinks: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
() => IconLinkVerticalProps,
|
||||
'entity-icon-link-props',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'entity-card';
|
||||
name: 'about';
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
@@ -923,6 +936,93 @@ const _default: FrontendPlugin<
|
||||
inputs: {};
|
||||
params: EntityContextMenuItemParams;
|
||||
}>;
|
||||
'entity-icon-link:catalog/catalog-view-source': ExtensionDefinition<{
|
||||
kind: 'entity-icon-link';
|
||||
name: 'catalog-view-source';
|
||||
config: {
|
||||
label: string | undefined;
|
||||
title: string | undefined;
|
||||
color: 'primary' | 'secondary' | undefined;
|
||||
href: string | undefined;
|
||||
hidden: boolean | undefined;
|
||||
disabled: boolean | undefined;
|
||||
};
|
||||
configInput: {
|
||||
color?: 'primary' | 'secondary' | undefined;
|
||||
hidden?: boolean | undefined;
|
||||
label?: string | undefined;
|
||||
title?: string | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
href?: string | undefined;
|
||||
};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
() => IconLinkVerticalProps,
|
||||
'entity-icon-link-props',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
props: IconLinkVerticalProps | (() => IconLinkVerticalProps);
|
||||
};
|
||||
}>;
|
||||
'entity-icon-link:catalog/scaffolder-launch-template': ExtensionDefinition<{
|
||||
kind: 'entity-icon-link';
|
||||
name: 'scaffolder-launch-template';
|
||||
config: {
|
||||
label: string | undefined;
|
||||
title: string | undefined;
|
||||
color: 'primary' | 'secondary' | undefined;
|
||||
href: string | undefined;
|
||||
hidden: boolean | undefined;
|
||||
disabled: boolean | undefined;
|
||||
};
|
||||
configInput: {
|
||||
color?: 'primary' | 'secondary' | undefined;
|
||||
hidden?: boolean | undefined;
|
||||
label?: string | undefined;
|
||||
title?: string | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
href?: string | undefined;
|
||||
};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
() => IconLinkVerticalProps,
|
||||
'entity-icon-link-props',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
props: IconLinkVerticalProps | (() => IconLinkVerticalProps);
|
||||
};
|
||||
}>;
|
||||
'entity-icon-link:catalog/techdocs-view-documentation': ExtensionDefinition<{
|
||||
kind: 'entity-icon-link';
|
||||
name: 'techdocs-view-documentation';
|
||||
config: {
|
||||
label: string | undefined;
|
||||
title: string | undefined;
|
||||
color: 'primary' | 'secondary' | undefined;
|
||||
href: string | undefined;
|
||||
hidden: boolean | undefined;
|
||||
disabled: boolean | undefined;
|
||||
};
|
||||
configInput: {
|
||||
color?: 'primary' | 'secondary' | undefined;
|
||||
hidden?: boolean | undefined;
|
||||
label?: string | undefined;
|
||||
title?: string | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
href?: string | undefined;
|
||||
};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
() => IconLinkVerticalProps,
|
||||
'entity-icon-link-props',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
params: {
|
||||
props: IconLinkVerticalProps | (() => IconLinkVerticalProps);
|
||||
};
|
||||
}>;
|
||||
'nav-item:catalog': ExtensionDefinition<{
|
||||
kind: 'nav-item';
|
||||
name: undefined;
|
||||
|
||||
@@ -42,6 +42,8 @@ import { UserListFilterKind } from '@backstage/plugin-catalog-react';
|
||||
|
||||
// @public
|
||||
export interface AboutCardProps {
|
||||
// (undocumented)
|
||||
subheader?: JSX.Element;
|
||||
// (undocumented)
|
||||
variant?: InfoCardVariants;
|
||||
}
|
||||
|
||||
@@ -14,17 +14,37 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';
|
||||
import {
|
||||
EntityIconLinkBlueprint,
|
||||
EntityCardBlueprint,
|
||||
} from '@backstage/plugin-catalog-react/alpha';
|
||||
import { compatWrapper } from '@backstage/core-compat-api';
|
||||
import { createExtensionInput } from '@backstage/frontend-plugin-api';
|
||||
import { HeaderIconLinkRow } from '@backstage/core-components';
|
||||
|
||||
export const catalogAboutEntityCard = EntityCardBlueprint.make({
|
||||
export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({
|
||||
name: 'about',
|
||||
params: {
|
||||
type: 'info',
|
||||
loader: async () =>
|
||||
import('../components/AboutCard').then(m =>
|
||||
compatWrapper(<m.AboutCard variant="gridItem" />),
|
||||
),
|
||||
inputs: {
|
||||
iconLinks: createExtensionInput([EntityIconLinkBlueprint.dataRefs.props]),
|
||||
},
|
||||
factory(originalFactory, { inputs }) {
|
||||
function Subheader() {
|
||||
// The props input functions may be calling other hooks, so we need to
|
||||
// call them in a component function to avoid breaking the rules of hooks.
|
||||
const links = inputs.iconLinks.map(iconLink =>
|
||||
iconLink.get(EntityIconLinkBlueprint.dataRefs.props)(),
|
||||
);
|
||||
return <HeaderIconLinkRow links={links} />;
|
||||
}
|
||||
return originalFactory({
|
||||
type: 'info',
|
||||
async loader() {
|
||||
const { AboutCard } = await import('../components/AboutCard');
|
||||
return compatWrapper(
|
||||
<AboutCard variant="gridItem" subheader={<Subheader />} />,
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha';
|
||||
import {
|
||||
useCatalogViewSourceEntityIconLinkProps,
|
||||
useScaffolderLaunchTemplateEntityIconLinkProps,
|
||||
useTechdocsViewDocumentionIconLinkProps,
|
||||
} from '../components/AboutCard/AboutCard';
|
||||
|
||||
const catalogViewSourceEntityIconLink = EntityIconLinkBlueprint.make({
|
||||
name: 'catalog-view-source',
|
||||
params: {
|
||||
props: useCatalogViewSourceEntityIconLinkProps,
|
||||
},
|
||||
});
|
||||
|
||||
const techdocsViewDocumentationAboutEntityLink = EntityIconLinkBlueprint.make({
|
||||
name: 'techdocs-view-documentation',
|
||||
params: {
|
||||
props: useTechdocsViewDocumentionIconLinkProps,
|
||||
},
|
||||
});
|
||||
|
||||
const scaffolderLaunchTemplateEntityIconLink = EntityIconLinkBlueprint.make({
|
||||
name: 'scaffolder-launch-template',
|
||||
params: {
|
||||
props: useScaffolderLaunchTemplateEntityIconLinkProps,
|
||||
},
|
||||
});
|
||||
|
||||
export default [
|
||||
catalogViewSourceEntityIconLink,
|
||||
techdocsViewDocumentationAboutEntityLink,
|
||||
scaffolderLaunchTemplateEntityIconLink,
|
||||
];
|
||||
@@ -33,6 +33,7 @@ import filters from './filters';
|
||||
import navItems from './navItems';
|
||||
import entityCards from './entityCards';
|
||||
import entityContents from './entityContents';
|
||||
import entityIconLinks from './entityIconLinks';
|
||||
import searchResultItems from './searchResultItems';
|
||||
import contextMenuItems from './contextMenuItems';
|
||||
|
||||
@@ -57,6 +58,7 @@ export default createFrontendPlugin({
|
||||
...navItems,
|
||||
...entityCards,
|
||||
...entityContents,
|
||||
...entityIconLinks,
|
||||
...contextMenuItems,
|
||||
...searchResultItems,
|
||||
],
|
||||
|
||||
@@ -28,7 +28,6 @@ import { makeStyles } from '@material-ui/core/styles';
|
||||
import {
|
||||
AppIcon,
|
||||
HeaderIconLinkRow,
|
||||
IconLinkVerticalProps,
|
||||
InfoCardVariants,
|
||||
Link,
|
||||
} from '@backstage/core-components';
|
||||
@@ -70,6 +69,63 @@ import {
|
||||
TECHDOCS_EXTERNAL_ANNOTATION,
|
||||
} from '@backstage/plugin-techdocs-common';
|
||||
|
||||
export function useCatalogViewSourceEntityIconLinkProps() {
|
||||
const { entity } = useEntity();
|
||||
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
|
||||
const { t } = useTranslationRef(catalogTranslationRef);
|
||||
const entitySourceLocation = getEntitySourceLocation(
|
||||
entity,
|
||||
scmIntegrationsApi,
|
||||
);
|
||||
return {
|
||||
label: t('aboutCard.viewSource'),
|
||||
disabled: !entitySourceLocation,
|
||||
icon: <ScmIntegrationIcon type={entitySourceLocation?.integrationType} />,
|
||||
href: entitySourceLocation?.locationTargetUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function useTechdocsViewDocumentionIconLinkProps() {
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
export function useScaffolderLaunchTemplateEntityIconLinkProps() {
|
||||
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 />,
|
||||
hidden: !isTemplateEntityV1beta3(entity),
|
||||
disabled: !templateRoute || !canCreateTemplateTask,
|
||||
href:
|
||||
templateRoute &&
|
||||
templateRoute({
|
||||
templateName: entity.metadata.name,
|
||||
namespace: entity.metadata.namespace || DEFAULT_NAMESPACE,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const useStyles = makeStyles({
|
||||
gridItemCard: {
|
||||
display: 'flex',
|
||||
@@ -97,6 +153,7 @@ const useStyles = makeStyles({
|
||||
*/
|
||||
export interface AboutCardProps {
|
||||
variant?: InfoCardVariants;
|
||||
subheader?: JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,15 +164,12 @@ export interface AboutCardProps {
|
||||
* card in your own repository instead, that is perfect for your own needs.
|
||||
*/
|
||||
export function AboutCard(props: AboutCardProps) {
|
||||
const { variant } = props;
|
||||
const app = useApp();
|
||||
const { variant, subheader } = props;
|
||||
const classes = useStyles();
|
||||
const { entity } = useEntity();
|
||||
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const viewTechdocLink = useRouteRef(viewTechDocRouteRef);
|
||||
const templateRoute = useRouteRef(createFromTemplateRouteRef);
|
||||
const sourceTemplateRef = useSourceTemplateCompoundEntityRef(entity);
|
||||
const { allowed: canRefresh } = useEntityPermission(
|
||||
@@ -123,53 +177,14 @@ export function AboutCard(props: AboutCardProps) {
|
||||
);
|
||||
const { t } = useTranslationRef(catalogTranslationRef);
|
||||
|
||||
const { allowed: canCreateTemplateTask } = usePermission({
|
||||
permission: taskCreatePermission,
|
||||
});
|
||||
|
||||
const entitySourceLocation = getEntitySourceLocation(
|
||||
entity,
|
||||
scmIntegrationsApi,
|
||||
);
|
||||
const entityMetadataEditUrl =
|
||||
entity.metadata.annotations?.[ANNOTATION_EDIT_URL];
|
||||
|
||||
const viewInSource: IconLinkVerticalProps = {
|
||||
label: t('aboutCard.viewSource'),
|
||||
disabled: !entitySourceLocation,
|
||||
icon: <ScmIntegrationIcon type={entitySourceLocation?.integrationType} />,
|
||||
href: entitySourceLocation?.locationTargetUrl,
|
||||
};
|
||||
const viewInTechDocs: IconLinkVerticalProps = {
|
||||
label: t('aboutCard.viewTechdocs'),
|
||||
disabled:
|
||||
!(
|
||||
entity.metadata.annotations?.[TECHDOCS_ANNOTATION] ||
|
||||
entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]
|
||||
) || !viewTechdocLink,
|
||||
icon: <DocsIcon />,
|
||||
href: buildTechDocsURL(entity, viewTechdocLink),
|
||||
};
|
||||
|
||||
const subHeaderLinks = [viewInSource, viewInTechDocs];
|
||||
|
||||
if (isTemplateEntityV1beta3(entity)) {
|
||||
const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon;
|
||||
|
||||
const launchTemplate: IconLinkVerticalProps = {
|
||||
label: t('aboutCard.launchTemplate'),
|
||||
icon: <Icon />,
|
||||
disabled: !templateRoute || !canCreateTemplateTask,
|
||||
href:
|
||||
templateRoute &&
|
||||
templateRoute({
|
||||
templateName: entity.metadata.name,
|
||||
namespace: entity.metadata.namespace || DEFAULT_NAMESPACE,
|
||||
}),
|
||||
};
|
||||
|
||||
subHeaderLinks.push(launchTemplate);
|
||||
}
|
||||
const viewCatalogSourceIconLink = useCatalogViewSourceEntityIconLinkProps();
|
||||
const viewTechdocsDocumentationIconLink =
|
||||
useTechdocsViewDocumentionIconLinkProps();
|
||||
const launchScaffolderTemplateIconLink =
|
||||
useScaffolderLaunchTemplateEntityIconLinkProps();
|
||||
|
||||
let cardClass = '';
|
||||
if (variant === 'gridItem') {
|
||||
@@ -240,7 +255,17 @@ export function AboutCard(props: AboutCardProps) {
|
||||
)}
|
||||
</>
|
||||
}
|
||||
subheader={<HeaderIconLinkRow links={subHeaderLinks} />}
|
||||
subheader={
|
||||
subheader ?? (
|
||||
<HeaderIconLinkRow
|
||||
links={[
|
||||
viewCatalogSourceIconLink,
|
||||
viewTechdocsDocumentationIconLink,
|
||||
launchScaffolderTemplateIconLink,
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent className={cardContentClass}>
|
||||
|
||||
Reference in New Issue
Block a user