Data Links: Store interpolated query in link models (#110912)
* Data Links: Store interpolated query in link models * Fix types * Fix type guard * Fix linting * Fix tests * Fix tests * Fix tests * Fix linting * Simplify the code * Linting * Linting * Revert redundant changes * Update test * Infer types * Linting * Fix null check * Rename prop * Rename prop * Rename prop * Drop interpolatedParams from span link * Drop interpolatedParams from span link
This commit is contained in:
@@ -102,6 +102,14 @@ export interface LinkModel<T = any> {
|
||||
// When a click callback exists, this is passed the raw mouse|react event
|
||||
onClick?: (e: any, origin?: any) => void;
|
||||
oneClick?: boolean;
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
interpolatedParams?: {
|
||||
query?: DataQuery;
|
||||
timeRange?: TimeRange;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,6 +48,14 @@ describe('mapInternalLinkToExplore', () => {
|
||||
title: 'dsName',
|
||||
href: `/explore?left=${encodeURIComponent('{"datasource":"uid","queries":[{"query":"12344"}]}')}`,
|
||||
onClick: undefined,
|
||||
interpolatedParams: {
|
||||
query: {
|
||||
query: '12344',
|
||||
datasource: {
|
||||
uid: 'uid',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -130,6 +138,14 @@ describe('mapInternalLinkToExplore', () => {
|
||||
replaceVariables: (val, scopedVars) => val.replace(/\$var/g, scopedVars!['var1']!.value),
|
||||
});
|
||||
|
||||
const query = {
|
||||
query: 'val1 val1',
|
||||
$var: 'foo',
|
||||
nested: { something: 'val1' },
|
||||
num: 1,
|
||||
arr: ['val1', 'non var'],
|
||||
};
|
||||
|
||||
expect(decodeURIComponent(link.href)).toEqual(
|
||||
`/explore?left=${JSON.stringify({
|
||||
range: {
|
||||
@@ -137,16 +153,17 @@ describe('mapInternalLinkToExplore', () => {
|
||||
to: DATE_AS_MS,
|
||||
},
|
||||
datasource: 'uid',
|
||||
queries: [
|
||||
{
|
||||
query: 'val1 val1',
|
||||
$var: 'foo',
|
||||
nested: { something: 'val1' },
|
||||
num: 1,
|
||||
arr: ['val1', 'non var'],
|
||||
},
|
||||
],
|
||||
queries: [query],
|
||||
})}`
|
||||
);
|
||||
|
||||
expect(link.interpolatedParams?.query).toEqual({
|
||||
datasource: {
|
||||
uid: 'uid',
|
||||
},
|
||||
...query,
|
||||
});
|
||||
|
||||
expect(link.interpolatedParams?.timeRange).toEqual(TIME_RANGE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,20 @@ export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkMod
|
||||
const interpolatedCorrelationData = interpolateObject(link.meta?.correlationData, scopedVars, replaceVariables);
|
||||
const title = link.title ? link.title : internalLink.datasourceName;
|
||||
|
||||
const interpolatedParams = interpolatedQuery
|
||||
? {
|
||||
query: {
|
||||
...interpolatedQuery,
|
||||
// data source is defined in a separate property in DataLink, we ensure it's put back together after interpolation
|
||||
datasource: {
|
||||
...interpolatedQuery.datasource,
|
||||
uid: internalLink.datasourceUid,
|
||||
},
|
||||
},
|
||||
...(range && { timeRange: range }),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
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
|
||||
@@ -72,6 +86,7 @@ export function mapInternalLinkToExplore(options: LinkToExploreOptions): LinkMod
|
||||
: undefined,
|
||||
target: link?.targetBlank ? '_blank' : '_self',
|
||||
origin: field,
|
||||
...(interpolatedParams && { interpolatedParams }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -8,12 +8,13 @@ type Props = {
|
||||
|
||||
export function ShareSpanButton(props: Props) {
|
||||
const { focusSpanLink } = props;
|
||||
const { interpolatedParams, ...linkProps } = focusSpanLink ?? {};
|
||||
return (
|
||||
<span>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
data-testid="share-span-button"
|
||||
{...focusSpanLink}
|
||||
{...linkProps}
|
||||
onClick={(e) => {
|
||||
// click handling logic copied from react router:
|
||||
// https://github.com/remix-run/react-router/blob/997b4d67e506d39ac6571cb369d6d2d6b3dda557/packages/react-router-dom/index.tsx#L392-L394s
|
||||
|
||||
@@ -688,7 +688,14 @@ describe('LogLineDetails', () => {
|
||||
if (field.config && field.config.links) {
|
||||
return field.config.links.map((link) => {
|
||||
return {
|
||||
href: '/explore?left=%7B%22range%22%3A%7B%22from%22%3A%22now-15m%22%2C%22to%22%3A%22now%22%7D%2C%22datasource%22%3A%22fetpfiwe8asqoe%22%2C%22queries%22%3A%5B%7B%22query%22%3A%22abcd1234%22%2C%22queryType%22%3A%22traceql%22%7D%5D%7D',
|
||||
href: '/explore',
|
||||
interpolatedParams: {
|
||||
query: {
|
||||
refId: 'A',
|
||||
query: 'abcd1234',
|
||||
queryType: 'traceql',
|
||||
},
|
||||
},
|
||||
title: 'tempo',
|
||||
target: '_blank',
|
||||
origin: field,
|
||||
@@ -750,7 +757,14 @@ describe('LogLineDetails', () => {
|
||||
if (field.config && field.config.links) {
|
||||
return field.config.links.map((link) => {
|
||||
return {
|
||||
href: '/explore?left=%7B%22range%22%3A%7B%22from%22%3A%22now-15m%22%2C%22to%22%3A%22now%22%7D%2C%22datasource%22%3A%22fetpfiwe8asqoe%22%2C%22queries%22%3A%5B%7B%22query%22%3A%22abcd1234%22%2C%22queryType%22%3A%22traceql%22%7D%5D%7D',
|
||||
href: '/explore',
|
||||
interpolatedParams: {
|
||||
query: {
|
||||
refId: 'A',
|
||||
query: 'abcd1234',
|
||||
queryType: 'traceql',
|
||||
},
|
||||
},
|
||||
title: 'tempo',
|
||||
target: '_blank',
|
||||
origin: field,
|
||||
|
||||
@@ -18,11 +18,16 @@ export function getTempoTraceFromLinks(fields: FieldDef[]) {
|
||||
}
|
||||
|
||||
function getTempoTraceFromLink(link: LinkModel) {
|
||||
const queryData = getDataSourceAndQueryFromLink(link);
|
||||
if (!queryData || queryData.queryType !== 'traceql') {
|
||||
return null;
|
||||
if (link.interpolatedParams?.query && isTempoQuery(link.interpolatedParams.query)) {
|
||||
const query = link.interpolatedParams.query;
|
||||
return {
|
||||
dsUID: query.datasource?.uid || '',
|
||||
query: query.query,
|
||||
queryType: query.queryType || '',
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return queryData;
|
||||
}
|
||||
|
||||
export type EmbeddedInternalLink = {
|
||||
@@ -31,31 +36,14 @@ export type EmbeddedInternalLink = {
|
||||
queryType: string;
|
||||
};
|
||||
|
||||
function getDataSourceAndQueryFromLink(link: LinkModel): EmbeddedInternalLink | null {
|
||||
if (!link.href) {
|
||||
return null;
|
||||
type TempoQuery = {
|
||||
query: string;
|
||||
queryType: string;
|
||||
};
|
||||
|
||||
const isTempoQuery = (query: unknown): query is TempoQuery => {
|
||||
if (!query || typeof query !== 'object') {
|
||||
return false;
|
||||
}
|
||||
const paramsStrings = link.href.split('?')[1];
|
||||
if (!paramsStrings) {
|
||||
return null;
|
||||
}
|
||||
const params = Object.values(Object.fromEntries(new URLSearchParams(paramsStrings)));
|
||||
try {
|
||||
const parsed = JSON.parse(params[0]);
|
||||
const dsUID: string = 'datasource' in parsed && parsed.datasource ? parsed.datasource.toString() : '';
|
||||
const query: string =
|
||||
'queries' in parsed && Array.isArray(parsed.queries) && 'query' in parsed.queries[0] && parsed.queries[0].query
|
||||
? parsed.queries[0].query.toString()
|
||||
: '';
|
||||
const queryType =
|
||||
'queryType' in parsed.queries[0] && parsed.queries[0].queryType ? parsed.queries[0].queryType.toString() : '';
|
||||
return dsUID && query && queryType
|
||||
? {
|
||||
dsUID,
|
||||
query,
|
||||
queryType,
|
||||
}
|
||||
: null;
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
return 'query' in query && 'queryType' in query;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user