Skip to content

Commit daaf192

Browse files
authored
fix(query-bar, editor): update query history autocompletion to be more selective COMPASS-8241 (#6193)
1 parent b2db494 commit daaf192

File tree

6 files changed

+355
-54
lines changed

6 files changed

+355
-54
lines changed

packages/compass-editor/src/codemirror/query-autocompleter-with-history.test.ts

Lines changed: 96 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { expect } from 'chai';
22
import { createQueryWithHistoryAutocompleter } from './query-autocompleter-with-history';
33
import { setupCodemirrorCompleter } from '../../test/completer';
44
import type { SavedQuery } from '../../dist/codemirror/query-history-autocompleter';
5-
import { createQuery } from './query-history-autocompleter';
5+
import type { Completion } from '@codemirror/autocomplete';
6+
7+
function getQueryHistoryAutocompletions(completions: Readonly<Completion[]>) {
8+
return completions.filter(
9+
({ type }) => type === 'favorite' || type === 'query-history'
10+
);
11+
}
612

713
describe('query history autocompleter', function () {
814
const { getCompletions, cleanup } = setupCodemirrorCompleter(
@@ -46,7 +52,7 @@ describe('query history autocompleter', function () {
4652
type: 'recent',
4753
lastExecuted: new Date('2023-06-04T18:00:00Z'),
4854
queryProperties: {
49-
filter: { isActive: true },
55+
filter: { isActive: true, score: { $exists: false } },
5056
project: { userId: 1, isActive: 1 },
5157
collation: { locale: 'simple' },
5258
sort: { userId: 1 },
@@ -71,32 +77,105 @@ describe('query history autocompleter', function () {
7177

7278
const mockOnApply: (query: SavedQuery['queryProperties']) => any = () => {};
7379

74-
it('returns all saved queries as completions on click', async function () {
80+
it('returns all saved queries as completions with default {}', async function () {
7581
expect(
76-
await getCompletions('{}', savedQueries, undefined, mockOnApply, 'light')
82+
await getCompletions('{}', {
83+
savedQueries,
84+
options: undefined,
85+
queryProperty: '',
86+
onApply: mockOnApply,
87+
theme: 'light',
88+
})
7789
).to.have.lengthOf(5);
7890
});
7991

80-
it('returns combined completions when user starts typing', async function () {
92+
it('returns all saved queries as completions with empty entry', async function () {
8193
expect(
82-
await getCompletions('foo', savedQueries, undefined, mockOnApply, 'light')
83-
).to.have.lengthOf(50);
94+
await getCompletions('', {
95+
savedQueries,
96+
options: undefined,
97+
queryProperty: '',
98+
onApply: mockOnApply,
99+
theme: 'light',
100+
})
101+
).to.have.lengthOf(5);
102+
});
103+
104+
it('returns combined completions that match the prefix of the field', async function () {
105+
const completions = await getCompletions('scor', {
106+
savedQueries,
107+
options: undefined,
108+
queryProperty: 'filter',
109+
onApply: mockOnApply,
110+
theme: 'light',
111+
});
112+
const queryHistoryCompletions = getQueryHistoryAutocompletions(completions);
113+
114+
expect(queryHistoryCompletions).to.have.lengthOf(2);
115+
expect(completions).to.have.length.greaterThan(
116+
queryHistoryCompletions.length
117+
);
118+
});
119+
120+
it('returns completions that match with multiple fields', async function () {
121+
const completions = getQueryHistoryAutocompletions(
122+
await getCompletions('{ price: 1, category: 1', {
123+
savedQueries,
124+
options: undefined,
125+
queryProperty: 'project',
126+
onApply: mockOnApply,
127+
theme: 'light',
128+
})
129+
);
130+
expect(completions).to.have.lengthOf(1);
131+
expect(completions[0].label).to.equal(`{
132+
productId: 1,
133+
category: 1,
134+
price: 1
135+
}`);
84136
});
85137

86-
it('completes "any text" when inside a string', async function () {
87-
const prettifiedSavedQueries = savedQueries.map((query) =>
88-
createQuery(query)
138+
it('does not return fields that match in other query properties', async function () {
139+
const completions = getQueryHistoryAutocompletions(
140+
await getCompletions('local', {
141+
savedQueries,
142+
options: undefined,
143+
queryProperty: 'project',
144+
onApply: mockOnApply,
145+
theme: 'light',
146+
})
89147
);
148+
149+
const queryHistoryCompletions = getQueryHistoryAutocompletions(completions);
150+
expect(queryHistoryCompletions).to.have.lengthOf(0);
151+
});
152+
153+
it('completes regular query autocompletion items', async function () {
154+
// 'foo' matches > 45 methods and fields in the query autocompletion.
155+
const completions = (
156+
await getCompletions('foo', {
157+
savedQueries,
158+
options: undefined,
159+
queryProperty: '',
160+
onApply: mockOnApply,
161+
theme: 'light',
162+
})
163+
).filter(({ type }) => type !== 'favorite' && type !== 'query-history');
164+
165+
expect(completions).to.have.length.greaterThan(40);
166+
});
167+
168+
it('completes fields inside a string', async function () {
90169
expect(
91170
(
92-
await getCompletions(
93-
'{ bar: 1, buz: 2, foo: "b',
171+
await getCompletions('{ bar: 1, buz: 2, foo: "b', {
94172
savedQueries,
95-
undefined,
96-
mockOnApply,
97-
'light'
98-
)
173+
options: undefined,
174+
queryProperty: '',
175+
onApply: mockOnApply,
176+
theme: 'light',
177+
})
99178
).map((completion) => completion.label)
100-
).to.deep.eq(['bar', '1', 'buz', '2', 'foo', ...prettifiedSavedQueries]);
179+
).to.deep.eq(['bar', '1', 'buz', '2', 'foo']);
101180
});
102181
});

packages/compass-editor/src/codemirror/query-autocompleter-with-history.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,25 @@ import type { CompletionOptions } from '../autocompleter';
1313
import { css } from '@mongodb-js/compass-components';
1414
import type { CodemirrorThemeType } from '../editor';
1515

16-
export const createQueryWithHistoryAutocompleter = (
17-
recentQueries: SavedQuery[],
18-
options: Pick<CompletionOptions, 'fields' | 'serverVersion'> = {},
19-
onApply: (query: SavedQuery['queryProperties']) => void,
20-
theme: CodemirrorThemeType
21-
): CompletionSource => {
22-
const queryHistoryAutocompleter = createQueryHistoryAutocompleter(
23-
recentQueries,
16+
export const createQueryWithHistoryAutocompleter = ({
17+
savedQueries,
18+
options = {},
19+
queryProperty,
20+
onApply,
21+
theme,
22+
}: {
23+
savedQueries: SavedQuery[];
24+
options?: Pick<CompletionOptions, 'fields' | 'serverVersion'>;
25+
queryProperty: string;
26+
onApply: (query: SavedQuery['queryProperties']) => void;
27+
theme: CodemirrorThemeType;
28+
}): CompletionSource => {
29+
const queryHistoryAutocompleter = createQueryHistoryAutocompleter({
30+
savedQueries,
2431
onApply,
25-
theme
26-
);
32+
queryProperty,
33+
theme,
34+
});
2735

2836
const originalQueryAutocompleter = createQueryAutocompleter(options);
2937
const historySection: CompletionSection = {

packages/compass-editor/src/codemirror/query-history-autocompleter.test.ts

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { expect } from 'chai';
2-
import { scaleBetween } from './query-history-autocompleter';
2+
import {
3+
createQueryDisplayLabel,
4+
createQueryLabel,
5+
scaleBetween,
6+
} from './query-history-autocompleter';
37

48
describe('scaleBetween', function () {
59
// args: unscaledNum, newScaleMin, newScaleMax, originalScaleMin, originalScaleMax
@@ -23,3 +27,107 @@ describe('scaleBetween', function () {
2327
expect(result).to.equal(25);
2428
});
2529
});
30+
31+
describe('createQueryLabel', function () {
32+
it('should return an empty string if the property does not exist', () => {
33+
const result = createQueryLabel(
34+
{
35+
type: 'favorite',
36+
lastExecuted: new Date('2023-06-03T16:00:00Z'),
37+
queryProperties: {
38+
filter: { name: 'pineapple' },
39+
project: {},
40+
sort: undefined,
41+
limit: undefined,
42+
},
43+
},
44+
'collation'
45+
);
46+
expect(result).to.equal('');
47+
});
48+
49+
it('should return the string representation of the property value if it exists', () => {
50+
const result = createQueryLabel(
51+
{
52+
type: 'favorite',
53+
lastExecuted: new Date('2023-06-03T16:00:00Z'),
54+
queryProperties: {
55+
filter: { name: 'pineapple' },
56+
project: {},
57+
sort: undefined,
58+
limit: undefined,
59+
},
60+
},
61+
'filter'
62+
);
63+
expect(result).to.equal("{\n name: 'pineapple'\n}");
64+
});
65+
66+
it('should return an empty string if the property value is undefined', () => {
67+
const result = createQueryLabel(
68+
{
69+
type: 'favorite',
70+
lastExecuted: new Date('2023-06-03T16:00:00Z'),
71+
queryProperties: {
72+
filter: { name: 'pineapple' },
73+
project: {},
74+
sort: undefined,
75+
limit: undefined,
76+
},
77+
},
78+
'exampleProperty'
79+
);
80+
expect(result).to.equal('');
81+
});
82+
});
83+
84+
describe('createQueryDisplayLabel', function () {
85+
it('should return an empty string if queryProperties is empty', () => {
86+
const result = createQueryDisplayLabel({
87+
type: 'recent',
88+
lastExecuted: new Date('2023-06-03T16:00:00Z'),
89+
queryProperties: {},
90+
});
91+
expect(result).to.equal('');
92+
});
93+
94+
it('should return a formatted label for multiple properties', () => {
95+
const result = createQueryDisplayLabel({
96+
type: 'favorite',
97+
lastExecuted: new Date('2023-06-03T16:00:00Z'),
98+
queryProperties: {
99+
filter: { name: 'pineapple' },
100+
project: { _id: 0 },
101+
sort: { score: -1 },
102+
hint: { indexName: 'score_1' },
103+
limit: 20,
104+
maxTimeMS: 1000,
105+
},
106+
});
107+
expect(result).to.equal(`{
108+
name: 'pineapple'
109+
}, project: {
110+
_id: 0
111+
}, sort: {
112+
score: -1
113+
}, hint: {
114+
indexName: 'score_1'
115+
}, limit: 20, maxTimeMS: 1000`);
116+
});
117+
118+
it('should handle empty or undefined property values', () => {
119+
const result = createQueryDisplayLabel({
120+
type: 'favorite',
121+
lastExecuted: new Date('2023-06-03T16:00:00Z'),
122+
queryProperties: {
123+
filter: { name: 'pineapple' },
124+
project: {},
125+
sort: undefined,
126+
limit: undefined,
127+
},
128+
});
129+
expect(result).to.equal(`{
130+
name: 'pineapple'
131+
}, project: {}, sort: undefined, limit: undefined`);
132+
});
133+
});

0 commit comments

Comments
 (0)