diff --git a/src/legacy/core_plugins/metrics/common/timerange_data_modes.js b/src/legacy/core_plugins/metrics/common/timerange_data_modes.js
new file mode 100644
index 00000000000000..7d69d36f213b7d
--- /dev/null
+++ b/src/legacy/core_plugins/metrics/common/timerange_data_modes.js
@@ -0,0 +1,44 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Time Range data modes.
+ * @constant
+ * @public
+ */
+export const TIME_RANGE_DATA_MODES = {
+ /**
+ * Entire timerange mode will match all the documents selected in the
+ * timerange timepicker
+ */
+ ENTIRE_TIME_RANGE: 'entire_time_range',
+
+ /**
+ * Last value mode will match only the documents for the specified interval
+ * from the end of the timerange.
+ */
+ LAST_VALUE: 'last_value',
+};
+
+/**
+ * Key for getting the Time Range mode from the Panel configuration object.
+ * @constant
+ * @public
+ */
+export const TIME_RANGE_MODE_KEY = 'time_range_mode';
diff --git a/src/legacy/core_plugins/metrics/common/ui_restrictions.js b/src/legacy/core_plugins/metrics/common/ui_restrictions.js
new file mode 100644
index 00000000000000..96726d51e4a7c9
--- /dev/null
+++ b/src/legacy/core_plugins/metrics/common/ui_restrictions.js
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * UI Restrictions keys
+ * @constant
+ * @public
+ */
+export const RESTRICTIONS_KEYS = {
+ /**
+ * Key for getting the white listed group by fields from the UIRestrictions object.
+ */
+ WHITE_LISTED_GROUP_BY_FIELDS: 'whiteListedGroupByFields',
+
+ /**
+ * Key for getting the white listed metrics from the UIRestrictions object.
+ */
+ WHITE_LISTED_METRICS: 'whiteListedMetrics',
+
+ /**
+ * Key for getting the white listed Time Range modes from the UIRestrictions object.
+ */
+ WHITE_LISTED_TIMERANGE_MODES: 'whiteListedTimerangeModes',
+};
+
+/**
+ * Default value for the UIRestriction
+ * @constant
+ * @public
+ */
+export const DEFAULT_UI_RESTRICTION = {
+ '*': true,
+};
diff --git a/src/legacy/core_plugins/metrics/public/components/index_pattern.js b/src/legacy/core_plugins/metrics/public/components/index_pattern.js
index 79f41357d07edb..a7a7d14ea9940d 100644
--- a/src/legacy/core_plugins/metrics/public/components/index_pattern.js
+++ b/src/legacy/core_plugins/metrics/public/components/index_pattern.js
@@ -25,11 +25,9 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
- EuiFormLabel,
- EuiSpacer,
+ EuiComboBox,
+ EuiText,
} from '@elastic/eui';
-import { FormattedMessage } from '@kbn/i18n/react';
-
import { FieldSelect } from './aggs/field_select';
import { createSelectHandler } from './lib/create_select_handler';
import { createTextHandler } from './lib/create_text_handler';
@@ -42,6 +40,11 @@ import {
isAutoInterval,
AUTO_INTERVAL,
} from './lib/get_interval';
+import { i18n } from '@kbn/i18n';
+import { TIME_RANGE_DATA_MODES, TIME_RANGE_MODE_KEY } from '../../common/timerange_data_modes';
+import { PANEL_TYPES } from '../../common/panel_types';
+import { isTimerangeModeEnabled } from '../lib/check_ui_restrictions';
+import { UIRestrictionsContext } from '../contexts/ui_restriction_context';
const RESTRICT_FIELDS = [ES_TYPES.DATE];
@@ -56,75 +59,126 @@ const validateIntervalValue = intervalValue => {
return validateReInterval(intervalValue);
};
-export const IndexPattern = props => {
- const { fields, prefix } = props;
- const handleSelectChange = createSelectHandler(props.onChange);
- const handleTextChange = createTextHandler(props.onChange);
+const htmlId = htmlIdGenerator();
+
+const isEntireTimeRangeActive = (model, isTimeSeries) =>
+ !isTimeSeries && model[TIME_RANGE_MODE_KEY] === TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE;
+
+export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model }) => {
+ const handleSelectChange = createSelectHandler(onChange);
+ const handleTextChange = createTextHandler(onChange);
const timeFieldName = `${prefix}time_field`;
const indexPatternName = `${prefix}index_pattern`;
const intervalName = `${prefix}interval`;
const dropBucketName = `${prefix}drop_last_bucket`;
const updateControlValidity = useContext(FormValidationContext);
+ const uiRestrictions = useContext(UIRestrictionsContext);
+
+ const timeRangeOptions = [
+ {
+ label: i18n.translate('tsvb.indexPattern.timeRange.lastValue', {
+ defaultMessage: 'Last value',
+ }),
+ value: TIME_RANGE_DATA_MODES.LAST_VALUE,
+ disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.LAST_VALUE, uiRestrictions),
+ },
+ {
+ label: i18n.translate('tsvb.indexPattern.timeRange.entireTimeRange', {
+ defaultMessage: 'Entire time range',
+ }),
+ value: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
+ disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE, uiRestrictions),
+ },
+ ];
const defaults = {
default_index_pattern: '',
[indexPatternName]: '*',
[intervalName]: AUTO_INTERVAL,
[dropBucketName]: 1,
+ [TIME_RANGE_MODE_KEY]: timeRangeOptions[0].value,
};
- const htmlId = htmlIdGenerator();
- const model = { ...defaults, ...props.model };
+ const model = { ...defaults, ..._model };
const isDefaultIndexPatternUsed = model.default_index_pattern && !model[indexPatternName];
const intervalValidation = validateIntervalValue(model[intervalName]);
+ const selectedTimeRangeOption = timeRangeOptions.find(
+ ({ value }) => model[TIME_RANGE_MODE_KEY] === value
+ );
+ const isTimeSeries = model.type === PANEL_TYPES.TIMESERIES;
updateControlValidity(intervalName, intervalValidation.isValid);
return (
-
-
+
+ {!isTimeSeries && (
+
+
+
+
+
+
+ {i18n.translate('tsvb.indexPattern.timeRange.hint', {
+ defaultMessage: `This setting controls the timespan used for matching documents.
+ "Entire timerange" will match all the documents selected in the timepicker.
+ "Last value" will match only the documents for the specified interval from the end of the timerange.`,
+ })}
+
+
+
+ )}
+
}
+ label={i18n.translate('tsvb.indexPatternLabel', { defaultMessage: 'Index pattern' })}
helpText={
- isDefaultIndexPatternUsed && (
-
- )
+ isDefaultIndexPatternUsed &&
+ i18n.translate('tsvb.indexPattern.searchByDefaultIndex', {
+ defaultMessage: 'Default index pattern is used. To query all indexes use *',
+ })
}
- fullWidth
>
- }
- fullWidth
+ label={i18n.translate('tsvb.indexPattern.timeFieldLabel', {
+ defaultMessage: 'Time field',
+ })}
>
@@ -133,20 +187,18 @@ export const IndexPattern = props => {
isInvalid={!intervalValidation.isValid}
error={intervalValidation.errorMessage}
id={htmlId('interval')}
- label={
-
- }
- helpText={
-
- }
+ label={i18n.translate('tsvb.indexPattern.intervalLabel', {
+ defaultMessage: 'Interval',
+ })}
+ helpText={i18n.translate('tsvb.indexPattern.intervalHelpText', {
+ defaultMessage: 'Examples: auto, 1m, 1d, 7d, 1y, >=1m',
+ description:
+ 'auto, 1m, 1d, 7d, 1y, >=1m are required values and must not be translated.',
+ })}
>
{
-
-
+
-
-
-
+
diff --git a/src/legacy/core_plugins/metrics/public/components/panel_config.js b/src/legacy/core_plugins/metrics/public/components/panel_config.js
index bd94a175dd977f..45da23b0830c29 100644
--- a/src/legacy/core_plugins/metrics/public/components/panel_config.js
+++ b/src/legacy/core_plugins/metrics/public/components/panel_config.js
@@ -18,6 +18,7 @@
*/
import PropTypes from 'prop-types';
import React, { useState, useEffect } from 'react';
+import { get } from 'lodash';
import { TimeseriesPanelConfig as timeseries } from './panel_config/timeseries';
import { MetricPanelConfig as metric } from './panel_config/metric';
import { TopNPanelConfig as topN } from './panel_config/top_n';
@@ -26,6 +27,7 @@ import { GaugePanelConfig as gauge } from './panel_config/gauge';
import { MarkdownPanelConfig as markdown } from './panel_config/markdown';
import { FormattedMessage } from '@kbn/i18n/react';
import { FormValidationContext } from '../contexts/form_validation_context';
+import { UIRestrictionsContext } from '../contexts/ui_restriction_context';
const types = {
timeseries,
@@ -43,11 +45,22 @@ export function PanelConfig(props) {
const { model } = props;
const Component = types[model.type];
const [formValidationResults] = useState({});
+ const [uiRestrictions, setUIRestrictions] = useState(null);
useEffect(() => {
model.isModelInvalid = !checkModelValidity(formValidationResults);
});
+ useEffect(() => {
+ const visDataSubscription = props.visData$.subscribe(visData =>
+ setUIRestrictions(get(visData, 'uiRestrictions', null))
+ );
+
+ return function cleanup() {
+ visDataSubscription.unsubscribe();
+ };
+ }, [props.visData$]);
+
const updateControlValidity = (controlKey, isControlValid) => {
formValidationResults[controlKey] = isControlValid;
};
@@ -55,7 +68,9 @@ export function PanelConfig(props) {
if (Component) {
return (
-
+
+
+
);
}
diff --git a/src/legacy/core_plugins/metrics/public/components/series.js b/src/legacy/core_plugins/metrics/public/components/series.js
index 74f7e49f3fbbef..a4963db09e1153 100644
--- a/src/legacy/core_plugins/metrics/public/components/series.js
+++ b/src/legacy/core_plugins/metrics/public/components/series.js
@@ -19,7 +19,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
-import { assign, get } from 'lodash';
+import { assign } from 'lodash';
import { TimeseriesSeries as timeseries } from './vis_types/timeseries/series';
import { MetricSeries as metric } from './vis_types/metric/series';
@@ -39,17 +39,10 @@ const lookup = {
};
export class Series extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- visible: true,
- selectedTab: 'metrics',
- uiRestrictions: undefined,
- };
-
- this.visDataSubscription = null;
- }
+ state = {
+ visible: true,
+ selectedTab: 'metrics',
+ };
switchTab = selectedTab => {
this.setState({ selectedTab });
@@ -79,16 +72,6 @@ export class Series extends Component {
});
};
- componentDidMount() {
- if (this.props.visData$) {
- this.visDataSubscription = this.props.visData$.subscribe(visData =>
- this.setState({
- uiRestrictions: get(visData, 'uiRestrictions'),
- })
- );
- }
- }
-
render() {
const { panel } = this.props;
const Component = lookup[panel.type];
@@ -107,7 +90,6 @@ export class Series extends Component {
panel: this.props.panel,
selectedTab: this.state.selectedTab,
style: this.props.style,
- uiRestrictions: this.state.uiRestrictions,
switchTab: this.switchTab,
toggleVisible: this.toggleVisible,
togglePanelActivation: this.togglePanelActivation,
@@ -125,12 +107,6 @@ export class Series extends Component {
/>
);
}
-
- componentWillUnmount() {
- if (this.visDataSubscription) {
- this.visDataSubscription.unsubscribe();
- }
- }
}
Series.defaultProps = {
diff --git a/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/series.js b/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/series.js
index e53750cc01b217..43e1ac9f0eda56 100644
--- a/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/series.js
+++ b/src/legacy/core_plugins/metrics/public/components/vis_types/timeseries/series.js
@@ -35,6 +35,7 @@ import {
import { Split } from '../../split';
import { createTextHandler } from '../../lib/create_text_handler';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
+import { PANEL_TYPES } from '../../../../common/panel_types';
const TimeseriesSeriesUI = injectI18n(function(props) {
const {
@@ -51,7 +52,11 @@ const TimeseriesSeriesUI = injectI18n(function(props) {
name,
uiRestrictions,
} = props;
- const defaults = { label: '' };
+
+ const defaults = {
+ label: '',
+ type: PANEL_TYPES.TIMESERIES,
+ };
const model = { ...defaults, ...props.model };
const handleChange = createTextHandler(onChange);
@@ -90,7 +95,7 @@ const TimeseriesSeriesUI = injectI18n(function(props) {
seriesBody = (
diff --git a/src/legacy/core_plugins/metrics/public/components/yes_no.js b/src/legacy/core_plugins/metrics/public/components/yes_no.js
index 3a3ca75212ddd5..63892ac80d85ea 100644
--- a/src/legacy/core_plugins/metrics/public/components/yes_no.js
+++ b/src/legacy/core_plugins/metrics/public/components/yes_no.js
@@ -24,7 +24,7 @@ import { EuiRadio, htmlIdGenerator } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
export function YesNo(props) {
- const { name, value } = props;
+ const { name, value, disabled } = props;
const handleChange = value => {
const { name } = props;
return () => {
@@ -50,6 +50,7 @@ export function YesNo(props) {
checked={Boolean(value)}
value="yes"
onChange={handleChange(1)}
+ disabled={disabled}
/>
);
@@ -75,3 +77,7 @@ YesNo.propTypes = {
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};
+
+YesNo.defaultProps = {
+ disabled: false,
+};
diff --git a/src/legacy/core_plugins/metrics/public/contexts/ui_restriction_context.js b/src/legacy/core_plugins/metrics/public/contexts/ui_restriction_context.js
new file mode 100644
index 00000000000000..24860b329e3043
--- /dev/null
+++ b/src/legacy/core_plugins/metrics/public/contexts/ui_restriction_context.js
@@ -0,0 +1,22 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+
+export const UIRestrictionsContext = React.createContext();
diff --git a/src/legacy/core_plugins/metrics/public/lib/check_ui_restrictions.js b/src/legacy/core_plugins/metrics/public/lib/check_ui_restrictions.js
index b233d96988d253..1fade6ebe98495 100644
--- a/src/legacy/core_plugins/metrics/public/lib/check_ui_restrictions.js
+++ b/src/legacy/core_plugins/metrics/public/lib/check_ui_restrictions.js
@@ -18,37 +18,72 @@
*/
import { get } from 'lodash';
+import { RESTRICTIONS_KEYS, DEFAULT_UI_RESTRICTION } from '../../common/ui_restrictions';
-export const DEFAULT_UI_RESTRICTION = {
- '*': true,
-};
-
-const RESTRICTION_TYPES = {
- WHITE_LISTED_GROUP_BY_FIELDS: 'whiteListedGroupByFields',
- WHITE_LISTER_METRICS: 'whiteListedMetrics',
-};
-
+/**
+ * Generic method for checking all types of the UI Restrictions
+ * @private
+ */
const checkUIRestrictions = (key, restrictions = DEFAULT_UI_RESTRICTION, type) => {
const isAllEnabled = get(restrictions, `${type}.*`, true);
- return isAllEnabled || get(restrictions, type, {})[key];
+ return isAllEnabled || Boolean(get(restrictions, type, {})[key]);
};
+/**
+ * Using this method, you can check whether a specific Metric (Aggregation) is allowed
+ * for current panel configuration or not.
+ * @public
+ * @param key - string value of Metric (Aggregation).
+ * @param restrictions - uiRestrictions object. Comes from the /data request.
+ * @return {boolean}
+ */
export const isMetricEnabled = (key, restrictions) => {
- return checkUIRestrictions(key, restrictions, RESTRICTION_TYPES.WHITE_LISTER_METRICS);
+ return checkUIRestrictions(key, restrictions, RESTRICTIONS_KEYS.WHITE_LISTED_METRICS);
};
+/**
+ * Using this method, you can check whether a specific Field is allowed
+ * for Metric (aggregation) or not.
+ * @public
+ * @param field - string value of data Field.
+ * @param metricType - string value of Metric (Aggregation).
+ * @param restrictions - uiRestrictions object. Comes from the /data request.
+ * @return {boolean}
+ */
export const isFieldEnabled = (field, metricType, restrictions = DEFAULT_UI_RESTRICTION) => {
if (isMetricEnabled(metricType, restrictions)) {
return checkUIRestrictions(
field,
- restrictions[RESTRICTION_TYPES.WHITE_LISTER_METRICS],
+ restrictions[RESTRICTIONS_KEYS.WHITE_LISTED_METRICS],
metricType
);
}
return false;
};
+/**
+ * Using this method, you can check whether a specific Group By mode is allowed
+ * for current panel configuration or not.
+ * @public
+ * @param key - string value of Group by mode.
+ * All available mode you can find in the following object SPLIT_MODES.
+ * @param restrictions - uiRestrictions object. Comes from the /data request.
+ * @return {boolean}
+ */
export const isGroupByFieldsEnabled = (key, restrictions) => {
- return checkUIRestrictions(key, restrictions, RESTRICTION_TYPES.WHITE_LISTED_GROUP_BY_FIELDS);
+ return checkUIRestrictions(key, restrictions, RESTRICTIONS_KEYS.WHITE_LISTED_GROUP_BY_FIELDS);
+};
+
+/**
+ * Using this method, you can check whether a specific time range 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 isTimerangeModeEnabled = (key, restrictions) => {
+ return checkUIRestrictions(key, restrictions, RESTRICTIONS_KEYS.WHITE_LISTED_TIMERANGE_MODES);
};
diff --git a/src/legacy/core_plugins/metrics/server/lib/search_strategies/default_search_capabilities.js b/src/legacy/core_plugins/metrics/server/lib/search_strategies/default_search_capabilities.js
index 39c86f7901203a..1e693ae287b274 100644
--- a/src/legacy/core_plugins/metrics/server/lib/search_strategies/default_search_capabilities.js
+++ b/src/legacy/core_plugins/metrics/server/lib/search_strategies/default_search_capabilities.js
@@ -21,6 +21,7 @@ import {
parseInterval,
getSuitableUnit,
} from '../vis_data/helpers/unit_to_seconds';
+import { RESTRICTIONS_KEYS } from '../../../common/ui_restrictions';
const getTimezoneFromRequest = request => {
return request.payload.timerange.timezone;
@@ -44,10 +45,15 @@ export class DefaultSearchCapabilities {
return this.createUiRestriction();
}
+ get whiteListedTimerangeModes() {
+ return this.createUiRestriction();
+ }
+
get uiRestrictions() {
return {
- whiteListedMetrics: this.whiteListedMetrics,
- whiteListedGroupByFields: this.whiteListedGroupByFields,
+ [RESTRICTIONS_KEYS.WHITE_LISTED_METRICS]: this.whiteListedMetrics,
+ [RESTRICTIONS_KEYS.WHITE_LISTED_GROUP_BY_FIELDS]: this.whiteListedGroupByFields,
+ [RESTRICTIONS_KEYS.WHITE_LISTED_TIMERANGE_MODES]: this.whiteListedTimerangeModes,
};
}
diff --git a/src/legacy/core_plugins/metrics/server/lib/search_strategies/default_search_capabilities.test.js b/src/legacy/core_plugins/metrics/server/lib/search_strategies/default_search_capabilities.test.js
index 5be38b7cce9271..b9b7759711567c 100644
--- a/src/legacy/core_plugins/metrics/server/lib/search_strategies/default_search_capabilities.test.js
+++ b/src/legacy/core_plugins/metrics/server/lib/search_strategies/default_search_capabilities.test.js
@@ -36,6 +36,14 @@ describe('DefaultSearchCapabilities', () => {
expect(defaultSearchCapabilities.defaultTimeInterval).toBe(null);
});
+ test('should return default uiRestrictions', () => {
+ expect(defaultSearchCapabilities.uiRestrictions).toEqual({
+ whiteListedMetrics: { '*': true },
+ whiteListedGroupByFields: { '*': true },
+ whiteListedTimerangeModes: { '*': true },
+ });
+ });
+
test('should return Search Timezone', () => {
defaultSearchCapabilities.request = {
payload: {
diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_bucket_size.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_bucket_size.js
index f2e1a15ce68a31..de23b90b21d6c0 100644
--- a/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_bucket_size.js
+++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_bucket_size.js
@@ -18,13 +18,13 @@
*/
import { calculateAuto } from './calculate_auto';
-import moment from 'moment';
import {
getUnitValue,
parseInterval,
convertIntervalToUnit,
ASCENDING_UNIT_ORDER,
} from './unit_to_seconds';
+import { getTimerangeDuration } from '../helpers/get_timerange';
import { INTERVAL_STRING_RE, GTE_INTERVAL_RE } from '../../../../common/interval_regexp';
const calculateBucketData = (timeInterval, capabilities) => {
@@ -61,16 +61,14 @@ const calculateBucketData = (timeInterval, capabilities) => {
};
};
-const getTimeRangeBucketSize = ({ min, max }) => {
- const from = moment.utc(min);
- const to = moment.utc(max);
- const duration = moment.duration(to.valueOf() - from.valueOf(), 'ms');
+const calculateBucketSizeForAutoInterval = req => {
+ const duration = getTimerangeDuration(req);
return calculateAuto.near(100, duration).asSeconds();
};
export const getBucketSize = (req, interval, capabilities) => {
- const bucketSize = getTimeRangeBucketSize(req.payload.timerange);
+ const bucketSize = calculateBucketSizeForAutoInterval(req);
let intervalString = `${bucketSize}s`;
const gteAutoMatch = Boolean(interval) && interval.match(GTE_INTERVAL_RE);
diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_timerange.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_timerange.js
index 2c6a41ab1b9682..4141c76f02464b 100644
--- a/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_timerange.js
+++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_timerange.js
@@ -19,8 +19,17 @@
import moment from 'moment';
-export function getTimerange(req) {
- const from = moment.utc(req.payload.timerange.min);
- const to = moment.utc(req.payload.timerange.max);
- return { from, to };
-}
+export const getTimerange = req => {
+ const { min, max } = req.payload.timerange;
+
+ return {
+ from: moment.utc(min),
+ to: moment.utc(max),
+ };
+};
+
+export const getTimerangeDuration = req => {
+ const { from, to } = getTimerange(req);
+
+ return moment.duration(to.valueOf() - from.valueOf(), 'ms');
+};
diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_timerange_mode.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_timerange_mode.js
new file mode 100644
index 00000000000000..8a83116b032b40
--- /dev/null
+++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/get_timerange_mode.js
@@ -0,0 +1,78 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ TIME_RANGE_DATA_MODES,
+ TIME_RANGE_MODE_KEY,
+} from '../../../../common/timerange_data_modes';
+import { PANEL_TYPES } from '../../../../common/panel_types';
+
+const OVERRIDE_INDEX_PATTERN_KEY = 'override_index_pattern';
+
+/**
+ * Check if passed 'series' has overridden index pattern or not.
+ * @private
+ * @param series - specific series
+ * @return {boolean}
+ */
+const hasOverriddenIndexPattern = series => Boolean(series[OVERRIDE_INDEX_PATTERN_KEY]);
+
+/**
+ * Get value of Time Range Mode for panel
+ * @private
+ * @param panel - panel configuration
+ * @return {string} - value of TIME_RANGE_DATA_MODES type
+ */
+const getPanelTimeRangeMode = panel => panel[TIME_RANGE_MODE_KEY];
+
+/**
+ * Get value of Time Range Mode for series
+ * @private
+ * @param series - specific series
+ * @return {string} - value of TIME_RANGE_DATA_MODES type
+ */
+const getSeriesTimeRangeMode = series => series[TIME_RANGE_MODE_KEY];
+
+/**
+ * Check if 'Entire Time Range' mode active or not.
+ * @public
+ * @param panel - panel configuration
+ * @param series - specific series
+ * @return {boolean}
+ */
+export const isEntireTimeRangeMode = (panel, series = {}) => {
+ if (panel.type === PANEL_TYPES.TIMESERIES) {
+ return false;
+ }
+
+ const timeRangeMode = hasOverriddenIndexPattern(series)
+ ? getSeriesTimeRangeMode(series)
+ : getPanelTimeRangeMode(panel);
+
+ return timeRangeMode === TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE;
+};
+
+/**
+ * Check if 'Last Value Time Range' mode active or not.
+ * @public
+ * @param panel - panel configuration
+ * @param series - specific series
+ * @return {boolean}
+ */
+export const isLastValueTimerangeMode = (panel, series) => !isEntireTimeRangeMode(panel, series);
diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js
index f7d94fc57a5572..40eaba621aabb7 100644
--- a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js
+++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js
@@ -17,10 +17,10 @@
* under the License.
*/
-import { dateHistogram } from '../date_histogram';
import { expect } from 'chai';
import sinon from 'sinon';
import { DefaultSearchCapabilities } from '../../../../search_strategies/default_search_capabilities';
+import { dateHistogram } from '../date_histogram';
describe('dateHistogram(req, panel, series)', () => {
let panel;
@@ -163,4 +163,48 @@ describe('dateHistogram(req, panel, series)', () => {
},
});
});
+
+ describe('dateHistogram for entire time range mode', () => {
+ it('should ignore entire range mode for timeseries', () => {
+ panel.time_range_mode = 'entire_time_range';
+ panel.type = 'timeseries';
+
+ const next = doc => doc;
+ const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)(
+ {}
+ );
+
+ expect(doc.aggs.test.aggs.timeseries.auto_date_histogram).to.eql(undefined);
+ expect(doc.aggs.test.aggs.timeseries.date_histogram).to.exist;
+ });
+
+ it('should returns valid date histogram for entire range mode', () => {
+ panel.time_range_mode = 'entire_time_range';
+
+ const next = doc => doc;
+ const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)(
+ {}
+ );
+ expect(doc).to.eql({
+ aggs: {
+ test: {
+ aggs: {
+ timeseries: {
+ auto_date_histogram: {
+ field: '@timestamp',
+ buckets: 1,
+ },
+ },
+ },
+ meta: {
+ timeField: '@timestamp',
+ seriesId: 'test',
+ bucketSize: 10,
+ intervalString: '10s',
+ },
+ },
+ },
+ });
+ });
+ });
});
diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/date_histogram.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/date_histogram.js
index c2231cf982ea4c..a15b3a44e2686c 100644
--- a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/date_histogram.js
+++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/series/date_histogram.js
@@ -22,24 +22,41 @@ import { dateHistogramInterval } from '../../../../../../data/common';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { offsetTime } from '../../offset_time';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
+import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode';
export function dateHistogram(req, panel, series, esQueryConfig, indexPatternObject, capabilities) {
return next => doc => {
const { timeField, interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
const { bucketSize, intervalString } = getBucketSize(req, interval, capabilities);
- const { from, to } = offsetTime(req, series.offset_time);
- const timezone = capabilities.searchTimezone;
-
- set(doc, `aggs.${series.id}.aggs.timeseries.date_histogram`, {
- field: timeField,
- min_doc_count: 0,
- time_zone: timezone,
- extended_bounds: {
- min: from.valueOf(),
- max: to.valueOf(),
- },
- ...dateHistogramInterval(intervalString),
- });
+
+ const getDateHistogramForLastBucketMode = () => {
+ const { from, to } = offsetTime(req, series.offset_time);
+ const timezone = capabilities.searchTimezone;
+
+ set(doc, `aggs.${series.id}.aggs.timeseries.date_histogram`, {
+ field: timeField,
+ min_doc_count: 0,
+ time_zone: timezone,
+ extended_bounds: {
+ min: from.valueOf(),
+ max: to.valueOf(),
+ },
+ ...dateHistogramInterval(intervalString),
+ });
+ };
+
+ const getDateHistogramForEntireTimerangeMode = () =>
+ set(doc, `aggs.${series.id}.aggs.timeseries.auto_date_histogram`, {
+ field: timeField,
+ buckets: 1,
+ });
+
+ isLastValueTimerangeMode(panel, series)
+ ? getDateHistogramForLastBucketMode()
+ : getDateHistogramForEntireTimerangeMode();
+
+ // master
+
set(doc, `aggs.${series.id}.meta`, {
timeField,
intervalString,
diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/table/date_histogram.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/table/date_histogram.js
index c3d469d69a320f..e1b246a1f024c2 100644
--- a/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/table/date_histogram.js
+++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/request_processors/table/date_histogram.js
@@ -20,6 +20,7 @@
import { set } from 'lodash';
import { dateHistogramInterval } from '../../../../../../data/common';
import { getBucketSize } from '../../helpers/get_bucket_size';
+import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { getTimerange } from '../../helpers/get_timerange';
import { calculateAggRoot } from './calculate_agg_root';
@@ -27,29 +28,54 @@ import { calculateAggRoot } from './calculate_agg_root';
export function dateHistogram(req, panel, esQueryConfig, indexPatternObject, capabilities) {
return next => doc => {
const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
- const { bucketSize, intervalString } = getBucketSize(req, interval, capabilities);
- const { from, to } = getTimerange(req);
- const timezone = capabilities.searchTimezone;
-
- panel.series.forEach(column => {
- const aggRoot = calculateAggRoot(doc, column);
- set(doc, `${aggRoot}.timeseries.date_histogram`, {
- field: timeField,
- min_doc_count: 0,
- time_zone: timezone,
- extended_bounds: {
- min: from.valueOf(),
- max: to.valueOf(),
- },
- ...dateHistogramInterval(intervalString),
+ const meta = {
+ timeField,
+ };
+
+ const getDateHistogramForLastBucketMode = () => {
+ const { bucketSize, intervalString } = getBucketSize(req, interval, capabilities);
+ const { from, to } = getTimerange(req);
+ const timezone = capabilities.searchTimezone;
+
+ panel.series.forEach(column => {
+ const aggRoot = calculateAggRoot(doc, column);
+
+ set(doc, `${aggRoot}.timeseries.date_histogram`, {
+ field: timeField,
+ min_doc_count: 0,
+ time_zone: timezone,
+ extended_bounds: {
+ min: from.valueOf(),
+ max: to.valueOf(),
+ },
+ ...dateHistogramInterval(intervalString),
+ });
+
+ set(doc, aggRoot.replace(/\.aggs$/, '.meta'), {
+ timeField,
+ intervalString,
+ bucketSize,
+ });
});
+ };
- set(doc, aggRoot.replace(/\.aggs$/, '.meta'), {
- timeField,
- intervalString,
- bucketSize,
+ const getDateHistogramForEntireTimerangeMode = () => {
+ panel.series.forEach(column => {
+ const aggRoot = calculateAggRoot(doc, column);
+
+ set(doc, `${aggRoot}.timeseries.auto_date_histogram`, {
+ field: timeField,
+ buckets: 1,
+ });
+
+ set(doc, aggRoot.replace(/\.aggs$/, '.meta'), meta);
});
- });
+ };
+
+ isLastValueTimerangeMode(panel)
+ ? getDateHistogramForLastBucketMode()
+ : getDateHistogramForEntireTimerangeMode();
+
return next(doc);
};
}
diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/drop_last_bucket.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/drop_last_bucket.js
index 937e2ee8182da5..a4a7b01fa6f5eb 100644
--- a/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/drop_last_bucket.js
+++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/series/drop_last_bucket.js
@@ -18,15 +18,23 @@
*/
import { get } from 'lodash';
+import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode';
+
export function dropLastBucket(resp, panel, series) {
return next => results => {
- const seriesDropLastBucket = get(series, 'override_drop_last_bucket', 1);
- const dropLastBucket = get(panel, 'drop_last_bucket', seriesDropLastBucket);
- if (dropLastBucket) {
- results.forEach(item => {
- item.data = item.data.slice(0, item.data.length - 1);
- });
+ const shouldDropLastBucket = isLastValueTimerangeMode(panel, series);
+
+ if (shouldDropLastBucket) {
+ const seriesDropLastBucket = get(series, 'override_drop_last_bucket', 1);
+ const dropLastBucket = get(panel, 'drop_last_bucket', seriesDropLastBucket);
+
+ if (dropLastBucket) {
+ results.forEach(item => {
+ item.data = item.data.slice(0, item.data.length - 1);
+ });
+ }
}
+
return next(results);
};
}
diff --git a/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/table/drop_last_bucket.js b/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/table/drop_last_bucket.js
index f594e7b559bfe6..05269335790b3f 100644
--- a/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/table/drop_last_bucket.js
+++ b/src/legacy/core_plugins/metrics/server/lib/vis_data/response_processors/table/drop_last_bucket.js
@@ -18,10 +18,18 @@
*/
import { dropLastBucket } from '../series/drop_last_bucket';
+import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode';
export function dropLastBucketFn(bucket, panel, series) {
return next => results => {
- const fn = dropLastBucket({ aggregations: bucket }, panel, series);
- return fn(next)(results);
+ const shouldDropLastBucket = isLastValueTimerangeMode(panel);
+
+ if (shouldDropLastBucket) {
+ const fn = dropLastBucket({ aggregations: bucket }, panel, series);
+
+ return fn(next)(results);
+ }
+
+ return next(results);
};
}
diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.js b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.js
index bb9b125901c055..b3a2526daccb30 100644
--- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.js
+++ b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.js
@@ -58,6 +58,12 @@ export const getRollupSearchCapabilities = (DefaultSearchCapabilities) =>
});
}
+ get whiteListedTimerangeModes() {
+ return this.createUiRestriction({
+ last_value: true,
+ });
+ }
+
getValidTimeInterval(userIntervalString) {
const parsedRollupJobInterval = this.parseInterval(this.defaultTimeInterval);
const inRollupJobUnit = this.convertIntervalToUnit(userIntervalString, parsedRollupJobInterval.unit);