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

Add autocomplete enhancements #507

Merged
40 changes: 23 additions & 17 deletions dashboards-observability/common/constants/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,27 +67,27 @@ export interface DataItem {
doc_count: any;
}

const JUST_SEARCH_REGEX = /\s*search\s+source\s*=\s*(\S+)/;
const SEARCH_WHERE_REGEX = /\s*search\s+source\s*=\s*(\S+)\s*\|\s*where\s+\S+\s*=\s*\S+/;
const SEARCH_MATCH_REGEX = /\s*search\s+source\s*=\s*(\S+)\s*\|\s*where\s+match\(\S+,\s*\S+\)/;
const JUST_SOURCE_REGEX = /\s*source\s*=\s*(\S+)/;
const SOURCE_WHERE_REGEX = /\s*source\s*=\s*(\S+)\s*\|\s*where\s+\S+\s*=\s*\S+/;
const SOURCE_MATCH_REGEX = /\s*source\s*=\s*(\S+)\s*\|\s*where\s+match\(\S+,\s*\S+\)/;
const JUST_SEARCH_REGEX = /\s*search\s+source\s*=\s*[^\\\/\?\"\<\>\|\s\,\#]*(\s*,\s*[^\\\/\?\"\<\>\|\s\,\#]+)*/;
Copy link
Member

@joshuali925 joshuali925 Feb 26, 2022

Choose a reason for hiding this comment

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

\s*[^\\\/\?\"\<\>\|\s\,\#]*

the first \s* is redundant? nvm it's not.. ignore this comment i need to read it again

const SEARCH_WHERE_REGEX = /\s*search\s+source\s*=\s*[^\\\/\?\"\<\>\|\s\,\#]*(\s*,\s*[^\\\/\?\"\<\>\|\s\,\#]+)*\s*\|\s*where\s+\S+\s*=\s*\S+/;
const SEARCH_MATCH_REGEX = /\s*search\s+source\s*=\s*[^\\\/\?\"\<\>\|\s\,\#]*(\s*,\s*[^\\\/\?\"\<\>\|\s\,\#]+)*\s*\|\s*where\s+match\(\S+,\s*\S+\)/;
const JUST_SOURCE_REGEX = /\s*source\s*=\s*[^\\\/\?\"\<\>\|\s\,\#]*(\s*,\s*[^\\\/\?\"\<\>\|\s\,\#]+)*/;
const SOURCE_WHERE_REGEX = /\s*source\s*=\s*[^\\\/\?\"\<\>\|\s\,\#]*(\s*,\s*[^\\\/\?\"\<\>\|\s\,\#]+)*\s*\|\s*where\s+\S+\s*=\s*\S+/;
const SOURCE_MATCH_REGEX = /\s*source\s*=\s*[^\\\/\?\"\<\>\|\s\,\#]*(\s*,\s*[^\\\/\?\"\<\>\|\s\,\#]+)*\s*\|\s*where\s+match\(\S+,\s*\S+\)/;
export const EMPTY_REGEX = /^\s*\S*$/;
export const FIELD_AFTER_COMMAND = /^\s*(dedup|eval|rare|top|rename|where\s+match\()\s+\S*$/;

// Regex for where command
export const MATCH_FIELD_AFTER_WHERE = /^\s*where\s+\S*$/;
export const EQUAL_AFTER_WHERE_FIELD = /^\s*where\s+(\S+)\s+$/;
export const DATA_AFTER_WHERE_EQUAL = /^\s*where\s+\S+\s*=\s*\S*$/;
export const PIPE_AFTER_WHERE = /^\s*where\s+\S+\s*=\s*\S+\s+$/;
export const COMMA_AFTER_FIELD = /^\s*where\s+match\(\s*(\S+)\s+$/;
export const DATA_AFTER_COMMA = /^\s*where\s+match\(\s*\S+\s*,\s*$/;
export const CLOSE_AFTER_DATA = /^\s*where\s+match\(\s*\S+\s*,\s*\S+\s+$/;
export const PIPE_AFTER_MATCH = /^\s*where\s+match\(\s*\S+\s*,\s*\S+\s*\S+\s*\)\s*$/;
export const DATA_AFTER_WHERE_EQUAL = /^\s*where\s+\S+\s*=\s*(("(\w|\s)*)|(\d*\.?\d*)|\w*)$/;
export const PIPE_AFTER_WHERE = /^\s*where\s+\S+\s*=\s*(("(\w|\s)+")|(\d+\.?\d*)|\w+)\s+$/;
export const COMMA_AFTER_FIELD = /^\s*where\s+match\(\s*([^\s,]+)\s*$/;
export const DATA_AFTER_COMMA = /^\s*where\s+match\(\s*\S+\s*,\s*(("(\w|\s)*)|(\d*\.?\d*)|\w*)$/;
export const CLOSE_AFTER_DATA = /^\s*where\s+match\(\s*\S+\s*,\s*(("(\w|\s)+")|(\d+\.?\d*)|\w+)\s+$/;
export const PIPE_AFTER_MATCH = /^\s*where\s+match\(\s*\S+\s*,\s*(("(\w|\s)+")|(\d+\.?\d*)|\w+)\s*\)\s*$/;

// Regex for dedup command
export const FIELD_IN_FIELD_LOOP = /^\s*dedup\s*\d*\s+\S+\s*(,\s*\S+\s*)*,\s*\S*$/;
export const FIELD_IN_FIELD_LOOP = /^\s*dedup\s*\d*\s+\S+\s*(,\s*\S+\s*)*,\s*([^\s,]*)$/;
export const PIPE_COMMA_AFTER_FIELD = /^\s*dedup\s*\d*\s+\S+\s*(,\s*\S+\s*)*\s+$/;
export const PIPE_AFTER_KEEP_EMPTY = /^\s*dedup\s*\d*\s+\S+\s*(,\s*\S+\s*)*\s*keepempty=true\s+$/;
export const PIPE_AFTER_CONSECUTIVE = /^\s*dedup\s*\d*\s+\S+\s*(,\s*\S+\s*)*\s*consecutive=true\s+$/;
Expand Down Expand Up @@ -125,8 +125,11 @@ export const PIPE_COMMA_AFTER_SORT_FIELD = /^\s*sort(\s+\d+)?((,\s*)?\s+(\+|\-)?
export const PLUS_MINUS_FIELD_IN_FIELDS_LOOP = /^\s*sort(\s+\d+)?((,\s*)?\s+(\+|\-)?\s*\S+\s*)*,\s+\S*$/;

// Regex for stats command
export const FIELD_AFTER_STATS_GROUP_BY = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))+\s+by\s+\S*$/;
export const FIELD_AFTER_AGGREGATION = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))*(,\s*)?(sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S*$/;
export const FIELD_SPAN_AFTER_GROUP_BY = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))+\s+by\s+\S*$/;
export const NUM_FIELD_AFTER_AGGREGATION = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))*(,\s*)?(sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S*$/;
export const FIELD_AFTER_SPAN = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))+\s+by\s+span\(\s*([^\s,]*)\s*$/;
export const CLOSE_AFTER_SPAN = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))+\s+by\s+span\(\s*[^\s,]+\s*,\s*(("(\w|\s)+")|(\d+\.?\d*)|\w+)\s+$/;
export const PIPE_AFTER_SPAN = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))+\s+by\s+span\(\s*[^\s,]+\s*,\s*(("(\w|\s)*")|(\d*\.?\d*)|\w*)\s*\)\s*$/;
export const CLOSE_AFTER_FIELD = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))*(,\s*)?(sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s+$/;
export const PIPE_COMMA_BY_AFTER_AGGREGATION = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))+\s+\S*$/;
export const PIPE_AFTER_STATS_GROUP_BY = /^\s*stats\s+((,\s*)?((sum|avg|max|min|var_samp|var_pop|stddev_samp|stddev_pop)\(\s*\S+\s*\)\s*)|((,\s*)?count\(\)\s*))+\s+by\s+\S+\s+$/;
Expand Down Expand Up @@ -167,8 +170,11 @@ export const regexForSuggestion = [
FIELD_AFTER_PLUS_MINUS_SORT,
PLUS_MINUS_FIELD_IN_FIELDS_LOOP,
PIPE_COMMA_AFTER_SORT_FIELD,
FIELD_AFTER_STATS_GROUP_BY,
FIELD_AFTER_AGGREGATION,
FIELD_SPAN_AFTER_GROUP_BY,
NUM_FIELD_AFTER_AGGREGATION,
FIELD_AFTER_SPAN,
CLOSE_AFTER_SPAN,
PIPE_AFTER_SPAN,
CLOSE_AFTER_FIELD,
PIPE_COMMA_BY_AFTER_AGGREGATION,
PIPE_AFTER_STATS_GROUP_BY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,19 @@ import {
PIPE_COMMA_AFTER_SORT_FIELD,
PLUS_MINUS_FIELD_IN_FIELDS_LOOP,
FIELD_AFTER_COMMAND,
FIELD_AFTER_STATS_GROUP_BY,
FIELD_AFTER_AGGREGATION,
FIELD_SPAN_AFTER_GROUP_BY,
NUM_FIELD_AFTER_AGGREGATION,
CLOSE_AFTER_FIELD,
PIPE_COMMA_BY_AFTER_AGGREGATION,
PIPE_AFTER_STATS_GROUP_BY,
AGGREGATION_FOR_STATS,
FIELD_AFTER_SPAN,
CLOSE_AFTER_SPAN,
PIPE_AFTER_SPAN,
} from '../../../../common/constants/autocomplete';

let currIndex: string = '';
// let currIndex: string = '';
Copy link
Contributor

Choose a reason for hiding this comment

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

can we remove this line if currIndex is no longer used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Good catch.

let currIndices: string[] = [];
let currField: string = '';
let currFieldType: string = '';

Expand Down Expand Up @@ -89,65 +93,74 @@ const getIndices = async (dslService: DSLService): Promise<void> => {
};

const getFields = async (dslService: DSLService): Promise<void> => {
if (currIndex !== '') {
const res = await dslService.fetchFields(currIndex);
if (!isEmpty(currIndices)) {
fieldsFromBackend.length = 0;
if (!res) {
return;
}
const resFieldList = Object.keys(res?.[currIndex].mappings.properties);
for (let i = 0; i < resFieldList.length; i++) {
const element = resFieldList[i];
if (
res?.[currIndex].mappings.properties[element].properties ||
res?.[currIndex].mappings.properties[element].fields
) {
fieldsFromBackend.push({ label: element, type: 'string' });
} else if (res?.[currIndex].mappings.properties[element].type === 'keyword') {
fieldsFromBackend.push({ label: element, type: 'string' });
} else {
fieldsFromBackend.push({
label: element,
type: res?.[currIndex].mappings.properties[element].type,
});
for (let i = 0; i < currIndices.length; i++) {
const index = currIndices[i];
const res = await dslService.fetchFields(index);
if (!res) {
return;
}
const resFieldList = Object.keys(res?.[index].mappings.properties);
for (let j = 0; j < resFieldList.length; j++) {
const element = resFieldList[j];
if (
res?.[index].mappings.properties[element].properties ||
res?.[index].mappings.properties[element].fields
) {
fieldsFromBackend.push({ label: element, type: 'string' });
} else if (res?.[index].mappings.properties[element].type === 'keyword') {
fieldsFromBackend.push({ label: element, type: 'string' });
} else {
fieldsFromBackend.push({
label: element,
type: res?.[index].mappings.properties[element].type,
});
}
fieldList.push(element);
}
fieldList.push(element);
}
}
};

const getDataValues = async (
index: string,
indices: string[],
field: string,
fieldType: string,
dslService: DSLService
): Promise<DataItem[]> => {
const res =
(await dslService.fetch(getDataValueQuery(index, field)))?.aggregations?.top_tags?.buckets ||
[];
dataValuesFromBackend.length = 0;
res.forEach((e: any) => {
if (fieldType === 'string') {
dataValuesFromBackend.push({ label: '"' + e.key + '"', doc_count: e.doc_count });
} else if (fieldType === 'boolean') {
if (e.key === 1) {
dataValuesFromBackend.push({ label: 'True', doc_count: e.doc_count });
} else {
dataValuesFromBackend.push({ label: 'False', doc_count: e.doc_count });
}
} else if (fieldType !== 'geo_point') {
dataValuesFromBackend.push({ label: String(e.key), doc_count: e.doc_count });
for (let i = 0; i < indices.length; i++) {
const index = indices[i];
const res = (await dslService.fetch(getDataValueQuery(index, field)))?.aggregations?.top_tags
?.buckets;
if (isEmpty(res)) {
continue;
}
});
return dataValuesFromBackend;
dataValuesFromBackend.length = 0;
res.forEach((e: any) => {
if (fieldType === 'string') {
dataValuesFromBackend.push({ label: '"' + e.key + '"', doc_count: e.doc_count });
} else if (fieldType === 'boolean') {
if (e.key === 1) {
dataValuesFromBackend.push({ label: 'True', doc_count: e.doc_count });
} else {
dataValuesFromBackend.push({ label: 'False', doc_count: e.doc_count });
}
} else if (fieldType !== 'geo_point') {
dataValuesFromBackend.push({ label: String(e.key), doc_count: e.doc_count });
}
});
return dataValuesFromBackend;
}
return [];
};

export const onItemSelect = async (
{ setQuery, item }: { setQuery: any; item: any },
dslService: DSLService
) => {
if (fieldsFromBackend.length === 0 && indexList.includes(item.itemName)) {
currIndex = item.itemName;
currIndices = [item.itemName];
getFields(dslService);
}
setQuery(item.label + ' ');
Expand Down Expand Up @@ -199,7 +212,7 @@ export const getFullSuggestions = async (
// Check the last full word in the query, then suggest inputs based off that
if (splittedModel.length === 1) {
currField = '';
currIndex = '';
currIndices = [];
return getFirstPipe(str, dslService);
} else if (splittedModel.length > 1) {
// Suggest commands after pipe
Expand Down Expand Up @@ -233,7 +246,7 @@ export const getFullSuggestions = async (
return fillSuggestions(str, prefix, indicesFromBackend);
// Suggest pipe after setting source
} else if (indexList.includes(splittedModel[splittedModel.length - 2])) {
currIndex = splittedModel[splittedModel.length - 2];
currIndices = [splittedModel[splittedModel.length - 2]];
getFields(dslService);
return filterSuggestions(
[{ label: str + '|', input: str, suggestion: '|', itemName: '|' }],
Expand Down Expand Up @@ -336,13 +349,13 @@ export const getFullSuggestions = async (
fieldsFromBackend.find(
(field: { label: string; type: string }) => field.label === currField
)?.type || '';
await getDataValues(currIndex, currField, currFieldType, dslService);
await getDataValues(currIndices, currField, currFieldType, dslService);
return filterSuggestions(fullSuggestions, lowerPrefix);
// Suggest , after match([field]
} else if (inMatch && fieldList.includes(splittedModel[splittedModel.length - 2])) {
currField = splittedModel[splittedModel.length - 2];
currFieldType = fieldsFromBackend.find((field) => field.label === currField)?.type || '';
await getDataValues(currIndex, currField, currFieldType, dslService);
await getDataValues(currIndices, currField, currFieldType, dslService);
return filterSuggestions(
[{ label: str + ',', input: str, suggestion: ',', itemName: ',' }],
lowerPrefix
Expand Down Expand Up @@ -387,14 +400,17 @@ export const getFullSuggestions = async (
return [];
};

const parseForIndex = (query: string) => {
const parseForIndices = (query: string) => {
for (let i = 0; i < regexForIndex.length; i++) {
const groupArray = regexForIndex[i].exec(query);
if (groupArray) {
return groupArray[1];
const afterEqual = query.substring(query.indexOf('=') + 1);
const beforePipe = afterEqual.substring(0, afterEqual.indexOf('|')) || afterEqual;
const noSpaces = beforePipe.replaceAll(/\s/g, '');
return noSpaces.split(',');
}
}
return '';
return [];
};

const parseForNextSuggestion = (command: string) => {
Expand All @@ -418,8 +434,8 @@ export const getSuggestionsAfterSource = async (
const lastWord = splitSpaceQuery[splitSpaceQuery.length - 1];
const lastCommand = splitPipeQuery[splitPipeQuery.length - 1];

if (isEmpty(currQuery)) {
currIndex = parseForIndex(base);
if (isEmpty(currQuery) || isEmpty(currIndices)) {
currIndices = parseForIndices(base);
getFields(dslService);
currField = '';
currFieldType = '';
Expand Down Expand Up @@ -475,12 +491,24 @@ export const getSuggestionsAfterSource = async (
]);
case CLOSE_AFTER_DATA:
case CLOSE_AFTER_FIELD:
case CLOSE_AFTER_SPAN:
return fillSuggestions(currQuery, lastWord, [{ label: ')' }]);
case COMMA_AFTER_FIELD:
currField = COMMA_AFTER_FIELD.exec(lastCommand)![1];
currFieldType = fieldsFromBackend.find((field) => field.label === currField)?.type || '';
await getDataValues(currIndex, currField, currFieldType, dslService);
await getDataValues(currIndices, currField, currFieldType, dslService);
return fillSuggestions(currQuery, lastWord, [{ label: ',' }]);
case FIELD_AFTER_SPAN:
const matchArray = FIELD_AFTER_SPAN.exec(lastCommand);
const tempField = matchArray![matchArray!.length - 1];
if (fieldList.includes(tempField)) {
currField = tempField;
currFieldType = fieldsFromBackend.find((field) => field.label === currField)?.type || '';
await getDataValues(currIndices, currField, currFieldType, dslService);
return fillSuggestions(currQuery, lastWord, [{ label: ',' }]);
} else {
return fillSuggestions(currQuery, lastWord, fieldsFromBackend);
}
case FIELD_AFTER_COMMAND:
case FIELD_IN_FIELD_LOOP:
case FIELD_AFTER_EVAL_EQUAL:
Expand All @@ -490,16 +518,22 @@ export const getSuggestionsAfterSource = async (
case FIELD_AFTER_BY:
case FIELD_AFTER_COMMA:
case FIELD_AFTER_PLUS_MINUS_SORT:
case FIELD_AFTER_STATS_GROUP_BY:
case FIELD_AFTER_AGGREGATION:
return fillSuggestions(currQuery, lastWord, fieldsFromBackend);
case FIELD_SPAN_AFTER_GROUP_BY:
return fillSuggestions(currQuery, lastWord, [{ label: 'span(' }, ...fieldsFromBackend]);
case NUM_FIELD_AFTER_AGGREGATION:
const numberFields = fieldsFromBackend.filter((field: { type: string }) =>
numberTypes.includes(field.type)
);
return fillSuggestions(currQuery, lastWord, numberFields);
case PIPE_AFTER_WHERE:
case PIPE_AFTER_MATCH:
case PIPE_AFTER_KEEP_EMPTY:
case PIPE_AFTER_CONSECUTIVE:
case PIPE_AFTER_GROUP_BY:
case PIPE_AFTER_HEAD:
case PIPE_AFTER_STATS_GROUP_BY:
case PIPE_AFTER_SPAN:
return fillSuggestions(currQuery, lastWord, [{ label: '|' }]);
case DATA_AFTER_WHERE_EQUAL:
case DATA_AFTER_COMMA:
Expand All @@ -508,7 +542,7 @@ export const getSuggestionsAfterSource = async (
case EQUAL_AFTER_EVAL_FIELD:
currField = next.exec(lastCommand)![1];
currFieldType = fieldsFromBackend.find((field) => field.label === currField)?.type || '';
await getDataValues(currIndex, currField, currFieldType, dslService);
await getDataValues(currIndices, currField, currFieldType, dslService);
return fillSuggestions(currQuery, lastWord, [{ label: '=' }]);
case MATCH_FIELD_AFTER_WHERE:
return fillSuggestions(currQuery, lastWord, [{ label: 'match(' }, ...fieldsFromBackend]);
Expand Down