Alerting: Prevent search from locking the browser (#87132)

This commit is contained in:
Gilles De Mey 2024-05-02 13:41:33 +02:00 committed by GitHub
parent ad5613d7d4
commit 1d06f33a8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -18,6 +18,10 @@ import { getRuleHealth, isAlertingRule, isGrafanaRulerRule, isPromRuleType } fro
import { calculateGroupTotals, calculateRuleFilteredTotals, calculateRuleTotals } from './useCombinedRuleNamespaces';
import { useURLSearchParams } from './useURLSearchParams';
// if the search term is longer than MAX_NEEDLE_SIZE we disable Levenshtein distance
const MAX_NEEDLE_SIZE = 25;
const INFO_THRESHOLD = Infinity;
export function useRulesFilter() {
const [queryParams, updateQueryParams] = useURLSearchParams();
const searchQuery = queryParams.get('search') ?? '';
@ -100,17 +104,6 @@ export const useFilteredRules = (namespaces: CombinedRuleNamespace[], filterStat
}, [namespaces, filterState]);
};
// Options details can be found here https://github.com/leeoniya/uFuzzy#options
// The following configuration complies with Damerau-Levenshtein distance
// https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
const ufuzzy = new uFuzzy({
intraMode: 1,
intraIns: 1,
intraSub: 1,
intraTrn: 1,
intraDel: 1,
});
export const filterRules = (
namespaces: CombinedRuleNamespace[],
filterState: RulesFilter = { dataSourceNames: [], labels: [], freeFormWords: [] }
@ -129,7 +122,13 @@ export const filterRules = (
if (namespaceFilter) {
const namespaceHaystack = filteredNamespaces.map((ns) => ns.name);
const [idxs, info, order] = ufuzzy.search(namespaceHaystack, namespaceFilter);
const ufuzzy = getSearchInstance(namespaceFilter);
const [idxs, info, order] = ufuzzy.search(
namespaceHaystack,
namespaceFilter,
getOutOfOrderLimit(namespaceFilter),
INFO_THRESHOLD
);
if (info && order) {
filteredNamespaces = order.map((idx) => filteredNamespaces[info.idx[idx]]);
} else if (idxs) {
@ -148,7 +147,14 @@ const reduceNamespaces = (filterState: RulesFilter) => {
if (groupNameFilter) {
const groupsHaystack = filteredGroups.map((g) => g.name);
const [idxs, info, order] = ufuzzy.search(groupsHaystack, groupNameFilter);
const ufuzzy = getSearchInstance(groupNameFilter);
const [idxs, info, order] = ufuzzy.search(
groupsHaystack,
groupNameFilter,
getOutOfOrderLimit(groupNameFilter),
INFO_THRESHOLD
);
if (info && order) {
filteredGroups = order.map((idx) => filteredGroups[info.idx[idx]]);
} else if (idxs) {
@ -178,7 +184,14 @@ const reduceGroups = (filterState: RulesFilter) => {
if (ruleNameQuery) {
const rulesHaystack = filteredRules.map((r) => r.name);
const [idxs, info, order] = ufuzzy.search(rulesHaystack, ruleNameQuery);
const ufuzzy = getSearchInstance(ruleNameQuery);
const [idxs, info, order] = ufuzzy.search(
rulesHaystack,
ruleNameQuery,
getOutOfOrderLimit(ruleNameQuery),
INFO_THRESHOLD
);
if (info && order) {
filteredRules = order.map((idx) => filteredRules[info.idx[idx]]);
} else if (idxs) {
@ -275,6 +288,15 @@ const reduceGroups = (filterState: RulesFilter) => {
};
};
// apply an outOfOrder limit which helps to limit the number of permutations to search for
// and prevents the browser from hanging
function getOutOfOrderLimit(searchTerm: string) {
const ufuzzy = getSearchInstance(searchTerm);
const termCount = ufuzzy.split(searchTerm).length;
return termCount < 5 ? 4 : 0;
}
function looseParseMatcher(matcherQuery: string): Matcher | undefined {
try {
return parseMatcher(matcherQuery);
@ -284,6 +306,23 @@ function looseParseMatcher(matcherQuery: string): Matcher | undefined {
}
}
// determine which search instance to use, very long search terms should match without checking for Levenshtein distance
function getSearchInstance(searchTerm: string): uFuzzy {
const searchTermExeedsMaxNeedleSize = searchTerm.length > MAX_NEEDLE_SIZE;
// Options details can be found here https://github.com/leeoniya/uFuzzy#options
// The following configuration complies with Damerau-Levenshtein distance
// https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
return new uFuzzy({
// we will disable Levenshtein distance for very long search terms this will help with performance
// as it will avoid creating a very complex regular expression
intraMode: searchTermExeedsMaxNeedleSize ? 0 : 1,
// split search terms only on whitespace, this will significantly reduce the amount of regex permutations to test
// and is important for performance with large amount of rules and large needle
interSplit: '\\s+',
});
}
const isQueryingDataSource = (rulerRule: RulerGrafanaRuleDTO, filterState: RulesFilter): boolean => {
if (!filterState.dataSourceNames?.length) {
return true;