Skip to content

Commit

Permalink
[ES|QL] [Discover] Keeps the histogram config on time change (elastic…
Browse files Browse the repository at this point in the history
…#208053)

## Summary

Closes elastic#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
  • Loading branch information
stratoula authored Feb 3, 2025
1 parent dac6600 commit b25f236
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,12 @@ export const histogramESQLSuggestionMock = {
'662552df-2cdc-4539-bf3b-73b9f827252c': {
index: 'e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a',
query: {
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`',
esql: 'from kibana_sample_data_logs | limit 10 | EVAL timestamp=DATE_TRUNC(30 second, @timestamp) | stats results = count(*) by timestamp',
},
columns: [
{
columnId: '@timestamp every 30 second',
fieldName: '@timestamp every 30 second',
columnId: 'timestamp',
fieldName: 'timestamp',
meta: {
type: 'date',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ describe('LensVisService attributes', () => {
],
"query": Object {
"esql": "from logstash-* | limit 10
| EVAL timestamp=DATE_TRUNC(10 minute, timestamp) | stats results = count(*) by timestamp | rename timestamp as \`timestamp every 10 minute\`",
| EVAL timestamp=DATE_TRUNC(10 minute, timestamp) | stats results = count(*) by timestamp",
},
"visualization": Object {
"gridConfig": Object {
Expand Down Expand Up @@ -757,7 +757,7 @@ describe('LensVisService attributes', () => {
it('should use the correct histogram query when no suggestion passed', async () => {
const histogramQuery = {
esql: `from logstash-* | limit 10
| EVAL timestamp=DATE_TRUNC(10 minute, @timestamp) | stats results = count(*) by timestamp | rename timestamp as \`@timestamp every 10 minute\``,
| EVAL timestamp=DATE_TRUNC(10 minute, @timestamp) | stats results = count(*) by timestamp`,
};
const lensVis = await getLensVisMock({
filters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe('LensVisService suggestions', () => {

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

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

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

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

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

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

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

expect(lensVis.visContext?.attributes.state.query).toStrictEqual(histogramQuery);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
deriveLensSuggestionFromLensAttributes,
type QueryParams,
injectESQLQueryIntoLensLayers,
TIMESTAMP_COLUMN,
} from '../utils/external_vis_context';
import { computeInterval } from '../utils/compute_interval';
import { enrichLensAttributesWithTablesData } from '../utils/lens_vis_from_table';
Expand Down Expand Up @@ -496,13 +497,14 @@ export class LensVisService {
interval,
breakdownColumn,
});
const dateFieldLabel = `${dataView.timeFieldName} every ${interval}`;
const context = {
dataViewSpec: dataView?.toSpec(),
fieldName: '',
textBasedColumns: [
{
id: `${dataView.timeFieldName} every ${interval}`,
name: `${dataView.timeFieldName} every ${interval}`,
id: TIMESTAMP_COLUMN,
name: dateFieldLabel,
meta: {
type: 'date',
},
Expand All @@ -526,9 +528,13 @@ export class LensVisService {

// here the attributes contain the main query and not the histogram one
const updatedAttributesWithQuery = preferredVisAttributes
? injectESQLQueryIntoLensLayers(preferredVisAttributes, {
esql: esqlQuery,
})
? injectESQLQueryIntoLensLayers(
preferredVisAttributes,
{
esql: esqlQuery,
},
dateFieldLabel
)
: undefined;

const suggestions =
Expand Down Expand Up @@ -598,7 +604,7 @@ export class LensVisService {

return appendToESQLQuery(
safeQuery,
`| EVAL timestamp=DATE_TRUNC(${queryInterval}, ${dataView.timeFieldName}) | stats results = count(*) by timestamp${breakdown}${sortBy} | rename timestamp as \`${dataView.timeFieldName} every ${queryInterval}\``
`| EVAL ${TIMESTAMP_COLUMN}=DATE_TRUNC(${queryInterval}, ${dataView.timeFieldName}) | stats results = count(*) by ${TIMESTAMP_COLUMN}${breakdown}${sortBy}`
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,5 +221,72 @@ describe('external_vis_context', () => {
injectESQLQueryIntoLensLayers(attributes, { esql: 'from foo | stats count(*)' })
).toStrictEqual(expectedAttributes);
});

it('should inject the interval to the Lens attributes for ES|QL config (textbased)', async () => {
const attributes = {
visualizationType: 'lnsXY',
state: {
visualization: { preferredSeriesType: 'line' },
datasourceStates: {
textBased: {
layers: {
layer1: {
query: { esql: 'from foo' },
columns: [
{
columnId: 'col1',
fieldName: 'field1',
},
{
columnId: 'timestamp',
fieldName: 'timestamp',
label: 'timestamp every 1h',
customLabel: true,
},
],
},
},
},
},
},
} as unknown as UnifiedHistogramVisContext['attributes'];

const expectedAttributes = {
...attributes,
state: {
...attributes.state,
datasourceStates: {
...attributes.state.datasourceStates,
textBased: {
...attributes.state.datasourceStates.textBased,
layers: {
layer1: {
query: { esql: 'from foo' },
columns: [
{
columnId: 'col1',
fieldName: 'field1',
},
{
columnId: 'timestamp',
fieldName: 'timestamp',
label: 'timestamp every 10 minutes',
customLabel: true,
},
],
},
},
},
},
},
} as unknown as UnifiedHistogramVisContext['attributes'];
expect(
injectESQLQueryIntoLensLayers(
attributes,
{ esql: 'from foo' },
'timestamp every 10 minutes'
)
).toStrictEqual(expectedAttributes);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
import { isEqual, cloneDeep } from 'lodash';
import type { DataView } from '@kbn/data-views-plugin/common';
import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import type { TextBasedLayerColumn } from '@kbn/lens-plugin/public/datasources/text_based/types';
import { getDatasourceId } from '@kbn/visualization-utils';
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
import type { PieVisualizationState, Suggestion, XYState } from '@kbn/lens-plugin/public';
import { UnifiedHistogramSuggestionType, UnifiedHistogramVisContext } from '../types';
import { removeTablesFromLensAttributes } from './lens_vis_from_table';

export const TIMESTAMP_COLUMN = 'timestamp';

export interface QueryParams {
dataView: DataView;
query?: Query | AggregateQuery;
Expand Down Expand Up @@ -104,9 +107,21 @@ export const isSuggestionShapeAndVisContextCompatible = (
);
};

const injectIntervalToDateTimeColumn = (
columns: TextBasedLayerColumn[],
dateFieldLabel: string
) => {
const dateColumn = columns.find((column) => column.columnId === TIMESTAMP_COLUMN);
if (dateColumn && dateColumn.label !== dateFieldLabel && dateColumn.customLabel) {
dateColumn.label = dateFieldLabel;
}
return columns;
};

export const injectESQLQueryIntoLensLayers = (
visAttributes: UnifiedHistogramVisContext['attributes'],
query: AggregateQuery
query: AggregateQuery,
dateFieldLabel?: string
) => {
const datasourceId = getDatasourceId(visAttributes.state.datasourceStates);

Expand All @@ -126,6 +141,12 @@ export const injectESQLQueryIntoLensLayers = (
if (!isEqual(layer.query, query)) {
layer.query = query;
}
if (dateFieldLabel && layer.columns) {
const columns = injectIntervalToDateTimeColumn(layer.columns, dateFieldLabel);
if (!isEqual(layer.columns, columns)) {
layer.columns = columns;
}
}
});
}
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ describe('Textbased Data Source', () => {
{
columnId: 'bytes',
fieldName: 'bytes',
customLabel: false,
label: 'bytes',
inMetricDimension: true,
meta: {
type: 'number',
Expand All @@ -391,6 +393,8 @@ describe('Textbased Data Source', () => {
{
columnId: 'dest',
fieldName: 'dest',
customLabel: false,
label: 'dest',
meta: {
type: 'string',
},
Expand Down Expand Up @@ -471,13 +475,17 @@ describe('Textbased Data Source', () => {
{
id: '@timestamp',
name: '@timestamp',
customLabel: false,
label: '@timestamp',
meta: {
type: 'date',
},
},
{
id: 'dest',
name: 'dest',
customLabel: false,
label: 'dest',
meta: {
type: 'string',
},
Expand Down Expand Up @@ -517,6 +525,8 @@ describe('Textbased Data Source', () => {
{
columnId: '@timestamp',
fieldName: '@timestamp',
customLabel: false,
label: '@timestamp',
inMetricDimension: true,
meta: {
type: 'date',
Expand All @@ -525,6 +535,8 @@ describe('Textbased Data Source', () => {
{
columnId: 'dest',
fieldName: 'dest',
customLabel: false,
label: 'dest',
inMetricDimension: true,
meta: {
type: 'string',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,10 @@ export function getTextBasedDatasource({
);
return {
columnId: c.variable ?? c.id,
fieldName: c.variable ? `?${c.variable}` : c.name,
fieldName: c.variable ? `?${c.variable}` : c.id,
variable: c.variable,
label: c.name,
customLabel: c.id !== c.name,
meta: c.meta,
// makes non-number fields to act as metrics, used for datatable suggestions
...(inMetricDimension && {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function mergeSuggestionWithVisContext({
(layer) =>
layer.columns?.some(
(c: { fieldName: string }) =>
!context?.textBasedColumns?.find((col) => col.name === c.fieldName)
!context?.textBasedColumns?.find((col) => col.id === c.fieldName)
) || layer.columns?.length !== context?.textBasedColumns?.length
)
) {
Expand Down

0 comments on commit b25f236

Please sign in to comment.