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:
Fredrik Adelöw
2023-03-07 11:29:04 +01:00
committed by GitHub
4 changed files with 137 additions and 8 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': minor
---
allow entity switch to render all cases that match the condition
+3 -1
View File
@@ -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;