Skip to content

Commit dbbfa12

Browse files
authored
feat(crud, schema, import-export): show insight for unbound array COMPASS-6836 (#4688)
* chore(compass-components): move all signals to one file * chore(compass-crud): show unbound array insight in document card * chore(compass-schema): show unbound array insight in schema field header * chore(compass-import-export): show unbound array insights in a toast during import * chore(import-export): update toast title
1 parent 70644a0 commit dbbfa12

File tree

25 files changed

+412
-220
lines changed

25 files changed

+412
-220
lines changed

packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
33
import {
44
Button,
55
MoreOptionsToggle,
6+
PerformanceSignals,
67
SignalPopover,
78
css,
89
spacing,
@@ -80,19 +81,7 @@ export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
8081
<div>
8182
<SignalPopover
8283
signals={{
83-
id: 'aggregation-executed-without-index',
84-
title: 'Aggregation executed without index',
85-
description: (
86-
<>
87-
This aggregation ran without an index. If you plan on using
88-
this query <strong>heavily</strong> in your application, you
89-
should create an index that covers this aggregation.
90-
</>
91-
),
92-
learnMoreLink:
93-
'https://www.mongodb.com/docs/v6.0/core/data-model-operations/#indexes',
94-
primaryActionButtonLabel: 'Create index',
95-
primaryActionButtonIcon: 'Plus',
84+
...PerformanceSignals.get('aggregation-executed-without-index'),
9685
onPrimaryActionButtonClick:
9786
onCollectionScanInsightActionButtonClick,
9887
}}
Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,21 @@
11
import { ADL, ATLAS } from '@mongodb-js/mongodb-constants';
2-
import type { Signal } from '@mongodb-js/compass-components';
2+
import {
3+
PerformanceSignals,
4+
type Signal,
5+
} from '@mongodb-js/compass-components';
36
import type { StoreStage } from '../modules/pipeline-builder/stage-editor';
47

5-
const ATLAS_SEARCH_LP_LINK = 'https://www.mongodb.com/cloud/atlas/lp/search-1';
6-
7-
const SIGNALS: Record<string, Signal> = {
8-
'atlas-text-regex-usage-in-stage': {
9-
id: 'atlas-text-regex-usage-in-stage',
10-
title: 'Alternate text search options available',
11-
description:
12-
"In many cases, Atlas Search is MongoDB's most efficient full text search option. Convert your query to $search for a wider range of functionality.",
13-
learnMoreLink:
14-
'https://www.mongodb.com/docs/atlas/atlas-search/best-practices/',
15-
primaryActionButtonLink: ATLAS_SEARCH_LP_LINK,
16-
primaryActionButtonLabel: 'Try Atlas Search',
17-
},
18-
'non-atlas-text-regex-usage-in-stage': {
19-
id: 'non-atlas-text-regex-usage-in-stage',
20-
title: 'Alternate text search options available',
21-
description:
22-
"In many cases, Atlas Search is MongoDB's most efficient full text search option. Connect with Atlas to explore the power of Atlas Search.",
23-
learnMoreLink:
24-
'https://www.mongodb.com/docs/atlas/atlas-search/best-practices/',
25-
primaryActionButtonLink: ATLAS_SEARCH_LP_LINK,
26-
primaryActionButtonLabel: 'Try Atlas Search',
27-
},
28-
'lookup-in-stage': {
29-
id: 'lookup-in-stage',
30-
title: '$lookup usage',
31-
description:
32-
'$lookup operations can be resource-intensive because they perform operations on two collections instead of one. In certain situations, embedding documents or arrays can enhance read performance.',
33-
learnMoreLink:
34-
'https://www.mongodb.com/docs/atlas/schema-suggestions/reduce-lookup-operations/#std-label-anti-pattern-denormalization',
35-
},
36-
} as const;
37-
388
export const getInsightForStage = (
399
{ stageOperator, value }: StoreStage,
4010
env: string
4111
): Signal | undefined => {
4212
const isAtlas = [ATLAS, ADL].includes(env);
4313
if (stageOperator === '$match' && /\$(text|regex)\b/.test(value ?? '')) {
4414
return isAtlas
45-
? SIGNALS['atlas-text-regex-usage-in-stage']
46-
: SIGNALS['non-atlas-text-regex-usage-in-stage'];
15+
? PerformanceSignals.get('atlas-text-regex-usage-in-stage')
16+
: PerformanceSignals.get('non-atlas-text-regex-usage-in-stage');
4717
}
4818
if (stageOperator === '$lookup') {
49-
return SIGNALS['lookup-in-stage'];
19+
return PerformanceSignals.get('lookup-in-stage');
5020
}
5121
};

packages/compass-collection/src/components/collection-header/collection-header.tsx

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
H3,
99
cx,
1010
SignalPopover,
11+
PerformanceSignals,
1112
} from '@mongodb-js/compass-components';
1213
import type { Signal } from '@mongodb-js/compass-components';
1314
import type { Document } from 'mongodb';
@@ -122,57 +123,22 @@ type CollectionHeaderProps = {
122123
stats: CollectionStatsObject;
123124
};
124125

125-
const ATLAS_SEARCH_LP_LINK = 'https://www.mongodb.com/cloud/atlas/lp/search-1';
126-
127-
const INSIGHTS = {
128-
'atlas-text-regex-usage-in-view': {
129-
id: 'atlas-text-regex-usage-in-view',
130-
title: 'Alternate text search options available',
131-
description:
132-
"In many cases, Atlas Search is MongoDB's most efficient full text search option. Convert your view’s query to $search for a wider range of functionality.",
133-
learnMoreLink:
134-
'https://www.mongodb.com/docs/atlas/atlas-search/best-practices/',
135-
primaryActionButtonLink: ATLAS_SEARCH_LP_LINK,
136-
primaryActionButtonLabel: 'Try Atlas Search',
137-
},
138-
'non-atlas-text-regex-usage-in-view': {
139-
id: 'non-atlas-text-regex-usage-in-view',
140-
title: 'Alternate text search options available',
141-
description:
142-
"In many cases, Atlas Search is MongoDB's most efficient full text search option. Connect with Atlas to explore the power of Atlas Search.",
143-
learnMoreLink:
144-
'https://www.mongodb.com/docs/atlas/atlas-search/best-practices/',
145-
primaryActionButtonLink: ATLAS_SEARCH_LP_LINK,
146-
primaryActionButtonLabel: 'Try Atlas Search',
147-
},
148-
'lookup-in-view': {
149-
id: 'lookup-in-view',
150-
title: '$lookup usage',
151-
description:
152-
'$lookup operations can be resource-intensive because they perform operations on two collections instead of one. In certain situations, embedding documents or arrays can enhance read performance.',
153-
learnMoreLink:
154-
'https://www.mongodb.com/docs/atlas/schema-suggestions/reduce-lookup-operations/#std-label-anti-pattern-denormalization',
155-
},
156-
} as const;
157-
158126
const getInsightsForPipeline = (pipeline: Document[], isAtlas: boolean) => {
159127
const insights = new Set<Signal>();
160128
for (const stage of pipeline) {
161129
if ('$match' in stage) {
162130
const stringifiedStageValue = JSON.stringify(stage);
163131
if (/\$(text|regex)\b/.test(stringifiedStageValue)) {
164132
insights.add(
165-
INSIGHTS[
166-
isAtlas
167-
? 'atlas-text-regex-usage-in-view'
168-
: 'non-atlas-text-regex-usage-in-view'
169-
]
133+
isAtlas
134+
? PerformanceSignals.get('atlas-text-regex-usage-in-view')
135+
: PerformanceSignals.get('non-atlas-text-regex-usage-in-view')
170136
);
171137
}
172138
}
173139

174140
if ('$lookup' in stage) {
175-
insights.add(INSIGHTS['lookup-in-view']);
141+
insights.add(PerformanceSignals.get('lookup-in-view'));
176142
}
177143
}
178144

packages/compass-components/src/components/document-list/document-actions-group.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ const actionsGroupItemSeparator = css({
2929
});
3030

3131
const actionsGroupIdle = css({
32-
'& > button': {
32+
'& > [data-action-item]': {
3333
display: 'none',
3434
},
3535
});
3636

3737
const actionsGroupHovered = css({
38-
'& > button': {
38+
'& > [data-action-item]': {
3939
display: 'block',
4040
},
4141
});
@@ -135,11 +135,15 @@ const DocumentActionsGroup: React.FunctionComponent<
135135
data-testid="expand-document-button"
136136
onClick={onExpand}
137137
className={actionsGroupItem}
138+
data-action-item
138139
></Button>
139140
)}
140141
<span className={actionsGroupItemSeparator}></span>
141142
{insights && (
142-
<div className={cx(actionsGroupItem, actionsGroupSignalPopover)}>
143+
<div
144+
className={cx(actionsGroupItem, actionsGroupSignalPopover)}
145+
data-action-item
146+
>
143147
<SignalPopover
144148
signals={insights}
145149
onPopoverOpenChange={setSignalOpened}
@@ -155,14 +159,15 @@ const DocumentActionsGroup: React.FunctionComponent<
155159
data-testid="edit-document-button"
156160
onClick={onEdit}
157161
className={actionsGroupItem}
162+
data-action-item
158163
></Button>
159164
)}
160165
{onCopy && (
161166
<Tooltip
162167
open={showCopyButtonTooltip}
163168
trigger={({ children }) => {
164169
return (
165-
<div>
170+
<div data-action-item>
166171
<Button
167172
size="xsmall"
168173
rightGlyph={<Icon role="presentation" glyph="Copy"></Icon>}
@@ -193,6 +198,7 @@ const DocumentActionsGroup: React.FunctionComponent<
193198
data-testid="clone-document-button"
194199
onClick={onClone}
195200
className={actionsGroupItem}
201+
data-action-item
196202
></Button>
197203
)}
198204
{onRemove && (
@@ -204,6 +210,7 @@ const DocumentActionsGroup: React.FunctionComponent<
204210
data-testid="remove-document-button"
205211
onClick={onRemove}
206212
className={actionsGroupItem}
213+
data-action-item
207214
></Button>
208215
)}
209216
</div>
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import React from 'react';
2+
import type { Signal } from './signal-popover';
3+
4+
const SIGNALS = [
5+
{
6+
id: 'aggregation-executed-without-index',
7+
title: 'Aggregation executed without index',
8+
description: (
9+
<>
10+
This aggregation ran without an index. If you plan on using this query{' '}
11+
<strong>heavily</strong> in your application, you should create an index
12+
that covers this aggregation.
13+
</>
14+
),
15+
learnMoreLink:
16+
'https://www.mongodb.com/docs/v6.0/core/data-model-operations/#indexes',
17+
primaryActionButtonLabel: 'Create index',
18+
primaryActionButtonIcon: 'Plus',
19+
},
20+
{
21+
id: 'atlas-text-regex-usage-in-view',
22+
title: 'Alternate text search options available',
23+
description:
24+
"In many cases, Atlas Search is MongoDB's most efficient full text search option. Convert your view’s query to $search for a wider range of functionality.",
25+
learnMoreLink:
26+
'https://www.mongodb.com/docs/atlas/atlas-search/best-practices/',
27+
primaryActionButtonLink: 'https://www.mongodb.com/cloud/atlas/lp/search-1',
28+
primaryActionButtonLabel: 'Try Atlas Search',
29+
},
30+
{
31+
id: 'non-atlas-text-regex-usage-in-view',
32+
title: 'Alternate text search options available',
33+
description:
34+
"In many cases, Atlas Search is MongoDB's most efficient full text search option. Connect with Atlas to explore the power of Atlas Search.",
35+
learnMoreLink:
36+
'https://www.mongodb.com/docs/atlas/atlas-search/best-practices/',
37+
primaryActionButtonLink: 'https://www.mongodb.com/cloud/atlas/lp/search-1',
38+
primaryActionButtonLabel: 'Try Atlas Search',
39+
},
40+
{
41+
id: 'lookup-in-view',
42+
title: '$lookup usage',
43+
description:
44+
'$lookup operations can be resource-intensive because they perform operations on two collections instead of one. In certain situations, embedding documents or arrays can enhance read performance.',
45+
learnMoreLink:
46+
'https://www.mongodb.com/docs/atlas/schema-suggestions/reduce-lookup-operations/#std-label-anti-pattern-denormalization',
47+
},
48+
{
49+
id: 'atlas-text-regex-usage-in-stage',
50+
title: 'Alternate text search options available',
51+
description:
52+
"In many cases, Atlas Search is MongoDB's most efficient full text search option. Convert your query to $search for a wider range of functionality.",
53+
learnMoreLink:
54+
'https://www.mongodb.com/docs/atlas/atlas-search/best-practices/',
55+
primaryActionButtonLink: 'https://www.mongodb.com/cloud/atlas/lp/search-1',
56+
primaryActionButtonLabel: 'Try Atlas Search',
57+
},
58+
{
59+
id: 'non-atlas-text-regex-usage-in-stage',
60+
title: 'Alternate text search options available',
61+
description:
62+
"In many cases, Atlas Search is MongoDB's most efficient full text search option. Connect with Atlas to explore the power of Atlas Search.",
63+
learnMoreLink:
64+
'https://www.mongodb.com/docs/atlas/atlas-search/best-practices/',
65+
primaryActionButtonLink: 'https://www.mongodb.com/cloud/atlas/lp/search-1',
66+
primaryActionButtonLabel: 'Try Atlas Search',
67+
},
68+
{
69+
id: 'lookup-in-stage',
70+
title: '$lookup usage',
71+
description:
72+
'$lookup operations can be resource-intensive because they perform operations on two collections instead of one. In certain situations, embedding documents or arrays can enhance read performance.',
73+
learnMoreLink:
74+
'https://www.mongodb.com/docs/atlas/schema-suggestions/reduce-lookup-operations/#std-label-anti-pattern-denormalization',
75+
},
76+
{
77+
id: 'query-executed-without-index',
78+
title: 'Query executed without index',
79+
description: (
80+
<>
81+
This query ran without an index. If you plan on using this query{' '}
82+
<strong>heavily</strong> in your application, you should create an index
83+
that covers this query.
84+
</>
85+
),
86+
learnMoreLink:
87+
'https://www.mongodb.com/docs/v6.0/core/data-model-operations/#indexes',
88+
primaryActionButtonLabel: 'Create index',
89+
primaryActionButtonIcon: 'Plus',
90+
},
91+
{
92+
id: 'bloated-document',
93+
title: 'Possibly bloated document',
94+
description:
95+
'Large documents can slow down queries by decreasing the number of documents that can be stored in RAM. Consider breaking up your data into more collections with smaller documents, and using references to consolidate the data you need.',
96+
learnMoreLink:
97+
'https://www.mongodb.com/docs/atlas/schema-suggestions/reduce-document-size/',
98+
},
99+
{
100+
id: 'explain-plan-without-index',
101+
title: 'Query executed without index',
102+
description: (
103+
<>
104+
This query ran without an index. If you plan on using this query{' '}
105+
<strong>heavily</strong> in your application, you should create an index
106+
that covers this aggregation.
107+
</>
108+
),
109+
learnMoreLink:
110+
'https://www.mongodb.com/docs/v6.0/core/data-model-operations/#indexes',
111+
primaryActionButtonLabel: 'Create index',
112+
primaryActionButtonIcon: 'Plus',
113+
},
114+
{
115+
id: 'too-many-indexes',
116+
title: 'High number of indexes on collection',
117+
description:
118+
'Consider reviewing your indexes to remove any that are unnecessary. Learn more about this anti-pattern',
119+
learnMoreLink:
120+
'https://www.mongodb.com/docs/manual/core/data-model-operations/#indexes',
121+
},
122+
{
123+
id: 'too-many-collections',
124+
title: 'Databases with too many collections',
125+
description:
126+
"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.",
127+
learnMoreLink:
128+
'https://www.mongodb.com/docs/v6.0/core/data-model-operations/#large-number-of-collections',
129+
},
130+
{
131+
id: 'unbound-array',
132+
title: 'Large array detected',
133+
description:
134+
'As arrays get larger, queries and indexes on that array field become less efficient. Ensure your arrays are bounded to maintain optimal query performance.',
135+
learnMoreLink:
136+
'https://www.mongodb.com/docs/atlas/schema-suggestions/avoid-unbounded-arrays/',
137+
},
138+
] as const;
139+
140+
export const PerformanceSignals = new Map(
141+
SIGNALS.map((signal) => {
142+
return [signal.id, signal];
143+
})
144+
) as {
145+
get(
146+
key: typeof SIGNALS[number]['id']
147+
): Pick<Signal, keyof typeof SIGNALS[number]>;
148+
};

packages/compass-components/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,4 @@ export { ComboboxWithCustomOption } from './components/combobox-with-custom-opti
174174
export { usePersistedState } from './hooks/use-persisted-state';
175175
export { GuideCue, GuideCueProvider } from './components/guide-cue/guide-cue';
176176
export type { Cue, GroupCue } from './components/guide-cue/guide-cue';
177+
export { PerformanceSignals } from './components/signals';

0 commit comments

Comments
 (0)