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

View File

@ -51,6 +51,7 @@ import {
setLoveConsentState,
setProClickState,
} from './utils';
import { isBlockActive } from './Main.utils';
export const updateRequestHeaders = 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 {
constructor(props) {
super(props);
@ -244,15 +295,15 @@ class Main extends React.Component {
render() {
const {
children,
location,
migrationModeProgress,
currentSchema,
serverVersion,
metadata,
console_opts,
currentSchema,
currentSource,
dispatch,
metadata,
migrationModeProgress,
pathname,
schemaList,
serverVersion,
} = this.props;
const {
@ -262,9 +313,6 @@ class Main extends React.Component {
const appPrefix = '';
const currentLocation = location.pathname;
const currentActiveBlock = getPathRoot(currentLocation);
const getMainContent = () => {
let mainContent = null;
@ -281,16 +329,6 @@ class Main extends React.Component {
return mainContent;
};
const getSettingsSelectedMarker = () => {
let metadataSelectedMarker = null;
if (currentActiveBlock === 'settings') {
metadataSelectedMarker = <span className={styles.selected} />;
}
return metadataSelectedMarker;
};
const getMetadataStatusIcon = () => {
if (metadata.inconsistentObjects.length === 0) {
return <FaCog size="1.3rem" className={styles.question} />;
@ -334,41 +372,7 @@ class Main extends React.Component {
return adminSecretHtml;
};
const getSidebarItem = (
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>
);
};
const currentActiveBlock = getPathRoot(pathname);
return (
<div className={styles.container}>
@ -398,6 +402,8 @@ class Main extends React.Component {
FaFlask,
tooltips.apiExplorer,
'/api/api-explorer',
appPrefix,
pathname,
true
)}
{getSidebarItem(
@ -408,25 +414,33 @@ class Main extends React.Component {
? schemaList.length
? getSchemaBaseRoute(currentSchema, currentSource)
: getDataSourceBaseRoute(currentSource)
: '/data'
: '/data',
appPrefix,
pathname
)}
{getSidebarItem(
'Actions',
FaCogs,
tooltips.actions,
'/actions/manage/actions'
'/actions/manage/actions',
appPrefix,
pathname
)}
{getSidebarItem(
'Remote Schemas',
FaPlug,
tooltips.remoteSchema,
'/remote-schemas/manage/schemas'
'/remote-schemas/manage/schemas',
appPrefix,
pathname
)}{' '}
{getSidebarItem(
'Events',
FaCloud,
tooltips.events,
'/events/data/manage'
'/events/data/manage',
appPrefix,
pathname
)}
</ul>
</div>
@ -453,7 +467,7 @@ class Main extends React.Component {
<Link to="/settings">
<div className={styles.headerRightNavbarBtn}>
{getMetadataStatusIcon()}
{getSettingsSelectedMarker()}
{getSettingsSelectedMarker(pathname)}
</div>
</Link>
<Help isSelected={currentActiveBlock === 'support'} />
@ -496,13 +510,13 @@ const mapStateToProps = (state, ownProps) => {
return {
...state.main,
header: state.header,
pathname: ownProps.location.pathname,
currentSchema: state.tables.currentSchema,
currentSource: state.tables.currentDataSource,
metadata: state.metadata,
console_opts: state.telemetry.console_opts,
requestHeaders: state.tables.dataHeaders,
schemaList: state.tables.schemaList,
pathname: ownProps.location.pathname,
inconsistentInheritedRole:
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;
}
const getEnvVarAsString = (value: string) => {
export const getEnvVarAsString = (value: string) => {
if (!value || value === 'undefined') return undefined;
return value;
};