console: fix cloud console header active tab highlighting

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4799
Co-authored-by: Nicolas Beaussart <7281023+beaussan@users.noreply.github.com>
GitOrigin-RevId: bc5b1c99f555ec39a43fbad5407461e8419003fa
This commit is contained in:
Luca Restagno 2022-06-30 10:05:30 +02:00 committed by hasura-bot
parent 25bd8b6e0f
commit 27398f40f4
6 changed files with 168 additions and 63 deletions

View File

@ -0,0 +1,45 @@
import { getPathRoot } from '../urlUtils';
describe('getPathRoot', () => {
describe('returns empty string with falsy paths', () => {
it.each([
{
path: undefined,
expected: '',
},
{
path: null,
expected: '',
},
{
path: '',
expected: '',
},
])('for %o', ({ path, expected }) => {
expect(getPathRoot(path)).toBe(expected);
});
});
describe('returns the first part of the path', () => {
it.each([
{
path: '/foo/bar/doe',
expected: 'foo',
},
{
path: '/foo/bar',
expected: 'foo',
},
{
path: '/foo',
expected: 'foo',
},
{
path: '/',
expected: '',
},
])('for %o', ({ path, expected }) => {
expect(getPathRoot(path)).toBe(expected);
});
});
});

View File

@ -1,6 +1,6 @@
export const getPathRoot = (path: string) => { export const getPathRoot = (path: string | undefined | null = '') =>
return path.split('/')[1]; path?.split('/')[1] ?? '';
};
export const stripTrailingSlash = (url: string) => { export const stripTrailingSlash = (url: string) => {
if (url && url.endsWith('/')) { if (url && url.endsWith('/')) {
return url.slice(0, -1); return url.slice(0, -1);

View File

@ -51,6 +51,7 @@ import {
setLoveConsentState, setLoveConsentState,
setProClickState, setProClickState,
} from './utils'; } from './utils';
import { isBlockActive } from './Main.utils';
export const updateRequestHeaders = props => { export const updateRequestHeaders = props => {
const { requestHeaders, dispatch } = props; const { requestHeaders, dispatch } = props;
@ -79,6 +80,56 @@ export const updateRequestHeaders = props => {
} }
}; };
const getSettingsSelectedMarker = pathname => {
const currentActiveBlock = getPathRoot(pathname);
if (currentActiveBlock === 'settings') {
return <span className={styles.selected} />;
}
return null;
};
const getSidebarItem = (
title,
icon,
tooltipText,
path,
appPrefix,
pathname,
isDefault = false
) => {
const itemTooltip = <Tooltip id={tooltipText}>{tooltipText}</Tooltip>;
const block = getPathRoot(path);
const isCurrentBlockActive = isBlockActive({
blockPath: path,
isDefaultBlock: isDefault,
pathname,
});
const className = isCurrentBlockActive ? styles.navSideBarActive : '';
return (
<OverlayTrigger placement="right" overlay={itemTooltip}>
<li>
<Link
className={className}
to={appPrefix + path}
data-test={`${title.toLowerCase()}-tab-link`}
>
<div className={styles.iconCenter} data-test={block}>
{React.createElement(icon, {
'aria-hidden': true,
})}
</div>
<p>{title}</p>
</Link>
</li>
</OverlayTrigger>
);
};
class Main extends React.Component { class Main extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -244,15 +295,15 @@ class Main extends React.Component {
render() { render() {
const { const {
children, children,
location,
migrationModeProgress,
currentSchema,
serverVersion,
metadata,
console_opts, console_opts,
currentSchema,
currentSource, currentSource,
dispatch, dispatch,
metadata,
migrationModeProgress,
pathname,
schemaList, schemaList,
serverVersion,
} = this.props; } = this.props;
const { const {
@ -262,9 +313,6 @@ class Main extends React.Component {
const appPrefix = ''; const appPrefix = '';
const currentLocation = location.pathname;
const currentActiveBlock = getPathRoot(currentLocation);
const getMainContent = () => { const getMainContent = () => {
let mainContent = null; let mainContent = null;
@ -281,16 +329,6 @@ class Main extends React.Component {
return mainContent; return mainContent;
}; };
const getSettingsSelectedMarker = () => {
let metadataSelectedMarker = null;
if (currentActiveBlock === 'settings') {
metadataSelectedMarker = <span className={styles.selected} />;
}
return metadataSelectedMarker;
};
const getMetadataStatusIcon = () => { const getMetadataStatusIcon = () => {
if (metadata.inconsistentObjects.length === 0) { if (metadata.inconsistentObjects.length === 0) {
return <FaCog size="1.3rem" className={styles.question} />; return <FaCog size="1.3rem" className={styles.question} />;
@ -334,41 +372,7 @@ class Main extends React.Component {
return adminSecretHtml; return adminSecretHtml;
}; };
const getSidebarItem = ( const currentActiveBlock = getPathRoot(pathname);
title,
icon,
tooltipText,
path,
isDefault = false
) => {
const itemTooltip = <Tooltip id={tooltipText}>{tooltipText}</Tooltip>;
const block = getPathRoot(path);
return (
<OverlayTrigger placement="right" overlay={itemTooltip}>
<li>
<Link
className={
currentActiveBlock === block ||
(isDefault && currentActiveBlock === '')
? styles.navSideBarActive
: ''
}
to={appPrefix + path}
data-test={`${title.toLowerCase()}-tab-link`}
>
<div className={styles.iconCenter} data-test={block}>
{React.createElement(icon, {
'aria-hidden': true,
})}
</div>
<p>{title}</p>
</Link>
</li>
</OverlayTrigger>
);
};
return ( return (
<div className={styles.container}> <div className={styles.container}>
@ -398,6 +402,8 @@ class Main extends React.Component {
FaFlask, FaFlask,
tooltips.apiExplorer, tooltips.apiExplorer,
'/api/api-explorer', '/api/api-explorer',
appPrefix,
pathname,
true true
)} )}
{getSidebarItem( {getSidebarItem(
@ -408,25 +414,33 @@ class Main extends React.Component {
? schemaList.length ? schemaList.length
? getSchemaBaseRoute(currentSchema, currentSource) ? getSchemaBaseRoute(currentSchema, currentSource)
: getDataSourceBaseRoute(currentSource) : getDataSourceBaseRoute(currentSource)
: '/data' : '/data',
appPrefix,
pathname
)} )}
{getSidebarItem( {getSidebarItem(
'Actions', 'Actions',
FaCogs, FaCogs,
tooltips.actions, tooltips.actions,
'/actions/manage/actions' '/actions/manage/actions',
appPrefix,
pathname
)} )}
{getSidebarItem( {getSidebarItem(
'Remote Schemas', 'Remote Schemas',
FaPlug, FaPlug,
tooltips.remoteSchema, tooltips.remoteSchema,
'/remote-schemas/manage/schemas' '/remote-schemas/manage/schemas',
appPrefix,
pathname
)}{' '} )}{' '}
{getSidebarItem( {getSidebarItem(
'Events', 'Events',
FaCloud, FaCloud,
tooltips.events, tooltips.events,
'/events/data/manage' '/events/data/manage',
appPrefix,
pathname
)} )}
</ul> </ul>
</div> </div>
@ -453,7 +467,7 @@ class Main extends React.Component {
<Link to="/settings"> <Link to="/settings">
<div className={styles.headerRightNavbarBtn}> <div className={styles.headerRightNavbarBtn}>
{getMetadataStatusIcon()} {getMetadataStatusIcon()}
{getSettingsSelectedMarker()} {getSettingsSelectedMarker(pathname)}
</div> </div>
</Link> </Link>
<Help isSelected={currentActiveBlock === 'support'} /> <Help isSelected={currentActiveBlock === 'support'} />
@ -496,13 +510,13 @@ const mapStateToProps = (state, ownProps) => {
return { return {
...state.main, ...state.main,
header: state.header, header: state.header,
pathname: ownProps.location.pathname,
currentSchema: state.tables.currentSchema, currentSchema: state.tables.currentSchema,
currentSource: state.tables.currentDataSource, currentSource: state.tables.currentDataSource,
metadata: state.metadata, metadata: state.metadata,
console_opts: state.telemetry.console_opts, console_opts: state.telemetry.console_opts,
requestHeaders: state.tables.dataHeaders, requestHeaders: state.tables.dataHeaders,
schemaList: state.tables.schemaList, schemaList: state.tables.schemaList,
pathname: ownProps.location.pathname,
inconsistentInheritedRole: inconsistentInheritedRole:
state.tables.modify.permissionsState.inconsistentInhertiedRole, state.tables.modify.permissionsState.inconsistentInhertiedRole,
}; };

View File

@ -0,0 +1,21 @@
import { getPathRoot } from '../Common/utils/urlUtils';
type IsBlockActiveArgs = {
blockPath: string;
pathname: string;
isDefaultBlock: boolean;
};
export const isBlockActive = ({
blockPath,
isDefaultBlock,
pathname,
}: IsBlockActiveArgs) => {
const block = getPathRoot(blockPath);
const currentActiveBlock = getPathRoot(pathname);
return (
currentActiveBlock === block ||
(isDefaultBlock && currentActiveBlock === '')
);
};

View File

@ -0,0 +1,25 @@
import { isBlockActive } from '../Main.utils';
describe('isBlockActive', () => {
it.each([
{
blockPath: '/api/api-explorer',
isDefaultBlock: true,
pathname: '/',
expected: true,
},
{
blockPath: '/data/default',
isDefaultBlock: false,
pathname: '/data/default',
expected: true,
},
])('for %o', ({ blockPath, isDefaultBlock, pathname, expected }) => {
const currentActiveBlock = isBlockActive({
blockPath,
isDefaultBlock,
pathname,
});
expect(currentActiveBlock).toBe(expected);
});
});

View File

@ -6,7 +6,7 @@ export interface ConsoleConfig {
type: string; type: string;
} }
const getEnvVarAsString = (value: string) => { export const getEnvVarAsString = (value: string) => {
if (!value || value === 'undefined') return undefined; if (!value || value === 'undefined') return undefined;
return value; return value;
}; };