Skip to content

Commit b25f236

Browse files
authored
[ES|QL] [Discover] Keeps the histogram config on time change (#208053)
## Summary Closes #198749 ![meow](https://github.com/user-attachments/assets/2cb2ff53-49f9-414e-985f-c0acd3945078) ### Checklist - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
1 parent dac6600 commit b25f236

9 files changed

Lines changed: 126 additions & 18 deletions

File tree

src/platform/plugins/shared/unified_histogram/public/__mocks__/suggestions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,12 @@ export const histogramESQLSuggestionMock = {
185185
'662552df-2cdc-4539-bf3b-73b9f827252c': {
186186
index: 'e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a',
187187
query: {
188-
esql: 'from kibana_sample_data_logs | limit 10 | EVAL timestamp=DATE_TRUNC(30 second, @timestamp) | stats results = count(*) by timestamp | rename timestamp as `@timestamp every 30 second`',
188+
esql: 'from kibana_sample_data_logs | limit 10 | EVAL timestamp=DATE_TRUNC(30 second, @timestamp) | stats results = count(*) by timestamp',
189189
},
190190
columns: [
191191
{
192-
columnId: '@timestamp every 30 second',
193-
fieldName: '@timestamp every 30 second',
192+
columnId: 'timestamp',
193+
fieldName: 'timestamp',
194194
meta: {
195195
type: 'date',
196196
},

src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.attributes.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ describe('LensVisService attributes', () => {
678678
],
679679
"query": Object {
680680
"esql": "from logstash-* | limit 10
681-
| EVAL timestamp=DATE_TRUNC(10 minute, timestamp) | stats results = count(*) by timestamp | rename timestamp as \`timestamp every 10 minute\`",
681+
| EVAL timestamp=DATE_TRUNC(10 minute, timestamp) | stats results = count(*) by timestamp",
682682
},
683683
"visualization": Object {
684684
"gridConfig": Object {
@@ -757,7 +757,7 @@ describe('LensVisService attributes', () => {
757757
it('should use the correct histogram query when no suggestion passed', async () => {
758758
const histogramQuery = {
759759
esql: `from logstash-* | limit 10
760-
| EVAL timestamp=DATE_TRUNC(10 minute, @timestamp) | stats results = count(*) by timestamp | rename timestamp as \`@timestamp every 10 minute\``,
760+
| EVAL timestamp=DATE_TRUNC(10 minute, @timestamp) | stats results = count(*) by timestamp`,
761761
};
762762
const lensVis = await getLensVisMock({
763763
filters,

src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.suggestions.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ describe('LensVisService suggestions', () => {
125125

126126
const histogramQuery = {
127127
esql: `from the-data-view | limit 100
128-
| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp | rename timestamp as \`@timestamp every 30 minute\``,
128+
| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp`,
129129
};
130130

131131
expect(lensVis.visContext?.attributes.state.query).toStrictEqual(histogramQuery);
@@ -163,7 +163,7 @@ describe('LensVisService suggestions', () => {
163163

164164
const histogramQuery = {
165165
esql: `from the-data-view | limit 100
166-
| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp | rename timestamp as \`@timestamp every 30 minute\``,
166+
| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp`,
167167
};
168168

169169
expect(lensVis.visContext?.attributes.state.query).toStrictEqual(histogramQuery);
@@ -248,7 +248,7 @@ describe('LensVisService suggestions', () => {
248248

249249
const histogramQuery = {
250250
esql: `from the-data-view | limit 100
251-
| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp, \`var0\` | sort \`var0\` asc | rename timestamp as \`@timestamp every 30 minute\``,
251+
| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp, \`var0\` | sort \`var0\` asc`,
252252
};
253253

254254
expect(lensVis.visContext?.attributes.state.query).toStrictEqual(histogramQuery);
@@ -329,7 +329,7 @@ describe('LensVisService suggestions', () => {
329329

330330
const histogramQuery = {
331331
esql: `from the-data-view | limit 100
332-
| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp, \`coordinates\` | rename timestamp as \`@timestamp every 30 minute\``,
332+
| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp, \`coordinates\``,
333333
};
334334

335335
expect(lensVis.visContext?.attributes.state.query).toStrictEqual(histogramQuery);

src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
deriveLensSuggestionFromLensAttributes,
5050
type QueryParams,
5151
injectESQLQueryIntoLensLayers,
52+
TIMESTAMP_COLUMN,
5253
} from '../utils/external_vis_context';
5354
import { computeInterval } from '../utils/compute_interval';
5455
import { enrichLensAttributesWithTablesData } from '../utils/lens_vis_from_table';
@@ -496,13 +497,14 @@ export class LensVisService {
496497
interval,
497498
breakdownColumn,
498499
});
500+
const dateFieldLabel = `${dataView.timeFieldName} every ${interval}`;
499501
const context = {
500502
dataViewSpec: dataView?.toSpec(),
501503
fieldName: '',
502504
textBasedColumns: [
503505
{
504-
id: `${dataView.timeFieldName} every ${interval}`,
505-
name: `${dataView.timeFieldName} every ${interval}`,
506+
id: TIMESTAMP_COLUMN,
507+
name: dateFieldLabel,
506508
meta: {
507509
type: 'date',
508510
},
@@ -526,9 +528,13 @@ export class LensVisService {
526528

527529
// here the attributes contain the main query and not the histogram one
528530
const updatedAttributesWithQuery = preferredVisAttributes
529-
? injectESQLQueryIntoLensLayers(preferredVisAttributes, {
530-
esql: esqlQuery,
531-
})
531+
? injectESQLQueryIntoLensLayers(
532+
preferredVisAttributes,
533+
{
534+
esql: esqlQuery,
535+
},
536+
dateFieldLabel
537+
)
532538
: undefined;
533539

534540
const suggestions =
@@ -598,7 +604,7 @@ export class LensVisService {
598604

599605
return appendToESQLQuery(
600606
safeQuery,
601-
`| EVAL timestamp=DATE_TRUNC(${queryInterval}, ${dataView.timeFieldName}) | stats results = count(*) by timestamp${breakdown}${sortBy} | rename timestamp as \`${dataView.timeFieldName} every ${queryInterval}\``
607+
`| EVAL ${TIMESTAMP_COLUMN}=DATE_TRUNC(${queryInterval}, ${dataView.timeFieldName}) | stats results = count(*) by ${TIMESTAMP_COLUMN}${breakdown}${sortBy}`
602608
);
603609
};
604610

src/platform/plugins/shared/unified_histogram/public/utils/external_vis_context.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,5 +221,72 @@ describe('external_vis_context', () => {
221221
injectESQLQueryIntoLensLayers(attributes, { esql: 'from foo | stats count(*)' })
222222
).toStrictEqual(expectedAttributes);
223223
});
224+
225+
it('should inject the interval to the Lens attributes for ES|QL config (textbased)', async () => {
226+
const attributes = {
227+
visualizationType: 'lnsXY',
228+
state: {
229+
visualization: { preferredSeriesType: 'line' },
230+
datasourceStates: {
231+
textBased: {
232+
layers: {
233+
layer1: {
234+
query: { esql: 'from foo' },
235+
columns: [
236+
{
237+
columnId: 'col1',
238+
fieldName: 'field1',
239+
},
240+
{
241+
columnId: 'timestamp',
242+
fieldName: 'timestamp',
243+
label: 'timestamp every 1h',
244+
customLabel: true,
245+
},
246+
],
247+
},
248+
},
249+
},
250+
},
251+
},
252+
} as unknown as UnifiedHistogramVisContext['attributes'];
253+
254+
const expectedAttributes = {
255+
...attributes,
256+
state: {
257+
...attributes.state,
258+
datasourceStates: {
259+
...attributes.state.datasourceStates,
260+
textBased: {
261+
...attributes.state.datasourceStates.textBased,
262+
layers: {
263+
layer1: {
264+
query: { esql: 'from foo' },
265+
columns: [
266+
{
267+
columnId: 'col1',
268+
fieldName: 'field1',
269+
},
270+
{
271+
columnId: 'timestamp',
272+
fieldName: 'timestamp',
273+
label: 'timestamp every 10 minutes',
274+
customLabel: true,
275+
},
276+
],
277+
},
278+
},
279+
},
280+
},
281+
},
282+
} as unknown as UnifiedHistogramVisContext['attributes'];
283+
expect(
284+
injectESQLQueryIntoLensLayers(
285+
attributes,
286+
{ esql: 'from foo' },
287+
'timestamp every 10 minutes'
288+
)
289+
).toStrictEqual(expectedAttributes);
290+
});
224291
});
225292
});

src/platform/plugins/shared/unified_histogram/public/utils/external_vis_context.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
import { isEqual, cloneDeep } from 'lodash';
1111
import type { DataView } from '@kbn/data-views-plugin/common';
1212
import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
13+
import type { TextBasedLayerColumn } from '@kbn/lens-plugin/public/datasources/text_based/types';
1314
import { getDatasourceId } from '@kbn/visualization-utils';
1415
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
1516
import type { PieVisualizationState, Suggestion, XYState } from '@kbn/lens-plugin/public';
1617
import { UnifiedHistogramSuggestionType, UnifiedHistogramVisContext } from '../types';
1718
import { removeTablesFromLensAttributes } from './lens_vis_from_table';
1819

20+
export const TIMESTAMP_COLUMN = 'timestamp';
21+
1922
export interface QueryParams {
2023
dataView: DataView;
2124
query?: Query | AggregateQuery;
@@ -104,9 +107,21 @@ export const isSuggestionShapeAndVisContextCompatible = (
104107
);
105108
};
106109

110+
const injectIntervalToDateTimeColumn = (
111+
columns: TextBasedLayerColumn[],
112+
dateFieldLabel: string
113+
) => {
114+
const dateColumn = columns.find((column) => column.columnId === TIMESTAMP_COLUMN);
115+
if (dateColumn && dateColumn.label !== dateFieldLabel && dateColumn.customLabel) {
116+
dateColumn.label = dateFieldLabel;
117+
}
118+
return columns;
119+
};
120+
107121
export const injectESQLQueryIntoLensLayers = (
108122
visAttributes: UnifiedHistogramVisContext['attributes'],
109-
query: AggregateQuery
123+
query: AggregateQuery,
124+
dateFieldLabel?: string
110125
) => {
111126
const datasourceId = getDatasourceId(visAttributes.state.datasourceStates);
112127

@@ -126,6 +141,12 @@ export const injectESQLQueryIntoLensLayers = (
126141
if (!isEqual(layer.query, query)) {
127142
layer.query = query;
128143
}
144+
if (dateFieldLabel && layer.columns) {
145+
const columns = injectIntervalToDateTimeColumn(layer.columns, dateFieldLabel);
146+
if (!isEqual(layer.columns, columns)) {
147+
layer.columns = columns;
148+
}
149+
}
129150
});
130151
}
131152
return {

x-pack/platform/plugins/shared/lens/public/datasources/text_based/text_based_languages.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,8 @@ describe('Textbased Data Source', () => {
383383
{
384384
columnId: 'bytes',
385385
fieldName: 'bytes',
386+
customLabel: false,
387+
label: 'bytes',
386388
inMetricDimension: true,
387389
meta: {
388390
type: 'number',
@@ -391,6 +393,8 @@ describe('Textbased Data Source', () => {
391393
{
392394
columnId: 'dest',
393395
fieldName: 'dest',
396+
customLabel: false,
397+
label: 'dest',
394398
meta: {
395399
type: 'string',
396400
},
@@ -471,13 +475,17 @@ describe('Textbased Data Source', () => {
471475
{
472476
id: '@timestamp',
473477
name: '@timestamp',
478+
customLabel: false,
479+
label: '@timestamp',
474480
meta: {
475481
type: 'date',
476482
},
477483
},
478484
{
479485
id: 'dest',
480486
name: 'dest',
487+
customLabel: false,
488+
label: 'dest',
481489
meta: {
482490
type: 'string',
483491
},
@@ -517,6 +525,8 @@ describe('Textbased Data Source', () => {
517525
{
518526
columnId: '@timestamp',
519527
fieldName: '@timestamp',
528+
customLabel: false,
529+
label: '@timestamp',
520530
inMetricDimension: true,
521531
meta: {
522532
type: 'date',
@@ -525,6 +535,8 @@ describe('Textbased Data Source', () => {
525535
{
526536
columnId: 'dest',
527537
fieldName: 'dest',
538+
customLabel: false,
539+
label: 'dest',
528540
inMetricDimension: true,
529541
meta: {
530542
type: 'string',

x-pack/platform/plugins/shared/lens/public/datasources/text_based/text_based_languages.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,10 @@ export function getTextBasedDatasource({
235235
);
236236
return {
237237
columnId: c.variable ?? c.id,
238-
fieldName: c.variable ? `?${c.variable}` : c.name,
238+
fieldName: c.variable ? `?${c.variable}` : c.id,
239239
variable: c.variable,
240+
label: c.name,
241+
customLabel: c.id !== c.name,
240242
meta: c.meta,
241243
// makes non-number fields to act as metrics, used for datatable suggestions
242244
...(inMetricDimension && {

x-pack/platform/plugins/shared/lens/public/lens_suggestions_api/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function mergeSuggestionWithVisContext({
5050
(layer) =>
5151
layer.columns?.some(
5252
(c: { fieldName: string }) =>
53-
!context?.textBasedColumns?.find((col) => col.name === c.fieldName)
53+
!context?.textBasedColumns?.find((col) => col.id === c.fieldName)
5454
) || layer.columns?.length !== context?.textBasedColumns?.length
5555
)
5656
) {

0 commit comments

Comments
 (0)