diff --git a/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx b/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx index 10e2eeb8d03..4fdb00d6748 100644 --- a/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx +++ b/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx @@ -55,15 +55,15 @@ describe('FlameGraphContainer', () => { it('should update search when row selected in top table', async () => { render(); await userEvent.click((await screen.findAllByTitle('Highlight symbol'))[0]); - expect(screen.getByDisplayValue('net/http.HandlerFunc.ServeHTTP')).toBeInTheDocument(); + expect(screen.getByDisplayValue('^net/http\\.HandlerFunc\\.ServeHTTP$')).toBeInTheDocument(); // Unclick the selection so that we can click something else and continue test checks await userEvent.click((await screen.findAllByTitle('Highlight symbol'))[0]); await userEvent.click((await screen.findAllByTitle('Highlight symbol'))[1]); - expect(screen.getByDisplayValue('total')).toBeInTheDocument(); + expect(screen.getByDisplayValue('^total$')).toBeInTheDocument(); // after it is highlighted it will be the only (first) item in the table so [1] -> [0] await userEvent.click((await screen.findAllByTitle('Highlight symbol'))[0]); - expect(screen.queryByDisplayValue('total')).not.toBeInTheDocument(); + expect(screen.queryByDisplayValue('^total$')).not.toBeInTheDocument(); }); it('should render options', async () => { diff --git a/packages/grafana-flamegraph/src/FlameGraphContainer.tsx b/packages/grafana-flamegraph/src/FlameGraphContainer.tsx index da09ea26739..5d95051e353 100644 --- a/packages/grafana-flamegraph/src/FlameGraphContainer.tsx +++ b/packages/grafana-flamegraph/src/FlameGraphContainer.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import * as React from 'react'; import { useMeasure } from 'react-use'; -import { DataFrame, GrafanaTheme2 } from '@grafana/data'; +import { DataFrame, GrafanaTheme2, escapeStringForRegex } from '@grafana/data'; import { ThemeContext } from '@grafana/ui'; import FlameGraph from './FlameGraph/FlameGraph'; @@ -189,11 +189,13 @@ const FlameGraphContainer = ({ const onSymbolClick = useCallback( (symbol: string) => { - if (search === symbol) { + const anchored = `^${escapeStringForRegex(symbol)}$`; + + if (search === anchored) { setSearch(''); } else { onTableSymbolClick?.(symbol); - setSearch(symbol); + setSearch(anchored); resetFocus(); } }, @@ -241,7 +243,13 @@ const FlameGraphContainer = ({ matchedLabels={matchedLabels} sandwichItem={sandwichItem} onSandwich={setSandwichItem} - onSearch={setSearch} + onSearch={(str) => { + if (!str) { + setSearch(''); + return; + } + setSearch(`^${escapeStringForRegex(str)}$`); + }} onTableSort={onTableSort} colorScheme={colorScheme} /> diff --git a/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx b/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx index 8c5461bb053..b9072f42c29 100644 --- a/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx +++ b/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx @@ -10,6 +10,7 @@ import { FieldType, GrafanaTheme2, MappingType, + escapeStringForRegex, } from '@grafana/data'; import { IconButton, @@ -332,7 +333,7 @@ type ActionCellProps = { function ActionCell(props: ActionCellProps) { const styles = getStylesActionCell(); const symbol = props.frame.fields.find((f: Field) => f.name === 'Symbol')?.values[props.rowIndex]; - const isSearched = props.search === symbol; + const isSearched = props.search === `^${escapeStringForRegex(String(symbol))}$`; const isSandwiched = props.sandwichItem === symbol; return (