diff --git a/package.json b/package.json index 13672622282..5df4c51cb9a 100644 --- a/package.json +++ b/package.json @@ -272,7 +272,7 @@ "@grafana/schema": "workspace:*", "@grafana/ui": "workspace:*", "@kusto/monaco-kusto": "5.3.6", - "@leeoniya/ufuzzy": "0.9.1", + "@leeoniya/ufuzzy": "1.0.2", "@lezer/common": "1.0.1", "@lezer/highlight": "1.1.2", "@lezer/lr": "1.3.1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 1524aee83d0..e288953e9db 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -52,7 +52,7 @@ "@grafana/data": "9.4.0-pre", "@grafana/e2e-selectors": "9.4.0-pre", "@grafana/schema": "9.4.0-pre", - "@leeoniya/ufuzzy": "0.9.0", + "@leeoniya/ufuzzy": "1.0.2", "@monaco-editor/react": "4.4.6", "@popperjs/core": "2.11.6", "@react-aria/button": "3.6.1", diff --git a/public/app/features/commandPalette/useMatches.ts b/public/app/features/commandPalette/useMatches.ts index 22c1b48cc10..2e726326141 100644 --- a/public/app/features/commandPalette/useMatches.ts +++ b/public/app/features/commandPalette/useMatches.ts @@ -186,8 +186,8 @@ function useInternalMatches(filtered: ActionImpl[], search: string): Match[] { return throttledFiltered.map((action) => ({ score: 0, action })); } - const haystack = throttledFiltered.map((action) => - [action.name, action.keywords, action.subtitle].join(' ').toLowerCase() + const haystack = throttledFiltered.map(({ name, keywords, subtitle }) => + `${name} ${keywords ?? ''} ${subtitle ?? ''}`.toLowerCase() ); const results: Match[] = []; @@ -201,35 +201,27 @@ function useInternalMatches(filtered: ActionImpl[], search: string): Match[] { const haystackItem = haystack[haystackIndex]; // Use the position of the match as a stand-in for score - const substringPosition = haystackItem.toLowerCase().indexOf(query); + const substringPosition = haystackItem.indexOf(query); if (substringPosition > -1) { - const score = haystack.length - substringPosition; + const score = substringPosition * -1; // lower position of the match should be a higher priority score const action = throttledFiltered[haystackIndex]; results.push({ score, action }); } } } else { - const allMatchedIndexes = new Set(); + const termCount = ufuzzy.split(throttledSearch).length; + const infoThresh = Infinity; + const oooSearch = termCount < 5; - const queryWords = ufuzzy.split(throttledSearch); - const queryPermutations = - queryWords.length < 5 ? uFuzzy.permute(queryWords).map((terms) => terms.join(' ')) : [throttledSearch]; - - for (const permutedSearchTerm of queryPermutations) { - const indexes = ufuzzy.filter(haystack, permutedSearchTerm); - const info = ufuzzy.info(indexes, haystack, permutedSearchTerm); - const order = ufuzzy.sort(info, haystack, permutedSearchTerm); + const [, info, order] = ufuzzy.search(haystack, throttledSearch, oooSearch, infoThresh); + if (info && order) { for (let orderIndex = 0; orderIndex < order.length; orderIndex++) { const actionIndex = order[orderIndex]; - - if (!allMatchedIndexes.has(actionIndex)) { - allMatchedIndexes.add(actionIndex); - const score = order.length - orderIndex; - const action = throttledFiltered[info.idx[actionIndex]]; - results.push({ score, action }); - } + const score = order.length - orderIndex; + const action = throttledFiltered[info.idx[actionIndex]]; + results.push({ score, action }); } } } diff --git a/public/app/features/search/service/frontend.ts b/public/app/features/search/service/frontend.ts index cd0dee1dfb2..b11fe34d77d 100644 --- a/public/app/features/search/service/frontend.ts +++ b/public/app/features/search/service/frontend.ts @@ -116,28 +116,27 @@ class FullResultCache { // eslint-disable-next-line const values = allFields.map((v) => [] as any[]); // empty value for each field - // out-of-order terms - const oooIdxs = new Set(); - const queryTerms = this.ufuzzy.split(query); - const oooNeedles = uFuzzy.permute(queryTerms).map((terms) => terms.join(' ')); + let [idxs, info, order] = this.ufuzzy.search(haystack, query, true); - oooNeedles.forEach((needle) => { - let idxs = this.ufuzzy.filter(haystack, needle); - let info = this.ufuzzy.info(idxs, haystack, needle); - let order = this.ufuzzy.sort(info, haystack, needle); + for (let c = 0; c < allFields.length; c++) { + let src = allFields[c].values.toArray(); + let dst = values[c]; - for (let i = 0; i < order.length; i++) { - let haystackIdx = info.idx[order[i]]; - - if (!oooIdxs.has(haystackIdx)) { - oooIdxs.add(haystackIdx); - - for (let c = 0; c < allFields.length; c++) { - values[c].push(allFields[c].values.get(haystackIdx)); - } + // <= 1000 matches (ranked) + if (info && order) { + for (let i = 0; i < order.length; i++) { + let haystackIdx = info.idx[order[i]]; + dst.push(src[haystackIdx]); } } - }); + // > 1000 matches (unranked) + else { + for (let i = 0; i < idxs.length; i++) { + let haystackIdx = idxs[i]; + dst.push(src[haystackIdx]); + } + } + } // mutates the search object this.empty.dataFrame.fields.forEach((f, idx) => { diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx index bbf4dd37959..63a953c6c8c 100644 --- a/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx +++ b/public/app/plugins/panel/flamegraph/components/FlameGraph/FlameGraph.tsx @@ -18,7 +18,7 @@ // THIS SOFTWARE. import { css } from '@emotion/css'; import uFuzzy from '@leeoniya/ufuzzy'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useMeasure } from 'react-use'; import { CoreApp, createTheme, DataFrame, FieldType, getDisplayProcessor } from '@grafana/data'; @@ -90,6 +90,26 @@ const FlameGraph = ({ [levels, totalTicks, rangeMin] ); + const [ufuzzy] = useState(() => { + return new uFuzzy(); + }); + + const uniqueLabels = useMemo(() => { + return [...new Set(data.fields.find((f) => f.name === 'label')?.values.toArray())]; + }, [data]); + + const foundLabels = useMemo(() => { + const foundLabels = new Set(); + + if (search) { + for (let idx of ufuzzy.filter(uniqueLabels, search)) { + foundLabels.add(uniqueLabels[idx]); + } + } + + return foundLabels; + }, [ufuzzy, search, uniqueLabels]); + const render = useCallback( (pixelsPerTick: number) => { if (!levels.length) { @@ -113,11 +133,6 @@ const FlameGraph = ({ theme: createTheme() /* theme does not matter for us here */, }); - const ufuzzy = new uFuzzy({ - intraMode: 0, - intraIns: 0, - }); - for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) { const level = levels[levelIndex]; // Get all the dimensions of the rectangles for the level. We do this by level instead of per rectangle, because @@ -125,11 +140,11 @@ const FlameGraph = ({ const dimensions = getRectDimensionsForLevel(level, levelIndex, totalTicks, rangeMin, pixelsPerTick, processor); for (const rect of dimensions) { // Render each rectangle based on the computed dimensions - renderRect(ctx, rect, totalTicks, rangeMin, rangeMax, search, levelIndex, topLevelIndex, ufuzzy); + renderRect(ctx, rect, totalTicks, rangeMin, rangeMax, search, levelIndex, topLevelIndex, foundLabels); } } }, - [levels, wrapperWidth, valueField, totalTicks, rangeMin, rangeMax, search, topLevelIndex] + [levels, wrapperWidth, valueField, totalTicks, rangeMin, rangeMax, search, topLevelIndex, foundLabels] ); useEffect(() => { diff --git a/public/app/plugins/panel/flamegraph/components/FlameGraph/rendering.ts b/public/app/plugins/panel/flamegraph/components/FlameGraph/rendering.ts index 34e3db9e2cd..22dbfac9eb8 100644 --- a/public/app/plugins/panel/flamegraph/components/FlameGraph/rendering.ts +++ b/public/app/plugins/panel/flamegraph/components/FlameGraph/rendering.ts @@ -86,7 +86,7 @@ export function renderRect( query: string, levelIndex: number, topLevelIndex: number, - ufuzzy: uFuzzy + foundNames: Set ) { if (rect.width < HIDE_THRESHOLD) { return; @@ -101,19 +101,17 @@ export function renderRect( const l = 65 + 7 * intensity; const name = rect.label; - const idxs = ufuzzy.filter([name], query); - const queryResult = query && idxs.length > 0; if (!rect.collapsed) { ctx.stroke(); if (query) { - ctx.fillStyle = queryResult ? getBarColor(h, l) : colors[55]; + ctx.fillStyle = foundNames.has(name) ? getBarColor(h, l) : colors[55]; } else { ctx.fillStyle = levelIndex > topLevelIndex - 1 ? getBarColor(h, l) : getBarColor(h, l + 15); } } else { - ctx.fillStyle = queryResult ? getBarColor(h, l) : colors[55]; + ctx.fillStyle = foundNames.has(name) ? getBarColor(h, l) : colors[55]; } ctx.fill(); diff --git a/yarn.lock b/yarn.lock index 361c20eef6c..abcbce22126 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5302,7 +5302,7 @@ __metadata: "@grafana/e2e-selectors": 9.4.0-pre "@grafana/schema": 9.4.0-pre "@grafana/tsconfig": ^1.2.0-rc1 - "@leeoniya/ufuzzy": 0.9.0 + "@leeoniya/ufuzzy": 1.0.2 "@mdx-js/react": 1.6.22 "@monaco-editor/react": 4.4.6 "@popperjs/core": 2.11.6 @@ -6246,17 +6246,10 @@ __metadata: languageName: node linkType: hard -"@leeoniya/ufuzzy@npm:0.9.0": - version: 0.9.0 - resolution: "@leeoniya/ufuzzy@npm:0.9.0" - checksum: ee1b781530b3dbddd44eb0923f576028829209648c7b7283e5981f89527ac029c034d234ac2e98c9c25e99fe1d97e524d1e3ee0a79f5cbb92230d36e9cfa69d5 - languageName: node - linkType: hard - -"@leeoniya/ufuzzy@npm:0.9.1": - version: 0.9.1 - resolution: "@leeoniya/ufuzzy@npm:0.9.1" - checksum: 27750dff2e754ec3729937abce7c36b87917325e934cef7b1bb54a2310fd1e497dcb725397abee8c714f24a84da155120177a93da8f9706eefe32a8a1bb66945 +"@leeoniya/ufuzzy@npm:1.0.2": + version: 1.0.2 + resolution: "@leeoniya/ufuzzy@npm:1.0.2" + checksum: 5460378a8c32d121b0bc7c8e95cde995316516655528e248051b1bf360cdca0311ef3275de14b802587748231333cee6183c931b3abba26f9e4236ecc4959aa3 languageName: node linkType: hard @@ -22083,7 +22076,7 @@ __metadata: "@grafana/tsconfig": ^1.2.0-rc1 "@grafana/ui": "workspace:*" "@kusto/monaco-kusto": 5.3.6 - "@leeoniya/ufuzzy": 0.9.1 + "@leeoniya/ufuzzy": 1.0.2 "@lezer/common": 1.0.1 "@lezer/highlight": 1.1.2 "@lezer/lr": 1.3.1