mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[ES|QL][Discover] Fixes CSV export with named params (#206914)
## Summary Closes https://github.com/elastic/kibana/issues/206719 Allows the csv report to get generated when there are time named params --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
75e1866915
commit
385d2c1154
6 changed files with 125 additions and 24 deletions
|
@ -32,11 +32,8 @@ import {
|
|||
} from '../constants';
|
||||
import { CsvESQLGenerator, JobParamsCsvESQL } from './generate_csv_esql';
|
||||
|
||||
const createMockJob = (
|
||||
params: Partial<JobParamsCsvESQL> = { query: { esql: '' } }
|
||||
): JobParamsCsvESQL => ({
|
||||
const createMockJob = (params: JobParamsCsvESQL): JobParamsCsvESQL => ({
|
||||
...params,
|
||||
query: { esql: '' },
|
||||
});
|
||||
|
||||
const mockTaskInstanceFields = { startedAt: null, retryAt: null };
|
||||
|
@ -106,7 +103,7 @@ describe('CsvESQLGenerator', () => {
|
|||
|
||||
it('formats an empty search result to CSV content', async () => {
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob({ columns: ['date', 'ip', 'message'] }),
|
||||
createMockJob({ query: { esql: '' }, columns: ['date', 'ip', 'message'] }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -138,7 +135,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -166,7 +163,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -196,7 +193,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -286,7 +283,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsvPromise = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfigWithAutoScrollDuration,
|
||||
taskInstanceFields,
|
||||
{
|
||||
|
@ -362,7 +359,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsvPromise = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfigWithAutoScrollDuration,
|
||||
taskInstanceFields,
|
||||
{
|
||||
|
@ -413,7 +410,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob({ columns: ['message', 'date', 'something else'] }),
|
||||
createMockJob({ query: { esql: '' }, columns: ['message', 'date', 'something else'] }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -484,7 +481,80 @@ describe('CsvESQLGenerator', () => {
|
|||
},
|
||||
},
|
||||
locale: 'en',
|
||||
query: '',
|
||||
query: query.esql,
|
||||
},
|
||||
},
|
||||
{
|
||||
strategy: 'esql',
|
||||
transport: {
|
||||
requestTimeout: '30s',
|
||||
},
|
||||
abortSignal: expect.any(AbortSignal),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('passes params to the query', async () => {
|
||||
const query = {
|
||||
esql: 'FROM custom-metrics-without-timestamp | WHERE event.ingested >= ?_tstart AND event.ingested <= ?_tend',
|
||||
};
|
||||
const filters = [
|
||||
{
|
||||
meta: {},
|
||||
query: {
|
||||
range: {
|
||||
'event.ingested': { format: 'strict_date_optional_time', gte: 'now-15m', lte: 'now' },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob({ query, filters }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
es: mockEsClient,
|
||||
data: mockDataClient,
|
||||
uiSettings: uiSettingsClient,
|
||||
},
|
||||
new CancellationToken(),
|
||||
mockLogger,
|
||||
stream
|
||||
);
|
||||
await generateCsv.generateData();
|
||||
|
||||
expect(mockDataClient.search).toHaveBeenCalledWith(
|
||||
{
|
||||
params: {
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'event.ingested': {
|
||||
format: 'strict_date_optional_time',
|
||||
gte: 'now-15m',
|
||||
lte: 'now',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
must: [],
|
||||
must_not: [],
|
||||
should: [],
|
||||
},
|
||||
},
|
||||
params: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
_tstart: expect.any(String),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
_tend: expect.any(String),
|
||||
}),
|
||||
]),
|
||||
locale: 'en',
|
||||
query: query.esql,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -508,7 +578,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -538,7 +608,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -576,7 +646,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -605,7 +675,7 @@ describe('CsvESQLGenerator', () => {
|
|||
throw new Error('An unknown error');
|
||||
});
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
@ -642,7 +712,7 @@ describe('CsvESQLGenerator', () => {
|
|||
});
|
||||
|
||||
const generateCsv = new CsvESQLGenerator(
|
||||
createMockJob(),
|
||||
createMockJob({ query: { esql: '' } }),
|
||||
mockConfig,
|
||||
mockTaskInstanceFields,
|
||||
{
|
||||
|
|
|
@ -14,7 +14,8 @@ import type { IScopedClusterClient, IUiSettingsClient, Logger } from '@kbn/core/
|
|||
import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types';
|
||||
import { ESQL_SEARCH_STRATEGY, cellHasFormulas, getEsQueryConfig } from '@kbn/data-plugin/common';
|
||||
import type { IScopedSearchClient } from '@kbn/data-plugin/server';
|
||||
import { type Filter, buildEsQuery } from '@kbn/es-query';
|
||||
import { type Filter, buildEsQuery, extractTimeRange } from '@kbn/es-query';
|
||||
import { getTimeFieldFromESQLQuery, getStartEndParams } from '@kbn/esql-utils';
|
||||
import type { ESQLSearchParams, ESQLSearchResponse } from '@kbn/es-types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
|
@ -76,6 +77,17 @@ export class CsvESQLGenerator {
|
|||
const { maxSizeBytes, bom, escapeFormulaValues } = settings;
|
||||
const builder = new MaxSizeStringBuilder(this.stream, byteSizeValueToNumber(maxSizeBytes), bom);
|
||||
|
||||
// it will return undefined if there are no _tstart, _tend named params in the query
|
||||
const timeFieldName = getTimeFieldFromESQLQuery(this.job.query.esql);
|
||||
const params = [];
|
||||
if (timeFieldName && this.job.filters) {
|
||||
const { timeRange } = extractTimeRange(this.job.filters, timeFieldName);
|
||||
if (timeRange) {
|
||||
const namedParams = getStartEndParams(this.job.query.esql, timeRange);
|
||||
params.push(...namedParams);
|
||||
}
|
||||
}
|
||||
|
||||
const filter =
|
||||
this.job.filters &&
|
||||
buildEsQuery(
|
||||
|
@ -91,6 +103,7 @@ export class CsvESQLGenerator {
|
|||
filter,
|
||||
// locale can be used for number/date formatting
|
||||
locale: i18n.getLocale(),
|
||||
...(params.length ? { params } : {}),
|
||||
// TODO: time_zone support was temporarily removed from ES|QL,
|
||||
// we will need to add it back in once it is supported again.
|
||||
// https://github.com/elastic/elasticsearch/pull/102767
|
||||
|
|
|
@ -31,5 +31,6 @@
|
|||
"@kbn/data-views-plugin",
|
||||
"@kbn/search-types",
|
||||
"@kbn/task-manager-plugin",
|
||||
"@kbn/esql-utils",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -24,4 +24,15 @@ describe('convertRangeFilterToTimeRange', () => {
|
|||
|
||||
expect(convertedRangeFilter).toEqual(filterAfterConvertedRangeFilter);
|
||||
});
|
||||
|
||||
it('should return converted range for relative dates', () => {
|
||||
const filter: any = { query: { range: { '@timestamp': { gte: 'now-1d', lte: 'now' } } } };
|
||||
const filterAfterConvertedRangeFilter = {
|
||||
from: 'now-1d',
|
||||
to: 'now',
|
||||
};
|
||||
const convertedRangeFilter = convertRangeFilterToTimeRange(filter);
|
||||
|
||||
expect(convertedRangeFilter).toEqual(filterAfterConvertedRangeFilter);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,20 +12,26 @@ import { keys } from 'lodash';
|
|||
import type { RangeFilter } from '../build_filters';
|
||||
import type { TimeRange } from './types';
|
||||
|
||||
const isRelativeTime = (value: string | number | undefined): boolean => {
|
||||
return typeof value === 'string' && value.includes('now');
|
||||
};
|
||||
|
||||
export function convertRangeFilterToTimeRange(filter: RangeFilter) {
|
||||
const key = keys(filter.query.range)[0];
|
||||
const values = filter.query.range[key];
|
||||
|
||||
const from = values.gt || values.gte;
|
||||
const to = values.lt || values.lte;
|
||||
return {
|
||||
from: moment(values.gt || values.gte),
|
||||
to: moment(values.lt || values.lte),
|
||||
from: from && isRelativeTime(from) ? String(from) : moment(from),
|
||||
to: to && isRelativeTime(to) ? String(to) : moment(to),
|
||||
};
|
||||
}
|
||||
|
||||
export function convertRangeFilterToTimeRangeString(filter: RangeFilter): TimeRange {
|
||||
const { from, to } = convertRangeFilterToTimeRange(filter);
|
||||
return {
|
||||
from: from?.toISOString(),
|
||||
to: to?.toISOString(),
|
||||
from: moment.isMoment(from) ? from?.toISOString() : from,
|
||||
to: moment.isMoment(to) ? to?.toISOString() : to,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ export interface TimefilterConfig {
|
|||
export type InputTimeRange =
|
||||
| TimeRange
|
||||
| {
|
||||
from: Moment;
|
||||
to: Moment;
|
||||
from: Moment | string;
|
||||
to: Moment | string;
|
||||
};
|
||||
|
||||
export type { TimeRangeBounds } from '../../../common';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue