Skip to content

Commit

Permalink
TSVB display interval information when building
Browse files Browse the repository at this point in the history
  • Loading branch information
alexwizp committed Feb 27, 2019
1 parent 9d77205 commit 7a6cc85
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/

import { relativeOptions } from '../../../../../ui/public/timepicker/relative_options';
import _ from 'lodash';
import moment from 'moment';
import { convertIntervalIntoUnit } from './get_interval';
import { i18n } from '@kbn/i18n';

const unitLookup = {
s: i18n.translate('tsvb.axisLabelOptions.secondsLabel', { defaultMessage: 'seconds' }),
m: i18n.translate('tsvb.axisLabelOptions.minutesLabel', { defaultMessage: 'minutes' }),
h: i18n.translate('tsvb.axisLabelOptions.hoursLabel', { defaultMessage: 'hours' }),
d: i18n.translate('tsvb.axisLabelOptions.daysLabel', { defaultMessage: 'days' }),
w: i18n.translate('tsvb.axisLabelOptions.weeksLabel', { defaultMessage: 'weeks' }),
M: i18n.translate('tsvb.axisLabelOptions.monthsLabel', { defaultMessage: 'months' }),
y: i18n.translate('tsvb.axisLabelOptions.yearsLabel', { defaultMessage: 'years' })
};
export function getAxisLabelString(interval) {
const units = _.pluck(_.clone(relativeOptions).reverse(), 'value')
.filter(s => /^[smhdwMy]$/.test(s));
const duration = moment.duration(interval, 'ms');
for (let i = 0; i < units.length; i++) {
const as = duration.as(units[i]);
if (Math.abs(as) > 1) {
const unitValue = Math.round(Math.abs(as));
const unitString = unitLookup[units[i]];
return i18n.translate('tsvb.axisLabelOptions.axisLabel',
{ defaultMessage: 'per {unitValue} {unitString}', values: { unitValue, unitString } });
}
const convertedValue = convertIntervalIntoUnit(interval);

if (convertedValue) {
return i18n.translate('tsvb.axisLabelOptions.axisLabel',
{ defaultMessage: 'per {unitValue} {unitString}', values: convertedValue });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 moment from 'moment';
import dateMath from '@elastic/datemath';
import { i18n } from '@kbn/i18n';
import { pluck, get, clone } from 'lodash';
import { relativeOptions } from '../../../../../ui/public/timepicker/relative_options';

const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + dateMath.units.join('|') + ')$');

export const unitLookup = {
s: i18n.translate('tsvb.getInterval.secondsLabel', { defaultMessage: 'seconds' }),
m: i18n.translate('tsvb.getInterval.minutesLabel', { defaultMessage: 'minutes' }),
h: i18n.translate('tsvb.getInterval.hoursLabel', { defaultMessage: 'hours' }),
d: i18n.translate('tsvb.getInterval.daysLabel', { defaultMessage: 'days' }),
w: i18n.translate('tsvb.getInterval.weeksLabel', { defaultMessage: 'weeks' }),
M: i18n.translate('tsvb.getInterval.monthsLabel', { defaultMessage: 'months' }),
y: i18n.translate('tsvb.getInterval.yearsLabel', { defaultMessage: 'years' })
};

export const convertIntervalIntoUnit = (interval, hasTranslateUnitString = true) => {
const units = pluck(clone(relativeOptions).reverse(), 'value')
.filter(s => /^[smhdwMy]$/.test(s));
const duration = moment.duration(interval, 'ms');

for (let i = 0; i < units.length; i++) {
const as = duration.as(units[i]);

if (Math.abs(as) > 1) {
return {
unitValue: Math.round(Math.abs(as)),
unitString: hasTranslateUnitString ? unitLookup[units[i]] : units[i]
};
}
}
};

export const convertStringIntervalIntoUnit = (stringInterval, hasTranslateUnitString = true) => {
const matches = stringInterval && stringInterval.match(INTERVAL_STRING_RE);

if (matches && unitLookup[matches[2]]) {
return {
unitValue: Number(matches[1]),
unitString: hasTranslateUnitString ? unitLookup[matches[2]] : matches[2]
};
}
};

export const getInterval = (visData, model) => {
const series = get(visData, `${model.id}.series`, []);
return series.reduce((currentInterval, item) => {
if (item.data.length > 1) {
const seriesInterval = item.data[1][0] - item.data[0][0];
if (!currentInterval || seriesInterval < currentInterval) return seriesInterval;
}
return currentInterval;
}, 0);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/

import PropTypes from 'prop-types';
import React, { Component } from 'react';
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, convertStringIntervalIntoUnit } from './lib/get_interval';

const MIN_CHART_HEIGHT = 250;

Expand All @@ -30,7 +30,8 @@ class VisEditorVisualization extends Component {
super(props);
this.state = {
height: MIN_CHART_HEIGHT,
dragging: false
dragging: false,
panelInterval: 0,
};

this.handleMouseUp = this.handleMouseUp.bind(this);
Expand All @@ -53,7 +54,7 @@ class VisEditorVisualization extends Component {
this.handleMouseMove = (event) => {
if (this.state.dragging) {
this.setState((prevState) => ({
height: Math.max(MIN_CHART_HEIGHT, prevState.height + event.movementY)
height: Math.max(MIN_CHART_HEIGHT, prevState.height + event.movementY),
}));
}
};
Expand All @@ -67,16 +68,16 @@ class VisEditorVisualization extends Component {
if (this._handler) {
this._handler.destroy();
}
if(this._subscription) {
if (this._subscription) {
this._subscription.unsubscribe();
}
}

onUpdate = () => {
this._handler.update({
timeRange: this.props.timeRange
timeRange: this.props.timeRange,
});
}
};

_loadVisualization() {
getVisualizeLoader().then(loader => {
Expand All @@ -94,6 +95,7 @@ class VisEditorVisualization extends Component {
});

this._subscription = this._handler.data$.subscribe((data) => {
this.setPanelInterval(data.visData);
this.props.onDataChange(data);
});

Expand All @@ -103,6 +105,14 @@ class VisEditorVisualization extends Component {
});
}

setPanelInterval(visData) {
const panelInterval = getInterval(visData, this.props.model);

if (this.state.panelInterval !== panelInterval) {
this.setState({ panelInterval });
}
}

componentDidUpdate() {
if (!this._handler) {
this._handlerUpdateHasAlreadyBeenTriggered = true;
Expand All @@ -115,6 +125,7 @@ class VisEditorVisualization extends Component {
componentDidMount() {
this._loadVisualization();
}

/**
* Resize the chart height when pressing up/down while the drag handle
* for resizing has the focus.
Expand All @@ -128,19 +139,40 @@ class VisEditorVisualization extends Component {
this.setState((prevState) => {
const newHeight = prevState.height + (keyCode === keyCodes.UP ? -15 : 15);
return {
height: Math.max(MIN_CHART_HEIGHT, newHeight)
height: Math.max(MIN_CHART_HEIGHT, newHeight),
};
});
}
}

hasShowPanelIntervalValue() {
return [
'metric',
'top_n',
'gauge',
'markdown'
].includes(this.props.model.type);
}

getFormattedPanelInterval() {
const interval = this.props.model.interval;

if (this.hasShowPanelIntervalValue() && interval) {
return interval === 'auto'
? convertIntervalIntoUnit(this.state.panelInterval, false)
: convertStringIntervalIntoUnit(interval, false);
}
}

render() {
const { dirty, autoApply } = this.props;
const style = { height: this.state.height };
if (this.state.dragging) {
style.userSelect = 'none';
}

const formattedPavelIntervalValue = this.getFormattedPanelInterval();

let applyMessage = (<FormattedMessage
id="tsvb.visEditorVisualization.changesSuccessfullyAppliedMessage"
defaultMessage="The latest changes have been applied."
Expand Down Expand Up @@ -171,6 +203,18 @@ class VisEditorVisualization extends Component {
/>
</EuiFlexItem>

{formattedPavelIntervalValue &&
<EuiFlexItem grow={false}>
<EuiText color="default" size="xs">
<FormattedMessage
id="tsvb.visEditorVisualization.panelInterval"
defaultMessage="Interval: last {unitValue}{unitString}"
values={formattedPavelIntervalValue}
/>
</EuiText>
</EuiFlexItem>
}

<EuiFlexItem grow={false}>
<EuiText color={dirty ? 'default' : 'subdued'} size="xs">
<p>
Expand All @@ -180,14 +224,14 @@ class VisEditorVisualization extends Component {
</EuiFlexItem>

{!autoApply &&
<EuiFlexItem grow={false}>
<EuiButton iconType="play" fill size="s" onClick={this.props.onCommit} disabled={!dirty}>
<FormattedMessage
id="tsvb.visEditorVisualization.applyChangesLabel"
defaultMessage="Apply changes"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton iconType="play" fill size="s" onClick={this.props.onCommit} disabled={!dirty}>
<FormattedMessage
id="tsvb.visEditorVisualization.applyChangesLabel"
defaultMessage="Apply changes"
/>
</EuiButton>
</EuiFlexItem>
}
</EuiFlexGroup>
);
Expand All @@ -213,10 +257,10 @@ class VisEditorVisualization extends Component {
onKeyDown={this.onSizeHandleKeyDown}
aria-label={this.props.intl.formatMessage({
id: 'tsvb.colorRules.adjustChartSizeAriaLabel',
defaultMessage: 'Press up/down to adjust the chart size'
defaultMessage: 'Press up/down to adjust the chart size',
})}
>
<i className="fa fa-ellipsis-h" />
<i className="fa fa-ellipsis-h"/>
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import _ from 'lodash';
import Timeseries from '../../../visualizations/components/timeseries';
import replaceVars from '../../lib/replace_vars';
import { getAxisLabelString } from '../../lib/get_axis_label_string';
import { getInterval } from '../../lib/get_interval';
import { createXaxisFormatter } from '../../lib/create_xaxis_formatter';

function hasSeparateAxis(row) {
Expand All @@ -35,20 +36,10 @@ function hasSeparateAxis(row) {

class TimeseriesVisualization extends Component {

constructor(props) {
super(props);
}

getInterval = () => {
const { visData, model } = this.props;
const series = _.get(visData, `${model.id}.series`, []);
return series.reduce((currentInterval, item) => {
if (item.data.length > 1) {
const seriesInterval = item.data[1][0] - item.data[0][0];
if (!currentInterval || seriesInterval < currentInterval) return seriesInterval;
}
return currentInterval;
}, 0);

return getInterval(visData, model);
}

xaxisFormatter = (val) => {
Expand Down

0 comments on commit 7a6cc85

Please sign in to comment.