diff --git a/package-lock.json b/package-lock.json index 174b19bdc25..220c6f663c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46896,7 +46896,8 @@ "@mongodb-js/compass-logging": "^1.1.9", "@mongodb-js/mongodb-redux-common": "^2.0.11", "bson": "^6.0.0", - "compass-preferences-model": "^2.13.0" + "compass-preferences-model": "^2.13.0", + "ejson-shell-parser": "^1.2.4" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.0.9", @@ -59879,6 +59880,7 @@ "chai": "^4.2.0", "compass-preferences-model": "^2.13.0", "depcheck": "^1.4.1", + "ejson-shell-parser": "*", "electron": "^24.8.2", "enzyme": "^3.11.0", "eslint": "^7.25.0", diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index bb0a91a1f4c..86b62db7d1c 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -93,7 +93,8 @@ "redux-thunk": "^2.4.1", "semver": "^5.4.1", "sinon": "^9.2.3", - "xvfb-maybe": "^0.2.1" + "xvfb-maybe": "^0.2.1", + "ejson-shell-parser": "^1.2.4" }, "dependencies": { "@mongodb-js/compass-components": "^1.13.0", diff --git a/packages/compass-indexes/src/components/create-search-index-modal/index.spec.tsx b/packages/compass-indexes/src/components/create-search-index-modal/index.spec.tsx new file mode 100644 index 00000000000..c3f90af875b --- /dev/null +++ b/packages/compass-indexes/src/components/create-search-index-modal/index.spec.tsx @@ -0,0 +1,107 @@ +import { expect } from 'chai'; +import { DEFAULT_INDEX_DEFINITION } from '.'; +import CreateSearchIndexModal from '.'; +import sinon from 'sinon'; +import { Provider } from 'react-redux'; + +import { render, screen, cleanup } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import React from 'react'; +import { getCodemirrorEditorValue } from '@mongodb-js/compass-editor'; +import { + openModalForCreation, + createIndexFailed, +} from '../../modules/search-indexes'; +import type { IndexesDataService } from '../../stores/store'; +import { setupStore } from '../../../test/setup-store'; + +describe('Create Search Index Modal', function () { + let store: ReturnType; + let dataProvider: Partial; + + beforeEach(function () { + dataProvider = { + createSearchIndex: sinon.spy(), + }; + + store = setupStore({ namespace: 'test.test' }, dataProvider); + + store.dispatch(openModalForCreation()); + + render( + + + + ); + }); + + afterEach(cleanup); + + describe('default behaviour', function () { + it('uses "default" as the default index name', function () { + const inputText: HTMLInputElement = screen.getByTestId( + 'name-of-search-index' + ); + + expect(inputText).to.not.be.null; + expect(inputText?.value).to.equal('default'); + }); + + it('uses a dynamic mapping as the default index definition', function () { + const defaultIndexDef = getCodemirrorEditorValue( + 'definition-of-search-index' + ); + + expect(defaultIndexDef).to.not.be.null; + expect(defaultIndexDef).to.equal(DEFAULT_INDEX_DEFINITION); + }); + }); + + describe('form validation', function () { + it('shows an error when the index name is empty', async function () { + const inputText: HTMLInputElement = screen.getByTestId( + 'name-of-search-index' + ); + + userEvent.clear(inputText); + expect(await screen.findByText('Please enter the name of the index.')).to + .exist; + }); + + it('shows server errors', async function () { + store.dispatch(createIndexFailed('InvalidIndexSpecificationOption')); + expect(store.getState().searchIndexes).to.have.property( + 'error', + 'Invalid index definition.' + ); + + expect(await screen.findByText('Invalid index definition.')).to.exist; + }); + }); + + describe('form behaviour', function () { + it('closes the modal on cancel', function () { + const cancelButton: HTMLButtonElement = screen + .getByText('Cancel') + .closest('button')!; + + userEvent.click(cancelButton); + expect(store.getState().searchIndexes.createIndex.isModalOpen).to.be + .false; + }); + + it('submits the modal on create search index', function () { + const submitButton: HTMLButtonElement = screen + .getByTestId('create-search-index-button') + .closest('button')!; + + userEvent.click(submitButton); + expect(dataProvider.createSearchIndex).to.have.been.calledOnceWithExactly( + 'test.test', + 'default', + { mappings: { dynamic: true } } + ); + }); + }); +}); diff --git a/packages/compass-indexes/src/components/create-search-index-modal/index.tsx b/packages/compass-indexes/src/components/create-search-index-modal/index.tsx new file mode 100644 index 00000000000..1cf99abc4ca --- /dev/null +++ b/packages/compass-indexes/src/components/create-search-index-modal/index.tsx @@ -0,0 +1,196 @@ +import React, { useCallback, useState } from 'react'; +import { useTrackOnChange } from '@mongodb-js/compass-logging'; +import { + Modal, + ModalFooter, + ModalHeader, + ModalBody, + TextInput, + Label, + spacing, + css, + HorizontalRule, + Subtitle, + Button, + Link, + Icon, + WarningSummary, + ErrorSummary, +} from '@mongodb-js/compass-components'; +import { closeModal, saveIndex } from '../../modules/search-indexes'; +import { connect } from 'react-redux'; +import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor'; +import type { RootState } from '../../modules'; +import _parseShellBSON, { ParseMode } from 'ejson-shell-parser'; +import type { Document } from 'mongodb'; + +// Copied from packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/utils.ts +function parseShellBSON(source: string): Document[] { + const parsed = _parseShellBSON(source, { mode: ParseMode.Loose }); + if (!parsed || typeof parsed !== 'object') { + // XXX(COMPASS-5689): We've hit the condition in + // https://github.com/mongodb-js/ejson-shell-parser/blob/c9c0145ababae52536ccd2244ac2ad01a4bbdef3/src/index.ts#L36 + throw new Error('The provided index definition is invalid.'); + } + return parsed; +} + +export const DEFAULT_INDEX_DEFINITION = `{ + "mappings": { + "dynamic": true + } +}`; + +const flexWithGapStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[3], +}); + +const bodyGapStyles = css({ + marginTop: spacing[3], +}); + +const toolbarStyles = css({ + display: 'flex', + gap: spacing[2], +}); + +type CreateSearchIndexModalProps = { + isModalOpen: boolean; + isBusy: boolean; + error?: string; + saveIndex: (indexName: string, indexDefinition: Document) => void; + closeModal: () => void; +}; + +export const CreateSearchIndexModal: React.FunctionComponent< + CreateSearchIndexModalProps +> = ({ isModalOpen, isBusy, error, saveIndex, closeModal }) => { + const [indexName, setIndexName] = useState('default'); + const [indexDefinition, setIndexDefinition] = useState( + DEFAULT_INDEX_DEFINITION + ); + const [parsingError, setParsingError] = useState( + undefined + ); + + const onSearchIndexDefinitionChanged = useCallback( + (newDefinition: string) => { + setParsingError(undefined); + + try { + parseShellBSON(newDefinition); + setIndexDefinition(newDefinition); + } catch (ex) { + setParsingError((ex as Error).message); + } + }, + [setIndexDefinition, setParsingError] + ); + + const onSaveIndex = useCallback(() => { + if (parsingError) { + return; + } + + const indexDefinitionDoc = parseShellBSON(indexDefinition); + saveIndex(indexName, indexDefinitionDoc); + }, [saveIndex, parsingError, indexName, indexDefinition]); + + useTrackOnChange( + 'COMPASS-SEARCH-INDEXES-UI', + (track) => { + if (isModalOpen) { + track('Screen', { name: 'create_search_index_modal' }); + } + }, + [isModalOpen], + undefined, + React + ); + + return ( + + + +
+ + ) => + setIndexName(evt.target.value) + } + /> +
+ +
+ Index Definition +

+ By default, search indexes will have the following search + configurations. You can refine this later. +

+ + View Atlas Search tutorials{' '} + + + + {parsingError && } + {error && } +
+
+ + + + + +
+ ); +}; + +const mapState = ({ searchIndexes }: RootState) => ({ + isModalOpen: searchIndexes.createIndex.isModalOpen, + isBusy: searchIndexes.createIndex.isBusy, + error: searchIndexes.error, +}); + +const mapDispatch = { + closeModal, + saveIndex, +}; + +export default connect(mapState, mapDispatch)(CreateSearchIndexModal); diff --git a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx index a657b2394ae..edc3bdd24df 100644 --- a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx +++ b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.spec.tsx @@ -26,6 +26,7 @@ const renderIndexesToolbar = ( isAtlasSearchSupported={false} isRefreshing={false} onChangeIndexView={() => {}} + onClickCreateAtlasSearchIndex={() => {}} {...props} /> ); @@ -278,10 +279,14 @@ describe('IndexesToolbar Component', function () { }); describe('when atlas search is supported in the cluster', function () { + let onClickCreateAtlasSearchIndexSpy: () => void; + beforeEach(function () { + onClickCreateAtlasSearchIndexSpy = sinon.spy(); renderIndexesToolbar({ isAtlasSearchSupported: true, onChangeIndexView: onChangeViewCallback, + onClickCreateAtlasSearchIndex: onClickCreateAtlasSearchIndexSpy, }); }); @@ -294,6 +299,22 @@ describe('IndexesToolbar Component', function () { 'search-indexes' ); }); + + describe('when create search index button is clicked', function () { + it('should open the search index popup', async function () { + userEvent.click(screen.getByText('Create').closest('button')!); + + const searchIndexButtonWrapper = await screen.findByText( + 'Search Index' + ); + const searchIndexButton = + searchIndexButtonWrapper.closest('button')!; + + userEvent.click(searchIndexButton); + + expect(onClickCreateAtlasSearchIndexSpy).to.have.been.calledOnce; + }); + }); }); describe('when atlas search is not supported in the cluster', function () { diff --git a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx index f0d7df8a9fe..751cf72c295 100644 --- a/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx +++ b/packages/compass-indexes/src/components/indexes-toolbar/indexes-toolbar.tsx @@ -56,14 +56,13 @@ type IndexesToolbarProps = { isRefreshing: boolean; onRefreshIndexes: () => void; onChangeIndexView: (newView: IndexView) => void; - + onClickCreateAtlasSearchIndex: () => void; // connected: isReadonlyView: boolean; isWritable: boolean; localAppRegistry: AppRegistry; writeStateDescription?: string; isAtlasSearchSupported: boolean; - // via withPreferences: readOnly?: boolean; }; @@ -80,6 +79,7 @@ export const IndexesToolbar: React.FunctionComponent = ({ onRefreshIndexes, onChangeIndexView, readOnly, // preferences readOnly. + onClickCreateAtlasSearchIndex, }) => { const isSearchManagementActive = usePreference( 'enableAtlasSearchIndexManagement', @@ -90,9 +90,7 @@ export const IndexesToolbar: React.FunctionComponent = ({ const onClickCreateIndex = useCallback(() => { localAppRegistry.emit('open-create-index-modal'); }, [localAppRegistry]); - const onClickCreateAtlasSearchIndex = useCallback(() => { - localAppRegistry.emit('open-create-search-index-modal'); - }, [localAppRegistry]); + const onChangeIndexesSegment = useCallback( (value: string) => { const newView = value as IndexView; diff --git a/packages/compass-indexes/src/components/indexes/indexes.spec.tsx b/packages/compass-indexes/src/components/indexes/indexes.spec.tsx index 8a07c061060..fe2b86a47ca 100644 --- a/packages/compass-indexes/src/components/indexes/indexes.spec.tsx +++ b/packages/compass-indexes/src/components/indexes/indexes.spec.tsx @@ -23,7 +23,14 @@ const renderIndexes = (props: Partial = {}) => { const allProps: Partial = { regularIndexes: { indexes: [], error: null, isRefreshing: false }, - searchIndexes: { indexes: [], error: null, status: 'PENDING' }, + searchIndexes: { + indexes: [], + error: null, + status: 'PENDING', + createIndex: { + isModalOpen: false, + }, + }, ...props, }; diff --git a/packages/compass-indexes/src/components/indexes/indexes.tsx b/packages/compass-indexes/src/components/indexes/indexes.tsx index dbd323488cb..54bf38e07b2 100644 --- a/packages/compass-indexes/src/components/indexes/indexes.tsx +++ b/packages/compass-indexes/src/components/indexes/indexes.tsx @@ -7,12 +7,16 @@ import IndexesToolbar from '../indexes-toolbar/indexes-toolbar'; import RegularIndexesTable from '../regular-indexes-table/regular-indexes-table'; import SearchIndexesTable from '../search-indexes-table/search-indexes-table'; import { refreshRegularIndexes } from '../../modules/regular-indexes'; -import { refreshSearchIndexes } from '../../modules/search-indexes'; +import { + openModalForCreation as openAtlasSearchModalForCreation, + refreshSearchIndexes, +} from '../../modules/search-indexes'; import type { State as RegularIndexesState } from '../../modules/regular-indexes'; import type { State as SearchIndexesState } from '../../modules/search-indexes'; import { SearchIndexesStatuses } from '../../modules/search-indexes'; import type { SearchIndexesStatus } from '../../modules/search-indexes'; import type { RootState } from '../../modules'; +import CreateSearchIndexModal from '../create-search-index-modal'; // This constant is used as a trigger to show an insight whenever number of // indexes in a collection is more than what is specified here. @@ -35,6 +39,7 @@ type IndexesProps = { searchIndexes: Pick; refreshRegularIndexes: () => void; refreshSearchIndexes: () => void; + onClickCreateAtlasSearchIndex: () => void; }; function isRefreshingStatus(status: SearchIndexesStatus) { @@ -50,6 +55,7 @@ export function Indexes({ searchIndexes, refreshRegularIndexes, refreshSearchIndexes, + onClickCreateAtlasSearchIndex, }: IndexesProps) { const [currentIndexesView, setCurrentIndexesView] = useState('regular-indexes'); @@ -96,11 +102,12 @@ export function Indexes({ return (
{!isReadonlyView && currentIndexesView === 'regular-indexes' && ( @@ -108,6 +115,7 @@ export function Indexes({ {!isReadonlyView && currentIndexesView === 'search-indexes' && ( )} +
); } @@ -125,6 +133,7 @@ const mapState = ({ const mapDispatch = { refreshRegularIndexes, refreshSearchIndexes, + onClickCreateAtlasSearchIndex: openAtlasSearchModalForCreation, }; export default connect(mapState, mapDispatch)(Indexes); diff --git a/packages/compass-indexes/src/modules/search-indexes.spec.ts b/packages/compass-indexes/src/modules/search-indexes.spec.ts index 1e6a26eaf6d..8933882a4ca 100644 --- a/packages/compass-indexes/src/modules/search-indexes.spec.ts +++ b/packages/compass-indexes/src/modules/search-indexes.spec.ts @@ -1,13 +1,16 @@ import { expect } from 'chai'; -import sinon from 'sinon'; -import type { SearchIndex } from 'mongodb-data-service'; import { SearchIndexesStatuses, + closeModal, + openModalForCreation, + saveIndex, fetchSearchIndexes, sortSearchIndexes, } from './search-indexes'; -import type { IndexesDataService } from '../stores/store'; import { setupStore } from '../../test/setup-store'; +import sinon from 'sinon'; +import type { IndexesDataService } from '../stores/store'; +import type { SearchIndex } from 'mongodb-data-service'; import { readonlyViewChanged } from './is-readonly-view'; const searchIndexes: SearchIndex[] = [ @@ -29,12 +32,20 @@ const searchIndexes: SearchIndex[] = [ describe('search-indexes module', function () { let store: ReturnType; + let dataProvider: Partial; let getSearchIndexesStub: any; beforeEach(function () { - store = setupStore({ - isSearchIndexesSupported: true, - }); + dataProvider = { + createSearchIndex: sinon.spy(), + }; + + store = setupStore( + { + isSearchIndexesSupported: true, + }, + dataProvider + ); getSearchIndexesStub = sinon .stub( @@ -181,4 +192,21 @@ describe('search-indexes module', function () { ]); }); }); + + it('opens the modal for creation', function () { + store.dispatch(openModalForCreation()); + expect(store.getState().searchIndexes.createIndex.isModalOpen).to.be.true; + }); + + it('closes an open modal', function () { + store.dispatch(openModalForCreation()); + store.dispatch(closeModal()); + expect(store.getState().searchIndexes.createIndex.isModalOpen).to.be.false; + }); + + it('creates the index when data is valid', async function () { + await store.dispatch(saveIndex('indexName', {})); + expect(store.getState().searchIndexes.createIndex.isModalOpen).to.be.false; + expect(dataProvider.createSearchIndex).to.have.been.calledOnce; + }); }); diff --git a/packages/compass-indexes/src/modules/search-indexes.ts b/packages/compass-indexes/src/modules/search-indexes.ts index e23a0cae55a..1adb529f3b9 100644 --- a/packages/compass-indexes/src/modules/search-indexes.ts +++ b/packages/compass-indexes/src/modules/search-indexes.ts @@ -1,6 +1,14 @@ import type { AnyAction } from 'redux'; import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; import { isAction } from './../utils/is-action'; +import { openToast } from '@mongodb-js/compass-components'; +import type { Document, MongoServerError } from 'mongodb'; + +const ATLAS_SEARCH_SERVER_ERRORS: Record = { + InvalidIndexSpecificationOption: 'Invalid index definition.', + IndexAlreadyExists: + 'This index name is already in use. Please choose another one.', +}; import type { SortDirection, IndexesThunkAction } from '.'; import type { SearchIndex } from 'mongodb-data-service'; @@ -40,11 +48,15 @@ export enum SearchIndexesStatuses { export type SearchIndexesStatus = keyof typeof SearchIndexesStatuses; export enum ActionTypes { + SetStatus = 'indexes/search-indexes/SetStatus', + OpenCreateSearchIndexModal = 'indexes/search-indexes/OpenCreateSearchIndexModal', + CreateSearchIndexStarted = 'indexes/search-indexes/CreateSearchIndexStarted', + CreateSearchIndexFailed = 'indexes/search-indexes/CreateSearchIndexFailed', + CreateSearchIndexSucceeded = 'indexes/search-indexes/CreateSearchIndexSucceed', + CreateSearchIndexCancelled = 'indexes/search-indexes/CreateSearchIndexCancelled', SetIsRefreshing = 'indexes/search-indexes/SetIsRefreshing', - SetSearchIndexes = 'indexes/search-indexes/SetSearchIndexes', SearchIndexesSorted = 'indexes/search-indexes/SearchIndexesSorted', - SetError = 'indexes/search-indexes/SetError', } @@ -52,6 +64,41 @@ type SetIsRefreshingAction = { type: ActionTypes.SetIsRefreshing; }; +type OpenCreateSearchIndexModalAction = { + type: ActionTypes.OpenCreateSearchIndexModal; +}; + +type CreateSearchIndexStartedAction = { + type: ActionTypes.CreateSearchIndexStarted; +}; + +type CreateSearchIndexFailedAction = { + type: ActionTypes.CreateSearchIndexFailed; + error: string; +}; + +type CreateSearchIndexSucceededAction = { + type: ActionTypes.CreateSearchIndexSucceeded; +}; + +type CreateSearchIndexCancelledAction = { + type: ActionTypes.CreateSearchIndexCancelled; +}; + +type CreateSearchIndexState = { + isModalOpen: boolean; + isBusy: boolean; +}; + +export type State = { + status: SearchIndexesStatus; + createIndex: CreateSearchIndexState; + error?: string; + indexes: SearchIndex[]; + sortOrder: SortDirection; + sortColumn: SearchSortColumn; +}; + type SetSearchIndexesAction = { type: ActionTypes.SetSearchIndexes; indexes: SearchIndex[]; @@ -66,7 +113,7 @@ type SearchIndexesSortedAction = { type SetErrorAction = { type: ActionTypes.SetError; - error: string | null; + error: string | undefined; }; type SearchIndexesActions = @@ -75,20 +122,16 @@ type SearchIndexesActions = | SearchIndexesSortedAction | SetErrorAction; -export type State = { - status: SearchIndexesStatus; - indexes: SearchIndex[]; - sortOrder: SortDirection; - sortColumn: SearchSortColumn; - error: string | null; -}; - export const INITIAL_STATE: State = { status: SearchIndexesStatuses.NOT_AVAILABLE, + createIndex: { + isModalOpen: false, + isBusy: false, + }, + error: undefined, indexes: [], sortOrder: 'asc', sortColumn: 'Name and Fields', - error: null, }; export default function reducer( @@ -99,7 +142,7 @@ export default function reducer( return { ...state, status: SearchIndexesStatuses.REFRESHING, - error: null, + error: undefined, }; } @@ -129,6 +172,79 @@ export default function reducer( error: action.error, status: SearchIndexesStatuses.ERROR, }; + } else if ( + isAction( + action, + ActionTypes.OpenCreateSearchIndexModal + ) + ) { + return { + ...state, + error: undefined, + createIndex: { + ...state.createIndex, + isModalOpen: true, + isBusy: false, + }, + }; + } else if ( + isAction( + action, + ActionTypes.CreateSearchIndexCancelled + ) + ) { + return { + ...state, + error: undefined, + createIndex: { + ...state.createIndex, + isModalOpen: false, + isBusy: false, + }, + }; + } else if ( + isAction( + action, + ActionTypes.CreateSearchIndexStarted + ) + ) { + return { + ...state, + error: undefined, + createIndex: { + ...state.createIndex, + isBusy: true, + }, + }; + } else if ( + isAction( + action, + ActionTypes.CreateSearchIndexFailed + ) + ) { + return { + ...state, + error: action.error, + createIndex: { + ...state.createIndex, + isBusy: false, + }, + }; + } else if ( + isAction( + action, + ActionTypes.CreateSearchIndexSucceeded + ) + ) { + return { + ...state, + error: undefined, + createIndex: { + ...state.createIndex, + isModalOpen: false, + isBusy: false, + }, + }; } return state; @@ -139,7 +255,68 @@ const setSearchIndexes = (indexes: SearchIndex[]): SetSearchIndexesAction => ({ indexes, }); -const setError = (error: string | null): SetErrorAction => ({ +export const openModalForCreation = (): OpenCreateSearchIndexModalAction => ({ + type: ActionTypes.OpenCreateSearchIndexModal, +}); + +export const closeModal = (): CreateSearchIndexCancelledAction => ({ + type: ActionTypes.CreateSearchIndexCancelled, +}); + +export const createIndexStarted = (): CreateSearchIndexStartedAction => ({ + type: ActionTypes.CreateSearchIndexStarted, +}); + +export const createIndexFailed = ( + error: string +): CreateSearchIndexFailedAction => ({ + type: ActionTypes.CreateSearchIndexFailed, + error: ATLAS_SEARCH_SERVER_ERRORS[error] || error, +}); + +export const createIndexSucceeded = (): CreateSearchIndexSucceededAction => ({ + type: ActionTypes.CreateSearchIndexSucceeded, +}); + +export const saveIndex = ( + indexName: string, + indexDefinition: Document +): IndexesThunkAction> => { + return async function (dispatch, getState) { + const { namespace, dataService } = getState(); + + dispatch(createIndexStarted()); + + if (indexName === '') { + dispatch(createIndexFailed('Please enter the name of the index.')); + return; + } + + try { + await dataService?.createSearchIndex( + namespace, + indexName, + indexDefinition + ); + } catch (ex) { + dispatch( + createIndexFailed( + (ex as MongoServerError).codeName || (ex as Error).message + ) + ); + return; + } + + dispatch(createIndexSucceeded()); + openToast('index-creation-in-progress', { + title: `Your index ${indexName} is in progress.`, + dismissible: true, + timeout: 5000, + variant: 'success', + }); + }; +}; +const setError = (error: string | undefined): SetErrorAction => ({ type: ActionTypes.SetError, error, }); diff --git a/packages/compass-indexes/test/setup-store.ts b/packages/compass-indexes/test/setup-store.ts index 2951d1591f0..9b5c0501de4 100644 --- a/packages/compass-indexes/test/setup-store.ts +++ b/packages/compass-indexes/test/setup-store.ts @@ -6,7 +6,48 @@ import type { } from '../src/stores/store'; import configureStore from '../src/stores/store'; -export const setupStore = (options: Partial = {}) => { +const NOOP_DATA_PROVIDER: IndexesDataService = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + indexes(ns: string, options: any) { + return Promise.resolve([]); + }, + isConnected() { + return true; + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + updateCollection(ns: string, flags: any) { + return Promise.resolve({}); + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + createIndex(ns, spec, options) { + return Promise.resolve('ok'); + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + dropIndex(ns, name) { + return Promise.resolve({}); + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getSearchIndexes(ns: string) { + return Promise.resolve([]); + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + createSearchIndex(ns: string, name: string, spec: any) { + return Promise.resolve('new-id'); + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + updateSearchIndex(ns: string, name: string, spec: any) { + return Promise.resolve(); + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + dropSearchIndex(ns: string, name: string) { + return Promise.resolve(); + }, +}; + +export const setupStore = ( + options: Partial = {}, + dataProvider: Partial = NOOP_DATA_PROVIDER +) => { const localAppRegistry = { on: Sinon.spy(), emit: Sinon.spy(), @@ -17,48 +58,10 @@ export const setupStore = (options: Partial = {}) => { getStore: Sinon.spy(), } as unknown as AppRegistry; - const dataProvider: IndexesDataService = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - indexes(ns: string, options: any) { - return Promise.resolve([]); - }, - isConnected() { - return true; - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - updateCollection(ns: string, flags: any) { - return Promise.resolve({}); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - createIndex(ns, spec, options) { - return Promise.resolve('ok'); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - dropIndex(ns, name) { - return Promise.resolve({}); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getSearchIndexes(ns: string) { - return Promise.resolve([]); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - createSearchIndex(ns: string, name: string, spec: any) { - return Promise.resolve('new-id'); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - updateSearchIndex(ns: string, name: string, spec: any) { - return Promise.resolve(); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - dropSearchIndex(ns: string, name: string) { - return Promise.resolve(); - }, - }; - return configureStore({ namespace: 'citibike.trips', dataProvider: { - dataProvider: dataProvider, + dataProvider: { ...NOOP_DATA_PROVIDER, ...dataProvider }, }, serverVersion: '6.0.0', isReadonly: false,