mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Link suppliers: getLinks API update (#29757)
* ContextMenuPlugin WIP * Remove Add annotations menu item from graph context menu * ts ifx * WIP * Tests updates * ts check fix * Fix rebase * Use replace function in angular graph data links
This commit is contained in:
parent
5f4b528122
commit
683ce69347
@ -4,6 +4,4 @@ export interface ScopedVar<T = any> {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ScopedVars {
|
||||
[key: string]: ScopedVar;
|
||||
}
|
||||
export interface ScopedVars extends Record<string, ScopedVar> {}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ScopedVars } from './ScopedVars';
|
||||
import { DataQuery } from './datasource';
|
||||
import { InterpolateFunction } from './panel';
|
||||
|
||||
/**
|
||||
* Callback info for DataLink click events
|
||||
*/
|
||||
export interface DataLinkClickEvent<T = any> {
|
||||
origin: T;
|
||||
scopedVars?: ScopedVars;
|
||||
replaceVariables: InterpolateFunction | undefined;
|
||||
e?: any; // mouse|react event
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ export interface LinkModel<T = any> {
|
||||
* TODO: ScopedVars in in GrafanaUI package!
|
||||
*/
|
||||
export interface LinkModelSupplier<T extends object> {
|
||||
getLinks(scopedVars?: any): Array<LinkModel<T>>;
|
||||
getLinks(replaceVariables?: InterpolateFunction): Array<LinkModel<T>>;
|
||||
}
|
||||
|
||||
export enum VariableOrigin {
|
||||
|
@ -1,7 +1,11 @@
|
||||
import React from 'react';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { setTemplateSrv } from '@grafana/runtime';
|
||||
import config from 'app/core/config';
|
||||
import { ShareLink, Props, State } from './ShareLink';
|
||||
import { initTemplateSrv } from '../../../../../test/helpers/initTemplateSrv';
|
||||
import { variableAdapters } from '../../../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../../../variables/query/adapter';
|
||||
|
||||
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
|
||||
getTimeSrv: () => ({
|
||||
@ -11,16 +15,6 @@ jest.mock('app/features/dashboard/services/TimeSrv', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
let fillVariableValuesForUrlMock = (params: any) => {};
|
||||
|
||||
jest.mock('app/features/templating/template_srv', () => ({
|
||||
getTemplateSrv: () => ({
|
||||
fillVariableValuesForUrl: (params: any) => {
|
||||
fillVariableValuesForUrlMock(params);
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
function mockLocationHref(href: string) {
|
||||
const location = window.location;
|
||||
|
||||
@ -98,6 +92,13 @@ function shareLinkScenario(description: string, scenarioFn: (ctx: ScenarioContex
|
||||
}
|
||||
|
||||
describe('ShareModal', () => {
|
||||
let templateSrv = initTemplateSrv([]);
|
||||
|
||||
beforeAll(() => {
|
||||
variableAdapters.register(createQueryVariableAdapter());
|
||||
setTemplateSrv(templateSrv);
|
||||
});
|
||||
|
||||
shareLinkScenario('shareUrl with current time range and panel', ctx => {
|
||||
ctx.setup(() => {
|
||||
mockLocationHref('http://server/#!/test');
|
||||
@ -174,33 +175,35 @@ describe('ShareModal', () => {
|
||||
expect(state?.imageUrl).toContain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
|
||||
});
|
||||
|
||||
it('should include template variables in url', async () => {
|
||||
mockLocationHref('http://server/#!/test');
|
||||
fillVariableValuesForUrlMock = (params: any) => {
|
||||
params['var-app'] = 'mupp';
|
||||
params['var-server'] = 'srv-01';
|
||||
};
|
||||
ctx.mount();
|
||||
ctx.wrapper?.setState({ includeTemplateVars: true });
|
||||
describe('template variables', () => {
|
||||
beforeEach(() => {
|
||||
templateSrv = initTemplateSrv([
|
||||
{ type: 'query', name: 'app', current: { value: 'mupp' } },
|
||||
{ type: 'query', name: 'server', current: { value: 'srv-01' } },
|
||||
]);
|
||||
setTemplateSrv(templateSrv);
|
||||
});
|
||||
|
||||
await ctx.wrapper?.instance().buildUrl();
|
||||
const state = ctx.wrapper?.state();
|
||||
expect(state?.shareUrl).toContain(
|
||||
'http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01'
|
||||
);
|
||||
});
|
||||
it('should include template variables in url', async () => {
|
||||
mockLocationHref('http://server/#!/test');
|
||||
ctx.mount();
|
||||
ctx.wrapper?.setState({ includeTemplateVars: true });
|
||||
|
||||
it('should shorten url', () => {
|
||||
mockLocationHref('http://server/#!/test');
|
||||
fillVariableValuesForUrlMock = (params: any) => {
|
||||
params['var-app'] = 'mupp';
|
||||
params['var-server'] = 'srv-01';
|
||||
};
|
||||
ctx.mount();
|
||||
ctx.wrapper?.setState({ includeTemplateVars: true, useShortUrl: true }, async () => {
|
||||
await ctx.wrapper?.instance().buildUrl();
|
||||
const state = ctx.wrapper?.state();
|
||||
expect(state?.shareUrl).toContain(`/goto/${mockUid}`);
|
||||
expect(state?.shareUrl).toContain(
|
||||
'http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01'
|
||||
);
|
||||
});
|
||||
|
||||
it('should shorten url', () => {
|
||||
mockLocationHref('http://server/#!/test');
|
||||
ctx.mount();
|
||||
ctx.wrapper?.setState({ includeTemplateVars: true, useShortUrl: true }, async () => {
|
||||
await ctx.wrapper?.instance().buildUrl();
|
||||
const state = ctx.wrapper?.state();
|
||||
expect(state?.shareUrl).toContain(`/goto/${mockUid}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { getTemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { createShortLink } from 'app/core/utils/shortLinks';
|
||||
import { PanelModel, dateTime, urlUtil } from '@grafana/data';
|
||||
import { getAllVariableValuesForUrl } from 'app/features/variables/getAllVariableValuesForUrl';
|
||||
|
||||
export function buildParams(
|
||||
useCurrentTimeRange: boolean,
|
||||
@ -10,7 +10,7 @@ export function buildParams(
|
||||
selectedTheme?: string,
|
||||
panel?: PanelModel
|
||||
) {
|
||||
const params = urlUtil.getUrlSearchParams();
|
||||
let params = urlUtil.getUrlSearchParams();
|
||||
|
||||
const range = getTimeSrv().timeRange();
|
||||
params.from = range.from.valueOf();
|
||||
@ -23,7 +23,10 @@ export function buildParams(
|
||||
}
|
||||
|
||||
if (includeTemplateVars) {
|
||||
getTemplateSrv().fillVariableValuesForUrl(params);
|
||||
params = {
|
||||
...params,
|
||||
...getAllVariableValuesForUrl(),
|
||||
};
|
||||
}
|
||||
|
||||
if (selectedTheme !== 'current') {
|
||||
|
@ -332,7 +332,6 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
dashboard={dashboard}
|
||||
title={panel.title}
|
||||
description={panel.description}
|
||||
scopedVars={panel.scopedVars}
|
||||
links={panel.links}
|
||||
error={errorMessage}
|
||||
isEditing={isEditing}
|
||||
|
@ -247,7 +247,6 @@ export class PanelChromeAngularUnconnected extends PureComponent<Props, State> {
|
||||
dashboard={dashboard}
|
||||
title={panel.title}
|
||||
description={panel.description}
|
||||
scopedVars={panel.scopedVars}
|
||||
angularComponent={angularComponent}
|
||||
links={panel.links}
|
||||
error={errorMessage}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { DataLink, LoadingState, PanelData, PanelMenuItem, QueryResultMetaNotice, ScopedVars } from '@grafana/data';
|
||||
import { AngularComponent, config, getTemplateSrv } from '@grafana/runtime';
|
||||
import { DataLink, LoadingState, PanelData, PanelMenuItem, QueryResultMetaNotice } from '@grafana/data';
|
||||
import { AngularComponent, config } from '@grafana/runtime';
|
||||
import { ClickOutsideWrapper, Icon, IconName, Tooltip, stylesFactory } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
@ -20,7 +20,6 @@ export interface Props {
|
||||
dashboard: DashboardModel;
|
||||
title?: string;
|
||||
description?: string;
|
||||
scopedVars?: ScopedVars;
|
||||
angularComponent?: AngularComponent | null;
|
||||
links?: DataLink[];
|
||||
error?: string;
|
||||
@ -148,9 +147,9 @@ export class PanelHeader extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { panel, scopedVars, error, isViewing, isEditing, data, alertState } = this.props;
|
||||
const { panel, error, isViewing, isEditing, data, alertState } = this.props;
|
||||
const { menuItems } = this.state;
|
||||
const title = getTemplateSrv().replace(panel.title, scopedVars, 'text');
|
||||
const title = panel.replaceVariables(panel.title, {}, 'text');
|
||||
|
||||
const panelHeaderClass = classNames({
|
||||
'panel-header': true,
|
||||
|
@ -46,7 +46,7 @@ export class PanelHeaderCorner extends Component<Props> {
|
||||
const markdown = panel.description || '';
|
||||
const interpolatedMarkdown = getTemplateSrv().replace(markdown, panel.scopedVars);
|
||||
const markedInterpolatedMarkdown = renderMarkdown(interpolatedMarkdown);
|
||||
const links = this.props.links && this.props.links.getLinks(panel);
|
||||
const links = this.props.links && this.props.links.getLinks(panel.replaceVariables);
|
||||
|
||||
return (
|
||||
<div className="panel-info-content markdown-html">
|
||||
|
@ -9,71 +9,42 @@ import {
|
||||
PanelData,
|
||||
FieldColorModeId,
|
||||
FieldColorConfigSettings,
|
||||
DataLinkBuiltInVars,
|
||||
VariableModel,
|
||||
} from '@grafana/data';
|
||||
import { ComponentClass } from 'react';
|
||||
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
|
||||
|
||||
class TablePanelCtrl {}
|
||||
|
||||
export const mockStandardProperties = () => {
|
||||
const unit = {
|
||||
id: 'unit',
|
||||
path: 'unit',
|
||||
name: 'Unit',
|
||||
description: 'Value units',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const decimals = {
|
||||
id: 'decimals',
|
||||
path: 'decimals',
|
||||
name: 'Decimals',
|
||||
description: 'Number of decimal to be shown for a value',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const boolean = {
|
||||
id: 'boolean',
|
||||
path: 'boolean',
|
||||
name: 'Boolean',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const fieldColor = {
|
||||
id: 'color',
|
||||
path: 'color',
|
||||
name: 'color',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
return [unit, decimals, boolean, fieldColor];
|
||||
};
|
||||
import { setTimeSrv } from '../services/TimeSrv';
|
||||
import { TemplateSrv } from '../../templating/template_srv';
|
||||
import { setTemplateSrv } from '@grafana/runtime';
|
||||
import { variableAdapters } from '../../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../../variables/query/adapter';
|
||||
|
||||
standardFieldConfigEditorRegistry.setInit(() => mockStandardProperties());
|
||||
standardEditorsRegistry.setInit(() => mockStandardProperties());
|
||||
|
||||
setTimeSrv({
|
||||
timeRangeForUrl: () => ({
|
||||
from: 1607687293000,
|
||||
to: 1607687293100,
|
||||
}),
|
||||
} as any);
|
||||
|
||||
setTemplateSrv(
|
||||
new TemplateSrv({
|
||||
// @ts-ignore
|
||||
getVariables: () => {
|
||||
return variablesMock;
|
||||
},
|
||||
// @ts-ignore
|
||||
getVariableWithName: (name: string) => {
|
||||
return variablesMock.filter(v => v.name === name)[0];
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
variableAdapters.setInit(() => [createQueryVariableAdapter()]);
|
||||
|
||||
describe('PanelModel', () => {
|
||||
describe('when creating new panel model', () => {
|
||||
let model: any;
|
||||
@ -147,7 +118,7 @@ describe('PanelModel', () => {
|
||||
id: 'table',
|
||||
},
|
||||
(null as unknown) as ComponentClass<PanelProps>, // react
|
||||
TablePanelCtrl // angular
|
||||
{} // angular
|
||||
);
|
||||
|
||||
panelPlugin.setPanelOptions(builder => {
|
||||
@ -240,6 +211,16 @@ describe('PanelModel', () => {
|
||||
expect(out).toBe('hello AAA');
|
||||
});
|
||||
|
||||
it('should interpolate $__url_time_range variable', () => {
|
||||
const out = model.replaceVariables(`/d/1?$${DataLinkBuiltInVars.keepTime}`);
|
||||
expect(out).toBe('/d/1?from=1607687293000&to=1607687293100');
|
||||
});
|
||||
|
||||
it('should interpolate $__all_variables variable', () => {
|
||||
const out = model.replaceVariables(`/d/1?$${DataLinkBuiltInVars.includeVars}`);
|
||||
expect(out).toBe('/d/1?var-test1=val1&var-test2=val2');
|
||||
});
|
||||
|
||||
it('should prefer the local variable value', () => {
|
||||
const extra = { aaa: { text: '???', value: 'XXX' } };
|
||||
const out = model.replaceVariables('hello $aaa and $bbb', extra);
|
||||
@ -468,3 +449,84 @@ describe('PanelModel', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export const mockStandardProperties = () => {
|
||||
const unit = {
|
||||
id: 'unit',
|
||||
path: 'unit',
|
||||
name: 'Unit',
|
||||
description: 'Value units',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const decimals = {
|
||||
id: 'decimals',
|
||||
path: 'decimals',
|
||||
name: 'Decimals',
|
||||
description: 'Number of decimal to be shown for a value',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const boolean = {
|
||||
id: 'boolean',
|
||||
path: 'boolean',
|
||||
name: 'Boolean',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
const fieldColor = {
|
||||
id: 'color',
|
||||
path: 'color',
|
||||
name: 'color',
|
||||
description: '',
|
||||
// @ts-ignore
|
||||
editor: () => null,
|
||||
// @ts-ignore
|
||||
override: () => null,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: () => true,
|
||||
};
|
||||
|
||||
return [unit, decimals, boolean, fieldColor];
|
||||
};
|
||||
|
||||
const variablesMock = [
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test1',
|
||||
label: 'Test1',
|
||||
hide: false,
|
||||
current: { value: 'val1' },
|
||||
skipUrlSync: false,
|
||||
getValueForUrl: function() {
|
||||
return 'val1';
|
||||
},
|
||||
} as VariableModel,
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test2',
|
||||
label: 'Test2',
|
||||
hide: false,
|
||||
current: { value: 'val2' },
|
||||
skipUrlSync: false,
|
||||
getValueForUrl: function() {
|
||||
return 'val2';
|
||||
},
|
||||
} as VariableModel,
|
||||
];
|
||||
|
@ -22,11 +22,15 @@ import {
|
||||
EventBusExtended,
|
||||
EventBusSrv,
|
||||
DataFrameDTO,
|
||||
urlUtil,
|
||||
DataLinkBuiltInVars,
|
||||
} from '@grafana/data';
|
||||
import { EDIT_PANEL_ID } from 'app/core/constants';
|
||||
import config from 'app/core/config';
|
||||
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
|
||||
import { PanelOptionsChangedEvent, PanelQueriesChangedEvent, PanelTransformationsChangedEvent } from 'app/types/events';
|
||||
import { getTimeSrv } from '../services/TimeSrv';
|
||||
import { getAllVariableValuesForUrl } from '../../variables/getAllVariableValuesForUrl';
|
||||
|
||||
export interface GridPos {
|
||||
x: number;
|
||||
@ -496,11 +500,28 @@ export class PanelModel implements DataConfigSource {
|
||||
this.events.publish(new PanelTransformationsChangedEvent());
|
||||
}
|
||||
|
||||
replaceVariables(value: string, extraVars?: ScopedVars, format?: string) {
|
||||
replaceVariables(value: string, extraVars: ScopedVars | undefined, format?: string | Function) {
|
||||
let vars = this.scopedVars;
|
||||
|
||||
if (extraVars) {
|
||||
vars = vars ? { ...vars, ...extraVars } : extraVars;
|
||||
}
|
||||
const allVariablesParams = getAllVariableValuesForUrl(vars);
|
||||
const variablesQuery = urlUtil.toUrlParams(allVariablesParams);
|
||||
const timeRangeUrl = urlUtil.toUrlParams(getTimeSrv().timeRangeForUrl());
|
||||
|
||||
vars = {
|
||||
...vars,
|
||||
[DataLinkBuiltInVars.keepTime]: {
|
||||
text: timeRangeUrl,
|
||||
value: timeRangeUrl,
|
||||
},
|
||||
[DataLinkBuiltInVars.includeVars]: {
|
||||
text: variablesQuery,
|
||||
value: variablesQuery,
|
||||
},
|
||||
};
|
||||
|
||||
return getTemplateSrv().replace(value, vars, format);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,14 @@
|
||||
import { getFieldLinksForExplore } from './links';
|
||||
import { ArrayVector, DataLink, dateTime, Field, FieldType, LinkModel, ScopedVars, TimeRange } from '@grafana/data';
|
||||
import {
|
||||
ArrayVector,
|
||||
DataLink,
|
||||
dateTime,
|
||||
Field,
|
||||
FieldType,
|
||||
InterpolateFunction,
|
||||
LinkModel,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { setLinkSrv } from '../../panel/panellinks/link_srv';
|
||||
|
||||
describe('getFieldLinksForExplore', () => {
|
||||
@ -57,7 +66,7 @@ describe('getFieldLinksForExplore', () => {
|
||||
|
||||
function setup(link: DataLink) {
|
||||
setLinkSrv({
|
||||
getDataLinkUIModel(link: DataLink, scopedVars: ScopedVars, origin: any): LinkModel<any> {
|
||||
getDataLinkUIModel(link: DataLink, replaceVariables: InterpolateFunction | undefined, origin: any): LinkModel<any> {
|
||||
return {
|
||||
href: link.url,
|
||||
title: link.title,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Field, LinkModel, TimeRange, mapInternalLinkToExplore } from '@grafana/data';
|
||||
import { Field, LinkModel, TimeRange, mapInternalLinkToExplore, InterpolateFunction } from '@grafana/data';
|
||||
import { getLinkSrv } from '../../panel/panellinks/link_srv';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { splitOpen } from '../state/main';
|
||||
@ -27,7 +27,10 @@ export const getFieldLinksForExplore = (
|
||||
return field.config.links
|
||||
? field.config.links.map(link => {
|
||||
if (!link.internal) {
|
||||
const linkModel = getLinkSrv().getDataLinkUIModel(link, scopedVars, field);
|
||||
const replace: InterpolateFunction = (value, vars) =>
|
||||
getTemplateSrv().replace(value, { ...vars, ...scopedVars });
|
||||
|
||||
const linkModel = getLinkSrv().getDataLinkUIModel(link, replace, field);
|
||||
if (!linkModel.title) {
|
||||
linkModel.title = getTitleFromHref(linkModel.href);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { getTheme } from '@grafana/ui';
|
||||
|
||||
describe('getFieldLinksSupplier', () => {
|
||||
let originalLinkSrv: LinkService;
|
||||
let templateSrv = new TemplateSrv();
|
||||
beforeAll(() => {
|
||||
// We do not need more here and TimeSrv is hard to setup fully.
|
||||
const timeSrvMock: TimeSrv = {
|
||||
@ -18,6 +19,7 @@ describe('getFieldLinksSupplier', () => {
|
||||
} as any;
|
||||
const linkService = new LinkSrv(new TemplateSrv(), timeSrvMock);
|
||||
originalLinkSrv = getLinkSrv();
|
||||
|
||||
setLinkSrv(linkService);
|
||||
});
|
||||
|
||||
@ -108,7 +110,7 @@ describe('getFieldLinksSupplier', () => {
|
||||
};
|
||||
|
||||
const supplier = getFieldLinksSupplier(fieldDisp);
|
||||
const links = supplier?.getLinks({}).map(m => {
|
||||
const links = supplier?.getLinks(templateSrv.replace.bind(templateSrv)).map(m => {
|
||||
return {
|
||||
title: m.title,
|
||||
href: m.href,
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
formattedValueToString,
|
||||
getFieldDisplayValuesProxy,
|
||||
getTimeField,
|
||||
InterpolateFunction,
|
||||
Labels,
|
||||
LinkModelSupplier,
|
||||
ScopedVar,
|
||||
@ -38,11 +39,11 @@ interface DataViewVars {
|
||||
fields?: Record<string, DisplayValue>;
|
||||
}
|
||||
|
||||
interface DataLinkScopedVars {
|
||||
__series?: ScopedVar<SeriesVars>;
|
||||
__field?: ScopedVar<FieldVars>;
|
||||
__value?: ScopedVar<ValueVars>;
|
||||
__data?: ScopedVar<DataViewVars>;
|
||||
interface DataLinkScopedVars extends ScopedVars {
|
||||
__series: ScopedVar<SeriesVars>;
|
||||
__field: ScopedVar<FieldVars>;
|
||||
__value: ScopedVar<ValueVars>;
|
||||
__data: ScopedVar<DataViewVars>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,10 +56,8 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
|
||||
}
|
||||
|
||||
return {
|
||||
getLinks: (existingScopedVars?: any) => {
|
||||
const scopedVars: DataLinkScopedVars = {
|
||||
...(existingScopedVars ?? {}),
|
||||
};
|
||||
getLinks: (replaceVariables: InterpolateFunction) => {
|
||||
const scopedVars: Partial<DataLinkScopedVars> = {};
|
||||
|
||||
if (value.view) {
|
||||
const { dataFrame } = value.view;
|
||||
@ -124,15 +123,23 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
|
||||
console.log('VALUE', value);
|
||||
}
|
||||
|
||||
const replace: InterpolateFunction = (value: string, vars: ScopedVars | undefined, fmt?: string | Function) => {
|
||||
const finalVars: ScopedVars = {
|
||||
...(scopedVars as ScopedVars),
|
||||
...vars,
|
||||
};
|
||||
return replaceVariables(value, finalVars, fmt);
|
||||
};
|
||||
|
||||
return links.map((link: DataLink) => {
|
||||
return getLinkSrv().getDataLinkUIModel(link, scopedVars as ScopedVars, value);
|
||||
return getLinkSrv().getDataLinkUIModel(link, replace, value);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getPanelLinksSupplier = (value: PanelModel): LinkModelSupplier<PanelModel> | undefined => {
|
||||
const links = value.links;
|
||||
export const getPanelLinksSupplier = (panel: PanelModel): LinkModelSupplier<PanelModel> | undefined => {
|
||||
const links = panel.links;
|
||||
|
||||
if (!links || links.length === 0) {
|
||||
return undefined;
|
||||
@ -141,7 +148,7 @@ export const getPanelLinksSupplier = (value: PanelModel): LinkModelSupplier<Pane
|
||||
return {
|
||||
getLinks: () => {
|
||||
return links.map(link => {
|
||||
return getLinkSrv().getDataLinkUIModel(link, value.scopedVars, value);
|
||||
return getLinkSrv().getDataLinkUIModel(link, panel.replaceVariables, panel);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
Field,
|
||||
FieldType,
|
||||
getFieldDisplayName,
|
||||
InterpolateFunction,
|
||||
KeyValue,
|
||||
LinkModel,
|
||||
locationUtil,
|
||||
@ -23,6 +24,7 @@ import {
|
||||
VariableSuggestion,
|
||||
VariableSuggestionsScope,
|
||||
} from '@grafana/data';
|
||||
import { getAllVariableValuesForUrl } from '../../variables/getAllVariableValuesForUrl';
|
||||
|
||||
const timeRangeVars = [
|
||||
{
|
||||
@ -253,7 +255,7 @@ export const getPanelOptionsVariableSuggestions = (plugin: PanelPlugin, data?: D
|
||||
};
|
||||
|
||||
export interface LinkService {
|
||||
getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars | undefined, origin: T) => LinkModel<T>;
|
||||
getDataLinkUIModel: <T>(link: DataLink, replaceVariables: InterpolateFunction | undefined, origin: T) => LinkModel<T>;
|
||||
getAnchorInfo: (link: any) => any;
|
||||
getLinkUrl: (link: any) => string;
|
||||
}
|
||||
@ -264,7 +266,7 @@ export class LinkSrv implements LinkService {
|
||||
|
||||
getLinkUrl(link: any) {
|
||||
let url = locationUtil.assureBaseUrl(this.templateSrv.replace(link.url || ''));
|
||||
const params: { [key: string]: any } = {};
|
||||
let params: { [key: string]: any } = {};
|
||||
|
||||
if (link.keepTime) {
|
||||
const range = this.timeSrv.timeRangeForUrl();
|
||||
@ -273,7 +275,10 @@ export class LinkSrv implements LinkService {
|
||||
}
|
||||
|
||||
if (link.includeVars) {
|
||||
this.templateSrv.fillVariableValuesForUrl(params);
|
||||
params = {
|
||||
...params,
|
||||
...getAllVariableValuesForUrl(),
|
||||
};
|
||||
}
|
||||
|
||||
url = urlUtil.appendQueryToUrl(url, urlUtil.toUrlParams(params));
|
||||
@ -290,16 +295,17 @@ export class LinkSrv implements LinkService {
|
||||
/**
|
||||
* Returns LinkModel which is basically a DataLink with all values interpolated through the templateSrv.
|
||||
*/
|
||||
getDataLinkUIModel = <T>(link: DataLink, scopedVars: ScopedVars | undefined, origin: T): LinkModel<T> => {
|
||||
const params: KeyValue = {};
|
||||
const timeRangeUrl = urlUtil.toUrlParams(this.timeSrv.timeRangeForUrl());
|
||||
|
||||
getDataLinkUIModel = <T>(
|
||||
link: DataLink,
|
||||
replaceVariables: InterpolateFunction | undefined,
|
||||
origin: T
|
||||
): LinkModel<T> => {
|
||||
let href = link.url;
|
||||
|
||||
if (link.onBuildUrl) {
|
||||
href = link.onBuildUrl({
|
||||
origin,
|
||||
scopedVars,
|
||||
replaceVariables,
|
||||
});
|
||||
}
|
||||
|
||||
@ -310,7 +316,7 @@ export class LinkSrv implements LinkService {
|
||||
if (link.onClick) {
|
||||
link.onClick({
|
||||
origin,
|
||||
scopedVars,
|
||||
replaceVariables,
|
||||
e,
|
||||
});
|
||||
}
|
||||
@ -319,27 +325,15 @@ export class LinkSrv implements LinkService {
|
||||
|
||||
const info: LinkModel<T> = {
|
||||
href: locationUtil.assureBaseUrl(href.replace(/\n/g, '')),
|
||||
title: this.templateSrv.replace(link.title || '', scopedVars),
|
||||
title: replaceVariables ? replaceVariables(link.title || '') : link.title,
|
||||
target: link.targetBlank ? '_blank' : '_self',
|
||||
origin,
|
||||
onClick,
|
||||
};
|
||||
|
||||
this.templateSrv.fillVariableValuesForUrl(params, scopedVars);
|
||||
|
||||
const variablesQuery = urlUtil.toUrlParams(params);
|
||||
|
||||
info.href = this.templateSrv.replace(info.href, {
|
||||
...scopedVars,
|
||||
[DataLinkBuiltInVars.keepTime]: {
|
||||
text: timeRangeUrl,
|
||||
value: timeRangeUrl,
|
||||
},
|
||||
[DataLinkBuiltInVars.includeVars]: {
|
||||
text: variablesQuery,
|
||||
value: variablesQuery,
|
||||
},
|
||||
});
|
||||
if (replaceVariables) {
|
||||
info.href = replaceVariables(info.href);
|
||||
}
|
||||
|
||||
info.href = getConfig().disableSanitizeHtml ? info.href : textUtil.sanitizeUrl(info.href);
|
||||
|
||||
@ -353,7 +347,10 @@ export class LinkSrv implements LinkService {
|
||||
*/
|
||||
getPanelLinkAnchorInfo(link: DataLink, scopedVars: ScopedVars) {
|
||||
deprecationWarning('link_srv.ts', 'getPanelLinkAnchorInfo', 'getDataLinkUIModel');
|
||||
return this.getDataLinkUIModel(link, scopedVars, {});
|
||||
const replace: InterpolateFunction = (value, vars, fmt) =>
|
||||
getTemplateSrv().replace(value, { ...scopedVars, ...vars }, fmt);
|
||||
|
||||
return this.getDataLinkUIModel(link, replace, {});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,9 @@
|
||||
import { advanceTo } from 'jest-date-mock';
|
||||
import {
|
||||
DataLinkBuiltInVars,
|
||||
FieldType,
|
||||
locationUtil,
|
||||
toDataFrame,
|
||||
VariableModel,
|
||||
VariableOrigin,
|
||||
} from '@grafana/data';
|
||||
import { FieldType, locationUtil, toDataFrame, VariableOrigin } from '@grafana/data';
|
||||
|
||||
import { getDataFrameVars, LinkSrv } from '../link_srv';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { updateConfig } from '../../../../core/config';
|
||||
import { variableAdapters } from '../../../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../../../variables/query/adapter';
|
||||
|
||||
jest.mock('app/core/core', () => ({
|
||||
appEvents: {
|
||||
@ -21,11 +11,6 @@ jest.mock('app/core/core', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const dataPointMock = {
|
||||
seriesName: 'A-series',
|
||||
datapoint: [1000000001, 1],
|
||||
};
|
||||
|
||||
describe('linkSrv', () => {
|
||||
let linkSrv: LinkSrv;
|
||||
|
||||
@ -56,116 +41,14 @@ describe('linkSrv', () => {
|
||||
timeSrv.setTime({ from: 'now-1h', to: 'now' });
|
||||
_dashboard.refresh = false;
|
||||
|
||||
const variablesMock = [
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test1',
|
||||
label: 'Test1',
|
||||
hide: false,
|
||||
current: { value: 'val1' },
|
||||
skipUrlSync: false,
|
||||
getValueForUrl: function() {
|
||||
return 'val1';
|
||||
},
|
||||
} as VariableModel,
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test2',
|
||||
label: 'Test2',
|
||||
hide: false,
|
||||
current: { value: 'val2' },
|
||||
skipUrlSync: false,
|
||||
getValueForUrl: function() {
|
||||
return 'val2';
|
||||
},
|
||||
} as VariableModel,
|
||||
];
|
||||
const _templateSrv = new TemplateSrv({
|
||||
// @ts-ignore
|
||||
getVariables: () => {
|
||||
return variablesMock;
|
||||
},
|
||||
// @ts-ignore
|
||||
getVariableWithName: (name: string) => {
|
||||
return variablesMock.filter(v => v.name === name)[0];
|
||||
},
|
||||
});
|
||||
|
||||
linkSrv = new LinkSrv(_templateSrv, timeSrv);
|
||||
linkSrv = new LinkSrv(new TemplateSrv(), timeSrv);
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
variableAdapters.register(createQueryVariableAdapter());
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
initLinkSrv();
|
||||
advanceTo(1000000000);
|
||||
});
|
||||
|
||||
describe('built in variables', () => {
|
||||
it('should add time range to url if $__url_time_range variable present', () => {
|
||||
expect(
|
||||
linkSrv.getDataLinkUIModel(
|
||||
{
|
||||
title: 'Any title',
|
||||
url: `/d/1?$${DataLinkBuiltInVars.keepTime}`,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
).href
|
||||
).toEqual('/d/1?from=now-1h&to=now');
|
||||
});
|
||||
|
||||
it('should add all variables to url if $__all_variables variable present', () => {
|
||||
expect(
|
||||
linkSrv.getDataLinkUIModel(
|
||||
{
|
||||
title: 'Any title',
|
||||
url: `/d/1?$${DataLinkBuiltInVars.includeVars}`,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
).href
|
||||
).toEqual('/d/1?var-test1=val1&var-test2=val2');
|
||||
});
|
||||
|
||||
it('should interpolate series name', () => {
|
||||
expect(
|
||||
linkSrv.getDataLinkUIModel(
|
||||
{
|
||||
title: 'Any title',
|
||||
url: `/d/1?var-test=$\{${DataLinkBuiltInVars.seriesName}}`,
|
||||
},
|
||||
{
|
||||
__series: {
|
||||
value: {
|
||||
name: 'A-series',
|
||||
},
|
||||
text: 'A-series',
|
||||
},
|
||||
},
|
||||
{}
|
||||
).href
|
||||
).toEqual('/d/1?var-test=A-series');
|
||||
});
|
||||
it('should interpolate value time', () => {
|
||||
expect(
|
||||
linkSrv.getDataLinkUIModel(
|
||||
{
|
||||
title: 'Any title',
|
||||
url: `/d/1?time=$\{${DataLinkBuiltInVars.valueTime}}`,
|
||||
},
|
||||
{
|
||||
__value: {
|
||||
value: { time: dataPointMock.datapoint[0] },
|
||||
text: 'Value',
|
||||
},
|
||||
},
|
||||
{}
|
||||
).href
|
||||
).toEqual('/d/1?time=1000000001');
|
||||
});
|
||||
it('should not trim white space from data links', () => {
|
||||
expect(
|
||||
linkSrv.getDataLinkUIModel(
|
||||
@ -173,16 +56,12 @@ describe('linkSrv', () => {
|
||||
title: 'White space',
|
||||
url: 'www.google.com?query=some query',
|
||||
},
|
||||
{
|
||||
__value: {
|
||||
value: { time: dataPointMock.datapoint[0] },
|
||||
text: 'Value',
|
||||
},
|
||||
},
|
||||
v => v,
|
||||
{}
|
||||
).href
|
||||
).toEqual('www.google.com?query=some query');
|
||||
});
|
||||
|
||||
it('should remove new lines from data link', () => {
|
||||
expect(
|
||||
linkSrv.getDataLinkUIModel(
|
||||
@ -190,12 +69,7 @@ describe('linkSrv', () => {
|
||||
title: 'New line',
|
||||
url: 'www.google.com?query=some\nquery',
|
||||
},
|
||||
{
|
||||
__value: {
|
||||
value: { time: dataPointMock.datapoint[0] },
|
||||
text: 'Value',
|
||||
},
|
||||
},
|
||||
v => v,
|
||||
{}
|
||||
).href
|
||||
).toEqual('www.google.com?query=somequery');
|
||||
@ -220,12 +94,7 @@ describe('linkSrv', () => {
|
||||
title: 'Any title',
|
||||
url,
|
||||
},
|
||||
{
|
||||
__value: {
|
||||
value: { time: dataPointMock.datapoint[0] },
|
||||
text: 'Value',
|
||||
},
|
||||
},
|
||||
v => v,
|
||||
{}
|
||||
).href;
|
||||
|
||||
@ -261,12 +130,7 @@ describe('linkSrv', () => {
|
||||
title: 'Any title',
|
||||
url,
|
||||
},
|
||||
{
|
||||
__value: {
|
||||
value: { time: dataPointMock.datapoint[0] },
|
||||
text: 'Value',
|
||||
},
|
||||
},
|
||||
v => v,
|
||||
{}
|
||||
).href;
|
||||
|
||||
|
@ -1,23 +1,12 @@
|
||||
import { TemplateSrv } from './template_srv';
|
||||
import { convertToStoreState } from 'test/helpers/convertToStoreState';
|
||||
import { getTemplateSrvDependencies } from '../../../test/helpers/getTemplateSrvDependencies';
|
||||
import { variableAdapters } from '../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../variables/query/adapter';
|
||||
import { dateTime, TimeRange } from '@grafana/data';
|
||||
import { initTemplateSrv } from '../../../test/helpers/initTemplateSrv';
|
||||
|
||||
describe('templateSrv', () => {
|
||||
let _templateSrv: any;
|
||||
|
||||
function initTemplateSrv(variables: any[], timeRange?: TimeRange) {
|
||||
const state = convertToStoreState(variables);
|
||||
|
||||
_templateSrv = new TemplateSrv(getTemplateSrvDependencies(state));
|
||||
_templateSrv.init(variables, timeRange);
|
||||
}
|
||||
|
||||
describe('init', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
});
|
||||
|
||||
it('should initialize template data', () => {
|
||||
@ -28,7 +17,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('replace can pass scoped vars', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
});
|
||||
|
||||
it('scoped vars should support objects', () => {
|
||||
@ -112,7 +101,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('getAdhocFilters', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
_templateSrv = initTemplateSrv([
|
||||
{
|
||||
type: 'datasource',
|
||||
name: 'ds',
|
||||
@ -141,7 +130,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('replace can pass multi / all format', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
_templateSrv = initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
@ -157,7 +146,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('when the globbed variable only has one value', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
_templateSrv = initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
@ -205,7 +194,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('variable with all option', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
_templateSrv = initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
@ -238,7 +227,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('variable with all option and custom value', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
_templateSrv = initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
@ -272,19 +261,19 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('lucene format', () => {
|
||||
it('should properly escape $test with lucene escape sequences', () => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]);
|
||||
const target = _templateSrv.replace('this:$test', {}, 'lucene');
|
||||
expect(target).toBe('this:value\\/4');
|
||||
});
|
||||
|
||||
it('should properly escape ${test} with lucene escape sequences', () => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]);
|
||||
const target = _templateSrv.replace('this:${test}', {}, 'lucene');
|
||||
expect(target).toBe('this:value\\/4');
|
||||
});
|
||||
|
||||
it('should properly escape ${test:lucene} with lucene escape sequences', () => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'value/4' } }]);
|
||||
const target = _templateSrv.replace('this:${test:lucene}', {});
|
||||
expect(target).toBe('this:value\\/4');
|
||||
});
|
||||
@ -292,7 +281,9 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('html format', () => {
|
||||
it('should encode values html escape sequences', () => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: '<script>alert(asd)</script>' } }]);
|
||||
_templateSrv = initTemplateSrv([
|
||||
{ type: 'query', name: 'test', current: { value: '<script>alert(asd)</script>' } },
|
||||
]);
|
||||
const target = _templateSrv.replace('$test', {}, 'html');
|
||||
expect(target).toBe('<script>alert(asd)</script>');
|
||||
});
|
||||
@ -391,7 +382,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('can check if variable exists', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
});
|
||||
|
||||
it('should return true if $test exists', () => {
|
||||
@ -432,7 +423,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('can highlight variables in string', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
});
|
||||
|
||||
it('should insert html', () => {
|
||||
@ -453,7 +444,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('updateIndex with simple value', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'muuuu' } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'muuuu' } }]);
|
||||
});
|
||||
|
||||
it('should set current value and update template data', () => {
|
||||
@ -462,104 +453,9 @@ describe('templateSrv', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillVariableValuesForUrl with multi value', () => {
|
||||
beforeAll(() => {
|
||||
variableAdapters.register(createQueryVariableAdapter());
|
||||
});
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
current: { value: ['val1', 'val2'] },
|
||||
getValueForUrl: function() {
|
||||
return this.current.value;
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set multiple url params', () => {
|
||||
const params: any = {};
|
||||
_templateSrv.fillVariableValuesForUrl(params);
|
||||
expect(params['var-test']).toMatchObject(['val1', 'val2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillVariableValuesForUrl skip url sync', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
{
|
||||
name: 'test',
|
||||
skipUrlSync: true,
|
||||
current: { value: 'value' },
|
||||
getValueForUrl: function() {
|
||||
return this.current.value;
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not include template variable value in url', () => {
|
||||
const params: any = {};
|
||||
_templateSrv.fillVariableValuesForUrl(params);
|
||||
expect(params['var-test']).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillVariableValuesForUrl with multi value with skip url sync', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
skipUrlSync: true,
|
||||
current: { value: ['val1', 'val2'] },
|
||||
getValueForUrl: function() {
|
||||
return this.current.value;
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not include template variable value in url', () => {
|
||||
const params: any = {};
|
||||
_templateSrv.fillVariableValuesForUrl(params);
|
||||
expect(params['var-test']).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillVariableValuesForUrl with multi value and scopedVars', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: ['val1', 'val2'] } }]);
|
||||
});
|
||||
|
||||
it('should set scoped value as url params', () => {
|
||||
const params: any = {};
|
||||
_templateSrv.fillVariableValuesForUrl(params, {
|
||||
test: { value: 'val1' },
|
||||
});
|
||||
expect(params['var-test']).toBe('val1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillVariableValuesForUrl with multi value, scopedVars and skip url sync', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: ['val1', 'val2'] } }]);
|
||||
});
|
||||
|
||||
it('should not set scoped value as url params', () => {
|
||||
const params: any = {};
|
||||
_templateSrv.fillVariableValuesForUrl(params, {
|
||||
test: { name: 'test', value: 'val1', skipUrlSync: true },
|
||||
});
|
||||
expect(params['var-test']).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceWithText', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
_templateSrv = initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'server',
|
||||
@ -604,7 +500,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('replaceWithText can pass all / multi value', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([
|
||||
_templateSrv = initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'server',
|
||||
@ -643,7 +539,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('built in interval variables', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([]);
|
||||
_templateSrv = initTemplateSrv([]);
|
||||
});
|
||||
|
||||
it('should replace $__interval_ms with interval milliseconds', () => {
|
||||
@ -656,7 +552,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('date formating', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([], {
|
||||
_templateSrv = initTemplateSrv([], {
|
||||
from: dateTime(1594671549254),
|
||||
to: dateTime(1595237229747),
|
||||
} as TimeRange);
|
||||
@ -690,7 +586,7 @@ describe('templateSrv', () => {
|
||||
|
||||
describe('handle objects gracefully', () => {
|
||||
beforeEach(() => {
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value: { test: 'A' } } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value: { test: 'A' } } }]);
|
||||
});
|
||||
|
||||
it('should not pass object to custom function', () => {
|
||||
@ -706,7 +602,7 @@ describe('templateSrv', () => {
|
||||
describe('handle objects gracefully and call toString if defined', () => {
|
||||
beforeEach(() => {
|
||||
const value = { test: 'A', toString: () => 'hello' };
|
||||
initTemplateSrv([{ type: 'query', name: 'test', current: { value } }]);
|
||||
_templateSrv = initTemplateSrv([{ type: 'query', name: 'test', current: { value } }]);
|
||||
});
|
||||
|
||||
it('should not pass object to custom function', () => {
|
||||
|
@ -5,7 +5,6 @@ import { variableRegex } from '../variables/utils';
|
||||
import { isAdHoc } from '../variables/guard';
|
||||
import { VariableModel } from '../variables/types';
|
||||
import { setTemplateSrv, TemplateSrv as BaseTemplateSrv } from '@grafana/runtime';
|
||||
import { variableAdapters } from '../variables/adapters';
|
||||
import { formatRegistry, FormatOptions } from './formatRegistry';
|
||||
import { ALL_VARIABLE_TEXT } from '../variables/state/types';
|
||||
|
||||
@ -309,22 +308,6 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
return this.replace(target, scopedVars, 'text');
|
||||
}
|
||||
|
||||
fillVariableValuesForUrl = (params: any, scopedVars?: ScopedVars) => {
|
||||
_.each(this.getVariables(), variable => {
|
||||
if (scopedVars && scopedVars[variable.name] !== void 0) {
|
||||
if (scopedVars[variable.name].skipUrlSync) {
|
||||
return;
|
||||
}
|
||||
params['var-' + variable.name] = scopedVars[variable.name].value;
|
||||
} else {
|
||||
if (variable.skipUrlSync) {
|
||||
return;
|
||||
}
|
||||
params['var-' + variable.name] = variableAdapters.get(variable.type).getValueForUrl(variable);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private getVariableAtIndex(name: string) {
|
||||
if (!name) {
|
||||
return;
|
||||
|
104
public/app/features/variables/getAllVariableValuesForUrl.test.ts
Normal file
104
public/app/features/variables/getAllVariableValuesForUrl.test.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { setTemplateSrv } from '@grafana/runtime';
|
||||
import { variableAdapters } from './adapters';
|
||||
import { createQueryVariableAdapter } from './query/adapter';
|
||||
import { getAllVariableValuesForUrl } from './getAllVariableValuesForUrl';
|
||||
import { initTemplateSrv } from '../../../test/helpers/initTemplateSrv';
|
||||
|
||||
describe('getAllVariableValuesForUrl', () => {
|
||||
beforeAll(() => {
|
||||
variableAdapters.register(createQueryVariableAdapter());
|
||||
});
|
||||
|
||||
describe('with multi value', () => {
|
||||
beforeEach(() => {
|
||||
setTemplateSrv(
|
||||
initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
current: { value: ['val1', 'val2'] },
|
||||
getValueForUrl: function() {
|
||||
return this.current.value;
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should set multiple url params', () => {
|
||||
let params: any = getAllVariableValuesForUrl();
|
||||
expect(params['var-test']).toMatchObject(['val1', 'val2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('skip url sync', () => {
|
||||
beforeEach(() => {
|
||||
setTemplateSrv(
|
||||
initTemplateSrv([
|
||||
{
|
||||
name: 'test',
|
||||
skipUrlSync: true,
|
||||
current: { value: 'value' },
|
||||
getValueForUrl: function() {
|
||||
return this.current.value;
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not include template variable value in url', () => {
|
||||
const params = getAllVariableValuesForUrl();
|
||||
expect(params['var-test']).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with multi value with skip url sync', () => {
|
||||
beforeEach(() => {
|
||||
setTemplateSrv(
|
||||
initTemplateSrv([
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
skipUrlSync: true,
|
||||
current: { value: ['val1', 'val2'] },
|
||||
getValueForUrl: function() {
|
||||
return this.current.value;
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not include template variable value in url', () => {
|
||||
const params = getAllVariableValuesForUrl();
|
||||
expect(params['var-test']).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillVariableValuesForUrl with multi value and scopedVars', () => {
|
||||
beforeEach(() => {
|
||||
setTemplateSrv(initTemplateSrv([{ type: 'query', name: 'test', current: { value: ['val1', 'val2'] } }]));
|
||||
});
|
||||
|
||||
it('should set scoped value as url params', () => {
|
||||
const params = getAllVariableValuesForUrl({
|
||||
test: { value: 'val1', text: 'val1text' },
|
||||
});
|
||||
expect(params['var-test']).toBe('val1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fillVariableValuesForUrl with multi value, scopedVars and skip url sync', () => {
|
||||
beforeEach(() => {
|
||||
setTemplateSrv(initTemplateSrv([{ type: 'query', name: 'test', current: { value: ['val1', 'val2'] } }]));
|
||||
});
|
||||
|
||||
it('should not set scoped value as url params', () => {
|
||||
const params = getAllVariableValuesForUrl({
|
||||
test: { name: 'test', value: 'val1', text: 'val1text', skipUrlSync: true },
|
||||
});
|
||||
expect(params['var-test']).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
27
public/app/features/variables/getAllVariableValuesForUrl.ts
Normal file
27
public/app/features/variables/getAllVariableValuesForUrl.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { ScopedVars } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { variableAdapters } from './adapters';
|
||||
|
||||
export function getAllVariableValuesForUrl(scopedVars?: ScopedVars) {
|
||||
const params: Record<string, string | string[]> = {};
|
||||
const variables = getTemplateSrv().getVariables();
|
||||
|
||||
// console.log(variables)
|
||||
for (let i = 0; i < variables.length; i++) {
|
||||
const variable = variables[i];
|
||||
if (scopedVars && scopedVars[variable.name] !== void 0) {
|
||||
if (scopedVars[variable.name].skipUrlSync) {
|
||||
continue;
|
||||
}
|
||||
params['var-' + variable.name] = scopedVars[variable.name].value;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
if (variable.skipUrlSync) {
|
||||
continue;
|
||||
}
|
||||
params['var-' + variable.name] = variableAdapters.get(variable.type).getValueForUrl(variable as any);
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
@ -218,7 +218,7 @@ class GraphElement {
|
||||
|
||||
const dataLinks = [
|
||||
{
|
||||
items: linksSupplier.getLinks(this.panel.scopedVars).map<MenuItem>(link => {
|
||||
items: linksSupplier.getLinks(this.panel.replaceVariables).map<MenuItem>(link => {
|
||||
return {
|
||||
label: link.title,
|
||||
url: link.href,
|
||||
|
@ -16,6 +16,7 @@ export const GraphPanel: React.FC<GraphPanelProps> = ({
|
||||
height,
|
||||
options,
|
||||
onChangeTimeRange,
|
||||
replaceVariables,
|
||||
}) => {
|
||||
return (
|
||||
<GraphNG
|
||||
@ -28,7 +29,7 @@ export const GraphPanel: React.FC<GraphPanelProps> = ({
|
||||
>
|
||||
<TooltipPlugin mode={options.tooltipOptions.mode as any} timeZone={timeZone} />
|
||||
<ZoomPlugin onZoom={onChangeTimeRange} />
|
||||
<ContextMenuPlugin timeZone={timeZone} />
|
||||
<ContextMenuPlugin timeZone={timeZone} replaceVariables={replaceVariables} />
|
||||
{data.annotations ? <ExemplarsPlugin exemplars={data.annotations} timeZone={timeZone} /> : <></>}
|
||||
{data.annotations ? <AnnotationsPlugin annotations={data.annotations} timeZone={timeZone} /> : <></>}
|
||||
</GraphNG>
|
||||
|
@ -9,7 +9,14 @@ import {
|
||||
Portal,
|
||||
usePlotData,
|
||||
} from '@grafana/ui';
|
||||
import { DataFrameView, DisplayValue, Field, getDisplayProcessor, getFieldDisplayName } from '@grafana/data';
|
||||
import {
|
||||
DataFrameView,
|
||||
DisplayValue,
|
||||
Field,
|
||||
getDisplayProcessor,
|
||||
getFieldDisplayName,
|
||||
InterpolateFunction,
|
||||
} from '@grafana/data';
|
||||
import { TimeZone } from '@grafana/data';
|
||||
import { useClickAway } from 'react-use';
|
||||
import { getFieldLinksSupplier } from '../../../../features/panel/panellinks/linkSuppliers';
|
||||
@ -19,9 +26,15 @@ interface ContextMenuPluginProps {
|
||||
timeZone: TimeZone;
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
replaceVariables?: InterpolateFunction;
|
||||
}
|
||||
|
||||
export const ContextMenuPlugin: React.FC<ContextMenuPluginProps> = ({ onClose, timeZone, defaultItems }) => {
|
||||
export const ContextMenuPlugin: React.FC<ContextMenuPluginProps> = ({
|
||||
onClose,
|
||||
timeZone,
|
||||
defaultItems,
|
||||
replaceVariables,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
@ -37,6 +50,7 @@ export const ContextMenuPlugin: React.FC<ContextMenuPluginProps> = ({ onClose, t
|
||||
defaultItems={defaultItems}
|
||||
timeZone={timeZone}
|
||||
selection={{ point, coords }}
|
||||
replaceVariables={replaceVariables}
|
||||
onClose={() => {
|
||||
clearSelection();
|
||||
if (onClose) {
|
||||
@ -59,9 +73,16 @@ interface ContextMenuProps {
|
||||
point: { seriesIdx: number | null; dataIdx: number | null };
|
||||
coords: { plotCanvas: { x: number; y: number }; viewport: { x: number; y: number } };
|
||||
};
|
||||
replaceVariables?: InterpolateFunction;
|
||||
}
|
||||
|
||||
export const ContextMenuView: React.FC<ContextMenuProps> = ({ selection, timeZone, defaultItems, ...otherProps }) => {
|
||||
export const ContextMenuView: React.FC<ContextMenuProps> = ({
|
||||
selection,
|
||||
timeZone,
|
||||
defaultItems,
|
||||
replaceVariables,
|
||||
...otherProps
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
const { data } = usePlotData();
|
||||
const { seriesIdx, dataIdx } = selection.point;
|
||||
@ -102,7 +123,7 @@ export const ContextMenuView: React.FC<ContextMenuProps> = ({ selection, timeZon
|
||||
|
||||
if (linksSupplier) {
|
||||
items.push({
|
||||
items: linksSupplier.getLinks(/*this.panel.scopedVars*/).map<MenuItem>(link => {
|
||||
items: linksSupplier.getLinks(replaceVariables).map<MenuItem>(link => {
|
||||
return {
|
||||
label: link.title,
|
||||
url: link.href,
|
||||
|
12
public/test/helpers/initTemplateSrv.ts
Normal file
12
public/test/helpers/initTemplateSrv.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { TimeRange } from '@grafana/data';
|
||||
import { convertToStoreState } from './convertToStoreState';
|
||||
import { TemplateSrv } from '../../app/features/templating/template_srv';
|
||||
import { getTemplateSrvDependencies } from './getTemplateSrvDependencies';
|
||||
|
||||
export function initTemplateSrv(variables: any[], timeRange?: TimeRange) {
|
||||
const state = convertToStoreState(variables);
|
||||
const srv = new TemplateSrv(getTemplateSrvDependencies(state));
|
||||
srv.init(variables, timeRange);
|
||||
|
||||
return srv;
|
||||
}
|
@ -3,7 +3,7 @@ set -e
|
||||
|
||||
echo -e "Collecting code stats (typescript errors & more)"
|
||||
|
||||
ERROR_COUNT_LIMIT=592
|
||||
ERROR_COUNT_LIMIT=584
|
||||
ERROR_COUNT="$(./node_modules/.bin/tsc --project tsconfig.json --noEmit --strict true | grep -oP 'Found \K(\d+)')"
|
||||
|
||||
if [ "$ERROR_COUNT" -gt $ERROR_COUNT_LIMIT ]; then
|
||||
|
Loading…
Reference in New Issue
Block a user