[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>
This commit is contained in:
Uladzislau Lasitsa 2021-09-14 17:04:02 +03:00 committed by GitHub
parent 60c30c4bb8
commit fb6e27ef88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 352 additions and 144 deletions

View file

@ -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 },
})
);
}
}

View file

@ -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[],

View file

@ -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;

View file

@ -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(
<FormattedMessage
id="visTypeTimeseries.agg.aggIsNotSupportedDescription"
defaultMessage="The {modelType} aggregation is no longer supported."
values={{ modelType: <EuiCode>{props.model.type}</EuiCode> }}
/>
);
} else if (!isMetricEnabled(model.type, uiRestrictions)) {
Component = TemporaryUnsupportedAgg;
Component = getInvalidAggComponent(
<FormattedMessage
id="visTypeTimeseries.agg.aggIsUnsupportedForPanelConfigDescription"
defaultMessage="The {modelType} aggregation is not supported for existing panel configuration."
values={{ modelType: <EuiCode>{props.model.type}</EuiCode> }}
/>
);
}
const style = {

View file

@ -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 = [

View file

@ -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 (
<AggRow
disableDelete={props.disableDelete}
@ -32,15 +33,9 @@ export function UnsupportedAgg(props: UnsupportedAggProps) {
siblings={props.siblings}
dragHandleProps={props.dragHandleProps}
>
<EuiTitle className="tvbAggRow__unavailable" size="xxxs">
<span>
<FormattedMessage
id="visTypeTimeseries.unsupportedAgg.aggIsNotSupportedDescription"
defaultMessage="The {modelType} aggregation is no longer supported."
values={{ modelType: <EuiCode>{props.model.type}</EuiCode> }}
/>
</span>
<EuiTitle className="tvbAggRow__unavailable" size="xxxs" data-test-subj="invalid_agg">
<span>{message}</span>
</EuiTitle>
</AggRow>
);
}
};

View file

@ -69,6 +69,7 @@ export function MathAgg(props) {
id={htmlId('aggregation')}
siblings={props.siblings}
value={model.type}
panelType={props.panel.type}
onChange={handleSelectChange('type')}
/>
</EuiFlexItem>

View file

@ -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 (
<AggRow
disableDelete={props.disableDelete}
model={props.model}
onAdd={props.onAdd}
onDelete={props.onDelete}
siblings={props.siblings}
dragHandleProps={props.dragHandleProps}
>
<EuiTitle className="tvbAggRow__unavailable" size="xxxs">
<span>
<FormattedMessage
id="visTypeTimeseries.unsupportedAgg.aggIsTemporaryUnsupportedDescription"
defaultMessage="The {modelType} aggregation is currently unsupported."
values={{ modelType: <EuiCode>{props.model.type}</EuiCode> }}
/>
</span>
</EuiTitle>
</AggRow>
);
}

View file

@ -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,
});

View file

@ -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<string>((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();
}

View file

@ -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
);

View file

@ -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);

View file

@ -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,
}
`);

View file

@ -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),
}),

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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<string>((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
);
}
}

View file

@ -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';

View file

@ -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)

View file

@ -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: {},

View file

@ -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<string>((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,
});
});
};

View file

@ -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');

View file

@ -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`);
}
}

View file

@ -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": "変更を適用",

View file

@ -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": "应用更改",