diff --git a/.changeset/add-entity-data-table-and-relation-card.md b/.changeset/add-entity-data-table-and-relation-card.md
new file mode 100644
index 0000000000..98c8cd9081
--- /dev/null
+++ b/.changeset/add-entity-data-table-and-relation-card.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': minor
+---
+
+Added `EntityDataTable`, `EntityRelationCard`, `entityDataTableColumns`, `entityColumnPresets`, and related types as alpha exports. These replace `EntityTable` and `RelatedEntitiesCard` (from `@backstage/plugin-catalog`) respectively, providing a unified BUI-based pattern for entity table cards.
diff --git a/.changeset/migrate-api-docs-entity-cards-to-bui.md b/.changeset/migrate-api-docs-entity-cards-to-bui.md
new file mode 100644
index 0000000000..afc44a832d
--- /dev/null
+++ b/.changeset/migrate-api-docs-entity-cards-to-bui.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-api-docs': patch
+---
+
+Migrated entity table cards (`ConsumedApisCard`, `ProvidedApisCard`, `HasApisCard`, `ConsumingComponentsCard`, `ProvidingComponentsCard`) to use BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing any of them triggers the legacy MUI-based rendering. The new `columnConfig` prop accepts `EntityColumnConfig[]` for BUI-based rendering.
diff --git a/.changeset/migrate-catalog-entity-cards-to-bui.md b/.changeset/migrate-catalog-entity-cards-to-bui.md
new file mode 100644
index 0000000000..84b91549a6
--- /dev/null
+++ b/.changeset/migrate-catalog-entity-cards-to-bui.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-catalog': minor
+---
+
+Migrated entity table cards (`HasComponentsCard`, `HasResourcesCard`, `HasSubcomponentsCard`, `HasSubdomainsCard`, `HasSystemsCard`, `DependsOnComponentsCard`, `DependsOnResourcesCard`, `DependencyOfComponentsCard`) to use BUI when no legacy props are passed. The old `variant`, `columns`, and `tableOptions` props are deprecated but still supported — passing any of them triggers the legacy MUI-based rendering. The new `columnConfig` prop accepts `EntityColumnConfig[]` for BUI-based rendering.
+
+`RelatedEntitiesCard` is deprecated — use `EntityRelationCard` from `@backstage/plugin-catalog-react/alpha` instead.
diff --git a/packages/app-legacy/src/components/catalog/EntityPage.tsx b/packages/app-legacy/src/components/catalog/EntityPage.tsx
index 7c42b62542..6a8daffad6 100644
--- a/packages/app-legacy/src/components/catalog/EntityPage.tsx
+++ b/packages/app-legacy/src/components/catalog/EntityPage.tsx
@@ -186,7 +186,7 @@ const overviewContent = (
-
+
);
@@ -215,10 +215,10 @@ const serviceEntityPage = (
-
+
-
+
@@ -246,10 +246,10 @@ const websiteEntityPage = (
-
+
-
+
@@ -374,13 +374,13 @@ const systemPage = (
-
+
-
+
-
+
@@ -417,7 +417,7 @@ const domainPage = (
-
+
@@ -436,7 +436,7 @@ const resourcePage = (
-
+
diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
index 367652b70d..d229b536cb 100644
--- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
@@ -138,7 +138,7 @@ const overviewContent = (
-
+
);
@@ -175,10 +175,10 @@ const serviceEntityPage = (
-
+
-
+
@@ -210,10 +210,10 @@ const websiteEntityPage = (
-
+
-
+
@@ -345,13 +345,13 @@ const systemPage = (
-
+
-
+
-
+
@@ -388,7 +388,7 @@ const domainPage = (
-
+
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index a0d50e78ab..7dcc9fd958 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -62,10 +62,12 @@
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
+ "@backstage/ui": "workspace:^",
"@graphiql/react": "0.29.0",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
+ "@remixicon/react": "^4.6.0",
"graphiql": "^3.9.0",
"graphql": "^16.0.0",
"graphql-ws": "^5.4.1",
diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md
index 0ee7ac5460..4afa55ce77 100644
--- a/plugins/api-docs/report-alpha.api.md
+++ b/plugins/api-docs/report-alpha.api.md
@@ -58,7 +58,7 @@ export const apiDocsTranslationRef: TranslationRef<
readonly 'providingComponentsCard.error.title': 'Could not load components';
readonly 'providingComponentsCard.title': 'Providers';
readonly 'providingComponentsCard.emptyContent.title': 'No component provides this API.';
- readonly apisCardHelpLinkTitle: 'Learn how to change this';
+ readonly apisCardHelpLinkTitle: 'Learn how to change this.';
}
>;
diff --git a/plugins/api-docs/report.api.md b/plugins/api-docs/report.api.md
index 693502fad9..370cbb3e0e 100644
--- a/plugins/api-docs/report.api.md
+++ b/plugins/api-docs/report.api.md
@@ -8,6 +8,7 @@ import { ApiRef } from '@backstage/frontend-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { CatalogTableRow } from '@backstage/plugin-catalog';
import { ComponentEntity } from '@backstage/catalog-model';
+import { EntityColumnConfig } from '@backstage/plugin-catalog-react/alpha';
import { EntityListPagination } from '@backstage/plugin-catalog-react';
import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
@@ -94,20 +95,54 @@ export type AsyncApiResolver = {
};
// @public (undocumented)
-export const ConsumedApisCard: (props: {
- variant?: InfoCardVariants;
- title?: string;
+export const ConsumedApisCard: (
+ props: ConsumedApisCardProps | ConsumedApisCardLegacyProps,
+) => JSX_2.Element;
+
+// @public @deprecated
+export interface ConsumedApisCardLegacyProps {
+ // @deprecated (undocumented)
columns?: TableColumn[];
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
-}) => JSX_2.Element;
+ // (undocumented)
+ title?: string;
+ // @deprecated (undocumented)
+ variant?: InfoCardVariants;
+}
// @public (undocumented)
-export const ConsumingComponentsCard: (props: {
- variant?: InfoCardVariants;
+export interface ConsumedApisCardProps {
+ // (undocumented)
+ columnConfig?: EntityColumnConfig[];
+ // (undocumented)
title?: string;
+}
+
+// @public (undocumented)
+export const ConsumingComponentsCard: (
+ props: ConsumingComponentsCardProps | ConsumingComponentsCardLegacyProps,
+) => JSX_2.Element;
+
+// @public @deprecated
+export interface ConsumingComponentsCardLegacyProps {
+ // @deprecated (undocumented)
columns?: TableColumn[];
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
-}) => JSX_2.Element;
+ // (undocumented)
+ title?: string;
+ // @deprecated (undocumented)
+ variant?: InfoCardVariants;
+}
+
+// @public (undocumented)
+export interface ConsumingComponentsCardProps {
+ // (undocumented)
+ columnConfig?: EntityColumnConfig[];
+ // (undocumented)
+ title?: string;
+}
// @public
export const DefaultApiExplorerPage: (
@@ -130,44 +165,29 @@ export function defaultDefinitionWidgets(): ApiDefinitionWidget[];
export const EntityApiDefinitionCard: () => JSX_2.Element;
// @public (undocumented)
-export const EntityConsumedApisCard: (props: {
- variant?: InfoCardVariants;
- title?: string;
- columns?: TableColumn[];
- tableOptions?: TableOptions;
-}) => JSX_2.Element;
+export const EntityConsumedApisCard: (
+ props: ConsumedApisCardProps | ConsumedApisCardLegacyProps,
+) => JSX_2.Element;
// @public (undocumented)
-export const EntityConsumingComponentsCard: (props: {
- variant?: InfoCardVariants;
- title?: string;
- columns?: TableColumn[];
- tableOptions?: TableOptions;
-}) => JSX_2.Element;
+export const EntityConsumingComponentsCard: (
+ props: ConsumingComponentsCardProps | ConsumingComponentsCardLegacyProps,
+) => JSX_2.Element;
// @public (undocumented)
-export const EntityHasApisCard: (props: {
- variant?: InfoCardVariants;
- title?: string;
- columns?: TableColumn[];
- tableOptions?: TableOptions;
-}) => JSX_2.Element;
+export const EntityHasApisCard: (
+ props: HasApisCardProps | HasApisCardLegacyProps,
+) => JSX_2.Element;
// @public (undocumented)
-export const EntityProvidedApisCard: (props: {
- variant?: InfoCardVariants;
- title?: string;
- columns?: TableColumn[];
- tableOptions?: TableOptions;
-}) => JSX_2.Element;
+export const EntityProvidedApisCard: (
+ props: ProvidedApisCardProps | ProvidedApisCardLegacyProps,
+) => JSX_2.Element;
// @public (undocumented)
-export const EntityProvidingComponentsCard: (props: {
- variant?: InfoCardVariants;
- title?: string;
- columns?: TableColumn[];
- tableOptions?: TableOptions;
-}) => JSX_2.Element;
+export const EntityProvidingComponentsCard: (
+ props: ProvidingComponentsCardProps | ProvidingComponentsCardLegacyProps,
+) => JSX_2.Element;
// @public (undocumented)
export const GraphQlDefinitionWidget: (
@@ -180,12 +200,29 @@ export type GraphQlDefinitionWidgetProps = {
};
// @public (undocumented)
-export const HasApisCard: (props: {
- variant?: InfoCardVariants;
- title?: string;
+export const HasApisCard: (
+ props: HasApisCardProps | HasApisCardLegacyProps,
+) => JSX_2.Element;
+
+// @public @deprecated
+export interface HasApisCardLegacyProps {
+ // @deprecated (undocumented)
columns?: TableColumn[];
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
-}) => JSX_2.Element;
+ // (undocumented)
+ title?: string;
+ // @deprecated (undocumented)
+ variant?: InfoCardVariants;
+}
+
+// @public (undocumented)
+export interface HasApisCardProps {
+ // (undocumented)
+ columnConfig?: EntityColumnConfig[];
+ // (undocumented)
+ title?: string;
+}
// @public (undocumented)
export const OpenApiDefinitionWidget: (
@@ -211,20 +248,54 @@ export type PlainApiDefinitionWidgetProps = {
};
// @public (undocumented)
-export const ProvidedApisCard: (props: {
- variant?: InfoCardVariants;
- title?: string;
+export const ProvidedApisCard: (
+ props: ProvidedApisCardProps | ProvidedApisCardLegacyProps,
+) => JSX_2.Element;
+
+// @public @deprecated
+export interface ProvidedApisCardLegacyProps {
+ // @deprecated (undocumented)
columns?: TableColumn[];
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
-}) => JSX_2.Element;
+ // (undocumented)
+ title?: string;
+ // @deprecated (undocumented)
+ variant?: InfoCardVariants;
+}
// @public (undocumented)
-export const ProvidingComponentsCard: (props: {
- variant?: InfoCardVariants;
+export interface ProvidedApisCardProps {
+ // (undocumented)
+ columnConfig?: EntityColumnConfig[];
+ // (undocumented)
title?: string;
+}
+
+// @public (undocumented)
+export const ProvidingComponentsCard: (
+ props: ProvidingComponentsCardProps | ProvidingComponentsCardLegacyProps,
+) => JSX_2.Element;
+
+// @public @deprecated
+export interface ProvidingComponentsCardLegacyProps {
+ // @deprecated (undocumented)
columns?: TableColumn[];
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
-}) => JSX_2.Element;
+ // (undocumented)
+ title?: string;
+ // @deprecated (undocumented)
+ variant?: InfoCardVariants;
+}
+
+// @public (undocumented)
+export interface ProvidingComponentsCardProps {
+ // (undocumented)
+ columnConfig?: EntityColumnConfig[];
+ // (undocumented)
+ title?: string;
+}
// @public (undocumented)
export const TrpcApiDefinitionWidget: (
diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
index e8ed6bedb5..b62616e98c 100644
--- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
+++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
@@ -60,7 +60,7 @@ describe('', () => {
relations: [],
};
- const { getByText, getByRole, container } = await renderInTestApp(
+ const { getByText, getByRole } = await renderInTestApp(
@@ -76,16 +76,11 @@ describe('', () => {
expect(getByText(/Consumed APIs/i)).toBeInTheDocument();
expect(getByText(/does not consume any APIs/i)).toBeInTheDocument();
- // Also render external link icon
const externalLink = getByRole('link');
expect(externalLink).toHaveAttribute(
'href',
'https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional',
);
- const externalLinkIcon: HTMLElement | null = container.querySelector(
- 'svg[class*="externalLink"]',
- );
- expect(externalLink).toContainElement(externalLinkIcon);
});
it('shows consumed APIs', async () => {
diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx
index 5dc6e4a473..2e289a7009 100644
--- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx
+++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx
@@ -21,7 +21,11 @@ import {
useEntity,
useRelatedEntities,
} from '@backstage/plugin-catalog-react';
-import { getApiEntityColumns } from './presets';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+} from '@backstage/plugin-catalog-react/alpha';
+import { getApiEntityColumns, getApiEntityColumnConfig } from './presets';
import {
CodeSnippet,
InfoCard,
@@ -35,15 +39,34 @@ import {
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { apiDocsTranslationRef } from '../../translation';
+/** @public */
+export interface ConsumedApisCardProps {
+ title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link ConsumedApisCardProps} instead.
* @public
*/
-export const ConsumedApisCard = (props: {
- variant?: InfoCardVariants;
+export interface ConsumedApisCardLegacyProps {
title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
-}) => {
+}
+
+function isLegacyProps(
+ props: ConsumedApisCardProps | ConsumedApisCardLegacyProps,
+): props is ConsumedApisCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+function ConsumedApisCardLegacy(props: ConsumedApisCardLegacyProps) {
const { t } = useTranslationRef(apiDocsTranslationRef);
const {
variant = 'gridItem',
@@ -102,4 +125,38 @@ export const ConsumedApisCard = (props: {
entities={entities as ApiEntity[]}
/>
);
+}
+
+/**
+ * @public
+ */
+export const ConsumedApisCard = (
+ props: ConsumedApisCardProps | ConsumedApisCardLegacyProps,
+) => {
+ const { t } = useTranslationRef(apiDocsTranslationRef);
+ const { entity } = useEntity();
+
+ if (isLegacyProps(props)) {
+ return ;
+ }
+
+ const {
+ title = t('consumedApisCard.title'),
+ columnConfig = getApiEntityColumnConfig(t),
+ } = props;
+
+ return (
+
+ );
};
diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx
index f0777e87a8..66f6d0b4f4 100644
--- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx
+++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx
@@ -21,8 +21,12 @@ import {
useEntity,
useRelatedEntities,
} from '@backstage/plugin-catalog-react';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+} from '@backstage/plugin-catalog-react/alpha';
import { useMemo } from 'react';
-import { createSpecApiTypeColumn } from './presets';
+import { createSpecApiTypeColumn, getHasApisColumnConfig } from './presets';
import {
CodeSnippet,
InfoCard,
@@ -36,15 +40,34 @@ import {
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { apiDocsTranslationRef } from '../../translation';
+/** @public */
+export interface HasApisCardProps {
+ title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link HasApisCardProps} instead.
* @public
*/
-export const HasApisCard = (props: {
- variant?: InfoCardVariants;
+export interface HasApisCardLegacyProps {
title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
-}) => {
+}
+
+function isLegacyProps(
+ props: HasApisCardProps | HasApisCardLegacyProps,
+): props is HasApisCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+function HasApisCardLegacy(props: HasApisCardLegacyProps) {
const { t } = useTranslationRef(apiDocsTranslationRef);
const presetColumns: TableColumn[] = useMemo(() => {
return [
@@ -110,4 +133,39 @@ export const HasApisCard = (props: {
entities={entities as ApiEntity[]}
/>
);
+}
+
+/**
+ * @public
+ */
+export const HasApisCard = (
+ props: HasApisCardProps | HasApisCardLegacyProps,
+) => {
+ const { t } = useTranslationRef(apiDocsTranslationRef);
+ const { entity } = useEntity();
+
+ if (isLegacyProps(props)) {
+ return ;
+ }
+
+ const {
+ title = t('hasApisCard.title'),
+ columnConfig = getHasApisColumnConfig(t),
+ } = props;
+
+ return (
+
+ );
};
diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
index df478566e9..b49da7a9a1 100644
--- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
+++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
@@ -60,7 +60,7 @@ describe('', () => {
relations: [],
};
- const { getByText, getByRole, container } = await renderInTestApp(
+ const { getByText, getByRole } = await renderInTestApp(
@@ -77,16 +77,11 @@ describe('', () => {
expect(getByText(/does not provide any APIs/i)).toBeInTheDocument();
expect(getByText(/Learn how to change this/)).toBeInTheDocument();
- // Also render external link icon
const externalLink = getByRole('link');
expect(externalLink).toHaveAttribute(
'href',
'https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional',
);
- const externalLinkIcon: HTMLElement | null = container.querySelector(
- 'svg[class*="externalLink"]',
- );
- expect(externalLink).toContainElement(externalLinkIcon);
});
it('shows consumed APIs', async () => {
diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx
index 784bfe2a2a..f99c10e176 100644
--- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx
+++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx
@@ -21,7 +21,11 @@ import {
useEntity,
useRelatedEntities,
} from '@backstage/plugin-catalog-react';
-import { getApiEntityColumns } from './presets';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+} from '@backstage/plugin-catalog-react/alpha';
+import { getApiEntityColumns, getApiEntityColumnConfig } from './presets';
import {
CodeSnippet,
InfoCard,
@@ -35,15 +39,34 @@ import {
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { apiDocsTranslationRef } from '../../translation';
+/** @public */
+export interface ProvidedApisCardProps {
+ title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link ProvidedApisCardProps} instead.
* @public
*/
-export const ProvidedApisCard = (props: {
- variant?: InfoCardVariants;
+export interface ProvidedApisCardLegacyProps {
title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
-}) => {
+}
+
+function isLegacyProps(
+ props: ProvidedApisCardProps | ProvidedApisCardLegacyProps,
+): props is ProvidedApisCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+function ProvidedApisCardLegacy(props: ProvidedApisCardLegacyProps) {
const { t } = useTranslationRef(apiDocsTranslationRef);
const {
variant = 'gridItem',
@@ -102,4 +125,38 @@ export const ProvidedApisCard = (props: {
entities={entities as ApiEntity[]}
/>
);
+}
+
+/**
+ * @public
+ */
+export const ProvidedApisCard = (
+ props: ProvidedApisCardProps | ProvidedApisCardLegacyProps,
+) => {
+ const { t } = useTranslationRef(apiDocsTranslationRef);
+ const { entity } = useEntity();
+
+ if (isLegacyProps(props)) {
+ return ;
+ }
+
+ const {
+ title = t('providedApisCard.title'),
+ columnConfig = getApiEntityColumnConfig(t),
+ } = props;
+
+ return (
+
+ );
};
diff --git a/plugins/api-docs/src/components/ApisCards/index.ts b/plugins/api-docs/src/components/ApisCards/index.ts
index 4a74549755..d78454bf28 100644
--- a/plugins/api-docs/src/components/ApisCards/index.ts
+++ b/plugins/api-docs/src/components/ApisCards/index.ts
@@ -15,5 +15,14 @@
*/
export { ConsumedApisCard } from './ConsumedApisCard';
+export type {
+ ConsumedApisCardLegacyProps,
+ ConsumedApisCardProps,
+} from './ConsumedApisCard';
export { HasApisCard } from './HasApisCard';
+export type { HasApisCardLegacyProps, HasApisCardProps } from './HasApisCard';
export { ProvidedApisCard } from './ProvidedApisCard';
+export type {
+ ProvidedApisCardLegacyProps,
+ ProvidedApisCardProps,
+} from './ProvidedApisCard';
diff --git a/plugins/api-docs/src/components/ApisCards/presets.tsx b/plugins/api-docs/src/components/ApisCards/presets.tsx
index 8341ce2c2b..3489797eb6 100644
--- a/plugins/api-docs/src/components/ApisCards/presets.tsx
+++ b/plugins/api-docs/src/components/ApisCards/presets.tsx
@@ -17,8 +17,12 @@
import { ApiEntity } from '@backstage/catalog-model';
import { TableColumn } from '@backstage/core-components';
import { EntityTable } from '@backstage/plugin-catalog-react';
-import ExtensionIcon from '@material-ui/icons/Extension';
-import ToggleButton from '@material-ui/lab/ToggleButton';
+import {
+ EntityColumnConfig,
+ entityDataTableColumns,
+} from '@backstage/plugin-catalog-react/alpha';
+import { ButtonIcon, Cell } from '@backstage/ui';
+import { RiPuzzleLine } from '@remixicon/react';
import { useState } from 'react';
import { ApiTypeTitle } from '../ApiDefinitionCard';
import { ApiDefinitionDialog } from '../ApiDefinitionDialog';
@@ -28,6 +32,7 @@ import {
} from '@backstage/core-plugin-api/alpha';
import { apiDocsTranslationRef } from '../../translation';
+/** @deprecated Use `getApiEntityColumnConfig` instead. */
export function createSpecApiTypeColumn(
t: TranslationFunction,
): TableColumn {
@@ -43,13 +48,13 @@ const ApiDefinitionButton = ({ apiEntity }: { apiEntity: ApiEntity }) => {
const { t } = useTranslationRef(apiDocsTranslationRef);
return (
<>
- setDialogOpen(!dialogOpen)}
- value={dialogOpen}
- >
-
-
+ onPress={() => setDialogOpen(!dialogOpen)}
+ variant="tertiary"
+ size="small"
+ icon={}
+ />
,
): TableColumn[] => {
@@ -81,3 +87,62 @@ export const getApiEntityColumns = (
createApiDefinitionColumn(t),
];
};
+
+// Column config presets
+
+function createSpecApiTypeColumnConfig(
+ t: TranslationFunction,
+): EntityColumnConfig {
+ return {
+ id: 'apiType',
+ label: t('apiEntityColumns.typeTitle'),
+ isSortable: true,
+ cell: entity => (
+ |
+
+ |
+ ),
+ sortValue: entity =>
+ (entity.spec as Record | undefined)?.type ?? '',
+ };
+}
+
+function createApiDefinitionColumnConfig(
+ t: TranslationFunction,
+): EntityColumnConfig {
+ return {
+ id: 'apiDefinition',
+ label: t('apiEntityColumns.apiDefinitionTitle'),
+ cell: entity => (
+
+
+ |
+ ),
+ };
+}
+
+export function getApiEntityColumnConfig(
+ t: TranslationFunction,
+): EntityColumnConfig[] {
+ return [
+ entityDataTableColumns.createEntityRefColumn({ defaultKind: 'API' }),
+ entityDataTableColumns.createSystemColumn(),
+ entityDataTableColumns.createOwnerColumn(),
+ createSpecApiTypeColumnConfig(t),
+ entityDataTableColumns.createSpecLifecycleColumn(),
+ entityDataTableColumns.createMetadataDescriptionColumn(),
+ createApiDefinitionColumnConfig(t),
+ ];
+}
+
+export function getHasApisColumnConfig(
+ t: TranslationFunction,
+): EntityColumnConfig[] {
+ return [
+ entityDataTableColumns.createEntityRefColumn({ defaultKind: 'API' }),
+ entityDataTableColumns.createOwnerColumn(),
+ createSpecApiTypeColumnConfig(t),
+ entityDataTableColumns.createSpecLifecycleColumn(),
+ entityDataTableColumns.createMetadataDescriptionColumn(),
+ ];
+}
diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx
index 1fda0d7e05..1006c069f6 100644
--- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx
+++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx
@@ -24,6 +24,11 @@ import {
useEntity,
useRelatedEntities,
} from '@backstage/plugin-catalog-react';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
CodeSnippet,
InfoCard,
@@ -37,15 +42,36 @@ import {
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { apiDocsTranslationRef } from '../../translation';
+/** @public */
+export interface ConsumingComponentsCardProps {
+ title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link ConsumingComponentsCardProps} instead.
* @public
*/
-export const ConsumingComponentsCard = (props: {
- variant?: InfoCardVariants;
+export interface ConsumingComponentsCardLegacyProps {
title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
-}) => {
+}
+
+function isLegacyProps(
+ props: ConsumingComponentsCardProps | ConsumingComponentsCardLegacyProps,
+): props is ConsumingComponentsCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+function ConsumingComponentsCardLegacy(
+ props: ConsumingComponentsCardLegacyProps,
+) {
const { t } = useTranslationRef(apiDocsTranslationRef);
const {
variant = 'gridItem',
@@ -99,4 +125,35 @@ export const ConsumingComponentsCard = (props: {
entities={entities as ComponentEntity[]}
/>
);
+}
+
+/**
+ * @public
+ */
+export const ConsumingComponentsCard = (
+ props: ConsumingComponentsCardProps | ConsumingComponentsCardLegacyProps,
+) => {
+ const { t } = useTranslationRef(apiDocsTranslationRef);
+
+ if (isLegacyProps(props)) {
+ return ;
+ }
+
+ const {
+ title = t('consumingComponentsCard.title'),
+ columnConfig = entityColumnPresets.component.columns,
+ } = props;
+
+ return (
+
+ );
};
diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx
index efdcd83247..e1be0c777d 100644
--- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx
+++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx
@@ -24,6 +24,11 @@ import {
useEntity,
useRelatedEntities,
} from '@backstage/plugin-catalog-react';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
CodeSnippet,
InfoCard,
@@ -38,12 +43,35 @@ import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { apiDocsTranslationRef } from '../../translation';
/** @public */
-export const ProvidingComponentsCard = (props: {
- variant?: InfoCardVariants;
+export interface ProvidingComponentsCardProps {
title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link ProvidingComponentsCardProps} instead.
+ * @public
+ */
+export interface ProvidingComponentsCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
-}) => {
+}
+
+function isLegacyProps(
+ props: ProvidingComponentsCardProps | ProvidingComponentsCardLegacyProps,
+): props is ProvidingComponentsCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+function ProvidingComponentsCardLegacy(
+ props: ProvidingComponentsCardLegacyProps,
+) {
const { t } = useTranslationRef(apiDocsTranslationRef);
const {
variant = 'gridItem',
@@ -97,4 +125,33 @@ export const ProvidingComponentsCard = (props: {
entities={entities as ComponentEntity[]}
/>
);
+}
+
+/** @public */
+export const ProvidingComponentsCard = (
+ props: ProvidingComponentsCardProps | ProvidingComponentsCardLegacyProps,
+) => {
+ const { t } = useTranslationRef(apiDocsTranslationRef);
+
+ if (isLegacyProps(props)) {
+ return ;
+ }
+
+ const {
+ title = t('providingComponentsCard.title'),
+ columnConfig = entityColumnPresets.component.columns,
+ } = props;
+
+ return (
+
+ );
};
diff --git a/plugins/api-docs/src/components/ComponentsCards/index.ts b/plugins/api-docs/src/components/ComponentsCards/index.ts
index 84dec071fb..33def6180c 100644
--- a/plugins/api-docs/src/components/ComponentsCards/index.ts
+++ b/plugins/api-docs/src/components/ComponentsCards/index.ts
@@ -15,4 +15,12 @@
*/
export { ConsumingComponentsCard } from './ConsumingComponentsCard';
+export type {
+ ConsumingComponentsCardLegacyProps,
+ ConsumingComponentsCardProps,
+} from './ConsumingComponentsCard';
export { ProvidingComponentsCard } from './ProvidingComponentsCard';
+export type {
+ ProvidingComponentsCardLegacyProps,
+ ProvidingComponentsCardProps,
+} from './ProvidingComponentsCard';
diff --git a/plugins/api-docs/src/translation.ts b/plugins/api-docs/src/translation.ts
index 385f5ecbaf..a6a43952bc 100644
--- a/plugins/api-docs/src/translation.ts
+++ b/plugins/api-docs/src/translation.ts
@@ -90,6 +90,6 @@ export const apiDocsTranslationRef = createTranslationRef({
title: 'No component provides this API.',
},
},
- apisCardHelpLinkTitle: 'Learn how to change this',
+ apisCardHelpLinkTitle: 'Learn how to change this.',
},
});
diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md
index 9c76a221dc..3cf5bc652c 100644
--- a/plugins/catalog-react/report-alpha.api.md
+++ b/plugins/catalog-react/report-alpha.api.md
@@ -4,6 +4,7 @@
```ts
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
+import { ColumnConfig } from '@backstage/ui';
import { ComponentType } from 'react';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { Entity } from '@backstage/catalog-model';
@@ -13,11 +14,13 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
import { FilterPredicate } from '@backstage/filter-predicates';
import { IconLinkVerticalProps } from '@backstage/core-components';
import { JSX as JSX_2 } from 'react';
+import { JSX as JSX_3 } from 'react/jsx-runtime';
import { JSXElementConstructor } from 'react';
import { ReactElement } from 'react';
import { ReactNode } from 'react';
import { ResourcePermission } from '@backstage/plugin-permission-common';
import { RouteRef } from '@backstage/frontend-plugin-api';
+import { TableItem } from '@backstage/ui';
import { TranslationRef } from '@backstage/frontend-plugin-api';
// @alpha
@@ -119,6 +122,7 @@ export const catalogReactTranslationRef: TranslationRef<
readonly 'entityTableColumnTitle.owner': 'Owner';
readonly 'entityTableColumnTitle.lifecycle': 'Lifecycle';
readonly 'entityTableColumnTitle.targets': 'Targets';
+ readonly 'entityRelationCard.emptyHelpLinkTitle': 'Learn how to change this.';
readonly 'missingAnnotationEmptyState.title': 'Missing Annotation';
readonly 'missingAnnotationEmptyState.readMore': 'Read more';
readonly 'missingAnnotationEmptyState.annotationYaml': 'Add the annotation to your {{entityKind}} YAML as shown in the highlighted example below:';
@@ -241,6 +245,32 @@ export const EntityCardBlueprint: ExtensionBlueprint<{
// @alpha (undocumented)
export type EntityCardType = 'info' | 'content';
+// @public (undocumented)
+export interface EntityColumnConfig extends ColumnConfig {
+ // (undocumented)
+ sortValue?: (entity: EntityRow) => string;
+}
+
+// @alpha (undocumented)
+export const entityColumnPresets: {
+ readonly component: {
+ readonly columns: EntityColumnConfig[];
+ readonly helpLink: 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component';
+ };
+ readonly resource: {
+ readonly columns: EntityColumnConfig[];
+ readonly helpLink: 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-resource';
+ };
+ readonly system: {
+ readonly columns: EntityColumnConfig[];
+ readonly helpLink: 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system';
+ };
+ readonly domain: {
+ readonly columns: EntityColumnConfig[];
+ readonly helpLink: 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-domain';
+ };
+};
+
// @alpha
export const EntityContentBlueprint: ExtensionBlueprint<{
kind: 'entity-content';
@@ -450,6 +480,46 @@ export type EntityContextMenuItemParams = {
filter?: FilterPredicate | ((entity: Entity) => boolean);
};
+// @public (undocumented)
+export function EntityDataTable(props: EntityDataTableProps): JSX_3.Element;
+
+// @public (undocumented)
+export const entityDataTableColumns: Readonly<{
+ createEntityRefColumn(options: {
+ defaultKind?: string;
+ isRowHeader?: boolean;
+ }): EntityColumnConfig;
+ createEntityRelationColumn(options: {
+ id: string;
+ translationKey: 'owner' | 'system' | 'domain';
+ relation: string;
+ defaultKind?: string;
+ filter?: {
+ kind: string;
+ };
+ }): EntityColumnConfig;
+ createOwnerColumn(): EntityColumnConfig;
+ createSystemColumn(): EntityColumnConfig;
+ createDomainColumn(): EntityColumnConfig;
+ createMetadataDescriptionColumn(): EntityColumnConfig;
+ createSpecTypeColumn(): EntityColumnConfig;
+ createSpecLifecycleColumn(): EntityColumnConfig;
+}>;
+
+// @public (undocumented)
+export interface EntityDataTableProps {
+ // (undocumented)
+ columnConfig: EntityColumnConfig[];
+ // (undocumented)
+ data: Entity[];
+ // (undocumented)
+ emptyState?: ReactNode;
+ // (undocumented)
+ error?: Error;
+ // (undocumented)
+ loading?: boolean;
+}
+
// @alpha (undocumented)
export const EntityHeaderBlueprint: ExtensionBlueprint<{
kind: 'entity-header';
@@ -557,6 +627,33 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{
};
}>;
+// @public (undocumented)
+export function EntityRelationCard(
+ props: EntityRelationCardProps,
+): JSX_3.Element;
+
+// @public (undocumented)
+export interface EntityRelationCardProps {
+ // (undocumented)
+ className?: string;
+ // (undocumented)
+ columnConfig: EntityColumnConfig[];
+ // (undocumented)
+ emptyState?: {
+ message: string;
+ helpLink?: string;
+ };
+ // (undocumented)
+ entityKind?: string;
+ // (undocumented)
+ relationType: string;
+ // (undocumented)
+ title: string;
+}
+
+// @public (undocumented)
+export type EntityRow = Entity & TableItem;
+
// @alpha (undocumented)
export const EntityTableColumnTitle: (
input: EntityTableColumnTitleProps,
diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts
index 6d2d76fd9c..a4c8daa83b 100644
--- a/plugins/catalog-react/src/alpha/index.ts
+++ b/plugins/catalog-react/src/alpha/index.ts
@@ -20,3 +20,5 @@ export { catalogReactTranslationRef } from '../translation';
export { isOwnerOf } from '../utils/isOwnerOf';
export { useEntityPermission } from '../hooks/useEntityPermission';
export * from '../components/EntityTable/TitleColumn';
+export * from '../components/EntityDataTable';
+export * from '../components/EntityRelationCard';
diff --git a/plugins/catalog-react/src/components/EntityDataTable/EntityDataTable.test.tsx b/plugins/catalog-react/src/components/EntityDataTable/EntityDataTable.test.tsx
new file mode 100644
index 0000000000..77b0e26a02
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityDataTable/EntityDataTable.test.tsx
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2026 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 { Entity } from '@backstage/catalog-model';
+import { renderInTestApp } from '@backstage/test-utils';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { CellText } from '@backstage/ui';
+import { EntityDataTable } from './EntityDataTable';
+import { EntityColumnConfig } from './columnFactories';
+import { entityRouteRef } from '../../routes';
+
+const columns: EntityColumnConfig[] = [
+ {
+ id: 'name',
+ label: 'Name',
+ isRowHeader: true,
+ isSortable: true,
+ cell: entity => ,
+ sortValue: entity => entity.metadata.name,
+ },
+ {
+ id: 'description',
+ label: 'Description',
+ cell: entity => ,
+ },
+];
+
+const entities: Entity[] = [
+ {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: { name: 'bravo', namespace: 'default', description: 'Second' },
+ },
+ {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: { name: 'alpha', namespace: 'default', description: 'First' },
+ },
+];
+
+describe('', () => {
+ it('renders entity data in a table', async () => {
+ await renderInTestApp(
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByText('bravo')).toBeInTheDocument();
+ expect(screen.getByText('alpha')).toBeInTheDocument();
+ expect(screen.getByText('Second')).toBeInTheDocument();
+ expect(screen.getByText('First')).toBeInTheDocument();
+ });
+
+ it('renders empty state when data is empty', async () => {
+ await renderInTestApp(
+ No entities found}
+ />,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByText('No entities found')).toBeInTheDocument();
+ });
+
+ it('renders error state', async () => {
+ await renderInTestApp(
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByText(/Something went wrong/)).toBeInTheDocument();
+ });
+
+ it('sorts data when clicking a sortable column header', async () => {
+ await renderInTestApp(
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ const rows = () => screen.getAllByRole('row').slice(1); // skip header row
+
+ // Initial order: bravo, alpha (insertion order)
+ expect(rows()[0]).toHaveTextContent('bravo');
+ expect(rows()[1]).toHaveTextContent('alpha');
+
+ // Click Name header to sort ascending
+ await userEvent.click(screen.getByText('Name'));
+
+ expect(rows()[0]).toHaveTextContent('alpha');
+ expect(rows()[1]).toHaveTextContent('bravo');
+
+ // Click again to sort descending
+ await userEvent.click(screen.getByText('Name'));
+
+ expect(rows()[0]).toHaveTextContent('bravo');
+ expect(rows()[1]).toHaveTextContent('alpha');
+ });
+
+ it('does not sort when column has no sortValue', async () => {
+ const unsortableColumns: EntityColumnConfig[] = [
+ {
+ id: 'name',
+ label: 'Name',
+ isRowHeader: true,
+ cell: entity => ,
+ },
+ ];
+
+ await renderInTestApp(
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ const rows = () => screen.getAllByRole('row').slice(1);
+
+ // Should maintain insertion order
+ expect(rows()[0]).toHaveTextContent('bravo');
+ expect(rows()[1]).toHaveTextContent('alpha');
+ });
+});
diff --git a/plugins/catalog-react/src/components/EntityDataTable/EntityDataTable.tsx b/plugins/catalog-react/src/components/EntityDataTable/EntityDataTable.tsx
new file mode 100644
index 0000000000..32234b1bc0
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityDataTable/EntityDataTable.tsx
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2026 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 { Entity, stringifyEntityRef } from '@backstage/catalog-model';
+import { Table, useTable } from '@backstage/ui';
+import { useCallback, useMemo, ReactNode } from 'react';
+import { EntityRow, EntityColumnConfig } from './columnFactories';
+import { SortDescriptor } from '@backstage/ui';
+
+/** @public */
+export interface EntityDataTableProps {
+ columnConfig: EntityColumnConfig[];
+ data: Entity[];
+ loading?: boolean;
+ error?: Error;
+ emptyState?: ReactNode;
+}
+
+/** @public */
+export function EntityDataTable(props: EntityDataTableProps) {
+ const { columnConfig, data, loading, error, emptyState } = props;
+
+ const tableData: EntityRow[] = useMemo(
+ () =>
+ data.map(entity => ({
+ ...entity,
+ id: stringifyEntityRef(entity),
+ })),
+ [data],
+ );
+
+ const sortFn = useCallback(
+ (items: EntityRow[], sort: SortDescriptor) => {
+ const column = columnConfig.find(c => c.id === sort.column);
+ if (!column?.sortValue) {
+ return items;
+ }
+ const getValue = column.sortValue;
+ const direction = sort.direction === 'descending' ? -1 : 1;
+ return [...items].sort(
+ (a, b) => getValue(a).localeCompare(getValue(b)) * direction,
+ );
+ },
+ [columnConfig],
+ );
+
+ const { tableProps } = useTable({
+ mode: 'complete',
+ data: tableData,
+ sortFn,
+ paginationOptions: { pageSize: tableData.length || 1 },
+ });
+
+ return (
+
+ );
+}
diff --git a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx
new file mode 100644
index 0000000000..49fb64caa8
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2026 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 {
+ Entity,
+ RELATION_OWNED_BY,
+ RELATION_PART_OF,
+} from '@backstage/catalog-model';
+import { Cell, CellText, Column, ColumnConfig, TableItem } from '@backstage/ui';
+import {
+ EntityRefLink,
+ EntityRefLinks,
+ humanizeEntityRef,
+} from '../EntityRefLink';
+import { EntityTableColumnTitle } from '../EntityTable/TitleColumn';
+import { getEntityRelations } from '../../utils';
+
+/** @public */
+export type EntityRow = Entity & TableItem;
+
+/** @public */
+export interface EntityColumnConfig extends ColumnConfig {
+ sortValue?: (entity: EntityRow) => string;
+}
+
+/** @public */
+export const columnFactories = Object.freeze({
+ createEntityRefColumn(options: {
+ defaultKind?: string;
+ isRowHeader?: boolean;
+ }): EntityColumnConfig {
+ const isRowHeader = options.isRowHeader ?? true;
+ return {
+ id: 'name',
+ label: 'Name',
+ header: () => (
+
+
+
+ ),
+ isRowHeader,
+ isSortable: true,
+ cell: entity => (
+
+
+ |
+ ),
+ sortValue: entity =>
+ entity.metadata?.title ||
+ humanizeEntityRef(entity, { defaultKind: options.defaultKind }),
+ };
+ },
+
+ createEntityRelationColumn(options: {
+ id: string;
+ translationKey: 'owner' | 'system' | 'domain';
+ relation: string;
+ defaultKind?: string;
+ filter?: { kind: string };
+ }): EntityColumnConfig {
+ return {
+ id: options.id,
+ label: options.id.charAt(0).toUpperCase() + options.id.slice(1),
+ header: () => (
+
+
+
+ ),
+ isSortable: true,
+ cell: entity => (
+
+
+ |
+ ),
+ sortValue: entity =>
+ getEntityRelations(entity, options.relation, options.filter)
+ .map(r => humanizeEntityRef(r, { defaultKind: options.defaultKind }))
+ .join(', '),
+ };
+ },
+
+ createOwnerColumn(): EntityColumnConfig {
+ return columnFactories.createEntityRelationColumn({
+ id: 'owner',
+ translationKey: 'owner',
+ relation: RELATION_OWNED_BY,
+ defaultKind: 'group',
+ });
+ },
+
+ createSystemColumn(): EntityColumnConfig {
+ return columnFactories.createEntityRelationColumn({
+ id: 'system',
+ translationKey: 'system',
+ relation: RELATION_PART_OF,
+ defaultKind: 'system',
+ filter: { kind: 'system' },
+ });
+ },
+
+ createDomainColumn(): EntityColumnConfig {
+ return columnFactories.createEntityRelationColumn({
+ id: 'domain',
+ translationKey: 'domain',
+ relation: RELATION_PART_OF,
+ defaultKind: 'domain',
+ filter: { kind: 'domain' },
+ });
+ },
+
+ createMetadataDescriptionColumn(): EntityColumnConfig {
+ return {
+ id: 'description',
+ label: 'Description',
+ header: () => (
+
+
+
+ ),
+ isSortable: true,
+ cell: entity => ,
+ sortValue: entity => entity.metadata.description ?? '',
+ };
+ },
+
+ createSpecTypeColumn(): EntityColumnConfig {
+ return {
+ id: 'type',
+ label: 'Type',
+ header: () => (
+
+
+
+ ),
+ isSortable: true,
+ cell: entity => (
+ | undefined)?.type ?? ''
+ }
+ />
+ ),
+ sortValue: entity =>
+ (entity.spec as Record | undefined)?.type ?? '',
+ };
+ },
+
+ createSpecLifecycleColumn(): EntityColumnConfig {
+ return {
+ id: 'lifecycle',
+ label: 'Lifecycle',
+ header: () => (
+
+
+
+ ),
+ isSortable: true,
+ cell: entity => (
+ | undefined)?.lifecycle ?? ''
+ }
+ />
+ ),
+ sortValue: entity =>
+ (entity.spec as Record | undefined)?.lifecycle ?? '',
+ };
+ },
+});
diff --git a/plugins/catalog-react/src/components/EntityDataTable/index.ts b/plugins/catalog-react/src/components/EntityDataTable/index.ts
new file mode 100644
index 0000000000..a313d3401e
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityDataTable/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2026 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { EntityDataTable } from './EntityDataTable';
+export type { EntityDataTableProps } from './EntityDataTable';
+export { columnFactories as entityDataTableColumns } from './columnFactories';
+export type { EntityRow, EntityColumnConfig } from './columnFactories';
+export { entityColumnPresets } from './presets';
diff --git a/plugins/catalog-react/src/components/EntityDataTable/presets.tsx b/plugins/catalog-react/src/components/EntityDataTable/presets.tsx
new file mode 100644
index 0000000000..7bbd94e7d7
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityDataTable/presets.tsx
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2026 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 { columnFactories, EntityColumnConfig } from './columnFactories';
+
+/** @alpha */
+export const entityColumnPresets = {
+ component: {
+ columns: [
+ columnFactories.createEntityRefColumn({ defaultKind: 'component' }),
+ columnFactories.createOwnerColumn(),
+ columnFactories.createSpecTypeColumn(),
+ columnFactories.createSpecLifecycleColumn(),
+ columnFactories.createMetadataDescriptionColumn(),
+ ] as EntityColumnConfig[],
+ helpLink:
+ 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component',
+ },
+ resource: {
+ columns: [
+ columnFactories.createEntityRefColumn({ defaultKind: 'resource' }),
+ columnFactories.createOwnerColumn(),
+ columnFactories.createSpecTypeColumn(),
+ columnFactories.createSpecLifecycleColumn(),
+ columnFactories.createMetadataDescriptionColumn(),
+ ] as EntityColumnConfig[],
+ helpLink:
+ 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-resource',
+ },
+ system: {
+ columns: [
+ columnFactories.createEntityRefColumn({ defaultKind: 'system' }),
+ columnFactories.createOwnerColumn(),
+ columnFactories.createMetadataDescriptionColumn(),
+ ] as EntityColumnConfig[],
+ helpLink:
+ 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system',
+ },
+ domain: {
+ columns: [
+ columnFactories.createEntityRefColumn({ defaultKind: 'domain' }),
+ columnFactories.createOwnerColumn(),
+ columnFactories.createMetadataDescriptionColumn(),
+ ] as EntityColumnConfig[],
+ helpLink:
+ 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-domain',
+ },
+} as const;
diff --git a/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.test.tsx b/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.test.tsx
new file mode 100644
index 0000000000..06e9059ee3
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.test.tsx
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2026 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 { Entity, RELATION_HAS_PART } from '@backstage/catalog-model';
+import {
+ catalogApiRef,
+ EntityProvider,
+ entityRouteRef,
+} from '@backstage/plugin-catalog-react';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
+import { screen } from '@testing-library/react';
+import { ComponentType, PropsWithChildren, ReactNode } from 'react';
+import { EntityRelationCard } from './EntityRelationCard';
+import { entityColumnPresets } from '../EntityDataTable/presets';
+import { catalogApiMock } from '../../testUtils/catalogApiMock';
+
+describe('', () => {
+ const catalogApi = catalogApiMock.mock();
+ let Wrapper: ComponentType>;
+
+ beforeEach(() => {
+ Wrapper = ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ );
+ });
+
+ afterEach(() => jest.resetAllMocks());
+
+ it('renders the card title', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'System',
+ metadata: { name: 'my-system', namespace: 'my-namespace' },
+ relations: [],
+ };
+
+ await renderInTestApp(
+
+
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByText('Has components')).toBeInTheDocument();
+ });
+
+ it('shows empty state message and help link when no related entities', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'System',
+ metadata: { name: 'my-system', namespace: 'my-namespace' },
+ relations: [],
+ };
+
+ await renderInTestApp(
+
+
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(
+ screen.getByText(/No component is part of this system/),
+ ).toBeInTheDocument();
+ const link = screen.getByRole('link');
+ expect(link).toHaveAttribute('href', 'https://backstage.io/docs');
+ });
+
+ it('shows related entities in the table', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'System',
+ metadata: { name: 'my-system', namespace: 'my-namespace' },
+ relations: [
+ {
+ targetRef: 'component:my-namespace/my-component',
+ type: RELATION_HAS_PART,
+ },
+ ],
+ };
+ catalogApi.getEntitiesByRefs.mockResolvedValue({
+ items: [
+ {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: { name: 'my-component', namespace: 'my-namespace' },
+ spec: { type: 'service', lifecycle: 'production' },
+ },
+ ],
+ });
+
+ await renderInTestApp(
+
+
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByText('Has components')).toBeInTheDocument();
+ expect(await screen.findByText(/my-component/)).toBeInTheDocument();
+ });
+
+ it('shows error state when loading fails', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'System',
+ metadata: { name: 'my-system', namespace: 'my-namespace' },
+ relations: [
+ {
+ targetRef: 'component:my-namespace/my-component',
+ type: RELATION_HAS_PART,
+ },
+ ],
+ };
+ catalogApi.getEntitiesByRefs.mockRejectedValue(
+ new Error('Request failed with 500'),
+ );
+
+ await renderInTestApp(
+
+
+
+
+ ,
+ {
+ mountedRoutes: {
+ '/catalog/:namespace/:kind/:name': entityRouteRef,
+ },
+ },
+ );
+
+ expect(screen.getByText('Has components')).toBeInTheDocument();
+ expect(
+ await screen.findByText(/Request failed with 500/),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.tsx b/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.tsx
new file mode 100644
index 0000000000..6554881fd5
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityRelationCard/EntityRelationCard.tsx
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2026 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 { Alert, Link, Text } from '@backstage/ui';
+import { useTranslationRef } from '@backstage/frontend-plugin-api';
+import { EntityInfoCard } from '../EntityInfoCard';
+import { EntityDataTable } from '../EntityDataTable';
+import { EntityColumnConfig } from '../EntityDataTable/columnFactories';
+import { useEntity, useRelatedEntities } from '../../hooks';
+import { catalogReactTranslationRef } from '../../translation';
+
+/** @public */
+export interface EntityRelationCardProps {
+ title: string;
+ relationType: string;
+ entityKind?: string;
+ columnConfig: EntityColumnConfig[];
+ emptyState?: {
+ message: string;
+ helpLink?: string;
+ };
+ className?: string;
+}
+
+/** @public */
+export function EntityRelationCard(props: EntityRelationCardProps) {
+ const {
+ title,
+ relationType,
+ entityKind,
+ columnConfig,
+ emptyState,
+ className,
+ } = props;
+ const { t } = useTranslationRef(catalogReactTranslationRef);
+ const { entity } = useEntity();
+ const { entities, loading, error } = useRelatedEntities(entity, {
+ type: relationType,
+ kind: entityKind,
+ });
+
+ return (
+
+ {error ? (
+
+ ) : (
+
+ {emptyState.message}{' '}
+ {emptyState.helpLink && (
+
+ {t('entityRelationCard.emptyHelpLinkTitle')}
+
+ )}
+
+ )
+ }
+ />
+ )}
+
+ );
+}
diff --git a/plugins/catalog-react/src/components/EntityRelationCard/index.ts b/plugins/catalog-react/src/components/EntityRelationCard/index.ts
new file mode 100644
index 0000000000..0f42b61fb7
--- /dev/null
+++ b/plugins/catalog-react/src/components/EntityRelationCard/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2026 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { EntityRelationCard } from './EntityRelationCard';
+export type { EntityRelationCardProps } from './EntityRelationCard';
diff --git a/plugins/catalog-react/src/translation.ts b/plugins/catalog-react/src/translation.ts
index 4411f7e004..77e0162e8b 100644
--- a/plugins/catalog-react/src/translation.ts
+++ b/plugins/catalog-react/src/translation.ts
@@ -168,6 +168,9 @@ export const catalogReactTranslationRef = createTranslationRef({
label: 'Label',
domain: 'Domain',
},
+ entityRelationCard: {
+ emptyHelpLinkTitle: 'Learn how to change this.',
+ },
missingAnnotationEmptyState: {
title: 'Missing Annotation',
readMore: 'Read more',
diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md
index a0b092a453..a26143a1f6 100644
--- a/plugins/catalog/report-alpha.api.md
+++ b/plugins/catalog/report-alpha.api.md
@@ -83,11 +83,11 @@ export const catalogTranslationRef: TranslationRef<
readonly 'catalogTable.starActionTitle': 'Add to favorites';
readonly 'catalogTable.unStarActionTitle': 'Remove from favorites';
readonly 'dependencyOfComponentsCard.title': 'Dependency of components';
- readonly 'dependencyOfComponentsCard.emptyMessage': 'No component depends on this component';
+ readonly 'dependencyOfComponentsCard.emptyMessage': 'No component depends on this component.';
readonly 'dependsOnComponentsCard.title': 'Depends on components';
- readonly 'dependsOnComponentsCard.emptyMessage': 'No component is a dependency of this component';
+ readonly 'dependsOnComponentsCard.emptyMessage': 'No component is a dependency of this component.';
readonly 'dependsOnResourcesCard.title': 'Depends on resources';
- readonly 'dependsOnResourcesCard.emptyMessage': 'No resource is a dependency of this component';
+ readonly 'dependsOnResourcesCard.emptyMessage': 'No resource is a dependency of this component.';
readonly 'entityContextMenu.copiedMessage': 'Copied!';
readonly 'entityContextMenu.moreButtonTitle': 'More';
readonly 'entityContextMenu.inspectMenuTitle': 'Inspect entity';
@@ -112,16 +112,16 @@ export const catalogTranslationRef: TranslationRef<
readonly entityProcessingErrorsDescription: 'The error below originates from';
readonly entityRelationWarningDescription: "This entity has relations to other entities, which can't be found in the catalog.\n Entities not found are: ";
readonly 'hasComponentsCard.title': 'Has components';
- readonly 'hasComponentsCard.emptyMessage': 'No component is part of this system';
+ readonly 'hasComponentsCard.emptyMessage': 'No component is part of this system.';
readonly 'hasResourcesCard.title': 'Has resources';
- readonly 'hasResourcesCard.emptyMessage': 'No resource is part of this system';
+ readonly 'hasResourcesCard.emptyMessage': 'No resource is part of this system.';
readonly 'hasSubcomponentsCard.title': 'Has subcomponents';
- readonly 'hasSubcomponentsCard.emptyMessage': 'No subcomponent is part of this component';
+ readonly 'hasSubcomponentsCard.emptyMessage': 'No subcomponent is part of this component.';
readonly 'hasSubdomainsCard.title': 'Has subdomains';
- readonly 'hasSubdomainsCard.emptyMessage': 'No subdomain is part of this domain';
+ readonly 'hasSubdomainsCard.emptyMessage': 'No subdomain is part of this domain.';
readonly 'hasSystemsCard.title': 'Has systems';
- readonly 'hasSystemsCard.emptyMessage': 'No system is part of this domain';
- readonly 'relatedEntitiesCard.emptyHelpLinkTitle': 'Learn how to change this';
+ readonly 'hasSystemsCard.emptyMessage': 'No system is part of this domain.';
+ readonly 'relatedEntitiesCard.emptyHelpLinkTitle': 'Learn how to change this.';
readonly 'systemDiagramCard.title': 'System Diagram';
readonly 'systemDiagramCard.description': 'Use pinch & zoom to move around the diagram.';
readonly 'systemDiagramCard.edgeLabels.dependsOn': 'depends on';
diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md
index 48e3f53de2..cebf1fd503 100644
--- a/plugins/catalog/report.api.md
+++ b/plugins/catalog/report.api.md
@@ -11,6 +11,7 @@ import { CompoundEntityRef } from '@backstage/catalog-model';
import { DomainEntity } from '@backstage/catalog-model';
import { ElementType } from 'react';
import { Entity } from '@backstage/catalog-model';
+import { EntityColumnConfig } from '@backstage/plugin-catalog-react/alpha';
import { EntityListContextProps } from '@backstage/plugin-catalog-react';
import { EntityListPagination } from '@backstage/plugin-catalog-react';
import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react';
@@ -290,40 +291,64 @@ export class DefaultStarredEntitiesApi implements StarredEntitiesApi {
toggleStarred(entityRef: string): Promise;
}
-// @public (undocumented)
-export interface DependencyOfComponentsCardProps {
- // (undocumented)
+// @public @deprecated
+export interface DependencyOfComponentsCardLegacyProps {
+ // @deprecated (undocumented)
columns?: TableColumn[];
- // (undocumented)
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
// (undocumented)
title?: string;
+ // @deprecated (undocumented)
+ variant?: InfoCardVariants;
+}
+
+// @public (undocumented)
+export interface DependencyOfComponentsCardProps {
// (undocumented)
+ columnConfig?: EntityColumnConfig[];
+ // (undocumented)
+ title?: string;
+}
+
+// @public @deprecated
+export interface DependsOnComponentsCardLegacyProps {
+ // @deprecated (undocumented)
+ columns?: TableColumn[];
+ // @deprecated (undocumented)
+ tableOptions?: TableOptions;
+ // (undocumented)
+ title?: string;
+ // @deprecated (undocumented)
variant?: InfoCardVariants;
}
// @public (undocumented)
export interface DependsOnComponentsCardProps {
// (undocumented)
- columns?: TableColumn[];
+ columnConfig?: EntityColumnConfig[];
// (undocumented)
+ title?: string;
+}
+
+// @public @deprecated
+export interface DependsOnResourcesCardLegacyProps {
+ // @deprecated (undocumented)
+ columns?: TableColumn[];
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
// (undocumented)
title?: string;
- // (undocumented)
+ // @deprecated (undocumented)
variant?: InfoCardVariants;
}
// @public (undocumented)
export interface DependsOnResourcesCardProps {
// (undocumented)
- columns?: TableColumn[];
- // (undocumented)
- tableOptions?: TableOptions;
+ columnConfig?: EntityColumnConfig[];
// (undocumented)
title?: string;
- // (undocumented)
- variant?: InfoCardVariants;
}
// @public
@@ -502,18 +527,26 @@ export function hasCatalogProcessingErrors(
},
): Promise;
-// @public (undocumented)
-export interface HasComponentsCardProps {
- // (undocumented)
+// @public @deprecated
+export interface HasComponentsCardLegacyProps {
+ // @deprecated (undocumented)
columns?: TableColumn[];
- // (undocumented)
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
// (undocumented)
title?: string;
- // (undocumented)
+ // @deprecated (undocumented)
variant?: InfoCardVariants;
}
+// @public (undocumented)
+export interface HasComponentsCardProps {
+ // (undocumented)
+ columnConfig?: EntityColumnConfig[];
+ // (undocumented)
+ title?: string;
+}
+
// @public
export function hasLabels(entity: Entity): boolean;
@@ -525,54 +558,88 @@ export function hasRelationWarnings(
},
): Promise;
-// @public (undocumented)
-export interface HasResourcesCardProps {
- // (undocumented)
+// @public @deprecated
+export interface HasResourcesCardLegacyProps {
+ // @deprecated (undocumented)
columns?: TableColumn[];
- // (undocumented)
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
// (undocumented)
title?: string;
+ // @deprecated (undocumented)
+ variant?: InfoCardVariants;
+}
+
+// @public (undocumented)
+export interface HasResourcesCardProps {
// (undocumented)
+ columnConfig?: EntityColumnConfig[];
+ // (undocumented)
+ title?: string;
+}
+
+// @public @deprecated
+export interface HasSubcomponentsCardLegacyProps {
+ // @deprecated (undocumented)
+ columns?: TableColumn[];
+ // (undocumented)
+ kind?: string;
+ // @deprecated (undocumented)
+ tableOptions?: TableOptions;
+ // (undocumented)
+ title?: string;
+ // @deprecated (undocumented)
variant?: InfoCardVariants;
}
// @public (undocumented)
export interface HasSubcomponentsCardProps {
// (undocumented)
- columns?: TableColumn[];
+ columnConfig?: EntityColumnConfig[];
// (undocumented)
kind?: string;
// (undocumented)
+ title?: string;
+}
+
+// @public @deprecated
+export interface HasSubdomainsCardLegacyProps {
+ // @deprecated (undocumented)
+ columns?: TableColumn[];
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
// (undocumented)
title?: string;
- // (undocumented)
+ // @deprecated (undocumented)
variant?: InfoCardVariants;
}
// @public (undocumented)
export interface HasSubdomainsCardProps {
// (undocumented)
- columns?: TableColumn[];
+ columnConfig?: EntityColumnConfig[];
// (undocumented)
+ title?: string;
+}
+
+// @public @deprecated
+export interface HasSystemsCardLegacyProps {
+ // @deprecated (undocumented)
+ columns?: TableColumn[];
+ // @deprecated (undocumented)
tableOptions?: TableOptions;
// (undocumented)
title?: string;
- // (undocumented)
+ // @deprecated (undocumented)
variant?: InfoCardVariants;
}
// @public (undocumented)
export interface HasSystemsCardProps {
// (undocumented)
- columns?: TableColumn[];
- // (undocumented)
- tableOptions?: TableOptions;
+ columnConfig?: EntityColumnConfig[];
// (undocumented)
title?: string;
- // (undocumented)
- variant?: InfoCardVariants;
}
// @public
diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx
index 7be14eb445..bd48eebaaf 100644
--- a/plugins/catalog/src/alpha/entityCards.tsx
+++ b/plugins/catalog/src/alpha/entityCards.tsx
@@ -100,7 +100,7 @@ export const catalogDependsOnComponentsEntityCard = EntityCardBlueprint.make({
filter: { kind: 'component' },
loader: async () =>
import('../components/DependsOnComponentsCard').then(m => (
-
+
)),
},
});
@@ -111,7 +111,7 @@ export const catalogDependsOnResourcesEntityCard = EntityCardBlueprint.make({
filter: { kind: 'component' },
loader: async () =>
import('../components/DependsOnResourcesCard').then(m => (
-
+
)),
},
});
@@ -122,7 +122,7 @@ export const catalogHasComponentsEntityCard = EntityCardBlueprint.make({
filter: { kind: 'system' },
loader: async () =>
import('../components/HasComponentsCard').then(m => (
-
+
)),
},
});
@@ -133,7 +133,7 @@ export const catalogHasResourcesEntityCard = EntityCardBlueprint.make({
filter: { kind: 'system' },
loader: async () =>
import('../components/HasResourcesCard').then(m => (
-
+
)),
},
});
@@ -144,7 +144,7 @@ export const catalogHasSubcomponentsEntityCard = EntityCardBlueprint.make({
filter: { kind: 'component' },
loader: async () =>
import('../components/HasSubcomponentsCard').then(m => (
-
+
)),
},
});
@@ -155,7 +155,7 @@ export const catalogHasSubdomainsEntityCard = EntityCardBlueprint.make({
filter: { kind: 'domain' },
loader: async () =>
import('../components/HasSubdomainsCard').then(m => (
-
+
)),
},
});
@@ -165,9 +165,7 @@ export const catalogHasSystemsEntityCard = EntityCardBlueprint.make({
params: {
filter: { kind: 'domain' },
loader: async () =>
- import('../components/HasSystemsCard').then(m => (
-
- )),
+ import('../components/HasSystemsCard').then(m => ),
},
});
diff --git a/plugins/catalog/src/alpha/translation.ts b/plugins/catalog/src/alpha/translation.ts
index 3d48c0915a..240dea6407 100644
--- a/plugins/catalog/src/alpha/translation.ts
+++ b/plugins/catalog/src/alpha/translation.ts
@@ -91,15 +91,15 @@ export const catalogTranslationRef = createTranslationRef({
},
dependencyOfComponentsCard: {
title: 'Dependency of components',
- emptyMessage: 'No component depends on this component',
+ emptyMessage: 'No component depends on this component.',
},
dependsOnComponentsCard: {
title: 'Depends on components',
- emptyMessage: 'No component is a dependency of this component',
+ emptyMessage: 'No component is a dependency of this component.',
},
dependsOnResourcesCard: {
title: 'Depends on resources',
- emptyMessage: 'No resource is a dependency of this component',
+ emptyMessage: 'No resource is a dependency of this component.',
},
entityContextMenu: {
copiedMessage: 'Copied!',
@@ -150,26 +150,26 @@ export const catalogTranslationRef = createTranslationRef({
"This entity has relations to other entities, which can't be found in the catalog.\n Entities not found are: ",
hasComponentsCard: {
title: 'Has components',
- emptyMessage: 'No component is part of this system',
+ emptyMessage: 'No component is part of this system.',
},
hasResourcesCard: {
title: 'Has resources',
- emptyMessage: 'No resource is part of this system',
+ emptyMessage: 'No resource is part of this system.',
},
hasSubcomponentsCard: {
title: 'Has subcomponents',
- emptyMessage: 'No subcomponent is part of this component',
+ emptyMessage: 'No subcomponent is part of this component.',
},
hasSubdomainsCard: {
title: 'Has subdomains',
- emptyMessage: 'No subdomain is part of this domain',
+ emptyMessage: 'No subdomain is part of this domain.',
},
hasSystemsCard: {
title: 'Has systems',
- emptyMessage: 'No system is part of this domain',
+ emptyMessage: 'No system is part of this domain.',
},
relatedEntitiesCard: {
- emptyHelpLinkTitle: 'Learn how to change this',
+ emptyHelpLinkTitle: 'Learn how to change this.',
},
systemDiagramCard: {
title: 'System Diagram',
diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx
index 72f6d5b85b..0bd2e9e094 100644
--- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx
+++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx
@@ -23,10 +23,15 @@ import {
TableColumn,
TableOptions,
} from '@backstage/core-components';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
asComponentEntities,
componentEntityColumns,
- componentEntityHelpLink,
+ componentEntityHelpLink as legacyHelpLink,
RelatedEntitiesCard,
} from '../RelatedEntitiesCard';
import { catalogTranslationRef } from '../../alpha/translation';
@@ -34,33 +39,76 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @public */
export interface DependencyOfComponentsCardProps {
- variant?: InfoCardVariants;
title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link DependencyOfComponentsCardProps} instead.
+ * @public
+ */
+export interface DependencyOfComponentsCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
}
+function isLegacyProps(
+ props:
+ | DependencyOfComponentsCardProps
+ | DependencyOfComponentsCardLegacyProps,
+): props is DependencyOfComponentsCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
export function DependencyOfComponentsCard(
- props: DependencyOfComponentsCardProps,
+ props:
+ | DependencyOfComponentsCardProps
+ | DependencyOfComponentsCardLegacyProps,
) {
const { t } = useTranslationRef(catalogTranslationRef);
+
+ if (isLegacyProps(props)) {
+ const {
+ variant = 'gridItem',
+ title = t('dependencyOfComponentsCard.title'),
+ columns = componentEntityColumns,
+ tableOptions = {},
+ } = props;
+ return (
+
+ );
+ }
+
const {
- variant = 'gridItem',
title = t('dependencyOfComponentsCard.title'),
- columns = componentEntityColumns,
- tableOptions = {},
+ columnConfig = entityColumnPresets.component.columns,
} = props;
return (
-
);
}
diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts b/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts
index 3d324e865d..a7067237ff 100644
--- a/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts
+++ b/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts
@@ -15,4 +15,7 @@
*/
export { DependencyOfComponentsCard } from './DependencyOfComponentsCard';
-export type { DependencyOfComponentsCardProps } from './DependencyOfComponentsCard';
+export type {
+ DependencyOfComponentsCardLegacyProps,
+ DependencyOfComponentsCardProps,
+} from './DependencyOfComponentsCard';
diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx
index 7276e3bf08..f92e917d62 100644
--- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx
@@ -20,10 +20,15 @@ import {
TableColumn,
TableOptions,
} from '@backstage/core-components';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
asComponentEntities,
componentEntityColumns,
- componentEntityHelpLink,
+ componentEntityHelpLink as legacyHelpLink,
RelatedEntitiesCard,
} from '../RelatedEntitiesCard';
import { catalogTranslationRef } from '../../alpha/translation';
@@ -31,31 +36,72 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @public */
export interface DependsOnComponentsCardProps {
- variant?: InfoCardVariants;
title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link DependsOnComponentsCardProps} instead.
+ * @public
+ */
+export interface DependsOnComponentsCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
}
-export function DependsOnComponentsCard(props: DependsOnComponentsCardProps) {
+function isLegacyProps(
+ props: DependsOnComponentsCardProps | DependsOnComponentsCardLegacyProps,
+): props is DependsOnComponentsCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+export function DependsOnComponentsCard(
+ props: DependsOnComponentsCardProps | DependsOnComponentsCardLegacyProps,
+) {
const { t } = useTranslationRef(catalogTranslationRef);
+
+ if (isLegacyProps(props)) {
+ const {
+ variant = 'gridItem',
+ title = t('dependsOnComponentsCard.title'),
+ columns = componentEntityColumns,
+ tableOptions = {},
+ } = props;
+ return (
+
+ );
+ }
+
const {
- variant = 'gridItem',
title = t('dependsOnComponentsCard.title'),
- columns = componentEntityColumns,
- tableOptions = {},
+ columnConfig = entityColumnPresets.component.columns,
} = props;
return (
-
);
}
diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/index.ts b/plugins/catalog/src/components/DependsOnComponentsCard/index.ts
index 8292526270..f62d7995aa 100644
--- a/plugins/catalog/src/components/DependsOnComponentsCard/index.ts
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/index.ts
@@ -15,4 +15,7 @@
*/
export { DependsOnComponentsCard } from './DependsOnComponentsCard';
-export type { DependsOnComponentsCardProps } from './DependsOnComponentsCard';
+export type {
+ DependsOnComponentsCardLegacyProps,
+ DependsOnComponentsCardProps,
+} from './DependsOnComponentsCard';
diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx
index 64153e98af..7d8ff6a650 100644
--- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx
@@ -20,42 +20,88 @@ import {
TableColumn,
TableOptions,
} from '@backstage/core-components';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
asResourceEntities,
- componentEntityHelpLink,
- RelatedEntitiesCard,
+ componentEntityHelpLink as legacyHelpLink,
resourceEntityColumns,
+ RelatedEntitiesCard,
} from '../RelatedEntitiesCard';
import { catalogTranslationRef } from '../../alpha/translation';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @public */
export interface DependsOnResourcesCardProps {
- variant?: InfoCardVariants;
title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link DependsOnResourcesCardProps} instead.
+ * @public
+ */
+export interface DependsOnResourcesCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
}
-export function DependsOnResourcesCard(props: DependsOnResourcesCardProps) {
+function isLegacyProps(
+ props: DependsOnResourcesCardProps | DependsOnResourcesCardLegacyProps,
+): props is DependsOnResourcesCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+export function DependsOnResourcesCard(
+ props: DependsOnResourcesCardProps | DependsOnResourcesCardLegacyProps,
+) {
const { t } = useTranslationRef(catalogTranslationRef);
+
+ if (isLegacyProps(props)) {
+ const {
+ variant = 'gridItem',
+ title = t('dependsOnResourcesCard.title'),
+ columns = resourceEntityColumns,
+ tableOptions = {},
+ } = props;
+ return (
+
+ );
+ }
+
const {
- variant = 'gridItem',
title = t('dependsOnResourcesCard.title'),
- columns = resourceEntityColumns,
- tableOptions = {},
+ columnConfig = entityColumnPresets.resource.columns,
} = props;
return (
-
);
}
diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/index.ts b/plugins/catalog/src/components/DependsOnResourcesCard/index.ts
index 8d638e16a2..1384df4da3 100644
--- a/plugins/catalog/src/components/DependsOnResourcesCard/index.ts
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/index.ts
@@ -15,4 +15,7 @@
*/
export { DependsOnResourcesCard } from './DependsOnResourcesCard';
-export type { DependsOnResourcesCardProps } from './DependsOnResourcesCard';
+export type {
+ DependsOnResourcesCardLegacyProps,
+ DependsOnResourcesCardProps,
+} from './DependsOnResourcesCard';
diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx
index 2917cdf1e1..c41eaf865d 100644
--- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx
+++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx
@@ -20,10 +20,15 @@ import {
TableColumn,
TableOptions,
} from '@backstage/core-components';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
asComponentEntities,
componentEntityColumns,
- componentEntityHelpLink,
+ componentEntityHelpLink as legacyHelpLink,
RelatedEntitiesCard,
} from '../RelatedEntitiesCard';
import { catalogTranslationRef } from '../../alpha/translation';
@@ -31,31 +36,72 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @public */
export interface HasComponentsCardProps {
- variant?: InfoCardVariants;
title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link HasComponentsCardProps} instead.
+ * @public
+ */
+export interface HasComponentsCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
}
-export function HasComponentsCard(props: HasComponentsCardProps) {
+function isLegacyProps(
+ props: HasComponentsCardProps | HasComponentsCardLegacyProps,
+): props is HasComponentsCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+export function HasComponentsCard(
+ props: HasComponentsCardProps | HasComponentsCardLegacyProps,
+) {
const { t } = useTranslationRef(catalogTranslationRef);
+
+ if (isLegacyProps(props)) {
+ const {
+ variant = 'gridItem',
+ title = t('hasComponentsCard.title'),
+ columns = componentEntityColumns,
+ tableOptions = {},
+ } = props;
+ return (
+
+ );
+ }
+
const {
- variant = 'gridItem',
title = t('hasComponentsCard.title'),
- columns = componentEntityColumns,
- tableOptions = {},
+ columnConfig = entityColumnPresets.component.columns,
} = props;
return (
-
);
}
diff --git a/plugins/catalog/src/components/HasComponentsCard/index.ts b/plugins/catalog/src/components/HasComponentsCard/index.ts
index 86038b6030..96278c4513 100644
--- a/plugins/catalog/src/components/HasComponentsCard/index.ts
+++ b/plugins/catalog/src/components/HasComponentsCard/index.ts
@@ -15,4 +15,7 @@
*/
export { HasComponentsCard } from './HasComponentsCard';
-export type { HasComponentsCardProps } from './HasComponentsCard';
+export type {
+ HasComponentsCardLegacyProps,
+ HasComponentsCardProps,
+} from './HasComponentsCard';
diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx
index 81cd432290..34659c4930 100644
--- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx
+++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx
@@ -20,42 +20,88 @@ import {
TableColumn,
TableOptions,
} from '@backstage/core-components';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
asResourceEntities,
- RelatedEntitiesCard,
resourceEntityColumns,
- resourceEntityHelpLink,
+ resourceEntityHelpLink as legacyHelpLink,
+ RelatedEntitiesCard,
} from '../RelatedEntitiesCard';
import { catalogTranslationRef } from '../../alpha/translation';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @public */
export interface HasResourcesCardProps {
- variant?: InfoCardVariants;
title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link HasResourcesCardProps} instead.
+ * @public
+ */
+export interface HasResourcesCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
}
-export function HasResourcesCard(props: HasResourcesCardProps) {
+function isLegacyProps(
+ props: HasResourcesCardProps | HasResourcesCardLegacyProps,
+): props is HasResourcesCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+export function HasResourcesCard(
+ props: HasResourcesCardProps | HasResourcesCardLegacyProps,
+) {
const { t } = useTranslationRef(catalogTranslationRef);
+
+ if (isLegacyProps(props)) {
+ const {
+ variant = 'gridItem',
+ title = t('hasResourcesCard.title'),
+ columns = resourceEntityColumns,
+ tableOptions = {},
+ } = props;
+ return (
+
+ );
+ }
+
const {
- variant = 'gridItem',
title = t('hasResourcesCard.title'),
- columns = resourceEntityColumns,
- tableOptions = {},
+ columnConfig = entityColumnPresets.resource.columns,
} = props;
return (
-
);
}
diff --git a/plugins/catalog/src/components/HasResourcesCard/index.ts b/plugins/catalog/src/components/HasResourcesCard/index.ts
index cdbbef680b..171808d75c 100644
--- a/plugins/catalog/src/components/HasResourcesCard/index.ts
+++ b/plugins/catalog/src/components/HasResourcesCard/index.ts
@@ -15,4 +15,7 @@
*/
export { HasResourcesCard } from './HasResourcesCard';
-export type { HasResourcesCardProps } from './HasResourcesCard';
+export type {
+ HasResourcesCardLegacyProps,
+ HasResourcesCardProps,
+} from './HasResourcesCard';
diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx
index 84087ed082..7307379d5d 100644
--- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx
+++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx
@@ -20,6 +20,11 @@ import {
TableColumn,
TableOptions,
} from '@backstage/core-components';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
asComponentEntities,
componentEntityColumns,
@@ -30,33 +35,77 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @public */
export interface HasSubcomponentsCardProps {
- variant?: InfoCardVariants;
title?: string;
+ columnConfig?: EntityColumnConfig[];
+ kind?: string;
+}
+
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link HasSubcomponentsCardProps} instead.
+ * @public
+ */
+export interface HasSubcomponentsCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
kind?: string;
}
-export function HasSubcomponentsCard(props: HasSubcomponentsCardProps) {
+function isLegacyProps(
+ props: HasSubcomponentsCardProps | HasSubcomponentsCardLegacyProps,
+): props is HasSubcomponentsCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+export function HasSubcomponentsCard(
+ props: HasSubcomponentsCardProps | HasSubcomponentsCardLegacyProps,
+) {
const { t } = useTranslationRef(catalogTranslationRef);
+
+ if (isLegacyProps(props)) {
+ const {
+ variant = 'gridItem',
+ title = t('hasSubcomponentsCard.title'),
+ columns = componentEntityColumns,
+ tableOptions = {},
+ kind = 'Component',
+ } = props;
+ return (
+
+ );
+ }
+
const {
- variant = 'gridItem',
title = t('hasSubcomponentsCard.title'),
- columns = componentEntityColumns,
- tableOptions = {},
+ columnConfig = entityColumnPresets.component.columns,
kind = 'Component',
} = props;
return (
-
);
}
diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/index.ts b/plugins/catalog/src/components/HasSubcomponentsCard/index.ts
index 6f43967d50..44a6faf125 100644
--- a/plugins/catalog/src/components/HasSubcomponentsCard/index.ts
+++ b/plugins/catalog/src/components/HasSubcomponentsCard/index.ts
@@ -15,4 +15,7 @@
*/
export { HasSubcomponentsCard } from './HasSubcomponentsCard';
-export type { HasSubcomponentsCardProps } from './HasSubcomponentsCard';
+export type {
+ HasSubcomponentsCardLegacyProps,
+ HasSubcomponentsCardProps,
+} from './HasSubcomponentsCard';
diff --git a/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx b/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx
index 367dbe6c94..63c349ec60 100644
--- a/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx
+++ b/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx
@@ -20,10 +20,15 @@ import {
TableColumn,
TableOptions,
} from '@backstage/core-components';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
asDomainEntities,
domainEntityColumns,
- domainEntityHelpLink,
+ domainEntityHelpLink as legacyHelpLink,
RelatedEntitiesCard,
} from '../RelatedEntitiesCard';
import { catalogTranslationRef } from '../../alpha/translation';
@@ -31,31 +36,72 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @public */
export interface HasSubdomainsCardProps {
- variant?: InfoCardVariants;
- tableOptions?: TableOptions;
- columns?: TableColumn[];
title?: string;
+ columnConfig?: EntityColumnConfig[];
}
-export function HasSubdomainsCard(props: HasSubdomainsCardProps) {
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link HasSubdomainsCardProps} instead.
+ * @public
+ */
+export interface HasSubdomainsCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
+ tableOptions?: TableOptions;
+ /** @deprecated Use `columnConfig` instead. */
+ columns?: TableColumn[];
+}
+
+function isLegacyProps(
+ props: HasSubdomainsCardProps | HasSubdomainsCardLegacyProps,
+): props is HasSubdomainsCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+export function HasSubdomainsCard(
+ props: HasSubdomainsCardProps | HasSubdomainsCardLegacyProps,
+) {
const { t } = useTranslationRef(catalogTranslationRef);
+
+ if (isLegacyProps(props)) {
+ const {
+ variant = 'gridItem',
+ title = t('hasSubdomainsCard.title'),
+ columns = domainEntityColumns,
+ tableOptions = {},
+ } = props;
+ return (
+
+ );
+ }
+
const {
- variant = 'gridItem',
title = t('hasSubdomainsCard.title'),
- columns = domainEntityColumns,
- tableOptions = {},
+ columnConfig = entityColumnPresets.domain.columns,
} = props;
return (
-
);
}
diff --git a/plugins/catalog/src/components/HasSubdomainsCard/index.ts b/plugins/catalog/src/components/HasSubdomainsCard/index.ts
index a740abbcc2..57353f4226 100644
--- a/plugins/catalog/src/components/HasSubdomainsCard/index.ts
+++ b/plugins/catalog/src/components/HasSubdomainsCard/index.ts
@@ -15,4 +15,7 @@
*/
export { HasSubdomainsCard } from './HasSubdomainsCard';
-export type { HasSubdomainsCardProps } from './HasSubdomainsCard';
+export type {
+ HasSubdomainsCardLegacyProps,
+ HasSubdomainsCardProps,
+} from './HasSubdomainsCard';
diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx
index fab9cf4769..1234c45d6f 100644
--- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx
+++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx
@@ -20,42 +20,88 @@ import {
TableColumn,
TableOptions,
} from '@backstage/core-components';
+import {
+ EntityRelationCard,
+ EntityColumnConfig,
+ entityColumnPresets,
+} from '@backstage/plugin-catalog-react/alpha';
import {
asSystemEntities,
- RelatedEntitiesCard,
systemEntityColumns,
- systemEntityHelpLink,
+ systemEntityHelpLink as legacyHelpLink,
+ RelatedEntitiesCard,
} from '../RelatedEntitiesCard';
import { catalogTranslationRef } from '../../alpha/translation';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
/** @public */
export interface HasSystemsCardProps {
- variant?: InfoCardVariants;
title?: string;
+ columnConfig?: EntityColumnConfig[];
+}
+
+/**
+ * Props for the legacy MUI-based rendering.
+ * @deprecated Use {@link HasSystemsCardProps} instead.
+ * @public
+ */
+export interface HasSystemsCardLegacyProps {
+ title?: string;
+ /** @deprecated Use `columnConfig` instead. */
+ variant?: InfoCardVariants;
+ /** @deprecated Use `columnConfig` instead. */
columns?: TableColumn[];
+ /** @deprecated Use `columnConfig` instead. */
tableOptions?: TableOptions;
}
-export function HasSystemsCard(props: HasSystemsCardProps) {
+function isLegacyProps(
+ props: HasSystemsCardProps | HasSystemsCardLegacyProps,
+): props is HasSystemsCardLegacyProps {
+ return 'variant' in props || 'columns' in props || 'tableOptions' in props;
+}
+
+export function HasSystemsCard(
+ props: HasSystemsCardProps | HasSystemsCardLegacyProps,
+) {
const { t } = useTranslationRef(catalogTranslationRef);
+
+ if (isLegacyProps(props)) {
+ const {
+ variant = 'gridItem',
+ title = t('hasSystemsCard.title'),
+ columns = systemEntityColumns,
+ tableOptions = {},
+ } = props;
+ return (
+
+ );
+ }
+
const {
- variant = 'gridItem',
title = t('hasSystemsCard.title'),
- columns = systemEntityColumns,
- tableOptions = {},
+ columnConfig = entityColumnPresets.system.columns,
} = props;
return (
-
);
}
diff --git a/plugins/catalog/src/components/HasSystemsCard/index.ts b/plugins/catalog/src/components/HasSystemsCard/index.ts
index 71fb7539b1..fd5c204cdd 100644
--- a/plugins/catalog/src/components/HasSystemsCard/index.ts
+++ b/plugins/catalog/src/components/HasSystemsCard/index.ts
@@ -15,4 +15,7 @@
*/
export { HasSystemsCard } from './HasSystemsCard';
-export type { HasSystemsCardProps } from './HasSystemsCard';
+export type {
+ HasSystemsCardLegacyProps,
+ HasSystemsCardProps,
+} from './HasSystemsCard';
diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx
index 1337a6dc12..e07a949965 100644
--- a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx
+++ b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx
@@ -70,6 +70,7 @@ export type RelatedEntitiesCardProps = {
* this card as its implementation with some of the props set to the appropriate
* values.
*
+ * @deprecated Use {@link @backstage/plugin-catalog-react/alpha#EntityRelationCard} instead.
* @public
*/
export const RelatedEntitiesCard = (
diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts
index a59283c677..303677b68a 100644
--- a/plugins/catalog/src/index.ts
+++ b/plugins/catalog/src/index.ts
@@ -57,9 +57,18 @@ export {
CatalogSearchResultListItem,
} from './plugin';
-export type { DependencyOfComponentsCardProps } from './components/DependencyOfComponentsCard';
-export type { DependsOnComponentsCardProps } from './components/DependsOnComponentsCard';
-export type { DependsOnResourcesCardProps } from './components/DependsOnResourcesCard';
+export type {
+ DependencyOfComponentsCardLegacyProps,
+ DependencyOfComponentsCardProps,
+} from './components/DependencyOfComponentsCard';
+export type {
+ DependsOnComponentsCardLegacyProps,
+ DependsOnComponentsCardProps,
+} from './components/DependsOnComponentsCard';
+export type {
+ DependsOnResourcesCardLegacyProps,
+ DependsOnResourcesCardProps,
+} from './components/DependsOnResourcesCard';
export type {
EntityLinksEmptyStateClassKey,
EntityLinksCardProps,
@@ -69,10 +78,25 @@ export type {
export type { SystemDiagramCardClassKey } from './components/SystemDiagramCard';
export type { DefaultCatalogPageProps } from './components/CatalogPage';
export type { EntityContextMenuClassKey } from './components/EntityContextMenu';
-export type { HasComponentsCardProps } from './components/HasComponentsCard';
-export type { HasResourcesCardProps } from './components/HasResourcesCard';
-export type { HasSubcomponentsCardProps } from './components/HasSubcomponentsCard';
-export type { HasSubdomainsCardProps } from './components/HasSubdomainsCard';
-export type { HasSystemsCardProps } from './components/HasSystemsCard';
+export type {
+ HasComponentsCardLegacyProps,
+ HasComponentsCardProps,
+} from './components/HasComponentsCard';
+export type {
+ HasResourcesCardLegacyProps,
+ HasResourcesCardProps,
+} from './components/HasResourcesCard';
+export type {
+ HasSubcomponentsCardLegacyProps,
+ HasSubcomponentsCardProps,
+} from './components/HasSubcomponentsCard';
+export type {
+ HasSubdomainsCardLegacyProps,
+ HasSubdomainsCardProps,
+} from './components/HasSubdomainsCard';
+export type {
+ HasSystemsCardLegacyProps,
+ HasSystemsCardProps,
+} from './components/HasSystemsCard';
export type { RelatedEntitiesCardProps } from './components/RelatedEntitiesCard';
export type { CatalogSearchResultListItemProps } from './components/CatalogSearchResultListItem';
diff --git a/yarn.lock b/yarn.lock
index 072fb23cee..82b80a61cd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3968,10 +3968,12 @@ __metadata:
"@backstage/plugin-catalog-react": "workspace:^"
"@backstage/plugin-permission-react": "workspace:^"
"@backstage/test-utils": "workspace:^"
+ "@backstage/ui": "workspace:^"
"@graphiql/react": "npm:0.29.0"
"@material-ui/core": "npm:^4.12.2"
"@material-ui/icons": "npm:^4.9.1"
"@material-ui/lab": "npm:4.0.0-alpha.61"
+ "@remixicon/react": "npm:^4.6.0"
"@testing-library/dom": "npm:^10.0.0"
"@testing-library/jest-dom": "npm:^6.0.0"
"@testing-library/react": "npm:^16.0.0"