mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ES|QL] [Discover] Keeps the histogram config on time change (#208053)
## Summary Closes https://github.com/elastic/kibana/issues/198749  ### 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
This commit is contained in:
parent
dac6600268
commit
b25f23674b
9 changed files with 126 additions and 18 deletions
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
@ -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',
|
||||
},
|
||||
|
@ -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 =
|
||||
|
@ -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}`
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
|
|
@ -383,6 +383,8 @@ describe('Textbased Data Source', () => {
|
|||
{
|
||||
columnId: 'bytes',
|
||||
fieldName: 'bytes',
|
||||
customLabel: false,
|
||||
label: 'bytes',
|
||||
inMetricDimension: true,
|
||||
meta: {
|
||||
type: 'number',
|
||||
|
@ -391,6 +393,8 @@ describe('Textbased Data Source', () => {
|
|||
{
|
||||
columnId: 'dest',
|
||||
fieldName: 'dest',
|
||||
customLabel: false,
|
||||
label: 'dest',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -471,6 +475,8 @@ describe('Textbased Data Source', () => {
|
|||
{
|
||||
id: '@timestamp',
|
||||
name: '@timestamp',
|
||||
customLabel: false,
|
||||
label: '@timestamp',
|
||||
meta: {
|
||||
type: 'date',
|
||||
},
|
||||
|
@ -478,6 +484,8 @@ describe('Textbased Data Source', () => {
|
|||
{
|
||||
id: 'dest',
|
||||
name: 'dest',
|
||||
customLabel: false,
|
||||
label: 'dest',
|
||||
meta: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -517,6 +525,8 @@ describe('Textbased Data Source', () => {
|
|||
{
|
||||
columnId: '@timestamp',
|
||||
fieldName: '@timestamp',
|
||||
customLabel: false,
|
||||
label: '@timestamp',
|
||||
inMetricDimension: true,
|
||||
meta: {
|
||||
type: 'date',
|
||||
|
@ -525,6 +535,8 @@ describe('Textbased Data Source', () => {
|
|||
{
|
||||
columnId: 'dest',
|
||||
fieldName: 'dest',
|
||||
customLabel: false,
|
||||
label: 'dest',
|
||||
inMetricDimension: true,
|
||||
meta: {
|
||||
type: 'string',
|
||||
|
|
|
@ -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 && {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue