From 70dd5b42ceec262f86ae28b2d8e55e602e0b7a0d Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 21 Dec 2022 05:09:31 -0800 Subject: [PATCH] DataLinks: Allow providing a dynamic data link builder (#60452) --- .betterer.results | 8 +- .../src/field/fieldOverrides.test.ts | 100 ++++++++++++++++-- .../grafana-data/src/field/fieldOverrides.ts | 15 ++- 3 files changed, 107 insertions(+), 16 deletions(-) diff --git a/.betterer.results b/.betterer.results index 5378bdcd3e1..242627a475f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -247,13 +247,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "24"], [0, 0, 0, "Unexpected any. Specify a different type.", "25"], [0, 0, 0, "Unexpected any. Specify a different type.", "26"], - [0, 0, 0, "Unexpected any. Specify a different type.", "27"], - [0, 0, 0, "Unexpected any. Specify a different type.", "28"], - [0, 0, 0, "Unexpected any. Specify a different type.", "29"], - [0, 0, 0, "Unexpected any. Specify a different type.", "30"], - [0, 0, 0, "Unexpected any. Specify a different type.", "31"], - [0, 0, 0, "Unexpected any. Specify a different type.", "32"], - [0, 0, 0, "Unexpected any. Specify a different type.", "33"] + [0, 0, 0, "Unexpected any. Specify a different type.", "27"] ], "packages/grafana-data/src/field/fieldOverrides.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/packages/grafana-data/src/field/fieldOverrides.test.ts b/packages/grafana-data/src/field/fieldOverrides.test.ts index 07a69a8bd58..f830ed1271a 100644 --- a/packages/grafana-data/src/field/fieldOverrides.test.ts +++ b/packages/grafana-data/src/field/fieldOverrides.test.ts @@ -9,6 +9,7 @@ import { FieldConfigPropertyItem, FieldConfigSource, FieldType, + GrafanaConfig, InterpolateFunction, ScopedVars, ThresholdsMode, @@ -566,9 +567,9 @@ describe('setDynamicConfigValue', () => { describe('getLinksSupplier', () => { it('will replace variables in url and title of the data link', () => { locationUtil.initialize({ - config: {} as any, - getVariablesUrlParams: (() => {}) as any, - getTimeRangeForUrl: (() => {}) as any, + config: {} as GrafanaConfig, + getVariablesUrlParams: () => ({}), + getTimeRangeForUrl: () => ({ from: 'now-7d', to: 'now' }), }); const f0 = new MutableDataFrame({ @@ -601,9 +602,9 @@ describe('getLinksSupplier', () => { it('handles internal links', () => { locationUtil.initialize({ - config: { appSubUrl: '' } as any, - getVariablesUrlParams: (() => {}) as any, - getTimeRangeForUrl: (() => {}) as any, + config: { appSubUrl: '' } as GrafanaConfig, + getVariablesUrlParams: () => ({}), + getTimeRangeForUrl: () => ({ from: 'now-7d', to: 'now' }), }); const datasourceUid = '1234'; @@ -651,6 +652,93 @@ describe('getLinksSupplier', () => { }) ); }); + + describe('dynamic links', () => { + beforeEach(() => { + locationUtil.initialize({ + config: {} as GrafanaConfig, + getVariablesUrlParams: () => ({}), + getTimeRangeForUrl: () => ({ from: 'now-7d', to: 'now' }), + }); + }); + it('handles link click handlers', () => { + const onClickSpy = jest.fn(); + const replaceSpy = jest.fn(); + const f0 = new MutableDataFrame({ + name: 'A', + fields: [ + { + name: 'message', + type: FieldType.string, + values: [10, 20], + config: { + links: [ + { + url: 'should not be ignored', + onClick: onClickSpy, + title: 'title to be interpolated', + }, + { + url: 'should not be ignored', + title: 'title to be interpolated', + }, + ], + }, + }, + ], + }); + + const supplier = getLinksSupplier(f0, f0.fields[0], {}, replaceSpy); + const links = supplier({}); + + expect(links.length).toBe(2); + expect(links[0].href).toEqual('should not be ignored'); + expect(links[0].onClick).toBeDefined(); + + links[0].onClick!({}); + + expect(onClickSpy).toBeCalledTimes(1); + }); + + it('handles links built dynamically', () => { + const replaceSpy = jest.fn().mockReturnValue('url interpolated 10'); + const onBuildUrlSpy = jest.fn(); + + const f0 = new MutableDataFrame({ + name: 'A', + fields: [ + { + name: 'message', + type: FieldType.string, + values: [10, 20], + config: { + links: [ + { + url: 'should be ignored', + onBuildUrl: () => { + onBuildUrlSpy(); + return 'url to be interpolated'; + }, + title: 'title to be interpolated', + }, + { + url: 'should not be ignored', + title: 'title to be interpolated', + }, + ], + }, + }, + ], + }); + + const supplier = getLinksSupplier(f0, f0.fields[0], {}, replaceSpy); + const links = supplier({}); + + expect(onBuildUrlSpy).toBeCalledTimes(1); + expect(links.length).toBe(2); + expect(links[0].href).toEqual('url interpolated 10'); + }); + }); }); describe('applyRawFieldOverrides', () => { diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index 5ca3c92becb..441635e69b8 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -450,9 +450,18 @@ export const getLinksSupplier = }); } - let href = locationUtil.assureBaseUrl(link.url.replace(/\n/g, '')); - href = replaceVariables(href, variables); - href = locationUtil.processUrl(href); + let href = link.onBuildUrl + ? link.onBuildUrl({ + origin: field, + replaceVariables, + }) + : link.url; + + if (href) { + locationUtil.assureBaseUrl(href.replace(/\n/g, '')); + href = replaceVariables(href, variables); + href = locationUtil.processUrl(href); + } const info: LinkModel = { href,