Skip to content

Commit 6351f72

Browse files
authored
feat(search-indexes): fields autocomplete COMPASS-7174 (#4927)
1 parent fa92670 commit 6351f72

File tree

11 files changed

+159
-3
lines changed

11 files changed

+159
-3
lines changed

packages/compass-components/src/components/modals/modal.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ const contentStyles = css({
88
width: '600px',
99
letterSpacing: 0,
1010
padding: 0,
11+
// The LG modal applies transform: translate3d(0, 0, 0) style to the modal
12+
// content and this messes up the autocompleter within the modal. So we clear
13+
// the transform here.
14+
transform: 'none',
1115
});
1216

1317
const modalFullScreenStyles = css({
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { expect } from 'chai';
2+
import { createSearchIndexAutocompleter } from './search-index-autocompleter';
3+
import { setupCodemirrorCompleter } from '../../test/completer';
4+
5+
describe('search-index autocompleter', function () {
6+
const { getCompletions, cleanup } = setupCodemirrorCompleter(
7+
createSearchIndexAutocompleter
8+
);
9+
10+
after(cleanup);
11+
12+
it('returns words in context when its not completing fields', function () {
13+
const completions = getCompletions('{ dynamic: true, type: "dy', {
14+
fields: ['_id', 'name', 'age'],
15+
});
16+
expect(completions.map((x) => x.label)).to.deep.equal([
17+
'dynamic',
18+
'true',
19+
'type',
20+
]);
21+
});
22+
23+
it('returns field names when autocompleting fields', function () {
24+
const completions = getCompletions('{ fields: { "a', {
25+
fields: ['_id', 'name', 'age'],
26+
});
27+
expect(completions.map((x) => x.label)).to.deep.equal([
28+
'_id',
29+
'name',
30+
'age',
31+
]);
32+
});
33+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { CompletionSource } from '@codemirror/autocomplete';
2+
import type { CompletionOptions } from '../autocompleter';
3+
import { completer } from '../autocompleter';
4+
import {
5+
ID_REGEX,
6+
createCompletionResultForIdPrefix,
7+
} from './ace-compat-autocompleter';
8+
import {
9+
completeWordsInString,
10+
getAncestryOfToken,
11+
resolveTokenAtCursor,
12+
} from './utils';
13+
14+
const isCompletingFields = (ancestors: string[]) => {
15+
return ancestors[ancestors.length - 1] === 'fields';
16+
};
17+
18+
export const createSearchIndexAutocompleter = (
19+
options: Pick<CompletionOptions, 'fields'> = {}
20+
): CompletionSource => {
21+
const completions = completer('', {
22+
meta: ['field:identifier'],
23+
...options,
24+
});
25+
26+
return (context) => {
27+
const token = resolveTokenAtCursor(context);
28+
const document = context.state.sliceDoc(0);
29+
const prefix = context.matchBefore(ID_REGEX);
30+
if (!prefix) {
31+
return null;
32+
}
33+
34+
const ancestors = getAncestryOfToken(token, document);
35+
36+
if (isCompletingFields(ancestors)) {
37+
return createCompletionResultForIdPrefix({
38+
prefix,
39+
completions,
40+
});
41+
}
42+
43+
return completeWordsInString(context);
44+
};
45+
};

packages/compass-editor/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export { createValidationAutocompleter } from './codemirror/validation-autocompl
2020
export { createQueryAutocompleter } from './codemirror/query-autocompleter';
2121
export { createStageAutocompleter } from './codemirror/stage-autocompleter';
2222
export { createAggregationAutocompleter } from './codemirror/aggregation-autocompleter';
23+
export { createSearchIndexAutocompleter } from './codemirror/search-index-autocompleter';

packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describe('Create Search Index Modal', function () {
3838
onSubmit={onSubmitSpy}
3939
onClose={onCloseSpy}
4040
error={'Invalid index definition.'}
41+
fields={[]}
4142
/>
4243
);
4344
});

packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { useCallback, useEffect, useRef, useState } from 'react';
1+
import React, {
2+
useCallback,
3+
useEffect,
4+
useRef,
5+
useState,
6+
useMemo,
7+
} from 'react';
28
import {
39
Modal,
410
ModalFooter,
@@ -18,13 +24,17 @@ import {
1824
Banner,
1925
rafraf,
2026
} from '@mongodb-js/compass-components';
21-
import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor';
27+
import {
28+
CodemirrorMultilineEditor,
29+
createSearchIndexAutocompleter,
30+
} from '@mongodb-js/compass-editor';
2231
import type { EditorRef } from '@mongodb-js/compass-editor';
2332
import _parseShellBSON, { ParseMode } from 'ejson-shell-parser';
2433
import type { Document } from 'mongodb';
2534
import { useTrackOnChange } from '@mongodb-js/compass-logging';
2635
import { SearchIndexTemplateDropdown } from '../search-index-template-dropdown';
2736
import type { SearchTemplate } from '@mongodb-js/mongodb-constants';
37+
import type { Field } from '../../modules/fields';
2838

2939
// Copied from packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/utils.ts
3040
function parseShellBSON(source: string): Document[] {
@@ -82,6 +92,7 @@ type BaseSearchIndexModalProps = {
8292
isModalOpen: boolean;
8393
isBusy: boolean;
8494
error: string | undefined;
95+
fields: Field[];
8596
onSubmit: (indexName: string, indexDefinition: Document) => void;
8697
onClose: () => void;
8798
};
@@ -95,6 +106,7 @@ export const BaseSearchIndexModal: React.FunctionComponent<
95106
isModalOpen,
96107
isBusy,
97108
error,
109+
fields,
98110
onSubmit,
99111
onClose,
100112
}) => {
@@ -171,6 +183,14 @@ export const BaseSearchIndexModal: React.FunctionComponent<
171183
[editorRef]
172184
);
173185

186+
const completer = useMemo(
187+
() =>
188+
createSearchIndexAutocompleter({
189+
fields,
190+
}),
191+
[fields]
192+
);
193+
174194
return (
175195
<Modal
176196
open={isModalOpen}
@@ -248,6 +268,7 @@ export const BaseSearchIndexModal: React.FunctionComponent<
248268
text={indexDefinition}
249269
onChangeText={onSearchIndexDefinitionChanged}
250270
minLines={16}
271+
completer={completer}
251272
/>
252273
</div>
253274
{parsingError && <WarningSummary warnings={parsingError} />}

packages/compass-indexes/src/components/search-indexes-modals/create-search-index-modal.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
44
import type { RootState } from '../../modules';
55
import type { Document } from 'mongodb';
66
import { BaseSearchIndexModal } from './base-search-index-modal';
7+
import type { Field } from '../../modules/fields';
78

89
export const DEFAULT_INDEX_DEFINITION = `{
910
mappings: {
@@ -15,13 +16,14 @@ type CreateSearchIndexModalProps = {
1516
isModalOpen: boolean;
1617
isBusy: boolean;
1718
error: string | undefined;
19+
fields: Field[];
1820
onCreateIndex: (indexName: string, indexDefinition: Document) => void;
1921
onCloseModal: () => void;
2022
};
2123

2224
export const CreateSearchIndexModal: React.FunctionComponent<
2325
CreateSearchIndexModalProps
24-
> = ({ isModalOpen, isBusy, error, onCreateIndex, onCloseModal }) => {
26+
> = ({ isModalOpen, isBusy, error, fields, onCreateIndex, onCloseModal }) => {
2527
return (
2628
<BaseSearchIndexModal
2729
mode={'create'}
@@ -30,6 +32,7 @@ export const CreateSearchIndexModal: React.FunctionComponent<
3032
isModalOpen={isModalOpen}
3133
isBusy={isBusy}
3234
error={error}
35+
fields={fields}
3336
onSubmit={onCreateIndex}
3437
onClose={onCloseModal}
3538
/>
@@ -40,10 +43,12 @@ const mapState = ({
4043
searchIndexes: {
4144
createIndex: { isBusy, isModalOpen, error },
4245
},
46+
fields,
4347
}: RootState) => ({
4448
isModalOpen,
4549
isBusy,
4650
error,
51+
fields,
4752
});
4853

4954
const mapDispatch = {

packages/compass-indexes/src/components/search-indexes-modals/update-search-index-modal.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { connect } from 'react-redux';
44
import type { RootState } from '../../modules';
55
import type { Document } from 'mongodb';
66
import { BaseSearchIndexModal } from './base-search-index-modal';
7+
import type { Field } from '../../modules/fields';
78

89
type UpdateSearchIndexModalProps = {
910
indexName: string;
1011
indexDefinition: string;
1112
isModalOpen: boolean;
1213
isBusy: boolean;
1314
error: string | undefined;
15+
fields: Field[];
1416
onUpdateIndex: (indexName: string, indexDefinition: Document) => void;
1517
onCloseModal: () => void;
1618
};
@@ -23,6 +25,7 @@ export const UpdateSearchIndexModal: React.FunctionComponent<
2325
isModalOpen,
2426
isBusy,
2527
error,
28+
fields,
2629
onUpdateIndex,
2730
onCloseModal,
2831
}) => {
@@ -34,6 +37,7 @@ export const UpdateSearchIndexModal: React.FunctionComponent<
3437
isModalOpen={isModalOpen}
3538
isBusy={isBusy}
3639
error={error}
40+
fields={fields}
3741
onSubmit={onUpdateIndex}
3842
onClose={onCloseModal}
3943
/>
@@ -45,6 +49,7 @@ const mapState = ({
4549
indexes,
4650
updateIndex: { indexName, isBusy, isModalOpen, error },
4751
},
52+
fields,
4853
}: RootState) => ({
4954
isModalOpen,
5055
isBusy,
@@ -55,6 +60,7 @@ const mapState = ({
5560
2
5661
),
5762
error,
63+
fields,
5864
});
5965

6066
const mapDispatch = {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { AnyAction } from 'redux';
2+
import { isAction } from './../utils/is-action';
3+
4+
export enum ActionTypes {
5+
SetFields = 'indexes/SetFields',
6+
}
7+
8+
export type Field = {
9+
name: string;
10+
description: string;
11+
};
12+
13+
type SetFieldsAction = {
14+
type: ActionTypes.SetFields;
15+
fields: Field[];
16+
};
17+
18+
type State = Field[];
19+
20+
export const INITIAL_STATE: State = [];
21+
22+
export default function reducer(state = INITIAL_STATE, action: AnyAction) {
23+
if (isAction<SetFieldsAction>(action, ActionTypes.SetFields)) {
24+
return action.fields;
25+
}
26+
return state;
27+
}
28+
29+
export const setFields = (fields: Field[]): SetFieldsAction => ({
30+
type: ActionTypes.SetFields,
31+
fields,
32+
});

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import regularIndexes from './regular-indexes';
1010
import searchIndexes from './search-indexes';
1111
import serverVersion from './server-version';
1212
import namespace from './namespace';
13+
import fields from './fields';
1314
import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
1415

1516
const reducer = combineReducers({
@@ -22,6 +23,7 @@ const reducer = combineReducers({
2223
namespace,
2324
regularIndexes,
2425
searchIndexes,
26+
fields,
2527
});
2628

2729
export type SortDirection = 'asc' | 'desc';

0 commit comments

Comments
 (0)