11import React , { useCallback } from 'react' ;
2- import { withPreferences } from 'compass-preferences-model/provider' ;
2+ import {
3+ usePreference ,
4+ withPreferences ,
5+ } from 'compass-preferences-model/provider' ;
36import { connect } from 'react-redux' ;
47
58import {
@@ -16,40 +19,43 @@ import type { StoreStage } from '../../modules/pipeline-builder/stage-editor';
1619import { filterStageOperators , isSearchStage } from '../../utils/stage' ;
1720import { isAtlasOnly } from '../../utils/stage' ;
1821import type { ServerEnvironment } from '../../modules/env' ;
22+ import type { CollectionStats } from '../../modules/collection-stats' ;
23+ import semver from 'semver' ; //import from mongodb-js/constants
1924
2025const inputWidth = spacing [ 1400 ] * 3 ;
2126// width of options popover
2227const comboxboxOptionsWidth = spacing [ 1200 ] * 10 ;
2328// left position of options popover wrt input. this aligns it with the start of input
2429const 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
5257const 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
82152export 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 ) => {
0 commit comments