mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-16 01:44:03 +03:00
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:
parent
25bd8b6e0f
commit
27398f40f4
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
||||
|
21
console/src/components/Main/Main.utils.ts
Normal file
21
console/src/components/Main/Main.utils.ts
Normal 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 === '')
|
||||
);
|
||||
};
|
25
console/src/components/Main/__tests__/Main.utils.spec.ts
Normal file
25
console/src/components/Main/__tests__/Main.utils.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user