Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TSVB] Fix Rollup Search with auto interval for Rollup Jobs in calendar intervals #36081

Merged
merged 10 commits into from
May 24, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { relativeOptions } from '../../../../../ui/public/timepicker/relative_op

import { GTE_INTERVAL_RE, INTERVAL_STRING_RE } from '../../../common/interval_regexp';

export const AUTO_INTERVAL = 'auto';

export const unitLookup = {
s: i18n.translate('tsvb.getInterval.secondsLabel', { defaultMessage: 'seconds' }),
m: i18n.translate('tsvb.getInterval.minutesLabel', { defaultMessage: 'minutes' }),
Expand Down Expand Up @@ -53,7 +55,7 @@ export const isGteInterval = (interval) => GTE_INTERVAL_RE.test(interval);

export const isIntervalValid = (interval) => {
return isString(interval) &&
(interval === 'auto' || INTERVAL_STRING_RE.test(interval) || isGteInterval(interval));
(interval === AUTO_INTERVAL || INTERVAL_STRING_RE.test(interval) || isGteInterval(interval));
};

export const getInterval = (visData, model) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { get, isEqual } from 'lodash';
import { keyCodes, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } from '@elastic/eui';
import { getVisualizeLoader } from 'ui/visualize/loader/visualize_loader';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import { getInterval, convertIntervalIntoUnit, isIntervalValid, isGteInterval } from './lib/get_interval';
import { getInterval, convertIntervalIntoUnit, isIntervalValid, isGteInterval, AUTO_INTERVAL } from './lib/get_interval';
import { PANEL_TYPES } from '../../common/panel_types';

const MIN_CHART_HEIGHT = 250;
Expand Down Expand Up @@ -71,7 +71,7 @@ class VisEditorVisualization extends Component {
timeRange,
appState,
savedObj,
onDataChange
onDataChange,
} = this.props;

this._handler = loader.embedVisualizationWithSavedObject(this._visEl.current, savedObj, {
Expand Down Expand Up @@ -112,34 +112,26 @@ class VisEditorVisualization extends Component {
};
});
}
}
};

hasShowPanelIntervalValue() {
const type = get(this.props, 'model.type', '');
const interval = get(this.props, 'model.interval', AUTO_INTERVAL);

return [
PANEL_TYPES.METRIC,
PANEL_TYPES.TOP_N,
PANEL_TYPES.GAUGE,
PANEL_TYPES.MARKDOWN,
PANEL_TYPES.TABLE,
].includes(type);
].includes(type) && (interval === AUTO_INTERVAL
|| isGteInterval(interval) || !isIntervalValid(interval));
}

getFormattedPanelInterval() {
const interval = get(this.props, 'model.interval') || 'auto';
const isValid = isIntervalValid(interval);
const shouldShowActualInterval = interval === 'auto' || isGteInterval(interval);
const interval = convertIntervalIntoUnit(this.state.panelInterval, false);

if (shouldShowActualInterval || !isValid) {
const autoInterval = convertIntervalIntoUnit(this.state.panelInterval, false);

if (autoInterval) {
return `${autoInterval.unitValue}${autoInterval.unitString}`;
}
} else {
return interval;
}
return interval ? `${interval.unitValue}${interval.unitString}` : null;
}

componentWillUnmount() {
Expand Down Expand Up @@ -173,7 +165,7 @@ class VisEditorVisualization extends Component {
title,
description,
onToggleAutoApply,
onCommit
onCommit,
} = this.props;
const style = { height: this.state.height };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default function MetricsVisProvider(Private, i18n) {
}],
time_field: '@timestamp',
index_pattern: '',
interval: 'auto',
interval: '',
axis_position: 'left',
axis_formatter: 'number',
axis_scale: 'normal',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { convertIntervalToUnit, parseInterval } from '../vis_data/helpers/unit_to_seconds';
import { convertIntervalToUnit, parseInterval, getSuitableUnit } from '../vis_data/helpers/unit_to_seconds';

const getTimezoneFromRequest = request => {
return request.payload.timerange.timezone;
Expand Down Expand Up @@ -63,6 +63,10 @@ export class DefaultSearchCapabilities {
return parseInterval(interval);
}

getSuitableUnit(intervalInSeconds) {
return getSuitableUnit(intervalInSeconds);
}

convertIntervalToUnit(intervalString, unit) {
const parsedInterval = this.parseInterval(intervalString);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import calculateAuto from './calculate_auto';
import moment from 'moment';
import unitToSeconds from './unit_to_seconds';
import { getUnitValue } from './unit_to_seconds';
import {
INTERVAL_STRING_RE,
GTE_INTERVAL_RE,
Expand All @@ -29,7 +29,7 @@ const calculateBucketData = (timeInterval, capabilities) => {
const intervalString = capabilities ? capabilities.getValidTimeInterval(timeInterval) : timeInterval;
const intervalStringMatch = intervalString.match(INTERVAL_STRING_RE);

let bucketSize = Number(intervalStringMatch[1]) * unitToSeconds(intervalStringMatch[2]);
let bucketSize = Number(intervalStringMatch[1]) * getUnitValue(intervalStringMatch[2]);

// don't go too small
if (bucketSize < 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import getSplits from './get_splits';
import getTimerange from './get_timerange';
import mapBucket from './map_bucket';
import parseSettings from './parse_settings';
import unitToSeconds from './unit_to_seconds';

export default {
bucketTransform,
Expand All @@ -42,5 +41,4 @@ export default {
getTimerange,
mapBucket,
parseSettings,
unitToSeconds,
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/
import { INTERVAL_STRING_RE } from '../../../../common/interval_regexp';
import { sortBy, isNumber } from 'lodash';

const units = {
ms: 0.001,
Expand All @@ -29,28 +30,44 @@ const units = {
y: 86400 * 7 * 4 * 12, // Leap year?
};

const sortedUnits = sortBy(Object.keys(units), key => units[key]);

export const parseInterval = (intervalString) => {
let value;
let unit;

if (intervalString) {
const matches = intervalString.match(INTERVAL_STRING_RE);

value = Number(matches[1]);
unit = matches[2];
if (matches) {
value = Number(matches[1]);
unit = matches[2];
}
}

return { value, unit };
};

export const convertIntervalToUnit = (intervalString, unit) => {
export const convertIntervalToUnit = (intervalString, newUnit) => {
const parsedInterval = parseInterval(intervalString);
const value = Number((parsedInterval.value * units[parsedInterval.unit] / units[unit]).toFixed(2));
let value;
let unit;

if (parsedInterval.value && units[newUnit]) {
value = Number((parsedInterval.value * units[parsedInterval.unit] / units[newUnit]).toFixed(2));
unit = newUnit;
}

return { value, unit };
};

export default (unit) => {
return units[unit];
};
export const getSuitableUnit = intervalInSeconds => sortedUnits.find((key, index, array) => {
const nextUnit = array[index + 1];
const isValidInput = isNumber(intervalInSeconds) && intervalInSeconds > 0;
const isLastItem = index + 1 === array.length;

return isValidInput && (intervalInSeconds >= units[key] && intervalInSeconds < units[nextUnit] || isLastItem);
});

export const getUnitValue = unit => units[unit];

Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* 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 { getUnitValue, parseInterval, convertIntervalToUnit, getSuitableUnit } from './unit_to_seconds';

describe('unit_to_seconds', () => {
describe('parseInterval()', () => {
test('should parse "1m" interval (positive)', () =>
expect(parseInterval('1m')).toEqual({
value: 1,
unit: 'm',
}));

test('should parse "134d" interval (positive)', () =>
expect(parseInterval('134d')).toEqual({
value: 134,
unit: 'd',
}));

test('should parse "30M" interval (positive)', () =>
expect(parseInterval('30M')).toEqual({
value: 30,
unit: 'M',
}));

test('should not parse "gm" interval (negative)', () =>
expect(parseInterval('gm')).toEqual({
value: undefined,
unit: undefined,
}));

test('should not parse "-1d" interval (negative)', () =>
expect(parseInterval('-1d')).toEqual({
value: undefined,
unit: undefined,
}));

test('should not parse "M" interval (negative)', () =>
expect(parseInterval('M')).toEqual({
value: undefined,
unit: undefined,
}));
});

describe('convertIntervalToUnit()', () => {
test('should convert "30m" interval to "h" unit (positive)', () =>
expect(convertIntervalToUnit('30m', 'h')).toEqual({
value: 0.5,
unit: 'h',
}));

test('should convert "1h" interval to "m" unit (positive)', () =>
expect(convertIntervalToUnit('1h', 'm')).toEqual({
value: 60,
unit: 'm',
}));

test('should convert "1h" interval to "ms" unit (positive)', () =>
expect(convertIntervalToUnit('1h', 'ms')).toEqual({
value: 3600000,
unit: 'ms',
}));


test('should not convert "30m" interval to "0" unit (positive)', () =>
expect(convertIntervalToUnit('30m', 'o')).toEqual({
value: undefined,
unit: undefined,
}));

test('should not convert "m" interval to "s" unit (positive)', () =>
expect(convertIntervalToUnit('m', 's')).toEqual({
value: undefined,
unit: undefined,
}));
});

describe('getSuitableUnit()', () => {
test('should return "d" unit for oneDayInSeconds (positive)', () => {
const oneDayInSeconds = getUnitValue('d') * 1;

expect(getSuitableUnit(oneDayInSeconds)).toBe('d');
});

test('should return "d" unit for twoDaysInSeconds (positive)', () => {
const twoDaysInSeconds = getUnitValue('d') * 2;

expect(getSuitableUnit(twoDaysInSeconds)).toBe('d');
});

test('should return "w" unit for threeWeeksInSeconds (positive)', () => {
const threeWeeksInSeconds = getUnitValue('w') * 3;

expect(getSuitableUnit(threeWeeksInSeconds)).toBe('w');
});

test('should return "y" unit for aroundOneYearInSeconds (positive)', () => {
const aroundOneYearInSeconds = getUnitValue('d') * 370;

expect(getSuitableUnit(aroundOneYearInSeconds)).toBe('y');
});

test('should return "y" unit for twoYearsInSeconds (positive)', () => {
const twoYearsInSeconds = getUnitValue('y') * 2;

expect(getSuitableUnit(twoYearsInSeconds)).toBe('y');
});


test('should return "undefined" unit for negativeNumber (negative)', () => {
const negativeNumber = -12;

expect(getSuitableUnit(negativeNumber)).toBeUndefined();
});

test('should return "undefined" unit for string value (negative)', () => {
const stringValue = 'string';

expect(getSuitableUnit(stringValue)).toBeUndefined();
});

test('should return "undefined" unit for number string value (negative)', () => {
const stringValue = '-12';

expect(getSuitableUnit(stringValue)).toBeUndefined();
});

test('should return "undefined" in case of no input value(negative)', () =>
expect(getSuitableUnit()).toBeUndefined());

});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { unitsMap } from '@elastic/datemath';

export const leastCommonInterval = (num = 0, base = 0) => Math.max(Math.ceil(num / base) * base, base);
export const isCalendarInterval = ({ unit, value }) => value === 1 && ['calendar', 'mixed'].includes(unitsMap[unit].type);
Loading