From 46836b1922b179ed18cdaaea5c408399477782a6 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 25 Sep 2025 15:51:30 -0400 Subject: [PATCH 1/7] COMPASS-9023(compass-query-bar): add a maxTimeMS over 5min warning to CompassWeb --- .../pipeline-options/pipeline-collation.tsx | 102 +++++++++++++----- .../src/preferences-schema.tsx | 12 +++ .../src/components/query-option.tsx | 99 ++++++++++++----- .../src/constants/query-option-definition.ts | 26 ++++- .../src/stores/query-bar-reducer.ts | 9 +- packages/compass-query-bar/src/utils/query.ts | 31 ++++-- packages/compass-web/src/entrypoint.tsx | 5 +- 7 files changed, 222 insertions(+), 62 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx index e70f37ab740..588cbb030fc 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { connect } from 'react-redux'; import type { ConnectedProps } from 'react-redux'; import { @@ -8,6 +8,7 @@ import { spacing, TextInput, palette, + Tooltip, } from '@mongodb-js/compass-components'; import type { RootState } from '../../../modules'; @@ -51,6 +52,9 @@ const collationInputId = 'aggregations-collation-toolbar-input'; const maxTimeMSLabelId = 'aggregations-max-time-ms-toolbar-input-label'; const maxTimeMSInputId = 'aggregations-max-time-ms-toolbar-input'; +// Data Explorer limits (5 minutes = 300,000ms) +const WEB_MAX_TIME_MS_LIMIT = 300_000; // 5 minutes + const PipelineCollation: React.FunctionComponent = ({ collationValue, collationHasError, @@ -58,16 +62,48 @@ const PipelineCollation: React.FunctionComponent = ({ maxTimeMSValue, maxTimeMSChanged, }) => { + const showMaxTimeMSWarning = Boolean(usePreference('showMaxTimeMSWarning')); + const onMaxTimeMSChanged = useCallback( (evt: React.ChangeEvent) => { if (maxTimeMSChanged) { - maxTimeMSChanged(parseInt(evt.currentTarget.value, 10)); + const parsed = Number(evt.currentTarget.value); + const newValue = Number.isNaN(parsed) ? 0 : parsed; + + // When warning is enabled, enforce the hard limit + if (showMaxTimeMSWarning && newValue > WEB_MAX_TIME_MS_LIMIT) { + maxTimeMSChanged(WEB_MAX_TIME_MS_LIMIT); + } else { + maxTimeMSChanged(newValue); + } } }, - [maxTimeMSChanged] + [maxTimeMSChanged, showMaxTimeMSWarning] ); + const maxTimeMSLimit = usePreference('maxTimeMS'); + // Determine the effective max limit when warning is enabled + const effectiveMaxLimit = useMemo(() => { + if (showMaxTimeMSWarning) { + return maxTimeMSLimit + ? Math.min(maxTimeMSLimit, WEB_MAX_TIME_MS_LIMIT) + : WEB_MAX_TIME_MS_LIMIT; + } + return maxTimeMSLimit; + }, [showMaxTimeMSWarning, maxTimeMSLimit]); + + // Check if value exceeds the limit when warning is enabled + const exceedsLimit = Boolean( + useMemo(() => { + return ( + showMaxTimeMSWarning && + maxTimeMSValue && + maxTimeMSValue >= WEB_MAX_TIME_MS_LIMIT + ); + }, [showMaxTimeMSWarning, maxTimeMSValue]) + ); + return (
= ({ > Max Time MS - maxTimeMSLimit - ? 'error' - : 'none' - } - onChange={onMaxTimeMSChanged} - /> + ) => ( +
+ effectiveMaxLimit) || + exceedsLimit + ? 'error' + : 'none' + } + onChange={onMaxTimeMSChanged} + /> + {children} +
+ )} + > + Operations longer than 5 minutes are not supported in the web + environment +
); }; diff --git a/packages/compass-preferences-model/src/preferences-schema.tsx b/packages/compass-preferences-model/src/preferences-schema.tsx index 813edd6488b..41b9346a84f 100644 --- a/packages/compass-preferences-model/src/preferences-schema.tsx +++ b/packages/compass-preferences-model/src/preferences-schema.tsx @@ -105,6 +105,8 @@ export type UserConfigurablePreferences = PermanentFeatureFlags & enableProxySupport: boolean; proxy: string; inferNamespacesFromPrivileges?: boolean; + // Features that are enabled by default in Date Explorer, but are disabled in Compass + showMaxTimeMSWarning?: boolean; }; /** @@ -1062,6 +1064,16 @@ export const storedUserPreferencesProps: Required<{ validator: z.boolean().default(true), type: 'boolean', }, + showMaxTimeMSWarning: { + ui: true, + cli: true, + global: true, + description: { + short: 'Show Max Time MS over 5min Warning for Data Explorer', + }, + validator: z.boolean().default(false), + type: 'boolean', + }, ...allFeatureFlagsProps, }; diff --git a/packages/compass-query-bar/src/components/query-option.tsx b/packages/compass-query-bar/src/components/query-option.tsx index 026bfdfa3ad..c09cb6995f8 100644 --- a/packages/compass-query-bar/src/components/query-option.tsx +++ b/packages/compass-query-bar/src/components/query-option.tsx @@ -1,7 +1,8 @@ -import React, { useCallback, useRef } from 'react'; +import React, { useCallback, useRef, useMemo } from 'react'; import { Label, TextInput, + Tooltip, css, cx, spacing, @@ -20,6 +21,7 @@ import type { QueryProperty } from '../constants/query-properties'; import type { RootState } from '../stores/query-bar-store'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; import { useConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; +import { usePreference } from 'compass-preferences-model/provider'; const queryOptionStyles = css({ display: 'flex', @@ -79,6 +81,9 @@ export const documentEditorLabelContainerStyles = css( } ); +// Data Explorer limits for maxTimeMS (5 minutes = 300,000ms) +const WEB_MAX_TIME_MS_LIMIT = 300_000; // 5 minutes + type QueryBarProperty = Exclude; type QueryOptionProps = { @@ -153,6 +158,24 @@ const QueryOption: React.FunctionComponent = ({ } }, [track, name, connectionInfoRef]); + // MaxTimeMS warning tooltip logic + const showMaxTimeMSWarning = Boolean(usePreference('showMaxTimeMSWarning')); + const numericValue = useMemo(() => { + if (!value) return 0; + const parsed = Number(value); + return Number.isNaN(parsed) ? 0 : parsed; + }, [value]); + + const exceedsMaxTimeMSLimit = Boolean( + useMemo(() => { + return ( + name === 'maxTimeMS' && + showMaxTimeMSWarning && + numericValue >= WEB_MAX_TIME_MS_LIMIT + ); + }, [name, showMaxTimeMSWarning, numericValue]) + ); + return (
= ({ /> ) : ( - {({ props }) => ( - ) => - onValueChange(evt.currentTarget.value) - } - onBlur={onBlurEditor} - placeholder={placeholder as string} - disabled={disabled} - {...props} - /> - )} + {({ props }) => { + const textInput = ( + ) => + onValueChange(evt.currentTarget.value) + } + onBlur={onBlurEditor} + placeholder={placeholder as string} + disabled={disabled} + {...props} + /> + ); + + // Wrap maxTimeMS field with tooltip in web environment when exceeding limit + if (exceedsMaxTimeMSLimit) { + return ( + ) => ( +
+ {textInput} + {children} +
+ )} + > + Operations longer than 5 minutes are not supported in the + web environment +
+ ); + } + + return textInput; + }}
)}
diff --git a/packages/compass-query-bar/src/constants/query-option-definition.ts b/packages/compass-query-bar/src/constants/query-option-definition.ts index 76c9425ef3d..f3dba68cc9e 100644 --- a/packages/compass-query-bar/src/constants/query-option-definition.ts +++ b/packages/compass-query-bar/src/constants/query-option-definition.ts @@ -9,6 +9,9 @@ export type QueryOptionOfTypeDocument = Exclude< 'maxTimeMS' | 'limit' | 'skip' >; +// Data Explorer limits (5 minutes = 300,000ms) +const WEB_MAX_TIME_MS_LIMIT = 300_000; // 5 minutes + export const OPTION_DEFINITION: { [optionName in QueryOption]: { name: optionName; @@ -70,12 +73,27 @@ export const OPTION_DEFINITION: { link: 'https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS/', extraTextInputProps() { const preferenceMaxTimeMS = usePreference('maxTimeMS'); - const props: { max?: number; placeholder?: string } = { - max: preferenceMaxTimeMS, + const showMaxTimeMSWarning = + usePreference('showMaxTimeMSWarning') ?? false; + + // Determine the effective max limit when warning is enabled + const effectiveMaxLimit = showMaxTimeMSWarning + ? preferenceMaxTimeMS + ? Math.min(preferenceMaxTimeMS, WEB_MAX_TIME_MS_LIMIT) + : WEB_MAX_TIME_MS_LIMIT + : preferenceMaxTimeMS; + + const props: { + max?: number; + placeholder?: string; + } = { + max: effectiveMaxLimit, }; - if (preferenceMaxTimeMS !== undefined && preferenceMaxTimeMS < 60000) { - props.placeholder = String(preferenceMaxTimeMS); + + if (effectiveMaxLimit !== undefined && effectiveMaxLimit < 60000) { + props.placeholder = String(effectiveMaxLimit); } + return props; }, }, diff --git a/packages/compass-query-bar/src/stores/query-bar-reducer.ts b/packages/compass-query-bar/src/stores/query-bar-reducer.ts index acd80141231..53156f46462 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -103,6 +103,8 @@ export const changeField = ( return (dispatch, getState, { preferences }) => { const parsedValue = validateField(name, stringValue, { maxTimeMS: preferences.getPreferences().maxTimeMS ?? undefined, + showMaxTimeMSWarning: + Boolean(preferences.getPreferences().showMaxTimeMSWarning) ?? false, }); const isValid = parsedValue !== false; dispatch({ @@ -162,7 +164,12 @@ export const resetQuery = ( return false; } const fields = mapQueryToFormFields( - { maxTimeMS: preferences.getPreferences().maxTimeMS }, + { + maxTimeMS: preferences.getPreferences().maxTimeMS, + showMaxTimeMSWarning: Boolean( + preferences.getPreferences().showMaxTimeMSWarning + ), + }, DEFAULT_FIELD_VALUES ); dispatch({ type: QueryBarActions.ResetQuery, fields, source }); diff --git a/packages/compass-query-bar/src/utils/query.ts b/packages/compass-query-bar/src/utils/query.ts index 91694a6289d..3c6cdab6acc 100644 --- a/packages/compass-query-bar/src/utils/query.ts +++ b/packages/compass-query-bar/src/utils/query.ts @@ -15,6 +15,9 @@ import type { } from '../constants/query-properties'; import { QUERY_PROPERTIES } from '../constants/query-properties'; +// Data Explorer limits (5 minutes = 300,000ms) +const WEB_MAX_TIME_MS_LIMIT = 300_000; // 5 minutes + export function mapFormFieldsToQuery(fields: QueryFormFields): BaseQuery { // We always want filter field to be in the query, even if the field // is empty. Probably would be better to handle where the query is @@ -56,7 +59,7 @@ export function doesQueryHaveExtraOptionsSet(fields?: QueryFormFields) { export function parseQueryAttributesToFormFields( query: Record, - preferences: Pick + preferences: Pick ): QueryFormFields { return Object.fromEntries( Object.entries(query) @@ -81,7 +84,7 @@ export function parseQueryAttributesToFormFields( * Map query document to the query fields state only preserving valid values */ export function mapQueryToFormFields( - preferences: Pick, + preferences: Pick, query?: BaseQuery, onlyValid = true ): QueryFormFields { @@ -125,7 +128,10 @@ function isQueryProperty(field: string): field is QueryProperty { export function validateField( field: string, value: string, - { maxTimeMS: preferencesMaxTimeMS }: Pick + { + maxTimeMS: preferencesMaxTimeMS, + showMaxTimeMSWarning, + }: Pick ) { const validated = validate(field, value); if (field === 'filter' && validated === '') { @@ -137,13 +143,24 @@ export function validateField( } // Additional validation for maxTimeMS to make sure that we are not over the - // upper bound set in preferences + // upper bound set in preferences or Data Explorer limits if (field === 'maxTimeMS') { + const maxTimeMS = Number(value); + + // When warning is enabled, enforce hard limit + if ( + showMaxTimeMSWarning && + !Number.isNaN(maxTimeMS) && + maxTimeMS > WEB_MAX_TIME_MS_LIMIT + ) { + return false; + } + + // Standard preference validation if ( typeof preferencesMaxTimeMS !== 'undefined' && value && - Number(value) > - (preferencesMaxTimeMS ?? DEFAULT_FIELD_VALUES['maxTimeMS']) + maxTimeMS > (preferencesMaxTimeMS ?? DEFAULT_FIELD_VALUES['maxTimeMS']) ) { return false; } @@ -160,7 +177,7 @@ export function validateField( export function isQueryFieldsValid( fields: QueryFormFields, - preferences: Pick + preferences: Pick ) { return Object.entries(fields).every( ([key, value]) => validateField(key, value.string, preferences) !== false diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index bd862df26c9..79867e07a16 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -382,7 +382,10 @@ const CompassWeb = ({ onLog, onDebug, }); - const preferencesAccess = useCompassWebPreferences(initialPreferences); + const preferencesAccess = useCompassWebPreferences({ + ...initialPreferences, + showMaxTimeMSWarning: true, + }); // TODO (COMPASS-9565): My Queries feature flag will be used to conditionally provide storage providers const initialWorkspaceRef = useRef(initialWorkspace); const initialWorkspaceTabsRef = useRef( From fee7deefc0ec98f5294b1fd6f81980ab61910854 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 29 Sep 2025 16:18:19 +0200 Subject: [PATCH 2/7] consolidate --- .../src/constants/query-option-definition.ts | 8 ++++++-- packages/compass-query-bar/src/utils/query.ts | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/compass-query-bar/src/constants/query-option-definition.ts b/packages/compass-query-bar/src/constants/query-option-definition.ts index f3dba68cc9e..d8785127860 100644 --- a/packages/compass-query-bar/src/constants/query-option-definition.ts +++ b/packages/compass-query-bar/src/constants/query-option-definition.ts @@ -9,8 +9,12 @@ export type QueryOptionOfTypeDocument = Exclude< 'maxTimeMS' | 'limit' | 'skip' >; -// Data Explorer limits (5 minutes = 300,000ms) -const WEB_MAX_TIME_MS_LIMIT = 300_000; // 5 minutes +/** + * Data Explorer limits (5 minutes = 300,000ms) + * This limit is artificial but necessary for the backend that powers DE. + * https://github.com/10gen/mms/blob/dea184f4a40db0a64ed0d6665d36265f62ae4f65/server/src/main/com/xgen/cloud/services/clusterconnection/runtime/ws/ClusterConnectionServerProvider.java#L50-L51 + */ +export const WEB_MAX_TIME_MS_LIMIT = 300_000; export const OPTION_DEFINITION: { [optionName in QueryOption]: { diff --git a/packages/compass-query-bar/src/utils/query.ts b/packages/compass-query-bar/src/utils/query.ts index 3c6cdab6acc..a98a0a48dbd 100644 --- a/packages/compass-query-bar/src/utils/query.ts +++ b/packages/compass-query-bar/src/utils/query.ts @@ -14,9 +14,7 @@ import type { QueryProperty, } from '../constants/query-properties'; import { QUERY_PROPERTIES } from '../constants/query-properties'; - -// Data Explorer limits (5 minutes = 300,000ms) -const WEB_MAX_TIME_MS_LIMIT = 300_000; // 5 minutes +import { WEB_MAX_TIME_MS_LIMIT } from '../constants/query-option-definition'; export function mapFormFieldsToQuery(fields: QueryFormFields): BaseQuery { // We always want filter field to be in the query, even if the field From a6ae7365a6b0bc4882be9d20b578e1ad71fa7c4b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 29 Sep 2025 11:56:43 -0400 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../pipeline-options/pipeline-collation.tsx | 16 +++++++--------- .../src/components/query-option.tsx | 16 +++++++--------- .../src/stores/query-bar-reducer.ts | 7 +++---- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx index 588cbb030fc..d39f69a0dfa 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx @@ -94,15 +94,13 @@ const PipelineCollation: React.FunctionComponent = ({ }, [showMaxTimeMSWarning, maxTimeMSLimit]); // Check if value exceeds the limit when warning is enabled - const exceedsLimit = Boolean( - useMemo(() => { - return ( - showMaxTimeMSWarning && - maxTimeMSValue && - maxTimeMSValue >= WEB_MAX_TIME_MS_LIMIT - ); - }, [showMaxTimeMSWarning, maxTimeMSValue]) - ); + const exceedsLimit = useMemo(() => { + return ( + showMaxTimeMSWarning && + maxTimeMSValue && + maxTimeMSValue >= WEB_MAX_TIME_MS_LIMIT + ); + }, [showMaxTimeMSWarning, maxTimeMSValue]); return (
= ({ return Number.isNaN(parsed) ? 0 : parsed; }, [value]); - const exceedsMaxTimeMSLimit = Boolean( - useMemo(() => { - return ( - name === 'maxTimeMS' && - showMaxTimeMSWarning && - numericValue >= WEB_MAX_TIME_MS_LIMIT - ); - }, [name, showMaxTimeMSWarning, numericValue]) - ); + const exceedsMaxTimeMSLimit = useMemo(() => { + return ( + name === 'maxTimeMS' && + showMaxTimeMSWarning && + numericValue >= WEB_MAX_TIME_MS_LIMIT + ); + }, [name, showMaxTimeMSWarning, numericValue]); return (
Date: Mon, 29 Sep 2025 18:08:13 +0200 Subject: [PATCH 4/7] constants --- .../pipeline-options/pipeline-collation.tsx | 5 +---- packages/compass-aggregations/src/constants.ts | 7 +++++++ .../compass-query-bar/src/components/query-option.tsx | 8 ++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx index d39f69a0dfa..9b90b488324 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx @@ -14,7 +14,7 @@ import { import type { RootState } from '../../../modules'; import { collationStringChanged } from '../../../modules/collation-string'; import { maxTimeMSChanged } from '../../../modules/max-time-ms'; -import { DEFAULT_MAX_TIME_MS } from '../../../constants'; +import { DEFAULT_MAX_TIME_MS, WEB_MAX_TIME_MS_LIMIT } from '../../../constants'; import { usePreference } from 'compass-preferences-model/provider'; const pipelineOptionsContainerStyles = css({ @@ -52,9 +52,6 @@ const collationInputId = 'aggregations-collation-toolbar-input'; const maxTimeMSLabelId = 'aggregations-max-time-ms-toolbar-input-label'; const maxTimeMSInputId = 'aggregations-max-time-ms-toolbar-input'; -// Data Explorer limits (5 minutes = 300,000ms) -const WEB_MAX_TIME_MS_LIMIT = 300_000; // 5 minutes - const PipelineCollation: React.FunctionComponent = ({ collationValue, collationHasError, diff --git a/packages/compass-aggregations/src/constants.ts b/packages/compass-aggregations/src/constants.ts index 0fe05957340..bedcd35cbe0 100644 --- a/packages/compass-aggregations/src/constants.ts +++ b/packages/compass-aggregations/src/constants.ts @@ -1,3 +1,10 @@ +/** + * Data Explorer limits (5 minutes = 300,000ms) + * This limit is artificial but necessary for the backend that powers DE. + * https://github.com/10gen/mms/blob/dea184f4a40db0a64ed0d6665d36265f62ae4f65/server/src/main/com/xgen/cloud/services/clusterconnection/runtime/ws/ClusterConnectionServerProvider.java#L50-L51 + */ +export const WEB_MAX_TIME_MS_LIMIT = 300_000; + /** * Default for maxTimeMS option. */ diff --git a/packages/compass-query-bar/src/components/query-option.tsx b/packages/compass-query-bar/src/components/query-option.tsx index 22de75dc306..f5d6347703f 100644 --- a/packages/compass-query-bar/src/components/query-option.tsx +++ b/packages/compass-query-bar/src/components/query-option.tsx @@ -11,7 +11,10 @@ import { } from '@mongodb-js/compass-components'; import { connect } from '../stores/context'; import OptionEditor from './option-editor'; -import { OPTION_DEFINITION } from '../constants/query-option-definition'; +import { + OPTION_DEFINITION, + WEB_MAX_TIME_MS_LIMIT, +} from '../constants/query-option-definition'; import type { QueryOptionOfTypeDocument, QueryOption as QueryOptionType, @@ -81,9 +84,6 @@ export const documentEditorLabelContainerStyles = css( } ); -// Data Explorer limits for maxTimeMS (5 minutes = 300,000ms) -const WEB_MAX_TIME_MS_LIMIT = 300_000; // 5 minutes - type QueryBarProperty = Exclude; type QueryOptionProps = { From cea25b5a21203e9143e22f867ce81a9a2aeb2b1b Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 1 Oct 2025 01:58:24 +0200 Subject: [PATCH 5/7] fix --- .../pipeline-options/pipeline-collation.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx index 9b90b488324..b629edab72f 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx @@ -91,13 +91,15 @@ const PipelineCollation: React.FunctionComponent = ({ }, [showMaxTimeMSWarning, maxTimeMSLimit]); // Check if value exceeds the limit when warning is enabled - const exceedsLimit = useMemo(() => { - return ( - showMaxTimeMSWarning && - maxTimeMSValue && - maxTimeMSValue >= WEB_MAX_TIME_MS_LIMIT - ); - }, [showMaxTimeMSWarning, maxTimeMSValue]); + const exceedsLimit = Boolean( + useMemo(() => { + return ( + showMaxTimeMSWarning && + maxTimeMSValue && + maxTimeMSValue >= WEB_MAX_TIME_MS_LIMIT + ); + }, [showMaxTimeMSWarning, maxTimeMSValue]) + ); return (
Date: Wed, 1 Oct 2025 02:54:52 +0200 Subject: [PATCH 6/7] fmt --- packages/compass-query-bar/src/stores/query-bar-reducer.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/compass-query-bar/src/stores/query-bar-reducer.ts b/packages/compass-query-bar/src/stores/query-bar-reducer.ts index 5305c1f7d53..f0a074cb06b 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -103,8 +103,9 @@ export const changeField = ( return (dispatch, getState, { preferences }) => { const parsedValue = validateField(name, stringValue, { maxTimeMS: preferences.getPreferences().maxTimeMS ?? undefined, - showMaxTimeMSWarning: - Boolean(preferences.getPreferences().showMaxTimeMSWarning), + showMaxTimeMSWarning: Boolean( + preferences.getPreferences().showMaxTimeMSWarning + ), }); const isValid = parsedValue !== false; dispatch({ From 29f02c30db4230fe3f5ff9a659442c8b827e055f Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Sun, 5 Oct 2025 10:21:35 -0400 Subject: [PATCH 7/7] change to use limit as pref --- .../pipeline-options/pipeline-collation.tsx | 30 +++++++++---------- .../compass-aggregations/src/constants.ts | 7 ----- .../src/preferences-schema.tsx | 11 +++---- .../src/components/query-option.tsx | 13 ++++---- .../src/constants/query-option-definition.ts | 22 +++++--------- .../src/stores/query-bar-reducer.ts | 30 ++++++------------- packages/compass-query-bar/src/utils/query.ts | 19 ++++++------ packages/compass-web/src/entrypoint.tsx | 2 +- 8 files changed, 52 insertions(+), 82 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx index b629edab72f..f568f484ee8 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-options/pipeline-collation.tsx @@ -14,7 +14,7 @@ import { import type { RootState } from '../../../modules'; import { collationStringChanged } from '../../../modules/collation-string'; import { maxTimeMSChanged } from '../../../modules/max-time-ms'; -import { DEFAULT_MAX_TIME_MS, WEB_MAX_TIME_MS_LIMIT } from '../../../constants'; +import { DEFAULT_MAX_TIME_MS } from '../../../constants'; import { usePreference } from 'compass-preferences-model/provider'; const pipelineOptionsContainerStyles = css({ @@ -59,7 +59,7 @@ const PipelineCollation: React.FunctionComponent = ({ maxTimeMSValue, maxTimeMSChanged, }) => { - const showMaxTimeMSWarning = Boolean(usePreference('showMaxTimeMSWarning')); + const maxTimeMSEnvLimit = usePreference('maxTimeMSEnvLimit'); const onMaxTimeMSChanged = useCallback( (evt: React.ChangeEvent) => { @@ -67,38 +67,38 @@ const PipelineCollation: React.FunctionComponent = ({ const parsed = Number(evt.currentTarget.value); const newValue = Number.isNaN(parsed) ? 0 : parsed; - // When warning is enabled, enforce the hard limit - if (showMaxTimeMSWarning && newValue > WEB_MAX_TIME_MS_LIMIT) { - maxTimeMSChanged(WEB_MAX_TIME_MS_LIMIT); + // When environment limit is set (> 0), enforce it + if (maxTimeMSEnvLimit && newValue > maxTimeMSEnvLimit) { + maxTimeMSChanged(maxTimeMSEnvLimit); } else { maxTimeMSChanged(newValue); } } }, - [maxTimeMSChanged, showMaxTimeMSWarning] + [maxTimeMSChanged, maxTimeMSEnvLimit] ); const maxTimeMSLimit = usePreference('maxTimeMS'); - // Determine the effective max limit when warning is enabled + // Determine the effective max limit when environment limit is set (> 0) const effectiveMaxLimit = useMemo(() => { - if (showMaxTimeMSWarning) { + if (maxTimeMSEnvLimit) { return maxTimeMSLimit - ? Math.min(maxTimeMSLimit, WEB_MAX_TIME_MS_LIMIT) - : WEB_MAX_TIME_MS_LIMIT; + ? Math.min(maxTimeMSLimit, maxTimeMSEnvLimit) + : maxTimeMSEnvLimit; } return maxTimeMSLimit; - }, [showMaxTimeMSWarning, maxTimeMSLimit]); + }, [maxTimeMSEnvLimit, maxTimeMSLimit]); - // Check if value exceeds the limit when warning is enabled + // Check if value exceeds the environment limit (when limit > 0) const exceedsLimit = Boolean( useMemo(() => { return ( - showMaxTimeMSWarning && + maxTimeMSEnvLimit && maxTimeMSValue && - maxTimeMSValue >= WEB_MAX_TIME_MS_LIMIT + maxTimeMSValue >= maxTimeMSEnvLimit ); - }, [showMaxTimeMSWarning, maxTimeMSValue]) + }, [maxTimeMSEnvLimit, maxTimeMSValue]) ); return ( diff --git a/packages/compass-aggregations/src/constants.ts b/packages/compass-aggregations/src/constants.ts index bedcd35cbe0..0fe05957340 100644 --- a/packages/compass-aggregations/src/constants.ts +++ b/packages/compass-aggregations/src/constants.ts @@ -1,10 +1,3 @@ -/** - * Data Explorer limits (5 minutes = 300,000ms) - * This limit is artificial but necessary for the backend that powers DE. - * https://github.com/10gen/mms/blob/dea184f4a40db0a64ed0d6665d36265f62ae4f65/server/src/main/com/xgen/cloud/services/clusterconnection/runtime/ws/ClusterConnectionServerProvider.java#L50-L51 - */ -export const WEB_MAX_TIME_MS_LIMIT = 300_000; - /** * Default for maxTimeMS option. */ diff --git a/packages/compass-preferences-model/src/preferences-schema.tsx b/packages/compass-preferences-model/src/preferences-schema.tsx index 41b9346a84f..799674538a6 100644 --- a/packages/compass-preferences-model/src/preferences-schema.tsx +++ b/packages/compass-preferences-model/src/preferences-schema.tsx @@ -106,7 +106,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags & proxy: string; inferNamespacesFromPrivileges?: boolean; // Features that are enabled by default in Date Explorer, but are disabled in Compass - showMaxTimeMSWarning?: boolean; + maxTimeMSEnvLimit?: number; }; /** @@ -1064,15 +1064,16 @@ export const storedUserPreferencesProps: Required<{ validator: z.boolean().default(true), type: 'boolean', }, - showMaxTimeMSWarning: { + maxTimeMSEnvLimit: { ui: true, cli: true, global: true, description: { - short: 'Show Max Time MS over 5min Warning for Data Explorer', + short: + 'Maximum time limit for operations in environment (milliseconds). Set to 0 for no limit.', }, - validator: z.boolean().default(false), - type: 'boolean', + validator: z.number().min(0).default(0), + type: 'number', }, ...allFeatureFlagsProps, diff --git a/packages/compass-query-bar/src/components/query-option.tsx b/packages/compass-query-bar/src/components/query-option.tsx index f5d6347703f..27492118a43 100644 --- a/packages/compass-query-bar/src/components/query-option.tsx +++ b/packages/compass-query-bar/src/components/query-option.tsx @@ -11,10 +11,7 @@ import { } from '@mongodb-js/compass-components'; import { connect } from '../stores/context'; import OptionEditor from './option-editor'; -import { - OPTION_DEFINITION, - WEB_MAX_TIME_MS_LIMIT, -} from '../constants/query-option-definition'; +import { OPTION_DEFINITION } from '../constants/query-option-definition'; import type { QueryOptionOfTypeDocument, QueryOption as QueryOptionType, @@ -159,7 +156,7 @@ const QueryOption: React.FunctionComponent = ({ }, [track, name, connectionInfoRef]); // MaxTimeMS warning tooltip logic - const showMaxTimeMSWarning = Boolean(usePreference('showMaxTimeMSWarning')); + const maxTimeMSEnvLimit = usePreference('maxTimeMSEnvLimit'); const numericValue = useMemo(() => { if (!value) return 0; const parsed = Number(value); @@ -169,10 +166,10 @@ const QueryOption: React.FunctionComponent = ({ const exceedsMaxTimeMSLimit = useMemo(() => { return ( name === 'maxTimeMS' && - showMaxTimeMSWarning && - numericValue >= WEB_MAX_TIME_MS_LIMIT + maxTimeMSEnvLimit && // 0 is falsy, so no limit when 0 + numericValue >= maxTimeMSEnvLimit ); - }, [name, showMaxTimeMSWarning, numericValue]); + }, [name, maxTimeMSEnvLimit, numericValue]); return (
; -/** - * Data Explorer limits (5 minutes = 300,000ms) - * This limit is artificial but necessary for the backend that powers DE. - * https://github.com/10gen/mms/blob/dea184f4a40db0a64ed0d6665d36265f62ae4f65/server/src/main/com/xgen/cloud/services/clusterconnection/runtime/ws/ClusterConnectionServerProvider.java#L50-L51 - */ -export const WEB_MAX_TIME_MS_LIMIT = 300_000; - export const OPTION_DEFINITION: { [optionName in QueryOption]: { name: optionName; @@ -77,14 +70,13 @@ export const OPTION_DEFINITION: { link: 'https://docs.mongodb.com/manual/reference/method/cursor.maxTimeMS/', extraTextInputProps() { const preferenceMaxTimeMS = usePreference('maxTimeMS'); - const showMaxTimeMSWarning = - usePreference('showMaxTimeMSWarning') ?? false; + const maxTimeMSEnvLimit = usePreference('maxTimeMSEnvLimit'); - // Determine the effective max limit when warning is enabled - const effectiveMaxLimit = showMaxTimeMSWarning + // Determine the effective max limit when environment limit is set (> 0) + const effectiveMaxLimit = maxTimeMSEnvLimit ? preferenceMaxTimeMS - ? Math.min(preferenceMaxTimeMS, WEB_MAX_TIME_MS_LIMIT) - : WEB_MAX_TIME_MS_LIMIT + ? Math.min(preferenceMaxTimeMS, maxTimeMSEnvLimit) + : maxTimeMSEnvLimit : preferenceMaxTimeMS; const props: { @@ -94,8 +86,8 @@ export const OPTION_DEFINITION: { max: effectiveMaxLimit, }; - if (effectiveMaxLimit !== undefined && effectiveMaxLimit < 60000) { - props.placeholder = String(effectiveMaxLimit); + if (effectiveMaxLimit && effectiveMaxLimit < 60000) { + props.placeholder = `${+effectiveMaxLimit}`; } return props; diff --git a/packages/compass-query-bar/src/stores/query-bar-reducer.ts b/packages/compass-query-bar/src/stores/query-bar-reducer.ts index f0a074cb06b..24691cf564f 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.ts @@ -52,7 +52,7 @@ type QueryBarState = { export const INITIAL_STATE: QueryBarState = { isReadonlyConnection: false, - fields: mapQueryToFormFields({}, DEFAULT_FIELD_VALUES), + fields: mapQueryToFormFields({ maxTimeMSEnvLimit: 0 }, DEFAULT_FIELD_VALUES), expanded: false, serverVersion: '3.6.0', lastAppliedQuery: { source: null, query: {} }, @@ -103,9 +103,7 @@ export const changeField = ( return (dispatch, getState, { preferences }) => { const parsedValue = validateField(name, stringValue, { maxTimeMS: preferences.getPreferences().maxTimeMS ?? undefined, - showMaxTimeMSWarning: Boolean( - preferences.getPreferences().showMaxTimeMSWarning - ), + maxTimeMSEnvLimit: preferences.getPreferences().maxTimeMSEnvLimit, }); const isValid = parsedValue !== false; dispatch({ @@ -165,11 +163,7 @@ export const resetQuery = ( return false; } const fields = mapQueryToFormFields( - { - maxTimeMS: preferences.getPreferences().maxTimeMS, - showMaxTimeMSWarning: - preferences.getPreferences().showMaxTimeMSWarning ?? false, - }, + preferences.getPreferences(), DEFAULT_FIELD_VALUES ); dispatch({ type: QueryBarActions.ResetQuery, fields, source }); @@ -186,10 +180,7 @@ export const setQuery = ( query: BaseQuery ): QueryBarThunkAction => { return (dispatch, getState, { preferences }) => { - const fields = mapQueryToFormFields( - { maxTimeMS: preferences.getPreferences().maxTimeMS }, - query - ); + const fields = mapQueryToFormFields(preferences.getPreferences(), query); dispatch({ type: QueryBarActions.SetQuery, fields }); }; }; @@ -245,14 +236,11 @@ export const applyFromHistory = ( } return acc; }, {}); - const fields = mapQueryToFormFields( - { maxTimeMS: preferences.getPreferences().maxTimeMS }, - { - ...DEFAULT_FIELD_VALUES, - ...query, - ...currentQuery, - } - ); + const fields = mapQueryToFormFields(preferences.getPreferences(), { + ...DEFAULT_FIELD_VALUES, + ...query, + ...currentQuery, + }); dispatch({ type: QueryBarActions.ApplyFromHistory, fields, diff --git a/packages/compass-query-bar/src/utils/query.ts b/packages/compass-query-bar/src/utils/query.ts index a98a0a48dbd..081a3cd6412 100644 --- a/packages/compass-query-bar/src/utils/query.ts +++ b/packages/compass-query-bar/src/utils/query.ts @@ -14,7 +14,6 @@ import type { QueryProperty, } from '../constants/query-properties'; import { QUERY_PROPERTIES } from '../constants/query-properties'; -import { WEB_MAX_TIME_MS_LIMIT } from '../constants/query-option-definition'; export function mapFormFieldsToQuery(fields: QueryFormFields): BaseQuery { // We always want filter field to be in the query, even if the field @@ -57,7 +56,7 @@ export function doesQueryHaveExtraOptionsSet(fields?: QueryFormFields) { export function parseQueryAttributesToFormFields( query: Record, - preferences: Pick + preferences: Pick ): QueryFormFields { return Object.fromEntries( Object.entries(query) @@ -82,7 +81,7 @@ export function parseQueryAttributesToFormFields( * Map query document to the query fields state only preserving valid values */ export function mapQueryToFormFields( - preferences: Pick, + preferences: Pick, query?: BaseQuery, onlyValid = true ): QueryFormFields { @@ -128,8 +127,8 @@ export function validateField( value: string, { maxTimeMS: preferencesMaxTimeMS, - showMaxTimeMSWarning, - }: Pick + maxTimeMSEnvLimit, + }: Pick ) { const validated = validate(field, value); if (field === 'filter' && validated === '') { @@ -141,15 +140,15 @@ export function validateField( } // Additional validation for maxTimeMS to make sure that we are not over the - // upper bound set in preferences or Data Explorer limits + // upper bound set in preferences or environment limits if (field === 'maxTimeMS') { const maxTimeMS = Number(value); - // When warning is enabled, enforce hard limit + // When environment limit is set (> 0), enforce it if ( - showMaxTimeMSWarning && + maxTimeMSEnvLimit && !Number.isNaN(maxTimeMS) && - maxTimeMS > WEB_MAX_TIME_MS_LIMIT + maxTimeMS > maxTimeMSEnvLimit ) { return false; } @@ -175,7 +174,7 @@ export function validateField( export function isQueryFieldsValid( fields: QueryFormFields, - preferences: Pick + preferences: Pick ) { return Object.entries(fields).every( ([key, value]) => validateField(key, value.string, preferences) !== false diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index 79867e07a16..083806962bc 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -384,7 +384,7 @@ const CompassWeb = ({ }); const preferencesAccess = useCompassWebPreferences({ ...initialPreferences, - showMaxTimeMSWarning: true, + maxTimeMSEnvLimit: 300_000, // 5 minutes limit for Data Explorer }); // TODO (COMPASS-9565): My Queries feature flag will be used to conditionally provide storage providers const initialWorkspaceRef = useRef(initialWorkspace);