Skip to content
This repository has been archived by the owner on Jul 30, 2022. It is now read-only.

Commit

Permalink
Merge pull request #6 in AVENGE/grafana from feature/timeTravel to re…
Browse files Browse the repository at this point in the history
…vdev

* commit '9ce65eaf67bc6e2cb96e27a74b34da7ab8d1a53a':
  Remove console
  typescript prettier...
  Time travel
  WIP for time trael...
  • Loading branch information
Steve Sutton authored and stevepostill committed Sep 14, 2021
2 parents 0f21fd1 + 9ce65ea commit 3f533cc
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 25 deletions.
5 changes: 3 additions & 2 deletions packages/grafana-data/src/datetime/rangeutil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,17 @@ export function describeTextRange(expr: any) {
* ```
* @category TimeUtils
* @param range - a time range (usually specified by the TimePicker)
* @param format - time format
* @alpha
*/
export function describeTimeRange(range: RawTimeRange, timeZone?: TimeZone): string {
export function describeTimeRange(range: RawTimeRange, timeZone?: TimeZone, format?: string): string {
const option = rangeIndex[range.from.toString() + ' to ' + range.to.toString()];

if (option) {
return option.display;
}

const options = { timeZone };
const options = { timeZone, format };

if (isDateTime(range.from) && isDateTime(range.to)) {
return dateTimeFormat(range.from, options) + ' to ' + dateTimeFormat(range.to, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,13 @@ const ZoomOutTooltip = () => (
const TimePickerTooltip = ({ timeRange, timeZone }: { timeRange: TimeRange; timeZone?: TimeZone }) => {
const theme = useTheme();
const styles = getLabelStyles(theme);

// TODO make configurable... .
const format = 'ddd, DD-MMM-YYYY hh:mm:ss a';
return (
<>
{dateTimeFormat(timeRange.from, { timeZone })}
{dateTimeFormat(timeRange.from, { timeZone, format })}
<div className="text-center">to</div>
{dateTimeFormat(timeRange.to, { timeZone })}
{dateTimeFormat(timeRange.to, { timeZone, format })}
<div className="text-center">
<span className={styles.utc}>{timeZoneFormatUserFriendly(timeZone)}</span>
</div>
Expand Down
148 changes: 148 additions & 0 deletions packages/grafana-ui/src/components/Reveal/TimeTravel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, { MouseEvent, useState } from 'react';
import { dateTime, DateTime, DurationUnit, SelectableValue } from '@grafana/data';
import { ButtonSelect } from '../Dropdown/ButtonSelect';
import { ButtonGroup, ToolbarButton } from '../Button';
import { useInterval } from 'react-use';

export const timeHops = [
{ label: '', value: '' },
{ label: '1m', value: '1m' },
{ label: '2m', value: '2m' },
{ label: '5m', value: '5m' },
{ label: '10m', value: '10m' },
{ label: '15m', value: '15m' },
{ label: '30m', value: '30m' },
{ label: '1h', value: '1h' },
{ label: '2hr', value: '2h' },
{ label: '3hr', value: '3h' },
{ label: '6hr', value: '6h' },
{ label: '12hr', value: '12h' },
{ label: '1d', value: '1d' },
{ label: '2d', value: '2d' },
{ label: '5d', value: '5d' },
{ label: '1w', value: '1w' },
{ label: '2w', value: '2w' },
{ label: '1M', value: '1M' },
] as Array<SelectableValue<string>>;

export const refreshIntervals = [
{ label: 'Off', value: '0' },
{ label: '250ms', value: '250' },
{ label: '500ms', value: '500' },
{ label: '750ms', value: '750' },
{ label: '1s', value: '1000' },
{ label: '2s', value: '2000' },
{ label: '5s', value: '5000' },
{ label: '10s', value: '10000' },
{ label: '30s', value: '30000' },
{ label: '1m', value: '60000' },
] as Array<SelectableValue<string>>;

export interface Props {
getStartTime: () => DateTime;
onUpdateTimeRange: (from: DateTime, to: DateTime) => void;
maxHops?: number;
}

function applyTimeOffset(start: DateTime, timeOffset: string, subtract?: boolean): [DateTime, DateTime] {
const parts = timeOffset.trim().match(/^(\d+)([s|m|h|d|w|M|y])$/);
if (parts?.length === 3) {
const amount = parseInt(parts[1], 10);
const duration = parts[2] as DurationUnit;
const from = subtract ? dateTime(start).subtract(amount, duration) : dateTime(start).add(amount, duration);
const to = dateTime(from).add(amount, duration).subtract(1, 's');
return [from, to];
}
return [start, start];
}

export function TimeTravel(props: Props) {
const [hops, setHops] = useState(() => 0);
const [timeHop, setTimeHop] = useState(() => '1h');
const [refreshInterval, setRefreshInterval] = useState((): string => '1000');
const [running, setRunning] = useState((): boolean => false);

useInterval(
() => {
if (timeHop) {
const maxHops = props.maxHops || 500; // need some sensible default
const [from, to] = applyTimeOffset(props.getStartTime(), timeHop);
setHops(hops + 1);
// console.log('Set time range ' + from.toISOString() + ' to ' + to.toISOString());
if (!from.isBefore(dateTime()) || hops > maxHops) {
setRunning(false);
setHops(0);
return;
}
props.onUpdateTimeRange(from, to);
}
},
running ? parseInt(refreshInterval, 10) : null
);

const onTimeHopChanged = (item: SelectableValue<string>) => {
setTimeHop(item.value!);
};

const onStartStopClicked = (event: MouseEvent<HTMLButtonElement>) => {
setRunning(!running);
};

const onRefreshIntervalChanged = (item: SelectableValue<string>) => {
setRefreshInterval(item.value!);
};

const onBack = () => {
const [from, to] = applyTimeOffset(props.getStartTime(), timeHop, true);
props.onUpdateTimeRange(from, to);
};

const onForward = () => {
const [from, to] = applyTimeOffset(props.getStartTime(), timeHop);
props.onUpdateTimeRange(from, to);
};

const selRefreshInterval = refreshIntervals.find(({ value }) => value === refreshInterval);
const selTimeHop = timeHops.find(({ value }) => value === timeHop);
const variant = 'default';
return (
<ButtonGroup className="refresh-picker">
{!running && (
<ToolbarButton
tooltip={'Go back one time step (' + timeHop + ')'}
variant={variant}
icon={'arrow-left'}
onClick={onBack}
/>
)}
{!running && (
<ToolbarButton
tooltip={'Go forward one time step (' + timeHop + ')'}
variant={variant}
icon={'arrow-right'}
onClick={onForward}
/>
)}
<ToolbarButton
tooltip={running ? 'Stop auto play' : 'Start auto play'}
variant={variant}
icon={running ? 'square' : 'play'}
onClick={onStartStopClicked}
/>
<ButtonSelect
value={selRefreshInterval}
title={'Refresh interval; when auto playing the dashboard will be updated once every interval'}
options={refreshIntervals}
onChange={onRefreshIntervalChanged}
variant={variant}
/>
<ButtonSelect
value={selTimeHop}
title={'Time step; when auto playing the dashboard date range will be shifted by this amount'}
options={timeHops}
onChange={onTimeHopChanged}
variant={variant}
/>
</ButtonGroup>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface OwnProps {
isFullscreen: boolean;
kioskMode: KioskMode;
hideTimePicker: boolean;
timeTravelVisible: boolean;
folderTitle?: string;
title: string;
onAddPanel: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Libraries
import React, { Component } from 'react';
import { dateMath, TimeRange, TimeZone } from '@grafana/data';
import { dateMath, DateTime, TimeRange, TimeZone, toUtc } from '@grafana/data';

// Types
import { DashboardModel } from '../../state';
Expand All @@ -14,6 +14,7 @@ import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { appEvents } from 'app/core/core';
import { ShiftTimeEvent, ShiftTimeEventPayload, TimeRangeUpdatedEvent, ZoomOutEvent } from '../../../../types/events';
import { Unsubscribable } from 'rxjs';
import { TimeTravel } from '@grafana/ui/src/components/Reveal/TimeTravel';

export interface Props {
dashboard: DashboardModel;
Expand All @@ -36,6 +37,17 @@ export class DashNavTimeControls extends Component<Props> {
this.forceUpdate();
};

onTimeTravelUpdateTimeRange = (from: DateTime, to: DateTime) => {
getTimeSrv().setTime(
{
from: toUtc(from.valueOf()),
to: toUtc(to.valueOf()),
},
undefined,
false
);
};

onRefresh = () => {
getTimeSrv().refreshDashboard();
return Promise.resolve();
Expand Down Expand Up @@ -102,6 +114,12 @@ export class DashNavTimeControls extends Component<Props> {
tooltip="Refresh dashboard"
noIntervalPicker={hideIntervalPicker}
/>
{dashboard.timepicker.timeTravel && (
<TimeTravel
getStartTime={() => getTimeSrv().timeRange().from}
onUpdateTimeRange={this.onTimeTravelUpdateTimeRange}
/>
)}
</ToolbarButtonRow>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export function GeneralSettingsUnconnected({ dashboard, updateTimeZone }: Props)
setRenderCounter(renderCounter + 1);
};

const onTimeTravelVisibleChange = (visible: boolean) => {
dashboard.timepicker.timeTravel = visible;
setRenderCounter(renderCounter + 1);
};

const onTimeZoneChange = (timeZone: TimeZone) => {
dashboard.timezone = timeZone;
setRenderCounter(renderCounter + 1);
Expand Down Expand Up @@ -113,8 +118,10 @@ export function GeneralSettingsUnconnected({ dashboard, updateTimeZone }: Props)
onRefreshIntervalChange={onRefreshIntervalChange}
onNowDelayChange={onNowDelayChange}
onHideTimePickerChange={onHideTimePickerChange}
onTimeTravelVisibleChange={onTimeTravelVisibleChange}
refreshIntervals={dashboard.timepicker.refresh_intervals}
timePickerHidden={dashboard.timepicker.hidden}
timeTravelVisible={dashboard.timepicker.timeTravel}
nowDelay={dashboard.timepicker.nowDelay}
timezone={dashboard.timezone}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ interface Props {
onRefreshIntervalChange: (interval: string[]) => void;
onNowDelayChange: (nowDelay: string) => void;
onHideTimePickerChange: (hide: boolean) => void;
onTimeTravelVisibleChange: (visible: boolean) => void;
refreshIntervals: string[];
timePickerHidden: boolean;
timeTravelVisible: boolean;
nowDelay: string;
timezone: TimeZone;
}
Expand Down Expand Up @@ -43,6 +45,10 @@ export class TimePickerSettings extends PureComponent<Props, State> {
this.props.onHideTimePickerChange(!this.props.timePickerHidden);
};

onTimeTravelVisibleChange = () => {
this.props.onTimeTravelVisibleChange(!this.props.timeTravelVisible);
};

onTimeZoneChange = (timeZone?: string) => {
if (typeof timeZone !== 'string') {
return;
Expand Down Expand Up @@ -79,6 +85,12 @@ export class TimePickerSettings extends PureComponent<Props, State> {
<Field label="Hide time picker">
<Switch value={!!this.props.timePickerHidden} onChange={this.onHideTimePickerChange} />
</Field>
<Field
label="Show time travel"
description="Allows the user to trigger time travel (note this can be computationally expensive)"
>
<Switch value={this.props.timeTravelVisible} onChange={this.onTimeTravelVisibleChange} />
</Field>
</CollapsableSection>
);
}
Expand Down
1 change: 1 addition & 0 deletions public/app/features/dashboard/containers/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
onAddPanel={this.onAddPanel}
kioskMode={kioskMode}
hideTimePicker={dashboard.timepicker.hidden}
timeTravelVisible={dashboard.timepicker.timeTravel}
/>
</header>
)}
Expand Down
32 changes: 17 additions & 15 deletions public/app/features/dashboard/services/TimeSrv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,22 @@ export class TimeSrv {
}
}

refreshDashboard() {
refreshDashboard(doSaveMetric?: boolean) {
if (this.dashboard) {
saveMetric({
category: 'grafana',
group: 'dashboard',
type: 'dashboard-range-change',
name: this.dashboard.title,
values: {
uid: this.dashboard.uid,
},
data: {
timeRange: this.timeRange(),
},
});
if (doSaveMetric === undefined || doSaveMetric) {
saveMetric({
category: 'grafana',
group: 'dashboard',
type: 'dashboard-range-change',
name: this.dashboard.title,
values: {
uid: this.dashboard.uid,
},
data: {
timeRange: this.timeRange(),
},
});
}
}
this.dashboard?.timeRangeUpdated(this.timeRange());
}
Expand Down Expand Up @@ -283,7 +285,7 @@ export class TimeSrv {
this.setAutoRefresh(this.previousAutoRefresh);
}

setTime(time: RawTimeRange, fromRouteUpdate?: boolean) {
setTime(time: RawTimeRange, fromRouteUpdate?: boolean, saveMetric?: boolean) {
extend(this.time, time);

// disable refresh if zoom in or zoom out
Expand All @@ -309,7 +311,7 @@ export class TimeSrv {
});
}

this.refreshDashboard();
this.refreshDashboard(saveMetric);
}

timeRangeForUrl = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export class VariableInput extends PureComponent<Props> {
ref={(instance) => {
if (instance) {
instance.focus();
instance.setAttribute('style', `width:${Math.max(instance.width, 80)}px`);
}
}}
type="text"
Expand Down
4 changes: 4 additions & 0 deletions public/app/plugins/panel/geomap/dims/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export function findField(frame: DataFrame, name?: string): Field | undefined {
return undefined;
}

if (!frame || !frame.fields) {
return undefined;
}

for (const field of frame.fields) {
if (RSupport.fieldNameMatches(frame, field, name)) {
return field;
Expand Down
Loading

0 comments on commit 3f533cc

Please sign in to comment.