Skip to content

Commit 7d234d4

Browse files
check for incompatibility
1 parent c50ad1c commit 7d234d4

File tree

2 files changed

+124
-49
lines changed

2 files changed

+124
-49
lines changed

packages/compass-aggregations/src/components/stage-toolbar/stage-operator-select.tsx

Lines changed: 121 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React, { useCallback } from 'react';
2-
import { withPreferences } from 'compass-preferences-model/provider';
2+
import {
3+
usePreference,
4+
withPreferences,
5+
} from 'compass-preferences-model/provider';
36
import { connect } from 'react-redux';
47

58
import {
@@ -16,40 +19,43 @@ import type { StoreStage } from '../../modules/pipeline-builder/stage-editor';
1619
import { filterStageOperators, isSearchStage } from '../../utils/stage';
1720
import { isAtlasOnly } from '../../utils/stage';
1821
import type { ServerEnvironment } from '../../modules/env';
22+
import type { CollectionStats } from '../../modules/collection-stats';
23+
import semver from 'semver'; //import from mongodb-js/constants
1924

2025
const inputWidth = spacing[1400] * 3;
2126
// width of options popover
2227
const comboxboxOptionsWidth = spacing[1200] * 10;
2328
// left position of options popover wrt input. this aligns it with the start of input
2429
const comboboxOptionsLeft = (comboxboxOptionsWidth - inputWidth) / 2;
30+
const MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_DE = '8.0.0';
31+
const isVersionSearchCompatibleForViewsDataExplorer = (
32+
serverVersion: string
33+
) => {
34+
try {
35+
return semver.gte(
36+
serverVersion,
37+
MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_DE
38+
);
39+
} catch {
40+
return false;
41+
}
42+
};
2543

26-
const comboboxStyles = css({
27-
width: inputWidth,
28-
'> :popover-open': {
29-
width: comboxboxOptionsWidth,
30-
whiteSpace: 'normal',
31-
// -4px to count for the input focus outline.
32-
marginLeft: `${comboboxOptionsLeft - 4}px`,
33-
},
34-
});
35-
36-
type StageOperatorSelectProps = {
37-
onChange: (index: number, name: string | null, snippet?: string) => void;
38-
index: number;
39-
selectedStage: string | null;
40-
isDisabled: boolean;
41-
stages: {
42-
name: string;
43-
env: ServerEnvironment[];
44-
description: string;
45-
}[];
46-
serverVersion: string;
47-
isReadonlyView: boolean;
48-
pipeline: any;
49-
fetchEffectivePipeline: () => void;
44+
// START: ALL OF THESE WILL BE IMPORTED FROM mongodb-constants
45+
const MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_COMPASS = '8.1.0';
46+
const isVersionSearchCompatibleForViews = (serverVersion: string) => {
47+
try {
48+
return semver.gte(
49+
serverVersion,
50+
MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_COMPASS
51+
);
52+
} catch {
53+
return false;
54+
}
5055
};
5156

5257
const isPipelineSearchQueryable = (
58+
//import from mongodb-js/constants
5359
pipeline: Array<Record<string, any>>
5460
): boolean => {
5561
for (const stage of pipeline) {
@@ -77,6 +83,70 @@ const isPipelineSearchQueryable = (
7783

7884
return true;
7985
};
86+
// END: ALL OF THESE WILL BE IMPORTED FROM mongodb-constants
87+
88+
const comboboxStyles = css({
89+
width: inputWidth,
90+
'> :popover-open': {
91+
width: comboxboxOptionsWidth,
92+
whiteSpace: 'normal',
93+
// -4px to count for the input focus outline.
94+
marginLeft: `${comboboxOptionsLeft - 4}px`,
95+
},
96+
});
97+
98+
type StageOperatorSelectProps = {
99+
onChange: (index: number, name: string | null, snippet?: string) => void;
100+
index: number;
101+
selectedStage: string | null;
102+
isDisabled: boolean;
103+
stages: {
104+
name: string;
105+
env: ServerEnvironment[];
106+
description: string;
107+
}[];
108+
serverVersion: string;
109+
isReadonlyView: boolean;
110+
collectionStats: CollectionStats;
111+
};
112+
113+
type Stage = {
114+
name: string;
115+
env: ServerEnvironment[];
116+
description: string;
117+
};
118+
119+
export const getStageDescription = (
120+
stage: Stage,
121+
isReadonlyView: boolean,
122+
versionIncompatibleCompass: boolean,
123+
versionIncompatibleDE: boolean,
124+
isPipelineSearchQueryable: boolean
125+
) => {
126+
if (isReadonlyView && isSearchStage(stage.name)) {
127+
if (!isPipelineSearchQueryable) {
128+
return (
129+
`Atlas only. Only views containing $addFields, $set or $match stages with the $expr operator are compatible with search indexes.` +
130+
stage.description
131+
);
132+
}
133+
134+
const minViewCompatibilityVersion = versionIncompatibleCompass
135+
? MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_COMPASS
136+
: MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_DE;
137+
const minMajorMinorVersion = minViewCompatibilityVersion
138+
.split('.')
139+
.slice(0, 2)
140+
.join('.');
141+
if (versionIncompatibleCompass || versionIncompatibleDE) {
142+
return (
143+
`Atlas only. Requires MongoDB ${minMajorMinorVersion}+ to run on a view. ` +
144+
stage.description
145+
);
146+
}
147+
}
148+
return (isAtlasOnly(stage.env) ? 'Atlas only. ' : '') + stage.description;
149+
};
80150

81151
// exported for tests
82152
export const StageOperatorSelect = ({
@@ -86,6 +156,7 @@ export const StageOperatorSelect = ({
86156
isDisabled,
87157
serverVersion,
88158
isReadonlyView,
159+
collectionStats,
89160
stages,
90161
}: StageOperatorSelectProps) => {
91162
const onStageOperatorSelected = useCallback(
@@ -95,26 +166,21 @@ export const StageOperatorSelect = ({
95166
[onChange, index]
96167
);
97168

98-
const getStageDescription = (stage: {
99-
name: string;
100-
env: ServerEnvironment[];
101-
description: string;
102-
}) => {
103-
if (isSearchStage(stage.name)) {
104-
// check if server version>8.0 and isReadonlyView after editing stages file
105-
if (isPipelineSearchQueryable([])) {
106-
return (
107-
`Atlas only. Requires MongoDB 8.1+ to run on a view. ` +
108-
stage.description
109-
);
110-
}
111-
return (
112-
`Atlas only. Only views containing $addFields, $set or $match stages with the $expr operator are compatible with search indexes.` +
113-
stage.description
114-
);
115-
}
116-
return (isAtlasOnly(stage.env) ? 'Atlas only. ' : '') + stage.description;
117-
};
169+
const enableAtlasSearchIndexes = usePreference('enableAtlasSearchIndexes');
170+
const versionIncompatibleCompass =
171+
enableAtlasSearchIndexes &&
172+
!isVersionSearchCompatibleForViews(serverVersion);
173+
const versionIncompatibleDE =
174+
!enableAtlasSearchIndexes &&
175+
!isVersionSearchCompatibleForViewsDataExplorer(serverVersion);
176+
const pipelineIsSearchQueryable = collectionStats?.pipeline
177+
? isPipelineSearchQueryable(collectionStats.pipeline as Document[])
178+
: true;
179+
const disableSearchStage =
180+
isReadonlyView &&
181+
(!pipelineIsSearchQueryable ||
182+
versionIncompatibleCompass ||
183+
versionIncompatibleDE);
118184

119185
return (
120186
<Combobox
@@ -127,13 +193,19 @@ export const StageOperatorSelect = ({
127193
data-testid="stage-operator-combobox"
128194
className={comboboxStyles}
129195
>
130-
{stages.map((stage, index) => (
196+
{stages.map((stage: Stage, index) => (
131197
<ComboboxOption
132198
data-testid={`combobox-option-stage-${stage.name}`}
133199
key={`combobox-option-stage-${index}`}
134200
value={stage.name}
135-
disabled={isSearchStage(stage.name)}
136-
description={getStageDescription(stage)}
201+
disabled={isSearchStage(stage.name) && disableSearchStage}
202+
description={getStageDescription(
203+
stage,
204+
isReadonlyView,
205+
versionIncompatibleCompass,
206+
versionIncompatibleDE,
207+
pipelineIsSearchQueryable
208+
)}
137209
/>
138210
))}
139211
</Combobox>
@@ -171,6 +243,7 @@ export default withPreferences(
171243
stages: stages,
172244
serverVersion: state.serverVersion,
173245
isReadonlyView: !!state.sourceName,
246+
collectionStats: state.collectionStats,
174247
};
175248
},
176249
(dispatch: any, ownProps) => {

packages/compass-aggregations/src/modules/collection-stats.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ import { isAction } from '../utils/is-action';
44

55
export type CollectionStats = {
66
document_count?: number;
7+
pipeline?: unknown[];
78
};
89

910
export const INITIAL_STATE: CollectionStats = {
1011
document_count: undefined,
1112
};
1213

1314
export function pickCollectionStats(collection: Collection): CollectionStats {
14-
const { document_count } = collection.toJSON();
15+
const { document_count, pipeline } = collection.toJSON();
1516
return {
1617
document_count,
18+
pipeline,
1719
};
1820
}
1921

0 commit comments

Comments
 (0)