Skip to content

Commit

Permalink
[TSVB] Show an indicator when using Last Value mode
Browse files Browse the repository at this point in the history
  • Loading branch information
DianaDerevyankina committed Feb 19, 2021
1 parent 8d9ac00 commit 5c21f3c
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/plugins/vis_type_timeseries/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type PanelSchema = TypeOf<typeof panel>;
export type VisPayload = TypeOf<typeof visPayloadSchema>;
export type FieldObject = TypeOf<typeof fieldObject>;

interface PanelData {
export interface PanelData {
id: string;
label: string;
data: Array<[number, number]>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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 React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiFlexItem, EuiLink, EuiText, EuiToolTip } from '@elastic/eui';
import { getUISettings } from '../../services';
import { convertIntervalIntoUnit, isAutoInterval, isGteInterval } from './lib/get_interval';
import { createIntervalBasedFormatter } from '../components/lib/create_interval_based_formatter';
import { TIME_RANGE_DATA_MODES } from '../../../common/timerange_data_modes';

const lastValueFormattedMessage = (
<FormattedMessage
id="visTypeTimeseries.dataTimeRangeModeLabel.lastValueMode"
defaultMessage="Last value"
/>
);

interface DataTimeRangeModeLabelProps {
seriesData: Array<Array<number | null>>;
panelInterval: number;
modelInterval: string;
modelTimeRangeMode: TIME_RANGE_DATA_MODES;
}

export const DataTimeRangeModeLabel = ({
seriesData,
panelInterval,
modelInterval,
modelTimeRangeMode,
}: DataTimeRangeModeLabelProps) => {
const hasShowPanelIntervalValue = () =>
isAutoInterval(modelInterval) || isGteInterval(modelInterval);

const getFormattedPanelInterval = () => {
const interval = convertIntervalIntoUnit(panelInterval, false);
return interval && `${interval.unitValue}${interval.unitString}`;
};

const getLastValueLabelWithTooltip = () => {
const dateFormat = getUISettings().get('dateFormat');
const scaledDataFormat = getUISettings().get('dateFormat:scaled');
const formatter = createIntervalBasedFormatter(panelInterval, scaledDataFormat, dateFormat);

const lastBucketDate = formatter(seriesData[seriesData.length - 1][0]);
const formattedPanelInterval = hasShowPanelIntervalValue() && getFormattedPanelInterval();
const tooltipContent = (
<FormattedMessage
id="visTypeTimeseries.dataTimeRangeModeLabel.lastValueTooltip"
defaultMessage="Bucket: {bucket} {interval}"
values={{
bucket: lastBucketDate,
interval: formattedPanelInterval && (
<p>
<FormattedMessage
id="visTypeTimeseries.dataTimeRangeModeLabel.panelInterval"
defaultMessage="Interval: {formattedPanelInterval}"
values={{ formattedPanelInterval }}
/>
</p>
),
}}
/>
);

return (
<EuiToolTip position="top" content={tooltipContent}>
<EuiLink href="#">{lastValueFormattedMessage}</EuiLink>
</EuiToolTip>
);
};

const lastValueLabel =
seriesData && panelInterval ? getLastValueLabelWithTooltip() : lastValueFormattedMessage;

return (
<EuiFlexItem grow={false}>
<EuiText color="default" size="xs">
<FormattedMessage
id="visTypeTimeseries.dataTimeRangeModeLabel.dataTimeRangeMode"
defaultMessage="Data timerange mode: {timeRangeMode}"
values={{
timeRangeMode:
modelTimeRangeMode === TIME_RANGE_DATA_MODES.LAST_VALUE ? (
lastValueLabel
) : (
<FormattedMessage
id="visTypeTimeseries.dataTimeRangeModeLabel.entireTimeRangeMode"
defaultMessage="Entire time range"
/>
),
}}
/>
</EuiText>
</EuiFlexItem>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@

import moment from 'moment';

function getFormat(interval, rules = []) {
function getFormat(interval: number, rules: string[][] = []) {
for (let i = rules.length - 1; i >= 0; i--) {
const rule = rules[i];
if (!rule[0] || interval >= moment.duration(rule[0])) {
if (!rule[0] || interval >= Number(moment.duration(rule[0]))) {
return rule[1];
}
}
}

export function createXaxisFormatter(interval, rules, dateFormat) {
return (val) => {
export function createIntervalBasedFormatter(
interval: number,
rules: string[][],
dateFormat: string
) {
return (val: moment.MomentInput): string => {
return moment(val).format(getFormat(interval, rules) ?? dateFormat);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { search } from '../../../../../../plugins/data/public';
const { parseEsInterval } = search.aggs;
import { GTE_INTERVAL_RE } from '../../../../common/interval_regexp';
import { AUTO_INTERVAL } from '../../../../common/constants';
import { PanelData, TimeseriesVisData } from '../../../../common/types';
import { TimeseriesVisParams } from '../../../metrics_fn';

export const unitLookup = {
s: i18n.translate('visTypeTimeseries.getInterval.secondsLabel', { defaultMessage: 'seconds' }),
Expand All @@ -24,9 +26,11 @@ export const unitLookup = {
y: i18n.translate('visTypeTimeseries.getInterval.yearsLabel', { defaultMessage: 'years' }),
};

export const convertIntervalIntoUnit = (interval, hasTranslateUnitString = true) => {
type TimeUnits = Array<keyof typeof unitLookup>;

export const convertIntervalIntoUnit = (interval: number, hasTranslateUnitString = true) => {
// Iterate units from biggest to smallest
const units = Object.keys(unitLookup).reverse();
const units = Object.keys(unitLookup).reverse() as TimeUnits;
const duration = moment.duration(interval, 'ms');

for (let i = 0; i < units.length; i++) {
Expand All @@ -41,11 +45,16 @@ export const convertIntervalIntoUnit = (interval, hasTranslateUnitString = true)
}
};

export const isGteInterval = (interval) => GTE_INTERVAL_RE.test(interval);
export const isAutoInterval = (interval) => !interval || interval === AUTO_INTERVAL;
export const isGteInterval = (interval: string) => GTE_INTERVAL_RE.test(interval);
export const isAutoInterval = (interval: string) => !interval || interval === AUTO_INTERVAL;

interface ValidationResult {
errorMessage: string;
isValid: boolean;
}

export const validateReInterval = (intervalValue) => {
const validationResult = {};
export const validateReInterval = (intervalValue: string) => {
const validationResult = {} as ValidationResult;

try {
parseEsInterval(intervalValue);
Expand All @@ -58,14 +67,12 @@ export const validateReInterval = (intervalValue) => {
return validationResult;
};

export const getInterval = (visData, model) => {
let series;

if (model && model.type === 'table') {
series = get(visData, `series[0].series`, []);
} else {
series = get(visData, `${model.id}.series`, []);
}
export const getInterval = (visData: TimeseriesVisData, model: TimeseriesVisParams) => {
const series = get(
visData,
model?.type === 'table' ? `series[0].series` : `${model.id}.series`,
[]
) as PanelData[];

return series.reduce((currentInterval, item) => {
if (item.data.length > 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@ import React, { Component } from 'react';
import { get } from 'lodash';
import { keys, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } from '@elastic/eui';
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
import {
getInterval,
convertIntervalIntoUnit,
isAutoInterval,
isGteInterval,
} from './lib/get_interval';
import { getInterval } from './lib/get_interval';
import { AUTO_INTERVAL } from '../../../common/constants';
import { PANEL_TYPES } from '../../../common/panel_types';
import { DataTimeRangeModeLabel } from './data_time_range_mode_label';

const MIN_CHART_HEIGHT = 300;

Expand Down Expand Up @@ -66,7 +62,15 @@ class VisEditorVisualizationUI extends Component {
this.props.eventEmitter.emit('embeddableRendered');

this._subscription = this._handler.handler.data$.subscribe((data) => {
this.setPanelInterval(data.value.visData);
const visData = data.value?.visData;
const series = get(visData, `${this.props.model.id}.series`, []);
this.setState({
seriesData: series?.length
? series[0].data
: visData?.series && visData.series[0]?.series[0]?.data,
});

this.setPanelInterval(visData);
onDataChange(data.value);
});
}
Expand Down Expand Up @@ -98,26 +102,16 @@ class VisEditorVisualizationUI extends Component {
}
};

hasShowPanelIntervalValue() {
isAnyPanelTypeExceptTimeseries() {
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) &&
(isAutoInterval(interval) || isGteInterval(interval))
);
}

getFormattedPanelInterval() {
const interval = convertIntervalIntoUnit(this.state.panelInterval, false);

return interval ? `${interval.unitValue}${interval.unitString}` : null;
return [
PANEL_TYPES.METRIC,
PANEL_TYPES.TOP_N,
PANEL_TYPES.GAUGE,
PANEL_TYPES.MARKDOWN,
PANEL_TYPES.TABLE,
].includes(type);
}

componentWillUnmount() {
Expand Down Expand Up @@ -147,15 +141,14 @@ class VisEditorVisualizationUI extends Component {
}

render() {
const { dirty, autoApply, title, description, onToggleAutoApply, onCommit } = this.props;
const style = { height: this.state.height };
const { dirty, autoApply, title, description, onToggleAutoApply, onCommit, model } = this.props;
const { seriesData, panelInterval, height, dragging } = this.state;
const style = { height };

if (this.state.dragging) {
if (dragging) {
style.userSelect = 'none';
}

const panelInterval = this.hasShowPanelIntervalValue() && this.getFormattedPanelInterval();

let applyMessage = (
<FormattedMessage
id="visTypeTimeseries.visEditorVisualization.changesSuccessfullyAppliedMessage"
Expand Down Expand Up @@ -194,18 +187,13 @@ class VisEditorVisualizationUI extends Component {
/>
</EuiFlexItem>

{panelInterval && (
<EuiFlexItem grow={false}>
<EuiText color="default" size="xs">
<p>
<FormattedMessage
id="visTypeTimeseries.visEditorVisualization.panelInterval"
defaultMessage="Interval: {panelInterval}"
values={{ panelInterval }}
/>
</p>
</EuiText>
</EuiFlexItem>
{this.isAnyPanelTypeExceptTimeseries() && (
<DataTimeRangeModeLabel
seriesData={seriesData}
panelInterval={panelInterval}
modelInterval={model.interval ?? AUTO_INTERVAL}
modelTimeRangeMode={model.time_range_mode}
/>
)}

<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { MarkdownSimple } from '../../../../../../../plugins/kibana_react/public
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';
import { createIntervalBasedFormatter } from '../../lib/create_interval_based_formatter';
import { STACKED_OPTIONS } from '../../../visualizations/constants';
import { getCoreStart } from '../../../../services';

Expand All @@ -35,7 +35,11 @@ class TimeseriesVisualization extends Component {
dateFormat = this.props.getConfig('dateFormat');

xAxisFormatter = (interval) => (val) => {
const formatter = createXaxisFormatter(interval, this.scaledDataFormat, this.dateFormat);
const formatter = createIntervalBasedFormatter(
interval,
this.scaledDataFormat,
this.dateFormat
);
return formatter(val);
};

Expand Down

0 comments on commit 5c21f3c

Please sign in to comment.