|
|
|
@ -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; |
|
|
|
|