mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 16:15:42 -06:00
Explore: Fix parsing absolute range when URL changes (#72578)
* Fix parsing absolute range when URL changes * Update public/app/features/explore/hooks/useStateSync/index.test.tsx Co-authored-by: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> * Clean up docs * Clean up --------- Co-authored-by: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com>
This commit is contained in:
parent
95f8cc09c7
commit
922dd94997
@ -97,8 +97,8 @@ where:
|
||||
// ... any other datasource-specific query parameters
|
||||
}[]; // array of queries for this pane
|
||||
range: {
|
||||
from: string | number; // the start time, in milliseconds since epoch
|
||||
to: string | number; // the end time, in milliseconds since epoch
|
||||
from: string; // the start time, in milliseconds since epoch
|
||||
to: string; // the end time, in milliseconds since epoch
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -396,6 +396,40 @@ describe('useStateSync', () => {
|
||||
expect(store.getState().explore.panes['one']?.range.raw).toMatchObject({ from: 'now-6h', to: 'now' });
|
||||
});
|
||||
|
||||
it('Changes time range when the range in the URL is updated to absolute range', async () => {
|
||||
const { waitForNextUpdate, rerender, store } = setup({
|
||||
queryParams: {
|
||||
panes: JSON.stringify({
|
||||
one: { datasource: 'loki-uid', queries: [{ expr: 'a' }], range: { from: 'now-1h', to: 'now' } },
|
||||
}),
|
||||
schemaVersion: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(store.getState().explore.panes['one']?.range.raw).toMatchObject({ from: 'now-1h', to: 'now' });
|
||||
|
||||
rerender({
|
||||
children: null,
|
||||
params: {
|
||||
panes: JSON.stringify({
|
||||
one: {
|
||||
datasource: 'loki-uid',
|
||||
queries: [{ expr: 'a' }],
|
||||
range: { from: '1500000000000', to: '1500000001000' },
|
||||
},
|
||||
}),
|
||||
schemaVersion: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(store.getState().explore.panes['one']?.range.raw.from.valueOf().toString()).toEqual('1500000000000');
|
||||
expect(store.getState().explore.panes['one']?.range.raw.to.valueOf().toString()).toEqual('1500000001000');
|
||||
});
|
||||
|
||||
it('uses the first query datasource if no root datasource is specified in the URL', async () => {
|
||||
const { waitForNextUpdate, store } = setup({
|
||||
queryParams: {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { identity, isEmpty, isEqual, isObject, mapValues, omitBy } from 'lodash';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { CoreApp, ExploreUrlState, isDateTime, TimeRange, RawTimeRange, DataSourceApi } from '@grafana/data';
|
||||
import { CoreApp, ExploreUrlState, RawTimeRange, DataSourceApi } from '@grafana/data';
|
||||
import { DataQuery, DataSourceRef } from '@grafana/schema';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
import { clearQueryKeys, getLastUsedDatasourceUID } from 'app/core/utils/explore';
|
||||
@ -15,7 +15,7 @@ import { clearPanes, splitClose, splitOpen, syncTimesAction } from '../../state/
|
||||
import { runQueries, setQueriesAction } from '../../state/query';
|
||||
import { selectPanes } from '../../state/selectors';
|
||||
import { changeRangeAction, updateTime } from '../../state/time';
|
||||
import { DEFAULT_RANGE } from '../../state/utils';
|
||||
import { DEFAULT_RANGE, fromURLRange, toURLTimeRange } from '../../state/utils';
|
||||
import { withUniqueRefIds } from '../../utils/queries';
|
||||
import { isFulfilled } from '../utils';
|
||||
|
||||
@ -118,7 +118,7 @@ export function useStateSync(params: ExploreQueryParams) {
|
||||
})
|
||||
.then(() => {
|
||||
if (update.range) {
|
||||
dispatch(updateTime({ exploreId, rawRange: range }));
|
||||
dispatch(updateTime({ exploreId, rawRange: fromURLRange(range) }));
|
||||
}
|
||||
|
||||
if (update.queries) {
|
||||
@ -358,7 +358,7 @@ const urlDiff = (
|
||||
} => {
|
||||
const datasource = !isEqual(currentUrlState?.datasource, oldUrlState?.datasource);
|
||||
const queries = !isEqual(currentUrlState?.queries, oldUrlState?.queries);
|
||||
const range = !isEqual(currentUrlState?.range || DEFAULT_RANGE, oldUrlState?.range || DEFAULT_RANGE);
|
||||
const range = !areRangesEqual(currentUrlState?.range || DEFAULT_RANGE, oldUrlState?.range || DEFAULT_RANGE);
|
||||
const panelsState = !isEqual(currentUrlState?.panelsState, oldUrlState?.panelsState);
|
||||
|
||||
return {
|
||||
@ -369,13 +369,20 @@ const urlDiff = (
|
||||
};
|
||||
};
|
||||
|
||||
const areRangesEqual = (a: RawTimeRange, b: RawTimeRange): boolean => {
|
||||
const parsedA = toURLTimeRange(a);
|
||||
const parsedB = toURLTimeRange(b);
|
||||
|
||||
return parsedA.from === parsedB.from && parsedA.to === parsedB.to;
|
||||
};
|
||||
|
||||
export function getUrlStateFromPaneState(pane: ExploreItemState): ExploreUrlState {
|
||||
return {
|
||||
// datasourceInstance should not be undefined anymore here but in case there is some path for it to be undefined
|
||||
// lets just fallback instead of crashing.
|
||||
datasource: pane.datasourceInstance?.uid || '',
|
||||
queries: pane.queries.map(clearQueryKeys),
|
||||
range: toRawTimeRange(pane.range),
|
||||
range: toURLTimeRange(pane.range.raw),
|
||||
// don't include panelsState in the url unless a piece of state is actually set
|
||||
panelsState: pruneObject(pane.panelsState),
|
||||
};
|
||||
@ -393,20 +400,3 @@ function pruneObject(obj: object): object | undefined {
|
||||
}
|
||||
return pruned;
|
||||
}
|
||||
|
||||
const toRawTimeRange = (range: TimeRange): RawTimeRange => {
|
||||
let from = range.raw.from;
|
||||
if (isDateTime(from)) {
|
||||
from = from.valueOf().toString(10);
|
||||
}
|
||||
|
||||
let to = range.raw.to;
|
||||
if (isDateTime(to)) {
|
||||
to = to.valueOf().toString(10);
|
||||
}
|
||||
|
||||
return {
|
||||
from,
|
||||
to,
|
||||
};
|
||||
};
|
||||
|
@ -157,6 +157,41 @@ export function getRange(range: RawTimeRange, timeZone: TimeZone): TimeRange {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range RawTimeRange - Note: Range in the URL is not RawTimeRange compliant (see #72578 for more details)
|
||||
*/
|
||||
export function fromURLRange(range: RawTimeRange): RawTimeRange {
|
||||
let rawTimeRange: RawTimeRange = DEFAULT_RANGE;
|
||||
let parsedRange = {
|
||||
from: parseRawTime(range.from),
|
||||
to: parseRawTime(range.to),
|
||||
};
|
||||
if (parsedRange.from !== null && parsedRange.to !== null) {
|
||||
rawTimeRange = { from: parsedRange.from, to: parsedRange.to };
|
||||
}
|
||||
return rawTimeRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range RawTimeRange - Note: Range in the URL is not RawTimeRange compliant (see #72578 for more details)
|
||||
*/
|
||||
export const toURLTimeRange = (range: RawTimeRange): RawTimeRange => {
|
||||
let from = range.from;
|
||||
if (isDateTime(from)) {
|
||||
from = from.valueOf().toString();
|
||||
}
|
||||
|
||||
let to = range.to;
|
||||
if (isDateTime(to)) {
|
||||
to = to.valueOf().toString();
|
||||
}
|
||||
|
||||
return {
|
||||
from,
|
||||
to,
|
||||
};
|
||||
};
|
||||
|
||||
function parseRawTime(value: string | DateTime): TimeFragment | null {
|
||||
if (value === null) {
|
||||
return null;
|
||||
|
Loading…
Reference in New Issue
Block a user