mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
3c6e0e8ef8
* 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
452 lines
14 KiB
TypeScript
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([]);
|
|
});
|
|
});
|
|
});
|