Glue: Show data links only for fully interpolated correlations (#59052)

* Create data link filters for Explore

* Add comments and make the code more explicit
This commit is contained in:
Piotr Jamróz 2022-11-29 10:09:38 +01:00 committed by GitHub
parent 57fbe264ea
commit 454c8841a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 27 deletions

View File

@ -1,5 +1,6 @@
import {
ArrayVector,
DataFrame,
DataLink,
dateTime,
Field,
@ -7,9 +8,11 @@ import {
InterpolateFunction,
LinkModel,
TimeRange,
toDataFrame,
} from '@grafana/data';
import { setTemplateSrv } from '@grafana/runtime';
import { initTemplateSrv } from '../../../../test/helpers/initTemplateSrv';
import { setContextSrv } from '../../../core/services/context_srv';
import { setLinkSrv } from '../../panel/panellinks/link_srv';
@ -17,18 +20,13 @@ import { getFieldLinksForExplore } from './links';
describe('getFieldLinksForExplore', () => {
beforeEach(() => {
setTemplateSrv({
replace(target, scopedVars, format) {
return target ?? '';
},
getVariables() {
return [];
},
containsTemplate() {
return false;
},
updateTimeRange(timeRange: TimeRange) {},
});
setTemplateSrv(
initTemplateSrv('key', [
{ type: 'custom', name: 'emptyVar', current: { value: null } },
{ type: 'custom', name: 'num', current: { value: 1 } },
{ type: 'custom', name: 'test', current: { value: 'foo' } },
])
);
});
it('returns correct link model for external link', () => {
@ -36,7 +34,12 @@ describe('getFieldLinksForExplore', () => {
title: 'external',
url: 'http://regionalhost',
});
const links = getFieldLinksForExplore({ field, rowIndex: 0, splitOpenFn: jest.fn(), range });
const links = getFieldLinksForExplore({
field,
rowIndex: ROW_WITH_TEXT_VALUE.index,
splitOpenFn: jest.fn(),
range,
});
expect(links[0].href).toBe('http://regionalhost');
expect(links[0].title).toBe('external');
@ -47,7 +50,12 @@ describe('getFieldLinksForExplore', () => {
title: '',
url: 'http://regionalhost',
});
const links = getFieldLinksForExplore({ field, rowIndex: 0, splitOpenFn: jest.fn(), range });
const links = getFieldLinksForExplore({
field,
rowIndex: ROW_WITH_TEXT_VALUE.index,
splitOpenFn: jest.fn(),
range,
});
expect(links[0].href).toBe('http://regionalhost');
expect(links[0].title).toBe('regionalhost');
@ -69,7 +77,7 @@ describe('getFieldLinksForExplore', () => {
},
});
const splitfn = jest.fn();
const links = getFieldLinksForExplore({ field, rowIndex: 0, splitOpenFn: splitfn, range });
const links = getFieldLinksForExplore({ field, rowIndex: ROW_WITH_TEXT_VALUE.index, splitOpenFn: splitfn, range });
expect(links[0].href).toBe(
`/explore?left=${encodeURIComponent(
@ -102,7 +110,7 @@ describe('getFieldLinksForExplore', () => {
},
false
);
const links = getFieldLinksForExplore({ field, rowIndex: 0, range });
const links = getFieldLinksForExplore({ field, rowIndex: ROW_WITH_TEXT_VALUE.index, range });
expect(links[0].href).toBe('http://regionalhost');
expect(links[0].title).toBe('external');
@ -121,11 +129,42 @@ describe('getFieldLinksForExplore', () => {
},
false
);
const links = getFieldLinksForExplore({ field, rowIndex: 0, range });
const links = getFieldLinksForExplore({ field, rowIndex: ROW_WITH_TEXT_VALUE.index, range });
expect(links).toHaveLength(0);
});
it('returns internal links when target contains defined template variables', () => {
const { field, range, dataFrame } = setup({
title: '',
url: '',
internal: {
query: { query: 'query_1-${__data.fields.flux-dimensions}' },
datasourceUid: 'uid_1',
datasourceName: 'test_ds',
},
});
const links = getFieldLinksForExplore({ field, rowIndex: ROW_WITH_TEXT_VALUE.index, range, dataFrame });
expect(links).toHaveLength(1);
});
it('returns no internal links when target contains empty template variables', () => {
const { field, range, dataFrame } = setup({
title: '',
url: '',
internal: {
query: { query: 'query_1-${__data.fields.flux-dimensions}' },
datasourceUid: 'uid_1',
datasourceName: 'test_ds',
},
});
const links = getFieldLinksForExplore({ field, rowIndex: ROW_WITH_NULL_VALUE.index, range, dataFrame });
expect(links).toHaveLength(0);
});
});
const ROW_WITH_TEXT_VALUE = { value: 'foo', index: 0 };
const ROW_WITH_NULL_VALUE = { value: null, index: 1 };
function setup(link: DataLink, hasAccess = true) {
setLinkSrv({
getDataLinkUIModel(link: DataLink, replaceVariables: InterpolateFunction | undefined, origin: any): LinkModel<any> {
@ -148,15 +187,19 @@ function setup(link: DataLink, hasAccess = true) {
hasAccessToExplore: () => hasAccess,
} as any);
const field: Field<string> = {
const field: Field<string | null> = {
name: 'flux-dimensions',
type: FieldType.string,
values: new ArrayVector([]),
values: new ArrayVector([ROW_WITH_TEXT_VALUE.value, ROW_WITH_NULL_VALUE.value]),
config: {
links: [link],
},
};
const dataFrame: DataFrame = toDataFrame({
fields: [field],
});
const range: TimeRange = {
from: dateTime('2020-10-14T00:00:00'),
to: dateTime('2020-10-14T01:00:00'),
@ -166,5 +209,5 @@ function setup(link: DataLink, hasAccess = true) {
},
};
return { range, field };
return { range, field, dataFrame };
}

View File

@ -10,12 +10,44 @@ import {
DataFrame,
getFieldDisplayValuesProxy,
SplitOpen,
DataLink,
} from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv';
import { getLinkSrv } from '../../panel/panellinks/link_srv';
type DataLinkFilter = (link: DataLink, scopedVars: ScopedVars) => boolean;
const dataLinkHasRequiredPermissions = (link: DataLink) => {
return !link.internal || contextSrv.hasAccessToExplore();
};
const dataLinkHasAllVariablesDefined = (link: DataLink, scopedVars: ScopedVars) => {
let hasAllRequiredVarDefined = true;
if (link.internal) {
let stringifiedQuery = '';
try {
stringifiedQuery = JSON.stringify(link.internal.query || {});
// Hook into format function to verify if all values are non-empty
// Format function is run on all existing field values allowing us to check it's value is non-empty
getTemplateSrv().replace(stringifiedQuery, scopedVars, (f: string) => {
hasAllRequiredVarDefined = hasAllRequiredVarDefined && !!f;
return '';
});
} catch (err) {}
}
return hasAllRequiredVarDefined;
};
/**
* Fixed list of filters used in Explore. DataLinks that do not pass all the filters will not
* be passed back to the visualization.
*/
const DATA_LINK_FILTERS: DataLinkFilter[] = [dataLinkHasAllVariablesDefined, dataLinkHasRequiredPermissions];
/**
* Get links from the field of a dataframe and in addition check if there is associated
* metadata with datasource in which case we will add onClick to open the link in new split window. This assumes
@ -56,13 +88,9 @@ export const getFieldLinksForExplore = (options: {
}
if (field.config.links) {
const links = [];
if (!contextSrv.hasAccessToExplore()) {
links.push(...field.config.links.filter((l) => !l.internal));
} else {
links.push(...field.config.links);
}
const links = field.config.links.filter((link) => {
return DATA_LINK_FILTERS.every((filter) => filter(link, scopedVars));
});
return links.map((link) => {
if (!link.internal) {