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

[RFC] Experiment: split Canvas Expression Definitions from async Expression logic #104990

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,42 @@ import {
ExpressionValueFilter,
} from 'src/plugins/expressions/common';

// @ts-expect-error untyped local
import { buildESRequest } from '../../../common/lib/request/build_es_request';

import { searchService } from '../../../public/services';

import { getFunctionHelp } from '../../../i18n';
import { i18n } from '@kbn/i18n';
import { ELASTICSEARCH, LUCENE } from '../../../i18n/constants';

interface Arguments {
index: string | null;
query: string;
}

const help = i18n.translate('xpack.canvas.functions.escountHelpText', {
defaultMessage: 'Query {ELASTICSEARCH} for the number of hits matching the specified query.',
values: {
ELASTICSEARCH,
},
});

const argHelp = {
query: i18n.translate('xpack.canvas.functions.escount.args.queryHelpText', {
defaultMessage: 'A {LUCENE} query string.',
values: {
LUCENE,
},
}),
index: i18n.translate('xpack.canvas.functions.escount.args.indexHelpText', {
defaultMessage: 'An index or index pattern. For example, {example}.',
values: {
example: '`"logstash-*"`',
},
}),
};

export function escount(): ExpressionFunctionDefinition<
'escount',
ExpressionValueFilter,
Arguments,
any
> {
const { help, args: argHelp } = getFunctionHelp().escount;

return {
name: 'escount',
type: 'number',
Expand All @@ -50,45 +66,9 @@ export function escount(): ExpressionFunctionDefinition<
help: argHelp.index,
},
},
fn: (input, args, handlers) => {
input.and = input.and.concat([
{
type: 'filter',
filterType: 'luceneQueryString',
query: args.query,
and: [],
},
]);

const esRequest = buildESRequest(
{
index: args.index,
body: {
track_total_hits: true,
size: 0,
query: {
bool: {
must: [{ match_all: {} }],
},
},
},
},
input
);

const search = searchService.getService().search;
const req = {
params: {
...esRequest,
},
};

return search
.search(req)
.toPromise()
.then((resp: any) => {
return resp.rawResponse.hits.total;
});
fn: async (input, args, context) => {
const { escountFn } = await import('./fns');
return await escountFn(input, args, context);
},
};
}
122 changes: 51 additions & 71 deletions x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,8 @@ import {
ExpressionValueFilter,
} from 'src/plugins/expressions/common';

// @ts-expect-error untyped local
import { buildESRequest } from '../../../common/lib/request/build_es_request';

import { searchService } from '../../../public/services';
import { ESSQL_SEARCH_STRATEGY } from '../../../common/lib/constants';
import { EssqlSearchStrategyRequest, EssqlSearchStrategyResponse } from '../../../types';
import { getFunctionHelp } from '../../../i18n';
import { i18n } from '@kbn/i18n';
import { ELASTICSEARCH, LUCENE } from '../../../i18n/constants';

interface Arguments {
index: string;
Expand All @@ -27,14 +22,58 @@ interface Arguments {
count: number;
}

const help = i18n.translate('xpack.canvas.functions.esdocsHelpText', {
defaultMessage:
'Query {ELASTICSEARCH} for raw documents. Specify the fields you want to retrieve, ' +
'especially if you are asking for a lot of rows.',
values: {
ELASTICSEARCH,
},
});

const argHelp = {
query: i18n.translate('xpack.canvas.functions.esdocs.args.queryHelpText', {
defaultMessage: 'A {LUCENE} query string.',
values: {
LUCENE,
},
}),
count: i18n.translate('xpack.canvas.functions.esdocs.args.countHelpText', {
defaultMessage:
'The number of documents to retrieve. For better performance, use a smaller data set.',
}),
fields: i18n.translate('xpack.canvas.functions.esdocs.args.fieldsHelpText', {
defaultMessage: 'A comma-separated list of fields. For better performance, use fewer fields.',
}),
index: i18n.translate('xpack.canvas.functions.esdocs.args.indexHelpText', {
defaultMessage: 'An index or index pattern. For example, {example}.',
values: {
example: '`"logstash-*"`',
},
}),
metaFields: i18n.translate('xpack.canvas.functions.esdocs.args.metaFieldsHelpText', {
defaultMessage: 'Comma separated list of meta fields. For example, {example}.',
values: {
example: '`"_index,_type"`',
},
}),
sort: i18n.translate('xpack.canvas.functions.esdocs.args.sortHelpText', {
defaultMessage:
'The sort direction formatted as {directions}. For example, {example1} or {example2}.',
values: {
directions: `\`"${['field', 'direction'].join(', ')}"\``,
example1: `\`"${['@timestamp', 'desc'].join(', ')}"\``,
example2: `\`"${['bytes', 'asc'].join(', ')}"\``,
},
}),
};

export function esdocs(): ExpressionFunctionDefinition<
'esdocs',
ExpressionValueFilter,
Arguments,
any
> {
const { help, args: argHelp } = getFunctionHelp().esdocs;

return {
name: 'esdocs',
type: 'datatable',
Expand Down Expand Up @@ -74,68 +113,9 @@ export function esdocs(): ExpressionFunctionDefinition<
help: argHelp.sort,
},
},
fn: async (input, args, handlers) => {
const { count, index, fields, sort } = args;

input.and = input.and.concat([
{
type: 'filter',
filterType: 'luceneQueryString',
query: args.query,
and: [],
},
]);

// Load ad-hoc to avoid adding to the page load bundle size
const squel = await import('safe-squel');

let query = squel.select({
autoQuoteTableNames: true,
autoQuoteFieldNames: true,
autoQuoteAliasNames: true,
nameQuoteCharacter: '"',
});

if (index) {
query.from(index);
}

if (fields) {
const allFields = fields.split(',').map((field) => field.trim());
allFields.forEach((field) => (query = query.field(field)));
}

if (sort) {
const [sortField, sortOrder] = sort.split(',').map((str) => str.trim());
if (sortField) {
query.order(`"${sortField}"`, sortOrder === 'asc');
}
}

const search = searchService.getService().search;

const req = {
count,
query: query.toString(),
filter: input.and,
};

// We're requesting the data using the ESSQL strategy because
// the SQL routes return type information with the result set
return search
.search<EssqlSearchStrategyRequest, EssqlSearchStrategyResponse>(req, {
strategy: ESSQL_SEARCH_STRATEGY,
})
.toPromise()
.then((resp: EssqlSearchStrategyResponse) => {
return {
type: 'datatable',
meta: {
type: 'essql',
},
...resp,
};
});
fn: async (input, args, context) => {
const { esdocsFn } = await import('./fns');
return await esdocsFn(input, args, context);
},
};
}
86 changes: 42 additions & 44 deletions x-pack/plugins/canvas/canvas_plugin_src/functions/browser/essql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import {
ExpressionFunctionDefinition,
ExpressionValueFilter,
} from 'src/plugins/expressions/common';
import { searchService } from '../../../public/services';
import { ESSQL_SEARCH_STRATEGY } from '../../../common/lib/constants';
import { EssqlSearchStrategyRequest, EssqlSearchStrategyResponse } from '../../../types';
import { getFunctionHelp } from '../../../i18n';

import { i18n } from '@kbn/i18n';
import { ELASTICSEARCH, SQL, ISO8601, UTC } from '../../../i18n/constants';

interface Arguments {
query: string;
Expand All @@ -21,14 +20,48 @@ interface Arguments {
timezone: string;
}

const help = i18n.translate('xpack.canvas.functions.essqlHelpText', {
defaultMessage: 'Queries {ELASTICSEARCH} using {ELASTICSEARCH} {SQL}.',
values: {
ELASTICSEARCH,
SQL,
},
});

const argHelp = {
query: i18n.translate('xpack.canvas.functions.essql.args.queryHelpText', {
defaultMessage: 'An {ELASTICSEARCH} {SQL} query.',
values: {
ELASTICSEARCH,
SQL,
},
}),
parameter: i18n.translate('xpack.canvas.functions.essql.args.parameterHelpText', {
defaultMessage: 'A parameter to be passed to the {SQL} query.',
values: {
SQL,
},
}),
count: i18n.translate('xpack.canvas.functions.essql.args.countHelpText', {
defaultMessage:
'The number of documents to retrieve. For better performance, use a smaller data set.',
}),
timezone: i18n.translate('xpack.canvas.functions.essql.args.timezoneHelpText', {
defaultMessage:
'The timezone to use for date operations. Valid {ISO8601} formats and {UTC} offsets both work.',
values: {
ISO8601,
UTC,
},
}),
};

export function essql(): ExpressionFunctionDefinition<
'essql',
ExpressionValueFilter,
Arguments,
any
> {
const { help, args: argHelp } = getFunctionHelp().essql;

return {
name: 'essql',
type: 'datatable',
Expand Down Expand Up @@ -60,44 +93,9 @@ export function essql(): ExpressionFunctionDefinition<
help: argHelp.timezone,
},
},
fn: (input, args, handlers) => {
const search = searchService.getService().search;
const { parameter, ...restOfArgs } = args;
const req = {
...restOfArgs,
params: parameter,
filter: input.and,
};

return search
.search<EssqlSearchStrategyRequest, EssqlSearchStrategyResponse>(req, {
strategy: ESSQL_SEARCH_STRATEGY,
})
.toPromise()
.then((resp: EssqlSearchStrategyResponse) => {
return {
type: 'datatable',
meta: {
type: 'essql',
},
...resp,
};
})
.catch((e) => {
let message = `Unexpected error from Elasticsearch: ${e.message}`;
if (e.err) {
const { type, reason } = e.err.attributes;
if (type === 'parsing_exception') {
message = `Couldn't parse Elasticsearch SQL query. You may need to add double quotes to names containing special characters. Check your query and try again. Error: ${reason}`;
} else {
message = `Unexpected error from Elasticsearch: ${type} - ${reason}`;
}
}

// Re-write the error message before surfacing it up
e.message = message;
throw e;
});
fn: async (input, args, context) => {
const { essqlFn } = await import('./fns');
return await essqlFn(input, args, context);
},
};
}
Loading