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:
Piotr Jamróz 2023-08-02 11:56:54 +02:00 committed by GitHub
parent 95f8cc09c7
commit 922dd94997
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 24 deletions

View File

@ -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
}
}
```

View File

@ -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: {

View File

@ -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,
};
};

View File

@ -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;