mirror of
https://github.com/grafana/grafana.git
synced 2025-09-21 00:13:18 +08:00
![renovate[bot]](/assets/img/avatar_default.png)
* Update Monthly patch updates * don't update dangerously-set-html-content * run prettier * fix types --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
253 lines
7.6 KiB
TypeScript
253 lines
7.6 KiB
TypeScript
// Copyright (c) 2017 The Jaeger Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { uniq as _uniq } from 'lodash';
|
|
import memoize from 'lru-memoize';
|
|
|
|
import { TraceSpan, TraceLink, TraceKeyValuePair, Trace, TNil } from '../types';
|
|
import { getConfigValue } from '../utils/config/get-config';
|
|
|
|
import { getParent } from './span';
|
|
|
|
const parameterRegExp = /#\{([^{}]*)\}/g;
|
|
|
|
type ProcessedTemplate = {
|
|
parameters: string[];
|
|
template: (template: { [key: string]: any }) => string;
|
|
};
|
|
|
|
export type ProcessedLinkPattern = {
|
|
object: any;
|
|
type: (link: string) => boolean;
|
|
key: (link: string) => boolean;
|
|
value: (value: any) => boolean;
|
|
url: ProcessedTemplate;
|
|
text: ProcessedTemplate;
|
|
parameters: string[];
|
|
};
|
|
|
|
type TLinksRV = Array<{ url: string; text: string }>;
|
|
|
|
function getParamNames(str: string) {
|
|
const names = new Set<string>();
|
|
str.replace(parameterRegExp, (match, name) => {
|
|
names.add(name);
|
|
return match;
|
|
});
|
|
return Array.from(names);
|
|
}
|
|
|
|
function stringSupplant(str: string, encodeFn: (unencoded: any) => string, map: Record<string, any>) {
|
|
return str.replace(parameterRegExp, (_, name) => {
|
|
const value = map[name];
|
|
return value == null ? '' : encodeFn(value);
|
|
});
|
|
}
|
|
|
|
export function processTemplate(template: unknown, encodeFn: (unencoded: any) => string): ProcessedTemplate {
|
|
if (typeof template !== 'string') {
|
|
/*
|
|
|
|
// kept on ice until #123 is implemented:
|
|
if (template && Array.isArray(template.parameters) && (typeof template.template === 'function')) {
|
|
return template;
|
|
}
|
|
|
|
*/
|
|
throw new Error('Invalid template');
|
|
}
|
|
return {
|
|
parameters: getParamNames(template),
|
|
template: stringSupplant.bind(null, template, encodeFn),
|
|
};
|
|
}
|
|
|
|
export function createTestFunction(entry?: any) {
|
|
if (typeof entry === 'string') {
|
|
return (arg: unknown) => arg === entry;
|
|
}
|
|
if (Array.isArray(entry)) {
|
|
return (arg: unknown) => entry.indexOf(arg) > -1;
|
|
}
|
|
/*
|
|
|
|
// kept on ice until #123 is implemented:
|
|
if (entry instanceof RegExp) {
|
|
return (arg: any) => entry.test(arg);
|
|
}
|
|
if (typeof entry === 'function') {
|
|
return entry;
|
|
}
|
|
|
|
*/
|
|
if (entry == null) {
|
|
return () => true;
|
|
}
|
|
throw new Error(`Invalid value: ${entry}`);
|
|
}
|
|
|
|
const identity = (a: any): typeof a => a;
|
|
|
|
export function processLinkPattern(pattern: any): ProcessedLinkPattern | null {
|
|
try {
|
|
const url = processTemplate(pattern.url, encodeURIComponent);
|
|
const text = processTemplate(pattern.text, identity);
|
|
return {
|
|
object: pattern,
|
|
type: createTestFunction(pattern.type),
|
|
key: createTestFunction(pattern.key),
|
|
value: createTestFunction(pattern.value),
|
|
url,
|
|
text,
|
|
parameters: _uniq(url.parameters.concat(text.parameters)),
|
|
};
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`Ignoring invalid link pattern: ${error}`, pattern);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function getParameterInArray(name: string, array?: TraceKeyValuePair[] | TNil) {
|
|
if (array) {
|
|
return array.find((entry) => entry.key === name);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function getParameterInAncestor(name: string, span: TraceSpan) {
|
|
let currentSpan: TraceSpan | TNil = span;
|
|
while (currentSpan) {
|
|
const result = getParameterInArray(name, currentSpan.tags) || getParameterInArray(name, currentSpan.process.tags);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
currentSpan = getParent(currentSpan);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function callTemplate(template: ProcessedTemplate, data: any) {
|
|
return template.template(data);
|
|
}
|
|
|
|
export function computeTraceLink(linkPatterns: ProcessedLinkPattern[], trace: Trace) {
|
|
const result: TLinksRV = [];
|
|
const validKeys = (Object.keys(trace) as Array<keyof Trace>).filter(
|
|
(key) => typeof trace[key] === 'string' || trace[key] === 'number'
|
|
);
|
|
|
|
linkPatterns
|
|
?.filter((pattern) => pattern?.type('traces'))
|
|
.forEach((pattern) => {
|
|
const parameterValues: Record<string, any> = {};
|
|
const allParameters = pattern?.parameters.every((parameter) => {
|
|
const key = parameter as keyof Trace;
|
|
if (validKeys.includes(key)) {
|
|
// At this point is safe to access to trace object using parameter variable because
|
|
// we validated parameter against validKeys, this implies that parameter a keyof Trace.
|
|
parameterValues[parameter] = trace[key];
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
if (allParameters) {
|
|
result.push({
|
|
url: callTemplate(pattern.url, parameterValues),
|
|
text: callTemplate(pattern.text, parameterValues),
|
|
});
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
export function computeLinks(
|
|
linkPatterns: ProcessedLinkPattern[],
|
|
span: TraceSpan,
|
|
items: TraceKeyValuePair[],
|
|
itemIndex: number
|
|
) {
|
|
const item = items[itemIndex];
|
|
let type = 'logs';
|
|
const processTags = span.process.tags === items;
|
|
if (processTags) {
|
|
type = 'process';
|
|
}
|
|
const spanTags = span.tags === items;
|
|
if (spanTags) {
|
|
type = 'tags';
|
|
}
|
|
const result: Array<{ url: string; text: string }> = [];
|
|
linkPatterns.forEach((pattern) => {
|
|
if (pattern.type(type) && pattern.key(item.key) && pattern.value(item.value)) {
|
|
const parameterValues: Record<string, any> = {};
|
|
const allParameters = pattern.parameters.every((parameter) => {
|
|
let entry = getParameterInArray(parameter, items);
|
|
if (!entry && !processTags) {
|
|
// do not look in ancestors for process tags because the same object may appear in different places in the hierarchy
|
|
// and the cache in getLinks uses that object as a key
|
|
entry = getParameterInAncestor(parameter, span);
|
|
}
|
|
if (entry) {
|
|
parameterValues[parameter] = entry.value;
|
|
return true;
|
|
}
|
|
// eslint-disable-next-line no-console
|
|
console.warn(
|
|
`Skipping link pattern, missing parameter ${parameter} for key ${item.key} in ${type}.`,
|
|
pattern.object
|
|
);
|
|
return false;
|
|
});
|
|
if (allParameters) {
|
|
result.push({
|
|
url: callTemplate(pattern.url, parameterValues),
|
|
text: callTemplate(pattern.text, parameterValues),
|
|
});
|
|
}
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
export function createGetLinks(linkPatterns: ProcessedLinkPattern[], cache: WeakMap<TraceKeyValuePair, TraceLink[]>) {
|
|
return (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => {
|
|
if (linkPatterns.length === 0) {
|
|
return [];
|
|
}
|
|
const item = items[itemIndex];
|
|
let result = cache.get(item);
|
|
if (!result) {
|
|
result = computeLinks(linkPatterns, span, items, itemIndex);
|
|
cache.set(item, result);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
const processedLinks = (getConfigValue('linkPatterns') || [])
|
|
.map(processLinkPattern)
|
|
.filter((link: ProcessedLinkPattern | null): link is ProcessedLinkPattern => Boolean(link));
|
|
|
|
export const getTraceLinks: (trace: Trace | undefined) => TLinksRV = memoize(10)((trace: Trace | undefined) => {
|
|
const result: TLinksRV = [];
|
|
if (!trace) {
|
|
return result;
|
|
}
|
|
return computeTraceLink(processedLinks, trace);
|
|
});
|
|
|
|
export default createGetLinks(processedLinks, new WeakMap());
|