Merge pull request #16585 from drodil/entity_switch_multi_render
feat: allow entity switch to render all cases that match
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': minor
|
||||
---
|
||||
|
||||
allow entity switch to render all cases that match the condition
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntitySwitch renderMultipleMatches="all">
|
||||
<EntitySwitch.Case if={isKind('component')} children={<p>A</p>} />
|
||||
<EntitySwitch.Case if={isKind('component')} children={<p>B</p>} />
|
||||
<EntitySwitch.Case if={isKind('system')} children={<p>C</p>} />
|
||||
<EntitySwitch.Case children={<p>D</p>} />
|
||||
</EntitySwitch>
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
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(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntitySwitch renderMultipleMatches="all">
|
||||
<EntitySwitch.Case if={isKind('system')} children={<p>A</p>} />
|
||||
<EntitySwitch.Case if={isKind('system')} children={<p>B</p>} />
|
||||
<EntitySwitch.Case children={<p>C</p>} />
|
||||
</EntitySwitch>
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
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(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntitySwitch renderMultipleMatches="all">
|
||||
<EntitySwitch.Case if={shouldRender} children={<p>A</p>} />
|
||||
<EntitySwitch.Case if={shouldRender} children={<p>B</p>} />
|
||||
<EntitySwitch.Case if={shouldNotRender} children={<p>C</p>} />
|
||||
<EntitySwitch.Case children={<p>D</p>} />
|
||||
</EntitySwitch>
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
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(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntitySwitch renderMultipleMatches="all">
|
||||
<EntitySwitch.Case if={shouldNotRender} children={<p>A</p>} />
|
||||
<EntitySwitch.Case if={shouldNotRender} children={<p>B</p>} />
|
||||
<EntitySwitch.Case children={<p>C</p>} />
|
||||
</EntitySwitch>
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ interface EntitySwitchCase {
|
||||
}
|
||||
|
||||
type SwitchCaseResult = {
|
||||
if: boolean | Promise<boolean>;
|
||||
if?: boolean | Promise<boolean>;
|
||||
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 <AsyncEntitySwitch results={results} />;
|
||||
return (
|
||||
<AsyncEntitySwitch
|
||||
results={results}
|
||||
renderMultipleMatches={props.renderMultipleMatches}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user