From 208df33e041ec385fc94d9c34f4ea3fd6b900a64 Mon Sep 17 00:00:00 2001 From: Hasith De Alwis <83139626+HasithDeAlwis@users.noreply.github.com> Date: Thu, 17 Jul 2025 05:00:57 -0400 Subject: [PATCH] Trace View: Add span filters to URL (#107363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(explore/spanFilters): move default filters and search props to shared folders chore: ammend with refactor commit chore: ammend, fix type errors * feat(explore): add spanFilters to global state on explore * feat(explore/spanFilters): sync span filters with global redux state * feat(explore): sync spanFilters with URL state * Moved span filters to panel state * Fix types * Fix tests in useSearch * Fix TraceView tests * Remove console.warn * fix(test/trace-view-container): add span filter context to intial state * refactor(traceview): use generic redux action * fix(traceview): prune array objects correctly to preserve tag state in URL --------- Co-authored-by: André Pereira --- packages/grafana-data/src/index.ts | 2 + packages/grafana-data/src/types/explore.ts | 26 ++ .../explore/TraceView/TraceView.test.tsx | 1 - .../features/explore/TraceView/TraceView.tsx | 7 +- .../TraceView/TraceViewContainer.test.tsx | 17 +- .../SearchBar/NextPrevResult.test.tsx | 4 +- .../SearchBar/TracePageSearchBar.test.tsx | 5 +- .../SearchBar/TracePageSearchBar.tsx | 5 +- .../SpanFilters/SpanFilters.test.tsx | 5 +- .../SpanFilters/SpanFilters.tsx | 26 +- .../SpanFilters/SpanFiltersTags.tsx | 10 +- .../TracePageHeader/TracePageHeader.test.tsx | 5 +- .../TracePageHeader/TracePageHeader.tsx | 7 +- .../components/utils/filter-spans.test.ts | 334 ++++++++++-------- .../components/utils/filter-spans.tsx | 23 +- .../explore/TraceView/useSearch.test.ts | 51 ++- .../features/explore/TraceView/useSearch.ts | 128 ++++--- .../hooks/useStateSync/external.utils.ts | 14 +- .../app/features/explore/state/constants.ts | 19 + .../app/plugins/panel/traces/TagsEditor.tsx | 8 +- .../app/plugins/panel/traces/TracesPanel.tsx | 6 +- 21 files changed, 450 insertions(+), 253 deletions(-) diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index 23414822473..2fced788c3e 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -492,6 +492,8 @@ export type { ExploreLogsPanelState, SplitOpenOptions, SplitOpen, + TraceSearchProps, + TraceSearchTag, } from './types/explore'; export type { TraceKeyValuePair, TraceLog, TraceSpanReference, TraceSpanRow } from './types/trace'; export type { FlotDataPoint } from './types/flot'; diff --git a/packages/grafana-data/src/types/explore.ts b/packages/grafana-data/src/types/explore.ts index f95d90ac409..68f9b999d40 100644 --- a/packages/grafana-data/src/types/explore.ts +++ b/packages/grafana-data/src/types/explore.ts @@ -18,6 +18,31 @@ export type URLRange = { to: URLRangeValue; }; +/** + * @internal + */ +export interface TraceSearchProps { + serviceName?: string; + serviceNameOperator: string; + spanName?: string; + spanNameOperator: string; + from?: string; + fromOperator: string; + to?: string; + toOperator: string; + tags: TraceSearchTag[]; + query?: string; + matchesOnly: boolean; + criticalPathOnly: boolean; +} + +export interface TraceSearchTag { + id: string; + key?: string; + operator: string; + value?: string; +} + /** @internal */ export interface ExploreUrlState { datasource: string | null; @@ -45,6 +70,7 @@ export interface ExploreCorrelationHelperData { export interface ExploreTracePanelState { spanId?: string; + spanFilters?: TraceSearchProps; } export interface ExploreLogsPanelState { diff --git a/public/app/features/explore/TraceView/TraceView.test.tsx b/public/app/features/explore/TraceView/TraceView.test.tsx index 8b716bef3e1..69caf06d38f 100644 --- a/public/app/features/explore/TraceView/TraceView.test.tsx +++ b/public/app/features/explore/TraceView/TraceView.test.tsx @@ -19,7 +19,6 @@ function getTraceView(frames: DataFrame[]) { return ( {}} traceProp={transformDataFrames(frames[0])!} diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index ecb71ee92a8..19203ec818a 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -14,6 +14,7 @@ import { mapInternalLinkToExplore, SplitOpen, TimeRange, + TraceSearchProps, } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { getTraceToLogsOptions, TraceToMetricsData, TraceToProfilesData } from '@grafana/o11y-ds-frontend'; @@ -40,7 +41,7 @@ import { createSpanLinkFactory } from './createSpanLink'; import { useChildrenState } from './useChildrenState'; import { useDetailState } from './useDetailState'; import { useHoverIndentGuide } from './useHoverIndentGuide'; -import { SearchProps, useSearch } from './useSearch'; +import { useSearch } from './useSearch'; import { useViewRange } from './useViewRange'; const getStyles = (theme: GrafanaTheme2) => ({ @@ -66,7 +67,7 @@ type Props = { createSpanLink?: SpanLinkFunc; focusedSpanId?: string; createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel; - spanFilters?: SearchProps; + spanFilters?: TraceSearchProps; timeRange: TimeRange; }; @@ -98,7 +99,7 @@ export function TraceView(props: Props) { const { removeHoverIndentGuideId, addHoverIndentGuideId, hoverIndentGuideIds } = useHoverIndentGuide(); const { viewRange, updateViewRangeTime, updateNextViewRangeTime } = useViewRange(); const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState(); - const { search, setSearch, spanFilterMatches } = useSearch(traceProp?.spans, spanFilters); + const { search, setSearch, spanFilterMatches } = useSearch(exploreId, traceProp?.spans, spanFilters); const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState(''); const [showSpanFilters, setShowSpanFilters] = useToggle(false); const [headerHeight, setHeaderHeight] = useState(100); diff --git a/public/app/features/explore/TraceView/TraceViewContainer.test.tsx b/public/app/features/explore/TraceView/TraceViewContainer.test.tsx index ca32e41af81..14783bdc41f 100644 --- a/public/app/features/explore/TraceView/TraceViewContainer.test.tsx +++ b/public/app/features/explore/TraceView/TraceViewContainer.test.tsx @@ -5,6 +5,8 @@ import { Provider } from 'react-redux'; import { TimeRange } from '@grafana/data'; import { configureStore } from '../../../store/configureStore'; +import { initialExploreState } from '../state/main'; +import { makeExplorePaneState } from '../state/utils'; // TODO: rebase after https://github.com/grafana/grafana/pull/105711, as this is already fixed // eslint-disable-next-line no-restricted-imports @@ -20,7 +22,19 @@ jest.mock('@grafana/runtime', () => { }); function renderTraceViewContainer(frames = [frameOld]) { - const store = configureStore(); + const initialState = { + explore: { + ...initialExploreState, + panes: { + left: makeExplorePaneState({ + initialized: true, + datasourceInstance: null, + }), + }, + }, + }; + + const store = configureStore(initialState); const { container, baseElement } = render( @@ -84,7 +98,6 @@ describe('TraceViewContainer', () => { const tagOption = screen.getByText('component'); await waitFor(() => expect(tagOption).toBeInTheDocument()); await user.click(tagOption); - await waitFor(() => { expect( screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' })[0].parentElement!.className diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.test.tsx index 7d4ab670e22..719d1e7aedc 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.test.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.test.tsx @@ -17,8 +17,8 @@ import userEvent from '@testing-library/user-event'; import { useState } from 'react'; import { createTheme } from '@grafana/data'; +import { DEFAULT_SPAN_FILTERS } from 'app/features/explore/state/constants'; -import { defaultFilters } from '../../../useSearch'; import { trace } from '../mocks'; import NextPrevResult, { getStyles } from './NextPrevResult'; @@ -39,7 +39,7 @@ describe('', () => { const [focusedSpanIndexForSearch, setFocusedSpanIndexForSearch] = useState(-1); const searchBarProps = { trace: trace, - search: defaultFilters, + search: DEFAULT_SPAN_FILTERS, spanFilterMatches: props.matches ? new Set(props.matches) : undefined, showSpanFilterMatchesOnly: false, setShowSpanFilterMatchesOnly: jest.fn(), diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.test.tsx index 29368222e46..96f1ba73aa7 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.test.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.test.tsx @@ -14,7 +14,8 @@ import { render, screen } from '@testing-library/react'; -import { defaultFilters } from '../../../useSearch'; +import { DEFAULT_SPAN_FILTERS } from 'app/features/explore/state/constants'; + import { trace } from '../mocks'; import TracePageSearchBar from './TracePageSearchBar'; @@ -23,7 +24,7 @@ describe('', () => { const TracePageSearchBarWithProps = (props: { matches: string[] | undefined }) => { const searchBarProps = { trace: trace, - search: defaultFilters, + search: DEFAULT_SPAN_FILTERS, spanFilterMatches: props.matches ? new Set(props.matches) : undefined, showSpanFilterMatchesOnly: false, setShowSpanFilterMatchesOnly: jest.fn(), diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx index 954b57cb039..458c0915e5f 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx @@ -15,12 +15,11 @@ import { css } from '@emotion/css'; import { memo, Dispatch, SetStateAction, useMemo } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, TraceSearchProps } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { Button, Switch, useStyles2 } from '@grafana/ui'; import { getButtonStyles } from '@grafana/ui/internal'; -import { SearchProps } from '../../../useSearch'; import { Trace } from '../../types/trace'; import { convertTimeFilter } from '../../utils/filter-spans'; @@ -28,7 +27,7 @@ import NextPrevResult from './NextPrevResult'; export type TracePageSearchBarProps = { trace: Trace; - search: SearchProps; + search: TraceSearchProps; spanFilterMatches: Set | undefined; setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void; setShowCriticalPathSpansOnly: (showCriticalPath: boolean) => void; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx index 9ecb184f686..69241d632a9 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx @@ -2,7 +2,8 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useState } from 'react'; -import { defaultFilters } from '../../../useSearch'; +import { DEFAULT_SPAN_FILTERS } from 'app/features/explore/state/constants'; + import { Trace } from '../../types/trace'; import { SpanFilters } from './SpanFilters'; @@ -50,7 +51,7 @@ const trace: Trace = { describe('SpanFilters', () => { let user: ReturnType; const SpanFiltersWithProps = ({ showFilters = true, matches }: { showFilters?: boolean; matches?: Set }) => { - const [search, setSearch] = useState(defaultFilters); + const [search, setSearch] = useState(DEFAULT_SPAN_FILTERS); const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false); const [showCriticalPathSpansOnly, setShowCriticalPathSpansOnly] = useState(false); const props = { diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx index 0708a574c17..d9909845984 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx @@ -13,14 +13,14 @@ // limitations under the License. import { css } from '@emotion/css'; -import React, { useState, useEffect, memo, useCallback } from 'react'; +import React, { useState, useEffect, memo, useCallback, useRef } from 'react'; -import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data'; +import { GrafanaTheme2, TraceSearchProps, SelectableValue, toOption } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { IntervalInput } from '@grafana/o11y-ds-frontend'; import { Collapse, Icon, InlineField, InlineFieldRow, Select, Stack, Tooltip, useStyles2 } from '@grafana/ui'; -import { defaultFilters, SearchProps } from '../../../useSearch'; +import { DEFAULT_SPAN_FILTERS } from '../../../../state/constants'; import { getTraceServiceNames, getTraceSpanNames } from '../../../utils/tags'; import SearchBarInput from '../../common/SearchBarInput'; import { Trace } from '../../types/trace'; @@ -31,8 +31,8 @@ import { SpanFiltersTags } from './SpanFiltersTags'; export type SpanFilterProps = { trace: Trace; - search: SearchProps; - setSearch: React.Dispatch>; + search: TraceSearchProps; + setSearch: (newSearch: TraceSearchProps) => void; showSpanFilters: boolean; setShowSpanFilters: (isOpen: boolean) => void; setFocusedSpanIdForSearch: React.Dispatch>; @@ -57,6 +57,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => { const [focusedSpanIndexForSearch, setFocusedSpanIndexForSearch] = useState(-1); const [tagKeys, setTagKeys] = useState>>(); const [tagValues, setTagValues] = useState<{ [key: string]: Array> }>({}); + const prevTraceIdRef = useRef(); const durationRegex = /^\d+(?:\.\d)?\d*(?:ns|us|µs|ms|s|m|h)$/; @@ -65,11 +66,20 @@ export const SpanFilters = memo((props: SpanFilterProps) => { setSpanNames(undefined); setTagKeys(undefined); setTagValues({}); - setSearch(defaultFilters); + setSearch(DEFAULT_SPAN_FILTERS); }, [setSearch]); useEffect(() => { - clear(); + // Only clear filters when trace ID actually changes (not on initial mount) + const currentTraceId = trace?.traceID; + + const traceHasChanged = prevTraceIdRef.current && prevTraceIdRef.current !== currentTraceId; + + if (traceHasChanged) { + clear(); + } + + prevTraceIdRef.current = currentTraceId; }, [clear, trace]); const setShowSpanFilterMatchesOnly = useCallback( @@ -90,7 +100,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => { return null; } - const setSpanFiltersSearch = (spanSearch: SearchProps) => { + const setSpanFiltersSearch = (spanSearch: TraceSearchProps) => { setFocusedSpanIndexForSearch(-1); setFocusedSpanIdForSearch(''); setSearch(spanSearch); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx index ce36acf89ea..3cf2d5f8488 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx @@ -2,18 +2,18 @@ import { css } from '@emotion/css'; import React from 'react'; import { useMount } from 'react-use'; -import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data'; +import { GrafanaTheme2, SelectableValue, toOption, TraceSearchProps, TraceSearchTag } from '@grafana/data'; import { t } from '@grafana/i18n'; import { AccessoryButton } from '@grafana/plugin-ui'; import { Input, Select, Stack, useStyles2 } from '@grafana/ui'; -import { randomId, SearchProps, Tag } from '../../../useSearch'; +import { randomId } from '../../../../state/constants'; import { getTraceTagKeys, getTraceTagValues } from '../../../utils/tags'; import { Trace } from '../../types/trace'; interface Props { - search: SearchProps; - setSearch: (search: SearchProps) => void; + search: TraceSearchProps; + setSearch: (search: TraceSearchProps) => void; trace: Trace; tagKeys?: Array>; setTagKeys: React.Dispatch> | undefined>>; @@ -47,7 +47,7 @@ export const SpanFiltersTags = ({ search, trace, setSearch, tagKeys, setTagKeys, } }); - const onTagChange = (tag: Tag, v: SelectableValue) => { + const onTagChange = (tag: TraceSearchTag, v: SelectableValue) => { setSearch({ ...search, tags: search.tags?.map((x) => { diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.test.tsx index cf123bb85af..95d78fff092 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.test.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.test.tsx @@ -15,8 +15,7 @@ import { getByText, render } from '@testing-library/react'; import { MutableDataFrame } from '@grafana/data'; - -import { defaultFilters } from '../../useSearch'; +import { DEFAULT_SPAN_FILTERS } from 'app/features/explore/state/constants'; import { TracePageHeader } from './TracePageHeader'; import { trace } from './mocks'; @@ -25,7 +24,7 @@ const setup = () => { const defaultProps = { trace, timeZone: '', - search: defaultFilters, + search: DEFAULT_SPAN_FILTERS, setSearch: jest.fn(), showSpanFilters: true, setShowSpanFilters: jest.fn(), diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx index 076edfccc46..72d3a035401 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx @@ -17,12 +17,11 @@ import cx from 'classnames'; import { memo, useEffect, useMemo } from 'react'; import * as React from 'react'; -import { CoreApp, DataFrame, dateTimeFormat, GrafanaTheme2 } from '@grafana/data'; +import { TraceSearchProps, CoreApp, DataFrame, dateTimeFormat, GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { TimeZone } from '@grafana/schema'; import { Badge, BadgeColor, Tooltip, useStyles2 } from '@grafana/ui'; -import { SearchProps } from '../../useSearch'; import ExternalLinks from '../common/ExternalLinks'; import TraceName from '../common/TraceName'; import { getTraceLinks } from '../model/link-patterns'; @@ -38,8 +37,8 @@ export type TracePageHeaderProps = { data: DataFrame; app?: CoreApp; timeZone: TimeZone; - search: SearchProps; - setSearch: React.Dispatch>; + search: TraceSearchProps; + setSearch: (newSearch: TraceSearchProps) => void; showSpanFilters: boolean; setShowSpanFilters: (isOpen: boolean) => void; setFocusedSpanIdForSearch: React.Dispatch>; diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts b/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts index 833ee607c3a..f3ab9f4a00c 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.test.ts @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { defaultFilters, defaultTagFilter } from '../../useSearch'; +import { DEFAULT_SPAN_FILTERS, DEFAULT_TAG_FILTERS } from 'app/features/explore/state/constants'; + import { TraceSpan } from '../types/trace'; import { filterSpans } from './filter-spans'; @@ -123,244 +124,273 @@ describe('filterSpans', () => { const spans = [span0, span2] as TraceSpan[]; it('should return `undefined` if spans is falsy', () => { - expect(filterSpans({ ...defaultFilters, spanName: 'operationName' }, null)).toBe(undefined); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, spanName: 'operationName' }, null)).toBe(undefined); }); // Service / span name it('should return spans whose serviceName match a filter', () => { - expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName0' }, spans)).toEqual(new Set([spanID0])); - expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName2' }, spans)).toEqual(new Set([spanID2])); - expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName2', serviceNameOperator: '!=' }, spans)).toEqual( - new Set([spanID0]) - ); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0' }, spans)).toEqual(new Set([spanID0])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName2' }, spans)).toEqual(new Set([spanID2])); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName2', serviceNameOperator: '!=' }, spans) + ).toEqual(new Set([spanID0])); }); it('should return spans whose operationName match a filter', () => { - expect(filterSpans({ ...defaultFilters, spanName: 'operationName0' }, spans)).toEqual(new Set([spanID0])); - expect(filterSpans({ ...defaultFilters, spanName: 'operationName2' }, spans)).toEqual(new Set([spanID2])); - expect(filterSpans({ ...defaultFilters, spanName: 'operationName2', spanNameOperator: '!=' }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, spanName: 'operationName0' }, spans)).toEqual(new Set([spanID0])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, spanName: 'operationName2' }, spans)).toEqual(new Set([spanID2])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, spanName: 'operationName2', spanNameOperator: '!=' }, spans)).toEqual( new Set([spanID0]) ); }); // Durations it('should return spans whose duration match a filter', () => { - expect(filterSpans({ ...defaultFilters, from: '2ns' }, spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans({ ...defaultFilters, from: '2us' }, spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans({ ...defaultFilters, from: '2ms' }, spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans({ ...defaultFilters, from: '3.05ms' }, spans)).toEqual(new Set([spanID2])); - expect(filterSpans({ ...defaultFilters, from: '3.05ms', fromOperator: '>=' }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, from: '2ns' }, spans)).toEqual(new Set([spanID0, spanID2])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, from: '2us' }, spans)).toEqual(new Set([spanID0, spanID2])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, from: '2ms' }, spans)).toEqual(new Set([spanID0, spanID2])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, from: '3.05ms' }, spans)).toEqual(new Set([spanID2])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, from: '3.05ms', fromOperator: '>=' }, spans)).toEqual( new Set([spanID0, spanID2]) ); - expect(filterSpans({ ...defaultFilters, from: '3.05ms', fromOperator: '>=', to: '4ms' }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, from: '3.05ms', fromOperator: '>=', to: '4ms' }, spans)).toEqual( new Set([spanID0]) ); - expect(filterSpans({ ...defaultFilters, to: '4ms' }, spans)).toEqual(new Set([spanID0])); - expect(filterSpans({ ...defaultFilters, to: '5ms', toOperator: '<=' }, spans)).toEqual(new Set([spanID0, spanID2])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, to: '4ms' }, spans)).toEqual(new Set([spanID0])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, to: '5ms', toOperator: '<=' }, spans)).toEqual( + new Set([spanID0, spanID2]) + ); }); // Tags it('should return spans whose tags kv.key match a filter', () => { - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1' }] }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1' }] }, spans)).toEqual( new Set([spanID0, spanID2]) ); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey0' }] }, spans)).toEqual( new Set([spanID0]) ); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2' }] }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey2' }] }, spans)).toEqual( new Set([spanID2]) ); expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }] }, spans) + filterSpans( + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey2', operator: '!=' }] }, + spans + ) ).toEqual(new Set([spanID0])); }); it('should return spans whose kind, statusCode, statusMessage, libraryName, libraryVersion, traceState, or id match a filter', () => { - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind' }] }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'kind' }] }, spans)).toEqual( new Set([spanID0, spanID2]) ); expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', value: 'kind0' }] }, spans) + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'kind', value: 'kind0' }] }, spans) ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', operator: '!=', value: 'kind0' }] }, + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'kind', operator: '!=', value: 'kind0' }] }, spans ) ).toEqual(new Set([spanID2])); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status' }] }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'status' }] }, spans)).toEqual( new Set([spanID0, spanID2]) ); expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', value: 'unset' }] }, spans) + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'status', value: 'unset' }] }, spans) ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', operator: '!=', value: 'unset' }] }, + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'status', operator: '!=', value: 'unset' }] }, spans ) ).toEqual(new Set([spanID2])); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message' }] }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'status.message' }] }, spans) + ).toEqual(new Set([spanID0, spanID2])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message', value: 'statusMessage0' }] }, + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'status.message', value: 'statusMessage0' }] }, spans ) ).toEqual(new Set([spanID0])); expect( filterSpans( { - ...defaultFilters, - tags: [{ ...defaultTagFilter, key: 'status.message', operator: '!=', value: 'statusMessage0' }], + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'status.message', operator: '!=', value: 'statusMessage0' }], }, spans ) ).toEqual(new Set([spanID2])); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name' }] }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'library.name' }] }, spans) + ).toEqual(new Set([spanID0, spanID2])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name', value: 'libraryName' }] }, + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'library.name', value: 'libraryName' }] }, spans ) ).toEqual(new Set([spanID0, spanID2])); expect( filterSpans( { - ...defaultFilters, - tags: [{ ...defaultTagFilter, key: 'library.name', operator: '!=', value: 'libraryName' }], + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'library.name', operator: '!=', value: 'libraryName' }], }, spans ) ).toEqual(new Set([])); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version' }] }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'library.version' }] }, spans) + ).toEqual(new Set([spanID0, spanID2])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version', value: 'libraryVersion0' }] }, + { + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'library.version', value: 'libraryVersion0' }], + }, spans ) ).toEqual(new Set([spanID0])); expect( filterSpans( { - ...defaultFilters, - tags: [{ ...defaultTagFilter, key: 'library.version', operator: '!=', value: 'libraryVersion0' }], + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'library.version', operator: '!=', value: 'libraryVersion0' }], }, spans ) ).toEqual(new Set([spanID2])); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state' }] }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'trace.state' }] }, spans) + ).toEqual(new Set([spanID0, spanID2])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state', value: 'traceState0' }] }, + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'trace.state', value: 'traceState0' }] }, spans ) ).toEqual(new Set([spanID0])); expect( filterSpans( { - ...defaultFilters, - tags: [{ ...defaultTagFilter, key: 'trace.state', operator: '!=', value: 'traceState0' }], + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'trace.state', operator: '!=', value: 'traceState0' }], }, spans ) ).toEqual(new Set([spanID2])); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id' }] }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'id' }] }, spans)).toEqual( new Set([spanID0, spanID2]) ); expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', value: 'span-id-0' }] }, spans) + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'id', value: 'span-id-0' }] }, spans) ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', operator: '!=', value: 'span-id-0' }] }, + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'id', operator: '!=', value: 'span-id-0' }] }, spans ) ).toEqual(new Set([spanID2])); }); it('should return spans whose process.tags kv.key match a filter', () => { - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey1' }] }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey0' }] }, spans)).toEqual( - new Set([spanID0]) - ); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey2' }] }, spans)).toEqual( - new Set([spanID2]) - ); expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey2', operator: '!=' }] }, spans) + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'processTagKey1' }] }, spans) + ).toEqual(new Set([spanID0, spanID2])); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'processTagKey0' }] }, spans) + ).toEqual(new Set([spanID0])); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'processTagKey2' }] }, spans) + ).toEqual(new Set([spanID2])); + expect( + filterSpans( + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'processTagKey2', operator: '!=' }] }, + spans + ) ).toEqual(new Set([spanID0])); }); it('should return spans whose logs have a field whose kv.key match a filter', () => { - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey1' }] }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey0' }] }, spans)).toEqual( - new Set([spanID0]) - ); - expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey2' }] }, spans)).toEqual( - new Set([spanID2]) - ); expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey2', operator: '!=' }] }, spans) + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'logFieldKey1' }] }, spans) + ).toEqual(new Set([spanID0, spanID2])); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'logFieldKey0' }] }, spans) + ).toEqual(new Set([spanID0])); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'logFieldKey2' }] }, spans) + ).toEqual(new Set([spanID2])); + expect( + filterSpans( + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'logFieldKey2', operator: '!=' }] }, + spans + ) ).toEqual(new Set([spanID0])); }); it('it should return logs have a name which matches the filter', () => { - expect(filterSpans({ ...defaultFilters, query: 'logName0' }, spans)).toEqual(new Set([spanID0])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, query: 'logName0' }, spans)).toEqual(new Set([spanID0])); }); it('should return no spans when logs is null', () => { const nullSpan = { ...span0, logs: null }; expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey1' }] }, [ + filterSpans({ ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'logFieldKey1' }] }, [ nullSpan, ] as unknown as TraceSpan[]) ).toEqual(new Set([])); }); it("should return spans whose tags' kv.key and kv.value match a filter", () => { - expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }] }, spans) - ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '=' }] }, + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue1' }] }, spans ) ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '!=' }] }, + { + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue1', operator: '=' }], + }, + spans + ) + ).toEqual(new Set([spanID0])); + expect( + filterSpans( + { + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue1', operator: '!=' }], + }, spans ) ).toEqual(new Set([spanID2])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '=~', value: 'tagValue' }] }, + { + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', operator: '=~', value: 'tagValue' }], + }, spans ) ).toEqual(new Set([spanID0, spanID2])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '!~', value: 'tagValue1' }] }, + { + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', operator: '!~', value: 'tagValue1' }], + }, spans ) ).toEqual(new Set([spanID2])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '!~', value: 'tag' }] }, + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', operator: '!~', value: 'tag' }] }, spans ) ).toEqual(new Set([])); @@ -368,14 +398,23 @@ describe('filterSpans', () => { it("should not return spans whose tags' kv.key match a filter but kv.value/operator does not match", () => { expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '!=' }] }, spans) + filterSpans( + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', operator: '!=' }] }, + spans + ) ).toEqual(new Set()); expect( - filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }] }, spans) + filterSpans( + { ...DEFAULT_SPAN_FILTERS, tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey2', operator: '!=' }] }, + spans + ) ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '!=' }] }, + { + ...DEFAULT_SPAN_FILTERS, + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue1', operator: '!=' }], + }, spans ) ).toEqual(new Set([spanID2])); @@ -386,10 +425,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey1' }, - { ...defaultTagFilter, key: 'tagKey0' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey1' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey0' }, ], }, spans @@ -398,10 +437,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }, - { ...defaultTagFilter, key: 'tagKey0' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue1' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey0' }, ], }, spans @@ -410,10 +449,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }, - { ...defaultTagFilter, key: 'tagKey0', value: 'tagValue0' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue1' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey0', value: 'tagValue0' }, ], }, spans @@ -424,10 +463,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey0' }, - { ...defaultTagFilter, key: 'tagKey2' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey0' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey2' }, ], }, spans @@ -436,10 +475,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey0', value: '' }, - { ...defaultTagFilter, key: 'tagKey2' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey0', value: '' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey2' }, ], }, spans @@ -450,10 +489,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey0', value: 'tagValue0' }, - { ...defaultTagFilter, key: 'tagKey2' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey0', value: 'tagValue0' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey2' }, ], }, spans @@ -462,10 +501,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey0', value: 'tagValue0' }, - { ...defaultTagFilter, key: 'tagKey2', value: 'tagValue2' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey0', value: 'tagValue0' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey2', value: 'tagValue2' }, ], }, spans @@ -474,10 +513,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }, - { ...defaultTagFilter, key: 'tagKey1', value: 'tagValue2' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue1' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue2' }, ], }, spans @@ -486,10 +525,10 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, tags: [ - { ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }, - { ...defaultTagFilter, key: 'tagKey2', value: 'tagValue2' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey1', value: 'tagValue1' }, + { ...DEFAULT_TAG_FILTERS, key: 'tagKey2', value: 'tagValue2' }, ], }, spans @@ -497,69 +536,78 @@ describe('filterSpans', () => { ).toEqual(new Set()); // query - expect(filterSpans({ ...defaultFilters, query: 'serviceName0' }, spans)).toEqual(new Set([spanID0])); - expect(filterSpans({ ...defaultFilters, query: 'tagKey1' }, spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans({ ...defaultFilters, query: 'does_not_exist' }, spans)).toEqual(new Set([])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, query: 'serviceName0' }, spans)).toEqual(new Set([spanID0])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, query: 'tagKey1' }, spans)).toEqual(new Set([spanID0, spanID2])); + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, query: 'does_not_exist' }, spans)).toEqual(new Set([])); }); // Multiple it('should return spans with multiple filters', () => { // service name + span name - expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0' }, spans)).toEqual( - new Set([spanID0]) - ); - expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName2' }, spans)).toEqual( - new Set([]) - ); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', spanName: 'operationName0' }, spans) + ).toEqual(new Set([spanID0])); + expect( + filterSpans({ ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', spanName: 'operationName2' }, spans) + ).toEqual(new Set([])); expect( filterSpans( - { ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName2', spanNameOperator: '!=' }, + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', spanName: 'operationName2', spanNameOperator: '!=' }, spans ) ).toEqual(new Set([spanID0])); // service name + span name + duration expect( - filterSpans({ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', from: '2ms' }, spans) + filterSpans( + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', spanName: 'operationName0', from: '2ms' }, + spans + ) ).toEqual(new Set([spanID0])); expect( - filterSpans({ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', to: '2ms' }, spans) + filterSpans( + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', spanName: 'operationName0', to: '2ms' }, + spans + ) ).toEqual(new Set([])); expect( - filterSpans({ ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms' }, spans) + filterSpans( + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms' }, + spans + ) ).toEqual(new Set([spanID2])); // service name + tag key expect( filterSpans( - { ...defaultFilters, serviceName: 'serviceName0', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey0' }] }, spans ) ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, serviceName: 'serviceName0', tags: [{ ...defaultTagFilter, key: 'tagKey1' }] }, + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1' }] }, spans ) ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, serviceName: 'serviceName2', tags: [{ ...defaultTagFilter, key: 'tagKey1' }] }, + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName2', tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1' }] }, spans ) ).toEqual(new Set([spanID2])); expect( filterSpans( - { ...defaultFilters, serviceName: 'serviceName2', tags: [{ ...defaultTagFilter, key: 'tagKey2' }] }, + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName2', tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey2' }] }, spans ) ).toEqual(new Set([spanID2])); expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', - tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '!=' }], + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey1', operator: '!=' }], }, spans ) @@ -567,38 +615,38 @@ describe('filterSpans', () => { // duration + tag expect( - filterSpans({ ...defaultFilters, from: '2ms', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, spans) + filterSpans({ ...DEFAULT_SPAN_FILTERS, from: '2ms', tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey0' }] }, spans) ).toEqual(new Set([spanID0])); expect( filterSpans( - { ...defaultFilters, to: '5ms', toOperator: '<=', tags: [{ ...defaultTagFilter, key: 'tagKey2' }] }, + { ...DEFAULT_SPAN_FILTERS, to: '5ms', toOperator: '<=', tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey2' }] }, spans ) ).toEqual(new Set([spanID2])); // query + other - expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName0', query: 'tag' }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', query: 'tag' }, spans)).toEqual( new Set([spanID0]) ); - expect(filterSpans({ ...defaultFilters, serviceName: 'serviceName0', query: 'tagKey2' }, spans)).toEqual( + expect(filterSpans({ ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', query: 'tagKey2' }, spans)).toEqual( new Set([]) ); expect( filterSpans( - { ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', query: 'tagKey1' }, + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName2', spanName: 'operationName2', query: 'tagKey1' }, spans ) ).toEqual(new Set([spanID2])); expect( filterSpans( - { ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms', query: 'kind2' }, + { ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms', query: 'kind2' }, spans ) ).toEqual(new Set([spanID2])); expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', spanName: 'operationName0', from: '2ms', @@ -612,14 +660,14 @@ describe('filterSpans', () => { expect( filterSpans( { - ...defaultFilters, + ...DEFAULT_SPAN_FILTERS, serviceName: 'serviceName0', spanName: 'operationName2', spanNameOperator: '!=', from: '3.05ms', fromOperator: '>=', to: '3.5ms', - tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }], + tags: [{ ...DEFAULT_TAG_FILTERS, key: 'tagKey2', operator: '!=' }], }, spans ) diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx index 82664ded026..daafa61c3c1 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx @@ -14,16 +14,15 @@ import { SpanStatusCode } from '@opentelemetry/api'; -import { TraceKeyValuePair } from '@grafana/data'; +import { TraceKeyValuePair, TraceSearchProps, TraceSearchTag } from '@grafana/data'; -import { SearchProps, Tag } from '../../useSearch'; import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE, ID } from '../constants/span'; import TNil from '../types/TNil'; import { TraceSpan } from '../types/trace'; // filter spans where all filters added need to be true for each individual span that is returned // i.e. the more filters added -> the more specific that the returned results are -export function filterSpans(searchProps: SearchProps, spans: TraceSpan[] | TNil) { +export function filterSpans(searchProps: TraceSearchProps, spans: TraceSpan[] | TNil) { if (!spans) { return undefined; } @@ -101,7 +100,7 @@ export function getQueryMatches(query: string, spans: TraceSpan[] | TNil) { return spans.filter(isSpanAMatch); } -const getTagMatches = (spans: TraceSpan[], tags: Tag[]) => { +const getTagMatches = (spans: TraceSpan[], tags: TraceSearchTag[]) => { // remove empty/default tags tags = tags.filter((tag) => { // tag.key === '' when it is cleared via pressing x icon in select field @@ -111,7 +110,7 @@ const getTagMatches = (spans: TraceSpan[], tags: Tag[]) => { if (tags.length > 0) { return spans.filter((span: TraceSpan) => { // match against every tag filter - return tags.every((tag: Tag) => { + return tags.every((tag: TraceSearchTag) => { if (tag.key && tag.value) { if ( (tag.operator === '=' && checkKeyValConditionForMatch(tag, span)) || @@ -146,7 +145,7 @@ const getTagMatches = (spans: TraceSpan[], tags: Tag[]) => { return undefined; }; -const checkKeyValConditionForRegex = (tag: Tag, span: TraceSpan) => { +const checkKeyValConditionForRegex = (tag: TraceSearchTag, span: TraceSpan) => { return ( span.tags.some((kv) => checkKeyAndValueForRegex(tag, kv)) || span.process.tags.some((kv) => checkKeyAndValueForRegex(tag, kv)) || @@ -167,7 +166,7 @@ const checkKeyValConditionForRegex = (tag: Tag, span: TraceSpan) => { ); }; -const checkKeyValConditionForMatch = (tag: Tag, span: TraceSpan) => { +const checkKeyValConditionForMatch = (tag: TraceSearchTag, span: TraceSpan) => { return ( span.tags.some((kv) => checkKeyAndValueForMatch(tag, kv)) || span.process.tags.some((kv) => checkKeyAndValueForMatch(tag, kv)) || @@ -190,11 +189,11 @@ const checkKeyForMatch = (tagKey: string, key: string) => { return tagKey === key.toString(); }; -const checkKeyAndValueForMatch = (tag: Tag, kv: TraceKeyValuePair) => { +const checkKeyAndValueForMatch = (tag: TraceSearchTag, kv: TraceKeyValuePair) => { return tag.key === kv.key && tag.value === getStringValue(kv.value); }; -const checkKeyAndValueForRegex = (tag: Tag, kv: TraceKeyValuePair) => { +const checkKeyAndValueForRegex = (tag: TraceSearchTag, kv: TraceKeyValuePair) => { return kv.key.includes(tag.key || '') && getStringValue(kv.value).includes(tag.value || ''); }; @@ -202,7 +201,7 @@ const getStringValue = (value: string | number | boolean | undefined) => { return value ? value.toString() : ''; }; -const getServiceNameMatches = (spans: TraceSpan[], searchProps: SearchProps) => { +const getServiceNameMatches = (spans: TraceSpan[], searchProps: TraceSearchProps) => { return spans.filter((span: TraceSpan) => { return searchProps.serviceNameOperator === '=' ? span.process.serviceName === searchProps.serviceName @@ -210,7 +209,7 @@ const getServiceNameMatches = (spans: TraceSpan[], searchProps: SearchProps) => }); }; -const getSpanNameMatches = (spans: TraceSpan[], searchProps: SearchProps) => { +const getSpanNameMatches = (spans: TraceSpan[], searchProps: TraceSearchProps) => { return spans.filter((span: TraceSpan) => { return searchProps.spanNameOperator === '=' ? span.operationName === searchProps.spanName @@ -218,7 +217,7 @@ const getSpanNameMatches = (spans: TraceSpan[], searchProps: SearchProps) => { }); }; -const getDurationMatches = (spans: TraceSpan[], searchProps: SearchProps) => { +const getDurationMatches = (spans: TraceSpan[], searchProps: TraceSearchProps) => { const from = convertTimeFilter(searchProps?.from || ''); const to = convertTimeFilter(searchProps?.to || ''); let filteredSpans: TraceSpan[] = []; diff --git a/public/app/features/explore/TraceView/useSearch.test.ts b/public/app/features/explore/TraceView/useSearch.test.ts index 0379aabba75..a92611b6c94 100644 --- a/public/app/features/explore/TraceView/useSearch.test.ts +++ b/public/app/features/explore/TraceView/useSearch.test.ts @@ -1,7 +1,40 @@ +import { configureStore } from '@reduxjs/toolkit'; import { act, renderHook } from '@testing-library/react'; +import React, { ReactNode } from 'react'; +import { Provider } from 'react-redux'; + +import { DEFAULT_SPAN_FILTERS } from '../state/constants'; import { TraceSpan } from './components/types/trace'; -import { defaultFilters, useSearch } from './useSearch'; +import { useSearch } from './useSearch'; + +// Create a mock store with the necessary structure +const createMockStore = (initialState = {}) => { + return configureStore({ + reducer: { + explore: (state = { panes: {} }) => state, + }, + preloadedState: { + explore: { + panes: {}, + ...initialState, + }, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + thunk: true, + serializableCheck: false, + immutableCheck: false, + }), + }); +}; + +// Create a wrapper component that provides the Redux store +const createWrapper = (store: ReturnType) => { + return ({ children }: { children: ReactNode }) => { + return React.createElement(Provider, { store, children }); + }; +}; describe('useSearch', () => { const spans = [ @@ -28,15 +61,23 @@ describe('useSearch', () => { ]; it('returns matching span IDs', async () => { - const { result } = renderHook(() => useSearch(spans)); - act(() => result.current.setSearch({ ...defaultFilters, serviceName: 'service1' })); + const store = createMockStore(); + const wrapper = createWrapper(store); + + // Use local state by not providing exploreId + const { result } = renderHook(() => useSearch(undefined, spans), { wrapper }); + act(() => result.current.setSearch({ ...DEFAULT_SPAN_FILTERS, serviceName: 'service1' })); expect(result.current.spanFilterMatches?.size).toBe(1); expect(result.current.spanFilterMatches?.has('span1')).toBe(true); }); it('works without spans', async () => { - const { result } = renderHook(() => useSearch()); - act(() => result.current.setSearch({ ...defaultFilters, serviceName: 'service1' })); + const store = createMockStore(); + const wrapper = createWrapper(store); + + // Use local state by not providing exploreId + const { result } = renderHook(() => useSearch(), { wrapper }); + act(() => result.current.setSearch({ ...DEFAULT_SPAN_FILTERS, serviceName: 'service1' })); expect(result.current.spanFilterMatches).toBe(undefined); }); }); diff --git a/public/app/features/explore/TraceView/useSearch.ts b/public/app/features/explore/TraceView/useSearch.ts index 779a318fec2..326f5627b9f 100644 --- a/public/app/features/explore/TraceView/useSearch.ts +++ b/public/app/features/explore/TraceView/useSearch.ts @@ -1,65 +1,87 @@ import { cloneDeep, merge } from 'lodash'; -import { useEffect, useMemo, useState } from 'react'; -import { v4 as uuidv4 } from 'uuid'; +import { useEffect, useMemo, useCallback, useState } from 'react'; -import { InterpolateFunction } from '@grafana/data'; +import { InterpolateFunction, TraceSearchProps } from '@grafana/data'; +import { useDispatch, useSelector } from 'app/types/store'; + +import { DEFAULT_SPAN_FILTERS, randomId } from '../state/constants'; +import { changePanelState } from '../state/explorePane'; import { TraceSpan } from './components/types/trace'; import { filterSpans } from './components/utils/filter-spans'; -export interface SearchProps { - serviceName?: string; - serviceNameOperator: string; - spanName?: string; - spanNameOperator: string; - from?: string; - fromOperator: string; - to?: string; - toOperator: string; - tags: Tag[]; - query?: string; - matchesOnly: boolean; - criticalPathOnly: boolean; -} - -export interface Tag { - id: string; - key?: string; - operator: string; - value?: string; -} - -export const randomId = () => uuidv4().slice(0, 12); - -export const defaultTagFilter = { - id: randomId(), - operator: '=', -}; - -export const defaultFilters = { - spanNameOperator: '=', - serviceNameOperator: '=', - fromOperator: '>', - toOperator: '<', - tags: [defaultTagFilter], - matchesOnly: false, - criticalPathOnly: false, -}; - /** * Controls the state of search input that highlights spans if they match the search string. - * @param spans + * Uses global state for Explore (when exploreId is provided) or local state for panels (when no exploreId). + * @param exploreId - The explore pane ID (optional, for global state management) + * @param spans - The trace spans to filter + * @param initialFilters - Initial filters to set */ -export function useSearch(spans?: TraceSpan[], initialFilters?: SearchProps) { - const [search, setSearch] = useState(merge(cloneDeep(defaultFilters), initialFilters ?? {})); +export function useSearch(exploreId?: string, spans?: TraceSpan[], initialFilters?: TraceSearchProps) { + const dispatch = useDispatch(); + // Global state logic (for Explore) + const panelState = useSelector((state) => state.explore.panes[exploreId ?? '']?.panelsState.trace); + const { spanFilters: globalFilters } = panelState || {}; + + // Local state logic (for TracesPanel and other non-Explore usage) + const [localSearch, setLocalSearch] = useState(() => { + const merged = merge(cloneDeep(DEFAULT_SPAN_FILTERS), initialFilters ?? {}); + // Ensure tags is always an array + if (!merged.tags || !Array.isArray(merged.tags)) { + merged.tags = [{ id: randomId(), operator: '=' }]; + } + return merged; + }); + + // Determine which state to use based on exploreId presence + const search = exploreId + ? globalFilters || merge(cloneDeep(DEFAULT_SPAN_FILTERS), initialFilters ?? {}) + : localSearch; + + // Ensure tags is always an array for safety + if (search && (!search.tags || !Array.isArray(search.tags))) { + search.tags = [{ id: randomId(), operator: '=' }]; + } + + // Global state initialization (only when exploreId exists) useEffect(() => { - if (initialFilters) { - setSearch((prev) => { - return merge(cloneDeep(prev), initialFilters); + if (exploreId && !globalFilters) { + const mergedFilters = merge(cloneDeep(DEFAULT_SPAN_FILTERS), initialFilters ?? {}); + // Ensure tags is always an array + if (!mergedFilters.tags || !Array.isArray(mergedFilters.tags)) { + mergedFilters.tags = [{ id: randomId(), operator: '=' }]; + } + + dispatch(changePanelState(exploreId, 'trace', { ...panelState, spanFilters: mergedFilters })); + } + }, [exploreId, initialFilters, globalFilters, dispatch, panelState]); + + // Local state updates (only when no exploreId) + useEffect(() => { + if (!exploreId && initialFilters) { + setLocalSearch((prev) => { + const merged = merge(cloneDeep(prev), initialFilters); + // Ensure tags is always an array + if (!merged.tags || !Array.isArray(merged.tags)) { + merged.tags = [{ id: randomId(), operator: '=' }]; + } + return merged; }); } - }, [initialFilters]); + }, [exploreId, initialFilters]); + + // Function to update span filters (global or local based on exploreId) + const setSearch = useCallback( + (newSearch: TraceSearchProps) => { + if (exploreId) { + dispatch(changePanelState(exploreId, 'trace', { ...panelState, spanFilters: newSearch })); + } else { + setLocalSearch(newSearch); + } + }, + [exploreId, dispatch, panelState] + ); const spanFilterMatches: Set | undefined = useMemo(() => { return spans && filterSpans(search, spans); @@ -68,12 +90,18 @@ export function useSearch(spans?: TraceSpan[], initialFilters?: SearchProps) { return { search, setSearch, spanFilterMatches }; } -export function replaceSearchVariables(replaceVariables: InterpolateFunction, search?: SearchProps) { +export function replaceSearchVariables(replaceVariables: InterpolateFunction, search?: TraceSearchProps) { if (!search) { return search; } const newSearch = { ...search }; + + // Ensure tags is always an array + if (!newSearch.tags || !Array.isArray(newSearch.tags)) { + newSearch.tags = [{ id: randomId(), operator: '=' }]; + } + if (newSearch.query) { newSearch.query = replaceVariables(newSearch.query); } diff --git a/public/app/features/explore/hooks/useStateSync/external.utils.ts b/public/app/features/explore/hooks/useStateSync/external.utils.ts index 2df25f0dfde..f974d185cfc 100644 --- a/public/app/features/explore/hooks/useStateSync/external.utils.ts +++ b/public/app/features/explore/hooks/useStateSync/external.utils.ts @@ -21,7 +21,19 @@ export function getUrlStateFromPaneState(pane: ExploreItemState): ExploreUrlStat * if the resulting object is empty, returns undefined **/ function pruneObject(obj: object): object | undefined { - let pruned = mapValues(obj, (value) => (isObject(value) ? pruneObject(value) : value)); + let pruned = mapValues(obj, (value: unknown) => { + if (isObject(value)) { + if (Array.isArray(value)) { + // For arrays, recursively prune each item and filter out empty results + const prunedArray = value + .map((item: unknown) => (isObject(item) ? pruneObject(item) : item)) + .filter((item: unknown) => !isEmpty(item)); + return prunedArray.length > 0 ? prunedArray : undefined; + } + return pruneObject(value); + } + return value; + }); pruned = omitBy(pruned, isEmpty); if (isEmpty(pruned)) { return undefined; diff --git a/public/app/features/explore/state/constants.ts b/public/app/features/explore/state/constants.ts index a4c20f15e0b..12d75401fd8 100644 --- a/public/app/features/explore/state/constants.ts +++ b/public/app/features/explore/state/constants.ts @@ -1,6 +1,25 @@ +import { v4 as uuidv4 } from 'uuid'; + import { config } from '@grafana/runtime'; export const DEFAULT_RANGE = { from: `now-${config.exploreDefaultTimeOffset}`, to: 'now', }; + +export const randomId = () => uuidv4().slice(0, 12); + +export const DEFAULT_TAG_FILTERS = { + id: randomId(), + operator: '=', +}; + +export const DEFAULT_SPAN_FILTERS = { + spanNameOperator: '=', + serviceNameOperator: '=', + fromOperator: '>', + toOperator: '<', + tags: [DEFAULT_TAG_FILTERS], + matchesOnly: false, + criticalPathOnly: false, +}; diff --git a/public/app/plugins/panel/traces/TagsEditor.tsx b/public/app/plugins/panel/traces/TagsEditor.tsx index 7e9c8a6fd1d..8c6bdc2bcf6 100644 --- a/public/app/plugins/panel/traces/TagsEditor.tsx +++ b/public/app/plugins/panel/traces/TagsEditor.tsx @@ -1,12 +1,12 @@ import { useEffect, useMemo, useState } from 'react'; -import { SelectableValue, StandardEditorProps } from '@grafana/data'; +import { SelectableValue, StandardEditorProps, TraceSearchProps } from '@grafana/data'; import { SpanFiltersTags } from '../../../features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags'; -import { defaultTagFilter, SearchProps } from '../../../features/explore/TraceView/useSearch'; import { transformDataFrames } from '../../../features/explore/TraceView/utils/transform'; +import { DEFAULT_TAG_FILTERS } from '../../../features/explore/state/constants'; -type Props = StandardEditorProps; +type Props = StandardEditorProps; export const TagsEditor = ({ value, onChange, context }: Props) => { const trace = useMemo(() => transformDataFrames(context.data[0]), [context.data]); @@ -15,7 +15,7 @@ export const TagsEditor = ({ value, onChange, context }: Props) => { useEffect(() => { if (!value.tags) { - onChange({ ...value, tags: [defaultTagFilter] }); + onChange({ ...value, tags: [DEFAULT_TAG_FILTERS] }); } }, [onChange, value]); diff --git a/public/app/plugins/panel/traces/TracesPanel.tsx b/public/app/plugins/panel/traces/TracesPanel.tsx index 7a9c9394827..3b4a983ddbc 100644 --- a/public/app/plugins/panel/traces/TracesPanel.tsx +++ b/public/app/plugins/panel/traces/TracesPanel.tsx @@ -2,14 +2,14 @@ import { css } from '@emotion/css'; import { useMemo, createRef } from 'react'; import { useAsync } from 'react-use'; -import { Field, LinkModel, PanelProps } from '@grafana/data'; +import { TraceSearchProps, Field, LinkModel, PanelProps } from '@grafana/data'; import { Trans } from '@grafana/i18n'; import { getDataSourceSrv } from '@grafana/runtime'; import { TraceView } from 'app/features/explore/TraceView/TraceView'; import { SpanLinkFunc } from 'app/features/explore/TraceView/components/types/links'; import { transformDataFrames } from 'app/features/explore/TraceView/utils/transform'; -import { replaceSearchVariables, SearchProps } from '../../../features/explore/TraceView/useSearch'; +import { replaceSearchVariables } from '../../../features/explore/TraceView/useSearch'; const styles = { wrapper: css({ @@ -22,7 +22,7 @@ export interface TracesPanelOptions { createSpanLink?: SpanLinkFunc; focusedSpanId?: string; createFocusSpanLink?: (traceId: string, spanId: string) => LinkModel; - spanFilters?: SearchProps; + spanFilters?: TraceSearchProps; } export const TracesPanel = ({ data, options, replaceVariables }: PanelProps) => {