mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
* Getting close * Restore angular app boot at startup * Moving angular annotations dependencies to app/angular or old graph * Remove redundant setLinkSrv call * Fixing graph test * Minor refactor based on review feedback * Create in get function
350 lines
9.3 KiB
TypeScript
350 lines
9.3 KiB
TypeScript
import { chain } from 'lodash';
|
|
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
|
import { getTemplateSrv } from '@grafana/runtime';
|
|
import { getConfig } from 'app/core/config';
|
|
import {
|
|
DataFrame,
|
|
DataLink,
|
|
DataLinkBuiltInVars,
|
|
deprecationWarning,
|
|
Field,
|
|
FieldType,
|
|
getFieldDisplayName,
|
|
InterpolateFunction,
|
|
KeyValue,
|
|
LinkModel,
|
|
locationUtil,
|
|
ScopedVars,
|
|
textUtil,
|
|
urlUtil,
|
|
VariableOrigin,
|
|
VariableSuggestion,
|
|
VariableSuggestionsScope,
|
|
} from '@grafana/data';
|
|
import { getVariablesUrlParams } from '../../variables/getAllVariableValuesForUrl';
|
|
|
|
const timeRangeVars = [
|
|
{
|
|
value: `${DataLinkBuiltInVars.keepTime}`,
|
|
label: 'Time range',
|
|
documentation: 'Adds current time range',
|
|
origin: VariableOrigin.BuiltIn,
|
|
},
|
|
{
|
|
value: `${DataLinkBuiltInVars.timeRangeFrom}`,
|
|
label: 'Time range: from',
|
|
documentation: "Adds current time range's from value",
|
|
origin: VariableOrigin.BuiltIn,
|
|
},
|
|
{
|
|
value: `${DataLinkBuiltInVars.timeRangeTo}`,
|
|
label: 'Time range: to',
|
|
documentation: "Adds current time range's to value",
|
|
origin: VariableOrigin.BuiltIn,
|
|
},
|
|
];
|
|
|
|
const seriesVars = [
|
|
{
|
|
value: `${DataLinkBuiltInVars.seriesName}`,
|
|
label: 'Name',
|
|
documentation: 'Name of the series',
|
|
origin: VariableOrigin.Series,
|
|
},
|
|
];
|
|
|
|
const valueVars = [
|
|
{
|
|
value: `${DataLinkBuiltInVars.valueNumeric}`,
|
|
label: 'Numeric',
|
|
documentation: 'Numeric representation of selected value',
|
|
origin: VariableOrigin.Value,
|
|
},
|
|
{
|
|
value: `${DataLinkBuiltInVars.valueText}`,
|
|
label: 'Text',
|
|
documentation: 'Text representation of selected value',
|
|
origin: VariableOrigin.Value,
|
|
},
|
|
{
|
|
value: `${DataLinkBuiltInVars.valueRaw}`,
|
|
label: 'Raw',
|
|
documentation: 'Raw value',
|
|
origin: VariableOrigin.Value,
|
|
},
|
|
];
|
|
|
|
const buildLabelPath = (label: string) => {
|
|
return label.includes('.') || label.trim().includes(' ') ? `["${label}"]` : `.${label}`;
|
|
};
|
|
|
|
export const getPanelLinksVariableSuggestions = (): VariableSuggestion[] => [
|
|
...getTemplateSrv()
|
|
.getVariables()
|
|
.map((variable) => ({
|
|
value: variable.name as string,
|
|
label: variable.name,
|
|
origin: VariableOrigin.Template,
|
|
})),
|
|
{
|
|
value: `${DataLinkBuiltInVars.includeVars}`,
|
|
label: 'All variables',
|
|
documentation: 'Adds current variables',
|
|
origin: VariableOrigin.Template,
|
|
},
|
|
...timeRangeVars,
|
|
];
|
|
|
|
const getFieldVars = (dataFrames: DataFrame[]) => {
|
|
const all = [];
|
|
for (const df of dataFrames) {
|
|
for (const f of df.fields) {
|
|
if (f.labels) {
|
|
for (const k of Object.keys(f.labels)) {
|
|
all.push(k);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const labels = chain(all).flatten().uniq().value();
|
|
|
|
return [
|
|
{
|
|
value: `${DataLinkBuiltInVars.fieldName}`,
|
|
label: 'Name',
|
|
documentation: 'Field name of the clicked datapoint (in ms epoch)',
|
|
origin: VariableOrigin.Field,
|
|
},
|
|
...labels.map((label) => ({
|
|
value: `__field.labels${buildLabelPath(label)}`,
|
|
label: `labels.${label}`,
|
|
documentation: `${label} label value`,
|
|
origin: VariableOrigin.Field,
|
|
})),
|
|
];
|
|
};
|
|
|
|
export const getDataFrameVars = (dataFrames: DataFrame[]) => {
|
|
let numeric: Field | undefined = undefined;
|
|
let title: Field | undefined = undefined;
|
|
const suggestions: VariableSuggestion[] = [];
|
|
const keys: KeyValue<true> = {};
|
|
|
|
if (dataFrames.length !== 1) {
|
|
// It's not possible to access fields of other dataframes. So if there are multiple dataframes we need to skip these suggestions.
|
|
// Also return early if there are no dataFrames.
|
|
return [];
|
|
}
|
|
|
|
const frame = dataFrames[0];
|
|
|
|
for (const field of frame.fields) {
|
|
const displayName = getFieldDisplayName(field, frame, dataFrames);
|
|
|
|
if (keys[displayName]) {
|
|
continue;
|
|
}
|
|
|
|
suggestions.push({
|
|
value: `__data.fields${buildLabelPath(displayName)}`,
|
|
label: `${displayName}`,
|
|
documentation: `Formatted value for ${displayName} on the same row`,
|
|
origin: VariableOrigin.Fields,
|
|
});
|
|
|
|
keys[displayName] = true;
|
|
|
|
if (!numeric && field.type === FieldType.number) {
|
|
numeric = { ...field, name: displayName };
|
|
}
|
|
|
|
if (!title && field.config.displayName && field.config.displayName !== field.name) {
|
|
title = { ...field, name: displayName };
|
|
}
|
|
}
|
|
|
|
if (suggestions.length) {
|
|
suggestions.push({
|
|
value: `__data.fields[0]`,
|
|
label: `Select by index`,
|
|
documentation: `Enter the field order`,
|
|
origin: VariableOrigin.Fields,
|
|
});
|
|
}
|
|
|
|
if (numeric) {
|
|
suggestions.push({
|
|
value: `__data.fields${buildLabelPath(numeric.name)}.numeric`,
|
|
label: `Show numeric value`,
|
|
documentation: `the numeric field value`,
|
|
origin: VariableOrigin.Fields,
|
|
});
|
|
suggestions.push({
|
|
value: `__data.fields${buildLabelPath(numeric.name)}.text`,
|
|
label: `Show text value`,
|
|
documentation: `the text value`,
|
|
origin: VariableOrigin.Fields,
|
|
});
|
|
}
|
|
|
|
if (title) {
|
|
suggestions.push({
|
|
value: `__data.fields${buildLabelPath(title.name)}`,
|
|
label: `Select by title`,
|
|
documentation: `Use the title to pick the field`,
|
|
origin: VariableOrigin.Fields,
|
|
});
|
|
}
|
|
|
|
return suggestions;
|
|
};
|
|
|
|
export const getDataLinksVariableSuggestions = (
|
|
dataFrames: DataFrame[],
|
|
scope?: VariableSuggestionsScope
|
|
): VariableSuggestion[] => {
|
|
const valueTimeVar = {
|
|
value: `${DataLinkBuiltInVars.valueTime}`,
|
|
label: 'Time',
|
|
documentation: 'Time value of the clicked datapoint (in ms epoch)',
|
|
origin: VariableOrigin.Value,
|
|
};
|
|
const includeValueVars = scope === VariableSuggestionsScope.Values;
|
|
|
|
return includeValueVars
|
|
? [
|
|
...seriesVars,
|
|
...getFieldVars(dataFrames),
|
|
...valueVars,
|
|
valueTimeVar,
|
|
...getDataFrameVars(dataFrames),
|
|
...getPanelLinksVariableSuggestions(),
|
|
]
|
|
: [
|
|
...seriesVars,
|
|
...getFieldVars(dataFrames),
|
|
...getDataFrameVars(dataFrames),
|
|
...getPanelLinksVariableSuggestions(),
|
|
];
|
|
};
|
|
|
|
export const getCalculationValueDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => {
|
|
const fieldVars = getFieldVars(dataFrames);
|
|
const valueCalcVar = {
|
|
value: `${DataLinkBuiltInVars.valueCalc}`,
|
|
label: 'Calculation name',
|
|
documentation: 'Name of the calculation the value is a result of',
|
|
origin: VariableOrigin.Value,
|
|
};
|
|
return [...seriesVars, ...fieldVars, ...valueVars, valueCalcVar, ...getPanelLinksVariableSuggestions()];
|
|
};
|
|
|
|
export interface LinkService {
|
|
getDataLinkUIModel: <T>(link: DataLink, replaceVariables: InterpolateFunction | undefined, origin: T) => LinkModel<T>;
|
|
getAnchorInfo: (link: any) => any;
|
|
getLinkUrl: (link: any) => string;
|
|
}
|
|
|
|
export class LinkSrv implements LinkService {
|
|
getLinkUrl(link: any) {
|
|
let url = locationUtil.assureBaseUrl(getTemplateSrv().replace(link.url || ''));
|
|
let params: { [key: string]: any } = {};
|
|
|
|
if (link.keepTime) {
|
|
const range = getTimeSrv().timeRangeForUrl();
|
|
params['from'] = range.from;
|
|
params['to'] = range.to;
|
|
}
|
|
|
|
if (link.includeVars) {
|
|
params = {
|
|
...params,
|
|
...getVariablesUrlParams(),
|
|
};
|
|
}
|
|
|
|
url = urlUtil.appendQueryToUrl(url, urlUtil.toUrlParams(params));
|
|
return getConfig().disableSanitizeHtml ? url : textUtil.sanitizeUrl(url);
|
|
}
|
|
|
|
getAnchorInfo(link: any) {
|
|
const templateSrv = getTemplateSrv();
|
|
const info: any = {};
|
|
info.href = this.getLinkUrl(link);
|
|
info.title = templateSrv.replace(link.title || '');
|
|
info.tooltip = templateSrv.replace(link.tooltip || '');
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* Returns LinkModel which is basically a DataLink with all values interpolated through the templateSrv.
|
|
*/
|
|
getDataLinkUIModel = <T>(
|
|
link: DataLink,
|
|
replaceVariables: InterpolateFunction | undefined,
|
|
origin: T
|
|
): LinkModel<T> => {
|
|
let href = link.url;
|
|
|
|
if (link.onBuildUrl) {
|
|
href = link.onBuildUrl({
|
|
origin,
|
|
replaceVariables,
|
|
});
|
|
}
|
|
|
|
const info: LinkModel<T> = {
|
|
href: locationUtil.assureBaseUrl(href.replace(/\n/g, '')),
|
|
title: link.title ?? '',
|
|
target: link.targetBlank ? '_blank' : undefined,
|
|
origin,
|
|
};
|
|
|
|
if (replaceVariables) {
|
|
info.href = replaceVariables(info.href);
|
|
info.title = replaceVariables(link.title);
|
|
}
|
|
|
|
if (link.onClick) {
|
|
info.onClick = (e) => {
|
|
link.onClick!({
|
|
origin,
|
|
replaceVariables,
|
|
e,
|
|
});
|
|
};
|
|
}
|
|
|
|
info.href = getConfig().disableSanitizeHtml ? info.href : textUtil.sanitizeUrl(info.href);
|
|
|
|
return info;
|
|
};
|
|
|
|
/**
|
|
* getPanelLinkAnchorInfo method is left for plugins compatibility reasons
|
|
*
|
|
* @deprecated Drilldown links should be generated using getDataLinkUIModel method
|
|
*/
|
|
getPanelLinkAnchorInfo(link: DataLink, scopedVars: ScopedVars) {
|
|
deprecationWarning('link_srv.ts', 'getPanelLinkAnchorInfo', 'getDataLinkUIModel');
|
|
const replace: InterpolateFunction = (value, vars, fmt) =>
|
|
getTemplateSrv().replace(value, { ...scopedVars, ...vars }, fmt);
|
|
|
|
return this.getDataLinkUIModel(link, replace, {});
|
|
}
|
|
}
|
|
|
|
let singleton: LinkService | undefined;
|
|
|
|
export function setLinkSrv(srv: LinkService) {
|
|
singleton = srv;
|
|
}
|
|
|
|
export function getLinkSrv(): LinkService {
|
|
if (!singleton) {
|
|
singleton = new LinkSrv();
|
|
}
|
|
return singleton;
|
|
}
|