grafana/public/app/features/panel/panellinks/specs/link_srv.test.ts
Josh Hunt 3c6e0e8ef8
Chore: ESlint import order (#44959)
* Add and configure eslint-plugin-import

* Fix the lint:ts npm command

* Autofix + prettier all the files

* Manually fix remaining files

* Move jquery code in jest-setup to external file to safely reorder imports

* Resolve issue caused by circular dependencies within Prometheus

* Update .betterer.results

* Fix missing // @ts-ignore

* ignore iconBundle.ts

* Fix missing // @ts-ignore
2022-04-22 14:33:13 +01:00

452 lines
14 KiB
TypeScript

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([]);
});
});
});