mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Improve handling time range keyboard shortcuts inside Explore (#73600)
* Handle time keyboard shortcuts inside Explore * Remove unused handler for make absolute * Remove unused code * Unify handling keyboard shorcuts logic
This commit is contained in:
parent
8b7566c299
commit
f11cc0e60e
@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
|
||||
import { TestProvider } from 'test/helpers/TestProvider';
|
||||
|
||||
import { DataSourceApi, LoadingState, CoreApp, createTheme, EventBusSrv, PluginExtensionTypes } from '@grafana/data';
|
||||
import { CoreApp, createTheme, DataSourceApi, EventBusSrv, LoadingState, PluginExtensionTypes } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { getPluginLinkExtensions } from '@grafana/runtime';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
@ -72,7 +72,6 @@ const dummyProps: Props = {
|
||||
isLive: false,
|
||||
syncedTimes: false,
|
||||
updateTimeRange: jest.fn(),
|
||||
makeAbsoluteTime: jest.fn(),
|
||||
graphResult: [],
|
||||
absoluteRange: {
|
||||
from: 0,
|
||||
|
@ -4,37 +4,34 @@ import memoizeOne from 'memoize-one';
|
||||
import React, { createRef } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
|
||||
import {
|
||||
AbsoluteTimeRange,
|
||||
EventBus,
|
||||
GrafanaTheme2,
|
||||
hasToggleableQueryFiltersSupport,
|
||||
LoadingState,
|
||||
QueryFixAction,
|
||||
RawTimeRange,
|
||||
EventBus,
|
||||
SplitOpenOptions,
|
||||
SupplementaryQueryType,
|
||||
hasToggleableQueryFiltersSupport,
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, getDataSourceSrv, reportInteraction } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import {
|
||||
AdHocFilterItem,
|
||||
CustomScrollbar,
|
||||
ErrorBoundaryAlert,
|
||||
PanelContainer,
|
||||
Themeable2,
|
||||
withTheme2,
|
||||
PanelContainer,
|
||||
AdHocFilterItem,
|
||||
} from '@grafana/ui';
|
||||
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR } from '@grafana/ui/src/components/Table/types';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { supportedFeatures } from 'app/core/history/richHistoryStorageProvider';
|
||||
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||
import { getNodeGraphDataFrames } from 'app/plugins/panel/nodeGraph/utils';
|
||||
import { StoreState } from 'app/types';
|
||||
import { AbsoluteTimeEvent } from 'app/types/events';
|
||||
|
||||
import { getTimeZone } from '../profile/state/selectors';
|
||||
|
||||
@ -67,7 +64,7 @@ import {
|
||||
setSupplementaryQueryEnabled,
|
||||
} from './state/query';
|
||||
import { isSplit } from './state/selectors';
|
||||
import { makeAbsoluteTime, updateTimeRange } from './state/time';
|
||||
import { updateTimeRange } from './state/time';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
@ -141,10 +138,10 @@ export type Props = ExploreProps & ConnectedProps<typeof connector>;
|
||||
*/
|
||||
export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
scrollElement: HTMLDivElement | undefined;
|
||||
absoluteTimeUnsubsciber: Unsubscribable | undefined;
|
||||
topOfViewRef = createRef<HTMLDivElement>();
|
||||
graphEventBus: EventBus;
|
||||
logsEventBus: EventBus;
|
||||
memoizedGetNodeGraphDataFrames = memoizeOne(getNodeGraphDataFrames);
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@ -155,14 +152,6 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
this.logsEventBus = props.eventBus.newScopedBus('logs', { onlyLocal: false });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.absoluteTimeUnsubsciber = appEvents.subscribe(AbsoluteTimeEvent, this.onMakeAbsoluteTime);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.absoluteTimeUnsubsciber?.unsubscribe();
|
||||
}
|
||||
|
||||
onChangeTime = (rawRange: RawTimeRange) => {
|
||||
const { updateTimeRange, exploreId } = this.props;
|
||||
updateTimeRange({ exploreId, rawRange });
|
||||
@ -228,11 +217,6 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
this.props.addQueryRow(exploreId, queryKeys.length);
|
||||
};
|
||||
|
||||
onMakeAbsoluteTime = () => {
|
||||
const { makeAbsoluteTime } = this.props;
|
||||
makeAbsoluteTime();
|
||||
};
|
||||
|
||||
/**
|
||||
* Used by Logs details.
|
||||
*/
|
||||
@ -450,8 +434,6 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
);
|
||||
}
|
||||
|
||||
memoizedGetNodeGraphDataFrames = memoizeOne(getNodeGraphDataFrames);
|
||||
|
||||
renderFlameGraphPanel() {
|
||||
const { queryResponse } = this.props;
|
||||
return <FlameGraphExploreContainer dataFrames={queryResponse.flameGraphFrames} />;
|
||||
@ -660,7 +642,6 @@ const mapDispatchToProps = {
|
||||
scanStopAction,
|
||||
setQueries,
|
||||
updateTimeRange,
|
||||
makeAbsoluteTime,
|
||||
addQueryRow,
|
||||
splitOpen,
|
||||
setSupplementaryQueryEnabled,
|
||||
|
@ -12,6 +12,7 @@ import { ExploreQueryParams } from 'app/types/explore';
|
||||
import { ExploreActions } from './ExploreActions';
|
||||
import { ExplorePaneContainer } from './ExplorePaneContainer';
|
||||
import { useExplorePageTitle } from './hooks/useExplorePageTitle';
|
||||
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';
|
||||
import { useSplitSizeUpdater } from './hooks/useSplitSizeUpdater';
|
||||
import { useStateSync } from './hooks/useStateSync';
|
||||
import { useTimeSrvFix } from './hooks/useTimeSrvFix';
|
||||
@ -38,7 +39,7 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor
|
||||
// if we were to update the URL on state change, the title would not match the URL.
|
||||
// Ultimately the URL is the single source of truth from which state is derived, the page title is not different
|
||||
useExplorePageTitle(props.queryParams);
|
||||
const { keybindings, chrome } = useGrafana();
|
||||
const { chrome } = useGrafana();
|
||||
const navModel = useNavModel('explore');
|
||||
const { updateSplitSize, widthCalc } = useSplitSizeUpdater(MIN_PANE_WIDTH);
|
||||
|
||||
@ -51,9 +52,7 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor
|
||||
chrome.update({ sectionNav: navModel });
|
||||
}, [chrome, navModel]);
|
||||
|
||||
useEffect(() => {
|
||||
keybindings.setupTimeRangeBindings(false);
|
||||
}, [keybindings]);
|
||||
useKeyboardShortcuts();
|
||||
|
||||
return (
|
||||
<div className={styles.pageScrollbarWrapper}>
|
||||
|
114
public/app/features/explore/hooks/useKeyboardShortcuts.test.tsx
Normal file
114
public/app/features/explore/hooks/useKeyboardShortcuts.test.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { dateTime, EventBusSrv } from '@grafana/data';
|
||||
import { getAppEvents } from '@grafana/runtime';
|
||||
import { AbsoluteTimeEvent, ShiftTimeEvent, ShiftTimeEventDirection, ZoomOutEvent } from 'app/types/events';
|
||||
|
||||
import { TestProvider } from '../../../../test/helpers/TestProvider';
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
import { initialExploreState } from '../state/main';
|
||||
import { makeExplorePaneState } from '../state/utils';
|
||||
|
||||
import { useKeyboardShortcuts } from './useKeyboardShortcuts';
|
||||
|
||||
const testEventBus = new EventBusSrv();
|
||||
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
return {
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
reportInteraction: jest.fn(),
|
||||
getAppEvents: () => testEventBus,
|
||||
};
|
||||
});
|
||||
|
||||
const NOW = new Date('2020-10-10T00:00:00.000Z');
|
||||
function daysFromNow(daysDiff: number) {
|
||||
return new Date(NOW.getTime() + daysDiff * 86400000);
|
||||
}
|
||||
|
||||
function setup() {
|
||||
const store = configureStore({
|
||||
explore: {
|
||||
...initialExploreState,
|
||||
panes: {
|
||||
left: makeExplorePaneState({
|
||||
range: {
|
||||
from: dateTime(),
|
||||
to: dateTime(),
|
||||
raw: { from: 'now-1d', to: 'now' },
|
||||
},
|
||||
}),
|
||||
right: makeExplorePaneState({
|
||||
range: {
|
||||
from: dateTime(),
|
||||
to: dateTime(),
|
||||
raw: { from: 'now-2d', to: 'now' },
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const Wrapper = () => {
|
||||
useKeyboardShortcuts();
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
render(
|
||||
<TestProvider store={store}>
|
||||
<Wrapper />
|
||||
</TestProvider>
|
||||
);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('useKeyboardShortcuts', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers().setSystemTime(NOW);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('changes both panes to absolute time range', () => {
|
||||
const store = setup();
|
||||
|
||||
getAppEvents().publish(new AbsoluteTimeEvent({ updateUrl: false }));
|
||||
|
||||
const exploreState = store.getState().explore;
|
||||
const panes = Object.values(exploreState.panes);
|
||||
expect(panes[0]!.absoluteRange.from).toBe(daysFromNow(-1).getTime());
|
||||
expect(panes[0]!.absoluteRange.to).toBe(daysFromNow(0).getTime());
|
||||
expect(panes[1]!.absoluteRange.from).toBe(daysFromNow(-2).getTime());
|
||||
expect(panes[1]!.absoluteRange.to).toBe(daysFromNow(0).getTime());
|
||||
});
|
||||
|
||||
it('shifts time range in both panes', () => {
|
||||
const store = setup();
|
||||
|
||||
getAppEvents().publish(new ShiftTimeEvent({ direction: ShiftTimeEventDirection.Left }));
|
||||
|
||||
const exploreState = store.getState().explore;
|
||||
const panes = Object.values(exploreState.panes);
|
||||
expect(panes[0]!.absoluteRange.from).toBe(daysFromNow(-1.5).getTime());
|
||||
expect(panes[0]!.absoluteRange.to).toBe(daysFromNow(-0.5).getTime());
|
||||
expect(panes[1]!.absoluteRange.from).toBe(daysFromNow(-3).getTime());
|
||||
expect(panes[1]!.absoluteRange.to).toBe(daysFromNow(-1).getTime());
|
||||
});
|
||||
|
||||
it('zooms out the time range in both panes', () => {
|
||||
const store = setup();
|
||||
|
||||
getAppEvents().publish(new ZoomOutEvent({ scale: 2 }));
|
||||
|
||||
const exploreState = store.getState().explore;
|
||||
const panes = Object.values(exploreState.panes);
|
||||
expect(panes[0]!.absoluteRange.from).toBe(daysFromNow(-1.5).getTime());
|
||||
expect(panes[0]!.absoluteRange.to).toBe(daysFromNow(0.5).getTime());
|
||||
expect(panes[1]!.absoluteRange.from).toBe(daysFromNow(-3).getTime());
|
||||
expect(panes[1]!.absoluteRange.to).toBe(daysFromNow(1).getTime());
|
||||
});
|
||||
});
|
42
public/app/features/explore/hooks/useKeyboardShortcuts.ts
Normal file
42
public/app/features/explore/hooks/useKeyboardShortcuts.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
|
||||
import { getAppEvents } from '@grafana/runtime';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
import { useDispatch } from 'app/types';
|
||||
import { AbsoluteTimeEvent, ShiftTimeEvent, ZoomOutEvent } from 'app/types/events';
|
||||
|
||||
import { makeAbsoluteTime, shiftTime, zoomOut } from '../state/time';
|
||||
|
||||
export function useKeyboardShortcuts() {
|
||||
const { keybindings } = useGrafana();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
keybindings.setupTimeRangeBindings(false);
|
||||
|
||||
const tearDown: Unsubscribable[] = [];
|
||||
|
||||
tearDown.push(
|
||||
getAppEvents().subscribe(AbsoluteTimeEvent, () => {
|
||||
dispatch(makeAbsoluteTime());
|
||||
})
|
||||
);
|
||||
|
||||
tearDown.push(
|
||||
getAppEvents().subscribe(ShiftTimeEvent, (event) => {
|
||||
dispatch(shiftTime(event.payload.direction));
|
||||
})
|
||||
);
|
||||
|
||||
tearDown.push(
|
||||
getAppEvents().subscribe(ZoomOutEvent, (event) => {
|
||||
dispatch(zoomOut(event.payload.scale));
|
||||
})
|
||||
);
|
||||
|
||||
return () => {
|
||||
tearDown.forEach((u) => u.unsubscribe());
|
||||
};
|
||||
}, [dispatch, keybindings]);
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { EventBusSrv } from '@grafana/data';
|
||||
|
||||
import { changeDatasource } from './helper/interactions';
|
||||
import { makeLogsQueryResponse } from './helper/query';
|
||||
import { setupExplore, tearDown, waitForExplore } from './helper/setup';
|
||||
@ -10,6 +12,13 @@ jest.mock('../../correlations/utils', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const testEventBus = new EventBusSrv();
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getAppEvents: () => testEventBus,
|
||||
}));
|
||||
|
||||
describe('Explore: handle datasource states', () => {
|
||||
afterEach(() => {
|
||||
tearDown();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DataQueryRequest, serializeStateToUrlParam } from '@grafana/data';
|
||||
import { DataQueryRequest, EventBusSrv, serializeStateToUrlParam } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { LokiQuery } from '../../../plugins/datasource/loki/types';
|
||||
@ -8,10 +8,13 @@ import { LokiQuery } from '../../../plugins/datasource/loki/types';
|
||||
import { makeLogsQueryResponse } from './helper/query';
|
||||
import { setupExplore, waitForExplore } from './helper/setup';
|
||||
|
||||
const testEventBus = new EventBusSrv();
|
||||
|
||||
const fetch = jest.fn();
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => ({ fetch }),
|
||||
getAppEvents: () => testEventBus,
|
||||
}));
|
||||
|
||||
jest.mock('app/core/core', () => ({
|
||||
|
@ -1,10 +1,17 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { serializeStateToUrlParam } from '@grafana/data';
|
||||
import { EventBusSrv, serializeStateToUrlParam } from '@grafana/data';
|
||||
|
||||
import { makeLogsQueryResponse } from './helper/query';
|
||||
import { setupExplore, tearDown, waitForExplore } from './helper/setup';
|
||||
|
||||
const testEventBus = new EventBusSrv();
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getAppEvents: () => testEventBus,
|
||||
}));
|
||||
|
||||
jest.mock('../../correlations/utils', () => {
|
||||
return {
|
||||
getCorrelationsBySourceUIDs: jest.fn().mockReturnValue({ correlations: [] }),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { serializeStateToUrlParam } from '@grafana/data';
|
||||
import { EventBusSrv, serializeStateToUrlParam } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput';
|
||||
@ -13,13 +13,13 @@ import {
|
||||
assertQueryHistoryComment,
|
||||
assertQueryHistoryElementsShown,
|
||||
assertQueryHistoryExists,
|
||||
assertQueryHistoryIsEmpty,
|
||||
assertQueryHistoryIsStarred,
|
||||
assertQueryHistoryTabIsSelected,
|
||||
assertQueryHistoryIsEmpty,
|
||||
} from './helper/assert';
|
||||
import {
|
||||
commentQueryHistory,
|
||||
closeQueryHistory,
|
||||
commentQueryHistory,
|
||||
deleteQueryHistory,
|
||||
inputQuery,
|
||||
loadMoreQueryHistory,
|
||||
@ -37,12 +37,15 @@ const fetchMock = jest.fn();
|
||||
const postMock = jest.fn();
|
||||
const getMock = jest.fn();
|
||||
const reportInteractionMock = jest.fn();
|
||||
const testEventBus = new EventBusSrv();
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => ({ fetch: fetchMock, post: postMock, get: getMock }),
|
||||
reportInteraction: (...args: object[]) => {
|
||||
reportInteractionMock(...args);
|
||||
},
|
||||
getAppEvents: () => testEventBus,
|
||||
}));
|
||||
|
||||
jest.mock('app/core/core', () => ({
|
||||
|
@ -3,23 +3,21 @@ import userEvent from '@testing-library/user-event';
|
||||
import React, { ComponentProps } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { serializeStateToUrlParam } from '@grafana/data';
|
||||
import { EventBusSrv, serializeStateToUrlParam } from '@grafana/data';
|
||||
|
||||
import * as mainState from '../state/main';
|
||||
|
||||
import { makeLogsQueryResponse } from './helper/query';
|
||||
import { setupExplore, waitForExplore } from './helper/setup';
|
||||
|
||||
const testEventBus = new EventBusSrv();
|
||||
|
||||
jest.mock('app/core/core', () => {
|
||||
return {
|
||||
contextSrv: {
|
||||
hasPermission: () => true,
|
||||
hasAccess: () => true,
|
||||
},
|
||||
appEvents: {
|
||||
subscribe: () => {},
|
||||
publish: () => {},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@ -36,6 +34,7 @@ const fetch = jest.fn().mockResolvedValue({ correlations: [] });
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => ({ fetch }),
|
||||
getAppEvents: () => testEventBus,
|
||||
}));
|
||||
|
||||
jest.mock('rxjs', () => ({
|
||||
|
@ -7,17 +7,6 @@ import { ExploreItemState } from 'app/types';
|
||||
import { createDefaultInitialState } from './helpers';
|
||||
import { changeRangeAction, timeReducer, updateTime } from './time';
|
||||
|
||||
const MOCK_TIME_RANGE = {};
|
||||
|
||||
const mockTimeSrv = {
|
||||
init: jest.fn(),
|
||||
timeRange: jest.fn().mockReturnValue(MOCK_TIME_RANGE),
|
||||
};
|
||||
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
|
||||
...jest.requireActual('app/features/dashboard/services/TimeSrv'),
|
||||
getTimeSrv: () => mockTimeSrv,
|
||||
}));
|
||||
|
||||
const mockTemplateSrv = {
|
||||
updateTimeRange: jest.fn(),
|
||||
};
|
||||
@ -29,10 +18,10 @@ jest.mock('@grafana/runtime', () => ({
|
||||
describe('Explore item reducer', () => {
|
||||
describe('When time is updated', () => {
|
||||
it('Time service is re-initialized and template service is updated with the new time range', async () => {
|
||||
const { dispatch } = configureStore(createDefaultInitialState().defaultInitialState as any);
|
||||
const state = createDefaultInitialState().defaultInitialState as any;
|
||||
const { dispatch } = configureStore(state);
|
||||
dispatch(updateTime({ exploreId: 'left' }));
|
||||
expect(mockTimeSrv.init).toBeCalled();
|
||||
expect(mockTemplateSrv.updateTimeRange).toBeCalledWith(MOCK_TIME_RANGE);
|
||||
expect(mockTemplateSrv.updateTimeRange).toBeCalledWith(state.explore.panes.left.range);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -4,12 +4,10 @@ import { AbsoluteTimeRange, dateTimeForTimeZone, LoadingState, RawTimeRange, Tim
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { RefreshPicker } from '@grafana/ui';
|
||||
import { getTimeRange, refreshIntervalToSortOrder, stopQueryState } from 'app/core/utils/explore';
|
||||
import { getShiftedTimeRange, getZoomedTimeRange } from 'app/core/utils/timePicker';
|
||||
import { sortLogsResult } from 'app/features/logs/utils';
|
||||
import { getFiscalYearStartMonth, getTimeZone } from 'app/features/profile/state/selectors';
|
||||
import { ExploreItemState, ThunkResult } from 'app/types';
|
||||
|
||||
import { getTimeSrv } from '../../dashboard/services/TimeSrv';
|
||||
import { TimeModel } from '../../dashboard/state/TimeModel';
|
||||
import { ExploreItemState, ThunkDispatch, ThunkResult } from 'app/types';
|
||||
|
||||
import { syncTimesAction } from './main';
|
||||
import { runQueries } from './query';
|
||||
@ -79,21 +77,10 @@ export const updateTime = (config: {
|
||||
|
||||
const range = getTimeRange(timeZone, rawRange, fiscalYearStartMonth);
|
||||
const absoluteRange: AbsoluteTimeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
|
||||
const timeModel: TimeModel = {
|
||||
time: range.raw,
|
||||
refresh: false,
|
||||
timepicker: {},
|
||||
getTimezone: () => timeZone,
|
||||
timeRangeUpdated: (rawTimeRange: RawTimeRange) => {
|
||||
dispatch(updateTimeRange({ exploreId: exploreId, rawRange: rawTimeRange }));
|
||||
},
|
||||
};
|
||||
|
||||
// We need to re-initialize TimeSrv because it might have been triggered by the other Explore pane (when split)
|
||||
getTimeSrv().init(timeModel);
|
||||
// After re-initializing TimeSrv we need to update the time range in Template service for interpolation
|
||||
// of __from and __to variables
|
||||
getTemplateSrv().updateTimeRange(getTimeSrv().timeRange());
|
||||
getTemplateSrv().updateTimeRange(range);
|
||||
|
||||
dispatch(changeRangeAction({ exploreId, range, absoluteRange }));
|
||||
};
|
||||
@ -118,24 +105,51 @@ export function syncTimes(exploreId: string): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the timepicker's time into absolute time.
|
||||
* The conversion is applied to all Explore panes.
|
||||
* Useful to produce a bookmarkable URL that points to the same data.
|
||||
*/
|
||||
export function makeAbsoluteTime(): ThunkResult<void> {
|
||||
function modifyExplorePanesTimeRange(
|
||||
modifier: (
|
||||
exploreId: string,
|
||||
exploreItemState: ExploreItemState,
|
||||
currentTimeRange: TimeRange,
|
||||
dispatch: ThunkDispatch
|
||||
) => void
|
||||
): ThunkResult<void> {
|
||||
return (dispatch, getState) => {
|
||||
const timeZone = getTimeZone(getState().user);
|
||||
const fiscalYearStartMonth = getFiscalYearStartMonth(getState().user);
|
||||
|
||||
Object.entries(getState().explore.panes).forEach(([exploreId, exploreItemState]) => {
|
||||
const range = getTimeRange(timeZone, exploreItemState!.range.raw, fiscalYearStartMonth);
|
||||
const absoluteRange: AbsoluteTimeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
|
||||
dispatch(updateTime({ exploreId, absoluteRange }));
|
||||
modifier(exploreId, exploreItemState!, range, dispatch);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the timepicker's time into absolute time.
|
||||
* The conversion is applied to all Explore panes.
|
||||
* Useful to produce a bookmarkable URL that points to the same data.
|
||||
*/
|
||||
export function makeAbsoluteTime(): ThunkResult<void> {
|
||||
return modifyExplorePanesTimeRange((exploreId, exploreItemState, range, dispatch) => {
|
||||
const absoluteRange: AbsoluteTimeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
|
||||
dispatch(updateTimeRange({ exploreId, absoluteRange }));
|
||||
});
|
||||
}
|
||||
|
||||
export function shiftTime(direction: number): ThunkResult<void> {
|
||||
return modifyExplorePanesTimeRange((exploreId, exploreItemState, range, dispatch) => {
|
||||
const shiftedRange = getShiftedTimeRange(direction, range);
|
||||
dispatch(updateTimeRange({ exploreId, absoluteRange: shiftedRange }));
|
||||
});
|
||||
}
|
||||
|
||||
export function zoomOut(scale: number): ThunkResult<void> {
|
||||
return modifyExplorePanesTimeRange((exploreId, exploreItemState, range, dispatch) => {
|
||||
const zoomedRange = getZoomedTimeRange(range, scale);
|
||||
dispatch(updateTimeRange({ exploreId, absoluteRange: zoomedRange }));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reducer for an Explore area, to be used by the global Explore reducer.
|
||||
*/
|
||||
|
@ -3,18 +3,18 @@ import { uniq } from 'lodash';
|
||||
import {
|
||||
AbsoluteTimeRange,
|
||||
DataSourceApi,
|
||||
dateMath,
|
||||
DateTime,
|
||||
EventBusExtended,
|
||||
getDefaultTimeRange,
|
||||
HistoryItem,
|
||||
isDateTime,
|
||||
LoadingState,
|
||||
LogRowModel,
|
||||
PanelData,
|
||||
RawTimeRange,
|
||||
TimeFragment,
|
||||
TimeRange,
|
||||
dateMath,
|
||||
DateTime,
|
||||
isDateTime,
|
||||
toUtc,
|
||||
URLRange,
|
||||
URLRangeValue,
|
||||
@ -42,7 +42,7 @@ export const storeGraphStyle = (graphStyle: string): void => {
|
||||
/**
|
||||
* Returns a fresh Explore area state
|
||||
*/
|
||||
export const makeExplorePaneState = (): ExploreItemState => ({
|
||||
export const makeExplorePaneState = (overrides?: Partial<ExploreItemState>): ExploreItemState => ({
|
||||
containerWidth: 0,
|
||||
datasourceInstance: null,
|
||||
history: [],
|
||||
@ -73,6 +73,7 @@ export const makeExplorePaneState = (): ExploreItemState => ({
|
||||
supplementaryQueries: loadSupplementaryQueries(),
|
||||
panelsState: {},
|
||||
correlations: undefined,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
export const createEmptyQueryResponse = (): ExplorePanelData => ({
|
||||
|
Loading…
Reference in New Issue
Block a user