Skip to content

Commit 84b81f4

Browse files
gribnoysupAnemy
andauthored
feat(compass-sidebar, databases-list): show insight when number of collections is too high COMPASS-6835 (#4534)
* feat(compass-sidebar, databases-list): show insight when number of collections is too high * chore(compass-sidebar): fix typo Co-authored-by: Rhys <[email protected]> --------- Co-authored-by: Rhys <[email protected]>
1 parent 4539b5c commit 84b81f4

File tree

6 files changed

+151
-31
lines changed

6 files changed

+151
-31
lines changed

packages/compass-components/src/components/interactive-popover.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type InteractivePopoverProps = {
4646
className?: string;
4747
children: React.ReactNode;
4848
trigger: (triggerProps: {
49-
onClick: (event: React.MouseEvent | React.TouchEvent) => void;
49+
onClick: React.MouseEventHandler<HTMLButtonElement>;
5050
ref: React.LegacyRef<HTMLButtonElement>;
5151
children: React.ReactNode;
5252
}) => React.ReactElement;
@@ -185,7 +185,10 @@ function InteractivePopover({
185185
<IconButton
186186
className={cx(closeButtonStyles, closeButtonClassName)}
187187
data-testid="interactive-popover-close-button"
188-
onClick={onClose}
188+
onClick={(evt) => {
189+
evt.stopPropagation();
190+
onClose();
191+
}}
189192
aria-label="Close"
190193
id={closeButtonId}
191194
ref={closeButtonRef}

packages/compass-components/src/components/signal-popover.tsx

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type SignalPopoverProps = {
5151
};
5252

5353
const signalCardContentStyles = css({
54+
'--signalCardBackgroundColor': palette.white,
5455
width: '100%',
5556
display: 'grid',
5657
gridTemplateColumns: '1fr',
@@ -59,7 +60,11 @@ const signalCardContentStyles = css({
5960
paddingBottom: spacing[4],
6061
paddingLeft: spacing[4],
6162
paddingRight: spacing[4],
62-
backgroundColor: palette.white,
63+
backgroundColor: 'var(--signalCardBackgroundColor)',
64+
});
65+
66+
const signalCardContentDarkModeStyles = css({
67+
'--signalCardBackgroundColor': palette.gray.dark4,
6368
});
6469

6570
const signalCardTitleStyles = css({
@@ -84,7 +89,9 @@ const signalCardLearnMoreLinkStyles = css({
8489
flex: 'none',
8590
});
8691

87-
const SignalCard: React.FunctionComponent<Signal> = ({
92+
const SignalCard: React.FunctionComponent<
93+
Signal & Pick<SignalPopoverProps, 'darkMode'>
94+
> = ({
8895
id,
8996
title,
9097
description,
@@ -93,11 +100,17 @@ const SignalCard: React.FunctionComponent<Signal> = ({
93100
primaryActionButtonLabel,
94101
primaryActionButtonIcon,
95102
primaryActionButtonVariant,
103+
darkMode: _darkMode,
96104
onPrimaryActionButtonClick,
97105
}) => {
106+
const darkMode = useDarkMode(_darkMode);
107+
98108
return (
99109
<div
100-
className={signalCardContentStyles}
110+
className={cx(
111+
signalCardContentStyles,
112+
darkMode && signalCardContentDarkModeStyles
113+
)}
101114
data-testid="insight-signal-card"
102115
data-signal-id={id}
103116
>
@@ -147,13 +160,25 @@ const multiSignalHeaderContainerStyles = css({
147160
fontVariantNumeric: 'tabular-nums',
148161
});
149162

163+
const multiSignalHeaderContainerDarkModeStyles = css({
164+
'--multiSignalHeaderBorderColor': palette.gray.dark2,
165+
'--multiSignalHeaderBackgroundColor': palette.gray.dark3,
166+
});
167+
150168
const MultiSignalHeader: React.FunctionComponent<{
151169
currentIndex: number;
152170
total: number;
153171
onIndexChange(newVal: number): void;
154-
}> = ({ currentIndex, total, onIndexChange }) => {
172+
darkMode?: boolean;
173+
}> = ({ currentIndex, total, onIndexChange, darkMode: _darkMode }) => {
174+
const darkMode = useDarkMode(_darkMode);
155175
return (
156-
<div className={multiSignalHeaderContainerStyles}>
176+
<div
177+
className={cx(
178+
multiSignalHeaderContainerStyles,
179+
darkMode && multiSignalHeaderContainerDarkModeStyles
180+
)}
181+
>
157182
<IconButton
158183
data-testid="insight-signal-show-prev-button"
159184
aria-label="Show previous insight"
@@ -200,7 +225,8 @@ const popoverContentContainerStyles = css({
200225
});
201226

202227
const transitionStyles = css({
203-
transitionProperty: 'opacity, width, border-radius',
228+
transitionProperty:
229+
'opacity, width, border-radius, color, box-shadow, background-color',
204230
transitionTimingFunction: 'linear',
205231
transitionDuration: '0.15s',
206232
});
@@ -213,9 +239,6 @@ const badgeStyles = css(
213239
background: 'none',
214240
},
215241
{
216-
'--badgeBackgroundColor': palette.blue.light3,
217-
'--badgeBorderColor': palette.blue.light2,
218-
'--badgeColor': palette.blue.dark1,
219242
position: 'relative',
220243
display: 'inline-block',
221244
width: 18,
@@ -233,20 +256,35 @@ const badgeStyles = css(
233256
transitionStyles
234257
);
235258

236-
const badgeDarkModeStyles = css({
237-
// TODO: https://jira.mongodb.org/browse/COMPASS-6912
259+
const badgeLightModeStyles = css({
238260
'--badgeBackgroundColor': palette.blue.light3,
239261
'--badgeBorderColor': palette.blue.light2,
240262
'--badgeColor': palette.blue.dark1,
241263
});
242264

265+
const badgeDarkModeStyles = css({
266+
'--badgeBackgroundColor': palette.blue.dark2,
267+
'--badgeBorderColor': palette.blue.dark1,
268+
'--badgeColor': palette.blue.light2,
269+
});
270+
243271
const badgeHoveredStyles = css({
272+
borderRadius: 5,
273+
});
274+
275+
const badgeHoveredLightModeStyles = css({
244276
'--badgeBackgroundColor': palette.blue.light1,
245277
'--badgeBorderColor': palette.blue.dark1,
246278
'--badgeColor': palette.white,
247279
borderRadius: 5,
248280
});
249281

282+
const badgeHoveredDarkModeStyles = css({
283+
'--badgeBackgroundColor': palette.blue.dark1,
284+
'--badgeBorderColor': palette.blue.base,
285+
'--badgeColor': palette.blue.light3,
286+
});
287+
250288
const badgeIconStyles = css({});
251289

252290
const badgeIconCollapsedStyles = css(
@@ -366,18 +404,29 @@ const SignalPopover: React.FunctionComponent<SignalPopoverProps> = ({
366404
setOpen={onPopoverOpenChange}
367405
spacing={spacing[2]}
368406
trigger={({ children, ...triggerProps }) => {
407+
const onTriggerClick = (evt: React.MouseEvent<HTMLButtonElement>) => {
408+
evt.stopPropagation();
409+
triggerProps.onClick(evt);
410+
};
369411
const props = mergeProps<HTMLButtonElement>(hoverProps, triggerProps, {
370412
className: cx(
371413
badgeStyles,
372414
isActive && badgeHoveredStyles,
373-
darkMode && badgeDarkModeStyles
415+
...(darkMode
416+
? [badgeDarkModeStyles, isActive && badgeHoveredDarkModeStyles]
417+
: [badgeLightModeStyles, isActive && badgeHoveredLightModeStyles])
374418
),
375419
style: { width: isActive ? activeBadgeWidth : 18 },
376420
ref: triggerRef,
377421
});
378422
return (
379423
<>
380-
<button {...props} data-testid="insight-badge-button" type="button">
424+
<button
425+
{...props}
426+
onClick={onTriggerClick}
427+
data-testid="insight-badge-button"
428+
type="button"
429+
>
381430
<Icon
382431
glyph="Bulb"
383432
size="small"
@@ -406,9 +455,10 @@ const SignalPopover: React.FunctionComponent<SignalPopoverProps> = ({
406455
currentIndex={currentSignalIndex}
407456
total={signals.length}
408457
onIndexChange={setCurrentSignalIndex}
458+
darkMode={darkMode}
409459
></MultiSignalHeader>
410460
)}
411-
<SignalCard {...currentSignal}></SignalCard>
461+
<SignalCard {...currentSignal} darkMode={darkMode}></SignalCard>
412462
</InteractivePopover>
413463
);
414464
};

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

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
useFocusRing,
1111
mergeProps,
1212
useDefaultAction,
13+
SignalPopover,
1314
} from '@mongodb-js/compass-components';
1415
import { withPreferences } from 'compass-preferences-model';
1516

@@ -75,6 +76,7 @@ const itemWrapper = css({
7576
display: 'flex',
7677
alignItems: 'center',
7778
height: spacing[5],
79+
gap: spacing[2],
7880
zIndex: 1,
7981
});
8082

@@ -92,7 +94,11 @@ const itemButtonWrapper = css({
9294
alignItems: 'center',
9395
minWidth: 0,
9496
paddingLeft: spacing[3],
95-
paddingRight: spacing[1],
97+
});
98+
99+
const signalContainerStyles = css({
100+
flex: 'none',
101+
fontSize: 0,
96102
});
97103

98104
const navigationItemLabel = css({
@@ -112,6 +118,7 @@ export function NavigationItem<Actions extends string>({
112118
actions,
113119
tabName,
114120
isActive,
121+
showTooManyCollectionsInsight,
115122
}: {
116123
isExpanded?: boolean;
117124
onAction(actionName: string, ...rest: any[]): void;
@@ -120,6 +127,7 @@ export function NavigationItem<Actions extends string>({
120127
actions?: ItemAction<Actions>[];
121128
tabName: string;
122129
isActive: boolean;
130+
showTooManyCollectionsInsight?: boolean;
123131
}) {
124132
const onClick = useCallback(() => {
125133
onAction('open-instance-workspace', tabName);
@@ -148,6 +156,20 @@ export function NavigationItem<Actions extends string>({
148156
<Icon glyph={glyph} size="small"></Icon>
149157
{isExpanded && <span className={navigationItemLabel}>{label}</span>}
150158
</div>
159+
{isExpanded && showTooManyCollectionsInsight && (
160+
<div className={signalContainerStyles}>
161+
<SignalPopover
162+
signals={{
163+
id: 'too-many-collections',
164+
title: 'Databases with too many collections',
165+
description:
166+
"An excessive number of collections and their associated indexes can drain resources and impact your database's performance. In general, try to limit your replica set to 10,000 collections.",
167+
learnMoreLink:
168+
'https://www.mongodb.com/docs/v3.0/core/data-model-operations/#large-number-of-collections',
169+
}}
170+
></SignalPopover>
171+
</div>
172+
)}
151173
{isExpanded && actions && (
152174
<ItemActionControls<Actions>
153175
iconSize="small"
@@ -177,6 +199,7 @@ export function NavigationItems({
177199
onAction,
178200
currentLocation,
179201
readOnly,
202+
showTooManyCollectionsInsight = false,
180203
}: {
181204
isExpanded?: boolean;
182205
isDataLake?: boolean;
@@ -185,6 +208,7 @@ export function NavigationItems({
185208
onAction(actionName: string, ...rest: any[]): void;
186209
currentLocation: string | null;
187210
readOnly?: boolean;
211+
showTooManyCollectionsInsight?: boolean;
188212
}) {
189213
const isReadOnly = readOnly || isDataLake || !isWritable;
190214
const databasesActions = useMemo(() => {
@@ -225,6 +249,7 @@ export function NavigationItems({
225249
tabName="Databases"
226250
actions={databasesActions}
227251
isActive={currentLocation === 'Databases'}
252+
showTooManyCollectionsInsight={showTooManyCollectionsInsight}
228253
/>
229254
{isExpanded && (
230255
<DatabaseCollectionFilter changeFilterRegex={changeFilterRegex} />
@@ -234,19 +259,31 @@ export function NavigationItems({
234259
);
235260
}
236261

237-
const mapStateToProps = (state: {
238-
location: string | null;
239-
instance?: {
240-
dataLake: {
241-
isDataLake: boolean;
262+
const mapStateToProps =
263+
(state: // TODO(COMPASS-6914): Properly type stores instead of this
264+
{
265+
location: string | null;
266+
databases: any;
267+
instance?: {
268+
dataLake: {
269+
isDataLake: boolean;
270+
};
271+
isWritable: boolean;
272+
};
273+
}) => {
274+
const totalCollectionsCount = state.databases.databases.reduce(
275+
(acc: number, db: { collectionsLength: number }) => {
276+
return acc + db.collectionsLength;
277+
},
278+
0
279+
);
280+
return {
281+
currentLocation: state.location,
282+
isDataLake: state.instance?.dataLake.isDataLake,
283+
isWritable: state.instance?.isWritable,
284+
showTooManyCollectionsInsight: totalCollectionsCount > 10_000,
242285
};
243-
isWritable: boolean;
244286
};
245-
}) => ({
246-
currentLocation: state.location,
247-
isDataLake: state.instance?.dataLake.isDataLake,
248-
isWritable: state.instance?.isWritable,
249-
});
250287

251288
const MappedNavigationItems = connect(mapStateToProps, {
252289
changeFilterRegex,

packages/databases-collections-list/src/databases.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ const DatabasesList: React.FunctionComponent<{
7878
{
7979
label: 'Collections',
8080
value: compactNumber(db.collectionsLength),
81+
insights:
82+
db.collectionsLength >= 10_000
83+
? {
84+
id: 'too-many-collections',
85+
title: 'Databases with too many collections',
86+
description:
87+
"An excessive number of collections and their associated indexes can drain resources and impact your database's performance. In general, try to limit your replica set to 10,000 collections.",
88+
learnMoreLink:
89+
'https://www.mongodb.com/docs/v3.0/core/data-model-operations/#large-number-of-collections',
90+
}
91+
: undefined,
8192
},
8293
{
8394
label: 'Indexes',

packages/databases-collections-list/src/namespace-card.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {
2222
BadgeVariant,
2323
IconGlyph,
2424
ItemAction,
25+
SignalPopover,
2526
} from '@mongodb-js/compass-components';
2627
import { NamespaceParam } from './namespace-param';
2728
import type { ItemType } from './use-create';
@@ -156,6 +157,7 @@ export type DataProp = {
156157
label: React.ReactNode;
157158
value: React.ReactNode;
158159
hint?: React.ReactNode;
160+
insights?: React.ComponentProps<typeof SignalPopover>['signals'];
159161
};
160162

161163
export type NamespaceItemCardProps = {
@@ -281,7 +283,7 @@ export const NamespaceItemCard: React.FunctionComponent<
281283
{viewType === 'grid' && badgesGroup}
282284

283285
<div className={cx(namespaceDataGroup, viewType === 'grid' && column)}>
284-
{data.map(({ label, value, hint }, idx) => {
286+
{data.map(({ label, value, hint, insights }, idx) => {
285287
return (
286288
<NamespaceParam
287289
key={idx}
@@ -290,6 +292,7 @@ export const NamespaceItemCard: React.FunctionComponent<
290292
value={value}
291293
status={status}
292294
viewType={viewType}
295+
insights={insights}
293296
></NamespaceParam>
294297
);
295298
})}

0 commit comments

Comments
 (0)