From dbf40abb7bed1bc5e0a19753c7188e0325d25cf2 Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 01:31:07 +0200 Subject: [PATCH 01/24] chore(explain-aggregation): explain aggregation --- .../pipeline-explain/explain-error.tsx | 56 +++++ .../pipeline-explain/explain-indexes.tsx | 29 +++ .../explain-query-performance.tsx | 57 +++++ .../pipeline-explain/explain-results.tsx | 50 +++++ .../src/components/pipeline-explain/index.tsx | 89 +++++++- .../src/modules/explain.ts | 203 +++++++++++++++++- .../src/utils/cancellable-aggregation.ts | 50 +++++ packages/explain-plan-helper/src/index.ts | 2 +- 8 files changed, 527 insertions(+), 9 deletions(-) create mode 100644 packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx create mode 100644 packages/compass-aggregations/src/components/pipeline-explain/explain-indexes.tsx create mode 100644 packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx create mode 100644 packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx new file mode 100644 index 00000000000..74c684b337b --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { + css, + spacing, + uiColors, + Subtitle, + ErrorSummary, + Button, +} from '@mongodb-js/compass-components'; + +type ExplainErrorProps = { + isNetworkError: boolean; + message: string; + onRetry: () => void; +}; + +const containerStyles = css({ + display: 'flex', + gap: spacing[2], + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', +}); + +const textStyles = css({ + color: uiColors.green.dark2, + textAlign: 'center', +}); + +export const ExplainError: React.FunctionComponent = ({ + isNetworkError, + message, + onRetry, +}) => { + if (!isNetworkError) { + return ( + + ); + } + + return ( +
+ + Oops! Looks like we hit a network issue. + + Let's try that again. + +
+ ); +}; diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-indexes.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-indexes.tsx new file mode 100644 index 00000000000..9ea276b8129 --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-indexes.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Badge, BadgeVariant, Body, css } from '@mongodb-js/compass-components'; +import type { IndexInformation } from '@mongodb-js/explain-plan-helper'; + +type ExplainIndexesProps = { + indexes: IndexInformation[]; +}; + +const containerStyles = css({ + display: 'flex', +}); + +export const ExplainIndexes: React.FunctionComponent = ({ + indexes, +}) => { + if (indexes.filter(({ index }) => index).length === 0) { + return No index available for this query.; + } + + return ( +
+ {indexes.map((info, idx) => ( + + {info.index} {info.shard && <>({info.shard})} + + ))} +
+ ); +}; diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx new file mode 100644 index 00000000000..a6bce0d277c --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { Body, Subtitle, css, spacing } from '@mongodb-js/compass-components'; +import type { IndexInformation } from '@mongodb-js/explain-plan-helper'; + +import { ExplainIndexes } from './explain-indexes'; + +type ExplainQueryPerformanceProps = { + executionTimeMillis: number; + nReturned: number; + usedIndexes: IndexInformation[]; +}; + +const containerStyles = css({ + display: 'flex', + gap: spacing[3], + flexDirection: 'column', +}); + +const statsStyles = css({ + gap: spacing[1], + display: 'flex', + flexDirection: 'column', +}); + +const statItemStyles = css({ + display: 'flex', + gap: spacing[1], +}); + +const statTitleStyles = css({ + whiteSpace: 'nowrap', +}); + +export const ExplainQueryPerformance: React.FunctionComponent = + ({ nReturned, executionTimeMillis, usedIndexes }) => { + return ( +
+ Query Performance Summary +
+
+ Documents returned: + {nReturned} +
+
+ Actual Query Execution time(ms): + {executionTimeMillis} +
+
+ + Query used the following indexes: + + +
+
+
+ ); + }; diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx new file mode 100644 index 00000000000..aadbca04164 --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { css, spacing, Card } from '@mongodb-js/compass-components'; +import { DocumentListView } from '@mongodb-js/compass-crud'; +import HadronDocument from 'hadron-document'; + +import type { ExplainData } from '../../modules/explain'; +import { ExplainQueryPerformance } from './explain-query-performance'; + +type ExplainResultsProps = { + plan: ExplainData['plan']; + stats?: ExplainData['stats']; +}; + +const containerStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[4], +}); + +const cardStyles = css({ + height: '350px', + overflowY: 'scroll', +}); + +export const ExplainResults: React.FunctionComponent = ({ + plan, + stats, +}) => { + return ( +
+ {stats && ( + + )} + + { + const str = doc.toEJSON(); + void navigator.clipboard.writeText(str); + }} + /> + +
+ ); +}; diff --git a/packages/compass-aggregations/src/components/pipeline-explain/index.tsx b/packages/compass-aggregations/src/components/pipeline-explain/index.tsx index dd7b1cda9da..806e172f4b4 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/index.tsx @@ -1,19 +1,88 @@ -import React from 'react'; -import { Modal, H3, Body } from '@mongodb-js/compass-components'; +import React, { useEffect } from 'react'; +import { + css, + spacing, + Modal, + CancelLoader, + H3, + ModalFooter, + Button, +} from '@mongodb-js/compass-components'; import { connect } from 'react-redux'; import type { RootState } from '../../modules'; -import { closeExplainModal } from '../../modules/explain'; +import type { ExplainData } from '../../modules/explain'; +import { + explainAggregation, + closeExplainModal, + cancelExplain, +} from '../../modules/explain'; +import { ExplainResults } from './explain-results'; +import { ExplainError } from './explain-error'; type PipelineExplainProps = { isModalOpen: boolean; + isLoading: boolean; + error?: RootState['explain']['error']; + explain?: ExplainData; onCloseModal: () => void; + onRunExplain: () => void; + onCancelExplain: () => void; }; +const contentStyles = css({ + marginTop: spacing[3], + marginBottom: spacing[3], +}); + +const footerStyles = css({ + paddingRight: 0, + paddingBottom: 0, +}); + export const PipelineExplain: React.FunctionComponent = ({ isModalOpen, + isLoading, + error, + explain, onCloseModal, + onRunExplain, + onCancelExplain, }) => { + useEffect(() => { + isModalOpen && onRunExplain(); + }, [isModalOpen, onRunExplain]); + + let content = null; + if (isLoading) { + content = ( + onCancelExplain()} + progressText="Running explain" + /> + ); + } else if (error) { + content = ( + + ); + } else if (explain) { + content = ; + } + + const modalFooter = ( + + + + ); + + const isFooterHidden = isLoading || error?.isNetworkError; + return ( = ({ data-testid="pipeline-explain-modal" >

Explain

- Implementation in progress ... +
{content}
+ {!isFooterHidden && modalFooter}
); }; -const mapState = ({ explain: { isModalOpen } }: RootState) => ({ - isModalOpen: isModalOpen, +const mapState = ({ + explain: { isModalOpen, isLoading, error, explain }, +}: RootState) => ({ + isModalOpen, + isLoading, + error, + explain, }); const mapDispatch = { onCloseModal: closeExplainModal, + onRunExplain: explainAggregation, + onCancelExplain: cancelExplain, }; export default connect(mapState, mapDispatch)(PipelineExplain); diff --git a/packages/compass-aggregations/src/modules/explain.ts b/packages/compass-aggregations/src/modules/explain.ts index 47c583c61d0..8dde025c588 100644 --- a/packages/compass-aggregations/src/modules/explain.ts +++ b/packages/compass-aggregations/src/modules/explain.ts @@ -1,8 +1,26 @@ import type { Reducer } from 'redux'; +import type { AggregateOptions, Document } from 'mongodb'; +import { MongoNetworkError, MongoServerSelectionError } from 'mongodb'; +import type { ThunkAction } from 'redux-thunk'; +import { ExplainPlan } from '@mongodb-js/explain-plan-helper'; +import type { IndexInformation } from '@mongodb-js/explain-plan-helper'; +import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; +import type { RootState } from '.'; +import { DEFAULT_MAX_TIME_MS } from '../constants'; +import { generateStage } from './stage'; +import { PROMISE_CANCELLED_ERROR } from '../utils/cancellable-promise'; +import { explainPipeline } from '../utils/cancellable-aggregation'; +const { log, mongoLogId } = createLoggerAndTelemetry( + 'COMPASS-AGGREGATIONS-UI' +); export enum ActionTypes { ModalOpened = 'compass-aggregations/modalOpened', ModalClosed = 'compass-aggregations/modalClosed', + ExplainStarted = 'compass-aggregations/explainStarted', + ExplainFinished = 'compass-aggregations/explainFinished', + ExplainFailed = 'compass-aggregations/explainFailed', + ExplainCancelled = 'compass-aggregations/explainCancelled', } type ModalOpenedAction = { @@ -13,15 +31,57 @@ type ModalClosedAction = { type: ActionTypes.ModalClosed; }; +type ExplainStartedAction = { + type: ActionTypes.ExplainStarted; + abortController: AbortController; +}; + +type ExplainFinishedAction = { + type: ActionTypes.ExplainFinished; + explain: ExplainData; +}; + +type ExplainFailedAction = { + type: ActionTypes.ExplainFailed; + error: ExplainError; +}; + +type ExplainCancelledAction = { + type: ActionTypes.ExplainCancelled; +}; + export type Actions = | ModalOpenedAction - | ModalClosedAction; + | ModalClosedAction + | ExplainStartedAction + | ExplainFinishedAction + | ExplainFailedAction + | ExplainCancelledAction; + +export type ExplainData = { + plan: Document; + stats?: { + executionTimeMillis: number; + nReturned: number; + usedIndexes: IndexInformation[]; + }; +}; + +type ExplainError = { + message: string; + isNetworkError: boolean; +} export type State = { + isLoading: boolean; isModalOpen: boolean; + explain?: ExplainData; + abortController?: AbortController; + error?: ExplainError; }; export const INITIAL_STATE: State = { + isLoading: false, isModalOpen: false, }; @@ -29,12 +89,43 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { switch (action.type) { case ActionTypes.ModalOpened: return { - isModalOpen: true, + isLoading: false, + abortController: undefined, + error: undefined, + explain: undefined, + isModalOpen: !state.isModalOpen, }; case ActionTypes.ModalClosed: return { + ...state, isModalOpen: false, }; + case ActionTypes.ExplainStarted: + return { + ...state, + explain: undefined, + error: undefined, + isLoading: true, + abortController: action.abortController, + }; + case ActionTypes.ExplainFinished: + return { + ...state, + explain: action.explain, + error: undefined, + isLoading: false, + abortController: undefined, + }; + case ActionTypes.ExplainFailed: + return { + ...state, + explain: undefined, + error: action.error, + isLoading: false, + abortController: undefined, + }; + case ActionTypes.ExplainCancelled: + return INITIAL_STATE; default: return state; } @@ -48,4 +139,112 @@ export const closeExplainModal = (): ModalClosedAction => ({ type: ActionTypes.ModalClosed, }); +export const cancelExplain = (): ThunkAction< + void, + RootState, + void, + Actions +> => { + return (_dispatch, getState) => { + const { explain: { abortController } } = getState(); + abortController?.abort(); + }; +}; + +export const explainAggregation = (): ThunkAction< + void, + RootState, + void, + Actions +> => { + return async (dispatch, getState) => { + const { + pipeline, + namespace, + maxTimeMS, + collation, + dataService: { dataService }, + } = getState(); + + if (!dataService) { + return; + } + + try { + const abortController = new AbortController(); + const signal = abortController.signal; + dispatch({ + type: ActionTypes.ExplainStarted, + abortController, + }); + + const options: AggregateOptions = { + maxTimeMS: maxTimeMS || DEFAULT_MAX_TIME_MS, + allowDiskUse: true, + collation: collation || undefined, + }; + + const rawExplain = await explainPipeline({ + dataService, + signal, + namespace, + pipeline: pipeline.map(generateStage).filter(x => Object.keys(x).length > 0), + options + }); + + const explain: ExplainData = { + plan: rawExplain, + }; + try { + const { + nReturned, + executionTimeMillis, + usedIndexes + } = new ExplainPlan(rawExplain as any); + const stats = { + executionTimeMillis, + nReturned, + usedIndexes, + } + explain.stats = stats; + } catch (e) { + log.warn( + mongoLogId(1_001_000_125), + 'Explain', + 'Failed to parse aggregation explain', + { message: (e as Error).message } + ); + } finally { + // If parsing fails, we still show raw explain json. + dispatch({ + type: ActionTypes.ExplainFinished, + explain, + }); + } + } catch (e) { + if ((e as Error).name === PROMISE_CANCELLED_ERROR) { + dispatch({ + type: ActionTypes.ExplainCancelled + }); + return; + } + dispatch({ + type: ActionTypes.ExplainFailed, + error: { + message: (e as Error).message, + isNetworkError: + e instanceof MongoNetworkError || + e instanceof MongoServerSelectionError, + }, + }); + log.warn( + mongoLogId(1_001_000_124), + 'Explain', + 'Failed to run aggregation explain', + { message: (e as Error).message } + ); + } + } +}; + export default reducer; \ No newline at end of file diff --git a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts index 0879b27de83..6a95b7e71f8 100644 --- a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts +++ b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts @@ -63,3 +63,53 @@ export async function aggregatePipeline({ return result; } + + +/** + * todo: move to data-service (COMPASS-5808) + */ +export async function explainPipeline({ + dataService, + signal, + namespace, + pipeline, + options, +}: { + dataService: DataService; + signal: AbortSignal; + namespace: string; + pipeline: Document[]; + options: AggregateOptions; +}): Promise { + if (signal.aborted) { + return Promise.reject(createCancelError()); + } + const session = dataService.startSession('CRUD'); + const cursor = dataService.aggregate( + namespace, + pipeline, + options + ); + const abort = () => { + Promise.all([ + cursor.close(), + dataService.killSessions(session) + ]).catch((err) => { + log.warn( + mongoLogId(1_001_000_107), + 'Aggregation explain', + 'Attempting to kill the session failed', + { error: err.message } + ); + }); + }; + signal.addEventListener('abort', abort, { once: true }); + let result = {}; + try { + // todo: support ADL + result = await raceWithAbort(cursor.explain(), signal); + } finally { + signal.removeEventListener('abort', abort); + } + return result; +} diff --git a/packages/explain-plan-helper/src/index.ts b/packages/explain-plan-helper/src/index.ts index 04577ea7365..93eb035cc5c 100644 --- a/packages/explain-plan-helper/src/index.ts +++ b/packages/explain-plan-helper/src/index.ts @@ -9,7 +9,7 @@ export type Stage = Record & { stage: string; [kParent]: Stage | null; }; -type IndexInformation = +export type IndexInformation = | { shard: string; index: string } | { shard: string; index: null } | { shard: null; index: string }; From 08c0ac1df854d08500d588c71d55ff3d4598e617 Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 10:20:53 +0200 Subject: [PATCH 02/24] chore(explain-aggregation): explain aggregation --- .../src/components/pipeline-explain/explain-error.tsx | 10 ++++++---- .../components/pipeline-explain/explain-indexes.tsx | 8 ++------ packages/compass-aggregations/src/modules/explain.ts | 10 +++------- .../src/utils/cancellable-aggregation.ts | 7 ++++--- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx index 74c684b337b..24450e649a9 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { css, spacing, - uiColors, Subtitle, ErrorSummary, Button, @@ -20,11 +19,14 @@ const containerStyles = css({ flexDirection: 'column', justifyContent: 'center', alignItems: 'center', + marginTop: spacing[4], + marginBottom: spacing[4], }); const textStyles = css({ - color: uiColors.green.dark2, textAlign: 'center', + marginTop: spacing[4], + marginBottom: spacing[4], }); export const ExplainError: React.FunctionComponent = ({ @@ -41,9 +43,9 @@ export const ExplainError: React.FunctionComponent = ({ return (
- Oops! Looks like we hit a network issue. + Oops! Looks like we hit a network issue.
+ Let's try that again.
- Let's try that again. + ); From 6346d21699674ffad5e19398e3b67ff33c4c05bb Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 12:03:27 +0200 Subject: [PATCH 04/24] chore(explain-aggregation): e2e tests --- .../compass-e2e-tests/helpers/selectors.ts | 2 + .../tests/collection-aggregations-tab.test.ts | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index fc2ce95348b..2d925f10b8b 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -598,6 +598,8 @@ export const NewPipelineActionsMenu = `${NewPipelineActions} + [role="menu"]`; export const SavePipelineActions = '#save-pipeline-actions'; export const SavePipelineActionsCreateView = '[data-testid="create-a-view"]'; export const SavePipelineActionsSaveAs = '[data-testid="save-pipeline-as"]'; +export const AggregationExplainButton = '[data-testid="pipeline-toolbar-explain-aggregation-button"]'; +export const AggregationExplainModal = '[data-testid="pipeline-explain-modal"]'; // Create view from pipeline modal export const CreateViewModal = '[data-testid="create_view_modal"]'; 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 6d2b6b5f5f5..7d7a0f332be 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -19,6 +19,8 @@ async function waitForAnyText( return text !== ''; }); } +const initialAggregationToolbarValue = process.env.COMPASS_SHOW_NEW_AGGREGATION_TOOLBAR; +const initialAggregationExplainValue = process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN; describe('Collection aggregations tab', function () { let compass: Compass; @@ -489,6 +491,48 @@ describe('Collection aggregations tab', function () { }); }); + describe('Aggregation Explain', function () { + let compass: Compass; + let browser: CompassBrowser; + + before(async function () { + process.env.COMPASS_SHOW_NEW_AGGREGATION_TOOLBAR = 'true'; + process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = 'true'; + + compass = await beforeTests(); + browser = compass.browser; + }); + + beforeEach(async function () { + await createNumbersCollection(); + await browser.connectWithConnectionString('mongodb://localhost:27018/test'); + // Some tests navigate away from the numbers collection aggregations tab + await browser.navigateToCollectionTab('test', 'numbers', 'Aggregations'); + // Get us back to the empty stage every time. Also test the Create New + // Pipeline flow while at it. + await browser.clickVisible(Selectors.CreateNewPipelineButton); + const modalElement = await browser.$(Selectors.ConfirmNewPipelineModal); + await modalElement.waitForDisplayed(); + await browser.clickVisible(Selectors.ConfirmNewPipelineModalConfirmButton); + await modalElement.waitForDisplayed({ reverse: true }); + }); + + after(async function () { + process.env.COMPASS_SHOW_NEW_AGGREGATION_TOOLBAR = initialAggregationToolbarValue; + process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = initialAggregationExplainValue; + await afterTests(compass, this.currentTest); + }); + + afterEach(async function () { + await afterTest(compass, this.currentTest); + }); + + it('shows the explain for a pipeline', async function () { + await browser.clickVisible(Selectors.AggregationExplainButton); + await browser.waitForAnimations(Selectors.AggregationExplainModal); + }); + }); + // TODO: stages can be re-arranged by drag and drop and the preview is refreshed after rearranging them // TODO: test auto-preview and limit // TODO: save a pipeline, close compass, re-open compass, load the pipeline From f1fd5b37580ffa5e228c16419f91740c0996fd21 Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 12:31:01 +0200 Subject: [PATCH 05/24] chore(explain-aggregation): e2e tests --- packages/compass-e2e-tests/helpers/selectors.ts | 4 ++++ .../tests/collection-aggregations-tab.test.ts | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index 2d925f10b8b..ddb4d8ca07a 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -598,6 +598,10 @@ export const NewPipelineActionsMenu = `${NewPipelineActions} + [role="menu"]`; export const SavePipelineActions = '#save-pipeline-actions'; export const SavePipelineActionsCreateView = '[data-testid="create-a-view"]'; export const SavePipelineActionsSaveAs = '[data-testid="save-pipeline-as"]'; + +// New Aggregation Toolbar Specific +export const AggregationToolbarCreateMenu = '[data-testid="create-new-menu"]'; +export const AggregationToolbarCreateNewPipeline = '[data-testid="create-new-menu-content"] > li:nth-child(1)'; export const AggregationExplainButton = '[data-testid="pipeline-toolbar-explain-aggregation-button"]'; export const AggregationExplainModal = '[data-testid="pipeline-explain-modal"]'; 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 7d7a0f332be..37c99cc45ce 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -510,7 +510,8 @@ describe('Collection aggregations tab', function () { await browser.navigateToCollectionTab('test', 'numbers', 'Aggregations'); // Get us back to the empty stage every time. Also test the Create New // Pipeline flow while at it. - await browser.clickVisible(Selectors.CreateNewPipelineButton); + await browser.clickVisible(Selectors.AggregationToolbarCreateMenu); + await browser.clickVisible(Selectors.AggregationToolbarCreateNewPipeline); const modalElement = await browser.$(Selectors.ConfirmNewPipelineModal); await modalElement.waitForDisplayed(); await browser.clickVisible(Selectors.ConfirmNewPipelineModalConfirmButton); @@ -530,6 +531,9 @@ describe('Collection aggregations tab', function () { it('shows the explain for a pipeline', async function () { await browser.clickVisible(Selectors.AggregationExplainButton); await browser.waitForAnimations(Selectors.AggregationExplainModal); + + const modal = await browser.$(Selectors.AggregationExplainModal); + expect(await modal.getText()).to.contain('Query Performance Summary'); }); }); From 89d0ca21f36475eda75d1b0cf0aa6172c61a72b5 Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 13:36:43 +0200 Subject: [PATCH 06/24] chore(explain-aggregation): log id and depcheck --- package-lock.json | 2 ++ packages/compass-aggregations/package.json | 1 + packages/compass-aggregations/src/modules/explain.ts | 4 ++-- .../compass-aggregations/src/utils/cancellable-aggregation.ts | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5e66b13bb0..63ceb2c41a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56811,6 +56811,7 @@ "license": "SSPL", "dependencies": { "@mongodb-js/compass-logging": "^0.12.0", + "@mongodb-js/explain-plan-helper": "^0.9.0", "@mongodb-js/mongodb-redux-common": "^1.12.0", "acorn-loose": "^8.0.2", "astring": "^1.7.0", @@ -108288,6 +108289,7 @@ "@mongodb-js/compass-field-store": "^7.23.0", "@mongodb-js/compass-logging": "^0.12.0", "@mongodb-js/eslint-config-compass": "^0.9.0", + "@mongodb-js/explain-plan-helper": "*", "@mongodb-js/mocha-config-compass": "^0.10.0", "@mongodb-js/mongodb-redux-common": "^1.12.0", "@mongodb-js/prettier-config-compass": "^0.5.0", diff --git a/packages/compass-aggregations/package.json b/packages/compass-aggregations/package.json index 35b4ab7adf0..f4ae4d13fc8 100644 --- a/packages/compass-aggregations/package.json +++ b/packages/compass-aggregations/package.json @@ -101,6 +101,7 @@ }, "dependencies": { "@mongodb-js/compass-logging": "^0.12.0", + "@mongodb-js/explain-plan-helper": "^0.9.0", "@mongodb-js/mongodb-redux-common": "^1.12.0", "acorn-loose": "^8.0.2", "astring": "^1.7.0", diff --git a/packages/compass-aggregations/src/modules/explain.ts b/packages/compass-aggregations/src/modules/explain.ts index 4a5fe2e7936..8b39342010e 100644 --- a/packages/compass-aggregations/src/modules/explain.ts +++ b/packages/compass-aggregations/src/modules/explain.ts @@ -205,7 +205,7 @@ export const explainAggregation = (): ThunkAction< explain.stats = stats; } catch (e) { log.warn( - mongoLogId(1_001_000_125), + mongoLogId(1_001_000_137), 'Explain', 'Failed to parse aggregation explain', { message: (e as Error).message } @@ -234,7 +234,7 @@ export const explainAggregation = (): ThunkAction< }, }); log.warn( - mongoLogId(1_001_000_124), + mongoLogId(1_001_000_138), 'Explain', 'Failed to run aggregation explain', { message: (e as Error).message } diff --git a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts index db0519d5609..58a94d76f09 100644 --- a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts +++ b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts @@ -96,7 +96,7 @@ export async function explainPipeline({ dataService.killSessions(session) ]).catch((err) => { log.warn( - mongoLogId(1_001_000_126), + mongoLogId(1001000139), 'Aggregation explain', 'Attempting to kill the session failed', { error: err.message } From c59cb9258858c8ce0dc2b9122dc6a756eb1968c0 Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 13:57:19 +0200 Subject: [PATCH 07/24] chore(explain-aggregation): pretty e2e --- .../compass-e2e-tests/helpers/selectors.ts | 6 ++++-- .../tests/collection-aggregations-tab.test.ts | 20 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index ddb4d8ca07a..dafd71737e0 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -601,8 +601,10 @@ export const SavePipelineActionsSaveAs = '[data-testid="save-pipeline-as"]'; // New Aggregation Toolbar Specific export const AggregationToolbarCreateMenu = '[data-testid="create-new-menu"]'; -export const AggregationToolbarCreateNewPipeline = '[data-testid="create-new-menu-content"] > li:nth-child(1)'; -export const AggregationExplainButton = '[data-testid="pipeline-toolbar-explain-aggregation-button"]'; +export const AggregationToolbarCreateNewPipeline = + '[data-testid="create-new-menu-content"] > li:nth-child(1)'; +export const AggregationExplainButton = + '[data-testid="pipeline-toolbar-explain-aggregation-button"]'; export const AggregationExplainModal = '[data-testid="pipeline-explain-modal"]'; // Create view from pipeline modal 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 37c99cc45ce..b19a9fa38aa 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -19,8 +19,10 @@ async function waitForAnyText( return text !== ''; }); } -const initialAggregationToolbarValue = process.env.COMPASS_SHOW_NEW_AGGREGATION_TOOLBAR; -const initialAggregationExplainValue = process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN; +const initialAggregationToolbarValue = + process.env.COMPASS_SHOW_NEW_AGGREGATION_TOOLBAR; +const initialAggregationExplainValue = + process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN; describe('Collection aggregations tab', function () { let compass: Compass; @@ -505,7 +507,9 @@ describe('Collection aggregations tab', function () { beforeEach(async function () { await createNumbersCollection(); - await browser.connectWithConnectionString('mongodb://localhost:27018/test'); + await browser.connectWithConnectionString( + 'mongodb://localhost:27018/test' + ); // Some tests navigate away from the numbers collection aggregations tab await browser.navigateToCollectionTab('test', 'numbers', 'Aggregations'); // Get us back to the empty stage every time. Also test the Create New @@ -514,13 +518,17 @@ describe('Collection aggregations tab', function () { await browser.clickVisible(Selectors.AggregationToolbarCreateNewPipeline); const modalElement = await browser.$(Selectors.ConfirmNewPipelineModal); await modalElement.waitForDisplayed(); - await browser.clickVisible(Selectors.ConfirmNewPipelineModalConfirmButton); + await browser.clickVisible( + Selectors.ConfirmNewPipelineModalConfirmButton + ); await modalElement.waitForDisplayed({ reverse: true }); }); after(async function () { - process.env.COMPASS_SHOW_NEW_AGGREGATION_TOOLBAR = initialAggregationToolbarValue; - process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = initialAggregationExplainValue; + process.env.COMPASS_SHOW_NEW_AGGREGATION_TOOLBAR = + initialAggregationToolbarValue; + process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = + initialAggregationExplainValue; await afterTests(compass, this.currentTest); }); From 1e2e0445ced890e6ff4b9fb6a6bad00af85f4f62 Mon Sep 17 00:00:00 2001 From: Basit <1305718+mabaasit@users.noreply.github.com> Date: Tue, 17 May 2022 15:29:39 +0200 Subject: [PATCH 08/24] Update packages/compass-aggregations/src/utils/cancellable-aggregation.ts Co-authored-by: Anna Henningsen --- .../compass-aggregations/src/utils/cancellable-aggregation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts index 58a94d76f09..2ff26c629c6 100644 --- a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts +++ b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts @@ -96,7 +96,7 @@ export async function explainPipeline({ dataService.killSessions(session) ]).catch((err) => { log.warn( - mongoLogId(1001000139), + mongoLogId(1_001_000_139), 'Aggregation explain', 'Attempting to kill the session failed', { error: err.message } From be6f5b48b390a6821361e20a8c297426481db82d Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 15:36:01 +0200 Subject: [PATCH 09/24] chore(explain-aggregation): remove network error --- .../pipeline-explain/explain-error.tsx | 58 ------------------- .../pipeline-explain/index.spec.tsx | 31 +--------- .../src/components/pipeline-explain/index.tsx | 14 ++--- .../src/modules/explain.ts | 19 ++---- 4 files changed, 9 insertions(+), 113 deletions(-) delete mode 100644 packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx deleted file mode 100644 index b76522525b9..00000000000 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-error.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import { - css, - spacing, - Subtitle, - ErrorSummary, - Button, -} from '@mongodb-js/compass-components'; - -type ExplainErrorProps = { - isNetworkError: boolean; - message: string; - onRetry: () => void; -}; - -const containerStyles = css({ - display: 'flex', - gap: spacing[2], - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - marginTop: spacing[4], - marginBottom: spacing[4], -}); - -const textStyles = css({ - textAlign: 'center', - marginTop: spacing[4], - marginBottom: spacing[4], -}); - -export const ExplainError: React.FunctionComponent = ({ - isNetworkError, - message, - onRetry, -}) => { - if (!isNetworkError) { - return ( - - ); - } - - return ( -
- - Oops! Looks like we hit a network issue.
- Let's try that again. -
- -
- ); -}; diff --git a/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx index 758146742df..8f16f3982c6 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx @@ -45,10 +45,7 @@ describe('PipelineExplain', function () { it('renders error state - non-network error', function () { renderPipelineExplain({ - error: { - isNetworkError: false, - message: 'Error occurred', - }, + error: 'Error occurred', }); const modal = screen.getByTestId('pipeline-explain-modal'); expect(within(modal).getByTestId('pipeline-explain-error')).to.exist; @@ -61,32 +58,6 @@ describe('PipelineExplain', function () { .exist; }); - it('renders error state - network error', function () { - const onRunExplainSpy = spy(); - renderPipelineExplain({ - error: { - isNetworkError: true, - message: 'Error occurred', - }, - onRunExplain: onRunExplainSpy, - }); - const modal = screen.getByTestId('pipeline-explain-modal'); - expect(within(modal).getByTestId('pipeline-explain-error')).to.exist; - expect(within(modal).findByText('Oops! Looks like we hit a network issue.')) - .to.exist; - // when the modal is open, onRunExplain is called to fetch the explain - expect(onRunExplainSpy.callCount).to.equal(1); - - expect(within(modal).getByTestId('pipeline-explain-retry-button')).to.exist; - userEvent.click(within(modal).getByTestId('pipeline-explain-retry-button')); - expect(onRunExplainSpy.callCount).to.equal(2); - - expect(() => { - within(modal).getByTestId('pipeline-explain-footer-close-button'), - 'does not render footer in network error state'; - }).to.throw; - }); - it('renders explain results - without stats', function () { renderPipelineExplain({ explain: { diff --git a/packages/compass-aggregations/src/components/pipeline-explain/index.tsx b/packages/compass-aggregations/src/components/pipeline-explain/index.tsx index 9e7b48e45c3..210e78da675 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/index.tsx @@ -7,6 +7,7 @@ import { H3, ModalFooter, Button, + ErrorSummary, } from '@mongodb-js/compass-components'; import { connect } from 'react-redux'; @@ -18,12 +19,11 @@ import { cancelExplain, } from '../../modules/explain'; import { ExplainResults } from './explain-results'; -import { ExplainError } from './explain-error'; type PipelineExplainProps = { isModalOpen: boolean; isLoading: boolean; - error?: RootState['explain']['error']; + error?: string; explain?: ExplainData; onCloseModal: () => void; onRunExplain: () => void; @@ -65,11 +65,7 @@ export const PipelineExplain: React.FunctionComponent = ({ ); } else if (error) { content = ( - + ); } else if (explain) { content = ; @@ -86,8 +82,6 @@ export const PipelineExplain: React.FunctionComponent = ({ ); - const isFooterHidden = isLoading || error?.isNetworkError; - return ( = ({ >

Explain

{content}
- {!isFooterHidden && modalFooter} + {!isLoading && modalFooter}
); }; diff --git a/packages/compass-aggregations/src/modules/explain.ts b/packages/compass-aggregations/src/modules/explain.ts index 8b39342010e..54f4ca81b3f 100644 --- a/packages/compass-aggregations/src/modules/explain.ts +++ b/packages/compass-aggregations/src/modules/explain.ts @@ -1,6 +1,5 @@ import type { Reducer } from 'redux'; import type { AggregateOptions, Document } from 'mongodb'; -import { MongoNetworkError, MongoServerSelectionError } from 'mongodb'; import type { ThunkAction } from 'redux-thunk'; import { ExplainPlan } from '@mongodb-js/explain-plan-helper'; import type { IndexInformation } from '@mongodb-js/explain-plan-helper'; @@ -43,7 +42,7 @@ type ExplainFinishedAction = { type ExplainFailedAction = { type: ActionTypes.ExplainFailed; - error: ExplainError; + error: string; }; type ExplainCancelledAction = { @@ -67,17 +66,12 @@ export type ExplainData = { }; }; -type ExplainError = { - message: string; - isNetworkError: boolean; -} - export type State = { isLoading: boolean; isModalOpen: boolean; explain?: ExplainData; abortController?: AbortController; - error?: ExplainError; + error?: string; }; export const INITIAL_STATE: State = { @@ -226,14 +220,9 @@ export const explainAggregation = (): ThunkAction< } dispatch({ type: ActionTypes.ExplainFailed, - error: { - message: (e as Error).message, - isNetworkError: - e instanceof MongoNetworkError || - e instanceof MongoServerSelectionError, - }, + error: (e as Error).message, }); - log.warn( + log.error( mongoLogId(1_001_000_138), 'Explain', 'Failed to run aggregation explain', From e35fa26cab969b8339f56590c6607972966270fe Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 15:37:44 +0200 Subject: [PATCH 10/24] chore(explain-aggregation): remove network error --- .../src/components/pipeline-explain/index.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx index 8f16f3982c6..59815d123f5 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/index.spec.tsx @@ -43,7 +43,7 @@ describe('PipelineExplain', function () { }, 'does not show footer in loading state').to.throw; }); - it('renders error state - non-network error', function () { + it('renders error state', function () { renderPipelineExplain({ error: 'Error occurred', }); From 19419568de3578735ccd76f7d13bcb012c6d28fc Mon Sep 17 00:00:00 2001 From: Basit Date: Tue, 17 May 2022 18:04:59 +0200 Subject: [PATCH 11/24] chore(explain-aggregation): clean up react --- .../pipeline-explain/explain-results.tsx | 14 ++-- .../src/components/pipeline-explain/index.tsx | 41 ++++------ .../pipeline-header/pipeline-actions.tsx | 4 +- .../src/modules/explain.ts | 82 ++++++++----------- 4 files changed, 57 insertions(+), 84 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx index 735e398708c..0df58191be2 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { css, spacing, Card } from '@mongodb-js/compass-components'; -import { DocumentListView } from '@mongodb-js/compass-crud'; +import { Document } from '@mongodb-js/compass-crud'; import HadronDocument from 'hadron-document'; import type { ExplainData } from '../../modules/explain'; @@ -26,6 +26,7 @@ export const ExplainResults: React.FunctionComponent = ({ plan, stats, }) => { + const doc = new HadronDocument(plan); return (
{stats && ( @@ -36,12 +37,11 @@ export const ExplainResults: React.FunctionComponent = ({ /> )} - { - const str = doc.toEJSON(); - void navigator.clipboard.writeText(str); + { + void navigator.clipboard.writeText(doc.toEJSON()); }} /> diff --git a/packages/compass-aggregations/src/components/pipeline-explain/index.tsx b/packages/compass-aggregations/src/components/pipeline-explain/index.tsx index 210e78da675..463e6fb4a73 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { css, spacing, @@ -13,11 +13,7 @@ import { connect } from 'react-redux'; import type { RootState } from '../../modules'; import type { ExplainData } from '../../modules/explain'; -import { - explainAggregation, - closeExplainModal, - cancelExplain, -} from '../../modules/explain'; +import { closeExplainModal, cancelExplain } from '../../modules/explain'; import { ExplainResults } from './explain-results'; type PipelineExplainProps = { @@ -26,7 +22,6 @@ type PipelineExplainProps = { error?: string; explain?: ExplainData; onCloseModal: () => void; - onRunExplain: () => void; onCancelExplain: () => void; }; @@ -46,13 +41,8 @@ export const PipelineExplain: React.FunctionComponent = ({ error, explain, onCloseModal, - onRunExplain, onCancelExplain, }) => { - useEffect(() => { - isModalOpen && onRunExplain(); - }, [isModalOpen, onRunExplain]); - let content = null; if (isLoading) { content = ( @@ -65,22 +55,15 @@ export const PipelineExplain: React.FunctionComponent = ({ ); } else if (error) { content = ( - + ); } else if (explain) { content = ; } - const modalFooter = ( - - - - ); + if (!content) { + return null; + } return ( = ({ >

Explain

{content}
- {!isLoading && modalFooter} + {!isLoading && ( + + + + )}
); }; @@ -106,7 +98,6 @@ const mapState = ({ const mapDispatch = { onCloseModal: closeExplainModal, - onRunExplain: explainAggregation, onCancelExplain: cancelExplain, }; export default connect(mapState, mapDispatch)(PipelineExplain); diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx index c71fc2befcb..4af1b7aa1e9 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx @@ -15,7 +15,7 @@ import { } from '../../../modules/aggregation'; import { isEmptyishStage } from '../../../modules/stage'; import { updateView } from '../../../modules/update-view'; -import { openExplainModal } from '../../../modules/explain'; +import { explainAggregation } from '../../../modules/explain'; const containerStyles = css({ display: 'flex', @@ -179,7 +179,7 @@ const mapDispatch = { onUpdateView: updateView, onRunAggregation: runAggregation, onExportAggregationResults: exportAggregationResults, - onExplainAggregation: openExplainModal, + onExplainAggregation: explainAggregation, }; export default connect(mapState, mapDispatch)(PipelineActions); diff --git a/packages/compass-aggregations/src/modules/explain.ts b/packages/compass-aggregations/src/modules/explain.ts index 54f4ca81b3f..e0304ddb677 100644 --- a/packages/compass-aggregations/src/modules/explain.ts +++ b/packages/compass-aggregations/src/modules/explain.ts @@ -14,22 +14,12 @@ const { log, mongoLogId } = createLoggerAndTelemetry( 'COMPASS-AGGREGATIONS-UI' ); export enum ActionTypes { - ModalOpened = 'compass-aggregations/modalOpened', - ModalClosed = 'compass-aggregations/modalClosed', ExplainStarted = 'compass-aggregations/explainStarted', ExplainFinished = 'compass-aggregations/explainFinished', ExplainFailed = 'compass-aggregations/explainFailed', ExplainCancelled = 'compass-aggregations/explainCancelled', } -type ModalOpenedAction = { - type: ActionTypes.ModalOpened; -}; - -type ModalClosedAction = { - type: ActionTypes.ModalClosed; -}; - type ExplainStartedAction = { type: ActionTypes.ExplainStarted; abortController: AbortController; @@ -50,8 +40,6 @@ type ExplainCancelledAction = { }; export type Actions = - | ModalOpenedAction - | ModalClosedAction | ExplainStartedAction | ExplainFinishedAction | ExplainFailedAction @@ -81,53 +69,47 @@ export const INITIAL_STATE: State = { const reducer: Reducer = (state = INITIAL_STATE, action) => { switch (action.type) { - case ActionTypes.ModalOpened: - return { - isLoading: false, - abortController: undefined, - error: undefined, - explain: undefined, - isModalOpen: true, - }; - case ActionTypes.ModalClosed: - case ActionTypes.ExplainCancelled: - return INITIAL_STATE; case ActionTypes.ExplainStarted: return { - ...state, + isModalOpen: true, + isLoading: true, explain: undefined, error: undefined, - isLoading: true, abortController: action.abortController, }; case ActionTypes.ExplainFinished: return { - ...state, + isModalOpen: true, + isLoading: false, explain: action.explain, error: undefined, - isLoading: false, abortController: undefined, }; case ActionTypes.ExplainFailed: return { - ...state, + isModalOpen: true, + isLoading: false, explain: undefined, error: action.error, - isLoading: false, abortController: undefined, }; + case ActionTypes.ExplainCancelled: + return INITIAL_STATE; default: return state; } }; -export const openExplainModal = (): ModalOpenedAction => ({ - type: ActionTypes.ModalOpened, -}); - -export const closeExplainModal = (): ModalClosedAction => ({ - type: ActionTypes.ModalClosed, -}); +export const closeExplainModal = (): ThunkAction< + void, + RootState, + void, + Actions +> => { + return (dispatch) => { + dispatch(cancelExplain()); + }; +}; export const cancelExplain = (): ThunkAction< void, @@ -135,9 +117,12 @@ export const cancelExplain = (): ThunkAction< void, Actions > => { - return (_dispatch, getState) => { + return (dispatch, getState) => { const { explain: { abortController } } = getState(); abortController?.abort(); + dispatch({ + type: ActionTypes.ExplainCancelled + }); }; }; @@ -212,22 +197,19 @@ export const explainAggregation = (): ThunkAction< }); } } catch (e) { - if ((e as Error).name === PROMISE_CANCELLED_ERROR) { + // Cancellation is handled in cancelExplain + if ((e as Error).name !== PROMISE_CANCELLED_ERROR) { dispatch({ - type: ActionTypes.ExplainCancelled + type: ActionTypes.ExplainFailed, + error: (e as Error).message, }); - return; + log.error( + mongoLogId(1_001_000_138), + 'Explain', + 'Failed to run aggregation explain', + { message: (e as Error).message } + ); } - dispatch({ - type: ActionTypes.ExplainFailed, - error: (e as Error).message, - }); - log.error( - mongoLogId(1_001_000_138), - 'Explain', - 'Failed to run aggregation explain', - { message: (e as Error).message } - ); } } }; From 0f4532a2f0d2acaadd71cb5b87021d9a2f4a86d8 Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 18 May 2022 10:11:03 +0200 Subject: [PATCH 12/24] chore(explain-aggregation): configurable explain button --- .../src/components/pipeline-toolbar/index.tsx | 5 ++++- .../pipeline-toolbar/pipeline-header/index.tsx | 3 +++ .../pipeline-header/pipeline-actions.tsx | 5 ++++- .../src/components/pipeline/pipeline.jsx | 2 ++ packages/compass-aggregations/src/plugin.jsx | 9 ++++++--- packages/compass-collection/src/stores/context.tsx | 1 + 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx index a4d6924cc82..68d9b577137 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx @@ -52,12 +52,14 @@ type PipelineToolbarProps = { isSettingsVisible: boolean; showRunButton: boolean; showExportButton: boolean; + showExplainButton: boolean; }; export const PipelineToolbar: React.FunctionComponent = ({ isSettingsVisible, showRunButton, - showExportButton + showExportButton, + showExplainButton, }) => { const [isOptionsVisible, setIsOptionsVisible] = useState(false); return ( @@ -76,6 +78,7 @@ export const PipelineToolbar: React.FunctionComponent = ({ onToggleOptions={() => setIsOptionsVisible(!isOptionsVisible)} showRunButton={showRunButton} showExportButton={showExportButton} + showExplainButton={showExplainButton} /> {isOptionsVisible && (
diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/index.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/index.tsx index cd1c2d02da9..10b4195ff43 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/index.tsx @@ -40,6 +40,7 @@ type PipelineHeaderProps = { isOptionsVisible: boolean; showRunButton: boolean; showExportButton: boolean; + showExplainButton: boolean; onShowSavedPipelines: () => void; onToggleOptions: () => void; isOpenPipelineVisible: boolean; @@ -49,6 +50,7 @@ export const PipelineHeader: React.FunctionComponent = ({ onShowSavedPipelines, showRunButton, showExportButton, + showExplainButton, onToggleOptions, isOptionsVisible, isOpenPipelineVisible, @@ -78,6 +80,7 @@ export const PipelineHeader: React.FunctionComponent = ({ isOptionsVisible={isOptionsVisible} showRunButton={showRunButton} showExportButton={showExportButton} + showExplainButton={showExplainButton} />
diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx index 4af1b7aa1e9..b4ff131f62c 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx @@ -52,6 +52,7 @@ type PipelineActionsProps = { isUpdateViewButtonDisabled?: boolean; onUpdateView: () => void; + showExplainButton?: boolean; isExplainButtonDisabled?: boolean; onExplainAggregation: () => void; @@ -68,6 +69,7 @@ export const PipelineActions: React.FunctionComponent = ({ showUpdateViewButton, isUpdateViewButtonDisabled, isExplainButtonDisabled, + showExplainButton: _showExplainButton, onUpdateView, onRunAggregation, onToggleOptions, @@ -79,7 +81,8 @@ export const PipelineActions: React.FunctionComponent = ({ process?.env?.COMPASS_ENABLE_AGGREGATION_EXPORT === 'true' && _showExportButton; const showExplainButton = - process?.env?.COMPASS_ENABLE_AGGREGATION_EXPLAIN === 'true'; + process?.env?.COMPASS_ENABLE_AGGREGATION_EXPLAIN === 'true' && + _showExplainButton; const optionsLabel = isOptionsVisible ? 'Less Options' : 'More Options'; return (
diff --git a/packages/compass-aggregations/src/components/pipeline/pipeline.jsx b/packages/compass-aggregations/src/components/pipeline/pipeline.jsx index 029f7eb5b60..a46a0784543 100644 --- a/packages/compass-aggregations/src/components/pipeline/pipeline.jsx +++ b/packages/compass-aggregations/src/components/pipeline/pipeline.jsx @@ -128,6 +128,7 @@ class Pipeline extends PureComponent { refreshInputDocuments: PropTypes.func.isRequired, showExportButton: PropTypes.bool.isRequired, showRunButton: PropTypes.bool.isRequired, + showExplainButton: PropTypes.bool.isRequired, }; static defaultProps = { @@ -259,6 +260,7 @@ class Pipeline extends PureComponent { ); } diff --git a/packages/compass-aggregations/src/plugin.jsx b/packages/compass-aggregations/src/plugin.jsx index 99db87aa9e5..dcef33b8a31 100644 --- a/packages/compass-aggregations/src/plugin.jsx +++ b/packages/compass-aggregations/src/plugin.jsx @@ -18,13 +18,15 @@ class Plugin extends Component { static propTypes = { store: PropTypes.object.isRequired, showExportButton: PropTypes.bool, - showRunButton: PropTypes.bool - } + showRunButton: PropTypes.bool, + showExplainButton: PropTypes.bool, + }; static defaultProps = { showExportButton: false, showRunButton: false, - } + showExplainButton: false, + }; /** * Connect the Plugin to the store and render. @@ -37,6 +39,7 @@ class Plugin extends Component { ); diff --git a/packages/compass-collection/src/stores/context.tsx b/packages/compass-collection/src/stores/context.tsx index 159ad7df36d..478c422e313 100644 --- a/packages/compass-collection/src/stores/context.tsx +++ b/packages/compass-collection/src/stores/context.tsx @@ -373,6 +373,7 @@ const createContext = ({ ...(role.name === 'Aggregations' && { showExportButton: true, showRunButton: true, + showExplainButton: true, }), }; From aa62431e876d0e0ca2aaeb1c72fc24be42ef8cf1 Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 18 May 2022 10:33:24 +0200 Subject: [PATCH 13/24] chore(explain-aggregation): tests --- .../src/components/pipeline-toolbar/index.spec.tsx | 14 ++++++++++---- .../pipeline-header/index.spec.tsx | 1 + .../pipeline-header/pipeline-actions.spec.tsx | 4 +++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx index c270416780c..128f51b6004 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx @@ -14,9 +14,10 @@ describe('PipelineToolbar', function () { render( ); @@ -130,7 +131,12 @@ describe('PipelineToolbar', function () { it('does not render toolbar settings', function () { render( - + ); const toolbar = screen.getByTestId('pipeline-toolbar'); diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/index.spec.tsx index 455872bc2ec..b1b39425638 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/index.spec.tsx @@ -23,6 +23,7 @@ describe('PipelineHeader', function () { isOptionsVisible showRunButton showExportButton + showExplainButton onShowSavedPipelines={onShowSavedPipelinesSpy} onToggleOptions={onToggleOptionsSpy} /> diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.spec.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.spec.tsx index 7496d616c92..8ba1e69377e 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.spec.tsx @@ -28,6 +28,7 @@ describe('PipelineActions', function () { isOptionsVisible={true} showRunButton={true} showExportButton={true} + showExplainButton={true} onRunAggregation={onRunAggregationSpy} onToggleOptions={onToggleOptionsSpy} onExportAggregationResults={onExportAggregationResultsSpy} @@ -94,6 +95,7 @@ describe('PipelineActions', function () { isOptionsVisible={false} showRunButton={true} showExportButton={true} + showExplainButton={true} onRunAggregation={onRunAggregationSpy} onToggleOptions={onToggleOptionsSpy} onExportAggregationResults={() => {}} @@ -121,7 +123,6 @@ describe('PipelineActions', function () { let onExplainAggregationSpy: SinonSpy; beforeEach(function () { - process.env.COMPASS_ENABLE_AGGREGATION_EXPORT = 'true'; process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = 'true'; onRunAggregationSpy = spy(); onExportAggregationResultsSpy = spy(); @@ -134,6 +135,7 @@ describe('PipelineActions', function () { isOptionsVisible={true} showRunButton={true} showExportButton={true} + showExplainButton={true} onRunAggregation={onRunAggregationSpy} onToggleOptions={() => {}} onExportAggregationResults={onExportAggregationResultsSpy} From e4216f6c706955330c338e942a79b64cfa9f7929 Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 18 May 2022 11:16:33 +0200 Subject: [PATCH 14/24] chore(explain-aggregation): doc types --- .../src/components/pipeline-explain/explain-results.tsx | 7 ++++--- packages/compass-crud/index.d.ts | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx index 0df58191be2..a7cd1c88214 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx @@ -26,7 +26,6 @@ export const ExplainResults: React.FunctionComponent = ({ plan, stats, }) => { - const doc = new HadronDocument(plan); return (
{stats && ( @@ -38,10 +37,12 @@ export const ExplainResults: React.FunctionComponent = ({ )} { - void navigator.clipboard.writeText(doc.toEJSON()); + void navigator.clipboard.writeText( + new HadronDocument(plan).toEJSON() + ); }} /> diff --git a/packages/compass-crud/index.d.ts b/packages/compass-crud/index.d.ts index 6973b5d0578..d5e22c2f551 100644 --- a/packages/compass-crud/index.d.ts +++ b/packages/compass-crud/index.d.ts @@ -1,7 +1,9 @@ import type HadronDocument from "hadron-document"; +type Doc = HadronDocument | Record; + export const Document: React.ComponentClass<{ - doc: HadronDocument; + doc: Doc; editable?: boolean; isTimeSeries?: boolean; removeDocument?: () => void; @@ -12,7 +14,7 @@ export const Document: React.ComponentClass<{ }>; type ListViewProps = { - docs: HadronDocument[]; + docs: Doc[]; isEditable?: boolean; isTimeSeries?: boolean; removeDocument?: (doc: HadronDocument) => void; From f4122d5ca9627ae3ca48821518629d6bbd494508 Mon Sep 17 00:00:00 2001 From: Basit <1305718+mabaasit@users.noreply.github.com> Date: Wed, 18 May 2022 11:25:44 +0200 Subject: [PATCH 15/24] Update packages/compass-aggregations/src/modules/explain.ts Co-authored-by: Sergey Petushkov --- packages/compass-aggregations/src/modules/explain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-aggregations/src/modules/explain.ts b/packages/compass-aggregations/src/modules/explain.ts index e0304ddb677..e9416630f5b 100644 --- a/packages/compass-aggregations/src/modules/explain.ts +++ b/packages/compass-aggregations/src/modules/explain.ts @@ -154,7 +154,7 @@ export const explainAggregation = (): ThunkAction< }); const options: AggregateOptions = { - maxTimeMS: maxTimeMS || DEFAULT_MAX_TIME_MS, + maxTimeMS: maxTimeMS ?? DEFAULT_MAX_TIME_MS, allowDiskUse: true, collation: collation || undefined, }; From 40d668028dcf1084f13f2999e9b1779b79983bb7 Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 18 May 2022 14:22:24 +0200 Subject: [PATCH 16/24] chore(explain-aggregation): fix ux issues --- .../explain-query-performance.tsx | 20 +++++++++++-------- .../pipeline-explain/explain-results.tsx | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx index 9db24a2f338..ed884a9688f 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx @@ -40,14 +40,18 @@ export const ExplainQueryPerformance: React.FunctionComponent Query Performance Summary
-
- Documents returned: - {nReturned} -
-
- Actual query execution time(ms): - {executionTimeMillis} -
+ {nReturned > 0 && ( +
+ Documents returned: + {nReturned} +
+ )} + {executionTimeMillis > 0 && ( +
+ Actual query execution time(ms): + {executionTimeMillis} +
+ )}
Query used the following indexes: diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx index a7cd1c88214..5c478d9b4aa 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx @@ -18,7 +18,7 @@ const containerStyles = css({ }); const cardStyles = css({ - height: '350px', + height: `calc(100vh - 450px)`, overflowY: 'scroll', }); From e35201aee6981522580ff47dbf77e850aa407692 Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 18 May 2022 14:28:31 +0200 Subject: [PATCH 17/24] chore(explain-aggregation): isDataLake --- .../src/modules/explain.ts | 4 ++- .../compass-aggregations/src/modules/index.ts | 6 ++++ .../src/modules/is-datalake.ts | 31 +++++++++++++++++++ .../compass-aggregations/src/stores/store.js | 5 +++ .../src/stores/store.spec.js | 1 + .../src/utils/cancellable-aggregation.ts | 3 +- .../src/modules/is-data-lake.ts | 5 +++ .../compass-collection/src/stores/context.tsx | 8 +++++ .../compass-collection/src/stores/index.ts | 11 ++++--- 9 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 packages/compass-aggregations/src/modules/is-datalake.ts diff --git a/packages/compass-aggregations/src/modules/explain.ts b/packages/compass-aggregations/src/modules/explain.ts index e0304ddb677..c2cf6dbce17 100644 --- a/packages/compass-aggregations/src/modules/explain.ts +++ b/packages/compass-aggregations/src/modules/explain.ts @@ -134,6 +134,7 @@ export const explainAggregation = (): ThunkAction< > => { return async (dispatch, getState) => { const { + isDataLake, pipeline, namespace, maxTimeMS, @@ -164,7 +165,8 @@ export const explainAggregation = (): ThunkAction< signal, namespace, pipeline: pipeline.map(generateStage).filter(x => Object.keys(x).length > 0), - options + options, + isDataLake, }); const explain: ExplainData = { diff --git a/packages/compass-aggregations/src/modules/index.ts b/packages/compass-aggregations/src/modules/index.ts index afecb3e1655..8ce6e35cdd1 100644 --- a/packages/compass-aggregations/src/modules/index.ts +++ b/packages/compass-aggregations/src/modules/index.ts @@ -124,6 +124,10 @@ import explain, { INITIAL_STATE as EXPLAIN_INITIAL_STATE } from './explain'; +import isDataLake, { + INITIAL_STATE as DATALAKE_INITIAL_STATE +} from './is-datalake'; + import workspace, { INITIAL_STATE as WORKSPACE_INITIAL_STATE } from './workspace'; @@ -182,6 +186,7 @@ export const INITIAL_STATE = { workspace: WORKSPACE_INITIAL_STATE, countDocuments: COUNT_INITIAL_STATE, explain: EXPLAIN_INITIAL_STATE, + isDataLake: DATALAKE_INITIAL_STATE, }; /** @@ -263,6 +268,7 @@ const appReducer = combineReducers({ countDocuments, aggregationWorkspaceId, explain, + isDataLake, }); export type RootState = ReturnType; diff --git a/packages/compass-aggregations/src/modules/is-datalake.ts b/packages/compass-aggregations/src/modules/is-datalake.ts new file mode 100644 index 00000000000..17bf08d68cc --- /dev/null +++ b/packages/compass-aggregations/src/modules/is-datalake.ts @@ -0,0 +1,31 @@ +import type { Reducer } from 'redux'; + +enum ActionTypes { + SetDataLake = 'compass-aggregations/setDataLake', +}; + +type SetDataLakeAction = { + type: ActionTypes.SetDataLake; + dataLake: boolean; +}; + +type Actions = SetDataLakeAction; +type State = boolean; + +export const INITIAL_STATE: State = false; + +const reducer: Reducer = (state = INITIAL_STATE, action) => { + switch (action.type) { + case ActionTypes.SetDataLake: + return action.dataLake; + default: + return state; + } +}; + +export const setDataLake = (dataLake: boolean): SetDataLakeAction => ({ + type: ActionTypes.SetDataLake, + dataLake, +}); + +export default reducer; \ No newline at end of file diff --git a/packages/compass-aggregations/src/stores/store.js b/packages/compass-aggregations/src/stores/store.js index 6729e9b9f5c..8c746d0ca0a 100644 --- a/packages/compass-aggregations/src/stores/store.js +++ b/packages/compass-aggregations/src/stores/store.js @@ -20,6 +20,7 @@ import { localAppRegistryActivated, globalAppRegistryActivated } from '@mongodb-js/mongodb-redux-common/app-registry'; +import { setDataLake } from '../modules/is-datalake'; /** * Refresh the input documents. @@ -306,6 +307,10 @@ const configureStore = (options = {}) => { getPipelineFromIndexedDB(options.aggregation.id)(store.dispatch); } + if (options.isDataLake) { + store.dispatch(setDataLake(options.isDataLake)); + } + return store; }; diff --git a/packages/compass-aggregations/src/stores/store.spec.js b/packages/compass-aggregations/src/stores/store.spec.js index 6148f0556a9..83988e80a3b 100644 --- a/packages/compass-aggregations/src/stores/store.spec.js +++ b/packages/compass-aggregations/src/stores/store.spec.js @@ -263,6 +263,7 @@ describe('Aggregation Store', function() { workspace: INITIAL_STATE.workspace, countDocuments: INITIAL_STATE.countDocuments, explain: INITIAL_STATE.explain, + isDataLake: INITIAL_STATE.isDataLake, }); }); }); diff --git a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts index 2ff26c629c6..49bb0666bdb 100644 --- a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts +++ b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts @@ -74,12 +74,14 @@ export async function explainPipeline({ namespace, pipeline, options, + isDataLake, }: { dataService: DataService; signal: AbortSignal; namespace: string; pipeline: Document[]; options: AggregateOptions; + isDataLake: boolean; }): Promise { if (signal.aborted) { return Promise.reject(createCancelError()); @@ -106,7 +108,6 @@ export async function explainPipeline({ signal.addEventListener('abort', abort, { once: true }); let result = {}; try { - const { dataLake: { isDataLake } } = await dataService.instance(); const verbosity = isDataLake ? 'queryPlannerExtended' : 'allPlansExecution'; result = await raceWithAbort(cursor.explain(verbosity), signal); } finally { diff --git a/packages/compass-collection/src/modules/is-data-lake.ts b/packages/compass-collection/src/modules/is-data-lake.ts index 9d584a35fe4..be5b1df5f46 100644 --- a/packages/compass-collection/src/modules/is-data-lake.ts +++ b/packages/compass-collection/src/modules/is-data-lake.ts @@ -32,3 +32,8 @@ export default function reducer( } return state; } + +export const dataLakeChanged = (isDataLake: boolean): AnyAction => ({ + type: IS_DATA_LAKE_CHANGED, + isDataLake, +}); \ No newline at end of file diff --git a/packages/compass-collection/src/stores/context.tsx b/packages/compass-collection/src/stores/context.tsx index 478c422e313..9cba2a3d800 100644 --- a/packages/compass-collection/src/stores/context.tsx +++ b/packages/compass-collection/src/stores/context.tsx @@ -107,6 +107,7 @@ const setupStore = ({ serverVersion, isReadonly, isTimeSeries, + isDataLake, isClustered, isFLE, actions, @@ -129,6 +130,7 @@ const setupStore = ({ serverVersion, isReadonly, isTimeSeries, + isDataLake, isClustered, isFLE, actions: actions, @@ -173,6 +175,7 @@ const setupPlugin = ({ serverVersion, isReadonly, isTimeSeries, + isDataLake, isClustered, isFLE, sourceName, @@ -190,6 +193,7 @@ const setupPlugin = ({ serverVersion, isReadonly, isTimeSeries, + isDataLake, isClustered, isFLE, sourceName, @@ -231,6 +235,7 @@ const setupScopedModals = ({ serverVersion, isReadonly, isTimeSeries, + isDataLake, isClustered, isFLE, sourceName, @@ -249,6 +254,7 @@ const setupScopedModals = ({ serverVersion, isReadonly, isTimeSeries, + isDataLake, isClustered, isFLE, sourceName, @@ -348,6 +354,7 @@ const createContext = ({ serverVersion, isReadonly, isTimeSeries, + isDataLake, isClustered, isFLE, actions, @@ -405,6 +412,7 @@ const createContext = ({ serverVersion, isReadonly, isTimeSeries, + isDataLake, isClustered, isFLE, sourceName, diff --git a/packages/compass-collection/src/stores/index.ts b/packages/compass-collection/src/stores/index.ts index 56833b5aa08..ef4f4d41103 100644 --- a/packages/compass-collection/src/stores/index.ts +++ b/packages/compass-collection/src/stores/index.ts @@ -5,6 +5,7 @@ import { createStore, applyMiddleware } from 'redux'; import type { AnyAction } from 'redux'; import thunk from 'redux-thunk'; import toNS from 'mongodb-ns'; +import type { DataService } from 'mongodb-data-service'; import appRegistry, { appRegistryActivated, @@ -19,6 +20,7 @@ import serverVersion, { INITIAL_STATE as SERVER_VERSION_INITIAL_STATE, } from '../modules/server-version'; import isDataLake, { + dataLakeChanged, INITIAL_STATE as IS_DATA_LAKE_INITIAL_STATE, } from '../modules/is-data-lake'; import stats, { @@ -248,12 +250,13 @@ store.onActivated = (appRegistry: AppRegistry) => { /** * Set the data service in the store when connected. - * - * @param {Error} error - The error. - * @param {DataService} dataService - The data service. */ - appRegistry.on('data-service-connected', (error, dataService) => { + appRegistry.on('data-service-connected', (error, dataService: DataService) => { store.dispatch(dataServiceConnected(error, dataService)); + void dataService.instance() + .then(({ dataLake: { isDataLake } }) => { + store.dispatch(dataLakeChanged(isDataLake)); + }); }); /** From c463a7019a07ea2ccd4829092ba1bd381594da74 Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 18 May 2022 15:05:11 +0200 Subject: [PATCH 18/24] chore(explain-aggregation): explain verbosity --- .../src/utils/cancellable-aggregation.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts index 49bb0666bdb..b71b502fe6f 100644 --- a/packages/compass-aggregations/src/utils/cancellable-aggregation.ts +++ b/packages/compass-aggregations/src/utils/cancellable-aggregation.ts @@ -108,7 +108,14 @@ export async function explainPipeline({ signal.addEventListener('abort', abort, { once: true }); let result = {}; try { - const verbosity = isDataLake ? 'queryPlannerExtended' : 'allPlansExecution'; + const lastStage = pipeline[pipeline.length - 1] ?? {}; + const isOutOrMergePipeline = + Object.prototype.hasOwnProperty.call(lastStage, '$out') || + Object.prototype.hasOwnProperty.call(lastStage, '$merge'); + const verbosity = isDataLake + ? 'queryPlannerExtended' + : isOutOrMergePipeline ? 'queryPlanner' // $out & $merge only work with queryPlanner + : 'allPlansExecution'; result = await raceWithAbort(cursor.explain(verbosity), signal); } finally { signal.removeEventListener('abort', abort); From 2e1c5982cca3da27007427b4eab25698fd9281d8 Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 18 May 2022 15:45:08 +0200 Subject: [PATCH 19/24] chore(explain-aggregation): lint --- .../compass-collection/src/modules/is-data-lake.ts | 2 +- packages/compass-collection/src/stores/index.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/compass-collection/src/modules/is-data-lake.ts b/packages/compass-collection/src/modules/is-data-lake.ts index be5b1df5f46..297385ce953 100644 --- a/packages/compass-collection/src/modules/is-data-lake.ts +++ b/packages/compass-collection/src/modules/is-data-lake.ts @@ -36,4 +36,4 @@ export default function reducer( export const dataLakeChanged = (isDataLake: boolean): AnyAction => ({ type: IS_DATA_LAKE_CHANGED, isDataLake, -}); \ No newline at end of file +}); diff --git a/packages/compass-collection/src/stores/index.ts b/packages/compass-collection/src/stores/index.ts index ef4f4d41103..8794e72c0e1 100644 --- a/packages/compass-collection/src/stores/index.ts +++ b/packages/compass-collection/src/stores/index.ts @@ -251,13 +251,15 @@ store.onActivated = (appRegistry: AppRegistry) => { /** * Set the data service in the store when connected. */ - appRegistry.on('data-service-connected', (error, dataService: DataService) => { - store.dispatch(dataServiceConnected(error, dataService)); - void dataService.instance() - .then(({ dataLake: { isDataLake } }) => { + appRegistry.on( + 'data-service-connected', + (error, dataService: DataService) => { + store.dispatch(dataServiceConnected(error, dataService)); + void dataService.instance().then(({ dataLake: { isDataLake } }) => { store.dispatch(dataLakeChanged(isDataLake)); }); - }); + } + ); /** * When the instance is loaded, set our server version. From 3845394f0b0f9df8214fdd127199566d508bbe19 Mon Sep 17 00:00:00 2001 From: Basit Date: Thu, 19 May 2022 09:38:18 +0200 Subject: [PATCH 20/24] chore(explain-aggregation): better instance handling --- .../pipeline-explain/explain-query-performance.tsx | 2 +- .../src/components/pipeline-explain/explain-results.tsx | 4 +++- packages/compass-collection/src/stores/index.ts | 9 ++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx index ed884a9688f..a24b1511696 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx @@ -40,7 +40,7 @@ export const ExplainQueryPerformance: React.FunctionComponent Query Performance Summary
- {nReturned > 0 && ( + {nReturned && (
Documents returned: {nReturned} diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx index 5c478d9b4aa..61ab80ac1a8 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-results.tsx @@ -18,7 +18,9 @@ const containerStyles = css({ }); const cardStyles = css({ - height: `calc(100vh - 450px)`, + // 170px works with minimum-height of compass + // todo: handle height for bigger sized compass + height: '170px', overflowY: 'scroll', }); diff --git a/packages/compass-collection/src/stores/index.ts b/packages/compass-collection/src/stores/index.ts index 8794e72c0e1..29ce8a96043 100644 --- a/packages/compass-collection/src/stores/index.ts +++ b/packages/compass-collection/src/stores/index.ts @@ -143,6 +143,12 @@ store.onActivated = (appRegistry: AppRegistry) => { } } ); + instance.dataLake.on( + 'change:isDataLake', + (_model: unknown, value: boolean) => { + store.dispatch(dataLakeChanged(value)); + } + ); } }); @@ -255,9 +261,6 @@ store.onActivated = (appRegistry: AppRegistry) => { 'data-service-connected', (error, dataService: DataService) => { store.dispatch(dataServiceConnected(error, dataService)); - void dataService.instance().then(({ dataLake: { isDataLake } }) => { - store.dispatch(dataLakeChanged(isDataLake)); - }); } ); From 78b7448fc7a4539e6088ee9832141f8fb7656545 Mon Sep 17 00:00:00 2001 From: Basit <1305718+mabaasit@users.noreply.github.com> Date: Thu, 19 May 2022 10:49:18 +0200 Subject: [PATCH 21/24] Update packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx Co-authored-by: Sergey Petushkov --- .../components/pipeline-explain/explain-query-performance.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx b/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx index a24b1511696..bf55d8ee8d0 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/explain-query-performance.tsx @@ -40,7 +40,7 @@ export const ExplainQueryPerformance: React.FunctionComponent Query Performance Summary
- {nReturned && ( + {typeof nReturned === 'number' && (
Documents returned: {nReturned} From dd83d1cba0f4ce3d1dafa9c903554c4943940ac3 Mon Sep 17 00:00:00 2001 From: Basit Date: Thu, 19 May 2022 13:04:33 +0200 Subject: [PATCH 22/24] chore(explain-aggregation): ts fix --- .../src/components/pipeline-explain/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-aggregations/src/components/pipeline-explain/index.tsx b/packages/compass-aggregations/src/components/pipeline-explain/index.tsx index 463e6fb4a73..c4f0db32c95 100644 --- a/packages/compass-aggregations/src/components/pipeline-explain/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-explain/index.tsx @@ -47,7 +47,7 @@ export const PipelineExplain: React.FunctionComponent = ({ if (isLoading) { content = ( onCancelExplain()} progressText="Running explain" From 4e34ab28eb1d2c27a8a9a0206916e6965321da30 Mon Sep 17 00:00:00 2001 From: Basit Date: Thu, 19 May 2022 14:28:26 +0200 Subject: [PATCH 23/24] feat(data-service): wait for explain modal --- .../compass-e2e-tests/tests/collection-aggregations-tab.test.ts | 2 ++ 1 file changed, 2 insertions(+) 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 ecf155ef9d3..80824ed57f0 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -549,6 +549,8 @@ describe('Collection aggregations tab', function () { await browser.waitForAnimations(Selectors.AggregationExplainModal); const modal = await browser.$(Selectors.AggregationExplainModal); + await browser.waitForAnimations(Selectors.AggregationExplainModal); + await modal.waitForDisplayed(); expect(await modal.getText()).to.contain('Query Performance Summary'); }); }); From f407c7a0dfdcb1bd812122423a5dfe91905a2390 Mon Sep 17 00:00:00 2001 From: Basit Date: Thu, 19 May 2022 15:55:03 +0200 Subject: [PATCH 24/24] chore(explain-aggregation): test fix --- .../compass-e2e-tests/tests/collection-aggregations-tab.test.ts | 2 +- 1 file changed, 1 insertion(+), 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 80824ed57f0..6fb5be8602f 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -549,8 +549,8 @@ describe('Collection aggregations tab', function () { await browser.waitForAnimations(Selectors.AggregationExplainModal); const modal = await browser.$(Selectors.AggregationExplainModal); - await browser.waitForAnimations(Selectors.AggregationExplainModal); await modal.waitForDisplayed(); + await browser.waitForAnimations(Selectors.AggregationExplainModal); expect(await modal.getText()).to.contain('Query Performance Summary'); }); });