mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[TSVB] Doesn't work correctly with pipeline aggregations in "entire time range" mode (#105073)
* 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>
This commit is contained in:
parent
22f0e762ed
commit
6e7e5155f5
26 changed files with 352 additions and 144 deletions
58
src/plugins/vis_type_timeseries/common/errors.ts
Normal file
58
src/plugins/vis_type_timeseries/common/errors.ts
Normal 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 },
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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[],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -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),
|
||||
}),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5169,7 +5169,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": "アグリゲーション",
|
||||
|
@ -5562,10 +5561,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": "変更を適用",
|
||||
|
|
|
@ -5214,7 +5214,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": "聚合",
|
||||
|
@ -5608,10 +5607,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": "应用更改",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue