mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PanelData: Adds timeRange prop to PanelData (#19361)
* Refactor: Adds newTimeRange property to PanelData * Refactor: Handles timeRange prop after requests * Refactor: Makes timeRange mandatory * Refactor: Adds DefaultTimeRange
This commit is contained in:
parent
e35de167f9
commit
889f8e3131
@ -41,3 +41,9 @@ export interface TimeOptions {
|
||||
export type TimeFragment = string | DateTime;
|
||||
|
||||
export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
export const DefaultTimeRange: TimeRange = {
|
||||
from: {} as DateTime,
|
||||
to: {} as DateTime,
|
||||
raw: { from: '6h', to: 'now' },
|
||||
};
|
||||
|
@ -16,6 +16,8 @@ export interface PanelData {
|
||||
series: DataFrame[];
|
||||
request?: DataQueryRequest;
|
||||
error?: DataQueryError;
|
||||
// Contains the range from the request or a shifted time range if a request uses relative time
|
||||
timeRange: TimeRange;
|
||||
}
|
||||
|
||||
export interface PanelProps<T = any> {
|
||||
|
@ -14,7 +14,7 @@ import templateSrv from 'app/features/templating/template_srv';
|
||||
import config from 'app/core/config';
|
||||
// Types
|
||||
import { DashboardModel, PanelModel } from '../state';
|
||||
import { LoadingState, ScopedVars, AbsoluteTimeRange, toUtc, toDataFrameDTO } from '@grafana/data';
|
||||
import { LoadingState, ScopedVars, AbsoluteTimeRange, toUtc, toDataFrameDTO, DefaultTimeRange } from '@grafana/data';
|
||||
|
||||
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
||||
|
||||
@ -52,6 +52,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
data: {
|
||||
state: LoadingState.NotStarted,
|
||||
series: [],
|
||||
timeRange: DefaultTimeRange,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -66,6 +67,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
if (this.hasPanelSnapshot) {
|
||||
this.setState({
|
||||
data: {
|
||||
...this.state.data,
|
||||
state: LoadingState.Done,
|
||||
series: getProcessedDataFrames(panel.snapshotData),
|
||||
},
|
||||
@ -241,6 +243,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
|
||||
const PanelComponent = plugin.panel;
|
||||
const innerPanelHeight = calculateInnerPanelHeight(panel, height);
|
||||
const timeRange = data.timeRange || this.timeSrv.timeRange();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -249,7 +252,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
<PanelComponent
|
||||
id={panel.id}
|
||||
data={data}
|
||||
timeRange={data.request ? data.request.range : this.timeSrv.timeRange()}
|
||||
timeRange={timeRange}
|
||||
timeZone={this.props.dashboard.getTimezone()}
|
||||
options={panel.getOptions()}
|
||||
transparent={panel.transparent}
|
||||
|
@ -2,25 +2,29 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { css } from 'emotion';
|
||||
|
||||
// Components
|
||||
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { QueryInspector } from './QueryInspector';
|
||||
import { QueryOptions } from './QueryOptions';
|
||||
import { PanelOptionsGroup, TransformationsEditor } from '@grafana/ui';
|
||||
import {
|
||||
PanelOptionsGroup,
|
||||
TransformationsEditor,
|
||||
DataQuery,
|
||||
DataSourceSelectItem,
|
||||
PanelData,
|
||||
AlphaNotice,
|
||||
PluginState,
|
||||
} from '@grafana/ui';
|
||||
import { QueryEditorRow } from './QueryEditorRow';
|
||||
|
||||
// Services
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import config from 'app/core/config';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../state/PanelModel';
|
||||
import { DashboardModel } from '../state/DashboardModel';
|
||||
import { DataQuery, DataSourceSelectItem, PanelData, AlphaNotice, PluginState } from '@grafana/ui';
|
||||
import { LoadingState, DataTransformerConfig } from '@grafana/data';
|
||||
import { LoadingState, DataTransformerConfig, DefaultTimeRange } from '@grafana/data';
|
||||
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
import { isSharedDashboardQuery, DashboardQueryEditor } from 'app/plugins/datasource/dashboard';
|
||||
@ -55,6 +59,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
data: {
|
||||
state: LoadingState.NotStarted,
|
||||
series: [],
|
||||
timeRange: DefaultTimeRange,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { LoadingState, toDataFrame } from '@grafana/data';
|
||||
import { LoadingState, toDataFrame, dateTime } from '@grafana/data';
|
||||
import { PanelData, DataQueryRequest } from '@grafana/ui';
|
||||
import { filterPanelDataToQuery } from './QueryEditorRow';
|
||||
|
||||
@ -28,6 +28,7 @@ describe('filterPanelDataToQuery', () => {
|
||||
makePretendRequest('sub2'),
|
||||
makePretendRequest('sub3'),
|
||||
]),
|
||||
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
||||
};
|
||||
|
||||
it('should not have an error unless the refId matches', () => {
|
||||
|
@ -2,13 +2,11 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
// Utils & Services
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
import { Emitter } from 'app/core/utils/emitter';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../state/PanelModel';
|
||||
import { DataQuery, DataSourceApi, PanelData, DataQueryRequest, ErrorBoundaryAlert } from '@grafana/ui';
|
||||
@ -320,10 +318,13 @@ export function filterPanelDataToQuery(data: PanelData, refId: string): PanelDat
|
||||
state = LoadingState.Error;
|
||||
}
|
||||
|
||||
const timeRange = data.timeRange;
|
||||
|
||||
return {
|
||||
state,
|
||||
series,
|
||||
request,
|
||||
error,
|
||||
timeRange,
|
||||
};
|
||||
}
|
||||
|
@ -1,27 +1,23 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Utils & Services
|
||||
import { AngularComponent, getAngularLoader } from '@grafana/runtime';
|
||||
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||
import { StoreState } from 'app/types';
|
||||
import { updateLocation } from 'app/core/actions';
|
||||
|
||||
// Components
|
||||
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
|
||||
import { VizTypePicker } from './VizTypePicker';
|
||||
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
||||
import { FadeIn } from 'app/core/components/Animations/FadeIn';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../state';
|
||||
import { DashboardModel } from '../state';
|
||||
import { PanelModel, DashboardModel } from '../state';
|
||||
import { VizPickerSearch } from './VizPickerSearch';
|
||||
import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
|
||||
import { PanelPlugin, PanelPluginMeta, PanelData } from '@grafana/ui';
|
||||
import { PanelCtrl } from 'app/plugins/sdk';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
import { LoadingState } from '@grafana/data';
|
||||
import { LoadingState, DefaultTimeRange } from '@grafana/data';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
@ -57,6 +53,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
||||
data: {
|
||||
state: LoadingState.NotStarted,
|
||||
series: [],
|
||||
timeRange: DefaultTimeRange,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,17 +1,22 @@
|
||||
// Libraries
|
||||
import _ from 'lodash';
|
||||
|
||||
// Utils
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { dateMath } from '@grafana/data';
|
||||
|
||||
// Types
|
||||
import { TimeRange, RawTimeRange, TimeZone } from '@grafana/data';
|
||||
import {
|
||||
dateMath,
|
||||
DefaultTimeRange,
|
||||
TimeRange,
|
||||
RawTimeRange,
|
||||
TimeZone,
|
||||
toUtc,
|
||||
dateTime,
|
||||
isDateTime,
|
||||
} from '@grafana/data';
|
||||
import { ITimeoutService, ILocationService } from 'angular';
|
||||
import { ContextSrv } from 'app/core/services/context_srv';
|
||||
import { DashboardModel } from '../state/DashboardModel';
|
||||
import { toUtc, dateTime, isDateTime } from '@grafana/data';
|
||||
import { getZoomedTimeRange, getShiftedTimeRange } from 'app/core/utils/timePicker';
|
||||
|
||||
export class TimeSrv {
|
||||
@ -32,7 +37,7 @@ export class TimeSrv {
|
||||
private contextSrv: ContextSrv
|
||||
) {
|
||||
// default time
|
||||
this.time = { from: '6h', to: 'now' };
|
||||
this.time = DefaultTimeRange.raw;
|
||||
|
||||
$rootScope.$on('zoom-out', this.zoomOut.bind(this));
|
||||
$rootScope.$on('shift-time', this.shiftTime.bind(this));
|
||||
|
@ -2,6 +2,7 @@ import { DataFrame, LoadingState, dateTime } from '@grafana/data';
|
||||
import { PanelData, DataSourceApi, DataQueryRequest, DataQueryResponse } from '@grafana/ui';
|
||||
import { Subscriber, Observable, Subscription } from 'rxjs';
|
||||
import { runRequest } from './runRequest';
|
||||
import { deepFreeze } from '../../../../test/core/redux/reducerTester';
|
||||
|
||||
jest.mock('app/core/services/backend_srv');
|
||||
|
||||
@ -186,19 +187,56 @@ describe('runRequest', () => {
|
||||
|
||||
runRequestScenario('If time range is relative', ctx => {
|
||||
ctx.setup(async () => {
|
||||
// any changes to ctx.request.range will throw and state would become LoadingState.Error
|
||||
deepFreeze(ctx.request.range);
|
||||
ctx.start();
|
||||
|
||||
// wait a bit
|
||||
await sleep(20);
|
||||
|
||||
ctx.emitPacket({ data: [{ name: 'DataB-1' } as DataFrame] });
|
||||
});
|
||||
|
||||
it('should update returned request range', () => {
|
||||
expect(ctx.results[0].request.range.to.valueOf()).not.toBe(ctx.fromStartTime);
|
||||
it('should add the correct timeRange property and the request range should not be mutated', () => {
|
||||
expect(ctx.results[0].timeRange.to.valueOf()).toBeDefined();
|
||||
expect(ctx.results[0].timeRange.to.valueOf()).not.toBe(ctx.toStartTime.valueOf());
|
||||
expect(ctx.results[0].timeRange.to.valueOf()).not.toBe(ctx.results[0].request.range.to.valueOf());
|
||||
|
||||
expectThatRangeHasNotMutated(ctx);
|
||||
});
|
||||
});
|
||||
|
||||
runRequestScenario('If time range is not relative', ctx => {
|
||||
ctx.setup(async () => {
|
||||
ctx.request.range.raw.from = ctx.fromStartTime;
|
||||
ctx.request.range.raw.to = ctx.toStartTime;
|
||||
// any changes to ctx.request.range will throw and state would become LoadingState.Error
|
||||
deepFreeze(ctx.request.range);
|
||||
ctx.start();
|
||||
|
||||
// wait a bit
|
||||
await sleep(20);
|
||||
|
||||
ctx.emitPacket({ data: [{ name: 'DataB-1' } as DataFrame] });
|
||||
});
|
||||
|
||||
it('should add the correct timeRange property and the request range should not be mutated', () => {
|
||||
expect(ctx.results[0].timeRange).toBeDefined();
|
||||
expect(ctx.results[0].timeRange.to.valueOf()).toBe(ctx.toStartTime.valueOf());
|
||||
expect(ctx.results[0].timeRange.to.valueOf()).toBe(ctx.results[0].request.range.to.valueOf());
|
||||
|
||||
expectThatRangeHasNotMutated(ctx);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const expectThatRangeHasNotMutated = (ctx: ScenarioCtx) => {
|
||||
// Make sure that the range for request is not changed and that deepfreeze hasn't thrown
|
||||
expect(ctx.results[0].request.range.to.valueOf()).toBe(ctx.toStartTime.valueOf());
|
||||
expect(ctx.results[0].error).not.toBeDefined();
|
||||
expect(ctx.results[0].state).toBe(LoadingState.Done);
|
||||
};
|
||||
|
||||
async function sleep(ms: number) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, ms);
|
||||
|
@ -34,14 +34,14 @@ export function processResponsePacket(packet: DataQueryResponse, state: RunningQ
|
||||
packets[packet.key || 'A'] = packet;
|
||||
|
||||
// Update the time range
|
||||
let timeRange = request.range;
|
||||
if (isString(timeRange.raw.from)) {
|
||||
timeRange = {
|
||||
from: dateMath.parse(timeRange.raw.from, false),
|
||||
to: dateMath.parse(timeRange.raw.to, true),
|
||||
raw: timeRange.raw,
|
||||
};
|
||||
}
|
||||
const range = { ...request.range };
|
||||
const timeRange = isString(range.raw.from)
|
||||
? {
|
||||
from: dateMath.parse(range.raw.from, false),
|
||||
to: dateMath.parse(range.raw.to, true),
|
||||
raw: range.raw,
|
||||
}
|
||||
: range;
|
||||
|
||||
const combinedData = flatten(
|
||||
lodashMap(packets, (packet: DataQueryResponse) => {
|
||||
@ -52,10 +52,8 @@ export function processResponsePacket(packet: DataQueryResponse, state: RunningQ
|
||||
const panelData = {
|
||||
state: packet.state || LoadingState.Done,
|
||||
series: combinedData,
|
||||
request: {
|
||||
...request,
|
||||
range: timeRange,
|
||||
},
|
||||
request,
|
||||
timeRange,
|
||||
};
|
||||
|
||||
return { packets, panelData };
|
||||
@ -75,6 +73,7 @@ export function runRequest(datasource: DataSourceApi, request: DataQueryRequest)
|
||||
state: LoadingState.Loading,
|
||||
series: [],
|
||||
request: request,
|
||||
timeRange: request.range,
|
||||
},
|
||||
packets: {},
|
||||
};
|
||||
@ -96,6 +95,7 @@ export function runRequest(datasource: DataSourceApi, request: DataQueryRequest)
|
||||
request.endTime = Date.now();
|
||||
|
||||
state = processResponsePacket(packet, state);
|
||||
|
||||
return state.panelData;
|
||||
}),
|
||||
// handle errors
|
||||
|
@ -1,19 +1,19 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { LoadingState } from '@grafana/data';
|
||||
import { LoadingState, TimeRange } from '@grafana/data';
|
||||
import { PanelData } from '@grafana/ui';
|
||||
|
||||
import QueryStatus from './QueryStatus';
|
||||
|
||||
describe('<QueryStatus />', () => {
|
||||
it('should render with a latency', () => {
|
||||
const res: PanelData = { series: [], state: LoadingState.Done };
|
||||
const res: PanelData = { series: [], state: LoadingState.Done, timeRange: {} as TimeRange };
|
||||
const wrapper = shallow(<QueryStatus latency={0} queryResponse={res} />);
|
||||
expect(wrapper.find('div').exists()).toBeTruthy();
|
||||
});
|
||||
it('should not render when query has not started', () => {
|
||||
const res: PanelData = { series: [], state: LoadingState.NotStarted };
|
||||
const res: PanelData = { series: [], state: LoadingState.NotStarted, timeRange: {} as TimeRange };
|
||||
const wrapper = shallow(<QueryStatus latency={0} queryResponse={res} />);
|
||||
expect(wrapper.getElement()).toBe(null);
|
||||
});
|
||||
|
@ -42,7 +42,7 @@ describe('Explore item reducer', () => {
|
||||
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
|
||||
.whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left }))
|
||||
.thenStateShouldEqual({
|
||||
...makeExploreItemState(),
|
||||
...initalState,
|
||||
scanning: true,
|
||||
});
|
||||
});
|
||||
@ -57,7 +57,7 @@ describe('Explore item reducer', () => {
|
||||
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
|
||||
.whenActionIsDispatched(scanStopAction({ exploreId: ExploreId.left }))
|
||||
.thenStateShouldEqual({
|
||||
...makeExploreItemState(),
|
||||
...initalState,
|
||||
scanning: false,
|
||||
scanRange: undefined,
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
refreshIntervalToSortOrder,
|
||||
} from 'app/core/utils/explore';
|
||||
import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState, ExploreMode } from 'app/types/explore';
|
||||
import { LoadingState, toLegacyResponseData } from '@grafana/data';
|
||||
import { LoadingState, toLegacyResponseData, DefaultTimeRange } from '@grafana/data';
|
||||
import { DataQuery, DataSourceApi, PanelData, DataQueryRequest } from '@grafana/ui';
|
||||
import {
|
||||
HigherOrderAction,
|
||||
@ -121,6 +121,7 @@ export const createEmptyQueryResponse = (): PanelData => ({
|
||||
request: {} as DataQueryRequest<DataQuery>,
|
||||
series: [],
|
||||
error: null,
|
||||
timeRange: DefaultTimeRange,
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -131,15 +131,16 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
}
|
||||
|
||||
if (data.request) {
|
||||
const { range, timeInfo } = data.request;
|
||||
if (range) {
|
||||
this.range = range;
|
||||
}
|
||||
const { timeInfo } = data.request;
|
||||
if (timeInfo) {
|
||||
this.timeInfo = timeInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.timeRange) {
|
||||
this.range = data.timeRange;
|
||||
}
|
||||
|
||||
if (this.useDataFrames) {
|
||||
this.handleDataFrames(data.series);
|
||||
} else {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { DataQuery, PanelData, DataSourceApi } from '@grafana/ui';
|
||||
import { QueryRunnerOptions } from 'app/features/dashboard/state/PanelQueryRunner';
|
||||
import { DashboardQuery } from './types';
|
||||
import { DashboardQuery, SHARED_DASHBODARD_QUERY } from './types';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { LoadingState } from '@grafana/data';
|
||||
import { SHARED_DASHBODARD_QUERY } from './types';
|
||||
import { LoadingState, DefaultTimeRange } from '@grafana/data';
|
||||
|
||||
export function isSharedDashboardQuery(datasource: string | DataSourceApi) {
|
||||
if (!datasource) {
|
||||
@ -76,5 +75,6 @@ function getQueryError(msg: string): PanelData {
|
||||
state: LoadingState.Error,
|
||||
series: [],
|
||||
error: { message: msg },
|
||||
timeRange: DefaultTimeRange,
|
||||
};
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ interface ObjectType extends Object {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const deepFreeze = <T>(obj: T): T => {
|
||||
export const deepFreeze = <T>(obj: T): T => {
|
||||
Object.freeze(obj);
|
||||
|
||||
const isNotException = (object: any, propertyName: any) =>
|
||||
|
Loading…
Reference in New Issue
Block a user