Skip to content

Commit

Permalink
Alerting: Prevent search from locking the browser (#87132)
Browse files Browse the repository at this point in the history
  • Loading branch information
gillesdemey committed May 2, 2024
1 parent ad5613d commit 1d06f33
Showing 1 changed file with 53 additions and 14 deletions.
67 changes: 53 additions & 14 deletions public/app/features/alerting/unified/hooks/useFilteredRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') ?? '';
Expand Down Expand Up @@ -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: [] }
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down

0 comments on commit 1d06f33

Please sign in to comment.