diff --git a/public/app/features/trails/MetricSelectScene.tsx b/public/app/features/trails/MetricSelectScene.tsx index 995a012db44..4912cfc1e6c 100644 --- a/public/app/features/trails/MetricSelectScene.tsx +++ b/public/app/features/trails/MetricSelectScene.tsx @@ -1,5 +1,4 @@ import { css } from '@emotion/css'; -import leven from 'leven'; import { debounce } from 'lodash'; import React, { useCallback } from 'react'; @@ -29,6 +28,7 @@ import { MetricCategoryCascader } from './MetricCategory/MetricCategoryCascader' import { MetricScene } from './MetricScene'; import { SelectMetricAction } from './SelectMetricAction'; import { hideEmptyPreviews } from './hideEmptyPreviews'; +import { sortRelatedMetrics } from './relatedMetrics'; import { getVariablesWithMetricConstant, trailDS, VAR_DATASOURCE, VAR_FILTERS_EXPR, VAR_METRIC_NAMES } from './shared'; import { getColorByIndex, getTrailFor } from './utils'; @@ -431,22 +431,6 @@ function getCardPanelFor(metric: string) { .build(); } -// Computes the Levenshtein distance between two strings, twice, once for the first half and once for the whole string. -function sortRelatedMetrics(metricList: string[], metric: string) { - return metricList.sort((aValue, bValue) => { - const aSplit = aValue.split('_'); - const aHalf = aSplit.slice(0, aSplit.length / 2).join('_'); - - const bSplit = bValue.split('_'); - const bHalf = bSplit.slice(0, bSplit.length / 2).join('_'); - - return ( - (leven(aHalf, metric!) || 0 + (leven(aValue, metric!) || 0)) - - (leven(bHalf, metric!) || 0 + (leven(bValue, metric!) || 0)) - ); - }); -} - function getStyles(theme: GrafanaTheme2) { return { container: css({ diff --git a/public/app/features/trails/relatedMetrics.ts b/public/app/features/trails/relatedMetrics.ts new file mode 100644 index 00000000000..9aaf24745e1 --- /dev/null +++ b/public/app/features/trails/relatedMetrics.ts @@ -0,0 +1,42 @@ +import leven from 'leven'; + +export function sortRelatedMetrics(metricList: string[], metric: string) { + return metricList.sort((aValue, bValue) => { + const a = getLevenDistances(aValue, metric); + const b = getLevenDistances(bValue, metric); + + return a.halfLeven + a.wholeLeven - (b.halfLeven + b.wholeLeven); + }); +} + +type LevenDistances = { halfLeven: number; wholeLeven: number }; +type TargetToLevenDistances = Map; + +const metricToTargetLevenDistances = new Map(); + +// Provides the Levenshtein distance between a metric to be sorted +// and a targetMetric compared to which all other metrics are being sorted +// There are two distances: once for the first half and once for the whole string. +// This operation is not expected to be symmetric; order of parameters matters +// since only `metric` is split. +function getLevenDistances(metric: string, targetMetric: string) { + let targetToDistances: TargetToLevenDistances | undefined = metricToTargetLevenDistances.get(metric); + if (!targetToDistances) { + targetToDistances = new Map(); + metricToTargetLevenDistances.set(metric, targetToDistances); + } + + let distances: LevenDistances | undefined = targetToDistances.get(targetMetric); + if (!distances) { + const metricSplit = metric.split('_'); + const metricHalf = metricSplit.slice(0, metricSplit.length / 2).join('_'); + + const halfLeven = leven(metricHalf, targetMetric!) || 0; + const wholeLeven = leven(metric, targetMetric!) || 0; + + distances = { halfLeven, wholeLeven }; + targetToDistances.set(targetMetric, distances); + } + + return distances; +}