mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tempo / Trace Viewer: Implement deep linking to spans
This commit is contained in:
@@ -644,7 +644,7 @@ describe('getLinksSupplier', () => {
|
||||
expect(links[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'testDS',
|
||||
href: `/explore?left=${encodeURIComponent('{"datasource":"testDS","queries":["12345"]}')}`,
|
||||
href: `/explore?left=${encodeURIComponent('{"datasource":"testDS","queries":["12345"],"panelsState":{}}')}`,
|
||||
onClick: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DataQuery } from './query';
|
||||
import { InterpolateFunction } from './panel';
|
||||
import { ExplorePanelsState } from './explore';
|
||||
|
||||
/**
|
||||
* Callback info for DataLink click events
|
||||
@@ -44,6 +45,7 @@ export interface InternalDataLink<T extends DataQuery = any> {
|
||||
query: T;
|
||||
datasourceUid: string;
|
||||
datasourceName: string;
|
||||
panelsState?: ExplorePanelsState;
|
||||
}
|
||||
|
||||
export type LinkTarget = '_blank' | '_self' | undefined;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PreferredVisualisationType } from './data';
|
||||
import { DataQuery } from './query';
|
||||
import { RawTimeRange, TimeRange } from './time';
|
||||
|
||||
@@ -10,11 +11,20 @@ export interface ExploreUrlState<T extends DataQuery = AnyQuery> {
|
||||
range: RawTimeRange;
|
||||
originPanelId?: number;
|
||||
context?: string;
|
||||
panelsState?: ExplorePanelsState;
|
||||
}
|
||||
|
||||
export interface ExplorePanelsState extends Partial<Record<PreferredVisualisationType, {}>> {
|
||||
trace?: ExploreTracePanelState;
|
||||
}
|
||||
|
||||
export interface ExploreTracePanelState {
|
||||
spanId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SplitOpen type is used in Explore and related components.
|
||||
*/
|
||||
export type SplitOpen = <T extends DataQuery = any>(
|
||||
options?: { datasourceUid: string; query: T; range?: TimeRange } | undefined
|
||||
options?: { datasourceUid: string; query: T; range?: TimeRange; panelsState?: ExplorePanelsState } | undefined
|
||||
) => void;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mapInternalLinkToExplore } from './dataLinks';
|
||||
import { FieldType } from '../types';
|
||||
import { DataLink, FieldType } from '../types';
|
||||
import { ArrayVector } from '../vector';
|
||||
|
||||
describe('mapInternalLinkToExplore', () => {
|
||||
@@ -31,7 +31,52 @@ describe('mapInternalLinkToExplore', () => {
|
||||
expect(link).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'dsName',
|
||||
href: `/explore?left=${encodeURIComponent('{"datasource":"dsName","queries":[{"query":"12344"}]}')}`,
|
||||
href: `/explore?left=${encodeURIComponent(
|
||||
'{"datasource":"dsName","queries":[{"query":"12344"}],"panelsState":{}}'
|
||||
)}`,
|
||||
onClick: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('includes panels state', () => {
|
||||
const panelsState = {
|
||||
trace: {
|
||||
spanId: 'abcdef',
|
||||
},
|
||||
};
|
||||
|
||||
const dataLink: DataLink = {
|
||||
url: '',
|
||||
title: '',
|
||||
internal: {
|
||||
datasourceUid: 'uid',
|
||||
datasourceName: 'dsName',
|
||||
query: { query: '12344' },
|
||||
panelsState,
|
||||
},
|
||||
};
|
||||
|
||||
const link = mapInternalLinkToExplore({
|
||||
link: dataLink,
|
||||
internalLink: dataLink.internal!,
|
||||
scopedVars: {},
|
||||
range: {} as any,
|
||||
field: {
|
||||
name: 'test',
|
||||
type: FieldType.number,
|
||||
config: {},
|
||||
values: new ArrayVector([2]),
|
||||
},
|
||||
replaceVariables: (val) => val,
|
||||
});
|
||||
|
||||
expect(link).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'dsName',
|
||||
href: `/explore?left=${encodeURIComponent(
|
||||
'{"datasource":"dsName","queries":[{"query":"12344"}],"panelsState":{"trace":{"spanId":"abcdef"}}}'
|
||||
)}`,
|
||||
onClick: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
DataLink,
|
||||
DataQuery,
|
||||
ExplorePanelsState,
|
||||
Field,
|
||||
InternalDataLink,
|
||||
InterpolateFunction,
|
||||
LinkModel,
|
||||
ScopedVars,
|
||||
SplitOpen,
|
||||
TimeRange,
|
||||
} from '../types';
|
||||
import { locationUtil } from './location';
|
||||
@@ -33,26 +35,28 @@ export type LinkToExploreOptions = {
|
||||
range: TimeRange;
|
||||
field: Field;
|
||||
internalLink: InternalDataLink;
|
||||
onClickFn?: (options: { datasourceUid: string; query: any; range?: TimeRange }) => void;
|
||||
onClickFn?: SplitOpen;
|
||||
replaceVariables: InterpolateFunction;
|
||||
};
|
||||
|
||||
export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkModel<Field> {
|
||||
const { onClickFn, replaceVariables, link, scopedVars, range, field, internalLink } = options;
|
||||
|
||||
const interpolatedQuery = interpolateQuery(link, scopedVars, replaceVariables);
|
||||
const interpolatedQuery = interpolateObject(link.internal?.query, scopedVars, replaceVariables);
|
||||
const interpolatedPanelsState = interpolateObject(link.internal?.panelsState, scopedVars, replaceVariables);
|
||||
const title = link.title ? link.title : internalLink.datasourceName;
|
||||
|
||||
return {
|
||||
title: replaceVariables(title, scopedVars),
|
||||
// In this case this is meant to be internal link (opens split view by default) the href will also points
|
||||
// to explore but this way you can open it in new tab.
|
||||
href: generateInternalHref(internalLink.datasourceName, interpolatedQuery, range),
|
||||
href: generateInternalHref(internalLink.datasourceName, interpolatedQuery, range, interpolatedPanelsState),
|
||||
onClick: onClickFn
|
||||
? () => {
|
||||
onClickFn({
|
||||
datasourceUid: internalLink.datasourceUid,
|
||||
query: interpolatedQuery,
|
||||
panelsState: interpolatedPanelsState,
|
||||
range,
|
||||
});
|
||||
}
|
||||
@@ -65,26 +69,32 @@ export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkMod
|
||||
/**
|
||||
* Generates href for internal derived field link.
|
||||
*/
|
||||
function generateInternalHref<T extends DataQuery = any>(datasourceName: string, query: T, range: TimeRange): string {
|
||||
function generateInternalHref<T extends DataQuery = any>(
|
||||
datasourceName: string,
|
||||
query: T,
|
||||
range: TimeRange,
|
||||
panelsState?: ExplorePanelsState
|
||||
): string {
|
||||
return locationUtil.assureBaseUrl(
|
||||
`/explore?left=${encodeURIComponent(
|
||||
serializeStateToUrlParam({
|
||||
range: range.raw,
|
||||
datasource: datasourceName,
|
||||
queries: [query],
|
||||
panelsState: panelsState,
|
||||
})
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
function interpolateQuery<T extends DataQuery = any>(
|
||||
link: DataLink,
|
||||
function interpolateObject<T extends object>(
|
||||
object: T | undefined,
|
||||
scopedVars: ScopedVars,
|
||||
replaceVariables: InterpolateFunction
|
||||
): T {
|
||||
let stringifiedQuery = '';
|
||||
try {
|
||||
stringifiedQuery = JSON.stringify(link.internal?.query || '');
|
||||
stringifiedQuery = JSON.stringify(object || {});
|
||||
} catch (err) {
|
||||
// should not happen and not much to do about this, possibly something non stringifiable in the query
|
||||
console.error(err);
|
||||
|
||||
@@ -195,9 +195,23 @@ export const urlUtil = {
|
||||
parseKeyValue,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* There are 2 versions of this, normal and compact. Normal is just the same object stringified while compact turns
|
||||
* properties of the object into array where the order is significant.
|
||||
* @param urlState
|
||||
* @param compact
|
||||
*/
|
||||
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
|
||||
if (compact) {
|
||||
return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]);
|
||||
const compactState: unknown[] = [urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries];
|
||||
// only serialize panel state if we have at least one non-default panel configuration
|
||||
if (urlState.panelsState !== undefined) {
|
||||
compactState.push({ __panelsState: urlState.panelsState });
|
||||
}
|
||||
return JSON.stringify(compactState);
|
||||
}
|
||||
return JSON.stringify(urlState);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user