mirror of
https://github.com/grafana/grafana.git
synced 2025-09-22 00:02:53 +08:00
Explore: Improve parsing ranges from URL (#72498)
* Fix parsing absolute range * Silence warning issued by moment js * Introduce URLRangeValue to enforce better type-checking * Fix unit tests * Allow not passing range to generate ExploreUrl * Use updated time range format in a test * Allow empty object to be passed as a data link for backward compatibility * Update mocks * Post-merge fixes * Simplify checking if range is passed as an empty object * Update docs
This commit is contained in:
@ -847,8 +847,8 @@ describe('getLinksSupplier', () => {
|
||||
|
||||
const links = supplier({ valueRowIndex: 0 });
|
||||
const rangeStr = JSON.stringify({
|
||||
from: range.from.toISOString(),
|
||||
to: range.to.toISOString(),
|
||||
from: range.from.valueOf().toString(),
|
||||
to: range.to.valueOf().toString(),
|
||||
});
|
||||
const encodeURIParams = `{"range":${rangeStr},"datasource":"${datasourceUid}","queries":["12345"]}`;
|
||||
expect(links.length).toBe(1);
|
||||
|
@ -400,7 +400,7 @@ const defaultInternalLinkPostProcessor: DataLinkPostProcessor = (options) => {
|
||||
internalLink: link.internal,
|
||||
scopedVars: dataLinkScopedVars,
|
||||
field,
|
||||
range: link.internal.range ?? ({} as any),
|
||||
range: link.internal.range,
|
||||
replaceVariables,
|
||||
});
|
||||
} else {
|
||||
|
@ -1,15 +1,28 @@
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
|
||||
import { PreferredVisualisationType } from './data';
|
||||
import { RawTimeRange, TimeRange } from './time';
|
||||
import { TimeRange } from './time';
|
||||
|
||||
type AnyQuery = DataQuery & Record<string, any>;
|
||||
|
||||
// enforce type-incompatibility with RawTimeRange to ensure it's parsed and converted.
|
||||
// URLRangeValue may be a string representing UTC time in ms, which is not a compatible
|
||||
// value for RawTimeRange when used as a string (it could only be an ISO formatted date)
|
||||
export type URLRangeValue = string | { __brand: 'URL Range Value' };
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type URLRange = {
|
||||
from: URLRangeValue;
|
||||
to: URLRangeValue;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export interface ExploreUrlState<T extends DataQuery = AnyQuery> {
|
||||
datasource: string | null;
|
||||
queries: T[];
|
||||
range: RawTimeRange;
|
||||
range: URLRange;
|
||||
panelsState?: ExplorePanelsState;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,21 @@
|
||||
import { DateTime, toUtc } from '../datetime';
|
||||
import { DataLink, FieldType, TimeRange } from '../types';
|
||||
|
||||
import { mapInternalLinkToExplore } from './dataLinks';
|
||||
|
||||
const createTimeRange = (from: DateTime, to: DateTime): TimeRange => ({
|
||||
from,
|
||||
to,
|
||||
raw: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
});
|
||||
|
||||
const DATE_AS_DATE_TIME = toUtc([2000, 1, 1]);
|
||||
const DATE_AS_MS = '949363200000';
|
||||
const TIME_RANGE = createTimeRange(DATE_AS_DATE_TIME, DATE_AS_DATE_TIME);
|
||||
|
||||
describe('mapInternalLinkToExplore', () => {
|
||||
it('creates internal link', () => {
|
||||
const dataLink = {
|
||||
@ -18,7 +32,6 @@ describe('mapInternalLinkToExplore', () => {
|
||||
link: dataLink,
|
||||
internalLink: dataLink.internal,
|
||||
scopedVars: {},
|
||||
range: {} as unknown as TimeRange,
|
||||
field: {
|
||||
name: 'test',
|
||||
type: FieldType.number,
|
||||
@ -59,7 +72,6 @@ describe('mapInternalLinkToExplore', () => {
|
||||
link: dataLink,
|
||||
internalLink: dataLink.internal!,
|
||||
scopedVars: {},
|
||||
range: {} as unknown as TimeRange,
|
||||
field: {
|
||||
name: 'test',
|
||||
type: FieldType.number,
|
||||
@ -106,7 +118,7 @@ describe('mapInternalLinkToExplore', () => {
|
||||
scopedVars: {
|
||||
var1: { text: '', value: 'val1' },
|
||||
},
|
||||
range: {} as unknown as TimeRange,
|
||||
range: TIME_RANGE,
|
||||
field: {
|
||||
name: 'test',
|
||||
type: FieldType.number,
|
||||
@ -118,6 +130,10 @@ describe('mapInternalLinkToExplore', () => {
|
||||
|
||||
expect(decodeURIComponent(link.href)).toEqual(
|
||||
`/explore?left=${JSON.stringify({
|
||||
range: {
|
||||
from: DATE_AS_MS,
|
||||
to: DATE_AS_MS,
|
||||
},
|
||||
datasource: 'uid',
|
||||
queries: [
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
} from '../types';
|
||||
|
||||
import { locationUtil } from './location';
|
||||
import { serializeStateToUrlParam } from './url';
|
||||
import { serializeStateToUrlParam, toURLRange } from './url';
|
||||
|
||||
export const DataLinkBuiltInVars = {
|
||||
keepTime: '__url_time_range',
|
||||
@ -33,7 +33,7 @@ export const DataLinkBuiltInVars = {
|
||||
export type LinkToExploreOptions = {
|
||||
link: DataLink;
|
||||
scopedVars: ScopedVars;
|
||||
range: TimeRange;
|
||||
range?: TimeRange;
|
||||
field: Field;
|
||||
internalLink: InternalDataLink;
|
||||
onClickFn?: SplitOpen;
|
||||
@ -77,13 +77,16 @@ export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkMod
|
||||
function generateInternalHref<T extends DataQuery = any>(
|
||||
datasourceUid: string,
|
||||
query: T,
|
||||
range: TimeRange,
|
||||
range?: TimeRange,
|
||||
panelsState?: ExplorePanelsState
|
||||
): string {
|
||||
return locationUtil.assureBaseUrl(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
serializeStateToUrlParam({
|
||||
range: range.raw,
|
||||
// @deprecated mapInternalLinkToExplore required passing range. Some consumers to generate the URL
|
||||
// with defaults pass range as `{} as any`. This is why we need to check for `range?.raw` not just
|
||||
// `range ? ...` here. This behavior will be marked as deprecated in #72498
|
||||
...(range?.raw ? { range: toURLRange(range.raw) } : {}),
|
||||
datasource: datasourceUid,
|
||||
queries: [query],
|
||||
panelsState: panelsState,
|
||||
|
@ -16,7 +16,7 @@ export { PanelOptionsEditorBuilder, FieldConfigEditorBuilder } from './OptionsUI
|
||||
export { arrayUtils };
|
||||
export { getFlotPairs, getFlotPairsConstant } from './flotPairs';
|
||||
export { locationUtil } from './location';
|
||||
export { urlUtil, type UrlQueryMap, type UrlQueryValue, serializeStateToUrlParam } from './url';
|
||||
export { urlUtil, type UrlQueryMap, type UrlQueryValue, serializeStateToUrlParam, toURLRange } from './url';
|
||||
export { DataLinkBuiltInVars, mapInternalLinkToExplore } from './dataLinks';
|
||||
export { DocsId } from './docs';
|
||||
export { makeClassES5Compatible } from './makeClassES5Compatible';
|
||||
|
@ -2,6 +2,8 @@
|
||||
* @preserve jquery-param (c) 2015 KNOWLEDGECODE | MIT
|
||||
*/
|
||||
|
||||
import { isDateTime } from '../datetime';
|
||||
import { URLRange, RawTimeRange } from '../types';
|
||||
import { ExploreUrlState } from '../types/explore';
|
||||
|
||||
/**
|
||||
@ -200,14 +202,37 @@ export const urlUtil = {
|
||||
|
||||
/**
|
||||
* Create an string that is used in URL to represent the Explore state. This is basically just a stringified json
|
||||
* that is that used as a state of a single Explore pane so it does not represent full Explore URL.
|
||||
* that is used as a state of a single Explore pane so it does not represent full Explore URL so some properties
|
||||
* may be omitted (they will be filled in with default values).
|
||||
*
|
||||
* @param urlState
|
||||
* @param compact this parameter is deprecated and will be removed in a future release.
|
||||
*/
|
||||
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
|
||||
export function serializeStateToUrlParam(urlState: Partial<ExploreUrlState>, compact?: boolean): string {
|
||||
if (compact !== undefined) {
|
||||
console.warn('`compact` parameter is deprecated and will be removed in a future release');
|
||||
}
|
||||
return JSON.stringify(urlState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts RawTimeRange to a string that is stored in the URL
|
||||
* - relative - stays as it is (e.g. "now")
|
||||
* - absolute - converted to ms
|
||||
*/
|
||||
export const toURLRange = (range: RawTimeRange): URLRange => {
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
Reference in New Issue
Block a user