diff --git a/.changeset/silver-bikes-breathe.md b/.changeset/silver-bikes-breathe.md
new file mode 100644
index 0000000000..63dabf7b2e
--- /dev/null
+++ b/.changeset/silver-bikes-breathe.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': minor
+---
+
+allow entity switch to render all cases that match the condition
diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md
index 5720ee60da..d891ebb7c8 100644
--- a/plugins/catalog/api-report.md
+++ b/plugins/catalog/api-report.md
@@ -360,7 +360,7 @@ export function EntityProcessingErrorsPanel(): JSX.Element | null;
// @public (undocumented)
export const EntitySwitch: {
- (props: EntitySwitchProps): JSX.Element | null;
+ (props: EntitySwitchProps): JSX.Element;
Case: (_props: EntitySwitchCaseProps) => null;
};
@@ -381,6 +381,8 @@ export interface EntitySwitchCaseProps {
export interface EntitySwitchProps {
// (undocumented)
children: ReactNode;
+ // (undocumented)
+ renderMultipleMatches?: 'first' | 'all';
}
// @public @deprecated (undocumented)
diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
index 164c7e60fd..8ee44041c8 100644
--- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
+++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
@@ -131,6 +131,48 @@ describe('EntitySwitch', () => {
expect(screen.getByText('B')).toBeInTheDocument();
});
+ it('should render all elements that match the condition', () => {
+ const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
+
+ render(
+
+
+
+ A
} />
+ B} />
+ C} />
+ D} />
+
+
+ ,
+ );
+
+ expect(screen.getByText('A')).toBeInTheDocument();
+ expect(screen.getByText('B')).toBeInTheDocument();
+ expect(screen.queryByText('C')).not.toBeInTheDocument();
+ expect(screen.queryByText('D')).not.toBeInTheDocument();
+ });
+
+ it('should render default element if none of the cases match', () => {
+ const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
+
+ render(
+
+
+
+ A} />
+ B} />
+ C} />
+
+
+ ,
+ );
+
+ expect(screen.queryByText('A')).not.toBeInTheDocument();
+ expect(screen.queryByText('B')).not.toBeInTheDocument();
+ expect(screen.getByText('C')).toBeInTheDocument();
+ });
+
it('should switch with async condition that is true', async () => {
const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
@@ -150,6 +192,51 @@ describe('EntitySwitch', () => {
expect(screen.queryByText('B')).not.toBeInTheDocument();
});
+ it('should render all elements with async result as true', async () => {
+ const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
+
+ const shouldRender = () => Promise.resolve(true);
+ const shouldNotRender = () => Promise.resolve(false);
+ render(
+
+
+
+ A} />
+ B} />
+ C} />
+ D} />
+
+
+ ,
+ );
+
+ await expect(screen.findByText('A')).resolves.toBeInTheDocument();
+ await expect(screen.findByText('B')).resolves.toBeInTheDocument();
+ expect(screen.queryByText('C')).not.toBeInTheDocument();
+ expect(screen.queryByText('D')).not.toBeInTheDocument();
+ });
+
+ it('should render default element if none of the async cases match', async () => {
+ const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
+
+ const shouldNotRender = () => Promise.resolve(false);
+ render(
+
+
+
+ A} />
+ B} />
+ C} />
+
+
+ ,
+ );
+
+ await expect(screen.findByText('C')).resolves.toBeInTheDocument();
+ expect(screen.queryByText('A')).not.toBeInTheDocument();
+ expect(screen.queryByText('B')).not.toBeInTheDocument();
+ });
+
it('should switch with sync condition that is false', async () => {
const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity;
diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
index 993e7daa80..6ad3c66ab3 100644
--- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
+++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx
@@ -49,7 +49,7 @@ interface EntitySwitchCase {
}
type SwitchCaseResult = {
- if: boolean | Promise;
+ if?: boolean | Promise;
children: JSX.Element;
};
@@ -59,6 +59,7 @@ type SwitchCaseResult = {
*/
export interface EntitySwitchProps {
children: ReactNode;
+ renderMultipleMatches?: 'first' | 'all';
}
/** @public */
@@ -94,25 +95,45 @@ export const EntitySwitch = (props: EntitySwitchProps) => {
}
return [
{
- if: condition?.(entity, { apis }) ?? true,
+ if: condition?.(entity, { apis }),
children: elementsChildren,
},
];
}),
[apis, entity, loading],
);
+
const hasAsyncCases = results.some(
r => typeof r.if === 'object' && 'then' in r.if,
);
if (hasAsyncCases) {
- return ;
+ return (
+
+ );
}
- return results.find(r => r.if)?.children ?? null;
+ if (props.renderMultipleMatches === 'all') {
+ const children = results.filter(r => r.if).map(r => r.children);
+ if (children.length === 0) {
+ return getDefaultChildren(results);
+ }
+ return <>{children}>;
+ }
+
+ return results.find(r => r.if)?.children ?? getDefaultChildren(results);
};
-function AsyncEntitySwitch({ results }: { results: SwitchCaseResult[] }) {
+function AsyncEntitySwitch({
+ results,
+ renderMultipleMatches,
+}: {
+ results: SwitchCaseResult[];
+ renderMultipleMatches?: 'first' | 'all';
+}) {
const { loading, value } = useAsync(async () => {
const promises = results.map(
async ({ if: condition, children: output }) => {
@@ -123,11 +144,21 @@ function AsyncEntitySwitch({ results }: { results: SwitchCaseResult[] }) {
} catch {
/* ignored */
}
-
return null;
},
);
- return (await Promise.all(promises)).find(Boolean) ?? null;
+
+ if (renderMultipleMatches === 'all') {
+ const children = (await Promise.all(promises)).filter(Boolean);
+ if (children.length === 0) {
+ return getDefaultChildren(results);
+ }
+ return <>{children}>;
+ }
+
+ return (
+ (await Promise.all(promises)).find(Boolean) ?? getDefaultChildren(results)
+ );
}, [results]);
if (loading || !value) {
@@ -137,4 +168,8 @@ function AsyncEntitySwitch({ results }: { results: SwitchCaseResult[] }) {
return value;
}
+function getDefaultChildren(results: SwitchCaseResult[]) {
+ return results.filter(r => r.if === undefined)[0].children ?? null;
+}
+
EntitySwitch.Case = EntitySwitchCaseComponent;