mirror of
https://github.com/grafana/grafana.git
synced 2025-09-19 22:52:51 +08:00
109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
import { compact, memoize, uniq } from 'lodash';
|
|
import memoizeOne from 'memoize-one';
|
|
|
|
import { Edge, Graph, Node } from 'app/core/utils/dag';
|
|
import { isExpressionQuery } from 'app/features/expressions/guards';
|
|
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
|
|
|
// memoized version of _createDagFromQueries to prevent recreating the DAG if no sources or targets are modified
|
|
export const createDagFromQueries = memoizeOne(
|
|
_createDagFromQueries,
|
|
(previous: Parameters<typeof _createDagFromQueries>, next: Parameters<typeof _createDagFromQueries>) => {
|
|
return fingerPrintQueries(previous[0]) === fingerPrintQueries(next[0]);
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Turn the array of alert queries (this means data queries and expressions)
|
|
* in to a DAG, a directed acyclical graph
|
|
*/
|
|
export function _createDagFromQueries(queries: AlertQuery[]): Graph {
|
|
const graph = new Graph();
|
|
|
|
const nodes = queries.map((query) => query.refId);
|
|
graph.createNodes(nodes);
|
|
|
|
queries.forEach((query) => {
|
|
const source = query.refId;
|
|
const isMathExpression = isExpressionQuery(query.model) && query.model.type === 'math';
|
|
|
|
// some expressions have multiple targets (like the math expression)
|
|
const targets = isMathExpression
|
|
? parseRefsFromMathExpression(query.model.expression ?? '')
|
|
: [query.model.expression];
|
|
|
|
targets.forEach((target) => {
|
|
const isSelf = source === target;
|
|
|
|
if (source && target && !isSelf) {
|
|
graph.link(target, source);
|
|
}
|
|
});
|
|
});
|
|
|
|
return graph;
|
|
}
|
|
|
|
/**
|
|
* parse an expression like "$A > $B" or "${FOO BAR} > 0" to an array of refIds
|
|
*/
|
|
export function parseRefsFromMathExpression(input: string): string[] {
|
|
// we'll use two regular expressions, one for "${var}" and one for "$var"
|
|
const r1 = new RegExp(/\$\{(?<var>[a-zA-Z0-9_ ]+?)\}/gm);
|
|
const r2 = new RegExp(/\$(?<var>[a-zA-Z0-9_]+)/gm);
|
|
|
|
const m1 = Array.from(input.matchAll(r1)).map((m) => m.groups?.var);
|
|
const m2 = Array.from(input.matchAll(r2)).map((m) => m.groups?.var);
|
|
|
|
return compact(uniq([...m1, ...m2]));
|
|
}
|
|
|
|
export const getOriginOfRefId = memoize(_getOriginsOfRefId, (refId, graph) => refId + fingerprintGraph(graph));
|
|
|
|
export function _getOriginsOfRefId(refId: string, graph: Graph): string[] {
|
|
const node = graph.getNode(refId);
|
|
|
|
let origins: Node[] = [];
|
|
|
|
// recurse through "node > inputEdges > inputNode"
|
|
function findChildNode(node: Node) {
|
|
const inputEdges = node.inputEdges;
|
|
|
|
if (inputEdges.length > 0) {
|
|
inputEdges.forEach((edge) => {
|
|
if (edge.inputNode) {
|
|
findChildNode(edge.inputNode);
|
|
}
|
|
});
|
|
} else {
|
|
origins?.push(node);
|
|
}
|
|
}
|
|
|
|
findChildNode(node);
|
|
|
|
return origins.map((origin) => origin.name);
|
|
}
|
|
|
|
// create a unique fingerprint of the DAG
|
|
export function fingerprintGraph(graph: Graph) {
|
|
return Object.keys(graph.nodes)
|
|
.map((name) => {
|
|
const n = graph.nodes[name];
|
|
let outputEdges = n.outputEdges.map((e: Edge) => e.outputNode?.name).join(', ');
|
|
let inputEdges = n.inputEdges.map((e: Edge) => e.inputNode?.name).join(', ');
|
|
return `${n.name}:${outputEdges}:${inputEdges}`;
|
|
})
|
|
.join(' ');
|
|
}
|
|
|
|
// create a unique fingerprint of the array of queries
|
|
export function fingerPrintQueries(queries: AlertQuery[]) {
|
|
return queries
|
|
.map((query) => {
|
|
const type = isExpressionQuery(query.model) ? query.model.type : query.queryType;
|
|
return query.refId + (query.model.expression ?? '') + type;
|
|
})
|
|
.join();
|
|
}
|