feat(EntityLayout): add 'if' prop to Route component

Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
Phil Kuang
2021-04-23 16:18:07 -04:00
parent 74a5b00eb6
commit e0c9ed759d
5 changed files with 105 additions and 5 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/core': patch
'@backstage/plugin-catalog': patch
---
Add `if` prop to `EntityLayout.Route` to conditionally render tabs
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export { TabbedLayout } from './TabbedLayout';
export { RoutedTabs } from './RoutedTabs';
+1
View File
@@ -33,6 +33,7 @@
"@backstage/catalog-client": "^0.3.10",
"@backstage/catalog-model": "^0.7.7",
"@backstage/core": "^0.7.6",
"@backstage/core-api": "^0.2.17",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.1",
"@backstage/integration-react": "^0.1.1",
@@ -101,4 +101,39 @@ describe('EntityLayout', () => {
expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
});
it('should conditionally render tabs', async () => {
const shouldRenderTab = (e: Entity) => e.metadata.name === 'my-entity';
const shouldNotRenderTab = (e: Entity) => e.metadata.name === 'some-entity';
const rendered = await renderInTestApp(
<ApiProvider apis={mockApis}>
<EntityContext.Provider value={mockEntityData}>
<EntityLayout>
<EntityLayout.Route path="/" title="tabbed-test-title">
<div>tabbed-test-content</div>
</EntityLayout.Route>
<EntityLayout.Route
path="/some-other-path"
title="tabbed-test-title-2"
if={shouldNotRenderTab}
>
<div>tabbed-test-content-2</div>
</EntityLayout.Route>
<EntityLayout.Route
path="/some-other-other-path"
title="tabbed-test-title-3"
if={shouldRenderTab}
>
<div>tabbed-test-content-3</div>
</EntityLayout.Route>
</EntityLayout>
</EntityContext.Provider>
</ApiProvider>,
);
expect(rendered.queryByText('tabbed-test-title')).toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-title-2')).not.toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-title-3')).toBeInTheDocument();
});
});
@@ -26,7 +26,7 @@ import {
IconComponent,
Page,
Progress,
TabbedLayout,
RoutedTabs,
} from '@backstage/core';
import {
EntityContext,
@@ -34,14 +34,70 @@ import {
getEntityRelations,
useEntityCompoundName,
} from '@backstage/plugin-catalog-react';
import { Box } from '@material-ui/core';
import { attachComponentData } from '@backstage/core-api';
import { Box, TabProps } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import { default as React, useContext, useState } from 'react';
import {
default as React,
Children,
Fragment,
isValidElement,
useContext,
useState,
} from 'react';
import { useNavigate } from 'react-router';
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
type SubRoute = {
path: string;
title: string;
children: JSX.Element;
if?: (entity: Entity) => boolean;
tabProps?: TabProps<React.ElementType, { component?: React.ElementType }>;
};
const Route: (props: SubRoute) => null = () => null;
// This causes all mount points that are discovered within this route to use the path of the route itself
attachComponentData(Route, 'core.gatherMountPoints', true);
function createSubRoutesFromChildren(
childrenProps: React.ReactNode,
entity: Entity | undefined,
): SubRoute[] {
// Directly comparing child.type with Route will not work with in
// combination with react-hot-loader in storybook
// https://github.com/gaearon/react-hot-loader/issues/304
const routeType = (
<Route path="" title="">
<div />
</Route>
).type;
return Children.toArray(childrenProps).flatMap(child => {
if (!isValidElement(child)) {
return [];
}
if (child.type === Fragment) {
return createSubRoutesFromChildren(child.props.children, entity);
}
if (child.type !== routeType) {
throw new Error('Child of EntityLayout must be an EntityLayout.Route');
}
const { path, title, children, if: condition, tabProps } = child.props;
if (condition && entity && !condition(entity)) {
return [];
}
return [{ path, title, children, tabProps }];
});
}
const EntityLayoutTitle = ({
entity,
title,
@@ -139,6 +195,7 @@ export const EntityLayout = ({
const { kind, namespace, name } = useEntityCompoundName();
const { entity, loading, error } = useContext(EntityContext);
const routes = createSubRoutesFromChildren(children, entity);
const { headerTitle, headerType } = headerProps(
kind,
namespace,
@@ -175,7 +232,7 @@ export const EntityLayout = ({
{loading && <Progress />}
{entity && <TabbedLayout>{children}</TabbedLayout>}
{entity && <RoutedTabs routes={routes} />}
{error && (
<Content>
@@ -192,4 +249,4 @@ export const EntityLayout = ({
);
};
EntityLayout.Route = TabbedLayout.Route;
EntityLayout.Route = Route;