Skip to content

Commit 44c7139

Browse files
mabaasitAnemy
andauthored
feat(explain-aggregation): add explain button in toolbar COMPASS-5787 (#3091)
Co-authored-by: Rhys <[email protected]>
1 parent d60b02f commit 44c7139

File tree

8 files changed

+164
-4
lines changed

8 files changed

+164
-4
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import { Modal, H3, Body } from '@mongodb-js/compass-components';
3+
import { connect } from 'react-redux';
4+
5+
import type { RootState } from '../../modules';
6+
import { closeExplainModal } from '../../modules/explain';
7+
8+
type PipelineExplainProps = {
9+
isModalOpen: boolean;
10+
onCloseModal: () => void;
11+
};
12+
13+
export const PipelineExplain: React.FunctionComponent<PipelineExplainProps> = ({
14+
isModalOpen,
15+
onCloseModal,
16+
}) => {
17+
return (
18+
<Modal
19+
setOpen={onCloseModal}
20+
open={isModalOpen}
21+
data-testid="pipeline-explain-modal"
22+
>
23+
<H3>Explain</H3>
24+
<Body>Implementation in progress ...</Body>
25+
</Modal>
26+
);
27+
};
28+
29+
const mapState = ({ explain: { isModalOpen } }: RootState) => ({
30+
isModalOpen: isModalOpen,
31+
});
32+
33+
const mapDispatch = {
34+
onCloseModal: closeExplainModal,
35+
};
36+
export default connect(mapState, mapDispatch)(PipelineExplain);

packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.spec.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,21 @@ import type { SinonSpy } from 'sinon';
88
import { PipelineActions } from './pipeline-actions';
99

1010
const initialEnableExport = process.env.COMPASS_ENABLE_AGGREGATION_EXPORT;
11+
const initialEnableExplain = process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN;
1112

1213
describe('PipelineActions', function () {
1314
describe('options visible', function () {
1415
let onRunAggregationSpy: SinonSpy;
1516
let onToggleOptionsSpy: SinonSpy;
1617
let onExportAggregationResultsSpy: SinonSpy;
18+
let onExplainAggregationSpy: SinonSpy;
1719
beforeEach(function () {
1820
process.env.COMPASS_ENABLE_AGGREGATION_EXPORT = 'true';
21+
process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = 'true';
1922
onRunAggregationSpy = spy();
2023
onToggleOptionsSpy = spy();
2124
onExportAggregationResultsSpy = spy();
25+
onExplainAggregationSpy = spy();
2226
render(
2327
<PipelineActions
2428
isOptionsVisible={true}
@@ -27,12 +31,16 @@ describe('PipelineActions', function () {
2731
onRunAggregation={onRunAggregationSpy}
2832
onToggleOptions={onToggleOptionsSpy}
2933
onExportAggregationResults={onExportAggregationResultsSpy}
34+
isExplainButtonDisabled={false}
35+
onExplainAggregation={onExplainAggregationSpy}
36+
onUpdateView={() => {}}
3037
/>
3138
);
3239
});
3340

3441
afterEach(function () {
3542
process.env.COMPASS_ENABLE_AGGREGATION_EXPORT = initialEnableExport;
43+
process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = initialEnableExplain;
3644
});
3745

3846
it('calls onRunAggregation callback on click', function () {
@@ -55,6 +63,15 @@ describe('PipelineActions', function () {
5563
expect(onExportAggregationResultsSpy.calledOnce).to.be.true;
5664
});
5765

66+
it('calls onExplainAggregation on click', function () {
67+
const button = screen.getByTestId(
68+
'pipeline-toolbar-explain-aggregation-button'
69+
);
70+
expect(button).to.exist;
71+
userEvent.click(button);
72+
expect(onExplainAggregationSpy.calledOnce).to.be.true;
73+
});
74+
5875
it('calls onToggleOptions on click', function () {
5976
const button = screen.getByTestId('pipeline-toolbar-options-button');
6077
expect(button).to.exist;
@@ -81,6 +98,8 @@ describe('PipelineActions', function () {
8198
onRunAggregation={onRunAggregationSpy}
8299
onToggleOptions={onToggleOptionsSpy}
83100
onExportAggregationResults={() => {}}
101+
onUpdateView={() => {}}
102+
onExplainAggregation={() => {}}
84103
/>
85104
);
86105
});
@@ -100,12 +119,16 @@ describe('PipelineActions', function () {
100119
describe('disables actions when pipeline is invalid', function () {
101120
let onRunAggregationSpy: SinonSpy;
102121
let onExportAggregationResultsSpy: SinonSpy;
122+
let onExplainAggregationSpy: SinonSpy;
103123
beforeEach(function () {
104124
process.env.COMPASS_ENABLE_AGGREGATION_EXPORT = 'true';
125+
process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = 'true';
105126
onRunAggregationSpy = spy();
106127
onExportAggregationResultsSpy = spy();
128+
onExplainAggregationSpy = spy();
107129
render(
108130
<PipelineActions
131+
isExplainButtonDisabled={true}
109132
isExportButtonDisabled={true}
110133
isRunButtonDisabled={true}
111134
isOptionsVisible={true}
@@ -114,12 +137,15 @@ describe('PipelineActions', function () {
114137
onRunAggregation={onRunAggregationSpy}
115138
onToggleOptions={() => {}}
116139
onExportAggregationResults={onExportAggregationResultsSpy}
140+
onExplainAggregation={onExplainAggregationSpy}
141+
onUpdateView={() => {}}
117142
/>
118143
);
119144
});
120145

121146
afterEach(function () {
122147
process.env.COMPASS_ENABLE_AGGREGATION_EXPORT = initialEnableExport;
148+
process.env.COMPASS_ENABLE_AGGREGATION_EXPLAIN = initialEnableExplain;
123149
});
124150

125151
it('run action disabled', function () {
@@ -143,5 +169,17 @@ describe('PipelineActions', function () {
143169
});
144170
expect(onExportAggregationResultsSpy.calledOnce).to.be.false;
145171
});
172+
173+
it('explain action disabled', function () {
174+
const button = screen.getByTestId(
175+
'pipeline-toolbar-explain-aggregation-button'
176+
);
177+
expect(button.getAttribute('disabled')).to.exist;
178+
179+
userEvent.click(button, undefined, {
180+
skipPointerEventsCheck: true,
181+
});
182+
expect(onExplainAggregationSpy.calledOnce).to.be.false;
183+
});
146184
});
147185
});

packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from '../../../modules/aggregation';
1616
import { isEmptyishStage } from '../../../modules/stage';
1717
import { updateView } from '../../../modules/update-view';
18+
import { openExplainModal } from '../../../modules/explain';
1819

1920
const containerStyles = css({
2021
display: 'flex',
@@ -51,6 +52,9 @@ type PipelineActionsProps = {
5152
isUpdateViewButtonDisabled?: boolean;
5253
onUpdateView: () => void;
5354

55+
isExplainButtonDisabled?: boolean;
56+
onExplainAggregation: () => void;
57+
5458
isOptionsVisible?: boolean;
5559
onToggleOptions: () => void;
5660
};
@@ -63,22 +67,26 @@ export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
6367
isExportButtonDisabled,
6468
showUpdateViewButton,
6569
isUpdateViewButtonDisabled,
70+
isExplainButtonDisabled,
6671
onUpdateView,
6772
onRunAggregation,
6873
onToggleOptions,
6974
onExportAggregationResults,
75+
onExplainAggregation,
7076
}) => {
7177
const optionsIcon = isOptionsVisible ? 'CaretDown' : 'CaretRight';
7278
const showExportButton =
7379
process?.env?.COMPASS_ENABLE_AGGREGATION_EXPORT === 'true' &&
7480
_showExportButton;
81+
const showExplainButton =
82+
process?.env?.COMPASS_ENABLE_AGGREGATION_EXPLAIN === 'true';
7583
const optionsLabel = isOptionsVisible ? 'Less Options' : 'More Options';
7684
return (
7785
<div className={containerStyles}>
7886
{showUpdateViewButton && (
7987
<Button
8088
aria-label="Update view"
81-
data-testid="pipeline-toolbar-export-aggregation-button"
89+
data-testid="pipeline-toolbar-update-view-aggregation-button"
8290
variant="primary"
8391
size="small"
8492
onClick={onUpdateView}
@@ -87,6 +95,18 @@ export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
8795
Update view
8896
</Button>
8997
)}
98+
{showExplainButton && (
99+
<Button
100+
aria-label="Explain aggregation"
101+
data-testid="pipeline-toolbar-explain-aggregation-button"
102+
variant="default"
103+
size="small"
104+
onClick={onExplainAggregation}
105+
disabled={isExplainButtonDisabled}
106+
>
107+
Explain
108+
</Button>
109+
)}
90110
{!showUpdateViewButton && showExportButton && (
91111
<Button
92112
aria-label="Export aggregation"
@@ -146,6 +166,7 @@ const mapState = ({ pipeline, editViewName, isModified }: RootState) => {
146166

147167
return {
148168
isRunButtonDisabled: isPipelineInvalid || isStageStateEmpty,
169+
isExplainButtonDisabled: isPipelineInvalid,
149170
isExportButtonDisabled:
150171
isMergeOrOutPipeline || isPipelineInvalid || isStageStateEmpty,
151172
showUpdateViewButton: Boolean(editViewName),
@@ -158,6 +179,7 @@ const mapDispatch = {
158179
onUpdateView: updateView,
159180
onRunAggregation: runAggregation,
160181
onExportAggregationResults: exportAggregationResults,
182+
onExplainAggregation: openExplainModal,
161183
};
162184

163185
export default connect(mapState, mapDispatch)(PipelineActions);

packages/compass-aggregations/src/components/pipeline/pipeline.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import ConfirmNewPipeline from './modals/confirm-new-pipeline';
1818
import styles from './pipeline.module.less';
1919

2020
import PipelineToolbar from '../pipeline-toolbar';
21+
import PipelineExplain from '../pipeline-explain';
2122
import PipelineBuilderWorkspace from '../pipeline-builder-workspace';
2223
import PipelineResultsWorkspace from '../pipeline-results-workspace';
2324
import {
@@ -126,7 +127,7 @@ class Pipeline extends PureComponent {
126127
toggleInputDocumentsCollapsed: PropTypes.func.isRequired,
127128
refreshInputDocuments: PropTypes.func.isRequired,
128129
showExportButton: PropTypes.bool.isRequired,
129-
showRunButton: PropTypes.bool.isRequired
130+
showRunButton: PropTypes.bool.isRequired,
130131
};
131132

132133
static defaultProps = {
@@ -362,6 +363,9 @@ class Pipeline extends PureComponent {
362363
{this.renderModifyingViewSourceError()}
363364
{this.renderPipelineWorkspace()}
364365
{this.renderSavePipeline()}
366+
{process?.env?.COMPASS_ENABLE_AGGREGATION_EXPLAIN === 'true' && (
367+
<PipelineExplain />
368+
)}
365369
<Settings
366370
isAtlasDeployed={this.props.isAtlasDeployed}
367371
isExpanded={this.props.settings.isExpanded}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Reducer } from 'redux';
2+
3+
export enum ActionTypes {
4+
ModalOpened = 'compass-aggregations/modalOpened',
5+
ModalClosed = 'compass-aggregations/modalClosed',
6+
}
7+
8+
type ModalOpenedAction = {
9+
type: ActionTypes.ModalOpened;
10+
};
11+
12+
type ModalClosedAction = {
13+
type: ActionTypes.ModalClosed;
14+
};
15+
16+
export type Actions =
17+
| ModalOpenedAction
18+
| ModalClosedAction;
19+
20+
export type State = {
21+
isModalOpen: boolean;
22+
};
23+
24+
export const INITIAL_STATE: State = {
25+
isModalOpen: false,
26+
};
27+
28+
const reducer: Reducer<State, Actions> = (state = INITIAL_STATE, action) => {
29+
switch (action.type) {
30+
case ActionTypes.ModalOpened:
31+
return {
32+
isModalOpen: true,
33+
};
34+
case ActionTypes.ModalClosed:
35+
return {
36+
isModalOpen: false,
37+
};
38+
default:
39+
return state;
40+
}
41+
};
42+
43+
export const openExplainModal = (): ModalOpenedAction => ({
44+
type: ActionTypes.ModalOpened,
45+
});
46+
47+
export const closeExplainModal = (): ModalClosedAction => ({
48+
type: ActionTypes.ModalClosed,
49+
});
50+
51+
export default reducer;

packages/compass-aggregations/src/modules/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ import aggregation, {
119119
import countDocuments, {
120120
INITIAL_STATE as COUNT_INITIAL_STATE
121121
} from './count-documents';
122+
123+
import explain, {
124+
INITIAL_STATE as EXPLAIN_INITIAL_STATE
125+
} from './explain';
126+
122127
import workspace, {
123128
INITIAL_STATE as WORKSPACE_INITIAL_STATE
124129
} from './workspace';
@@ -176,6 +181,7 @@ export const INITIAL_STATE = {
176181
aggregation: AGGREGATION_INITIAL_STATE,
177182
workspace: WORKSPACE_INITIAL_STATE,
178183
countDocuments: COUNT_INITIAL_STATE,
184+
explain: EXPLAIN_INITIAL_STATE,
179185
};
180186

181187
/**
@@ -255,7 +261,8 @@ const appReducer = combineReducers({
255261
aggregation,
256262
workspace,
257263
countDocuments,
258-
aggregationWorkspaceId
264+
aggregationWorkspaceId,
265+
explain,
259266
});
260267

261268
export type RootState = ReturnType<typeof appReducer>;
@@ -291,7 +298,7 @@ const doNamespaceChanged = (state: RootState, action: AnyAction) => {
291298
*
292299
* @returns {Object} The new state.
293300
*/
294-
const doReset= (state: RootState) => ({
301+
const doReset = (state: RootState) => ({
295302
...INITIAL_STATE,
296303
aggregationWorkspaceId: state.aggregationWorkspaceId
297304
});

packages/compass-aggregations/src/stores/store.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ describe('Aggregation Store', function() {
262262
aggregation: INITIAL_STATE.aggregation,
263263
workspace: INITIAL_STATE.workspace,
264264
countDocuments: INITIAL_STATE.countDocuments,
265+
explain: INITIAL_STATE.explain,
265266
});
266267
});
267268
});

packages/compass/src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ declare module 'process' {
5151
*/
5252
COMPASS_SHOW_NEW_AGGREGATION_TOOLBAR?: 'true';
5353
COMPASS_ENABLE_AGGREGATION_EXPORT?: 'true' | 'false';
54+
COMPASS_ENABLE_AGGREGATION_EXPLAIN?: 'true' | 'false';
5455

5556
/**
5657
* Permanent feature flag for debugging.

0 commit comments

Comments
 (0)