From fb6e27ef882c7006e60f2b9032702b31adf4317a Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 14 Sep 2021 17:04:02 +0300 Subject: [PATCH] [TSVB] Doesn't work correctly with pipeline aggregations in "entire time range" mode (#105073) (#112076) * Use date_histogram instead of auto_date_histogram in pipeline aggregations * Fix ci * Fix eslint * start disable parent pipeline aggs and show error * Fix CI * Fix eslint * Fix CI * Add functional tests * Some fixes * Fix lint * Use agg_utils * Fix lint * Fix text * Fix lint * Fix tests * Fixed condition * Fix math aggregation * math should pass panel type as prop Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../vis_type_timeseries/common/errors.ts | 58 ++++++++++++++++ .../common/fields_utils.ts | 21 +----- .../common/validate_interval.ts | 21 +----- .../application/components/aggs/agg.tsx | 21 ++++-- .../components/aggs/agg_select.tsx | 28 +++++++- .../{unsupported_agg.tsx => invalid_agg.tsx} | 21 +++--- .../application/components/aggs/math.js | 1 + .../aggs/temporary_unsupported_agg.tsx | 46 ------------- .../default_search_capabilities.test.ts | 2 + .../default_search_capabilities.ts | 33 +++++++++ .../rollup_search_capabilities.test.ts | 3 +- .../search_strategies_registry.test.ts | 4 +- .../default_search_strategy.test.ts | 9 ++- .../strategies/default_search_strategy.ts | 1 + .../strategies/rollup_search_strategy.ts | 1 + .../server/lib/vis_data/get_series_data.ts | 12 +++- .../server/lib/vis_data/get_table_data.ts | 8 +++ .../server/lib/vis_data/helpers/check_aggs.ts | 27 ++++++++ .../server/lib/vis_data/helpers/index.ts | 1 + .../series/date_histogram.js | 47 +++++++++---- .../series/date_histogram.test.js | 2 +- .../table/date_histogram.ts | 42 ++++++++--- test/functional/apps/visualize/_tsvb_chart.ts | 69 +++++++++++++++++++ .../page_objects/visual_builder_page.ts | 10 +++ .../translations/translations/ja-JP.json | 4 -- .../translations/translations/zh-CN.json | 4 -- 26 files changed, 352 insertions(+), 144 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/common/errors.ts rename src/plugins/vis_type_timeseries/public/application/components/aggs/{unsupported_agg.tsx => invalid_agg.tsx} (62%) delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/aggs/temporary_unsupported_agg.tsx create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/check_aggs.ts diff --git a/src/plugins/vis_type_timeseries/common/errors.ts b/src/plugins/vis_type_timeseries/common/errors.ts new file mode 100644 index 00000000000000..6a23a003d29eed --- /dev/null +++ b/src/plugins/vis_type_timeseries/common/errors.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* eslint-disable max-classes-per-file */ + +import { i18n } from '@kbn/i18n'; + +export class UIError extends Error { + constructor(message: string) { + super(message); + } + + public get name() { + return this.constructor.name; + } + + public get errBody() { + return this.message; + } +} + +export class FieldNotFoundError extends UIError { + constructor(name: string) { + super( + i18n.translate('visTypeTimeseries.errors.fieldNotFound', { + defaultMessage: `Field "{field}" not found`, + values: { field: name }, + }) + ); + } +} + +export class ValidateIntervalError extends UIError { + constructor() { + super( + i18n.translate('visTypeTimeseries.errors.maxBucketsExceededErrorMessage', { + defaultMessage: + 'Your query attempted to fetch too much data. Reducing the time range or changing the interval used usually fixes the issue.', + }) + ); + } +} + +export class AggNotSupportedInMode extends UIError { + constructor(metricType: string, timeRangeMode: string) { + super( + i18n.translate('visTypeTimeseries.wrongAggregationErrorMessage', { + defaultMessage: 'The aggregation {metricType} is not supported in {timeRangeMode} mode', + values: { metricType, timeRangeMode }, + }) + ); + } +} diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.ts b/src/plugins/vis_type_timeseries/common/fields_utils.ts index b64fcc383a1bb9..1af0340dfa5253 100644 --- a/src/plugins/vis_type_timeseries/common/fields_utils.ts +++ b/src/plugins/vis_type_timeseries/common/fields_utils.ts @@ -6,29 +6,10 @@ * Side Public License, v 1. */ -import { i18n } from '@kbn/i18n'; import { FieldSpec } from '../../data/common'; import { isNestedField } from '../../data/common'; import { FetchedIndexPattern, SanitizedFieldType } from './types'; - -export class FieldNotFoundError extends Error { - constructor(name: string) { - super( - i18n.translate('visTypeTimeseries.fields.fieldNotFound', { - defaultMessage: `Field "{field}" not found`, - values: { field: name }, - }) - ); - } - - public get name() { - return this.constructor.name; - } - - public get errBody() { - return this.message; - } -} +import { FieldNotFoundError } from './errors'; export const extractFieldLabel = ( fields: SanitizedFieldType[], diff --git a/src/plugins/vis_type_timeseries/common/validate_interval.ts b/src/plugins/vis_type_timeseries/common/validate_interval.ts index 7f9ccf20c0eb1d..7c7a4e7badfc08 100644 --- a/src/plugins/vis_type_timeseries/common/validate_interval.ts +++ b/src/plugins/vis_type_timeseries/common/validate_interval.ts @@ -6,28 +6,9 @@ * Side Public License, v 1. */ -import { i18n } from '@kbn/i18n'; import { GTE_INTERVAL_RE } from './interval_regexp'; import { parseInterval, TimeRangeBounds } from '../../data/common'; - -export class ValidateIntervalError extends Error { - constructor() { - super( - i18n.translate('visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage', { - defaultMessage: - 'Your query attempted to fetch too much data. Reducing the time range or changing the interval used usually fixes the issue.', - }) - ); - } - - public get name() { - return this.constructor.name; - } - - public get errBody() { - return this.message; - } -} +import { ValidateIntervalError } from './errors'; export function validateInterval(bounds: TimeRangeBounds, interval: string, maxBuckets: number) { const { min, max } = bounds; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.tsx index 3c68cb02dd07eb..08f8c072eef3b1 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg.tsx @@ -7,16 +7,17 @@ */ import React, { useMemo, useEffect, HTMLAttributes } from 'react'; +import { EuiCode } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; // @ts-ignore import { aggToComponent } from '../lib/agg_to_component'; // @ts-ignore import { isMetricEnabled } from '../../lib/check_ui_restrictions'; +import { getInvalidAggComponent } from './invalid_agg'; // @ts-expect-error not typed yet import { seriesChangeHandler } from '../lib/series_change_handler'; import { checkIfNumericMetric } from '../lib/check_if_numeric_metric'; import { getFormatterType } from '../lib/get_formatter_type'; -import { UnsupportedAgg } from './unsupported_agg'; -import { TemporaryUnsupportedAgg } from './temporary_unsupported_agg'; import { DATA_FORMATTERS } from '../../../../common/enums'; import type { Metric, Panel, Series, SanitizedFieldType } from '../../../../common/types'; import type { DragHandleProps } from '../../../types'; @@ -43,9 +44,21 @@ export function Agg(props: AggProps) { let Component = aggToComponent[model.type]; if (!Component) { - Component = UnsupportedAgg; + Component = getInvalidAggComponent( + {props.model.type} }} + /> + ); } else if (!isMetricEnabled(model.type, uiRestrictions)) { - Component = TemporaryUnsupportedAgg; + Component = getInvalidAggComponent( + {props.model.type} }} + /> + ); } const style = { diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx index 719ebbbe5a91d9..2959712bb9f007 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useContext } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; // @ts-ignore @@ -16,6 +16,8 @@ import { getAggsByType, getAggsByPredicate } from '../../../../common/agg_utils' import type { Agg } from '../../../../common/agg_utils'; import type { Metric } from '../../../../common/types'; import { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions'; +import { PanelModelContext } from '../../contexts/panel_model_context'; +import { PANEL_TYPES, TIME_RANGE_DATA_MODES } from '../../../../common/enums'; type AggSelectOption = EuiComboBoxOptionOption; @@ -35,16 +37,35 @@ function filterByPanelType(panelType: string) { panelType === 'table' ? agg.value !== TSVB_METRIC_TYPES.SERIES_AGG : true; } +export function isMetricAvailableForPanel( + aggId: string, + panelType: string, + timeRangeMode?: string +) { + if ( + panelType !== PANEL_TYPES.TIMESERIES && + timeRangeMode === TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE + ) { + return ( + !pipelineAggs.some((agg) => agg.value === aggId) && aggId !== TSVB_METRIC_TYPES.SERIES_AGG + ); + } + + return true; +} + interface AggSelectUiProps { id: string; panelType: string; siblings: Metric[]; value: string; uiRestrictions?: TimeseriesUIRestrictions; + timeRangeMode?: string; onChange: (currentlySelectedOptions: AggSelectOption[]) => void; } export function AggSelect(props: AggSelectUiProps) { + const panelModel = useContext(PanelModelContext); const { siblings, panelType, value, onChange, uiRestrictions, ...rest } = props; const selectedOptions = allAggOptions.filter((option) => { @@ -69,7 +90,10 @@ export function AggSelect(props: AggSelectUiProps) { } else { const disableSiblingAggs = (agg: AggSelectOption) => ({ ...agg, - disabled: !enablePipelines || !isMetricEnabled(agg.value, uiRestrictions), + disabled: + !enablePipelines || + !isMetricEnabled(agg.value, uiRestrictions) || + !isMetricAvailableForPanel(agg.value as string, panelType, panelModel?.time_range_mode), }); options = [ diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/unsupported_agg.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/invalid_agg.tsx similarity index 62% rename from src/plugins/vis_type_timeseries/public/application/components/aggs/unsupported_agg.tsx rename to src/plugins/vis_type_timeseries/public/application/components/aggs/invalid_agg.tsx index 70c5499597e66d..7fb4b31c2347e7 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/unsupported_agg.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/invalid_agg.tsx @@ -7,13 +7,12 @@ */ import React from 'react'; -import { EuiCode, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiTitle } from '@elastic/eui'; import { AggRow } from './agg_row'; import type { Metric } from '../../../../common/types'; import { DragHandleProps } from '../../../types'; -interface UnsupportedAggProps { +interface InvalidAggProps { disableDelete: boolean; model: Metric; siblings: Metric[]; @@ -22,7 +21,9 @@ interface UnsupportedAggProps { onDelete: () => void; } -export function UnsupportedAgg(props: UnsupportedAggProps) { +export const getInvalidAggComponent = (message: JSX.Element | string) => ( + props: InvalidAggProps +) => { return ( - - - {props.model.type} }} - /> - + + {message} ); -} +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js index e92659e677860a..f00a485f2d759f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js @@ -69,6 +69,7 @@ export function MathAgg(props) { id={htmlId('aggregation')} siblings={props.siblings} value={model.type} + panelType={props.panel.type} onChange={handleSelectChange('type')} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/temporary_unsupported_agg.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/temporary_unsupported_agg.tsx deleted file mode 100644 index b85da5955ac658..00000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/temporary_unsupported_agg.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiCode, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { AggRow } from './agg_row'; -import type { Metric } from '../../../../common/types'; -import { DragHandleProps } from '../../../types'; - -interface TemporaryUnsupportedAggProps { - disableDelete: boolean; - model: Metric; - siblings: Metric[]; - dragHandleProps: DragHandleProps; - onAdd: () => void; - onDelete: () => void; -} - -export function TemporaryUnsupportedAgg(props: TemporaryUnsupportedAggProps) { - return ( - - - - {props.model.type} }} - /> - - - - ); -} diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts index 8029e8684c4418..351691d4c42a35 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts @@ -7,12 +7,14 @@ */ import { DefaultSearchCapabilities } from './default_search_capabilities'; +import type { Panel } from '../../../../common/types'; describe('DefaultSearchCapabilities', () => { let defaultSearchCapabilities: DefaultSearchCapabilities; beforeEach(() => { defaultSearchCapabilities = new DefaultSearchCapabilities({ + panel: {} as Panel, timezone: 'UTC', maxBucketsLimit: 2000, }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts index b60d2e61e9a434..0240ac93b60e83 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts @@ -13,19 +13,30 @@ import { getSuitableUnit, } from '../../vis_data/helpers/unit_to_seconds'; import { RESTRICTIONS_KEYS } from '../../../../common/ui_restrictions'; +import { + TIME_RANGE_DATA_MODES, + PANEL_TYPES, + BUCKET_TYPES, + TSVB_METRIC_TYPES, +} from '../../../../common/enums'; +import { getAggsByType, AGG_TYPE } from '../../../../common/agg_utils'; +import type { Panel } from '../../../../common/types'; export interface SearchCapabilitiesOptions { timezone?: string; maxBucketsLimit: number; + panel?: Panel; } export class DefaultSearchCapabilities { public timezone: SearchCapabilitiesOptions['timezone']; public maxBucketsLimit: SearchCapabilitiesOptions['maxBucketsLimit']; + public panel?: Panel; constructor(options: SearchCapabilitiesOptions) { this.timezone = options.timezone; this.maxBucketsLimit = options.maxBucketsLimit; + this.panel = options.panel; } public get defaultTimeInterval() { @@ -33,6 +44,28 @@ export class DefaultSearchCapabilities { } public get whiteListedMetrics() { + if ( + this.panel && + this.panel.type !== PANEL_TYPES.TIMESERIES && + this.panel.time_range_mode === TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE + ) { + const aggs = getAggsByType((agg) => agg.id); + const allAvailableAggs = [ + ...aggs[AGG_TYPE.METRIC], + ...aggs[AGG_TYPE.SIBLING_PIPELINE], + TSVB_METRIC_TYPES.MATH, + BUCKET_TYPES.TERMS, + ].reduce( + (availableAggs, aggType) => ({ + ...availableAggs, + [aggType]: { + '*': true, + }, + }), + {} + ); + return this.createUiRestriction(allAvailableAggs); + } return this.createUiRestriction(); } diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts index 7426c74dc24261..e1cc1f1f26eb22 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts @@ -7,6 +7,7 @@ */ import { Unit } from '@elastic/datemath'; +import type { Panel } from '../../../../common/types'; import { RollupSearchCapabilities } from './rollup_search_capabilities'; describe('Rollup Search Capabilities', () => { @@ -32,7 +33,7 @@ describe('Rollup Search Capabilities', () => { }; rollupSearchCaps = new RollupSearchCapabilities( - { maxBucketsLimit: 2000, timezone: 'UTC' }, + { maxBucketsLimit: 2000, timezone: 'UTC', panel: {} as Panel }, fieldsCapabilities, rollupIndex ); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts index f6114a4117bb84..7f5f12602998f9 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts @@ -52,7 +52,7 @@ describe('SearchStrategyRegister', () => { }); test('should return a DefaultSearchStrategy instance', async () => { - const req = { body: {} } as VisTypeTimeseriesRequest; + const req = { body: { panels: [] } } as VisTypeTimeseriesRequest; const { searchStrategy, capabilities } = (await registry.getViableStrategy( requestContext, @@ -73,7 +73,7 @@ describe('SearchStrategyRegister', () => { }); test('should return a MockSearchStrategy instance', async () => { - const req = { body: {} } as VisTypeTimeseriesRequest; + const req = { body: { panels: [] } } as VisTypeTimeseriesRequest; const anotherSearchStrategy = new MockSearchStrategy(); registry.addStrategy(anotherSearchStrategy); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts index 9fa79c7b80f8cc..3638a438ec736b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts @@ -27,9 +27,11 @@ describe('DefaultSearchStrategy', () => { let req: VisTypeTimeseriesVisDataRequest; beforeEach(() => { - req = { - body: {}, - } as VisTypeTimeseriesVisDataRequest; + req = ({ + body: { + panels: [], + }, + } as unknown) as VisTypeTimeseriesVisDataRequest; defaultSearchStrategy = new DefaultSearchStrategy(); }); @@ -46,6 +48,7 @@ describe('DefaultSearchStrategy', () => { expect(value.capabilities).toMatchInlineSnapshot(` DefaultSearchCapabilities { "maxBucketsLimit": undefined, + "panel": undefined, "timezone": undefined, } `); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts index 17451f7e5777e9..34892ec797c0be 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts @@ -27,6 +27,7 @@ export class DefaultSearchStrategy extends AbstractSearchStrategy { return { isViable: true, capabilities: new DefaultSearchCapabilities({ + panel: req.body.panels ? req.body.panels[0] : null, timezone: req.body.timerange?.timezone, maxBucketsLimit: await uiSettings.get(MAX_BUCKETS_SETTING), }), diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts index 0ac00863d0a73b..7a1d1574aa7bb1 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts @@ -74,6 +74,7 @@ export class RollupSearchStrategy extends AbstractSearchStrategy { capabilities = new RollupSearchCapabilities( { maxBucketsLimit: await uiSettings.get(MAX_BUCKETS_SETTING), + panel: req.body.panels ? req.body.panels[0] : null, }, fieldsCapabilities, rollupIndex diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts index 12fe95ccc50cae..a9a3825f5a9dfe 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts @@ -13,6 +13,8 @@ import { getAnnotations } from './get_annotations'; import { handleResponseBody } from './series/handle_response_body'; import { getSeriesRequestParams } from './series/get_request_params'; import { getActiveSeries } from './helpers/get_active_series'; +import { isAggSupported } from './helpers/check_aggs'; +import { isEntireTimeRangeMode } from './helpers/get_timerange_mode'; import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesVisDataRequest, @@ -55,9 +57,13 @@ export async function getSeriesData( const handleError = handleErrorResponse(panel); try { - const bodiesPromises = getActiveSeries(panel).map((series) => - getSeriesRequestParams(req, panel, panelIndex, series, capabilities, services) - ); + const bodiesPromises = getActiveSeries(panel).map((series) => { + if (isEntireTimeRangeMode(panel, series)) { + isAggSupported(series.metrics); + } + + return getSeriesRequestParams(req, panel, panelIndex, series, capabilities, services); + }); const fieldFetchServices = { indexPatternsService, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts index 7e1332f8018564..3b53147dc6f93b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts @@ -15,6 +15,8 @@ import { processBucket } from './table/process_bucket'; import { createFieldsFetcher } from '../search_strategies/lib/fields_fetcher'; import { extractFieldLabel } from '../../../common/fields_utils'; +import { isAggSupported } from './helpers/check_aggs'; +import { isEntireTimeRangeMode } from './helpers/get_timerange_mode'; import type { VisTypeTimeseriesRequestHandlerContext, @@ -71,6 +73,12 @@ export async function getTableData( const handleError = handleErrorResponse(panel); try { + if (isEntireTimeRangeMode(panel)) { + panel.series.forEach((column) => { + isAggSupported(column.metrics); + }); + } + const body = await buildTableRequest({ req, panel, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/check_aggs.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/check_aggs.ts new file mode 100644 index 00000000000000..bc420045dd434c --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/check_aggs.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AggNotSupportedInMode } from '../../../../common/errors'; +import { getAggsByType, AGG_TYPE } from '../../../../common/agg_utils'; +import { TSVB_METRIC_TYPES, TIME_RANGE_DATA_MODES } from '../../../../common/enums'; +import { Metric } from '../../../../common/types'; + +export function isAggSupported(metrics: Metric[]) { + const parentPipelineAggs = getAggsByType((agg) => agg.id)[AGG_TYPE.PARENT_PIPELINE]; + const metricTypes = metrics.filter( + (metric) => + parentPipelineAggs.includes(metric.type) || metric.type === TSVB_METRIC_TYPES.SERIES_AGG + ); + + if (metricTypes.length) { + throw new AggNotSupportedInMode( + metricTypes.map((metric) => metric.type).join(', '), + TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE + ); + } +} diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.ts index 41d302422c0b73..7dfecc9811dd97 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.ts @@ -15,6 +15,7 @@ export { getBucketsPath } from './get_buckets_path'; export { isEntireTimeRangeMode, isLastValueTimerangeMode } from './get_timerange_mode'; export { getLastMetric } from './get_last_metric'; export { getSplits } from './get_splits'; +export { isAggSupported } from './check_aggs'; // @ts-expect-error no typed yet export { bucketTransform } from './bucket_transform'; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js index cec3e82d5e37c8..6349a75993aa81 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js @@ -11,6 +11,8 @@ import { getBucketSize } from '../../helpers/get_bucket_size'; import { offsetTime } from '../../offset_time'; import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; +import { AGG_TYPE, getAggsByType } from '../../../../../common/agg_utils'; +import { TSVB_METRIC_TYPES } from '../../../../../common/enums'; const { dateHistogramInterval } = search.aggs; @@ -30,19 +32,17 @@ export function dateHistogram( const { timeField, interval, maxBars } = await buildSeriesMetaParams(); const { from, to } = offsetTime(req, series.offset_time); + const { timezone } = capabilities; + const { intervalString } = getBucketSize( + req, + interval, + capabilities, + maxBars ? Math.min(maxBarsUiSettings, maxBars) : barTargetUiSettings + ); let bucketInterval; const overwriteDateHistogramForLastBucketMode = () => { - const { timezone } = capabilities; - - const { intervalString } = getBucketSize( - req, - interval, - capabilities, - maxBars ? Math.min(maxBarsUiSettings, maxBars) : barTargetUiSettings - ); - overwrite(doc, `aggs.${series.id}.aggs.timeseries.date_histogram`, { field: timeField, min_doc_count: 0, @@ -58,12 +58,35 @@ export function dateHistogram( }; const overwriteDateHistogramForEntireTimerangeMode = () => { - overwrite(doc, `aggs.${series.id}.aggs.timeseries.auto_date_histogram`, { + const metricAggs = getAggsByType((agg) => agg.id)[AGG_TYPE.METRIC]; + + // we should use auto_date_histogram only for metric aggregations and math + if ( + series.metrics.every( + (metric) => metricAggs.includes(metric.type) || metric.type === TSVB_METRIC_TYPES.MATH + ) + ) { + overwrite(doc, `aggs.${series.id}.aggs.timeseries.auto_date_histogram`, { + field: timeField, + buckets: 1, + }); + + bucketInterval = `${to.valueOf() - from.valueOf()}ms`; + return; + } + + overwrite(doc, `aggs.${series.id}.aggs.timeseries.date_histogram`, { field: timeField, - buckets: 1, + min_doc_count: 0, + time_zone: timezone, + extended_bounds: { + min: from.valueOf(), + max: to.valueOf(), + }, + ...dateHistogramInterval(intervalString), }); - bucketInterval = `${to.valueOf() - from.valueOf()}ms`; + bucketInterval = intervalString; }; isLastValueTimerangeMode(panel, series) diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index 022718ece435d9..b09b2c28d77e3d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -36,7 +36,7 @@ describe('dateHistogram(req, panel, series)', () => { interval: '10s', id: 'panelId', }; - series = { id: 'test' }; + series = { id: 'test', metrics: [{ type: 'avg' }] }; config = { allowLeadingWildcards: true, queryStringOptions: {}, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.ts index ac19a266430f33..27470d5868a5c6 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.ts @@ -9,6 +9,8 @@ import { overwrite, getBucketSize, isLastValueTimerangeMode, getTimerange } from '../../helpers'; import { calculateAggRoot } from './calculate_agg_root'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; +import { AGG_TYPE, getAggsByType } from '../../../../../common/agg_utils'; +import { TSVB_METRIC_TYPES } from '../../../../../common/enums'; import type { TableRequestProcessorsFunction, TableSearchRequestMeta } from './types'; @@ -32,10 +34,10 @@ export const dateHistogram: TableRequestProcessorsFunction = ({ panelId: panel.id, }; - const overwriteDateHistogramForLastBucketMode = () => { - const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); - const { timezone } = capabilities; + const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); + const { timezone } = capabilities; + const overwriteDateHistogramForLastBucketMode = () => { panel.series.forEach((column) => { const aggRoot = calculateAggRoot(doc, column); @@ -58,19 +60,41 @@ export const dateHistogram: TableRequestProcessorsFunction = ({ }; const overwriteDateHistogramForEntireTimerangeMode = () => { - const intervalString = `${to.valueOf() - from.valueOf()}ms`; + const metricAggs = getAggsByType((agg) => agg.id)[AGG_TYPE.METRIC]; + let bucketInterval; panel.series.forEach((column) => { const aggRoot = calculateAggRoot(doc, column); - overwrite(doc, `${aggRoot}.timeseries.auto_date_histogram`, { - field: timeField, - buckets: 1, - }); + // we should use auto_date_histogram only for metric aggregations and math + if ( + column.metrics.every( + (metric) => metricAggs.includes(metric.type) || metric.type === TSVB_METRIC_TYPES.MATH + ) + ) { + overwrite(doc, `${aggRoot}.timeseries.auto_date_histogram`, { + field: timeField, + buckets: 1, + }); + + bucketInterval = `${to.valueOf() - from.valueOf()}ms`; + } else { + overwrite(doc, `${aggRoot}.timeseries.date_histogram`, { + field: timeField, + min_doc_count: 0, + time_zone: timezone, + extended_bounds: { + min: from.valueOf(), + max: to.valueOf(), + }, + ...dateHistogramInterval(intervalString), + }); + bucketInterval = intervalString; + } overwrite(doc, aggRoot.replace(/\.aggs$/, '.meta'), { ...meta, - intervalString, + intervalString: bucketInterval, }); }); }; diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index c7f228e9aa05c3..6a5c062268c25d 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -103,6 +103,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(kibanaIndexPatternModeValue).to.eql('32,212,254,720'); }); + it('should show error if we use parent pipeline aggregations in entire time range mode', async () => { + await visualBuilder.selectAggType('Max'); + await visualBuilder.setFieldForAggregation('machine.ram'); + await visualBuilder.createNewAgg(); + await visualBuilder.selectAggType('derivative', 1); + await visualBuilder.setFieldForAggregation('Max of machine.ram', 1); + + const value = await visualBuilder.getMetricValue(); + + expect(value).to.eql('0'); + + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.setMetricsDataTimerangeMode('Entire time range'); + await visualBuilder.clickDataTab('metric'); + await visualBuilder.checkInvalidAggComponentIsPresent(); + const error = await visualBuilder.getVisualizeError(); + + expect(error).to.eql( + 'The aggregation derivative is not supported in entire_time_range mode' + ); + }); + describe('Color rules', () => { beforeEach(async () => { await visualBuilder.selectAggType('Min'); @@ -164,6 +186,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await visualBuilder.clickDataTab('gauge'); }); + it('should show error if we use parent pipeline aggregations in entire time range mode', async () => { + await visualBuilder.clickPanelOptions('gauge'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.clickDataTab('gauge'); + await visualBuilder.selectAggType('Max'); + await visualBuilder.setFieldForAggregation('machine.ram'); + await visualBuilder.createNewAgg(); + await visualBuilder.selectAggType('derivative', 1); + await visualBuilder.setFieldForAggregation('Max of machine.ram', 1); + + const value = await visualBuilder.getGaugeCount(); + + expect(value).to.eql('0'); + + await visualBuilder.clickPanelOptions('gauge'); + await visualBuilder.setMetricsDataTimerangeMode('Entire time range'); + await visualBuilder.clickDataTab('gauge'); + await visualBuilder.checkInvalidAggComponentIsPresent(); + const error = await visualBuilder.getVisualizeError(); + + expect(error).to.eql( + 'The aggregation derivative is not supported in entire_time_range mode' + ); + }); + it('should verify gauge label and count display', async () => { await visChart.waitForVisualizationRenderingStabilized(); const gaugeLabel = await visualBuilder.getGaugeLabel(); @@ -296,6 +343,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(secondTopNBarStyle).to.contain('background-color: rgb(128, 224, 138);'); }); + it('should show error if we use parent pipeline aggregations in entire time range mode', async () => { + await visualBuilder.selectAggType('Max'); + await visualBuilder.setFieldForAggregation('machine.ram'); + await visualBuilder.createNewAgg(); + await visualBuilder.selectAggType('derivative', 1); + await visualBuilder.setFieldForAggregation('Max of machine.ram', 1); + + const value = await visualBuilder.getTopNCount(); + + expect(value).to.eql('0'); + + await visualBuilder.clickPanelOptions('topN'); + await visualBuilder.setMetricsDataTimerangeMode('Entire time range'); + await visualBuilder.clickDataTab('topN'); + await visualBuilder.checkInvalidAggComponentIsPresent(); + const error = await visualBuilder.getVisualizeError(); + + expect(error).to.eql( + 'The aggregation derivative is not supported in entire_time_range mode' + ); + }); + describe('Color rules', () => { it('should apply color rules to visualization background and bar', async () => { await visualBuilder.selectAggType('Value Count'); diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 81b2e2763eb1d0..c324de1231b7d9 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -873,4 +873,14 @@ export class VisualBuilderPageObject extends FtrService { const areas = (await this.getChartItems(chartData)) as DebugState['areas']; return areas?.[nth]?.lines.y1.points.map(({ x, y }) => [x, y]); } + + public async getVisualizeError() { + const visError = await this.testSubjects.find(`visualization-error`); + const errorSpans = await visError.findAllByClassName('euiText--extraSmall'); + return await errorSpans[0].getVisibleText(); + } + + public async checkInvalidAggComponentIsPresent() { + await this.testSubjects.existOrFail(`invalid_agg`); + } } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 217a6a8bb8d575..9c815f04c2a069 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5279,7 +5279,6 @@ "visTypeTimeseries.emptyTextValue": "(空)", "visTypeTimeseries.error.requestForPanelFailedErrorMessage": "このパネルのリクエストに失敗しました", "visTypeTimeseries.fetchFields.loadIndexPatternFieldsErrorMessage": "index_pattern フィールドを読み込めません", - "visTypeTimeseries.fields.fieldNotFound": "フィールド\"{field}\"が見つかりません", "visTypeTimeseries.fieldSelect.fieldIsNotValid": "\"{fieldParameter}\"フィールドは無効であり、現在のインデックスで使用できません。新しいフィールドを選択してください。", "visTypeTimeseries.fieldSelect.selectFieldPlaceholder": "フィールドを選択してください...", "visTypeTimeseries.filterRatio.aggregationLabel": "アグリゲーション", @@ -5672,10 +5671,7 @@ "visTypeTimeseries.units.perMillisecond": "ミリ秒単位", "visTypeTimeseries.units.perMinute": "分単位", "visTypeTimeseries.units.perSecond": "秒単位", - "visTypeTimeseries.unsupportedAgg.aggIsNotSupportedDescription": "{modelType} 集約はサポートされなくなりました。", - "visTypeTimeseries.unsupportedAgg.aggIsTemporaryUnsupportedDescription": "{modelType} 集約は現在サポートされていません。", "visTypeTimeseries.unsupportedSplit.splitIsUnsupportedDescription": "{modelType} での分割はサポートされていません。", - "visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage": "クエリが取得を試みたデータが多すぎます。通常、時間範囲を狭くするか、使用される間隔を変更すると、問題が解決します。", "visTypeTimeseries.vars.variableNameAriaLabel": "変数名", "visTypeTimeseries.vars.variableNamePlaceholder": "変数名", "visTypeTimeseries.visEditorVisualization.applyChangesLabel": "変更を適用", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8f14b4b22b0f71..4dfff43f2cbfe9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5325,7 +5325,6 @@ "visTypeTimeseries.emptyTextValue": "(空)", "visTypeTimeseries.error.requestForPanelFailedErrorMessage": "对此面板的请求失败", "visTypeTimeseries.fetchFields.loadIndexPatternFieldsErrorMessage": "无法加载 index_pattern 字段", - "visTypeTimeseries.fields.fieldNotFound": "未找到字段“{field}”", "visTypeTimeseries.fieldSelect.fieldIsNotValid": "“{fieldParameter}”字段无效,无法用于当前索引。请选择新字段。", "visTypeTimeseries.fieldSelect.selectFieldPlaceholder": "选择字段......", "visTypeTimeseries.filterRatio.aggregationLabel": "聚合", @@ -5719,10 +5718,7 @@ "visTypeTimeseries.units.perMillisecond": "每毫秒", "visTypeTimeseries.units.perMinute": "每分钟", "visTypeTimeseries.units.perSecond": "每秒", - "visTypeTimeseries.unsupportedAgg.aggIsNotSupportedDescription": "不再支持 {modelType} 聚合。", - "visTypeTimeseries.unsupportedAgg.aggIsTemporaryUnsupportedDescription": "当前不支持 {modelType} 聚合。", "visTypeTimeseries.unsupportedSplit.splitIsUnsupportedDescription": "不支持按 {modelType} 拆分", - "visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage": "您的查询尝试提取过多的数据。缩短时间范围或更改所用的时间间隔通常可解决问题。", "visTypeTimeseries.vars.variableNameAriaLabel": "变量名称", "visTypeTimeseries.vars.variableNamePlaceholder": "变量名称", "visTypeTimeseries.visEditorVisualization.applyChangesLabel": "应用更改",