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

[ES|QL] [Discover] Creating where clause filters from the table, sidebar and table row viewer #181399

Merged
merged 35 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4ec560b
[ES|QL] Propagate the query with where clasue
stratoula Apr 23, 2024
199c8b2
Take under consideration the last command
stratoula Apr 23, 2024
71b16d2
Merge branch 'main' into discover-create-where-filter
stratoula Apr 24, 2024
6281901
Take under cosideration the filter existence
stratoula Apr 24, 2024
d4f6fc3
Disable filter actions when the table is embedded
stratoula Apr 25, 2024
07a2075
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Apr 25, 2024
c995cc0
Simplifications
stratoula Apr 25, 2024
11639c5
Fixes
stratoula Apr 25, 2024
ab80ffb
Merge branch 'main' into discover-create-where-filter
stratoula Apr 26, 2024
dfbe8dd
More fixes
stratoula Apr 26, 2024
149c888
Fix quotes
stratoula Apr 26, 2024
4cf0952
Fix jest tests
stratoula Apr 26, 2024
6d726f3
Fix jest tests
stratoula Apr 26, 2024
2d64643
Merge branch 'main' into discover-create-where-filter
stratoula Apr 29, 2024
92b5050
Some cleanuo
stratoula Apr 29, 2024
deee9d4
Add functional tests
stratoula Apr 29, 2024
067d5d3
Fix FTs
stratoula Apr 29, 2024
1f95a3e
Merge branch 'main' into discover-create-where-filter
stratoula Apr 30, 2024
523d505
Wait for the query to run - test stabilization
stratoula Apr 30, 2024
dc6ff3e
Merge branch 'main' into discover-create-where-filter
stratoula Apr 30, 2024
112e501
Merge branch 'main' into discover-create-where-filter
stratoula Apr 30, 2024
aa0e9fa
Merge with main and resolve conflicts
stratoula May 1, 2024
a6ddb09
Address some of the comments
stratoula May 1, 2024
aca8198
Fix case bug
stratoula May 1, 2024
8c14396
Use recursive function
stratoula May 1, 2024
22e5a44
Merge branch 'main' into discover-create-where-filter
stratoula May 1, 2024
9142030
Merge branch 'main' into discover-create-where-filter
stratoula May 2, 2024
ebad482
Revert implememtation and rely on regex
stratoula May 2, 2024
193e670
Do not append if filter is not null already exists
stratoula May 2, 2024
41cb5ed
Merge branch 'main' into discover-create-where-filter
stratoula May 5, 2024
4dc57a9
Merge branch 'main' into discover-create-where-filter
stratoula May 6, 2024
fff036d
Correct existing wrong Discover tests
stratoula May 6, 2024
adc9d6e
Pass the correct type for the extension in the test
stratoula May 6, 2024
0d16316
Simoplification
stratoula May 6, 2024
8bc656e
Update jest test
stratoula May 6, 2024
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
2 changes: 2 additions & 0 deletions packages/kbn-esql-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ This package contains utilities for ES|QL.
- *getIndexPatternFromESQLQuery*: Use this to retrieve the index pattern from the `from` command.
- *getLimitFromESQLQuery*: Use this function to get the limit for a given query. The limit can be either set from the `limit` command or can be a default value set in ES.
- *removeDropCommandsFromESQLQuery*: Use this function to remove all the occurences of the `drop` command from the query.
- *appendToESQLQuery*: Use this function to append more pipes in an existing ES|QL query. It adds the additional commands in a new line.
- *appendWhereClauseToESQLQuery*: Use this function to append where clause in an existing query.

1 change: 1 addition & 0 deletions packages/kbn-esql-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export {
getInitialESQLQuery,
getESQLWithSafeLimit,
appendToESQLQuery,
appendWhereClauseToESQLQuery,
TextBasedLanguages,
} from './src';

Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-esql-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ export {
getLimitFromESQLQuery,
removeDropCommandsFromESQLQuery,
} from './utils/query_parsing_helpers';
export { appendToESQLQuery } from './utils/append_to_query';
export { appendToESQLQuery, appendWhereClauseToESQLQuery } from './utils/append_to_query';
142 changes: 130 additions & 12 deletions packages/kbn-esql-utils/src/utils/append_to_query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,139 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { appendToESQLQuery } from './append_to_query';
import { appendToESQLQuery, appendWhereClauseToESQLQuery } from './append_to_query';

describe('appendToESQLQuery', () => {
it('append the text on a new line after the query', () => {
expect(appendToESQLQuery('from logstash-* // meow', '| stats var = avg(woof)')).toBe(
`from logstash-* // meow
describe('appendToQuery', () => {
describe('appendToESQLQuery', () => {
it('append the text on a new line after the query', () => {
expect(appendToESQLQuery('from logstash-* // meow', '| stats var = avg(woof)')).toBe(
`from logstash-* // meow
| stats var = avg(woof)`
);
});
);
});

it('append the text on a new line after the query for text with variables', () => {
const limit = 10;
expect(appendToESQLQuery('from logstash-*', `| limit ${limit}`)).toBe(
`from logstash-*
it('append the text on a new line after the query for text with variables', () => {
const limit = 10;
expect(appendToESQLQuery('from logstash-*', `| limit ${limit}`)).toBe(
`from logstash-*
| limit 10`
);
);
});
});

describe('appendWhereClauseToESQLQuery', () => {
it('appends a filter in where clause in an existing query', () => {
expect(
appendWhereClauseToESQLQuery('from logstash-* // meow', 'dest', 'tada!', '+', 'string')
).toBe(
`from logstash-* // meow
| where \`dest\`=="tada!"`
);
});
it('appends a filter out where clause in an existing query', () => {
expect(
appendWhereClauseToESQLQuery('from logstash-* // meow', 'dest', 'tada!', '-', 'string')
).toBe(
`from logstash-* // meow
| where \`dest\`!="tada!"`
);
});

it('appends a where clause in an existing query with casting to string when the type is not string or number', () => {
expect(
appendWhereClauseToESQLQuery('from logstash-* // meow', 'dest', 'tada!', '-', 'ip')
).toBe(
`from logstash-* // meow
| where \`dest\`::string!="tada!"`
);
});

it('appends a where clause in an existing query with casting to string when the type is not given', () => {
expect(appendWhereClauseToESQLQuery('from logstash-* // meow', 'dest', 'tada!', '-')).toBe(
`from logstash-* // meow
| where \`dest\`::string!="tada!"`
);
});

it('appends a where clause in an existing query checking that the value is not null if the user asks for existence', () => {
expect(
appendWhereClauseToESQLQuery(
'from logstash-* // meow',
'dest',
undefined,
'_exists_',
'string'
)
).toBe(
`from logstash-* // meow
| where \`dest\` is not null`
);
});

it('appends an and clause in an existing query with where command as the last pipe', () => {
expect(
appendWhereClauseToESQLQuery(
'from logstash-* | where country == "GR"',
'dest',
'Crete',
'+',
'string'
)
).toBe(
`from logstash-* | where country == "GR"
and \`dest\`=="Crete"`
);
});

it('doesnt append anything in an existing query with where command as the last pipe if the filter preexists', () => {
expect(
appendWhereClauseToESQLQuery(
'from logstash-* | where country == "GR"',
'country',
'GR',
'+',
'string'
)
).toBe(`from logstash-* | where country == "GR"`);
});

it('changes the operator in an existing query with where command as the last pipe if the filter preexists but has the opposite operator', () => {
expect(
appendWhereClauseToESQLQuery(
'from logstash-* | where country == "GR"',
'country',
'GR',
'-',
'string'
)
).toBe(`from logstash-* | where country != "GR"`);
});

it('changes the operator in an existing query with where command as the last pipe if the filter preexists but has the opposite operator, the field has backticks', () => {
expect(
appendWhereClauseToESQLQuery(
'from logstash-* | where `country` == "GR"',
'country',
'GR',
'-',
'string'
)
).toBe(`from logstash-* | where \`country\` != "GR"`);
});

it('appends an and clause in an existing query with where command as the last pipe if the filter preexists but the operator is not the correct one', () => {
expect(
appendWhereClauseToESQLQuery(
`from logstash-* | where CIDR_MATCH(ip1, "127.0.0.2/32", "127.0.0.3/32")`,
'ip',
'127.0.0.2/32',
'-',
'ip'
)
).toBe(
`from logstash-* | where CIDR_MATCH(ip1, "127.0.0.2/32", "127.0.0.3/32")
and \`ip\`::string!="127.0.0.2/32"`
);
});
});
});
122 changes: 122 additions & 0 deletions packages/kbn-esql-utils/src/utils/append_to_query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,131 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import {
type ESQLFunction,
type ESQLSingleAstItem,
type ESQLColumn,
type ESQLLiteral,
getAstAndSyntaxErrors,
} from '@kbn/esql-ast';

interface FilterArguments {
fieldName: string;
value: string;
operator: string;
}

// Append in a new line the appended text to take care of the case where the user adds a comment at the end of the query
// in these cases a base query such as "from index // comment" will result in errors or wrong data if we don't append in a new line
export function appendToESQLQuery(baseESQLQuery: string, appendedText: string): string {
return `${baseESQLQuery}\n${appendedText}`;
}

function findInAst(
astItem: ESQLFunction,
fieldName: string,
value: string
): FilterArguments | undefined {
const singleAstItem = astItem as ESQLFunction;
const args = singleAstItem.args;
const isLastNode = args?.some((a) => 'type' in a && a.type === 'column');
if (isLastNode) {
const column = args.find((a) => {
const argument = a as ESQLSingleAstItem;
return argument.type === 'column';
}) as ESQLColumn;
const literal = args.find((a) => {
const argument = a as ESQLSingleAstItem;
return argument.type === 'literal';
}) as ESQLLiteral;
if (column?.name === fieldName && literal?.name.includes(value)) {
return {
fieldName,
value,
operator: astItem.name,
};
} else {
return undefined;
}
} else {
const functions = args?.filter((item) => 'type' in item && item.type === 'function');
for (const functionAstItem of functions) {
const item = findInAst(functionAstItem as ESQLFunction, fieldName, value);
if (item) {
return item;
}
}
}
}

export function appendWhereClauseToESQLQuery(
baseESQLQuery: string,
field: string,
value: unknown,
operation: '+' | '-' | '_exists_',
fieldType?: string
): string {
let operator = operation === '+' ? '==' : '!=';
let filterValue = typeof value === 'string' ? `"${value.replace(/"/g, '\\"')}"` : value;
// Adding the backticks here are they are needed for special char fields
let fieldName = `\`${field}\``;

// casting to string
// there are some field types such as the ip that need
// to cast in string first otherwise ES will fail
if (fieldType !== 'string' && fieldType !== 'number' && fieldType !== 'boolean') {
fieldName = `${fieldName}::string`;
stratoula marked this conversation as resolved.
Show resolved Hide resolved
}
jughosta marked this conversation as resolved.
Show resolved Hide resolved

// checking that the value is not null
// this is the existence filter
if (operation === '_exists_') {
fieldName = `\`${String(field)}\``;
operator = ' is not null';
filterValue = '';
}

const { ast } = getAstAndSyntaxErrors(baseESQLQuery);

const lastCommandIsWhere = ast[ast.length - 1].name === 'where';
// if where command already exists in the end of the query:
// - we need to append with and if the filter doesnt't exist
// - we need to change the filter operator if the filter exists with different operator
// - we do nothing if the filter exists with the same operator
jughosta marked this conversation as resolved.
Show resolved Hide resolved
if (lastCommandIsWhere) {
const whereCommand = ast[ast.length - 1];
const whereAstText = whereCommand.text;
// the filter already exists in the where clause
if (whereAstText.includes(field) && whereAstText.includes(String(filterValue))) {
const pipesArray = baseESQLQuery.split('|');
const whereClause = pipesArray[pipesArray.length - 1];

const args = ast[ast.length - 1].args[0] as ESQLFunction;
const astItem = findInAst(args, field, String(value));

if (astItem) {
const existingOperator = astItem.operator;
if (!['==', '!='].includes(existingOperator.trim())) {
return appendToESQLQuery(baseESQLQuery, `and ${fieldName}${operator}${filterValue}`);
}
// the filter is the same
if (existingOperator === operator) {
return baseESQLQuery;
// the filter has different operator
} else {
const matches = whereClause.match(new RegExp(field + '(.*)' + String(filterValue)));
if (matches) {
const existingFilter = matches[0].trim();
const newFilter = existingFilter.replace(existingOperator, operator);
return baseESQLQuery.replace(existingFilter, newFilter);
}
}
}
}
// filter does not exist in the where clause
const whereClause = `and ${fieldName}${operator}${filterValue}`;
return appendToESQLQuery(baseESQLQuery, whereClause);
stratoula marked this conversation as resolved.
Show resolved Hide resolved
}
const whereClause = `| where ${fieldName}${operator}${filterValue}`;
return appendToESQLQuery(baseESQLQuery, whereClause);
}
Loading