Skip to content

feat(compass-aggregations): add confirm banner for edit pipeline and new banners COMPASS-9700 #7198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Aug 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f5f1030
add confirmation banner
DarshanaVenkatesh Aug 18, 2025
acf5e6b
Merge branch 'main' into COMPASS-9700
DarshanaVenkatesh Aug 18, 2025
fe7c781
add version incompatible banner
DarshanaVenkatesh Aug 18, 2025
fbf8f07
add disabled tooltip
DarshanaVenkatesh Aug 19, 2025
80ba891
fix incorrect readonly
DarshanaVenkatesh Aug 19, 2025
fd085b6
add tests
DarshanaVenkatesh Aug 19, 2025
d9a9171
Merge branch 'main' into COMPASS-9700
DarshanaVenkatesh Aug 19, 2025
894cb50
add missing default params
DarshanaVenkatesh Aug 19, 2025
d99f756
move isPipelineSearchQueryable into compass-utils and update dependen…
DarshanaVenkatesh Aug 19, 2025
d245b89
add mongodb to dependencies in compass-utils
DarshanaVenkatesh Aug 20, 2025
af80a53
make isSearchPipelineQueryable true for not read only views
DarshanaVenkatesh Aug 20, 2025
6f36bf1
fix update-view.spec.ts
DarshanaVenkatesh Aug 20, 2025
3bd25a5
add negative test case
DarshanaVenkatesh Aug 20, 2025
ba3063d
remove async
DarshanaVenkatesh Aug 20, 2025
71f3629
Merge branch 'main' into COMPASS-9700
DarshanaVenkatesh Aug 20, 2025
082bd3c
just introduce new function to return whether has search indexes
DarshanaVenkatesh Aug 22, 2025
2bf679d
remove compass-utils dep
DarshanaVenkatesh Aug 22, 2025
e2d2f27
remove compass utils changes and cleanup
DarshanaVenkatesh Aug 22, 2025
434f636
update lock file
DarshanaVenkatesh Aug 22, 2025
598efb2
fix accidental remove
DarshanaVenkatesh Aug 22, 2025
285d349
fix stage.spec.ts
DarshanaVenkatesh Aug 22, 2025
784087c
return false instead of errror
DarshanaVenkatesh Aug 22, 2025
4c20596
check 8.0+
DarshanaVenkatesh Aug 22, 2025
af2b650
fix test
DarshanaVenkatesh Aug 22, 2025
06f3621
update package-lock
DarshanaVenkatesh Aug 22, 2025
8662ee5
fix mergee
DarshanaVenkatesh Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 77 additions & 56 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/compass-aggregations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"@mongodb-js/compass-utils": "^0.9.10",
"@mongodb-js/compass-workspaces": "^0.51.0",
"@mongodb-js/explain-plan-helper": "^1.4.17",
"@mongodb-js/mongodb-constants": "^0.12.2",
"@mongodb-js/mongodb-constants": "^0.14.0",
"@mongodb-js/my-queries-storage": "^0.37.0",
"@mongodb-js/shell-bson-parser": "^1.2.0",
"bson": "^6.10.4",
Expand Down
22 changes: 22 additions & 0 deletions packages/compass-aggregations/src/modules/search-indexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,26 @@ export const createSearchIndex = (): PipelineBuilderThunkAction<void> => {
};
};

/**
* Checks whether a namespace has existing search indexes
*
* @param namespace - collection/view namespace
* @param dataService - dataService instance
* @returns whether namespace has existing search indexes
*/
export const namespaceHasSearchIndexes = async (
namespace: string,
dataService: { getSearchIndexes?: (ns: string) => Promise<SearchIndex[]> }
): Promise<boolean> => {
try {
if (!dataService.getSearchIndexes) {
throw new Error('Cannot get search indexes in this environment');
}
const indexes = await dataService.getSearchIndexes(namespace);
return indexes.length > 0;
} catch {
return false;
}
};

export default reducer;
59 changes: 55 additions & 4 deletions packages/compass-aggregations/src/modules/update-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
} from '@mongodb-js/compass-connections/provider';
import { createDefaultConnectionInfo } from '@mongodb-js/testing-library-compass';

// Importing this to stub showConfirmation
import * as updateViewSlice from './update-view';
import * as searchIndexesSlice from './search-indexes';

const TEST_CONNECTION_INFO = { ...createDefaultConnectionInfo(), title: '' };

describe('update-view module', function () {
Expand Down Expand Up @@ -48,10 +52,20 @@ describe('update-view module', function () {
let stateMock: any;
let getStateMock: () => any;
let updateCollectionFake = sinon.fake();
let showConfirmationStub: sinon.SinonStub;
let namespaceHasSearchIndexesStub: sinon.SinonStub;

beforeEach(async function () {
beforeEach(function () {
dispatchFake = sinon.fake();
updateCollectionFake = sinon.fake.resolves(undefined);
showConfirmationStub = sinon
.stub(updateViewSlice, 'showConfirmation')
.resolves(true);

namespaceHasSearchIndexesStub = sinon
.stub(searchIndexesSlice, 'namespaceHasSearchIndexes')
.resolves(true);

stateMock = {
pipelineBuilder: { pipelineMode: 'builder-ui' },
focusMode: { isEnabled: false },
Expand All @@ -62,20 +76,57 @@ describe('update-view module', function () {
updateCollection: updateCollectionFake,
},
},
serverVersion: '8.1.0',
};
getStateMock = () => stateMock;
});

afterEach(function () {
showConfirmationStub.restore();
namespaceHasSearchIndexesStub.restore();
});

it('first it calls to dismiss any existing error', async function () {
const runUpdateView = updateView();
await runUpdateView(dispatchFake, getStateMock, thunkArg as any);
});

it('first it calls to dismiss any existing error', function () {
expect(dispatchFake.firstCall.args[0]).to.deep.equal({
type: 'aggregations/update-view/DISMISS_VIEW_UPDATE_ERROR',
});
});

it('calls the data service to update the view for the provided ns', function () {
it('does not shows confirmation banner if search indexes are not present', async function () {
namespaceHasSearchIndexesStub.resolves(false);
const runUpdateView = updateView();
await runUpdateView(dispatchFake, getStateMock, thunkArg as any);

expect(showConfirmationStub.calledOnce).to.be.false;
});

it('shows confirmation banner when search indexes are present', async function () {
const runUpdateView = updateView();
await runUpdateView(dispatchFake, getStateMock, thunkArg as any);

expect(showConfirmationStub.calledOnce).to.be.true;
expect(showConfirmationStub.firstCall.args[0]).to.deep.include({
title: `Are you sure you want to update the view?`,
buttonText: 'Update',
});
});

it('does not update view if not confirmed', async function () {
showConfirmationStub.resolves(false);

const runUpdateView = updateView();
await runUpdateView(dispatchFake, getStateMock, thunkArg as any);

expect(updateCollectionFake.calledOnce).to.be.false;
});

it('calls the data service to update the view for the provided ns', async function () {
const runUpdateView = updateView();
await runUpdateView(dispatchFake, getStateMock, thunkArg as any);

expect(updateCollectionFake.firstCall.args[0]).to.equal('aa.bb');
expect(updateCollectionFake.firstCall.args[1]).to.deep.equal({
viewOn: 'bb',
Expand Down
30 changes: 30 additions & 0 deletions packages/compass-aggregations/src/modules/update-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
import type { PipelineBuilderThunkAction } from '.';
import { isAction } from '../utils/is-action';
import type { AnyAction } from 'redux';
import { showConfirmation as showConfirmationModal } from '@mongodb-js/compass-components';
import { namespaceHasSearchIndexes } from './search-indexes';
import { VIEW_PIPELINE_UTILS } from '@mongodb-js/mongodb-constants';

export type UpdateViewState = null | string;

Expand Down Expand Up @@ -73,6 +76,8 @@ export const dismissViewError = (): DismissViewUpdateErrorAction => ({
type: DISMISS_VIEW_UPDATE_ERROR,
});

//Exporting this for test only to stub it and set its value
export const showConfirmation = showConfirmationModal;
/**
* Updates a view.
*
Expand All @@ -96,6 +101,7 @@ export const updateView = (): PipelineBuilderThunkAction<Promise<void>> => {
const state = getState();
const ds = state.dataService.dataService;
const viewNamespace = state.editViewName;
const serverVersion = state.serverVersion;

if (!viewNamespace) {
return;
Expand All @@ -107,6 +113,30 @@ export const updateView = (): PipelineBuilderThunkAction<Promise<void>> => {
getState(),
pipelineBuilder
);

if (
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsDataExplorer(
serverVersion
) &&
ds &&
(await namespaceHasSearchIndexes(viewNamespace, ds))
) {
const pipelineIsSearchQueryable =
VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(viewPipeline);
const confirmed = await showConfirmation({
title: `Are you sure you want to update the view?`,
description: pipelineIsSearchQueryable
? 'There are search indexes created on this view. Updating the view will result in an index rebuild, which will consume additional resources on your cluster.'
: 'This update will make the view incompatible with search indexes and will cause all search indexes to fail. Only views containing $addFields, $set or $match stages with the $expr operator are compatible with search indexes.',
buttonText: 'Update',
variant: pipelineIsSearchQueryable ? 'primary' : 'danger',
});

if (!confirmed) {
return;
}
}

const options = {
viewOn: toNS(state.namespace).collection,
pipeline: viewPipeline,
Expand Down
29 changes: 18 additions & 11 deletions packages/compass-aggregations/src/utils/stage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ describe('utils', function () {
expect(searchMeta.length).to.be.equal(1);
});

it('returns $search stage for a view', function () {
const search = filterStageOperators({
...filter,
sourceName: 'simple.sample',
}).filter((o) => o.name === '$search');

expect(search.length).to.be.equal(1);
});

it('returns $searchMeta stage for a view', function () {
const searchMeta = filterStageOperators({
...filter,
sourceName: 'simple.sample',
}).filter((o) => o.name === '$searchMeta');

expect(searchMeta.length).to.be.equal(1);
});

// $documents only works for db.aggregate, not coll.aggregate
it('does not return $documents stage for a regular collection', function () {
const documents = filterStageOperators({ ...filter }).filter(
Expand Down Expand Up @@ -97,17 +115,6 @@ describe('utils', function () {

expect(searchStages.length).to.be.equal(0);
});

it('does not return full-text search stages for views', function () {
const searchStages = filterStageOperators({
...filter,
sourceName: 'simple.sample',
}).filter((o) =>
['$search', '$searchMeta', '$documents'].includes(o.name)
);

expect(searchStages.length).to.be.equal(0);
});
});

context('when on-prem', function () {
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-collection/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@mongodb-js/compass-telemetry": "^1.13.0",
"@mongodb-js/compass-workspaces": "^0.51.0",
"@mongodb-js/connection-info": "^0.17.1",
"@mongodb-js/mongodb-constants": "^0.12.2",
"@mongodb-js/mongodb-constants": "^0.14.0",
"compass-preferences-model": "^2.50.0",
"hadron-document": "^8.9.5",
"mongodb": "^6.17.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"@codemirror/view": "^6.38.0",
"@lezer/highlight": "^1.2.1",
"@mongodb-js/compass-components": "^1.48.0",
"@mongodb-js/mongodb-constants": "^0.12.2",
"@mongodb-js/mongodb-constants": "^0.14.0",
"mongodb-query-parser": "^4.3.0",
"polished": "^4.2.2",
"prettier": "^2.7.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-indexes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"@mongodb-js/compass-logging": "^1.7.11",
"@mongodb-js/compass-telemetry": "^1.13.0",
"@mongodb-js/compass-workspaces": "^0.51.0",
"@mongodb-js/mongodb-constants": "^0.12.2",
"@mongodb-js/mongodb-constants": "^0.14.0",
"@mongodb-js/shell-bson-parser": "^1.2.0",
"@mongodb-js/connection-info": "^0.17.1",
"bson": "^6.10.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IndexesToolbar } from './indexes-toolbar';
import type { PreferencesAccess } from 'compass-preferences-model';
import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';
import { PreferencesProvider } from 'compass-preferences-model/provider';
import type { Document } from 'mongodb';

describe('IndexesToolbar Component', function () {
before(cleanup);
Expand All @@ -39,6 +40,7 @@ describe('IndexesToolbar Component', function () {
onRefreshIndexes={() => {}}
isSearchIndexesSupported={false}
isRefreshing={false}
collectionStats={{ index_count: 0, index_size: 0, pipeline: [] }}
onIndexViewChanged={() => {}}
onCreateRegularIndexClick={() => {}}
onCreateSearchIndexClick={() => {}}
Expand Down Expand Up @@ -153,10 +155,38 @@ describe('IndexesToolbar Component', function () {
expect(screen.getByText('Create Search Index')).to.be.visible;
});

it('should not render the refresh button', function () {
it('should render the refresh button', function () {
expect(screen.queryByText('Refresh')).to.be.visible;
});
});

describe('and pipeline is not queryable', function () {
it('should disable the create search index button', function () {
const pipelineMock: Document[] = [
{ $project: { newField: 'testValue' } },
];
const mockCollectionStats = {
index_count: 0,
index_size: 0,
pipeline: pipelineMock,
};

renderIndexesToolbar({
isReadonlyView: true,
serverVersion: '8.1.0',
indexView: 'search-indexes',
collectionStats: mockCollectionStats,
});

expect(screen.getByText('Create Search Index')).to.be.visible;
expect(
screen
.getByText('Create Search Index')
.closest('button')
?.getAttribute('aria-disabled')
).to.equal('true');
});
});
});

describe('when it is preferences ReadOnly', function () {
Expand Down
Loading
Loading