123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- import { FieldType, locationUtil, toDataFrame, VariableOrigin } from '@grafana/data';
- import { setTemplateSrv } from '@grafana/runtime';
- import { getTimeSrv, setTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
- import { TemplateSrv } from 'app/features/templating/template_srv';
- import { variableAdapters } from 'app/features/variables/adapters';
- import { createQueryVariableAdapter } from 'app/features/variables/query/adapter';
- import { initTemplateSrv } from '../../../../../test/helpers/initTemplateSrv';
- import { updateConfig } from '../../../../core/config';
- import { getDataFrameVars, LinkSrv } from '../link_srv';
- jest.mock('app/core/core', () => ({
- appEvents: {
- subscribe: () => {},
- },
- }));
- describe('linkSrv', () => {
- let linkSrv: LinkSrv;
- let templateSrv: TemplateSrv;
- let originalTimeService: TimeSrv;
- function initLinkSrv() {
- const _dashboard: any = {
- time: { from: 'now-6h', to: 'now' },
- getTimezone: jest.fn(() => 'browser'),
- timeRangeUpdated: () => {},
- };
- const timeSrv = new TimeSrv({} as any);
- timeSrv.init(_dashboard);
- timeSrv.setTime({ from: 'now-1h', to: 'now' });
- _dashboard.refresh = false;
- setTimeSrv(timeSrv);
- templateSrv = initTemplateSrv('key', [
- { type: 'query', name: 'home', current: { value: '127.0.0.1' } },
- { type: 'query', name: 'server1', current: { value: '192.168.0.100' } },
- ]);
- setTemplateSrv(templateSrv);
- linkSrv = new LinkSrv();
- }
- beforeAll(() => {
- originalTimeService = getTimeSrv();
- variableAdapters.register(createQueryVariableAdapter());
- });
- beforeEach(() => {
- initLinkSrv();
- jest.resetAllMocks();
- });
- afterAll(() => {
- setTimeSrv(originalTimeService);
- });
- describe('getDataLinkUIModel', () => {
- describe('built in variables', () => {
- it('should not trim white space from data links', () => {
- expect(
- linkSrv.getDataLinkUIModel(
- {
- title: 'White space',
- url: 'www.google.com?query=some query',
- },
- (v) => v,
- {}
- ).href
- ).toEqual('www.google.com?query=some query');
- });
- it('should remove new lines from data link', () => {
- expect(
- linkSrv.getDataLinkUIModel(
- {
- title: 'New line',
- url: 'www.google.com?query=some\nquery',
- },
- (v) => v,
- {}
- ).href
- ).toEqual('www.google.com?query=somequery');
- });
- });
- describe('sanitization', () => {
- const url = "javascript:alert('broken!);";
- it.each`
- disableSanitizeHtml | expected
- ${true} | ${url}
- ${false} | ${'about:blank'}
- `(
- "when disable disableSanitizeHtml set to '$disableSanitizeHtml' then result should be '$expected'",
- ({ disableSanitizeHtml, expected }) => {
- updateConfig({
- disableSanitizeHtml,
- });
- const link = linkSrv.getDataLinkUIModel(
- {
- title: 'Any title',
- url,
- },
- (v) => v,
- {}
- ).href;
- expect(link).toBe(expected);
- }
- );
- });
- describe('Building links with root_url set', () => {
- it.each`
- url | appSubUrl | expected
- ${'/d/XXX'} | ${'/grafana'} | ${'/grafana/d/XXX'}
- ${'/grafana/d/XXX'} | ${'/grafana'} | ${'/grafana/d/XXX'}
- ${'d/whatever'} | ${'/grafana'} | ${'d/whatever'}
- ${'/d/XXX'} | ${''} | ${'/d/XXX'}
- ${'/grafana/d/XXX'} | ${''} | ${'/grafana/d/XXX'}
- ${'d/whatever'} | ${''} | ${'d/whatever'}
- `(
- "when link '$url' and config.appSubUrl set to '$appSubUrl' then result should be '$expected'",
- ({ url, appSubUrl, expected }) => {
- locationUtil.initialize({
- config: { appSubUrl } as any,
- getVariablesUrlParams: (() => {}) as any,
- getTimeRangeForUrl: (() => {}) as any,
- });
- const link = linkSrv.getDataLinkUIModel(
- {
- title: 'Any title',
- url,
- },
- (v) => v,
- {}
- ).href;
- expect(link).toBe(expected);
- }
- );
- });
- });
- describe('getAnchorInfo', () => {
- it('returns variable values for variable names in link.href and link.tooltip', () => {
- jest.spyOn(linkSrv, 'getLinkUrl');
- jest.spyOn(templateSrv, 'replace');
- expect(linkSrv.getLinkUrl).toBeCalledTimes(0);
- expect(templateSrv.replace).toBeCalledTimes(0);
- const link = linkSrv.getAnchorInfo({
- type: 'link',
- icon: 'dashboard',
- tags: [],
- url: '/graph?home=$home',
- title: 'Visit home',
- tooltip: 'Visit ${home:raw}',
- });
- expect(linkSrv.getLinkUrl).toBeCalledTimes(1);
- expect(templateSrv.replace).toBeCalledTimes(3);
- expect(link).toStrictEqual({ href: '/graph?home=127.0.0.1', title: 'Visit home', tooltip: 'Visit 127.0.0.1' });
- });
- });
- describe('getLinkUrl', () => {
- it('converts link urls', () => {
- const linkUrl = linkSrv.getLinkUrl({
- url: '/graph',
- });
- const linkUrlWithVar = linkSrv.getLinkUrl({
- url: '/graph?home=$home',
- });
- expect(linkUrl).toBe('/graph');
- expect(linkUrlWithVar).toBe('/graph?home=127.0.0.1');
- });
- it('appends current dashboard time range if keepTime is true', () => {
- const anchorInfoKeepTime = linkSrv.getLinkUrl({
- keepTime: true,
- url: '/graph',
- });
- expect(anchorInfoKeepTime).toBe('/graph?from=now-1h&to=now');
- });
- it('adds all variables to the url if includeVars is true', () => {
- const anchorInfoIncludeVars = linkSrv.getLinkUrl({
- includeVars: true,
- url: '/graph',
- });
- expect(anchorInfoIncludeVars).toBe('/graph?var-home=127.0.0.1&var-server1=192.168.0.100');
- });
- it('respects config disableSanitizeHtml', () => {
- const anchorInfo = {
- url: 'javascript:alert(document.domain)',
- };
- expect(linkSrv.getLinkUrl(anchorInfo)).toBe('about:blank');
- updateConfig({
- disableSanitizeHtml: true,
- });
- expect(linkSrv.getLinkUrl(anchorInfo)).toBe(anchorInfo.url);
- });
- });
- });
- describe('getDataFrameVars', () => {
- describe('when called with a DataFrame that contains fields without nested path', () => {
- it('then it should return correct suggestions', () => {
- const frame = toDataFrame({
- name: 'indoor',
- fields: [
- { name: 'time', type: FieldType.time, values: [1, 2, 3] },
- { name: 'temperature', type: FieldType.number, values: [10, 11, 12] },
- ],
- });
- const suggestions = getDataFrameVars([frame]);
- expect(suggestions).toEqual([
- {
- value: '__data.fields.time',
- label: 'time',
- documentation: `Formatted value for time on the same row`,
- origin: VariableOrigin.Fields,
- },
- {
- value: '__data.fields.temperature',
- label: 'temperature',
- documentation: `Formatted value for temperature on the same row`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields[0]`,
- label: `Select by index`,
- documentation: `Enter the field order`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields.temperature.numeric`,
- label: `Show numeric value`,
- documentation: `the numeric field value`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields.temperature.text`,
- label: `Show text value`,
- documentation: `the text value`,
- origin: VariableOrigin.Fields,
- },
- ]);
- });
- });
- describe('when called with a DataFrame that contains fields with nested path', () => {
- it('then it should return correct suggestions', () => {
- const frame = toDataFrame({
- name: 'temperatures',
- fields: [
- { name: 'time', type: FieldType.time, values: [1, 2, 3] },
- { name: 'temperature.indoor', type: FieldType.number, values: [10, 11, 12] },
- ],
- });
- const suggestions = getDataFrameVars([frame]);
- expect(suggestions).toEqual([
- {
- value: '__data.fields.time',
- label: 'time',
- documentation: `Formatted value for time on the same row`,
- origin: VariableOrigin.Fields,
- },
- {
- value: '__data.fields["temperature.indoor"]',
- label: 'temperature.indoor',
- documentation: `Formatted value for temperature.indoor on the same row`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields[0]`,
- label: `Select by index`,
- documentation: `Enter the field order`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields["temperature.indoor"].numeric`,
- label: `Show numeric value`,
- documentation: `the numeric field value`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields["temperature.indoor"].text`,
- label: `Show text value`,
- documentation: `the text value`,
- origin: VariableOrigin.Fields,
- },
- ]);
- });
- });
- describe('when called with a DataFrame that contains fields with displayName', () => {
- it('then it should return correct suggestions', () => {
- const frame = toDataFrame({
- name: 'temperatures',
- fields: [
- { name: 'time', type: FieldType.time, values: [1, 2, 3] },
- { name: 'temperature.indoor', type: FieldType.number, values: [10, 11, 12] },
- ],
- });
- frame.fields[1].config = { ...frame.fields[1].config, displayName: 'Indoor Temperature' };
- const suggestions = getDataFrameVars([frame]);
- expect(suggestions).toEqual([
- {
- value: '__data.fields.time',
- label: 'time',
- documentation: `Formatted value for time on the same row`,
- origin: VariableOrigin.Fields,
- },
- {
- value: '__data.fields["Indoor Temperature"]',
- label: 'Indoor Temperature',
- documentation: `Formatted value for Indoor Temperature on the same row`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields[0]`,
- label: `Select by index`,
- documentation: `Enter the field order`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields["Indoor Temperature"].numeric`,
- label: `Show numeric value`,
- documentation: `the numeric field value`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields["Indoor Temperature"].text`,
- label: `Show text value`,
- documentation: `the text value`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields["Indoor Temperature"]`,
- label: `Select by title`,
- documentation: `Use the title to pick the field`,
- origin: VariableOrigin.Fields,
- },
- ]);
- });
- });
- describe('when called with a DataFrame that contains fields with duplicate names', () => {
- it('then it should ignore duplicates', () => {
- const frame = toDataFrame({
- name: 'temperatures',
- fields: [
- { name: 'time', type: FieldType.time, values: [1, 2, 3] },
- { name: 'temperature.indoor', type: FieldType.number, values: [10, 11, 12] },
- { name: 'temperature.outdoor', type: FieldType.number, values: [20, 21, 22] },
- ],
- });
- frame.fields[1].config = { ...frame.fields[1].config, displayName: 'Indoor Temperature' };
- // Someone makes a mistake when renaming a field
- frame.fields[2].config = { ...frame.fields[2].config, displayName: 'Indoor Temperature' };
- const suggestions = getDataFrameVars([frame]);
- expect(suggestions).toEqual([
- {
- value: '__data.fields.time',
- label: 'time',
- documentation: `Formatted value for time on the same row`,
- origin: VariableOrigin.Fields,
- },
- {
- value: '__data.fields["Indoor Temperature"]',
- label: 'Indoor Temperature',
- documentation: `Formatted value for Indoor Temperature on the same row`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields[0]`,
- label: `Select by index`,
- documentation: `Enter the field order`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields["Indoor Temperature"].numeric`,
- label: `Show numeric value`,
- documentation: `the numeric field value`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields["Indoor Temperature"].text`,
- label: `Show text value`,
- documentation: `the text value`,
- origin: VariableOrigin.Fields,
- },
- {
- value: `__data.fields["Indoor Temperature"]`,
- label: `Select by title`,
- documentation: `Use the title to pick the field`,
- origin: VariableOrigin.Fields,
- },
- ]);
- });
- });
- describe('when called with multiple DataFrames', () => {
- it('it should not return any suggestions', () => {
- const frame1 = toDataFrame({
- name: 'server1',
- fields: [
- { name: 'time', type: FieldType.time, values: [1, 2, 3] },
- { name: 'value', type: FieldType.number, values: [10, 11, 12] },
- ],
- });
- const frame2 = toDataFrame({
- name: 'server2',
- fields: [
- { name: 'time', type: FieldType.time, values: [1, 2, 3] },
- { name: 'value', type: FieldType.number, values: [10, 11, 12] },
- ],
- });
- const suggestions = getDataFrameVars([frame1, frame2]);
- expect(suggestions).toEqual([]);
- });
- });
- });
|