Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Auto Suggest] DQL Updates #7593

Merged
merged 19 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ snapshots.js
.yarn-local-mirror

# Ignore the generated antlr files
/src/plugins/data/public/antlr/opensearch_sql/grammar/.antlr
/src/plugins/data/public/antlr/opensearch_sql/grammar/.antlr
/src/plugins/data/public/antlr/dql/grammar/.antlr
198 changes: 182 additions & 16 deletions src/plugins/data/public/antlr/dql/code_completion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@
import { monaco } from '@osd/monaco';
import { getSuggestions } from './code_completion';
import {
allCarrierValueSuggestions,
booleanOperatorSuggestions,
carrierValues,
carrierWithNotSuggestions,
fieldNameWithNotSuggestions,
logCarrierValueSuggestion,
notOperatorSuggestion,
openCarrierValueSuggestion,
testingIndex,
} from '../shared/constants';
import { DataPublicPluginStart, IDataPluginServices } from '../../types';

jest.mock('../../services', () => ({
getUiService: () => ({
Expand All @@ -20,6 +26,14 @@ jest.mock('../../services', () => ({
}),
}));

const mockValueSuggestions = jest.fn((passedin) => {
const { field, query } = passedin;
if (field?.name === 'Carrier') {
return carrierValues.filter((val) => val.startsWith(query));
}
return [];
});

const getSuggestionsAtPos = async (query: string, endPos: number) => {
return await getSuggestions({
query,
Expand All @@ -28,82 +42,234 @@ const getSuggestionsAtPos = async (query: string, endPos: number) => {
language: '', // not relevant
selectionEnd: 0, // not relevant
selectionStart: 0, // not relevant
services: { appName: 'discover' },
services: {
appName: 'discover',
data: ({
autocomplete: { getValueSuggestions: mockValueSuggestions },
} as unknown) as DataPublicPluginStart,
} as IDataPluginServices,
});
};

const getSuggestionAtEnd = async (query: string) => {
const getSuggestionsAtEnd = async (query: string) => {
return await getSuggestionsAtPos(query, query.length + 1);
};

const testAroundClosing = (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function contains 2-4 tests that can be triggered around a certain query. These were common patterns found in queries that needed to be tested, basically the behavior around closed/unclosed quotes and parenthesis

query: string,
surroundingChars: Array<string | undefined>,
testName: string,
expectedValue: any,
includeBase?: boolean
) => {
if (includeBase) {
it(testName, async () => {
expect(await getSuggestionsAtEnd(query)).toStrictEqual(expectedValue);
});
}

let currQuery = query;
if (surroundingChars[0]) {
it(testName + ' - opened', async () => {
currQuery = currQuery + surroundingChars[0];
expect(await getSuggestionsAtEnd(currQuery)).toStrictEqual(expectedValue);
});
}

it(testName + ' - closed', async () => {
currQuery = currQuery + surroundingChars[1];
expect(await getSuggestionsAtPos(currQuery, currQuery.length)).toStrictEqual(expectedValue);
});

// TODO: besides NOT issue, need to consolidate behavior where there is no val vs w/ val
it.skip(testName + ' - no suggestion after closing', async () => {
expect(await getSuggestionsAtEnd(currQuery)).toStrictEqual([]);
});
};

describe('Test Boolean Operators', () => {
it('should suggest AND and OR after expression', async () => {
expect(await getSuggestionAtEnd('field: value ')).toStrictEqual(booleanOperatorSuggestions);
expect(await getSuggestionsAtEnd('field: value ')).toStrictEqual(booleanOperatorSuggestions);
});

it('should suggest NOT initially', async () => {
expect(await getSuggestionAtEnd('')).toContainEqual(notOperatorSuggestion);
expect(await getSuggestionsAtEnd('')).toContainEqual(notOperatorSuggestion);
});

it('should suggest NOT after expression', async () => {
expect(await getSuggestionAtEnd('field: value and ')).toContainEqual(notOperatorSuggestion);
expect(await getSuggestionsAtEnd('field: value and ')).toContainEqual(notOperatorSuggestion);
});

it('should not suggest NOT twice', async () => {
expect(await getSuggestionAtEnd('not ')).not.toContainEqual(notOperatorSuggestion);
expect(await getSuggestionsAtEnd('not ')).not.toContainEqual(notOperatorSuggestion);
});

it('should suggest after multiple token search', async () => {
expect(await getSuggestionAtEnd('field: one two three ')).toStrictEqual(
expect(await getSuggestionsAtEnd('field: one two three ')).toStrictEqual(
booleanOperatorSuggestions
);
});

it('should suggest after phrase value', async () => {
expect(await getSuggestionAtEnd('field: "value" ')).toStrictEqual(booleanOperatorSuggestions);
expect(await getSuggestionsAtEnd('field: "value" ')).toStrictEqual(booleanOperatorSuggestions);
});

it('should suggest after number', async () => {
expect(await getSuggestionAtEnd('field: 123 ')).toStrictEqual(booleanOperatorSuggestions);
expect(await getSuggestionsAtEnd('field: 123 ')).toStrictEqual(booleanOperatorSuggestions);
});

it('should not suggest after incomplete quote', async () => {
expect(await getSuggestionAtEnd('field: "value ')).not.toStrictEqual(
expect(await getSuggestionsAtEnd('field: "value ')).not.toStrictEqual(
booleanOperatorSuggestions
);
});
});

describe('Test Boolean Operators within groups', () => {
it('should suggest AND and OR', async () => {
expect(await getSuggestionAtEnd('field: (value ')).toStrictEqual(booleanOperatorSuggestions);
expect(await getSuggestionsAtEnd('field: (value ')).toStrictEqual(booleanOperatorSuggestions);
});

it('should suggest NOT after expression', async () => {
expect(await getSuggestionAtEnd('field: (value and ')).toContainEqual(notOperatorSuggestion);
expect(await getSuggestionsAtEnd('field: (value and ')).toContainEqual(notOperatorSuggestion);
});

it('should suggest operator within nested group', async () => {
expect(await getSuggestionAtEnd('field: ("one" and ("two" ')).toStrictEqual(
expect(await getSuggestionsAtEnd('field: ("one" and ("two" ')).toStrictEqual(
booleanOperatorSuggestions
);
});
});

describe('Test field suggestions', () => {
it('basic field suggestion', async () => {
expect(await getSuggestionAtEnd('')).toStrictEqual(fieldNameWithNotSuggestions);
expect(await getSuggestionsAtEnd('')).toStrictEqual(fieldNameWithNotSuggestions);
});

it('field suggestion after one term', async () => {
expect(await getSuggestionAtEnd('field: value and ')).toStrictEqual(
expect(await getSuggestionsAtEnd('field: value and ')).toStrictEqual(
fieldNameWithNotSuggestions
);
});

it('field suggestion within group', async () => {
expect(await getSuggestionAtEnd('field: value and (one: "two" or ')).toStrictEqual(
expect(await getSuggestionsAtEnd('field: value and (one: "two" or ')).toStrictEqual(
fieldNameWithNotSuggestions
);
});
});

describe('Test basic value suggestions', () => {
it('do not suggest unknown field', async () => {
expect(await getSuggestionsAtEnd('field: ')).toStrictEqual([]);
});

it('suggest token search value for field', async () => {
expect(await getSuggestionsAtEnd('Carrier: ')).toStrictEqual(allCarrierValueSuggestions);
});

it('suggest value for field without surrounding space', async () => {
expect(await getSuggestionsAtEnd('Carrier:')).toStrictEqual(allCarrierValueSuggestions);
});

it('suggest value from partial value', async () => {
expect(await getSuggestionsAtEnd('Carrier: Log')).toStrictEqual(logCarrierValueSuggestion);
});

it('suggest multiple values from partial value', async () => {
expect(await getSuggestionsAtEnd('Carrier: Open')).toStrictEqual(openCarrierValueSuggestion);
});

testAroundClosing(
'Carrier: ',
['"', '"'],
'should suggest within phrase',
allCarrierValueSuggestions
);

it('suggest rest of partial value within quotes', async () => {
const query = 'Carrier: "OpenSearch"';
expect(await getSuggestionsAtPos(query, query.length)).toStrictEqual(
openCarrierValueSuggestion
);
});

// it('should suggest within multiple token search context'); <-- maybe it means suggest either bool OR next partial value
});

describe('Test value suggestion with multiple terms', () => {
testAroundClosing(
'Carrier: BeatsWest or Carrier: ',
['"', '"'],
'should suggest after one field value expression',
allCarrierValueSuggestions,
true
);

it('should suggest after field value expression and partial value', async () => {
expect(await getSuggestionsAtEnd('Carrier: BeatsWest or Carrier: Open')).toStrictEqual(
openCarrierValueSuggestion
);
});

testAroundClosing(
'Carrier: BeatsWest or Carrier: "Open',
[undefined, '"'],
'should suggest after field value expression in partial value quotes',
openCarrierValueSuggestion,
true
);
});

describe('Test group value suggestions', () => {
testAroundClosing(
'Carrier: ',
['(', ')'],
'should suggest within grouping',
carrierWithNotSuggestions
);

testAroundClosing(
'Carrier: (',
['"', '"'],
'should suggest within grouping and phrase',
allCarrierValueSuggestions
);

testAroundClosing(
'Carrier: ("',
[undefined, '")'],
'should suggest within closed grouping and phrase',
allCarrierValueSuggestions
);

testAroundClosing(
'Carrier: (BeatsWest or ',
[undefined, ')'],
'should suggest after grouping with term',
carrierWithNotSuggestions,
true
);

testAroundClosing(
'Carrier: (BeatsWest or ',
['"', '")'],
'should suggest in phrase after grouping with term',
allCarrierValueSuggestions
);

testAroundClosing(
'Carrier: ("BeatsWest" or ',
[undefined, ')'],
'should suggest after grouping with phrase',
carrierWithNotSuggestions,
true
);

testAroundClosing(
'Carrier: ("BeatsWest" or ',
['"', '")'],
'should suggest in phrase after grouping with phrase',
allCarrierValueSuggestions
);
});
Loading
Loading