diff --git a/packages/compass-aggregations/src/modules/index.ts b/packages/compass-aggregations/src/modules/index.ts
index c1f967a890b..7bbf4ac37a0 100644
--- a/packages/compass-aggregations/src/modules/index.ts
+++ b/packages/compass-aggregations/src/modules/index.ts
@@ -40,6 +40,7 @@ import focusMode from './focus-mode';
import sidePanel from './side-panel';
import collectionsFields from './collections-fields';
import insights from './insights';
+import searchIndexes from './search-indexes';
/**
* The main application reducer.
@@ -84,6 +85,7 @@ const rootReducer = combineReducers({
sidePanel,
collectionsFields,
insights,
+ searchIndexes,
});
export type RootState = ReturnType
;
diff --git a/packages/compass-aggregations/src/modules/search-indexes.spec.ts b/packages/compass-aggregations/src/modules/search-indexes.spec.ts
new file mode 100644
index 00000000000..4961706b13d
--- /dev/null
+++ b/packages/compass-aggregations/src/modules/search-indexes.spec.ts
@@ -0,0 +1,192 @@
+import { expect } from 'chai';
+import reducer, { fetchIndexes, ActionTypes } from './search-indexes';
+import configureStore from '../../test/configure-store';
+import { DATA_SERVICE_CONNECTED } from './data-service';
+import sinon from 'sinon';
+
+describe('search-indexes module', function () {
+ describe('#reducer', function () {
+ it('returns default state', function () {
+ expect(
+ reducer(undefined, {
+ type: 'test',
+ })
+ ).to.deep.equal({
+ isSearchIndexesSupported: false,
+ indexes: [],
+ status: 'INITIAL',
+ });
+ });
+ it('returns state when fetching starts', function () {
+ expect(
+ reducer(undefined, {
+ type: ActionTypes.FetchIndexesStarted,
+ })
+ ).to.deep.equal({
+ isSearchIndexesSupported: false,
+ indexes: [],
+ status: 'LOADING',
+ });
+ });
+ it('returns state when fetching succeeds', function () {
+ expect(
+ reducer(undefined, {
+ type: ActionTypes.FetchIndexesFinished,
+ indexes: [{ name: 'default' }, { name: 'vector_index' }],
+ })
+ ).to.deep.equal({
+ isSearchIndexesSupported: false,
+ indexes: [{ name: 'default' }, { name: 'vector_index' }],
+ status: 'READY',
+ });
+ });
+ it('returns state when fetching fails', function () {
+ expect(
+ reducer(undefined, {
+ type: ActionTypes.FetchIndexesFailed,
+ })
+ ).to.deep.equal({
+ isSearchIndexesSupported: false,
+ indexes: [],
+ status: 'ERROR',
+ });
+ });
+ });
+ describe('#actions', function () {
+ let store: ReturnType;
+ beforeEach(function () {
+ store = configureStore({
+ pipeline: [],
+ isSearchIndexesSupported: true,
+ namespace: 'test.listings',
+ });
+ });
+ context('fetchIndexes', function () {
+ it('fetches search indexes and sets status to READY', async function () {
+ const spy = sinon.spy((ns: string) => {
+ expect(ns).to.equal('test.listings');
+ return Promise.resolve([
+ { name: 'default' },
+ { name: 'vector_index' },
+ ]);
+ });
+
+ store.dispatch({
+ type: DATA_SERVICE_CONNECTED,
+ dataService: {
+ getSearchIndexes: spy,
+ },
+ });
+
+ await store.dispatch(fetchIndexes());
+
+ expect(store.getState().searchIndexes).to.deep.equal({
+ isSearchIndexesSupported: true,
+ indexes: [{ name: 'default' }, { name: 'vector_index' }],
+ status: 'READY',
+ });
+ });
+
+ it('does not fetch indexes when status is LOADING', async function () {
+ // Set the status to LOADING
+ store.dispatch({
+ type: ActionTypes.FetchIndexesStarted,
+ });
+
+ const spy = sinon.spy();
+ store.dispatch({
+ type: DATA_SERVICE_CONNECTED,
+ dataService: {
+ getSearchIndexes: spy,
+ },
+ });
+
+ await store.dispatch(fetchIndexes());
+ await store.dispatch(fetchIndexes());
+ await store.dispatch(fetchIndexes());
+
+ expect(spy.callCount).to.equal(0);
+ });
+
+ it('does not fetch indexes when status is READY', async function () {
+ // Set the status to LOADING
+ store.dispatch({
+ type: ActionTypes.FetchIndexesFinished,
+ indexes: [{ name: 'default' }, { name: 'vector_index' }],
+ });
+
+ const spy = sinon.spy();
+ store.dispatch({
+ type: DATA_SERVICE_CONNECTED,
+ dataService: {
+ getSearchIndexes: spy,
+ },
+ });
+
+ await store.dispatch(fetchIndexes());
+ await store.dispatch(fetchIndexes());
+ await store.dispatch(fetchIndexes());
+
+ expect(spy.callCount).to.equal(0);
+
+ expect(store.getState().searchIndexes).to.deep.equal({
+ isSearchIndexesSupported: true,
+ indexes: [{ name: 'default' }, { name: 'vector_index' }],
+ status: 'READY',
+ });
+ });
+
+ it('sets ERROR status when fetching indexes fails', async function () {
+ const spy = sinon.spy((ns: string) => {
+ expect(ns).to.equal('test.listings');
+ return Promise.reject(new Error('Failed to fetch indexes'));
+ });
+
+ store.dispatch({
+ type: DATA_SERVICE_CONNECTED,
+ dataService: {
+ getSearchIndexes: spy,
+ },
+ });
+
+ await store.dispatch(fetchIndexes());
+
+ expect(store.getState().searchIndexes).to.deep.equal({
+ isSearchIndexesSupported: true,
+ indexes: [],
+ status: 'ERROR',
+ });
+ });
+
+ it('fetchs indexes in error state', async function () {
+ // Set the status to ERROR
+ store.dispatch({
+ type: ActionTypes.FetchIndexesFailed,
+ });
+
+ const spy = sinon.spy((ns: string) => {
+ expect(ns).to.equal('test.listings');
+ return Promise.resolve([
+ { name: 'default' },
+ { name: 'vector_index' },
+ ]);
+ });
+
+ store.dispatch({
+ type: DATA_SERVICE_CONNECTED,
+ dataService: {
+ getSearchIndexes: spy,
+ },
+ });
+
+ await store.dispatch(fetchIndexes());
+
+ expect(store.getState().searchIndexes).to.deep.equal({
+ isSearchIndexesSupported: true,
+ indexes: [{ name: 'default' }, { name: 'vector_index' }],
+ status: 'READY',
+ });
+ });
+ });
+ });
+});
diff --git a/packages/compass-aggregations/src/modules/search-indexes.ts b/packages/compass-aggregations/src/modules/search-indexes.ts
new file mode 100644
index 00000000000..411e0a588f5
--- /dev/null
+++ b/packages/compass-aggregations/src/modules/search-indexes.ts
@@ -0,0 +1,119 @@
+import type { AnyAction, Reducer } from 'redux';
+import type { PipelineBuilderThunkAction } from '.';
+import { localAppRegistryEmit } from '@mongodb-js/mongodb-redux-common/app-registry';
+import type { SearchIndex } from 'mongodb-data-service';
+import { isAction } from '../utils/is-action';
+
+enum SearchIndexesStatuses {
+ INITIAL = 'INITIAL',
+ LOADING = 'LOADING',
+ READY = 'READY',
+ ERROR = 'ERROR',
+}
+
+export type SearchIndexesStatus = keyof typeof SearchIndexesStatuses;
+
+export enum ActionTypes {
+ FetchIndexesStarted = 'compass-aggregations/search-indexes/FetchIndexesStarted',
+ FetchIndexesFinished = 'compass-aggregations/search-indexes/FetchIndexesFinished',
+ FetchIndexesFailed = 'compass-aggregations/search-indexes/FetchIndexesFailed',
+}
+
+type FetchIndexesStartedAction = {
+ type: ActionTypes.FetchIndexesStarted;
+};
+
+type FetchIndexesFinishedAction = {
+ type: ActionTypes.FetchIndexesFinished;
+ indexes: SearchIndex[];
+};
+
+type FetchIndexesFailedAction = {
+ type: ActionTypes.FetchIndexesFailed;
+};
+
+type State = {
+ isSearchIndexesSupported: boolean;
+ indexes: SearchIndex[];
+ status: SearchIndexesStatus;
+};
+
+export const INITIAL_STATE: State = {
+ isSearchIndexesSupported: false,
+ indexes: [],
+ status: SearchIndexesStatuses.INITIAL,
+};
+
+const reducer: Reducer = (state = INITIAL_STATE, action: AnyAction) => {
+ if (
+ isAction(action, ActionTypes.FetchIndexesStarted)
+ ) {
+ return {
+ ...state,
+ status: SearchIndexesStatuses.LOADING,
+ };
+ }
+ if (
+ isAction(
+ action,
+ ActionTypes.FetchIndexesFinished
+ )
+ ) {
+ return {
+ ...state,
+ indexes: action.indexes,
+ status: SearchIndexesStatuses.READY,
+ };
+ }
+ if (
+ isAction(action, ActionTypes.FetchIndexesFailed)
+ ) {
+ return {
+ ...state,
+ status: SearchIndexesStatuses.ERROR,
+ };
+ }
+ return state;
+};
+
+export const fetchIndexes = (): PipelineBuilderThunkAction> => {
+ return async (dispatch, getState) => {
+ const {
+ namespace,
+ dataService: { dataService },
+ searchIndexes: { status },
+ } = getState();
+
+ if (
+ !dataService ||
+ status === SearchIndexesStatuses.LOADING ||
+ status === SearchIndexesStatuses.READY
+ ) {
+ return;
+ }
+
+ dispatch({
+ type: ActionTypes.FetchIndexesStarted,
+ });
+
+ try {
+ const indexes = await dataService.getSearchIndexes(namespace);
+ dispatch({
+ type: ActionTypes.FetchIndexesFinished,
+ indexes,
+ });
+ } catch (e) {
+ dispatch({
+ type: ActionTypes.FetchIndexesFailed,
+ });
+ }
+ };
+};
+
+export const createSearchIndex = (): PipelineBuilderThunkAction => {
+ return (dispatch) => {
+ dispatch(localAppRegistryEmit('open-create-search-index-modal'));
+ };
+};
+
+export default reducer;
diff --git a/packages/compass-aggregations/src/stores/store.spec.ts b/packages/compass-aggregations/src/stores/store.spec.ts
index 08440ff4194..992dabed4b9 100644
--- a/packages/compass-aggregations/src/stores/store.spec.ts
+++ b/packages/compass-aggregations/src/stores/store.spec.ts
@@ -137,6 +137,7 @@ describe('Aggregation Store', function () {
focusMode: INITIAL_STATE.focusMode,
sidePanel: INITIAL_STATE.sidePanel,
collectionsFields: INITIAL_STATE.collectionsFields,
+ searchIndexes: INITIAL_STATE.searchIndexes,
});
});
});
diff --git a/packages/compass-aggregations/src/stores/store.ts b/packages/compass-aggregations/src/stores/store.ts
index 1d52f7db58d..cb59538b327 100644
--- a/packages/compass-aggregations/src/stores/store.ts
+++ b/packages/compass-aggregations/src/stores/store.ts
@@ -26,6 +26,7 @@ import {
} from '../modules/collections-fields';
import type { CollectionInfo } from '../modules/collections-fields';
import { disableAIFeature } from '../modules/pipeline-builder/pipeline-ai';
+import { INITIAL_STATE as SEARCH_INDEXES_INITIAL_STATE } from '../modules/search-indexes';
export type ConfigureStoreOptions = {
/**
@@ -133,6 +134,10 @@ export type ConfigureStoreOptions = {
* Service for interacting with Atlas-only features
*/
atlasService: AtlasService;
+ /**
+ * Whether or not search indexes are supported in the current environment
+ */
+ isSearchIndexesSupported: boolean;
}>;
const configureStore = (options: ConfigureStoreOptions) => {
@@ -228,6 +233,10 @@ const configureStore = (options: ConfigureStoreOptions) => {
},
sourceName: options.sourceName,
editViewName: options.editViewName,
+ searchIndexes: {
+ ...SEARCH_INDEXES_INITIAL_STATE,
+ isSearchIndexesSupported: Boolean(options.isSearchIndexesSupported),
+ },
},
applyMiddleware(
thunk.withExtraArgument({
diff --git a/packages/compass-aggregations/src/utils/insights.ts b/packages/compass-aggregations/src/utils/insights.ts
index 5f983cc5e9a..5683c8dd89b 100644
--- a/packages/compass-aggregations/src/utils/insights.ts
+++ b/packages/compass-aggregations/src/utils/insights.ts
@@ -4,16 +4,29 @@ import {
type Signal,
} from '@mongodb-js/compass-components';
import type { StoreStage } from '../modules/pipeline-builder/stage-editor';
+import type { ServerEnvironment } from '../modules/env';
export const getInsightForStage = (
{ stageOperator, value }: StoreStage,
- env: string
+ env: ServerEnvironment,
+ isSearchIndexesSupported: boolean,
+ onCreateSearchIndex: () => void
): Signal | undefined => {
const isAtlas = [ATLAS, ADL].includes(env);
if (stageOperator === '$match' && /\$(text|regex)\b/.test(value ?? '')) {
- return isAtlas
- ? PerformanceSignals.get('atlas-text-regex-usage-in-stage')
- : PerformanceSignals.get('non-atlas-text-regex-usage-in-stage');
+ if (isAtlas) {
+ return isSearchIndexesSupported
+ ? {
+ ...PerformanceSignals.get(
+ 'atlas-with-search-text-regex-usage-in-stage'
+ ),
+ onPrimaryActionButtonClick: onCreateSearchIndex,
+ }
+ : PerformanceSignals.get(
+ 'atlas-without-search-text-regex-usage-in-stage'
+ );
+ }
+ return PerformanceSignals.get('non-atlas-text-regex-usage-in-stage');
}
if (stageOperator === '$lookup') {
return PerformanceSignals.get('lookup-in-stage');
diff --git a/packages/compass-aggregations/src/utils/pipeline-storage.ts b/packages/compass-aggregations/src/utils/pipeline-storage.ts
index 2bda3bce86c..98acb089285 100644
--- a/packages/compass-aggregations/src/utils/pipeline-storage.ts
+++ b/packages/compass-aggregations/src/utils/pipeline-storage.ts
@@ -1,5 +1,5 @@
+import type { Stats } from '@mongodb-js/compass-user-data';
import { UserData, z } from '@mongodb-js/compass-user-data';
-import type { Stats } from 'fs';
import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { prettify } from '../modules/pipeline-builder/pipeline-parser/utils';
diff --git a/packages/compass-app-stores/package.json b/packages/compass-app-stores/package.json
index feebee5368e..d3caf8f17f7 100644
--- a/packages/compass-app-stores/package.json
+++ b/packages/compass-app-stores/package.json
@@ -11,7 +11,7 @@
"email": "compass@mongodb.com"
},
"homepage": "https://github.com/mongodb-js/compass",
- "version": "7.3.1",
+ "version": "7.3.2",
"repository": {
"type": "git",
"url": "https://github.com/mongodb-js/compass.git"
@@ -54,11 +54,11 @@
"reformat": "npm run prettier -- --write . && npm run eslint . --fix"
},
"devDependencies": {
- "@mongodb-js/eslint-config-compass": "^1.0.9",
+ "@mongodb-js/eslint-config-compass": "^1.0.10",
"@mongodb-js/mocha-config-compass": "^1.3.1",
"@mongodb-js/prettier-config-compass": "^1.0.1",
"@mongodb-js/tsconfig-compass": "^1.0.3",
- "@mongodb-js/webpack-config-compass": "^1.2.2",
+ "@mongodb-js/webpack-config-compass": "^1.2.3",
"@types/chai": "^4.2.21",
"@types/mocha": "^9.0.0",
"@types/sinon-chai": "^3.2.5",
@@ -66,7 +66,7 @@
"debug": "^4.3.1",
"depcheck": "^1.4.1",
"eslint": "^7.25.0",
- "hadron-app-registry": "^9.0.11",
+ "hadron-app-registry": "^9.0.12",
"mocha": "^10.2.0",
"mongodb-ns": "^2.4.0",
"nyc": "^15.1.0",
@@ -76,10 +76,10 @@
"xvfb-maybe": "^0.2.1"
},
"dependencies": {
- "mongodb-instance-model": "^12.12.1"
+ "mongodb-instance-model": "^12.12.2"
},
"peerDependencies": {
- "mongodb-instance-model": "^12.12.1"
+ "mongodb-instance-model": "^12.12.2"
},
"publishConfig": {
"access": "public"
diff --git a/packages/compass-collection/package.json b/packages/compass-collection/package.json
index 3a3bac7fc8a..3b1e2e6146a 100644
--- a/packages/compass-collection/package.json
+++ b/packages/compass-collection/package.json
@@ -14,7 +14,7 @@
"email": "compass@mongodb.com"
},
"homepage": "https://github.com/mongodb-js/compass",
- "version": "4.15.1",
+ "version": "4.16.0",
"apiVersion": "3.0.0",
"repository": {
"type": "git",
@@ -56,28 +56,28 @@
"reformat": "npm run prettier -- --write . && npm run eslint . --fix"
},
"peerDependencies": {
- "@mongodb-js/compass-components": "^1.15.0",
- "@mongodb-js/compass-logging": "^1.2.2",
+ "@mongodb-js/compass-components": "^1.16.0",
+ "@mongodb-js/compass-logging": "^1.2.3",
"bson": "^6.0.0",
- "compass-preferences-model": "^2.15.1",
- "hadron-app-registry": "^9.0.11",
+ "compass-preferences-model": "^2.15.2",
+ "hadron-app-registry": "^9.0.12",
"hadron-ipc": "^3.2.2",
"react": "^17.0.2"
},
"dependencies": {
- "@mongodb-js/compass-components": "^1.15.0",
- "@mongodb-js/compass-logging": "^1.2.2",
+ "@mongodb-js/compass-components": "^1.16.0",
+ "@mongodb-js/compass-logging": "^1.2.3",
"bson": "^6.0.0",
- "compass-preferences-model": "^2.15.1",
- "hadron-app-registry": "^9.0.11",
+ "compass-preferences-model": "^2.15.2",
+ "hadron-app-registry": "^9.0.12",
"hadron-ipc": "^3.2.2"
},
"devDependencies": {
- "@mongodb-js/eslint-config-compass": "^1.0.9",
+ "@mongodb-js/eslint-config-compass": "^1.0.10",
"@mongodb-js/mocha-config-compass": "^1.3.1",
"@mongodb-js/prettier-config-compass": "^1.0.1",
"@mongodb-js/tsconfig-compass": "^1.0.3",
- "@mongodb-js/webpack-config-compass": "^1.2.2",
+ "@mongodb-js/webpack-config-compass": "^1.2.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"@types/chai": "^4.2.21",
@@ -92,9 +92,9 @@
"eslint": "^7.25.0",
"mocha": "^10.2.0",
"mongodb": "^6.0.0",
- "mongodb-collection-model": "^5.12.1",
- "mongodb-data-service": "^22.12.1",
- "mongodb-instance-model": "^12.12.1",
+ "mongodb-collection-model": "^5.12.2",
+ "mongodb-data-service": "^22.12.2",
+ "mongodb-instance-model": "^12.12.2",
"mongodb-ns": "^2.4.0",
"numeral": "^2.0.6",
"nyc": "^15.1.0",
diff --git a/packages/compass-collection/src/stores/collection-tab.ts b/packages/compass-collection/src/stores/collection-tab.ts
index 42eb3b17570..12f7ea4e1c3 100644
--- a/packages/compass-collection/src/stores/collection-tab.ts
+++ b/packages/compass-collection/src/stores/collection-tab.ts
@@ -110,6 +110,10 @@ export function configureStore(options: CollectionTabOptions) {
store.dispatch(selectTab('Indexes'));
});
+ localAppRegistry.on('open-create-search-index-modal', () => {
+ store.dispatch(selectTab('Indexes'));
+ });
+
localAppRegistry.on('generate-aggregation-from-query', () => {
store.dispatch(selectTab('Aggregations'));
});
diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json
index ccf388d288a..7d826a006f5 100644
--- a/packages/compass-components/package.json
+++ b/packages/compass-components/package.json
@@ -1,6 +1,6 @@
{
"name": "@mongodb-js/compass-components",
- "version": "1.15.0",
+ "version": "1.16.0",
"description": "React Components used in Compass",
"license": "SSPL",
"main": "lib/index.js",
@@ -46,6 +46,7 @@
"@leafygreen-ui/hooks": "^7.3.3",
"@leafygreen-ui/icon": "^11.21.0",
"@leafygreen-ui/icon-button": "^15.0.3",
+ "@leafygreen-ui/info-sprinkle": "^1.0.0",
"@leafygreen-ui/inline-definition": "^6.0.0",
"@leafygreen-ui/leafygreen-provider": "^3.1.0",
"@leafygreen-ui/lib": "^10.0.0",
@@ -78,7 +79,7 @@
"@react-stately/tooltip": "^3.0.5",
"bson": "^6.0.0",
"focus-trap-react": "^8.4.2",
- "hadron-document": "^8.4.1",
+ "hadron-document": "^8.4.2",
"hadron-type-checker": "^7.1.0",
"is-electron-renderer": "^2.0.1",
"lodash": "^4.17.21",
@@ -92,7 +93,7 @@
"react": "^17.0.2"
},
"devDependencies": {
- "@mongodb-js/eslint-config-compass": "^1.0.9",
+ "@mongodb-js/eslint-config-compass": "^1.0.10",
"@mongodb-js/mocha-config-compass": "^1.3.1",
"@mongodb-js/prettier-config-compass": "^1.0.1",
"@mongodb-js/tsconfig-compass": "^1.0.3",
diff --git a/packages/compass-components/src/components/guide-cue/guide-cue-service.ts b/packages/compass-components/src/components/guide-cue/guide-cue-service.ts
index 45eb959722e..a710c36cadc 100644
--- a/packages/compass-components/src/components/guide-cue/guide-cue-service.ts
+++ b/packages/compass-components/src/components/guide-cue/guide-cue-service.ts
@@ -88,14 +88,19 @@ export class GuideCueService extends EventTarget {
if (!this._activeCue) {
return;
}
- return this.dispatchEvent(
- new CustomEvent('show-cue', {
- detail: {
- cueId: this._activeCue.cueId,
- groupId: this._activeCue.groupId,
- },
- })
- );
+ try {
+ return this.dispatchEvent(
+ new CustomEvent('show-cue', {
+ detail: {
+ cueId: this._activeCue.cueId,
+ groupId: this._activeCue.groupId,
+ },
+ })
+ );
+ } catch (ex) {
+ // TODO(COMPASS-7357): this seems to be a temporary error happening sometimes during test.
+ // In that case, assume the event is not dispatched
+ }
}
private validateCueData(groupId: GroupName, step: number) {
diff --git a/packages/compass-components/src/components/leafygreen.tsx b/packages/compass-components/src/components/leafygreen.tsx
index 6384826ddad..894a14bbadc 100644
--- a/packages/compass-components/src/components/leafygreen.tsx
+++ b/packages/compass-components/src/components/leafygreen.tsx
@@ -19,6 +19,7 @@ import {
MongoDBLogo,
} from '@leafygreen-ui/logo';
import { Menu, MenuSeparator, MenuItem } from '@leafygreen-ui/menu';
+import { InfoSprinkle } from '@leafygreen-ui/info-sprinkle';
// If a leafygreen Menu (and therefore MenuItems) makes its way into a
diff --git a/packages/compass-crud/src/components/delete-data-menu.tsx b/packages/compass-crud/src/components/delete-data-menu.tsx
new file mode 100644
index 00000000000..99d56ac9ca1
--- /dev/null
+++ b/packages/compass-crud/src/components/delete-data-menu.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import { Icon, Button, Tooltip } from '@mongodb-js/compass-components';
+import { usePreference } from 'compass-preferences-model';
+
+type DeleteMenuButtonProps = {
+ isWritable: boolean;
+ onClick: () => void;
+};
+
+const DeleteMenuButton: React.FunctionComponent
= ({
+ isWritable,
+ onClick,
+}) => {
+ const isVisible = usePreference('enableBulkDeleteOperations', React);
+
+ if (!isVisible) {
+ return null;
+ }
+
+ return (
+ }
+ >
+ Delete
+
+ );
+};
+
+type DeleteMenuProps = DeleteMenuButtonProps & {
+ disabledTooltip: string;
+};
+
+const DeleteMenu: React.FunctionComponent = ({
+ isWritable,
+ onClick,
+ disabledTooltip,
+}) => {
+ if (isWritable) {
+ return (
+
+ );
+ }
+
+ return (
+ ) => (
+
+
+ {tooltipChildren}
+
+ )}
+ // Disable the tooltip when the instance is in a writable state.
+ isDisabled={isWritable}
+ justify="middle"
+ delay={500}
+ >
+ {disabledTooltip}
+
+ );
+};
+
+export default DeleteMenu;
diff --git a/packages/compass-crud/src/components/document-list.tsx b/packages/compass-crud/src/components/document-list.tsx
index 38aadf5cba1..bea484ba65f 100644
--- a/packages/compass-crud/src/components/document-list.tsx
+++ b/packages/compass-crud/src/components/document-list.tsx
@@ -21,6 +21,7 @@ import type { DocumentTableViewProps } from './table-view/document-table-view';
import DocumentTableView from './table-view/document-table-view';
import type { CrudToolbarProps } from './crud-toolbar';
import { CrudToolbar } from './crud-toolbar';
+import { toJSString } from 'mongodb-query-parser';
import type { DOCUMENTS_STATUSES } from '../constants/documents-statuses';
import {
@@ -31,8 +32,14 @@ import {
} from '../constants/documents-statuses';
import './index.less';
-import type { CrudStore, BSONObject, DocumentView } from '../stores/crud-store';
-import type Document from 'hadron-document';
+import type {
+ CrudStore,
+ BSONObject,
+ DocumentView,
+ QueryState,
+} from '../stores/crud-store';
+import { getToolbarSignal } from '../utils/toolbar-signal';
+import BulkDeleteModal from './bulk-delete-modal';
const listAndJsonStyles = css({
padding: spacing[3],
@@ -70,6 +77,9 @@ export type DocumentListProps = {
debouncingLoad?: boolean;
viewChanged: CrudToolbarProps['viewSwitchHandler'];
darkMode?: boolean;
+ isCollectionScan?: boolean;
+ isSearchIndexesSupported: boolean;
+ query: QueryState;
} & Omit &
Omit &
Omit &
@@ -104,8 +114,6 @@ export type DocumentListProps = {
| 'instanceDescription'
| 'refreshDocuments'
| 'resultId'
- | 'isCollectionScan'
- | 'onCollectionScanInsightActionButtonClick'
>;
/**
@@ -228,6 +236,36 @@ class DocumentList extends React.Component {
}
}
+ onOpenBulkDeleteDialog() {
+ this.props.store.openBulkDeleteDialog();
+ }
+
+ onCancelBulkDeleteDialog() {
+ this.props.store.closeBulkDeleteDialog();
+ }
+
+ onConfirmBulkDeleteDialog() {
+ void this.props.store.runBulkDelete();
+ }
+
+ /**
+ * Render the bulk deletion modal
+ */
+ renderDeletionModal() {
+ return (
+
+ );
+ }
/**
* Render EmptyContent view when no documents are present.
*
@@ -308,6 +346,7 @@ class DocumentList extends React.Component {
isExportable={this.props.isExportable}
onApplyClicked={this.onApplyClicked.bind(this)}
onResetClicked={this.onResetClicked.bind(this)}
+ onDeleteButtonClicked={this.onOpenBulkDeleteDialog.bind(this)}
openExportFileDialog={this.props.openExportFileDialog}
outdated={this.props.outdated}
readonly={!this.props.isEditable}
@@ -316,9 +355,16 @@ class DocumentList extends React.Component {
instanceDescription={this.props.instanceDescription}
refreshDocuments={this.props.refreshDocuments}
resultId={this.props.resultId}
- isCollectionScan={this.props.isCollectionScan}
- onCollectionScanInsightActionButtonClick={this.props.store.openCreateIndexModal.bind(
- this.props.store
+ querySkip={this.props.store.state.query.skip}
+ queryLimit={this.props.store.state.query.limit}
+ insights={getToolbarSignal(
+ JSON.stringify(this.props.query.filter),
+ Boolean(this.props.isCollectionScan),
+ this.props.isSearchIndexesSupported,
+ this.props.store.openCreateIndexModal.bind(this.props.store),
+ this.props.store.openCreateSearchIndexModal.bind(
+ this.props.store
+ )
)}
/>
}
@@ -326,6 +372,7 @@ class DocumentList extends React.Component {
{this.renderZeroState()}
{this.renderContent()}
{this.renderInsertModal()}
+ {this.renderDeletionModal()}
);
@@ -427,6 +474,9 @@ DocumentList.propTypes = {
isWritable: PropTypes.bool,
instanceDescription: PropTypes.string,
darkMode: PropTypes.bool,
+ isCollectionScan: PropTypes.bool,
+ isSearchIndexesSupported: PropTypes.bool,
+ query: PropTypes.object,
};
DocumentList.defaultProps = {
diff --git a/packages/compass-crud/src/components/editable-document.tsx b/packages/compass-crud/src/components/editable-document.tsx
index a5bde2f03b5..61370715fe5 100644
--- a/packages/compass-crud/src/components/editable-document.tsx
+++ b/packages/compass-crud/src/components/editable-document.tsx
@@ -86,7 +86,7 @@ class EditableDocument extends React.Component<
if (this.state.editing || this.state.deleting) {
// If the underlying document changed, that means that the collection
// contents have been refreshed. In that case, stop editing/deleting.
- setImmediate(() => {
+ setTimeout(() => {
this.setState({ editing: false, deleting: false });
});
}
diff --git a/packages/compass-crud/src/components/table-view/cell-editor.tsx b/packages/compass-crud/src/components/table-view/cell-editor.tsx
index 9baa1d7c915..fc1297ca96d 100644
--- a/packages/compass-crud/src/components/table-view/cell-editor.tsx
+++ b/packages/compass-crud/src/components/table-view/cell-editor.tsx
@@ -507,9 +507,9 @@ class CellEditor
}
onAddField(...args: Parameters