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