diff --git a/packages/compass-indexes/src/components/create-index-actions/create-index-actions.spec.jsx b/packages/compass-indexes/src/components/create-index-actions/create-index-actions.spec.jsx
index ba27d6421f3..f76599042c2 100644
--- a/packages/compass-indexes/src/components/create-index-actions/create-index-actions.spec.jsx
+++ b/packages/compass-indexes/src/components/create-index-actions/create-index-actions.spec.jsx
@@ -5,15 +5,12 @@ import sinon from 'sinon';
import {
render,
screen,
- cleanup,
- fireEvent,
+ userEvent,
within,
} from '@mongodb-js/testing-library-compass';
import CreateIndexActions from '../create-index-actions';
-const noop = () => {};
-
describe('CreateIndexActions Component', function () {
let clearErrorSpy;
let onCreateIndexClickSpy;
@@ -29,8 +26,6 @@ describe('CreateIndexActions Component', function () {
clearErrorSpy = null;
onCreateIndexClickSpy = null;
closeCreateIndexModalSpy = null;
-
- cleanup();
});
it('renders a cancel button', function () {
@@ -38,7 +33,6 @@ describe('CreateIndexActions Component', function () {
@@ -54,14 +48,13 @@ describe('CreateIndexActions Component', function () {
);
const button = screen.getByTestId('create-index-actions-cancel-button');
- fireEvent.click(button);
+ userEvent.click(button);
expect(closeCreateIndexModalSpy).to.have.been.calledOnce;
});
});
@@ -72,7 +65,6 @@ describe('CreateIndexActions Component', function () {
@@ -81,7 +73,7 @@ describe('CreateIndexActions Component', function () {
const button = screen.getByTestId(
'create-index-actions-create-index-button'
);
- fireEvent.click(button);
+ userEvent.click(button);
expect(onCreateIndexClickSpy).to.have.been.calledOnce;
});
});
@@ -91,7 +83,6 @@ describe('CreateIndexActions Component', function () {
@@ -109,7 +100,6 @@ describe('CreateIndexActions Component', function () {
@@ -126,7 +116,6 @@ describe('CreateIndexActions Component', function () {
@@ -137,25 +126,9 @@ describe('CreateIndexActions Component', function () {
);
const closeIcon = within(errorBanner).getByLabelText('X Icon');
- fireEvent.click(closeIcon);
+ userEvent.click(closeIcon);
expect(clearErrorSpy).to.have.been.calledOnce;
});
-
- it('does not render in progress banner', function () {
- render(
-
- );
-
- const inProgressBanner = screen.queryByTestId(
- 'create-index-actions-in-progress-banner-wrapper'
- );
- expect(inProgressBanner).to.not.exist;
- });
});
context('without error', function () {
@@ -164,7 +137,6 @@ describe('CreateIndexActions Component', function () {
@@ -175,42 +147,5 @@ describe('CreateIndexActions Component', function () {
);
expect(errorBanner).to.not.exist;
});
-
- context('when in progress', function () {
- beforeEach(function () {
- render(
-
- );
- });
-
- afterEach(cleanup);
-
- it('renders in progress banner', function () {
- const inProgressBanner = screen.getByTestId(
- 'create-index-actions-in-progress-banner-wrapper'
- );
- expect(inProgressBanner).to.contain.text('Index creation in progress');
- });
-
- it('hides the create index button', function () {
- const onCreateIndexClickButton = screen.queryByTestId(
- 'create-index-actions-create-index-button'
- );
- expect(onCreateIndexClickButton).to.not.exist;
- });
-
- it('renames the cancel button to close', function () {
- const cancelButton = screen.getByTestId(
- 'create-index-actions-cancel-button'
- );
- expect(cancelButton.textContent).to.be.equal('Close');
- });
- });
});
});
diff --git a/packages/compass-indexes/src/components/create-index-actions/create-index-actions.tsx b/packages/compass-indexes/src/components/create-index-actions/create-index-actions.tsx
index cc1f9ab9991..ab6aab6d67f 100644
--- a/packages/compass-indexes/src/components/create-index-actions/create-index-actions.tsx
+++ b/packages/compass-indexes/src/components/create-index-actions/create-index-actions.tsx
@@ -24,72 +24,46 @@ const createIndexButtonStyles = css({
*/
function CreateIndexActions({
error,
- inProgress,
onErrorBannerCloseClick,
onCreateIndexClick,
onCancelCreateIndexClick,
}: {
error: string | null;
- inProgress: boolean;
onErrorBannerCloseClick: () => void;
onCreateIndexClick: () => void;
onCancelCreateIndexClick: () => void;
}) {
- const renderError = () => {
- if (!error) {
- return;
- }
-
- return (
-
-
- {error}
-
-
- );
- };
-
- const renderInProgress = () => {
- if (error || !inProgress) {
- return;
- }
-
- return (
-
-
- Index creation in progress. The dialog can be closed.
-
-
- );
- };
-
return (
- {renderError()}
- {renderInProgress()}
+ {error && (
+
+
+ {error}
+
+
+ )}
+
- {!inProgress && (
-
- )}
);
}
diff --git a/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx b/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx
index 189c6988968..2f4e744bc66 100644
--- a/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx
+++ b/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx
@@ -12,7 +12,7 @@ import {
fieldTypeUpdated,
updateFieldName,
errorCleared,
- createIndex,
+ createIndexFormSubmitted,
createIndexClosed,
} from '../../modules/create-index';
import { CreateIndexForm } from '../create-index-form/create-index-form';
@@ -28,7 +28,6 @@ type CreateIndexModalProps = React.ComponentProps & {
isVisible: boolean;
namespace: string;
error: string | null;
- inProgress: boolean;
onErrorBannerCloseClick: () => void;
onCreateIndexClick: () => void;
onCancelCreateIndexClick: () => void;
@@ -38,7 +37,6 @@ function CreateIndexModal({
isVisible,
namespace,
error,
- inProgress,
onErrorBannerCloseClick,
onCreateIndexClick,
onCancelCreateIndexClick,
@@ -88,7 +86,6 @@ function CreateIndexModal({
@@ -98,10 +95,9 @@ function CreateIndexModal({
}
const mapState = ({ namespace, serverVersion, createIndex }: RootState) => {
- const { fields, inProgress, error, isVisible } = createIndex;
+ const { fields, error, isVisible } = createIndex;
return {
fields,
- inProgress,
error,
isVisible,
namespace,
@@ -111,7 +107,7 @@ const mapState = ({ namespace, serverVersion, createIndex }: RootState) => {
const mapDispatch = {
onErrorBannerCloseClick: errorCleared,
- onCreateIndexClick: createIndex,
+ onCreateIndexClick: createIndexFormSubmitted,
onCancelCreateIndexClick: createIndexClosed,
onAddFieldClick: fieldAdded,
onRemoveFieldClick: fieldRemoved,
diff --git a/packages/compass-indexes/src/modules/create-index.spec.ts b/packages/compass-indexes/src/modules/create-index.spec.ts
index d96ee47e06a..dd46621441b 100644
--- a/packages/compass-indexes/src/modules/create-index.spec.ts
+++ b/packages/compass-indexes/src/modules/create-index.spec.ts
@@ -1,10 +1,9 @@
import { expect } from 'chai';
-import sinon from 'sinon';
import { setupStore } from '../../test/setup-store';
import {
- createIndex,
+ createIndexFormSubmitted,
updateFieldName,
fieldAdded,
fieldRemoved,
@@ -14,7 +13,6 @@ import {
createIndexOpened,
createIndexClosed,
errorCleared,
- INITIAL_STATE,
} from './create-index';
import type { IndexesStore } from '../stores/store';
@@ -24,13 +22,13 @@ describe('create-index module', function () {
store = setupStore();
});
- describe('#createIndex', function () {
+ describe('#createIndexFormSubmitted', function () {
beforeEach(function () {
store.dispatch(updateFieldName(0, 'foo'));
store.dispatch(fieldTypeUpdated(0, 'text'));
});
- it('validates field name & type', async function () {
+ it('validates field name & type', function () {
Object.assign(store.getState(), {
createIndex: {
...store.getState().createIndex,
@@ -42,14 +40,14 @@ describe('create-index module', function () {
],
},
});
- await store.dispatch(createIndex());
+ store.dispatch(createIndexFormSubmitted());
expect(store.getState().createIndex.error).to.equal(
'You must select a field name and type'
);
});
- it('validates collation', async function () {
+ it('validates collation', function () {
Object.assign(store.getState(), {
createIndex: {
...store.getState().createIndex,
@@ -63,14 +61,14 @@ describe('create-index module', function () {
},
},
});
- await store.dispatch(createIndex());
+ store.dispatch(createIndexFormSubmitted());
expect(store.getState().createIndex.error).to.equal(
'You must provide a valid collation object'
);
});
- it('validates TTL', async function () {
+ it('validates TTL', function () {
Object.assign(store.getState(), {
createIndex: {
...store.getState().createIndex,
@@ -84,14 +82,14 @@ describe('create-index module', function () {
},
},
});
- await store.dispatch(createIndex());
+ store.dispatch(createIndexFormSubmitted());
expect(store.getState().createIndex.error).to.equal(
'Bad TTL: "not a ttl"'
);
});
- it('validates wildcard projection', async function () {
+ it('validates wildcard projection', function () {
Object.assign(store.getState(), {
createIndex: {
...store.getState().createIndex,
@@ -105,14 +103,14 @@ describe('create-index module', function () {
},
},
});
- await store.dispatch(createIndex());
+ store.dispatch(createIndexFormSubmitted());
expect(store.getState().createIndex.error).to.equal(
'Bad WildcardProjection: SyntaxError: Unexpected token \'o\', "not a wildc"... is not valid JSON'
);
});
- it('validates columnstore projection', async function () {
+ it('validates columnstore projection', function () {
Object.assign(store.getState(), {
createIndex: {
...store.getState().createIndex,
@@ -126,14 +124,14 @@ describe('create-index module', function () {
},
},
});
- await store.dispatch(createIndex());
+ store.dispatch(createIndexFormSubmitted());
expect(store.getState().createIndex.error).to.equal(
'Bad ColumnstoreProjection: SyntaxError: Unexpected token \'o\', "not a colum"... is not valid JSON'
);
});
- it('validates partial filter expression', async function () {
+ it('validates partial filter expression', function () {
Object.assign(store.getState(), {
createIndex: {
...store.getState().createIndex,
@@ -147,124 +145,12 @@ describe('create-index module', function () {
},
},
});
- await store.dispatch(createIndex());
+ store.dispatch(createIndexFormSubmitted());
expect(store.getState().createIndex.error).to.equal(
'Bad PartialFilterExpression: SyntaxError: Unexpected end of JSON input'
);
});
-
- it('succeeds if dataService.createIndex() resolves', async function () {
- let stateBeforeCreateIndex;
-
- const createIndexStub = sinon
- .stub()
- .callsFake(async (): Promise => {
- // store it so we can assert on the in-between state
- stateBeforeCreateIndex = { ...store.getState().createIndex };
- return Promise.resolve('ok');
- });
-
- store = setupStore(
- {},
- {
- createIndex: createIndexStub,
- }
- );
-
- store.dispatch(updateFieldName(0, 'foo'));
- store.dispatch(fieldTypeUpdated(0, '1 (asc)'));
-
- store.dispatch(optionToggled('unique', true));
- store.dispatch(optionToggled('name', true));
- store.dispatch(optionChanged('name', 'my-index'));
- store.dispatch(optionToggled('expireAfterSeconds', true));
- store.dispatch(optionChanged('expireAfterSeconds', '60'));
- store.dispatch(optionToggled('partialFilterExpression', true));
- store.dispatch(
- optionChanged('partialFilterExpression', '{ "rating": { "$gt": 5 } }')
- );
-
- await store.dispatch(createIndex());
-
- // make sure it got to insert
- expect(createIndexStub.callCount).to.equal(1);
-
- // it should have set it to be in progress before calling dataService.createIndex
- expect(stateBeforeCreateIndex).to.deep.equal({
- inProgress: true,
- isVisible: false,
- error: null,
- fields: [{ name: 'foo', type: '1 (asc)' }],
- options: {
- unique: { value: false, enabled: true },
- name: { value: 'my-index', enabled: true },
- expireAfterSeconds: { value: '60', enabled: true },
- partialFilterExpression: {
- value: '{ "rating": { "$gt": 5 } }',
- enabled: true,
- },
- wildcardProjection: { value: '', enabled: false },
- collation: { value: '', enabled: false },
- columnstoreProjection: { value: '', enabled: false },
- sparse: { value: false, enabled: false },
- },
- });
-
- const [ns, spec, options] = createIndexStub.args[0];
- expect(ns).to.equal('citibike.trips');
- expect(spec).to.deep.equal({ foo: 1 });
- expect(options).to.deep.equal({
- expireAfterSeconds: 60,
- name: 'my-index',
- partialFilterExpression: {
- rating: {
- $gt: 5,
- },
- },
- });
-
- expect(store.getState().createIndex).to.deep.equal(INITIAL_STATE);
- });
-
- it('fails if dataService.createIndex() rejects', async function () {
- const createIndexStub = sinon
- .stub()
- .rejects(new Error('This is an error'));
-
- store = setupStore(
- {},
- {
- createIndex: createIndexStub,
- }
- );
-
- store.dispatch(updateFieldName(0, 'foo'));
- store.dispatch(fieldTypeUpdated(0, 'text'));
-
- await store.dispatch(createIndex());
-
- // make sure it got to insert
- expect(createIndexStub.callCount).to.equal(1);
-
- // state should be there with an error, not inProgress anymore
- expect(store.getState().createIndex).to.deep.equal({
- inProgress: false,
- isVisible: false,
- error: 'This is an error',
- fields: [{ name: 'foo', type: 'text' }],
- options: {
- unique: { value: false, enabled: false },
- name: { value: '', enabled: false },
- expireAfterSeconds: { value: '', enabled: false },
- partialFilterExpression: { value: '', enabled: false },
- wildcardProjection: { value: '', enabled: false },
- collation: { value: '', enabled: false },
- columnstoreProjection: { value: '', enabled: false },
- sparse: { value: false, enabled: false },
- },
- });
- });
});
describe('fieldAdded', function () {
diff --git a/packages/compass-indexes/src/modules/create-index.tsx b/packages/compass-indexes/src/modules/create-index.tsx
index f9ada33fa4a..fb5d380197c 100644
--- a/packages/compass-indexes/src/modules/create-index.tsx
+++ b/packages/compass-indexes/src/modules/create-index.tsx
@@ -1,15 +1,13 @@
import { EJSON, ObjectId } from 'bson';
-import type { CreateIndexesOptions, IndexSpecification } from 'mongodb';
+import type { CreateIndexesOptions } from 'mongodb';
import { isCollationValid } from 'mongodb-query-parser';
import React from 'react';
import type { Action, Reducer, Dispatch } from 'redux';
import { Badge } from '@mongodb-js/compass-components';
import { isAction } from '../utils/is-action';
-import type { InProgressIndex } from './regular-indexes';
import type { IndexesThunkAction } from '.';
-import { hasColumnstoreIndex } from '../utils/columnstore-indexes';
import type { RootState } from '.';
-import { refreshRegularIndexes } from './regular-indexes';
+import { createRegularIndex } from './regular-indexes';
export enum ActionTypes {
FieldAdded = 'compass-indexes/create-index/fields/field-added',
@@ -26,10 +24,7 @@ export enum ActionTypes {
CreateIndexOpened = 'compass-indexes/create-index/create-index-shown',
CreateIndexClosed = 'compass-indexes/create-index/create-index-hidden',
- // These also get used by the regular-indexes slice's reducer
- IndexCreationStarted = 'compass-indexes/create-index/index-creation-started',
- IndexCreationSucceeded = 'compass-indexes/create-index/index-creation-succeeded',
- IndexCreationFailed = 'compass-indexes/create-index/index-creation-failed',
+ CreateIndexFormSubmitted = 'compass-indexes/create-index/create-index-form-submitted',
}
// fields
@@ -58,6 +53,10 @@ type FieldsChangedAction = {
fields: Field[];
};
+/**
+ * Emitted only when the form fails client-side validation before being
+ * submitted
+ */
type ErrorEncounteredAction = {
type: ActionTypes.ErrorEncountered;
error: string;
@@ -75,20 +74,12 @@ type CreateIndexClosedAction = {
type: ActionTypes.CreateIndexClosed;
};
-export type IndexCreationStartedAction = {
- type: ActionTypes.IndexCreationStarted;
- inProgressIndex: InProgressIndex;
-};
-
-export type IndexCreationSucceededAction = {
- type: ActionTypes.IndexCreationSucceeded;
- inProgressIndexId: string;
-};
-
-export type IndexCreationFailedAction = {
- type: ActionTypes.IndexCreationFailed;
- inProgressIndexId: string;
- error: string;
+/**
+ * Dispatched when the form passed the client validation and the form data was
+ * submitted for index creation
+ */
+type CreateIndexFormSubmittedAction = {
+ type: ActionTypes.CreateIndexFormSubmitted;
};
export const fieldAdded = () => ({
@@ -259,11 +250,15 @@ const INITIAL_OPTIONS_STATE = Object.fromEntries(
// other
export type State = {
- // modal state
- inProgress: boolean;
+ // A unique id assigned to the create index modal on open, will be used when
+ // creating an instance of in-progress index and can be used to map the index
+ // to the form if needed
+ indexId: string;
+
+ // Whether or not the modal is open or closed
isVisible: boolean;
- // validation
+ // Client-side validation error
error: string | null;
// form fields related
@@ -274,15 +269,18 @@ export type State = {
};
export const INITIAL_STATE: State = {
- inProgress: false,
+ indexId: new ObjectId().toHexString(),
isVisible: false,
error: null,
fields: INITIAL_FIELDS_STATE,
options: INITIAL_OPTIONS_STATE,
};
-function getInitialState() {
- return JSON.parse(JSON.stringify(INITIAL_STATE));
+function getInitialState(): State {
+ return {
+ ...JSON.parse(JSON.stringify(INITIAL_STATE)),
+ indexId: new ObjectId().toHexString(),
+ };
}
//-------
@@ -304,67 +302,9 @@ export const errorCleared = (): ErrorClearedAction => ({
type: ActionTypes.ErrorCleared,
});
-const indexCreationStarted = (
- inProgressIndex: InProgressIndex
-): IndexCreationStartedAction => ({
- type: ActionTypes.IndexCreationStarted,
- inProgressIndex,
-});
-
-const indexCreationSucceeded = (
- inProgressIndexId: string
-): IndexCreationSucceededAction => ({
- type: ActionTypes.IndexCreationSucceeded,
- inProgressIndexId,
-});
-
-const indexCreationFailed = (
- inProgressIndexId: string,
- error: string
-): IndexCreationFailedAction => ({
- type: ActionTypes.IndexCreationFailed,
- inProgressIndexId,
- error,
-});
-
export type CreateIndexSpec = {
[key: string]: string | number;
};
-const prepareIndex = ({
- ns,
- name,
- spec,
-}: {
- ns: string;
- name?: string;
- spec: CreateIndexSpec;
-}): InProgressIndex => {
- const inProgressIndexId = new ObjectId().toHexString();
- const inProgressIndexFields = Object.keys(spec).map((field: string) => ({
- field,
- value: spec[field],
- }));
- const inProgressIndexName =
- name ||
- Object.keys(spec).reduce((previousValue, currentValue) => {
- return `${
- previousValue === '' ? '' : `${previousValue}_`
- }${currentValue}_${spec[currentValue]}`;
- }, '');
- return {
- id: inProgressIndexId,
- extra: {
- status: 'inprogress',
- },
- key: spec,
- fields: inProgressIndexFields,
- name: inProgressIndexName,
- ns,
- size: 0,
- relativeSize: 0,
- usageCount: 0,
- };
-};
function isEmptyValue(value: unknown) {
if (value === '') {
@@ -377,24 +317,16 @@ function isEmptyValue(value: unknown) {
return false;
}
-export const createIndex = (): IndexesThunkAction<
- Promise,
- | ErrorEncounteredAction
- | IndexCreationStartedAction
- | IndexCreationSucceededAction
- | IndexCreationFailedAction
+export const createIndexFormSubmitted = (): IndexesThunkAction<
+ void,
+ ErrorEncounteredAction | CreateIndexFormSubmittedAction
> => {
- return async (
- dispatch,
- getState,
- { dataService, track, connectionInfoRef }
- ) => {
- const state = getState();
+ return (dispatch, getState) => {
const spec = {} as CreateIndexSpec;
// Check for field errors.
if (
- state.createIndex.fields.some(
+ getState().createIndex.fields.some(
(field: Field) => field.name === '' || field.type === ''
)
) {
@@ -402,9 +334,9 @@ export const createIndex = (): IndexesThunkAction<
return;
}
- const stateOptions = state.createIndex.options;
+ const formIndexOptions = getState().createIndex.options;
- state.createIndex.fields.forEach((field: Field) => {
+ getState().createIndex.fields.forEach((field: Field) => {
let type: string | number = field.type;
if (field.type === '1 (asc)') type = 1;
if (field.type === '-1 (desc)') type = -1;
@@ -415,48 +347,48 @@ export const createIndex = (): IndexesThunkAction<
// Check for collation errors.
const collation =
- isCollationValid(stateOptions.collation.value ?? '') || undefined;
+ isCollationValid(formIndexOptions.collation.value ?? '') || undefined;
- if (stateOptions.collation.enabled && !collation) {
+ if (formIndexOptions.collation.enabled && !collation) {
dispatch(errorEncountered('You must provide a valid collation object'));
return;
}
- if (stateOptions.collation.enabled) {
+ if (formIndexOptions.collation.enabled) {
options.collation = collation;
}
- if (stateOptions.unique.enabled) {
- options.unique = stateOptions.unique.value;
+ if (formIndexOptions.unique.enabled) {
+ options.unique = formIndexOptions.unique.value;
}
- if (stateOptions.sparse.enabled) {
- options.sparse = stateOptions.sparse.value;
+ if (formIndexOptions.sparse.enabled) {
+ options.sparse = formIndexOptions.sparse.value;
}
// The server will generate a name when we don't provide one.
- if (stateOptions.name.enabled && stateOptions.name.value) {
- options.name = stateOptions.name.value;
+ if (formIndexOptions.name.enabled && formIndexOptions.name.value) {
+ options.name = formIndexOptions.name.value;
}
- if (stateOptions.expireAfterSeconds.enabled) {
+ if (formIndexOptions.expireAfterSeconds.enabled) {
options.expireAfterSeconds = Number(
- stateOptions.expireAfterSeconds.value
+ formIndexOptions.expireAfterSeconds.value
);
if (isNaN(options.expireAfterSeconds)) {
dispatch(
errorEncountered(
- `Bad TTL: "${String(stateOptions.expireAfterSeconds.value)}"`
+ `Bad TTL: "${String(formIndexOptions.expireAfterSeconds.value)}"`
)
);
return;
}
}
- if (stateOptions.wildcardProjection.enabled) {
+ if (formIndexOptions.wildcardProjection.enabled) {
try {
options.wildcardProjection = EJSON.parse(
- stateOptions.wildcardProjection.value ?? ''
+ formIndexOptions.wildcardProjection.value ?? ''
) as Document;
} catch (err) {
dispatch(errorEncountered(`Bad WildcardProjection: ${String(err)}`));
@@ -464,11 +396,11 @@ export const createIndex = (): IndexesThunkAction<
}
}
- if (stateOptions.columnstoreProjection.enabled) {
+ if (formIndexOptions.columnstoreProjection.enabled) {
try {
// @ts-expect-error columnstoreProjection is not a part of CreateIndexesOptions yet.
options.columnstoreProjection = EJSON.parse(
- stateOptions.columnstoreProjection.value ?? ''
+ formIndexOptions.columnstoreProjection.value ?? ''
) as Document;
} catch (err) {
dispatch(errorEncountered(`Bad ColumnstoreProjection: ${String(err)}`));
@@ -476,10 +408,10 @@ export const createIndex = (): IndexesThunkAction<
}
}
- if (stateOptions.partialFilterExpression.enabled) {
+ if (formIndexOptions.partialFilterExpression.enabled) {
try {
options.partialFilterExpression = EJSON.parse(
- state.createIndex.options.partialFilterExpression.value ?? ''
+ formIndexOptions.partialFilterExpression.value ?? ''
) as Document;
} catch (err) {
dispatch(
@@ -495,45 +427,18 @@ export const createIndex = (): IndexesThunkAction<
// explicitly can lead to the server errors for some index types that don't
// support them (even though technically user is not enabling them)
for (const optionName of Object.keys(
- stateOptions
- ) as (keyof typeof stateOptions)[]) {
- if (isEmptyValue(stateOptions[optionName].value)) {
+ formIndexOptions
+ ) as (keyof typeof formIndexOptions)[]) {
+ if (isEmptyValue(formIndexOptions[optionName].value)) {
// @ts-expect-error columnstoreProjection is not a part of CreateIndexesOptions yet.
delete options[optionName];
}
}
- const ns = state.namespace;
- const inProgressIndex = prepareIndex({ ns, name: options.name, spec });
-
- dispatch(indexCreationStarted(inProgressIndex));
-
- const trackEvent = {
- unique: options.unique,
- ttl: stateOptions.expireAfterSeconds.enabled,
- columnstore_index: hasColumnstoreIndex(state.createIndex.fields),
- has_columnstore_projection: stateOptions.columnstoreProjection.enabled,
- has_wildcard_projection: stateOptions.wildcardProjection.enabled,
- custom_collation: stateOptions.collation.enabled,
- geo:
- state.createIndex.fields.filter(
- ({ type }: { type: string }) => type === '2dsphere'
- ).length > 0,
- atlas_search: false,
- };
-
- try {
- await dataService.createIndex(ns, spec as IndexSpecification, options);
- dispatch(indexCreationSucceeded(inProgressIndex.id));
- track('Index Created', trackEvent, connectionInfoRef.current);
-
- // Start a new fetch so that the newly added index's details can be
- // loaded. indexCreationSucceeded() will remove the in-progress one, but
- // we still need the new info.
- await dispatch(refreshRegularIndexes());
- } catch (err) {
- dispatch(indexCreationFailed(inProgressIndex.id, (err as Error).message));
- }
+ dispatch({ type: ActionTypes.CreateIndexFormSubmitted });
+ void dispatch(
+ createRegularIndex(getState().createIndex.indexId, spec, options)
+ );
};
};
@@ -609,7 +514,7 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => {
isAction(action, ActionTypes.CreateIndexOpened)
) {
return {
- ...state,
+ ...getInitialState(),
isVisible: true,
};
}
@@ -638,34 +543,17 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => {
}
if (
- isAction(
+ isAction(
action,
- ActionTypes.IndexCreationStarted
+ ActionTypes.CreateIndexFormSubmitted
)
) {
return {
...state,
- error: null,
- inProgress: true,
- };
- }
- if (
- isAction(
- action,
- ActionTypes.IndexCreationSucceeded
- )
- ) {
- return {
- ...getInitialState(),
+ isVisible: false,
};
}
- if (
- isAction(action, ActionTypes.IndexCreationFailed)
- ) {
- return { ...state, inProgress: false, error: action.error };
- }
-
return state;
};
diff --git a/packages/compass-indexes/src/modules/regular-indexes.ts b/packages/compass-indexes/src/modules/regular-indexes.ts
index e51e1bcdcaa..042dc2775a1 100644
--- a/packages/compass-indexes/src/modules/regular-indexes.ts
+++ b/packages/compass-indexes/src/modules/regular-indexes.ts
@@ -11,18 +11,14 @@ import type { FetchStatus } from '../utils/fetch-status';
import { FetchReasons } from '../utils/fetch-reason';
import type { FetchReason } from '../utils/fetch-reason';
import { isAction } from '../utils/is-action';
-import { ActionTypes as CreateIndexActionTypes } from './create-index';
-import type {
- CreateIndexSpec,
- IndexCreationStartedAction,
- IndexCreationSucceededAction,
- IndexCreationFailedAction,
-} from './create-index';
+import type { CreateIndexSpec } from './create-index';
import type { IndexesThunkAction, RootState } from '.';
import {
hideModalDescription,
unhideModalDescription,
} from '../utils/modal-descriptions';
+import type { IndexSpecification, CreateIndexesOptions } from 'mongodb';
+import { hasColumnstoreIndex } from '../utils/columnstore-indexes';
export type RegularIndex = Omit<
IndexDefinition,
@@ -45,6 +41,44 @@ export type InProgressIndex = {
};
};
+const prepareInProgressIndex = (
+ id: string,
+ {
+ ns,
+ name,
+ spec,
+ }: {
+ ns: string;
+ name?: string;
+ spec: CreateIndexSpec;
+ }
+): InProgressIndex => {
+ const inProgressIndexFields = Object.keys(spec).map((field: string) => ({
+ field,
+ value: spec[field],
+ }));
+ const inProgressIndexName =
+ name ||
+ Object.keys(spec).reduce((previousValue, currentValue) => {
+ return `${
+ previousValue === '' ? '' : `${previousValue}_`
+ }${currentValue}_${spec[currentValue]}`;
+ }, '');
+ return {
+ id,
+ extra: {
+ status: 'inprogress',
+ },
+ key: spec,
+ fields: inProgressIndexFields,
+ name: inProgressIndexName,
+ ns,
+ size: 0,
+ relativeSize: 0,
+ usageCount: 0,
+ };
+};
+
export enum ActionTypes {
IndexesOpened = 'compass-indexes/regular-indexes/indexes-opened',
IndexesClosed = 'compass-indexes/regular-indexes/indexes-closed',
@@ -53,10 +87,14 @@ export enum ActionTypes {
FetchIndexesSucceeded = 'compass-indexes/regular-indexes/fetch-indexes-succeeded',
FetchIndexesFailed = 'compass-indexes/regular-indexes/fetch-indexes-failed',
- // Basically the same thing as CreateIndexActionTypes.IndexCreationSucceeded
+ // Basically the same thing as ActionTypes.IndexCreationSucceeded
// in that it will remove the index, but it is for manually removing the row
// of an index that failed
FailedIndexRemoved = 'compass-indexes/regular-indexes/failed-index-removed',
+
+ IndexCreationStarted = 'compass-indexes/create-index/index-creation-started',
+ IndexCreationSucceeded = 'compass-indexes/create-index/index-creation-succeeded',
+ IndexCreationFailed = 'compass-indexes/create-index/index-creation-failed',
}
type IndexesOpenedAction = {
@@ -82,6 +120,22 @@ type FetchIndexesFailedAction = {
error: string;
};
+type IndexCreationStartedAction = {
+ type: ActionTypes.IndexCreationStarted;
+ inProgressIndex: InProgressIndex;
+};
+
+type IndexCreationSucceededAction = {
+ type: ActionTypes.IndexCreationSucceeded;
+ inProgressIndexId: string;
+};
+
+type IndexCreationFailedAction = {
+ type: ActionTypes.IndexCreationFailed;
+ inProgressIndexId: string;
+ error: string;
+};
+
type FailedIndexRemovedAction = {
type: ActionTypes.FailedIndexRemoved;
inProgressIndexId: string;
@@ -171,7 +225,7 @@ export default function reducer(
if (
isAction(
action,
- CreateIndexActionTypes.IndexCreationStarted
+ ActionTypes.IndexCreationStarted
)
) {
// Add the new in-progress index to the in-progress indexes.
@@ -196,7 +250,7 @@ export default function reducer(
if (
isAction(
action,
- CreateIndexActionTypes.IndexCreationSucceeded
+ ActionTypes.IndexCreationSucceeded
) ||
isAction(action, ActionTypes.FailedIndexRemoved)
) {
@@ -212,10 +266,7 @@ export default function reducer(
}
if (
- isAction(
- action,
- CreateIndexActionTypes.IndexCreationFailed
- )
+ isAction(action, ActionTypes.IndexCreationFailed)
) {
const idx = state.inProgressIndexes.findIndex(
(x) => x.id === action.inProgressIndexId
@@ -323,6 +374,7 @@ const fetchIndexes = (
}
};
};
+
export const fetchRegularIndexes = (): IndexesThunkAction<
Promise,
FetchIndexesActions
@@ -382,6 +434,89 @@ export const stopPollingRegularIndexes = (tabId: string) => {
};
};
+const indexCreationStarted = (
+ inProgressIndex: InProgressIndex
+): IndexCreationStartedAction => ({
+ type: ActionTypes.IndexCreationStarted,
+ inProgressIndex,
+});
+
+const indexCreationSucceeded = (
+ inProgressIndexId: string
+): IndexCreationSucceededAction => ({
+ type: ActionTypes.IndexCreationSucceeded,
+ inProgressIndexId,
+});
+
+const indexCreationFailed = (
+ inProgressIndexId: string,
+ error: string
+): IndexCreationFailedAction => ({
+ type: ActionTypes.IndexCreationFailed,
+ inProgressIndexId,
+ error,
+});
+
+export function createRegularIndex(
+ inProgressIndexId: string,
+ spec: CreateIndexSpec,
+ options: CreateIndexesOptions
+): IndexesThunkAction<
+ Promise,
+ | IndexCreationStartedAction
+ | IndexCreationSucceededAction
+ | IndexCreationFailedAction
+> {
+ return async (
+ dispatch,
+ getState,
+ { track, dataService, connectionInfoRef }
+ ) => {
+ const ns = getState().namespace;
+ const inProgressIndex = prepareInProgressIndex(inProgressIndexId, {
+ ns,
+ name: options.name,
+ spec,
+ });
+
+ dispatch(indexCreationStarted(inProgressIndex));
+
+ const fieldsFromSpec = Object.entries(spec).map(([k, v]) => {
+ return { name: k, type: String(v) };
+ });
+
+ const trackEvent = {
+ unique: options.unique,
+ ttl: typeof options.expireAfterSeconds !== 'undefined',
+ columnstore_index: hasColumnstoreIndex(fieldsFromSpec),
+ has_columnstore_projection:
+ // @ts-expect-error columnstoreProjection is not a part of
+ // CreateIndexesOptions yet.
+ typeof options.columnstoreProjection !== 'undefined',
+ has_wildcard_projection:
+ typeof options.wildcardProjection !== 'undefined',
+ custom_collation: typeof options.collation !== 'undefined',
+ geo: fieldsFromSpec.some(({ type }) => {
+ return type === '2dsphere';
+ }),
+ atlas_search: false,
+ };
+
+ try {
+ await dataService.createIndex(ns, spec as IndexSpecification, options);
+ dispatch(indexCreationSucceeded(inProgressIndexId));
+ track('Index Created', trackEvent, connectionInfoRef.current);
+
+ // Start a new fetch so that the newly added index's details can be
+ // loaded. indexCreationSucceeded() will remove the in-progress one, but
+ // we still need the new info.
+ await dispatch(refreshRegularIndexes());
+ } catch (err) {
+ dispatch(indexCreationFailed(inProgressIndexId, (err as Error).message));
+ }
+ };
+}
+
const failedIndexRemoved = (
inProgressIndexId: string
): FailedIndexRemovedAction => ({