mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 00:25:46 -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
|
// ... any other datasource-specific query parameters
|
||||||
}[]; // array of queries for this pane
|
}[]; // array of queries for this pane
|
||||||
range: {
|
range: {
|
||||||
from: string | number; // the start time, in milliseconds since epoch
|
from: string; // the start time, in milliseconds since epoch
|
||||||
to: string | number; // the end 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' });
|
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 () => {
|
it('uses the first query datasource if no root datasource is specified in the URL', async () => {
|
||||||
const { waitForNextUpdate, store } = setup({
|
const { waitForNextUpdate, store } = setup({
|
||||||
queryParams: {
|
queryParams: {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { identity, isEmpty, isEqual, isObject, mapValues, omitBy } from 'lodash';
|
import { identity, isEmpty, isEqual, isObject, mapValues, omitBy } from 'lodash';
|
||||||
import { useEffect, useRef } from 'react';
|
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 { DataQuery, DataSourceRef } from '@grafana/schema';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
import { clearQueryKeys, getLastUsedDatasourceUID } from 'app/core/utils/explore';
|
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 { runQueries, setQueriesAction } from '../../state/query';
|
||||||
import { selectPanes } from '../../state/selectors';
|
import { selectPanes } from '../../state/selectors';
|
||||||
import { changeRangeAction, updateTime } from '../../state/time';
|
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 { withUniqueRefIds } from '../../utils/queries';
|
||||||
import { isFulfilled } from '../utils';
|
import { isFulfilled } from '../utils';
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ export function useStateSync(params: ExploreQueryParams) {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (update.range) {
|
if (update.range) {
|
||||||
dispatch(updateTime({ exploreId, rawRange: range }));
|
dispatch(updateTime({ exploreId, rawRange: fromURLRange(range) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.queries) {
|
if (update.queries) {
|
||||||
@ -358,7 +358,7 @@ const urlDiff = (
|
|||||||
} => {
|
} => {
|
||||||
const datasource = !isEqual(currentUrlState?.datasource, oldUrlState?.datasource);
|
const datasource = !isEqual(currentUrlState?.datasource, oldUrlState?.datasource);
|
||||||
const queries = !isEqual(currentUrlState?.queries, oldUrlState?.queries);
|
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);
|
const panelsState = !isEqual(currentUrlState?.panelsState, oldUrlState?.panelsState);
|
||||||
|
|
||||||
return {
|
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 {
|
export function getUrlStateFromPaneState(pane: ExploreItemState): ExploreUrlState {
|
||||||
return {
|
return {
|
||||||
// datasourceInstance should not be undefined anymore here but in case there is some path for it to be undefined
|
// 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.
|
// lets just fallback instead of crashing.
|
||||||
datasource: pane.datasourceInstance?.uid || '',
|
datasource: pane.datasourceInstance?.uid || '',
|
||||||
queries: pane.queries.map(clearQueryKeys),
|
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
|
// don't include panelsState in the url unless a piece of state is actually set
|
||||||
panelsState: pruneObject(pane.panelsState),
|
panelsState: pruneObject(pane.panelsState),
|
||||||
};
|
};
|
||||||
@ -393,20 +400,3 @@ function pruneObject(obj: object): object | undefined {
|
|||||||
}
|
}
|
||||||
return pruned;
|
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 {
|
function parseRawTime(value: string | DateTime): TimeFragment | null {
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
return null;
|
return null;
|
||||||
|
Loading…
Reference in New Issue
Block a user