Skip to content

Commit 4bf2666

Browse files
authored
feat(sidebar): disable clicking on the performance tab when unsupported in Atlas (#5235)
* feat(sidebar): disallow clicking on the performance tab when unsupported * chore(sidebar): use the same behavior for adf; fix tests
1 parent e85db71 commit 4bf2666

File tree

10 files changed

+197
-52
lines changed

10 files changed

+197
-52
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-home/src/index.spec.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const createDataService = () => ({
2727
configuredKMSProviders() {
2828
return [];
2929
},
30+
currentOp() {},
31+
top() {},
3032
on() {},
3133
off() {},
3234
removeListener() {},

packages/compass-sidebar/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"eslint": "^7.25.0",
108108
"lodash": "^4.17.21",
109109
"mocha": "^10.2.0",
110+
"mongodb": "^6.3.0",
110111
"mongodb-ns": "^2.4.0",
111112
"nyc": "^15.1.0",
112113
"prettier": "^2.7.1",

packages/compass-sidebar/src/components/navigation-items.spec.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ function renderNavigationItems(
2020
onAction={() => {
2121
/* noop */
2222
}}
23-
showPerformanceItem={false}
2423
showCreateDatabaseAction={true}
24+
isPerformanceTabSupported={true}
2525
onFilterChange={() => {
2626
/* noop */
2727
}}
@@ -59,4 +59,17 @@ describe('NavigationItems [Component]', function () {
5959
expect(screen.queryByLabelText(createDatabaseText)).to.not.exist;
6060
});
6161
});
62+
63+
describe('when performance tab is not supported', function () {
64+
it('renders disabled "Performance" navigation item', function () {
65+
renderNavigationItems({
66+
isPerformanceTabSupported: false,
67+
});
68+
69+
expect(screen.getByRole('button', { name: 'Performance' })).to.exist;
70+
expect(
71+
screen.getByRole('button', { name: 'Performance' })
72+
).to.have.attribute('aria-disabled', 'true');
73+
});
74+
});
6275
});

packages/compass-sidebar/src/components/navigation-items.tsx

Lines changed: 102 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useCallback, useMemo } from 'react';
22
import { connect } from 'react-redux';
33
import {
44
useHoverState,
@@ -14,6 +14,9 @@ import {
1414
PerformanceSignals,
1515
Placeholder,
1616
ContentWithFallback,
17+
palette,
18+
useDarkMode,
19+
Tooltip,
1720
} from '@mongodb-js/compass-components';
1821
import { usePreference, withPreferences } from 'compass-preferences-model';
1922
import type { ItemAction } from '@mongodb-js/compass-components';
@@ -117,17 +120,31 @@ const navigationItemLabel = css({
117120
marginLeft: spacing[2],
118121
});
119122

123+
const navigationItemDisabledDarkModeStyles = css({
124+
'--item-color': palette.gray.dark1,
125+
'--item-color-active': palette.gray.dark1,
126+
'--item-bg-color-hover': 'var(--item-bg-color)',
127+
});
128+
129+
const navigationItemDisabledLightModeStyles = css({
130+
'--item-color': palette.gray.base,
131+
'--item-color-active': palette.gray.base,
132+
'--item-bg-color-hover': 'var(--item-bg-color)',
133+
});
134+
120135
const navigationItemActionIcons = css({ color: 'inherit' });
121136

122137
export function NavigationItem<Actions extends string>({
123138
isExpanded,
124139
onAction,
125-
onClick,
140+
onClick: onButtonClick,
126141
glyph,
127142
label,
128143
actions,
129144
isActive,
130145
showTooManyCollectionsInsight,
146+
disabled: isButtonDisabled = false,
147+
disabledMessage: buttonDisabledMessage,
131148
}: {
132149
isExpanded?: boolean;
133150
onAction(actionName: Actions, ...rest: any[]): void;
@@ -137,17 +154,35 @@ export function NavigationItem<Actions extends string>({
137154
actions?: ItemAction<Actions>[];
138155
isActive: boolean;
139156
showTooManyCollectionsInsight?: boolean;
157+
disabled?: boolean;
158+
disabledMessage?: string;
140159
}) {
160+
const darkMode = useDarkMode();
141161
const showInsights = usePreference('showInsights', React);
162+
const onClick = useCallback(() => {
163+
if (isButtonDisabled) {
164+
return;
165+
}
166+
onButtonClick();
167+
}, [isButtonDisabled, onButtonClick]);
142168
const [hoverProps] = useHoverState();
143169
const focusRingProps = useFocusRing();
144170
const defaultActionProps = useDefaultAction(onClick);
145171

146172
const navigationItemProps = mergeProps(
147173
{
148-
className: cx(navigationItem, isActive && activeNavigationItem),
174+
className: cx(
175+
navigationItem,
176+
isActive && activeNavigationItem,
177+
isButtonDisabled &&
178+
(darkMode
179+
? navigationItemDisabledDarkModeStyles
180+
: navigationItemDisabledLightModeStyles)
181+
),
182+
role: 'button',
149183
['aria-label']: label,
150184
['aria-current']: isActive,
185+
['aria-disabled']: isButtonDisabled,
151186
tabIndex: 0,
152187
},
153188
hoverProps,
@@ -156,37 +191,52 @@ export function NavigationItem<Actions extends string>({
156191
) as React.HTMLProps<HTMLDivElement>;
157192

158193
return (
159-
<div {...navigationItemProps}>
160-
<div className={itemWrapper}>
161-
<div className={itemButtonWrapper}>
162-
<Icon glyph={glyph} size="small"></Icon>
163-
{isExpanded && <span className={navigationItemLabel}>{label}</span>}
164-
</div>
165-
{showInsights && isExpanded && showTooManyCollectionsInsight && (
166-
<div className={signalContainerStyles}>
167-
<SignalPopover
168-
signals={PerformanceSignals.get('too-many-collections')}
169-
></SignalPopover>
194+
<Tooltip
195+
align="right"
196+
spacing={spacing[3]}
197+
isDisabled={!isButtonDisabled || !buttonDisabledMessage}
198+
trigger={({ children: tooltip, ...triggerProps }) => {
199+
const props = mergeProps(triggerProps, navigationItemProps);
200+
return (
201+
<div {...props}>
202+
<div className={itemWrapper}>
203+
<div className={itemButtonWrapper}>
204+
<Icon glyph={glyph} size="small"></Icon>
205+
{isExpanded && (
206+
<span className={navigationItemLabel}>{label}</span>
207+
)}
208+
{tooltip}
209+
</div>
210+
{showInsights && isExpanded && showTooManyCollectionsInsight && (
211+
<div className={signalContainerStyles}>
212+
<SignalPopover
213+
signals={PerformanceSignals.get('too-many-collections')}
214+
></SignalPopover>
215+
</div>
216+
)}
217+
{!isButtonDisabled && isExpanded && actions && (
218+
<ItemActionControls<Actions>
219+
iconSize="small"
220+
onAction={onAction}
221+
data-testid="sidebar-navigation-item-actions"
222+
actions={actions}
223+
// This is what renders the "create database" action,
224+
// the icons here should always be clearly visible,
225+
// so we let the icon to inherit the foreground color of
226+
// the text
227+
isVisible={true}
228+
iconClassName={navigationItemActionIcons}
229+
collapseToMenuThreshold={3}
230+
></ItemActionControls>
231+
)}
232+
<div className={cx('item-background', itemBackground)} />
233+
</div>
170234
</div>
171-
)}
172-
{isExpanded && actions && (
173-
<ItemActionControls<Actions>
174-
iconSize="small"
175-
onAction={onAction}
176-
data-testid="sidebar-navigation-item-actions"
177-
actions={actions}
178-
// This is what renders the "create database" action,
179-
// the icons here should always be clearly visible,
180-
// so we let the icon to inherit the foreground color of
181-
// the text
182-
isVisible={true}
183-
iconClassName={navigationItemActionIcons}
184-
collapseToMenuThreshold={3}
185-
></ItemActionControls>
186-
)}
187-
<div className={cx('item-background', itemBackground)} />
188-
</div>
189-
</div>
235+
);
236+
}}
237+
>
238+
{buttonDisabledMessage}
239+
</Tooltip>
190240
);
191241
}
192242

@@ -201,8 +251,8 @@ const PlaceholderItem = ({ forLabel }: { forLabel: string }) => {
201251
export function NavigationItems({
202252
isReady,
203253
isExpanded,
204-
showCreateDatabaseAction = false,
205-
showPerformanceItem = false,
254+
showCreateDatabaseAction,
255+
isPerformanceTabSupported,
206256
onFilterChange,
207257
onAction,
208258
currentLocation,
@@ -211,8 +261,8 @@ export function NavigationItems({
211261
}: {
212262
isReady?: boolean;
213263
isExpanded?: boolean;
214-
showCreateDatabaseAction?: boolean;
215-
showPerformanceItem?: boolean;
264+
showCreateDatabaseAction: boolean;
265+
isPerformanceTabSupported: boolean;
216266
onFilterChange(regex: RegExp | null): void;
217267
onAction(actionName: string, ...rest: any[]): void;
218268
currentLocation: string | null;
@@ -272,16 +322,16 @@ export function NavigationItems({
272322
label="My Queries"
273323
isActive={currentLocation === 'My Queries'}
274324
/>
275-
{showPerformanceItem && (
276-
<NavigationItem<''>
277-
isExpanded={isExpanded}
278-
onAction={onAction}
279-
onClick={openPerformanceWorkspace}
280-
glyph="Gauge"
281-
label="Performance"
282-
isActive={currentLocation === 'Performance'}
283-
/>
284-
)}
325+
<NavigationItem<''>
326+
isExpanded={isExpanded}
327+
onAction={onAction}
328+
onClick={openPerformanceWorkspace}
329+
glyph="Gauge"
330+
label="Performance"
331+
isActive={currentLocation === 'Performance'}
332+
disabled={!isPerformanceTabSupported}
333+
disabledMessage="Performance metrics are not available for your deployment or to your database user"
334+
/>
285335
<NavigationItem<DatabasesActions>
286336
isExpanded={isExpanded}
287337
onAction={onAction}
@@ -321,9 +371,10 @@ const mapStateToProps = (
321371
0
322372
);
323373

324-
const isReady = ['ready', 'refreshing'].includes(
325-
state.instance?.status ?? ''
326-
);
374+
const isReady =
375+
['ready', 'refreshing'].includes(state.instance?.status ?? '') &&
376+
state.isPerformanceTabSupported !== null;
377+
327378
const isDataLake = state.instance?.dataLake.isDataLake ?? false;
328379
const isWritable = state.instance?.isWritable ?? false;
329380

@@ -332,6 +383,7 @@ const mapStateToProps = (
332383
showPerformanceItem: !isDataLake,
333384
showCreateDatabaseAction: !isDataLake && isWritable && !preferencesReadOnly,
334385
showTooManyCollectionsInsight: totalCollectionsCount > 10_000,
386+
isPerformanceTabSupported: !isDataLake && !!state.isPerformanceTabSupported,
335387
};
336388
};
337389

packages/compass-sidebar/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { mongoDBInstanceLocator } from '@mongodb-js/compass-app-stores/provider'
77
import { dataServiceLocator } from 'mongodb-data-service/provider';
88
import type { DataService } from 'mongodb-data-service';
99
import type { MongoDBInstance } from 'mongodb-instance-model';
10+
import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider';
11+
import { createLoggerAndTelemetryLocator } from '@mongodb-js/compass-logging/provider';
1012

1113
function activate() {
1214
// noop
@@ -21,6 +23,7 @@ export const CompassSidebarPlugin = registerHadronPlugin<
2123
{
2224
instance: () => MongoDBInstance;
2325
dataService: () => DataService;
26+
logger: () => LoggerAndTelemetry;
2427
}
2528
>(
2629
{
@@ -32,10 +35,12 @@ export const CompassSidebarPlugin = registerHadronPlugin<
3235
globalAppRegistry,
3336
instance,
3437
dataService,
38+
logger,
3539
}: {
3640
globalAppRegistry: AppRegistry;
3741
instance: MongoDBInstance;
3842
dataService: DataService;
43+
logger: LoggerAndTelemetry;
3944
},
4045
helpers: ActivateHelpers
4146
) {
@@ -45,6 +50,7 @@ export const CompassSidebarPlugin = registerHadronPlugin<
4550
instance,
4651
dataService,
4752
connectionInfo: initialConnectionInfo,
53+
logger,
4854
},
4955
helpers
5056
);
@@ -57,6 +63,7 @@ export const CompassSidebarPlugin = registerHadronPlugin<
5763
{
5864
instance: mongoDBInstanceLocator,
5965
dataService: dataServiceLocator,
66+
logger: createLoggerAndTelemetryLocator('COMPASS-SIDEBAR-UI'),
6067
}
6168
);
6269

packages/compass-sidebar/src/modules/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ import isExpanded from './is-expanded';
3030
import type { AppRegistry } from 'hadron-app-registry';
3131
import type { DataServiceAction, DataServiceState } from './data-service';
3232
import dataService from './data-service';
33+
import type {
34+
IsPerformanceTabSupportedState,
35+
SetIsPerformanceTabSupportedAction,
36+
} from './is-performance-tab-supported';
37+
import isPerformanceTabSupported from './is-performance-tab-supported';
3338

3439
export interface RootState {
3540
appRegistry: {
@@ -44,6 +49,7 @@ export interface RootState {
4449
isDetailsExpanded: IsDetailsExpandedState;
4550
isGenuineMongoDBVisible: IsGenuineMongoDBVisibleState;
4651
isExpanded: IsExpandedState;
52+
isPerformanceTabSupported: IsPerformanceTabSupportedState;
4753
}
4854

4955
export type RootAction =
@@ -54,7 +60,8 @@ export type RootAction =
5460
| ToggleIsDetailsExpandedAction
5561
| IsGenuineMongoDBVisibleAction
5662
| IsExpandedAction
57-
| DataServiceAction;
63+
| DataServiceAction
64+
| SetIsPerformanceTabSupportedAction;
5865

5966
/**
6067
* The reducer.
@@ -69,6 +76,7 @@ const reducer = combineReducers<RootState, RootAction>({
6976
isDetailsExpanded,
7077
isGenuineMongoDBVisible,
7178
isExpanded,
79+
isPerformanceTabSupported,
7280
});
7381

7482
export default reducer;

0 commit comments

Comments
 (0)