Merge pull request #28651 from backstage/catalog-inspect-dialog-url
Catalog: retain inspect dialog visibility on page reload
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
---
|
||||
|
||||
Fixed an issue causing the `CatalogGraphCard` to redraw its content whenever the parent component re-renders, resulting in flickering.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-catalog': minor
|
||||
---
|
||||
|
||||
The Entity Page now retains the visibility of the Inspect Dialog after a reload. This allows sharing the URL with the dialog open.
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import qs from 'qs';
|
||||
import React, { MouseEvent, ReactNode, useCallback } from 'react';
|
||||
import React, { MouseEvent, ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { catalogGraphRouteRef } from '../../routes';
|
||||
import {
|
||||
@@ -86,7 +86,7 @@ export const CatalogGraphCard = (
|
||||
} = props;
|
||||
|
||||
const { entity } = useEntity();
|
||||
const entityName = getCompoundEntityRef(entity);
|
||||
const entityName = useMemo(() => getCompoundEntityRef(entity), [entity]);
|
||||
const catalogEntityRoute = useRouteRef(entityRouteRef);
|
||||
const catalogGraphRoute = useRouteRef(catalogGraphRouteRef);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -685,7 +685,9 @@ export function humanizeEntityRef(
|
||||
export function InspectEntityDialog(props: {
|
||||
open: boolean;
|
||||
entity: Entity;
|
||||
initialTab?: 'overview' | 'ancestry' | 'colocated' | 'json' | 'yaml';
|
||||
onClose: () => void;
|
||||
onSelect?: (tab: string) => void;
|
||||
}): React_2.JSX.Element | null;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -24,7 +24,7 @@ import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { ComponentProps, useEffect } from 'react';
|
||||
import { AncestryPage } from './components/AncestryPage';
|
||||
import { ColocatedPage } from './components/ColocatedPage';
|
||||
import { JsonPage } from './components/JsonPage';
|
||||
@@ -85,6 +85,19 @@ function a11yProps(index: number) {
|
||||
};
|
||||
}
|
||||
|
||||
const tabNames: Record<
|
||||
NonNullable<ComponentProps<typeof InspectEntityDialog>['initialTab']>,
|
||||
string
|
||||
> = {
|
||||
overview: 'Overview',
|
||||
ancestry: 'Ancestry',
|
||||
colocated: 'Colocated',
|
||||
json: 'Raw JSON',
|
||||
yaml: 'Raw YAML',
|
||||
} as const;
|
||||
|
||||
const tabs = Object.keys(tabNames) as Array<keyof typeof tabNames>;
|
||||
|
||||
/**
|
||||
* A dialog that lets users inspect the low level details of their entities.
|
||||
*
|
||||
@@ -93,15 +106,20 @@ function a11yProps(index: number) {
|
||||
export function InspectEntityDialog(props: {
|
||||
open: boolean;
|
||||
entity: Entity;
|
||||
initialTab?: 'overview' | 'ancestry' | 'colocated' | 'json' | 'yaml';
|
||||
onClose: () => void;
|
||||
onSelect?: (tab: string) => void;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const [activeTab, setActiveTab] = React.useState(0);
|
||||
|
||||
const [activeTab, setActiveTab] = React.useState(
|
||||
getTabIndex(tabs, props.initialTab),
|
||||
);
|
||||
const { t } = useTranslationRef(catalogReactTranslationRef);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveTab(0);
|
||||
}, [props.open]);
|
||||
getTabIndex(tabs, props.initialTab);
|
||||
}, [props.open, props.initialTab]);
|
||||
|
||||
if (!props.entity) {
|
||||
return null;
|
||||
@@ -125,15 +143,16 @@ export function InspectEntityDialog(props: {
|
||||
orientation="vertical"
|
||||
variant="scrollable"
|
||||
value={activeTab}
|
||||
onChange={(_, newValue) => setActiveTab(newValue)}
|
||||
onChange={(_, tabIndex) => {
|
||||
setActiveTab(tabIndex);
|
||||
props.onSelect?.(tabs[tabIndex]);
|
||||
}}
|
||||
aria-label="Inspector options"
|
||||
className={classes.tabs}
|
||||
>
|
||||
<Tab label="Overview" {...a11yProps(0)} />
|
||||
<Tab label="Ancestry" {...a11yProps(1)} />
|
||||
<Tab label="Colocated" {...a11yProps(2)} />
|
||||
<Tab label="Raw JSON" {...a11yProps(3)} />
|
||||
<Tab label="Raw YAML" {...a11yProps(4)} />
|
||||
{tabs.map((tab, index) => (
|
||||
<Tab key={tab} label={tabNames[tab]} {...a11yProps(index)} />
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
<TabPanel value={activeTab} index={0}>
|
||||
@@ -161,3 +180,10 @@ export function InspectEntityDialog(props: {
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function getTabIndex(
|
||||
allTabs: string[],
|
||||
initialTab: keyof typeof tabNames | undefined,
|
||||
) {
|
||||
return initialTab ? allTabs.indexOf(initialTab) : 0;
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ import Box from '@material-ui/core/Box';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { TabProps } from '@material-ui/core/Tab';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import React, { ComponentProps, useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { catalogTranslationRef } from '../../alpha/translation';
|
||||
import { rootRouteRef, unregisterRedirectRouteRef } from '../../routes';
|
||||
@@ -284,15 +284,15 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
);
|
||||
|
||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||
const [inspectionDialogOpen, setInspectionDialogOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const catalogRoute = useRouteRef(rootRouteRef);
|
||||
const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef);
|
||||
const { t } = useTranslationRef(catalogTranslationRef);
|
||||
|
||||
const cleanUpAfterRemoval = async () => {
|
||||
setConfirmationDialogOpen(false);
|
||||
setInspectionDialogOpen(false);
|
||||
navigate(
|
||||
unregisterRedirectRoute ? unregisterRedirectRoute() : catalogRoute(),
|
||||
);
|
||||
@@ -318,10 +318,12 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
// to another entity.
|
||||
useEffect(() => {
|
||||
setConfirmationDialogOpen(false);
|
||||
setInspectionDialogOpen(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location.pathname]);
|
||||
|
||||
const selectedInspectTab = searchParams.get('inspect');
|
||||
const showInspectTab = typeof selectedInspectTab === 'string';
|
||||
|
||||
return (
|
||||
<Page themeId={entity?.spec?.type?.toString() ?? 'home'}>
|
||||
<Header
|
||||
@@ -353,7 +355,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}
|
||||
UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}
|
||||
onUnregisterEntity={() => setConfirmationDialogOpen(true)}
|
||||
onInspectEntity={() => setInspectionDialogOpen(true)}
|
||||
onInspectEntity={() => setSearchParams('inspect')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -385,17 +387,26 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
</Content>
|
||||
)}
|
||||
|
||||
{showInspectTab && (
|
||||
<InspectEntityDialog
|
||||
entity={entity!}
|
||||
initialTab={
|
||||
(selectedInspectTab as ComponentProps<
|
||||
typeof InspectEntityDialog
|
||||
>['initialTab']) || undefined
|
||||
}
|
||||
onSelect={newTab => setSearchParams(`inspect=${newTab}`)}
|
||||
open
|
||||
onClose={() => setSearchParams()}
|
||||
/>
|
||||
)}
|
||||
|
||||
<UnregisterEntityDialog
|
||||
open={confirmationDialogOpen}
|
||||
entity={entity!}
|
||||
onConfirm={cleanUpAfterRemoval}
|
||||
onClose={() => setConfirmationDialogOpen(false)}
|
||||
/>
|
||||
<InspectEntityDialog
|
||||
open={inspectionDialogOpen}
|
||||
entity={entity!}
|
||||
onClose={() => setInspectionDialogOpen(false)}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user