From 1834ed89581846a9d3d4c5003380edd86d69a3d3 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Wed, 15 Jul 2026 07:50:01 -0700 Subject: [PATCH 1/7] chore(e2e-tests): update server aggregation error warning for latest alpha --- .../tests/collection-aggregations-tab.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index d9c9d75f91d..101df06b229 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -1004,7 +1004,10 @@ describe('Collection aggregations tab', function () { 8996503, // Allow "$function is deprecated" warning (l: LogEntry) => { return ( - l.id === 23799 && ['Interrupted'].includes(l.attr?.error?.codeName) + l.id === 23799 && + ['Interrupted', 'ClientDisconnect'].includes( + l.attr?.error?.codeName as string + ) ); } ); From 064f36787f19942aa834bc0abf9381cb8fc1d15c Mon Sep 17 00:00:00 2001 From: Rhys Date: Wed, 15 Jul 2026 11:38:56 -0700 Subject: [PATCH 2/7] fixup: avoid cast Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../tests/collection-aggregations-tab.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index 101df06b229..94a48458e81 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -1005,9 +1005,7 @@ describe('Collection aggregations tab', function () { (l: LogEntry) => { return ( l.id === 23799 && - ['Interrupted', 'ClientDisconnect'].includes( - l.attr?.error?.codeName as string - ) + ['Interrupted', 'ClientDisconnect'].includes(l.attr?.error?.codeName) ); } ); From 6657633ae8f0bf74b77d76028b9f7f6e61707d42 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Wed, 22 Jul 2026 19:23:11 -0700 Subject: [PATCH 3/7] fixup: use new long running --- .../tests/collection-aggregations-tab.test.ts | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index 94a48458e81..087be186cb6 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -1003,24 +1003,54 @@ describe('Collection aggregations tab', function () { const unsubscribeAllowWarnings = allowServerWarnings( 8996503, // Allow "$function is deprecated" warning (l: LogEntry) => { + // In 9.0 the errors for ClientDisconnect and Interrupted race when an + // operation is cancelled, so we allow both. Older servers only report + // Interrupted. + const allowedCodeNames = serverSatisfies('>=9.0.0-alpha0', true) + ? ['ClientDisconnect', 'Interrupted'] + : ['Interrupted']; return ( - l.id === 23799 && - ['Interrupted', 'ClientDisconnect'].includes(l.attr?.error?.codeName) + l.id === 23799 && allowedCodeNames.includes(l.attr?.error?.codeName) ); } ); try { + // Nesting this $map N times will give runtime of 1000 ^ N, + // so with N = 5 it is basically infinite. const slowQuery = `{ - sleep: { - $function: { - body: function () { - return sleep(10000) || true; - }, - args: [], - lang: "js", + $expr: { + $map: { + input: { + $range: [0, 1000] + }, + in: { + $map: { + input: { + $range: [0, 1000] }, - }, - }`; + in: { + $map: { + input: {$range: [0, 1000]}, + in: { + $map: { + input: { + $range: [0, 1000] + }, + in: { + $map: { + input: {$range: [0, 1000]}, + in: '$$this' + } + } + } + } + } + } + } + } + } + } +}`; // Set first stage to a very slow $addFields await browser.selectStageOperator(0, '$addFields'); From 4c75228324e427cf354acf865eac12f91721f4d9 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Fri, 24 Jul 2026 10:29:53 -0700 Subject: [PATCH 4/7] fixup: new CPU bounded long running query, add comments to aggregation runs --- .../src/modules/aggregation.spec.ts | 6 ++ .../src/modules/aggregation.ts | 7 ++ .../pipeline-preview-manager.ts | 9 ++- .../tests/collection-aggregations-tab.test.ts | 68 +++++++++++-------- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/packages/compass-aggregations/src/modules/aggregation.spec.ts b/packages/compass-aggregations/src/modules/aggregation.spec.ts index 1a548461916..03d3ba8263b 100644 --- a/packages/compass-aggregations/src/modules/aggregation.spec.ts +++ b/packages/compass-aggregations/src/modules/aggregation.spec.ts @@ -24,6 +24,7 @@ import { defaultPreferencesInstance } from 'compass-preferences-model'; import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; import type { AggregationsStore } from '../stores/store'; +import { PipelineBuilder } from './pipeline-builder/pipeline-builder'; const getMockedStore = ( aggregation: AggregateState, @@ -69,6 +70,7 @@ describe('aggregation module', function () { it('runs an aggregation', async function () { const mockDocuments = [{ id: 1 }, { id: 2 }]; + const stopPreview = spy(PipelineBuilder.prototype, 'stopPreview'); const store: AggregationsStore = ( await configureStore( { pipeline: [] }, @@ -80,9 +82,13 @@ describe('aggregation module', function () { ) ).plugin.store; + stopPreview.resetHistory(); await store.dispatch(runAggregation() as any); const aggregation = store.getState().aggregation; + expect(stopPreview).to.have.been.calledOnce; + stopPreview.restore(); + expect(omit(aggregation, 'documents')).to.deep.equal({ pipeline: [], isLast: true, diff --git a/packages/compass-aggregations/src/modules/aggregation.ts b/packages/compass-aggregations/src/modules/aggregation.ts index a49a997c454..746d1068332 100644 --- a/packages/compass-aggregations/src/modules/aggregation.ts +++ b/packages/compass-aggregations/src/modules/aggregation.ts @@ -29,6 +29,10 @@ import type { DataService } from '../modules/data-service'; import toNS from 'mongodb-ns'; import type { PreferencesAccess } from 'compass-preferences-model'; +// Used in the `comment` on the aggregate command to help identify +// the operation in server logs and currentOp. +export const RUN_AGGREGATION_COMMENT = 'Compass: Run aggregation'; + const WRITE_STAGE_LINK = { $merge: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/', @@ -331,6 +335,8 @@ export const runAggregation = (): PipelineBuilderThunkAction> => { return; } + pipelineBuilder.stopPreview(); + void dispatch(fetchExplainForPipeline()); dispatch({ type: ActionTypes.RunAggregation, @@ -454,6 +460,7 @@ const fetchAggregationData = ( const options: AggregateOptions = { maxTimeMS: maxTimeMS ?? DEFAULT_MAX_TIME_MS, collation: collation ?? undefined, + comment: RUN_AGGREGATION_COMMENT, }; const lastStage = pipeline[pipeline.length - 1]; diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-preview-manager.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-preview-manager.ts index ee8dcf25e7d..a646b35691d 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-preview-manager.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-preview-manager.ts @@ -14,6 +14,10 @@ import isEqual from 'lodash/isEqual'; import type { DataService } from '../data-service'; import type { PreferencesAccess } from 'compass-preferences-model'; +// Used in the `comment` on the aggregate command to help identify +// the operation in server logs and currentOp. +export const PREVIEW_AGGREGATION_COMMENT = 'Compass: Aggregation preview'; + export const DEFAULT_SAMPLE_SIZE = 100000; export const DEFAULT_PREVIEW_LIMIT = 10; @@ -120,7 +124,10 @@ export class PipelinePreviewManager { previewSize, totalDocumentCount, }), - options, + options: { + comment: PREVIEW_AGGREGATION_COMMENT, + ...options, + }, }); this.queue.delete(idx); return result; diff --git a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index 087be186cb6..65aa2cf9826 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -1000,46 +1000,54 @@ describe('Collection aggregations tab', function () { return this.skip(); } + // We tag each aggregate command with a `comment` so we can tell the + // run aggregation apart from the stage preview aggregations. + const RUN_AGGREGATION_COMMENT = 'Compass: Run aggregation'; + const PREVIEW_AGGREGATION_COMMENT = 'Compass: Aggregation preview'; + + let sawRunAggregationInterrupt = false; const unsubscribeAllowWarnings = allowServerWarnings( 8996503, // Allow "$function is deprecated" warning (l: LogEntry) => { - // In 9.0 the errors for ClientDisconnect and Interrupted race when an - // operation is cancelled, so we allow both. Older servers only report - // Interrupted. - const allowedCodeNames = serverSatisfies('>=9.0.0-alpha0', true) - ? ['ClientDisconnect', 'Interrupted'] - : ['Interrupted']; - return ( - l.id === 23799 && allowedCodeNames.includes(l.attr?.error?.codeName) - ); + const comment = l.attr?.cmd?.comment as string | undefined; + const matches = + l.id === 23799 && + l.attr?.error?.codeName === 'Interrupted' && + comment !== undefined && + [RUN_AGGREGATION_COMMENT, PREVIEW_AGGREGATION_COMMENT].includes( + comment + ); + if (matches && comment === RUN_AGGREGATION_COMMENT) { + sawRunAggregationInterrupt = true; + } + return matches; } ); try { - // Nesting this $map N times will give runtime of 1000 ^ N, - // so with N = 5 it is basically infinite. + // Nesting this $reduce N times will give runtime of 1000 ^ N, so with + // N = 5 it is basically infinite. const slowQuery = `{ - $expr: { - $map: { - input: { - $range: [0, 1000] - }, + slow: { + $reduce: { + input: {$range: [0, 1000]}, + initialValue: 0, in: { - $map: { - input: { - $range: [0, 1000] - }, + $reduce: { + input: {$range: [0, 1000]}, + initialValue: 0, in: { - $map: { + $reduce: { input: {$range: [0, 1000]}, + initialValue: 0, in: { - $map: { - input: { - $range: [0, 1000] - }, + $reduce: { + input: {$range: [0, 1000]}, + initialValue: 0, in: { - $map: { + $reduce: { input: {$range: [0, 1000]}, - in: '$$this' + initialValue: 0, + in: {$add: ['$$value', 1]} } } } @@ -1068,6 +1076,12 @@ describe('Collection aggregations tab', function () { // load anything and dismissed "Loading" banner) const emptyResultsBanner = browser.$(Selectors.AggregationEmptyResults); await emptyResultsBanner.waitForDisplayed(); + + // Wait until the server has actually logged the run aggregation's + // cancellation error before we remove the allowlist, otherwise it could + // arrive later and leak into a subsequent test's server warnings + // checkpoint. + await browser.waitUntil(() => sawRunAggregationInterrupt); } finally { unsubscribeAllowWarnings(); } From f20e5676fca0837f2264ac8c3a9ab08c82673885 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Fri, 24 Jul 2026 10:32:18 -0700 Subject: [PATCH 5/7] fixup: remove gate --- .../tests/collection-aggregations-tab.test.ts | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index 65aa2cf9826..4b5aff3465c 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -995,34 +995,26 @@ describe('Collection aggregations tab', function () { }); it('supports cancelling long-running aggregations', async function () { - if (isTestingWebAtlasCloud()) { - // No $function on the free tier, skipping this test. - return this.skip(); - } - // We tag each aggregate command with a `comment` so we can tell the // run aggregation apart from the stage preview aggregations. const RUN_AGGREGATION_COMMENT = 'Compass: Run aggregation'; const PREVIEW_AGGREGATION_COMMENT = 'Compass: Aggregation preview'; let sawRunAggregationInterrupt = false; - const unsubscribeAllowWarnings = allowServerWarnings( - 8996503, // Allow "$function is deprecated" warning - (l: LogEntry) => { - const comment = l.attr?.cmd?.comment as string | undefined; - const matches = - l.id === 23799 && - l.attr?.error?.codeName === 'Interrupted' && - comment !== undefined && - [RUN_AGGREGATION_COMMENT, PREVIEW_AGGREGATION_COMMENT].includes( - comment - ); - if (matches && comment === RUN_AGGREGATION_COMMENT) { - sawRunAggregationInterrupt = true; - } - return matches; + const unsubscribeAllowWarnings = allowServerWarnings((l: LogEntry) => { + const comment = l.attr?.cmd?.comment as string | undefined; + const matches = + l.id === 23799 && + l.attr?.error?.codeName === 'Interrupted' && + comment !== undefined && + [RUN_AGGREGATION_COMMENT, PREVIEW_AGGREGATION_COMMENT].includes( + comment + ); + if (matches && comment === RUN_AGGREGATION_COMMENT) { + sawRunAggregationInterrupt = true; } - ); + return matches; + }); try { // Nesting this $reduce N times will give runtime of 1000 ^ N, so with // N = 5 it is basically infinite. From 741adbffa0542cde7ca78c0fffe019150ac95475 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Fri, 24 Jul 2026 12:01:33 -0700 Subject: [PATCH 6/7] fixup: remove compass name in comment --- packages/compass-aggregations/src/modules/aggregation.ts | 2 +- .../src/modules/pipeline-builder/pipeline-preview-manager.ts | 2 +- .../tests/collection-aggregations-tab.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compass-aggregations/src/modules/aggregation.ts b/packages/compass-aggregations/src/modules/aggregation.ts index 746d1068332..2f867a6eb61 100644 --- a/packages/compass-aggregations/src/modules/aggregation.ts +++ b/packages/compass-aggregations/src/modules/aggregation.ts @@ -31,7 +31,7 @@ import type { PreferencesAccess } from 'compass-preferences-model'; // Used in the `comment` on the aggregate command to help identify // the operation in server logs and currentOp. -export const RUN_AGGREGATION_COMMENT = 'Compass: Run aggregation'; +export const RUN_AGGREGATION_COMMENT = 'Run aggregation'; const WRITE_STAGE_LINK = { $merge: diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-preview-manager.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-preview-manager.ts index a646b35691d..e12642935ee 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-preview-manager.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-preview-manager.ts @@ -16,7 +16,7 @@ import type { PreferencesAccess } from 'compass-preferences-model'; // Used in the `comment` on the aggregate command to help identify // the operation in server logs and currentOp. -export const PREVIEW_AGGREGATION_COMMENT = 'Compass: Aggregation preview'; +export const PREVIEW_AGGREGATION_COMMENT = 'Aggregation preview'; export const DEFAULT_SAMPLE_SIZE = 100000; diff --git a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index 4b5aff3465c..4ef34ce8118 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -997,8 +997,8 @@ describe('Collection aggregations tab', function () { it('supports cancelling long-running aggregations', async function () { // We tag each aggregate command with a `comment` so we can tell the // run aggregation apart from the stage preview aggregations. - const RUN_AGGREGATION_COMMENT = 'Compass: Run aggregation'; - const PREVIEW_AGGREGATION_COMMENT = 'Compass: Aggregation preview'; + const RUN_AGGREGATION_COMMENT = 'Run aggregation'; + const PREVIEW_AGGREGATION_COMMENT = 'Aggregation preview'; let sawRunAggregationInterrupt = false; const unsubscribeAllowWarnings = allowServerWarnings((l: LogEntry) => { From c1bd254a45047bfe2f83cd368c3295e7bcda69f5 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Fri, 24 Jul 2026 14:39:43 -0700 Subject: [PATCH 7/7] fixup: use sleep on pre 8 --- .../tests/collection-aggregations-tab.test.ts | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index 4ef34ce8118..9ef627b5a31 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -995,30 +995,58 @@ describe('Collection aggregations tab', function () { }); it('supports cancelling long-running aggregations', async function () { - // We tag each aggregate command with a `comment` so we can tell the - // run aggregation apart from the stage preview aggregations. + // Fall back to a $function sleep in earlier server versions. + // The driver disconnect cancel doesn't always kill CPU bound op + // on earlier versions. + const useSleepSlowQuery = serverSatisfies('<8.0.0'); + + if (useSleepSlowQuery && isTestingWebAtlasCloud()) { + // No $function on the free tier, skipping this test. + return this.skip(); + } + + // We tag each aggregation command with identifiable `comment`s. const RUN_AGGREGATION_COMMENT = 'Run aggregation'; const PREVIEW_AGGREGATION_COMMENT = 'Aggregation preview'; let sawRunAggregationInterrupt = false; - const unsubscribeAllowWarnings = allowServerWarnings((l: LogEntry) => { - const comment = l.attr?.cmd?.comment as string | undefined; - const matches = - l.id === 23799 && - l.attr?.error?.codeName === 'Interrupted' && - comment !== undefined && - [RUN_AGGREGATION_COMMENT, PREVIEW_AGGREGATION_COMMENT].includes( - comment - ); - if (matches && comment === RUN_AGGREGATION_COMMENT) { - sawRunAggregationInterrupt = true; - } - return matches; - }); + const unsubscribeAllowWarnings = useSleepSlowQuery + ? allowServerWarnings( + 8996503, // Allow "$function is deprecated" warning + (l: LogEntry) => { + return l.id === 23799 && l.attr?.error?.codeName === 'Interrupted'; + } + ) + : allowServerWarnings((l: LogEntry) => { + const comment = l.attr?.cmd?.comment as string | undefined; + const matches = + l.id === 23799 && + l.attr?.error?.codeName === 'Interrupted' && + comment !== undefined && + [RUN_AGGREGATION_COMMENT, PREVIEW_AGGREGATION_COMMENT].includes( + comment + ); + if (matches && comment === RUN_AGGREGATION_COMMENT) { + sawRunAggregationInterrupt = true; + } + return matches; + }); try { - // Nesting this $reduce N times will give runtime of 1000 ^ N, so with - // N = 5 it is basically infinite. - const slowQuery = `{ + const slowQuery = useSleepSlowQuery + ? `{ + sleep: { + $function: { + body: function () { + return sleep(10000) || true; + }, + args: [], + lang: "js", + }, + }, + }` + : // Nesting this $reduce N times will give runtime of 1000 ^ N, so with + // N = 5 it is basically infinite. + `{ slow: { $reduce: { input: {$range: [0, 1000]}, @@ -1069,11 +1097,12 @@ describe('Collection aggregations tab', function () { const emptyResultsBanner = browser.$(Selectors.AggregationEmptyResults); await emptyResultsBanner.waitForDisplayed(); - // Wait until the server has actually logged the run aggregation's - // cancellation error before we remove the allowlist, otherwise it could - // arrive later and leak into a subsequent test's server warnings - // checkpoint. - await browser.waitUntil(() => sawRunAggregationInterrupt); + if (!useSleepSlowQuery) { + // Wait until the server has logged the run aggregation's + // cancellation error before we remove the allowlist, so we don't + // leak it into other tests. + await browser.waitUntil(() => sawRunAggregationInterrupt); + } } finally { unsubscribeAllowWarnings(); }