Skip to content

Commit 0420416

Browse files
authored
feat(search-indexes): add search index signals COMPASS-7176 (#4972)
1 parent 80ce9c1 commit 0420416

File tree

18 files changed

+252
-107
lines changed

18 files changed

+252
-107
lines changed

packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.spec.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ describe('FocusModeModalHeader', function () {
2626
return render(
2727
<FocusModeModalHeader
2828
isEnabled
29+
env={'on-prem'}
30+
isSearchIndexesSupported={false}
31+
onCreateSearchIndex={noop}
32+
stage={{} as any}
2933
stageIndex={0}
3034
stages={[
3135
{

packages/compass-aggregations/src/components/focus-mode/focus-mode-modal-header.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import {
1414
formatHotkey,
1515
SignalPopover,
1616
} from '@mongodb-js/compass-components';
17-
import type { Signal } from '@mongodb-js/compass-components';
18-
import React, { useState } from 'react';
17+
import React, { useMemo, useState } from 'react';
1918
import { connect } from 'react-redux';
2019
import type { RootState } from '../../modules';
2120
import {
@@ -26,6 +25,8 @@ import { changeStageDisabled } from '../../modules/pipeline-builder/stage-editor
2625
import type { StoreStage } from '../../modules/pipeline-builder/stage-editor';
2726
import { getInsightForStage } from '../../utils/insights';
2827
import { usePreference } from 'compass-preferences-model';
28+
import { createSearchIndex } from '../../modules/search-indexes';
29+
import type { ServerEnvironment } from '../../modules/env';
2930

3031
type Stage = {
3132
idxInStore: number;
@@ -36,7 +37,10 @@ type FocusModeModalHeaderProps = {
3637
stageIndex: number;
3738
isEnabled: boolean;
3839
stages: Stage[];
39-
insight?: Signal;
40+
stage?: StoreStage;
41+
env: ServerEnvironment;
42+
isSearchIndexesSupported: boolean;
43+
onCreateSearchIndex: () => void;
4044
onStageSelect: (index: number) => void;
4145
onStageDisabledToggleClick: (index: number, newVal: boolean) => void;
4246
onAddStageClick: (index: number) => void;
@@ -93,15 +97,29 @@ export const FocusModeModalHeader: React.FunctionComponent<
9397
> = ({
9498
stageIndex,
9599
isEnabled,
96-
insight,
97100
stages,
101+
env,
102+
isSearchIndexesSupported,
103+
stage,
104+
onCreateSearchIndex,
98105
onAddStageClick,
99106
onStageSelect,
100107
onStageDisabledToggleClick,
101108
}) => {
102109
const [menuOpen, setMenuOpen] = useState(false);
103110
const showInsights = usePreference('showInsights', React);
104111

112+
const insight = useMemo(() => {
113+
if (stage) {
114+
return getInsightForStage(
115+
stage,
116+
env,
117+
isSearchIndexesSupported,
118+
onCreateSearchIndex
119+
);
120+
}
121+
}, [stage, env, isSearchIndexesSupported, onCreateSearchIndex]);
122+
105123
const isFirst = stages[0].idxInStore === stageIndex;
106124
const isLast = stages[stages.length - 1].idxInStore === stageIndex;
107125

@@ -322,13 +340,16 @@ export default connect(
322340
pipelineBuilder: {
323341
stageEditor: { stages },
324342
},
343+
searchIndexes: { isSearchIndexesSupported },
325344
} = state;
326345
const stage = stages[stageIndex] as StoreStage;
327346

328347
return {
329348
stageIndex,
330349
isEnabled: !stage?.disabled,
331-
insight: stage ? getInsightForStage(stage, env) : undefined,
350+
stage,
351+
env,
352+
isSearchIndexesSupported,
332353
stages: stages.reduce<Stage[]>((accumulator, stage, idxInStore) => {
333354
if (stage.type === 'stage') {
334355
accumulator.push({
@@ -344,5 +365,6 @@ export default connect(
344365
onStageSelect: selectFocusModeStage,
345366
onStageDisabledToggleClick: changeStageDisabled,
346367
onAddStageClick: addStageInFocusMode,
368+
onCreateSearchIndex: createSearchIndex,
347369
}
348370
)(FocusModeModalHeader);
Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,46 @@
11
import React from 'react';
2-
import type { ComponentProps } from 'react';
32
import { render, screen } from '@testing-library/react';
43
import { expect } from 'chai';
54
import { Provider } from 'react-redux';
6-
import sinon from 'sinon';
75

86
import configureStore from '../../../test/configure-store';
9-
import { FocusMode } from './focus-mode';
7+
import FocusMode from './focus-mode';
8+
import { disableFocusMode, enableFocusMode } from '../../modules/focus-mode';
109

11-
const renderFocusMode = (
12-
props: Partial<ComponentProps<typeof FocusMode>> = {}
13-
) => {
10+
const renderFocusMode = () => {
11+
const store = configureStore({
12+
pipeline: [{ $match: { _id: 1 } }, { $limit: 10 }, { $out: 'out' }],
13+
});
1414
render(
15-
<Provider
16-
store={configureStore({
17-
pipeline: [{ $match: { _id: 1 } }, { $limit: 10 }, { $out: 'out' }],
18-
})}
19-
>
20-
<FocusMode
21-
isModalOpen={true}
22-
isAutoPreviewEnabled={true}
23-
onCloseModal={() => {}}
24-
{...props}
25-
/>
15+
<Provider store={store}>
16+
<FocusMode />
2617
</Provider>
2718
);
19+
return store;
2820
};
2921

3022
describe('FocusMode', function () {
3123
it('does not show modal when closed', function () {
32-
renderFocusMode({ isModalOpen: false });
24+
const store = renderFocusMode();
25+
store.dispatch(disableFocusMode());
3326
expect(() => {
3427
screen.getByTestId('focus-mode-modal');
3528
}).to.throw;
3629
});
3730

3831
it('shows modal when open', function () {
39-
renderFocusMode({ isModalOpen: true });
32+
const store = renderFocusMode();
33+
store.dispatch(enableFocusMode(0));
4034
expect(screen.getByTestId('focus-mode-modal')).to.exist;
4135
});
4236

43-
it('calls onCloseModal when close button is clicked', function () {
44-
const onCloseModal = sinon.spy();
45-
renderFocusMode({ onCloseModal, isModalOpen: true });
46-
47-
expect(onCloseModal).to.not.have.been.called;
37+
it('hides modal when close button is clicked', function () {
38+
const store = renderFocusMode();
39+
store.dispatch(enableFocusMode(0));
4840
screen.getByLabelText(/close modal/i).click();
49-
expect(onCloseModal).to.have.been.calledOnce;
41+
42+
expect(() => {
43+
screen.getByTestId('focus-mode-modal');
44+
}).to.throw;
5045
});
5146
});

packages/compass-aggregations/src/components/stage-toolbar/index.spec.tsx

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
11
import React from 'react';
2-
import type { ComponentProps } from 'react';
32
import { render, screen } from '@testing-library/react';
43
import { expect } from 'chai';
54
import { Provider } from 'react-redux';
65

76
import configureStore from '../../../test/configure-store';
8-
import { StageToolbar } from './';
7+
import StageToolbar from './';
8+
import {
9+
changeStageCollapsed,
10+
changeStageDisabled,
11+
} from '../../modules/pipeline-builder/stage-editor';
912

10-
const renderStageToolbar = (
11-
props: Partial<ComponentProps<typeof StageToolbar>> = {}
12-
) => {
13+
const renderStageToolbar = () => {
14+
const store = configureStore({
15+
pipeline: [{ $match: { _id: 1 } }, { $limit: 10 }, { $out: 'out' }],
16+
});
1317
render(
14-
<Provider
15-
store={configureStore({
16-
pipeline: [{ $match: { _id: 1 } }, { $limit: 10 }, { $out: 'out' }],
17-
})}
18-
>
19-
<StageToolbar
20-
hasServerError={false}
21-
hasSyntaxError={false}
22-
index={0}
23-
idxInPipeline={0}
24-
isAutoPreviewing={false}
25-
isCollapsed={false}
26-
isDisabled={false}
27-
onOpenFocusMode={() => {}}
28-
{...props}
29-
/>
18+
<Provider store={store}>
19+
<StageToolbar index={0} />
3020
</Provider>
3121
);
22+
return store;
3223
};
3324

3425
describe('StageToolbar', function () {
@@ -50,13 +41,15 @@ describe('StageToolbar', function () {
5041
});
5142
context('renders stage text', function () {
5243
it('when stage is disabled', function () {
53-
renderStageToolbar({ isDisabled: true });
44+
const store = renderStageToolbar();
45+
store.dispatch(changeStageDisabled(0, true));
5446
expect(
5547
screen.getByText('Stage disabled. Results not passed in the pipeline.')
5648
).to.exist;
5749
});
5850
it('when stage is collapsed', function () {
59-
renderStageToolbar({ isCollapsed: true });
51+
const store = renderStageToolbar();
52+
store.dispatch(changeStageCollapsed(0, true));
6053
expect(
6154
screen.getByText(
6255
'A sample of the aggregated results from this stage will be shown below.'

packages/compass-aggregations/src/components/stage-toolbar/index.tsx

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22
import { connect } from 'react-redux';
33
import {
44
Icon,
@@ -11,7 +11,6 @@ import {
1111
IconButton,
1212
SignalPopover,
1313
} from '@mongodb-js/compass-components';
14-
import type { Signal } from '@mongodb-js/compass-components';
1514
import type { RootState } from '../../modules';
1615
import ToggleStage from './toggle-stage';
1716
import StageCollapser from './stage-collapser';
@@ -22,6 +21,8 @@ import OptionMenu from './option-menu';
2221
import type { StoreStage } from '../../modules/pipeline-builder/stage-editor';
2322
import { getInsightForStage } from '../../utils/insights';
2423
import { usePreference } from 'compass-preferences-model';
24+
import type { ServerEnvironment } from '../../modules/env';
25+
import { createSearchIndex } from '../../modules/search-indexes';
2526

2627
const toolbarStyles = css({
2728
width: '100%',
@@ -92,13 +93,12 @@ const rightStyles = css({
9293

9394
type StageToolbarProps = {
9495
index: number;
95-
idxInPipeline: number;
96-
isAutoPreviewing?: boolean;
97-
hasSyntaxError?: boolean;
98-
hasServerError?: boolean;
99-
isCollapsed?: boolean;
100-
isDisabled?: boolean;
101-
insight?: Signal;
96+
97+
stage: StoreStage;
98+
env: ServerEnvironment;
99+
isSearchIndexesSupported: boolean;
100+
onCreateSearchIndex: () => void;
101+
102102
onOpenFocusMode: (index: number) => void;
103103
onStageOperatorChange?: (
104104
index: number,
@@ -113,40 +113,53 @@ const COLLAPSED_TEXT =
113113

114114
export function StageToolbar({
115115
index,
116-
idxInPipeline,
117-
hasSyntaxError,
118-
hasServerError,
119-
isCollapsed,
120-
isDisabled,
121-
insight,
116+
stage,
117+
env,
118+
isSearchIndexesSupported,
119+
onCreateSearchIndex,
122120
onOpenFocusMode,
123121
onStageOperatorChange,
124122
}: StageToolbarProps) {
125123
const showInsights = usePreference('showInsights', React);
126124
const darkMode = useDarkMode();
127125

126+
const insight = useMemo(
127+
() =>
128+
getInsightForStage(
129+
stage,
130+
env,
131+
isSearchIndexesSupported,
132+
onCreateSearchIndex
133+
),
134+
[stage, env, isSearchIndexesSupported, onCreateSearchIndex]
135+
);
136+
128137
return (
129138
<div
130139
className={cx(
131140
'stage-editor-toolbar',
132141
toolbarStyles,
133142
darkMode ? toolbarStylesDark : toolbarStylesLight,
134-
hasSyntaxError && toolbarWarningStyles,
135-
hasServerError && toolbarErrorStyles,
136-
isCollapsed && collapsedToolbarStyles
143+
hasSyntaxError(stage) && toolbarWarningStyles,
144+
!!stage.serverError && toolbarErrorStyles,
145+
stage.collapsed && collapsedToolbarStyles
137146
)}
138147
>
139148
<div className={leftStyles}>
140149
<div className={shortSpacedStyles}>
141150
<StageCollapser index={index} />
142-
<Body weight="medium">Stage {idxInPipeline + 1}</Body>
151+
<Body weight="medium">Stage {stage.idxInPipeline + 1}</Body>
143152
<StageOperatorSelect onChange={onStageOperatorChange} index={index} />
144153
</div>
145154
<ToggleStage index={index} />
146155
{showInsights && insight && <SignalPopover signals={insight} />}
147156
</div>
148157
<div className={textStyles}>
149-
{isDisabled ? DISABLED_TEXT : isCollapsed ? COLLAPSED_TEXT : null}
158+
{stage.disabled
159+
? DISABLED_TEXT
160+
: stage.collapsed
161+
? COLLAPSED_TEXT
162+
: null}
150163
</div>
151164
<div className={rightStyles}>
152165
<IconButton
@@ -173,19 +186,17 @@ export default connect(
173186
pipelineBuilder: {
174187
stageEditor: { stages },
175188
},
189+
searchIndexes: { isSearchIndexesSupported },
176190
} = state;
177191
const stage = stages[ownProps.index] as StoreStage;
178192
return {
179-
idxInPipeline: stage.idxInPipeline,
180-
isAutoPreviewing: !!state.autoPreview,
181-
hasSyntaxError: hasSyntaxError(stage),
182-
hasServerError: !!stage.serverError,
183-
isCollapsed: stage.collapsed,
184-
isDisabled: stage.disabled,
185-
insight: getInsightForStage(stage, env),
193+
stage,
194+
env,
195+
isSearchIndexesSupported,
186196
};
187197
},
188198
{
189199
onOpenFocusMode: enableFocusMode,
200+
onCreateSearchIndex: createSearchIndex,
190201
}
191202
)(StageToolbar);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import focusMode from './focus-mode';
4040
import sidePanel from './side-panel';
4141
import collectionsFields from './collections-fields';
4242
import insights from './insights';
43+
import searchIndexes from './search-indexes';
4344

4445
/**
4546
* The main application reducer.
@@ -84,6 +85,7 @@ const rootReducer = combineReducers({
8485
sidePanel,
8586
collectionsFields,
8687
insights,
88+
searchIndexes,
8789
});
8890

8991
export type RootState = ReturnType<typeof rootReducer>;

0 commit comments

Comments
 (0)