mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[TSVB] Fix the broken "aggregate function" in TSVB table (#119967)
* [TSVB] Fix the broken "aggregate function" in TSVB table Closes: #91149 * [TSVB] Table series filter and aggregation function applied at the same time cause an error # Conflicts: # src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/split_by_everything.ts # src/plugins/vis_types/timeseries/server/lib/vis_data/request_processors/table/split_by_terms.ts * some work * filter terms columns * fix error message on no pivot_id * fix CI * enable aggregation function for entire timerange * fix PR comments * update check_aggs * fix series aggs for table * unify error messages * fix pr comment: restrictions: UIRestrictions = DEFAULT_UI_RESTRICTION * fix i18n translation error * fixes translations Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
348bfb8b33
commit
31b805a314
47 changed files with 338 additions and 249 deletions
|
@ -7,16 +7,24 @@
|
|||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { RESTRICTIONS_KEYS, DEFAULT_UI_RESTRICTION } from '../../../common/ui_restrictions';
|
||||
import {
|
||||
RESTRICTIONS_KEYS,
|
||||
DEFAULT_UI_RESTRICTION,
|
||||
UIRestrictions,
|
||||
TimeseriesUIRestrictions,
|
||||
} from './ui_restrictions';
|
||||
|
||||
/**
|
||||
* Generic method for checking all types of the UI Restrictions
|
||||
* @private
|
||||
*/
|
||||
const checkUIRestrictions = (key, restrictions = DEFAULT_UI_RESTRICTION, type) => {
|
||||
const checkUIRestrictions = (
|
||||
key: string,
|
||||
type: string,
|
||||
restrictions: UIRestrictions = DEFAULT_UI_RESTRICTION
|
||||
) => {
|
||||
const isAllEnabled = get(restrictions, `${type}.*`, true);
|
||||
|
||||
return isAllEnabled || Boolean(get(restrictions, type, {})[key]);
|
||||
return isAllEnabled || Boolean(get(restrictions, [type, key], false));
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -27,8 +35,11 @@ const checkUIRestrictions = (key, restrictions = DEFAULT_UI_RESTRICTION, type) =
|
|||
* @param restrictions - uiRestrictions object. Comes from the /data request.
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isMetricEnabled = (key, restrictions) => {
|
||||
return checkUIRestrictions(key, restrictions, RESTRICTIONS_KEYS.WHITE_LISTED_METRICS);
|
||||
export const isMetricEnabled = (
|
||||
key: string,
|
||||
restrictions: TimeseriesUIRestrictions | undefined
|
||||
) => {
|
||||
return checkUIRestrictions(key, RESTRICTIONS_KEYS.WHITE_LISTED_METRICS, restrictions);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -40,12 +51,16 @@ export const isMetricEnabled = (key, restrictions) => {
|
|||
* @param restrictions - uiRestrictions object. Comes from the /data request.
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isFieldEnabled = (field, metricType, restrictions = DEFAULT_UI_RESTRICTION) => {
|
||||
export const isFieldEnabled = (
|
||||
field: string,
|
||||
metricType: string,
|
||||
restrictions?: TimeseriesUIRestrictions
|
||||
) => {
|
||||
if (isMetricEnabled(metricType, restrictions)) {
|
||||
return checkUIRestrictions(
|
||||
field,
|
||||
restrictions[RESTRICTIONS_KEYS.WHITE_LISTED_METRICS],
|
||||
metricType
|
||||
metricType,
|
||||
restrictions?.[RESTRICTIONS_KEYS.WHITE_LISTED_METRICS]
|
||||
);
|
||||
}
|
||||
return false;
|
||||
|
@ -60,8 +75,8 @@ export const isFieldEnabled = (field, metricType, restrictions = DEFAULT_UI_REST
|
|||
* @param restrictions - uiRestrictions object. Comes from the /data request.
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isGroupByFieldsEnabled = (key, restrictions) => {
|
||||
return checkUIRestrictions(key, restrictions, RESTRICTIONS_KEYS.WHITE_LISTED_GROUP_BY_FIELDS);
|
||||
export const isGroupByFieldsEnabled = (key: string, restrictions: TimeseriesUIRestrictions) => {
|
||||
return checkUIRestrictions(key, RESTRICTIONS_KEYS.WHITE_LISTED_GROUP_BY_FIELDS, restrictions);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -73,6 +88,26 @@ export const isGroupByFieldsEnabled = (key, restrictions) => {
|
|||
* @param restrictions - uiRestrictions object. Comes from the /data request.
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isTimerangeModeEnabled = (key, restrictions) => {
|
||||
return checkUIRestrictions(key, restrictions, RESTRICTIONS_KEYS.WHITE_LISTED_TIMERANGE_MODES);
|
||||
export const isTimerangeModeEnabled = (key: string, restrictions: TimeseriesUIRestrictions) => {
|
||||
return checkUIRestrictions(key, RESTRICTIONS_KEYS.WHITE_LISTED_TIMERANGE_MODES, restrictions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Using this method, you can check whether a specific configuration feature is allowed
|
||||
* for current panel configuration or not.
|
||||
* @public
|
||||
* @param key - string value of the time range mode.
|
||||
* All available mode you can find in the following object TIME_RANGE_DATA_MODES.
|
||||
* @param restrictions - uiRestrictions object. Comes from the /data request.
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isConfigurationFeatureEnabled = (
|
||||
key: string,
|
||||
restrictions: TimeseriesUIRestrictions
|
||||
) => {
|
||||
return checkUIRestrictions(
|
||||
key,
|
||||
RESTRICTIONS_KEYS.WHITE_LISTED_CONFIGURATION_FEATURES,
|
||||
restrictions
|
||||
);
|
||||
};
|
|
@ -46,12 +46,37 @@ export class ValidateIntervalError extends UIError {
|
|||
}
|
||||
}
|
||||
|
||||
export class AggNotSupportedInMode extends UIError {
|
||||
constructor(metricType: string, timeRangeMode: string) {
|
||||
export class AggNotSupportedError extends UIError {
|
||||
constructor(metricType: string) {
|
||||
super(
|
||||
i18n.translate('visTypeTimeseries.wrongAggregationErrorMessage', {
|
||||
defaultMessage: 'The aggregation {metricType} is not supported in {timeRangeMode} mode',
|
||||
values: { metricType, timeRangeMode },
|
||||
defaultMessage:
|
||||
'The {metricType} aggregation is not supported for existing panel configuration.',
|
||||
values: { metricType },
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const filterCannotBeAppliedErrorMessage = i18n.translate(
|
||||
'visTypeTimeseries.filterCannotBeAppliedError',
|
||||
{
|
||||
defaultMessage: 'The "filter" cannot be applied with this configuration',
|
||||
}
|
||||
);
|
||||
|
||||
export class FilterCannotBeAppliedError extends UIError {
|
||||
constructor() {
|
||||
super(filterCannotBeAppliedErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
export class PivotNotSelectedForTableError extends UIError {
|
||||
constructor() {
|
||||
super(
|
||||
i18n.translate('visTypeTimeseries.table.noResultsAvailableWithDescriptionMessage', {
|
||||
defaultMessage:
|
||||
'No results available. You must choose a group by field for this visualization.',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,19 +25,27 @@ export enum RESTRICTIONS_KEYS {
|
|||
WHITE_LISTED_METRICS = 'whiteListedMetrics',
|
||||
|
||||
/**
|
||||
* Key for getting the white listed Time Range modes from the UIRestrictions object.
|
||||
* Key for getting the white listed Time Range modes from the UIRestrictions object.
|
||||
*/
|
||||
WHITE_LISTED_TIMERANGE_MODES = 'whiteListedTimerangeModes',
|
||||
|
||||
/**
|
||||
* Key for getting the white listed Configuration Features from the UIRestrictions object.
|
||||
*/
|
||||
WHITE_LISTED_CONFIGURATION_FEATURES = 'whiteListedConfigurationFeatures',
|
||||
}
|
||||
|
||||
export interface UIRestrictions {
|
||||
'*': boolean;
|
||||
[restriction: string]: boolean;
|
||||
[key: string]: boolean | UIRestrictions;
|
||||
}
|
||||
|
||||
export type TimeseriesUIRestrictions = {
|
||||
[key in RESTRICTIONS_KEYS]: Record<string, UIRestrictions>;
|
||||
};
|
||||
export interface TimeseriesUIRestrictions extends UIRestrictions {
|
||||
[RESTRICTIONS_KEYS.WHITE_LISTED_GROUP_BY_FIELDS]: UIRestrictions;
|
||||
[RESTRICTIONS_KEYS.WHITE_LISTED_METRICS]: UIRestrictions;
|
||||
[RESTRICTIONS_KEYS.WHITE_LISTED_TIMERANGE_MODES]: UIRestrictions;
|
||||
[RESTRICTIONS_KEYS.WHITE_LISTED_CONFIGURATION_FEATURES]: UIRestrictions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default value for the UIRestriction
|
||||
|
|
|
@ -11,8 +11,7 @@ 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 { isMetricEnabled } from '../../../../common/check_ui_restrictions';
|
||||
import { getInvalidAggComponent } from './invalid_agg';
|
||||
// @ts-expect-error not typed yet
|
||||
import { seriesChangeHandler } from '../lib/series_change_handler';
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// @ts-ignore
|
||||
import { isMetricEnabled } from '../../lib/check_ui_restrictions';
|
||||
|
||||
import { isMetricEnabled } from '../../../../common/check_ui_restrictions';
|
||||
import { VisDataContext } from '../../contexts/vis_data_context';
|
||||
import { getAggsByType, getAggsByPredicate } from '../../../../common/agg_utils';
|
||||
import type { Agg } from '../../../../common/agg_utils';
|
||||
|
@ -64,7 +64,7 @@ export function AggSelect(props: AggSelectUiProps) {
|
|||
} else {
|
||||
const disableSiblingAggs = (agg: AggSelectOption) => ({
|
||||
...agg,
|
||||
disabled: !enablePipelines || !isMetricEnabled(agg.value, uiRestrictions),
|
||||
disabled: !enablePipelines || !isMetricEnabled(agg.value as string, uiRestrictions),
|
||||
});
|
||||
|
||||
options = [
|
||||
|
|
|
@ -18,8 +18,7 @@ import { getIndexPatternKey } from '../../../../common/index_patterns_utils';
|
|||
import type { SanitizedFieldType, IndexPatternValue } from '../../../../common/types';
|
||||
import type { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions';
|
||||
|
||||
// @ts-ignore
|
||||
import { isFieldEnabled } from '../../lib/check_ui_restrictions';
|
||||
import { isFieldEnabled } from '../../../../common/check_ui_restrictions';
|
||||
|
||||
interface FieldSelectProps {
|
||||
label: string | ReactNode;
|
||||
|
|
|
@ -32,7 +32,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { PANEL_TYPES, TIME_RANGE_DATA_MODES, TIME_RANGE_MODE_KEY } from '../../../common/enums';
|
||||
import { AUTO_INTERVAL } from '../../../common/constants';
|
||||
import { isTimerangeModeEnabled } from '../lib/check_ui_restrictions';
|
||||
import { isTimerangeModeEnabled } from '../../../common/check_ui_restrictions';
|
||||
import { VisDataContext } from '../contexts/vis_data_context';
|
||||
import { PanelModelContext } from '../contexts/panel_model_context';
|
||||
import { FormValidationContext } from '../contexts/form_validation_context';
|
||||
|
|
|
@ -15,7 +15,7 @@ import { QueryStringInput, QueryStringInputProps } from '../../../../../../plugi
|
|||
import { getDataStart } from '../../services';
|
||||
import { fetchIndexPattern, isStringTypeIndexPattern } from '../../../common/index_patterns_utils';
|
||||
|
||||
type QueryBarWrapperProps = Pick<QueryStringInputProps, 'query' | 'onChange'> & {
|
||||
type QueryBarWrapperProps = Pick<QueryStringInputProps, 'query' | 'onChange' | 'isInvalid'> & {
|
||||
indexPatterns: IndexPatternValue[];
|
||||
'data-test-subj'?: string;
|
||||
};
|
||||
|
@ -23,6 +23,7 @@ type QueryBarWrapperProps = Pick<QueryStringInputProps, 'query' | 'onChange'> &
|
|||
export function QueryBarWrapper({
|
||||
query,
|
||||
onChange,
|
||||
isInvalid,
|
||||
indexPatterns,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: QueryBarWrapperProps) {
|
||||
|
@ -64,6 +65,7 @@ export function QueryBarWrapper({
|
|||
<QueryStringInput
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
isInvalid={isInvalid}
|
||||
indexPatterns={indexes}
|
||||
{...coreStartContext}
|
||||
dataTestSubj={dataTestSubj}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { SplitByFilter } from './splits/filter';
|
|||
import { SplitByFilters } from './splits/filters';
|
||||
import { SplitByEverything } from './splits/everything';
|
||||
import { SplitUnsupported } from './splits/unsupported_split';
|
||||
import { isGroupByFieldsEnabled } from '../lib/check_ui_restrictions';
|
||||
import { isGroupByFieldsEnabled } from '../../../common/check_ui_restrictions';
|
||||
import { getDefaultQueryLanguage } from './lib/get_default_query_language';
|
||||
|
||||
const SPLIT_MODES = {
|
||||
|
|
|
@ -10,7 +10,7 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { EuiComboBox } from '@elastic/eui';
|
||||
import { injectI18n } from '@kbn/i18n-react';
|
||||
import { isGroupByFieldsEnabled } from '../../lib/check_ui_restrictions';
|
||||
import { isGroupByFieldsEnabled } from '../../../../common/check_ui_restrictions';
|
||||
|
||||
function GroupBySelectUi(props) {
|
||||
const { intl, uiRestrictions } = props;
|
||||
|
|
|
@ -35,6 +35,9 @@ import { getDefaultQueryLanguage } from '../../lib/get_default_query_language';
|
|||
import { checkIfNumericMetric } from '../../lib/check_if_numeric_metric';
|
||||
import { QueryBarWrapper } from '../../query_bar_wrapper';
|
||||
import { DATA_FORMATTERS } from '../../../../../common/enums';
|
||||
import { isConfigurationFeatureEnabled } from '../../../../../common/check_ui_restrictions';
|
||||
import { filterCannotBeAppliedErrorMessage } from '../../../../../common/errors';
|
||||
import { KBN_FIELD_TYPES } from '../../../../../../../data/public';
|
||||
|
||||
export class TableSeriesConfig extends Component {
|
||||
UNSAFE_componentWillMount() {
|
||||
|
@ -123,6 +126,9 @@ export class TableSeriesConfig extends Component {
|
|||
const isKibanaIndexPattern =
|
||||
this.props.panel.use_kibana_indexes || this.props.indexPatternForQuery === '';
|
||||
|
||||
const isFilterCannotBeApplied =
|
||||
model.filter?.query && !isConfigurationFeatureEnabled('filter', this.props.uiRestrictions);
|
||||
|
||||
return (
|
||||
<div className="tvbAggRow">
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
|
@ -174,6 +180,8 @@ export class TableSeriesConfig extends Component {
|
|||
defaultMessage="Filter"
|
||||
/>
|
||||
}
|
||||
isInvalid={isFilterCannotBeApplied}
|
||||
error={filterCannotBeAppliedErrorMessage}
|
||||
fullWidth
|
||||
>
|
||||
<QueryBarWrapper
|
||||
|
@ -181,6 +189,7 @@ export class TableSeriesConfig extends Component {
|
|||
language: model?.filter?.language || getDefaultQueryLanguage(),
|
||||
query: model?.filter?.query || '',
|
||||
}}
|
||||
isInvalid={isFilterCannotBeApplied}
|
||||
onChange={(filter) => this.props.onChange({ filter })}
|
||||
indexPatterns={[this.props.indexPatternForQuery]}
|
||||
/>
|
||||
|
@ -214,6 +223,15 @@ export class TableSeriesConfig extends Component {
|
|||
value={model.aggregate_by}
|
||||
onChange={handleSelectChange('aggregate_by')}
|
||||
fullWidth
|
||||
restrict={[
|
||||
KBN_FIELD_TYPES.NUMBER,
|
||||
KBN_FIELD_TYPES.BOOLEAN,
|
||||
KBN_FIELD_TYPES.DATE,
|
||||
KBN_FIELD_TYPES.IP,
|
||||
KBN_FIELD_TYPES.STRING,
|
||||
]}
|
||||
uiRestrictions={this.props.uiRestrictions}
|
||||
type={'terms'}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>
|
||||
|
@ -268,4 +286,5 @@ TableSeriesConfig.propTypes = {
|
|||
model: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
uiRestrictions: PropTypes.object,
|
||||
};
|
||||
|
|
|
@ -70,6 +70,7 @@ function TableSeriesUI(props) {
|
|||
model={props.model}
|
||||
onChange={props.onChange}
|
||||
indexPatternForQuery={props.indexPatternForQuery}
|
||||
uiRestrictions={props.uiRestrictions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -240,31 +240,15 @@ class TableVis extends Component {
|
|||
closeExternalUrlErrorModal = () => this.setState({ accessDeniedDrilldownUrl: null });
|
||||
|
||||
render() {
|
||||
const { visData, model } = this.props;
|
||||
const { visData } = this.props;
|
||||
const { accessDeniedDrilldownUrl } = this.state;
|
||||
const header = this.renderHeader();
|
||||
let rows;
|
||||
let rows = null;
|
||||
|
||||
if (isArray(visData.series) && visData.series.length) {
|
||||
rows = visData.series.map(this.renderRow);
|
||||
} else {
|
||||
const message = model.pivot_id ? (
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.table.noResultsAvailableMessage"
|
||||
defaultMessage="No results available."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.table.noResultsAvailableWithDescriptionMessage"
|
||||
defaultMessage="No results available. You must choose a group by field for this visualization."
|
||||
/>
|
||||
);
|
||||
rows = (
|
||||
<tr>
|
||||
<td colSpan={this.visibleSeries.length + 1}>{message}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RedirectAppLinks
|
||||
|
|
|
@ -33,6 +33,7 @@ describe('DefaultSearchCapabilities', () => {
|
|||
whiteListedMetrics: { '*': true },
|
||||
whiteListedGroupByFields: { '*': true },
|
||||
whiteListedTimerangeModes: { '*': true },
|
||||
whiteListedConfigurationFeatures: { '*': true },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
parseInterval,
|
||||
getSuitableUnit,
|
||||
} from '../../vis_data/helpers/unit_to_seconds';
|
||||
import { RESTRICTIONS_KEYS } from '../../../../common/ui_restrictions';
|
||||
import { RESTRICTIONS_KEYS, TimeseriesUIRestrictions } from '../../../../common/ui_restrictions';
|
||||
import {
|
||||
TIME_RANGE_DATA_MODES,
|
||||
PANEL_TYPES,
|
||||
|
@ -28,6 +28,17 @@ export interface SearchCapabilitiesOptions {
|
|||
panel?: Panel;
|
||||
}
|
||||
|
||||
const convertAggsToRestriction = (allAvailableAggs: string[]) =>
|
||||
allAvailableAggs.reduce(
|
||||
(availableAggs, aggType) => ({
|
||||
...availableAggs,
|
||||
[aggType]: {
|
||||
'*': true,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
export class DefaultSearchCapabilities {
|
||||
public timezone: SearchCapabilitiesOptions['timezone'];
|
||||
public maxBucketsLimit: SearchCapabilitiesOptions['maxBucketsLimit'];
|
||||
|
@ -44,30 +55,35 @@ 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
|
||||
) {
|
||||
if (this.panel) {
|
||||
const aggs = getAggsByType<string>((agg) => agg.id);
|
||||
const allAvailableAggs = [
|
||||
...aggs[AGG_TYPE.METRIC],
|
||||
...aggs[AGG_TYPE.SIBLING_PIPELINE],
|
||||
TSVB_METRIC_TYPES.MATH,
|
||||
TSVB_METRIC_TYPES.CALCULATION,
|
||||
BUCKET_TYPES.TERMS,
|
||||
// SERIES_AGG should be blocked for table
|
||||
...(this.panel.type === PANEL_TYPES.TABLE ? [] : [TSVB_METRIC_TYPES.SERIES_AGG]),
|
||||
].reduce(
|
||||
(availableAggs, aggType) => ({
|
||||
...availableAggs,
|
||||
[aggType]: {
|
||||
'*': true,
|
||||
},
|
||||
}),
|
||||
{}
|
||||
);
|
||||
return this.createUiRestriction(allAvailableAggs);
|
||||
|
||||
if (
|
||||
this.panel.type !== PANEL_TYPES.TIMESERIES &&
|
||||
this.panel.time_range_mode === TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE
|
||||
) {
|
||||
return this.createUiRestriction(
|
||||
convertAggsToRestriction([
|
||||
...aggs[AGG_TYPE.METRIC],
|
||||
...aggs[AGG_TYPE.SIBLING_PIPELINE],
|
||||
TSVB_METRIC_TYPES.MATH,
|
||||
TSVB_METRIC_TYPES.CALCULATION,
|
||||
BUCKET_TYPES.TERMS,
|
||||
// SERIES_AGG should be blocked for table
|
||||
...(this.panel.type === PANEL_TYPES.TABLE ? [] : [TSVB_METRIC_TYPES.SERIES_AGG]),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (this.panel?.type === PANEL_TYPES.TABLE) {
|
||||
return this.createUiRestriction(
|
||||
convertAggsToRestriction(
|
||||
[...Object.values(aggs).flat(), BUCKET_TYPES.TERMS].filter(
|
||||
(item) => item !== TSVB_METRIC_TYPES.SERIES_AGG
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.createUiRestriction();
|
||||
}
|
||||
|
@ -80,12 +96,18 @@ export class DefaultSearchCapabilities {
|
|||
return this.createUiRestriction();
|
||||
}
|
||||
|
||||
public get whiteListedConfigurationFeatures() {
|
||||
return this.createUiRestriction();
|
||||
}
|
||||
|
||||
public get uiRestrictions() {
|
||||
return {
|
||||
[RESTRICTIONS_KEYS.WHITE_LISTED_METRICS]: this.whiteListedMetrics,
|
||||
[RESTRICTIONS_KEYS.WHITE_LISTED_GROUP_BY_FIELDS]: this.whiteListedGroupByFields,
|
||||
[RESTRICTIONS_KEYS.WHITE_LISTED_TIMERANGE_MODES]: this.whiteListedTimerangeModes,
|
||||
};
|
||||
[RESTRICTIONS_KEYS.WHITE_LISTED_CONFIGURATION_FEATURES]:
|
||||
this.whiteListedConfigurationFeatures,
|
||||
} as TimeseriesUIRestrictions;
|
||||
}
|
||||
|
||||
createUiRestriction(restrictionsObject?: Record<string, any>) {
|
||||
|
|
|
@ -84,6 +84,12 @@ export class RollupSearchCapabilities extends DefaultSearchCapabilities {
|
|||
});
|
||||
}
|
||||
|
||||
public get whiteListedConfigurationFeatures() {
|
||||
return this.createUiRestriction({
|
||||
filter: false,
|
||||
});
|
||||
}
|
||||
|
||||
getValidTimeInterval(userIntervalString: string) {
|
||||
const parsedRollupJobInterval = this.parseInterval(this.defaultTimeInterval);
|
||||
const inRollupJobUnit = this.convertIntervalToUnit(
|
||||
|
|
|
@ -14,7 +14,7 @@ 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,
|
||||
|
@ -61,10 +61,7 @@ export async function getSeriesData(
|
|||
|
||||
try {
|
||||
const bodiesPromises = getActiveSeries(panel).map((series) => {
|
||||
if (isEntireTimeRangeMode(panel, series)) {
|
||||
isAggSupported(series.metrics, capabilities);
|
||||
}
|
||||
|
||||
isAggSupported(series.metrics, capabilities);
|
||||
return getSeriesRequestParams(req, panel, panelIndex, series, capabilities, services);
|
||||
});
|
||||
|
||||
|
|
|
@ -16,7 +16,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 { isConfigurationFeatureEnabled } from '../../../common/check_ui_restrictions';
|
||||
import { FilterCannotBeAppliedError, PivotNotSelectedForTableError } from '../../../common/errors';
|
||||
|
||||
import type {
|
||||
VisTypeTimeseriesRequestHandlerContext,
|
||||
|
@ -76,10 +77,15 @@ export async function getTableData(
|
|||
const handleError = handleErrorResponse(panel);
|
||||
|
||||
try {
|
||||
if (isEntireTimeRangeMode(panel)) {
|
||||
panel.series.forEach((column) => {
|
||||
isAggSupported(column.metrics, capabilities);
|
||||
});
|
||||
panel.series.forEach((series) => {
|
||||
isAggSupported(series.metrics, capabilities);
|
||||
if (series.filter?.query && !isConfigurationFeatureEnabled('filter', capabilities)) {
|
||||
throw new FilterCannotBeAppliedError();
|
||||
}
|
||||
});
|
||||
|
||||
if (!panel.pivot_id) {
|
||||
throw new PivotNotSelectedForTableError();
|
||||
}
|
||||
|
||||
const body = await buildTableRequest({
|
||||
|
|
|
@ -6,37 +6,18 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { AggNotSupportedInMode } from '../../../../common/errors';
|
||||
import { TIME_RANGE_DATA_MODES } from '../../../../common/enums';
|
||||
import { DEFAULT_UI_RESTRICTION, RESTRICTIONS_KEYS } from '../../../../common/ui_restrictions';
|
||||
import { AggNotSupportedError } from '../../../../common/errors';
|
||||
import { isMetricEnabled } from '../../../../common/check_ui_restrictions';
|
||||
|
||||
import { Metric } from '../../../../common/types';
|
||||
import { SearchCapabilities } from '../../search_strategies';
|
||||
|
||||
// @todo: will be removed in 8.1
|
||||
// That logic was moved into common folder in that PR https://github.com/elastic/kibana/pull/119967
|
||||
// isMetricEnabled method should be used instead. See check_ui_restrictions.ts file
|
||||
const checkUIRestrictions = (key: string, restrictions: Record<string, unknown>, type: string) => {
|
||||
const isAllEnabled = get(restrictions ?? DEFAULT_UI_RESTRICTION, `${type}.*`, true);
|
||||
|
||||
return isAllEnabled || Boolean(get(restrictions ?? DEFAULT_UI_RESTRICTION, [type, key], false));
|
||||
};
|
||||
import type { Metric } from '../../../../common/types';
|
||||
import type { SearchCapabilities } from '../../search_strategies';
|
||||
|
||||
export function isAggSupported(metrics: Metric[], capabilities: SearchCapabilities) {
|
||||
const metricTypes = metrics.filter(
|
||||
(metric) =>
|
||||
!checkUIRestrictions(
|
||||
metric.type,
|
||||
capabilities.uiRestrictions,
|
||||
RESTRICTIONS_KEYS.WHITE_LISTED_METRICS
|
||||
)
|
||||
(metric) => !isMetricEnabled(metric.type, capabilities.uiRestrictions)
|
||||
);
|
||||
|
||||
if (metricTypes.length) {
|
||||
throw new AggNotSupportedInMode(
|
||||
metricTypes.map((metric) => `"${metric.type}"`).join(', '),
|
||||
TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE
|
||||
);
|
||||
throw new AggNotSupportedError(metricTypes.map((metric) => `"${metric.type}"`).join(', '));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import { getSplits } from './get_splits';
|
||||
import { Panel, Series } from '../../../../common/types';
|
||||
|
||||
describe('getSplits(resp, panel, series)', () => {
|
||||
test('should return a splits for everything/filter group bys', async () => {
|
||||
|
@ -19,7 +20,7 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const panel = { type: 'timeseries' };
|
||||
const panel = { type: 'timeseries' } as Panel;
|
||||
const series = {
|
||||
id: 'SERIES',
|
||||
color: 'rgb(255, 0, 0)',
|
||||
|
@ -28,8 +29,9 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
{ id: 'AVG', type: 'avg', field: 'cpu' },
|
||||
{ id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' },
|
||||
],
|
||||
};
|
||||
expect(await getSplits(resp, panel, series, undefined)).toEqual([
|
||||
} as Series;
|
||||
|
||||
expect(await getSplits(resp, panel, series, undefined, () => {})).toEqual([
|
||||
{
|
||||
id: 'SERIES',
|
||||
label: 'Overall Average of Average of cpu',
|
||||
|
@ -72,9 +74,10 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
{ id: 'AVG', type: 'avg', field: 'cpu' },
|
||||
{ id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' },
|
||||
],
|
||||
};
|
||||
const panel = { type: 'top_n' };
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
} as unknown as Series;
|
||||
const panel = { type: 'top_n' } as Panel;
|
||||
|
||||
expect(await getSplits(resp, panel, series, undefined, () => [])).toEqual([
|
||||
{
|
||||
id: 'SERIES:example-01',
|
||||
key: 'example-01',
|
||||
|
@ -131,9 +134,9 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
{ id: 'AVG', type: 'avg', field: 'cpu' },
|
||||
{ id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' },
|
||||
],
|
||||
};
|
||||
const panel = { type: 'top_n' };
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
} as unknown as Series;
|
||||
const panel = { type: 'top_n' } as Panel;
|
||||
expect(await getSplits(resp, panel, series, undefined, () => [])).toEqual([
|
||||
{
|
||||
id: 'SERIES:example-01',
|
||||
key: 'example-01',
|
||||
|
@ -192,10 +195,10 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
{ id: 'AVG', type: 'avg', field: 'cpu' },
|
||||
{ id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' },
|
||||
],
|
||||
};
|
||||
const panel = { type: 'top_n' };
|
||||
} as unknown as Series;
|
||||
const panel = { type: 'top_n' } as Panel;
|
||||
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
expect(await getSplits(resp, panel, series, undefined, () => [])).toEqual([
|
||||
{
|
||||
id: 'SERIES:example-01',
|
||||
key: 'example-01',
|
||||
|
@ -248,10 +251,10 @@ describe('getSplits(resp, panel, series)', () => {
|
|||
{ id: 'filter-2', color: '#0F0', filter: 'status_code:[300 TO *]', label: '300s' },
|
||||
],
|
||||
metrics: [{ id: 'COUNT', type: 'count' }],
|
||||
};
|
||||
const panel = { type: 'timeseries' };
|
||||
} as unknown as Series;
|
||||
const panel = { type: 'timeseries' } as Panel;
|
||||
|
||||
expect(await getSplits(resp, panel, series)).toEqual([
|
||||
expect(await getSplits(resp, panel, series, undefined, () => [])).toEqual([
|
||||
{
|
||||
id: 'SERIES:filter-1',
|
||||
key: 'filter-1',
|
|
@ -42,7 +42,7 @@ export async function getSplits<TRawResponse = unknown, TMeta extends BaseMeta =
|
|||
resp: TRawResponse,
|
||||
panel: Panel,
|
||||
series: Series,
|
||||
meta: TMeta,
|
||||
meta: TMeta | undefined,
|
||||
extractFields: Function
|
||||
): Promise<Array<SplittedData<TMeta>>> {
|
||||
if (!meta) {
|
||||
|
@ -52,12 +52,20 @@ export async function getSplits<TRawResponse = unknown, TMeta extends BaseMeta =
|
|||
const color = new Color(series.color);
|
||||
const metric = getLastMetric(series);
|
||||
const buckets = get(resp, `aggregations.${series.id}.buckets`);
|
||||
const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : [];
|
||||
|
||||
const fieldsForSeries = meta?.index ? await extractFields({ id: meta.index }) : [];
|
||||
const splitByLabel = calculateLabel(metric, series.metrics, fieldsForSeries);
|
||||
|
||||
if (buckets) {
|
||||
if (Array.isArray(buckets)) {
|
||||
return buckets.map((bucket) => {
|
||||
if (bucket.column_filter) {
|
||||
bucket = {
|
||||
...bucket,
|
||||
...bucket.column_filter,
|
||||
};
|
||||
}
|
||||
|
||||
bucket.id = `${series.id}:${bucket.key}`;
|
||||
bucket.splitByLabel = splitByLabel;
|
||||
bucket.label = formatKey(bucket.key, series);
|
||||
|
@ -101,7 +109,7 @@ export async function getSplits<TRawResponse = unknown, TMeta extends BaseMeta =
|
|||
label: series.label || splitByLabel,
|
||||
color: color.string(),
|
||||
...mergeObj,
|
||||
meta,
|
||||
meta: meta!,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -25,8 +25,12 @@ function removeEmptyTopLevelAggregation(doc, series) {
|
|||
|
||||
if (isEmptyFilter(filter) && !hasSiblingPipelineAggregation(doc.aggs[series.id].aggs)) {
|
||||
const meta = _.get(doc, `aggs.${series.id}.meta`);
|
||||
|
||||
overwrite(doc, `aggs`, doc.aggs[series.id].aggs);
|
||||
overwrite(doc, `aggs.timeseries.meta`, meta);
|
||||
overwrite(doc, `aggs.timeseries.meta`, {
|
||||
...meta,
|
||||
normalized: true,
|
||||
});
|
||||
}
|
||||
|
||||
return doc;
|
||||
|
|
|
@ -77,6 +77,7 @@ describe('normalizeQuery', () => {
|
|||
|
||||
expect(modifiedDoc.aggs.timeseries.meta).toEqual({
|
||||
timeField: 'order_date',
|
||||
normalized: true,
|
||||
intervalString: '10s',
|
||||
bucketSize: 10,
|
||||
seriesId: [seriesId],
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { get } from 'lodash';
|
||||
import { buildEsQuery } from '@kbn/es-query';
|
||||
import { overwrite } from '../../helpers';
|
||||
|
||||
import type { TableRequestProcessorsFunction } from './types';
|
||||
|
||||
export const applyFilters: TableRequestProcessorsFunction =
|
||||
({ panel, esQueryConfig, seriesIndex }) =>
|
||||
(next) =>
|
||||
(doc) => {
|
||||
panel.series.forEach((column) => {
|
||||
const hasAggregateByApplied = Boolean(column.aggregate_by && column.aggregate_function);
|
||||
let filterSelector = `aggs.pivot.aggs.${column.id}.filter`;
|
||||
|
||||
if (hasAggregateByApplied && column.filter?.query) {
|
||||
const originalAggsSelector = `aggs.pivot.aggs.${column.id}.aggs`;
|
||||
const originalAggs = get(doc, originalAggsSelector);
|
||||
|
||||
overwrite(doc, originalAggsSelector, {
|
||||
column_filter: {
|
||||
aggs: originalAggs,
|
||||
},
|
||||
});
|
||||
|
||||
filterSelector = `${originalAggsSelector}.column_filter.filter`;
|
||||
}
|
||||
|
||||
if (column.filter?.query) {
|
||||
overwrite(
|
||||
doc,
|
||||
filterSelector,
|
||||
buildEsQuery(seriesIndex.indexPattern || undefined, [column.filter], [], esQueryConfig)
|
||||
);
|
||||
} else {
|
||||
if (!hasAggregateByApplied) {
|
||||
overwrite(doc, `${filterSelector}.match_all`, {});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return next(doc);
|
||||
};
|
|
@ -6,16 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { has } from 'lodash';
|
||||
|
||||
import type { TableSearchRequest } from '../table/types';
|
||||
import type { Series } from '../../../../../common/types';
|
||||
|
||||
export function calculateAggRoot(doc: TableSearchRequest, column: Series) {
|
||||
let aggRoot = `aggs.pivot.aggs.${column.id}.aggs`;
|
||||
|
||||
if (has(doc, `aggs.pivot.aggs.${column.id}.aggs.column_filter`)) {
|
||||
aggRoot = `aggs.pivot.aggs.${column.id}.aggs.column_filter.aggs`;
|
||||
}
|
||||
return aggRoot;
|
||||
return `aggs.pivot.aggs.${column.id}.aggs`;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
export { pivot } from './pivot';
|
||||
export { query } from './query';
|
||||
export { splitByEverything } from './split_by_everything';
|
||||
export { applyFilters } from './apply_filters';
|
||||
export { splitByTerms } from './split_by_terms';
|
||||
export { dateHistogram } from './date_histogram';
|
||||
export { metricBuckets } from './metric_buckets';
|
||||
|
|
|
@ -85,6 +85,7 @@ describe('normalizeQuery', () => {
|
|||
|
||||
expect(modifiedDoc.aggs.pivot.aggs[seriesId].meta).toEqual({
|
||||
seriesId,
|
||||
normalized: true,
|
||||
timeField: 'order_date',
|
||||
intervalString: '10s',
|
||||
bucketSize: 10,
|
||||
|
|
|
@ -41,14 +41,16 @@ export const normalizeQuery: TableRequestProcessorsFunction = () => {
|
|||
};
|
||||
|
||||
overwrite(normalizedSeries, `${seriesId}`, agg);
|
||||
overwrite(normalizedSeries, `${seriesId}.meta`, meta);
|
||||
overwrite(normalizedSeries, `${seriesId}.meta`, {
|
||||
...meta,
|
||||
normalized: true,
|
||||
});
|
||||
} else {
|
||||
overwrite(normalizedSeries, `${seriesId}`, value);
|
||||
}
|
||||
});
|
||||
|
||||
overwrite(doc, 'aggs.pivot.aggs', normalizedSeries);
|
||||
|
||||
return doc;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,35 +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 { buildEsQuery } from '@kbn/es-query';
|
||||
import { overwrite } from '../../helpers';
|
||||
|
||||
import type { TableRequestProcessorsFunction } from './types';
|
||||
|
||||
export const splitByEverything: TableRequestProcessorsFunction =
|
||||
({ panel, esQueryConfig, seriesIndex }) =>
|
||||
(next) =>
|
||||
(doc) => {
|
||||
const indexPattern = seriesIndex.indexPattern || undefined;
|
||||
|
||||
panel.series
|
||||
.filter((c) => !(c.aggregate_by && c.aggregate_function))
|
||||
.forEach((column) => {
|
||||
if (column.filter) {
|
||||
overwrite(
|
||||
doc,
|
||||
`aggs.pivot.aggs.${column.id}.filter`,
|
||||
buildEsQuery(indexPattern, [column.filter], [], esQueryConfig)
|
||||
);
|
||||
} else {
|
||||
overwrite(doc, `aggs.pivot.aggs.${column.id}.filter.match_all`, {});
|
||||
}
|
||||
});
|
||||
|
||||
return next(doc);
|
||||
};
|
|
@ -6,33 +6,19 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { buildEsQuery } from '@kbn/es-query';
|
||||
import { overwrite } from '../../helpers';
|
||||
|
||||
import type { TableRequestProcessorsFunction } from './types';
|
||||
|
||||
export const splitByTerms: TableRequestProcessorsFunction = ({
|
||||
panel,
|
||||
esQueryConfig,
|
||||
seriesIndex,
|
||||
}) => {
|
||||
const indexPattern = seriesIndex.indexPattern || undefined;
|
||||
|
||||
export const splitByTerms: TableRequestProcessorsFunction = ({ panel }) => {
|
||||
return (next) => (doc) => {
|
||||
panel.series
|
||||
.filter((c) => c.aggregate_by && c.aggregate_function)
|
||||
.forEach((column) => {
|
||||
overwrite(doc, `aggs.pivot.aggs.${column.id}.terms.field`, column.aggregate_by);
|
||||
overwrite(doc, `aggs.pivot.aggs.${column.id}.terms.size`, 100);
|
||||
|
||||
if (column.filter) {
|
||||
overwrite(
|
||||
doc,
|
||||
`aggs.pivot.aggs.${column.id}.column_filter.filter`,
|
||||
buildEsQuery(indexPattern, [column.filter], [], esQueryConfig)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return next(doc);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -32,6 +32,7 @@ export interface TableRequestProcessorsParams {
|
|||
export interface TableSearchRequestMeta extends BaseMeta {
|
||||
panelId?: string;
|
||||
timeField?: string;
|
||||
normalized?: boolean;
|
||||
}
|
||||
|
||||
export type TableSearchRequest = Record<string, any>;
|
||||
|
|
|
@ -14,13 +14,13 @@ import { dropLastBucket } from '../series/drop_last_bucket';
|
|||
import type { TableResponseProcessorsFunction } from './types';
|
||||
|
||||
export const dropLastBucketFn: TableResponseProcessorsFunction =
|
||||
({ bucket, panel, series }) =>
|
||||
({ response, panel, series }) =>
|
||||
(next) =>
|
||||
(results) => {
|
||||
const shouldDropLastBucket = isLastValueTimerangeMode(panel);
|
||||
|
||||
if (shouldDropLastBucket) {
|
||||
const fn = dropLastBucket({ aggregations: bucket }, panel, series);
|
||||
const fn = dropLastBucket(response, panel, series);
|
||||
|
||||
return fn(next)(results);
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ import { mathAgg } from '../series/math';
|
|||
import type { TableResponseProcessorsFunction } from './types';
|
||||
|
||||
export const math: TableResponseProcessorsFunction =
|
||||
({ bucket, panel, series, meta, extractFields }) =>
|
||||
({ response, panel, series, meta, extractFields }) =>
|
||||
(next) =>
|
||||
(results) => {
|
||||
const mathFn = mathAgg({ aggregations: bucket }, panel, series, meta, extractFields);
|
||||
const mathFn = mathAgg(response, panel, series, meta, extractFields);
|
||||
return mathFn(next)(results);
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@ import type { TableResponseProcessorsFunction } from './types';
|
|||
import type { PanelDataArray } from '../../../../../common/types/vis_data';
|
||||
|
||||
export const percentile: TableResponseProcessorsFunction =
|
||||
({ bucket, panel, series, meta, extractFields }) =>
|
||||
({ response, panel, series, meta, extractFields }) =>
|
||||
(next) =>
|
||||
async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
@ -24,11 +24,7 @@ export const percentile: TableResponseProcessorsFunction =
|
|||
return next(results);
|
||||
}
|
||||
|
||||
const fakeResp = {
|
||||
aggregations: bucket,
|
||||
};
|
||||
|
||||
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
(await getSplits(response, panel, series, meta, extractFields)).forEach((split) => {
|
||||
// table allows only one percentile in a series (the last one will be chosen in case of several)
|
||||
const lastPercentile = last(metric.percentiles)?.value ?? 0;
|
||||
const percentileKey = toPercentileNumber(lastPercentile);
|
||||
|
|
|
@ -15,7 +15,7 @@ import type { TableResponseProcessorsFunction } from './types';
|
|||
import type { PanelDataArray } from '../../../../../common/types/vis_data';
|
||||
|
||||
export const percentileRank: TableResponseProcessorsFunction =
|
||||
({ bucket, panel, series, meta, extractFields }) =>
|
||||
({ response, panel, series, meta, extractFields }) =>
|
||||
(next) =>
|
||||
async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
@ -24,11 +24,7 @@ export const percentileRank: TableResponseProcessorsFunction =
|
|||
return next(results);
|
||||
}
|
||||
|
||||
const fakeResp = {
|
||||
aggregations: bucket,
|
||||
};
|
||||
|
||||
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
(await getSplits(response, panel, series, meta, extractFields)).forEach((split) => {
|
||||
// table allows only one percentile rank in a series (the last one will be chosen in case of several)
|
||||
const lastRankValue = last(metric.values) ?? 0;
|
||||
const lastPercentileNumber = toPercentileNumber(lastRankValue);
|
||||
|
|
|
@ -44,5 +44,6 @@ export const seriesAgg: TableResponseProcessorsFunction =
|
|||
data: data[0],
|
||||
});
|
||||
}
|
||||
|
||||
return next(results);
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ import { TSVB_METRIC_TYPES } from '../../../../../common/enums';
|
|||
import type { TableResponseProcessorsFunction } from './types';
|
||||
|
||||
export const stdMetric: TableResponseProcessorsFunction =
|
||||
({ bucket, panel, series, meta, extractFields }) =>
|
||||
({ response, panel, series, meta, extractFields }) =>
|
||||
(next) =>
|
||||
async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
@ -33,13 +33,8 @@ export const stdMetric: TableResponseProcessorsFunction =
|
|||
return next(results);
|
||||
}
|
||||
|
||||
const fakeResp = {
|
||||
aggregations: bucket,
|
||||
};
|
||||
|
||||
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
(await getSplits(response, panel, series, meta, extractFields)).forEach((split) => {
|
||||
const data = mapEmptyToZero(metric, split.timeseries.buckets);
|
||||
|
||||
results.push({
|
||||
id: split.id,
|
||||
label: split.label,
|
||||
|
|
|
@ -12,7 +12,7 @@ import type { TableResponseProcessorsFunction } from './types';
|
|||
import type { PanelDataArray } from '../../../../../common/types/vis_data';
|
||||
|
||||
export const stdSibling: TableResponseProcessorsFunction =
|
||||
({ bucket, panel, series, meta, extractFields }) =>
|
||||
({ response, panel, series, meta, extractFields }) =>
|
||||
(next) =>
|
||||
async (results) => {
|
||||
const metric = getLastMetric(series);
|
||||
|
@ -20,8 +20,7 @@ export const stdSibling: TableResponseProcessorsFunction =
|
|||
if (!/_bucket$/.test(metric.type)) return next(results);
|
||||
if (metric.type === 'std_deviation_bucket' && metric.mode === 'band') return next(results);
|
||||
|
||||
const fakeResp = { aggregations: bucket };
|
||||
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
|
||||
(await getSplits(response, panel, series, meta, extractFields)).forEach((split) => {
|
||||
const data: PanelDataArray[] = split.timeseries.buckets.map((b) => {
|
||||
return [b.key, getSiblingAggValue(split, metric)];
|
||||
});
|
||||
|
|
|
@ -12,7 +12,9 @@ import type { TableSearchRequestMeta } from '../../request_processors/table/type
|
|||
import type { Panel, Series, PanelData } from '../../../../../common/types';
|
||||
|
||||
export interface TableResponseProcessorsParams {
|
||||
bucket: Record<string, unknown>;
|
||||
response: {
|
||||
aggregations: Record<string, any>;
|
||||
};
|
||||
panel: Panel;
|
||||
series: Series;
|
||||
meta: TableSearchRequestMeta;
|
||||
|
|
|
@ -159,6 +159,7 @@ describe('buildRequestBody(req)', () => {
|
|||
},
|
||||
meta: {
|
||||
intervalString: '10s',
|
||||
normalized: true,
|
||||
seriesId: 'c9b5f9c0-e403-11e6-be91-6f7688e9fac7',
|
||||
timeField: '@timestamp',
|
||||
panelId: 'c9b5d2b0-e403-11e6-be91-6f7688e9fac7',
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
query,
|
||||
pivot,
|
||||
splitByTerms,
|
||||
splitByEverything,
|
||||
applyFilters,
|
||||
dateHistogram,
|
||||
metricBuckets,
|
||||
siblingBuckets,
|
||||
|
@ -36,12 +36,12 @@ export function buildTableRequest(params: TableRequestProcessorsParams) {
|
|||
query,
|
||||
pivot,
|
||||
splitByTerms,
|
||||
splitByEverything,
|
||||
dateHistogram,
|
||||
metricBuckets,
|
||||
siblingBuckets,
|
||||
filterRatios,
|
||||
positiveRate,
|
||||
applyFilters,
|
||||
normalizeQuery,
|
||||
],
|
||||
params
|
||||
|
|
|
@ -62,7 +62,9 @@ function createBuckets(series: string[]) {
|
|||
timeField: 'timestamp',
|
||||
seriesId,
|
||||
},
|
||||
buckets: createBucketsObjects(size, trend, seriesId),
|
||||
timeseries: {
|
||||
buckets: createBucketsObjects(size, trend, seriesId),
|
||||
},
|
||||
};
|
||||
}
|
||||
return baseObj;
|
||||
|
@ -113,11 +115,13 @@ describe('processBucket(panel)', () => {
|
|||
timeField: 'timestamp',
|
||||
seriesId: SERIES_ID,
|
||||
},
|
||||
buckets: [
|
||||
// this is a flat case, but 0/0 has not a valid number result
|
||||
createValueObject(0, 0, SERIES_ID),
|
||||
createValueObject(1, 0, SERIES_ID),
|
||||
],
|
||||
timeseries: {
|
||||
buckets: [
|
||||
// this is a flat case, but 0/0 has not a valid number result
|
||||
createValueObject(0, 0, SERIES_ID),
|
||||
createValueObject(1, 0, SERIES_ID),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await bucketProcessor(bucketforNaNResult);
|
||||
|
|
|
@ -13,8 +13,9 @@ import { buildTableResponse } from './build_response_body';
|
|||
import { createFieldsFetcher } from '../../search_strategies/lib/fields_fetcher';
|
||||
|
||||
import type { Panel } from '../../../../common/types';
|
||||
import type { PanelDataArray } from '../../../../common/types/vis_data';
|
||||
import type { TableSearchRequestMeta } from '../request_processors/table/types';
|
||||
import { PanelDataArray } from '../../../../common/types/vis_data';
|
||||
import type { TableResponseProcessorsParams } from '../response_processors/table/types';
|
||||
|
||||
function trendSinceLastBucket(data: PanelDataArray[]) {
|
||||
if (data.length < 2) {
|
||||
|
@ -36,23 +37,22 @@ export function processBucket({ panel, extractFields }: ProcessTableBucketParams
|
|||
return async (bucket: Record<string, unknown>) => {
|
||||
const resultSeries = await Promise.all(
|
||||
getActiveSeries(panel).map(async (series) => {
|
||||
const timeseries = get(bucket, `${series.id}.timeseries`);
|
||||
const buckets = get(bucket, `${series.id}.buckets`);
|
||||
let meta: TableSearchRequestMeta = {};
|
||||
const response: TableResponseProcessorsParams['response'] = {
|
||||
aggregations: {
|
||||
[series.id]: get(bucket, `${series.id}`),
|
||||
},
|
||||
};
|
||||
const meta = (response.aggregations[series.id]?.meta ?? {}) as TableSearchRequestMeta;
|
||||
|
||||
if (!timeseries && buckets) {
|
||||
meta = get(bucket, `${series.id}.meta`) as TableSearchRequestMeta;
|
||||
|
||||
overwrite(bucket, series.id, {
|
||||
meta,
|
||||
timeseries: {
|
||||
buckets: get(bucket, `${series.id}.buckets`),
|
||||
},
|
||||
if (meta.normalized && !get(response, `aggregations.${series.id}.timeseries`)) {
|
||||
overwrite(response, `aggregations.${series.id}.timeseries`, {
|
||||
buckets: get(bucket, `${series.id}.buckets`),
|
||||
});
|
||||
delete response.aggregations[series.id].buckets;
|
||||
}
|
||||
|
||||
const [result] = await buildTableResponse({
|
||||
bucket,
|
||||
response,
|
||||
panel,
|
||||
series,
|
||||
meta,
|
||||
|
|
|
@ -122,7 +122,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const error = await visualBuilder.getVisualizeError();
|
||||
|
||||
expect(error).to.eql(
|
||||
'The aggregation "derivative" is not supported in entire_time_range mode'
|
||||
'The "derivative" aggregation is not supported for existing panel configuration.'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -208,7 +208,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const error = await visualBuilder.getVisualizeError();
|
||||
|
||||
expect(error).to.eql(
|
||||
'The aggregation "derivative" is not supported in entire_time_range mode'
|
||||
'The "derivative" aggregation is not supported for existing panel configuration.'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -362,7 +362,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const error = await visualBuilder.getVisualizeError();
|
||||
|
||||
expect(error).to.eql(
|
||||
'The aggregation "derivative" is not supported in entire_time_range mode'
|
||||
'The "derivative" aggregation is not supported for existing panel configuration.'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -731,7 +731,7 @@ export class VisualBuilderPageObject extends FtrService {
|
|||
|
||||
public async checkPreviewIsDisabled(): Promise<void> {
|
||||
this.log.debug(`Check no data message is present`);
|
||||
await this.testSubjects.existOrFail('timeseriesVis > visNoResult', { timeout: 5000 });
|
||||
await this.testSubjects.existOrFail('visualization-error', { timeout: 5000 });
|
||||
}
|
||||
|
||||
public async cloneSeries(nth: number = 0): Promise<void> {
|
||||
|
|
|
@ -4682,7 +4682,6 @@
|
|||
"visTypeTimeseries.table.labelPlaceholder": "ラベル",
|
||||
"visTypeTimeseries.table.maxLabel": "最高",
|
||||
"visTypeTimeseries.table.minLabel": "最低",
|
||||
"visTypeTimeseries.table.noResultsAvailableMessage": "結果がありません。",
|
||||
"visTypeTimeseries.table.noResultsAvailableWithDescriptionMessage": "結果がありません。このビジュアライゼーションは、フィールドでグループを選択する必要があります。",
|
||||
"visTypeTimeseries.table.optionsTab.dataLabel": "データ",
|
||||
"visTypeTimeseries.table.optionsTab.ignoreGlobalFilterLabel": "グローバルフィルターを無視しますか?",
|
||||
|
@ -4816,7 +4815,6 @@
|
|||
"visTypeTimeseries.visPicker.tableLabel": "表",
|
||||
"visTypeTimeseries.visPicker.timeSeriesLabel": "時系列",
|
||||
"visTypeTimeseries.visPicker.topNLabel": "トップ N",
|
||||
"visTypeTimeseries.wrongAggregationErrorMessage": "アグリゲーション{metricType}は{timeRangeMode}モードでサポートされていません",
|
||||
"visTypeTimeseries.yesButtonLabel": "はい",
|
||||
"visTypeVega.editor.formatError": "仕様のフォーマット中にエラーが発生",
|
||||
"visTypeVega.editor.reformatAsHJSONButtonLabel": "HJSON に変換",
|
||||
|
|
|
@ -4715,7 +4715,6 @@
|
|||
"visTypeTimeseries.table.labelPlaceholder": "标签",
|
||||
"visTypeTimeseries.table.maxLabel": "最大值",
|
||||
"visTypeTimeseries.table.minLabel": "最小值",
|
||||
"visTypeTimeseries.table.noResultsAvailableMessage": "没有可用结果。",
|
||||
"visTypeTimeseries.table.noResultsAvailableWithDescriptionMessage": "没有可用结果。必须为此可视化选择分组依据字段。",
|
||||
"visTypeTimeseries.table.optionsTab.dataLabel": "数据",
|
||||
"visTypeTimeseries.table.optionsTab.ignoreGlobalFilterLabel": "忽略全局筛选?",
|
||||
|
@ -4849,7 +4848,6 @@
|
|||
"visTypeTimeseries.visPicker.tableLabel": "表",
|
||||
"visTypeTimeseries.visPicker.timeSeriesLabel": "时间序列",
|
||||
"visTypeTimeseries.visPicker.topNLabel": "排名前 N",
|
||||
"visTypeTimeseries.wrongAggregationErrorMessage": "{timeRangeMode} 模式下不支持聚合 {metricType}",
|
||||
"visTypeTimeseries.yesButtonLabel": "是",
|
||||
"visTypeVega.editor.formatError": "格式化规范时出错",
|
||||
"visTypeVega.editor.reformatAsHJSONButtonLabel": "重新格式化为 HJSON",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue