From 090b8d61e2e3a324d41ed2e0b5b757892ef82ac2 Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Wed, 19 Jul 2023 14:31:58 +0100 Subject: [PATCH] Tracing: Remove newTraceViewHeader feature toggle (#71818) * Remove TracePageHeader, uiFind, setTrace and many spanFindMatches * Removed TracePageSearchBar * Update useSearch * Update filterSpans * Update docs * Updated tests * Add trace to tests * Remove feature toggle * Renames in useSearch * Renames in filter-spans * Cleanup fixes * Rename TracePageHeader * Rename TracePageSearchBar * Update test * Update style for long urls * Style and check --- docs/sources/datasources/jaeger/_index.md | 5 - docs/sources/datasources/tempo/_index.md | 5 - docs/sources/datasources/zipkin/_index.md | 5 - docs/sources/explore/trace-integration.md | 5 - .../feature-toggles/index.md | 1 - .../src/types/featureToggles.gen.ts | 1 - pkg/services/featuremgmt/registry.go | 7 - pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 - .../explore/TraceView/TraceView.test.tsx | 2 - .../features/explore/TraceView/TraceView.tsx | 92 ++-- .../TraceView/TraceViewContainer.test.tsx | 50 --- .../explore/TraceView/TraceViewContainer.tsx | 34 +- .../NewTracePageHeader.test.tsx | 65 --- .../TracePageHeader/NewTracePageHeader.tsx | 203 --------- .../SearchBar/NewTracePageSearchBar.test.tsx | 59 --- .../SearchBar/NewTracePageSearchBar.tsx | 159 ------- .../SearchBar/NextPrevResult.tsx | 6 +- .../SearchBar/TracePageSearchBar.test.tsx | 84 ++-- .../SearchBar/TracePageSearchBar.tsx | 268 +++++------- .../SpanFilters/SpanFilters.tsx | 4 +- .../TracePageHeader/TracePageHeader.test.tsx | 117 ++--- .../TracePageHeader/TracePageHeader.tsx | 325 ++++++++------ .../components/TracePageHeader/index.tsx | 3 +- .../VirtualizedTraceView.test.tsx | 9 - .../VirtualizedTraceView.tsx | 29 +- .../TraceTimelineViewer/index.test.tsx | 1 - .../components/TraceTimelineViewer/index.tsx | 7 +- .../explore/TraceView/components/index.ts | 5 +- .../components/utils/filter-spans.test.ts | 401 +++++------------- .../components/utils/filter-spans.tsx | 60 +-- .../explore/TraceView/useSearch.test.ts | 23 +- .../features/explore/TraceView/useSearch.ts | 20 +- .../app/plugins/panel/traces/TracesPanel.tsx | 27 +- 34 files changed, 565 insertions(+), 1522 deletions(-) delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.test.tsx delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NewTracePageSearchBar.test.tsx delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NewTracePageSearchBar.tsx diff --git a/docs/sources/datasources/jaeger/_index.md b/docs/sources/datasources/jaeger/_index.md index a1d6c8737be..2c81a31c062 100644 --- a/docs/sources/datasources/jaeger/_index.md +++ b/docs/sources/datasources/jaeger/_index.md @@ -304,11 +304,6 @@ If the file has multiple traces, Grafana visualizes its first trace. ## Span Filters -{{% admonition type="note" %}} -This feature is behind the `newTraceViewHeader` [feature toggle]({{< relref "../../setup-grafana/configure-grafana#feature_toggles" >}}). -If you use Grafana Cloud, open a [support ticket in the Cloud Portal](/profile/org#support) to access this feature. -{{% /admonition %}} - ![Screenshot of span filtering](/media/docs/tempo/screenshot-grafana-tempo-span-filters.png) Using span filters, you can filter your spans in the trace timeline viewer. The more filters you add, the more specific are the filtered spans. diff --git a/docs/sources/datasources/tempo/_index.md b/docs/sources/datasources/tempo/_index.md index 858b6f3d44f..5c1c9811943 100644 --- a/docs/sources/datasources/tempo/_index.md +++ b/docs/sources/datasources/tempo/_index.md @@ -370,11 +370,6 @@ To open a query in Tempo with the span name of that row automatically set in the ## Span Filters -{{% admonition type="note" %}} -This feature is behind the `newTraceViewHeader` [feature toggle]({{< relref "../../setup-grafana/configure-grafana#feature_toggles" >}}). -If you use Grafana Cloud, open a [support ticket in the Cloud Portal](/profile/org#support) to access this feature. -{{% /admonition %}} - ![Screenshot of span filtering](/media/docs/tempo/screenshot-grafana-tempo-span-filters.png) Using span filters, you can filter your spans in the trace timeline viewer. The more filters you add, the more specific are the filtered spans. diff --git a/docs/sources/datasources/zipkin/_index.md b/docs/sources/datasources/zipkin/_index.md index 120ddbee22e..7ab841d567c 100644 --- a/docs/sources/datasources/zipkin/_index.md +++ b/docs/sources/datasources/zipkin/_index.md @@ -273,11 +273,6 @@ If the file has multiple traces, Grafana visualizes its first trace. ## Span Filters -{{% admonition type="note" %}} -This feature is behind the `newTraceViewHeader` [feature toggle]({{< relref "../../setup-grafana/configure-grafana#feature_toggles" >}}). -If you use Grafana Cloud, open a [support ticket in the Cloud Portal](/profile/org#support) to access this feature. -{{% /admonition %}} - ![Screenshot of span filtering](/media/docs/tempo/screenshot-grafana-tempo-span-filters.png) Using span filters, you can filter your spans in the trace timeline viewer. The more filters you add, the more specific are the filtered spans. diff --git a/docs/sources/explore/trace-integration.md b/docs/sources/explore/trace-integration.md index 6a3afe24676..0e4fe66ce16 100644 --- a/docs/sources/explore/trace-integration.md +++ b/docs/sources/explore/trace-integration.md @@ -59,11 +59,6 @@ Shows condensed view or the trace timeline. Drag your mouse over the minimap to ### Span Filters -{{% admonition type="note" %}} -This feature is behind the `newTraceViewHeader` [feature toggle]({{< relref "../setup-grafana/configure-grafana/feature-toggles" >}}). -If you use Grafana Cloud, open a [support ticket in the Cloud Portal](/profile/org#support) to access this feature. -{{% /admonition %}} - ![Screenshot of span filtering](/media/docs/tempo/screenshot-grafana-tempo-span-filters.png) Using span filters, you can filter your spans in the trace timeline viewer. The more filters you add, the more specific are the filtered spans. diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 265dc38e2c9..b30d3b8826c 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -78,7 +78,6 @@ Experimental features might be changed or removed without prior notice. | `queryOverLive` | Use Grafana Live WebSocket to execute backend queries | | `lokiExperimentalStreaming` | Support new streaming approach for loki (prototype, needs special loki build) | | `storage` | Configurable storage for dashboards, datasources, and resources | -| `newTraceViewHeader` | Shows the new trace view header | | `datasourceQueryMultiStatus` | Introduce HTTP 207 Multi Status for api/ds/query | | `traceToMetrics` | Enable trace to metrics links | | `prometheusWideSeries` | Enable wide series responses in the Prometheus datasource | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index c7c3078457e..6b45a31235e 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -29,7 +29,6 @@ export interface FeatureToggles { featureHighlights?: boolean; migrationLocking?: boolean; storage?: boolean; - newTraceViewHeader?: boolean; correlations?: boolean; datasourceQueryMultiStatus?: boolean; traceToMetrics?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 4b55cc2c4fb..3ba9c130c30 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -79,13 +79,6 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaAppPlatformSquad, }, - { - Name: "newTraceViewHeader", - Description: "Shows the new trace view header", - Stage: FeatureStageExperimental, - FrontendOnly: true, - Owner: grafanaObservabilityTracesAndProfilingSquad, - }, { Name: "correlations", Description: "Correlations page", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index b52f297bfaf..35378d04333 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -10,7 +10,6 @@ lokiExperimentalStreaming,experimental,@grafana/observability-logs,false,false,f featureHighlights,GA,@grafana/grafana-as-code,false,false,false,false migrationLocking,preview,@grafana/backend-platform,false,false,false,false storage,experimental,@grafana/grafana-app-platform-squad,false,false,false,false -newTraceViewHeader,experimental,@grafana/observability-traces-and-profiling,false,false,false,true correlations,preview,@grafana/explore-squad,false,false,false,false datasourceQueryMultiStatus,experimental,@grafana/plugins-platform-backend,false,false,false,false traceToMetrics,experimental,@grafana/observability-traces-and-profiling,false,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 71e7a44cfaf..116d4fdc321 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -51,10 +51,6 @@ const ( // Configurable storage for dashboards, datasources, and resources FlagStorage = "storage" - // FlagNewTraceViewHeader - // Shows the new trace view header - FlagNewTraceViewHeader = "newTraceViewHeader" - // FlagCorrelations // Correlations page FlagCorrelations = "correlations" diff --git a/public/app/features/explore/TraceView/TraceView.test.tsx b/public/app/features/explore/TraceView/TraceView.test.tsx index 191f51fdf5e..fd08ed9595f 100644 --- a/public/app/features/explore/TraceView/TraceView.test.tsx +++ b/public/app/features/explore/TraceView/TraceView.test.tsx @@ -29,8 +29,6 @@ function getTraceView(frames: DataFrame[]) { dataFrames={frames} splitOpenFn={() => {}} traceProp={transformDataFrames(frames[0])!} - search="" - focusedSpanIdForSearch="" queryResponse={mockPanelData} datasource={undefined} topOfViewRef={topOfViewRef} diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index 8a4ba0d38ba..fdba79cf86a 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -15,7 +15,7 @@ import { PanelData, SplitOpen, } from '@grafana/data'; -import { config, getTemplateSrv } from '@grafana/runtime'; +import { getTemplateSrv } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; import { getTraceToLogsOptions, TraceToLogsData } from 'app/core/components/TraceToLogs/TraceToLogsSettings'; @@ -27,21 +27,14 @@ import { useDispatch, useSelector } from 'app/types'; import { changePanelState } from '../state/explorePane'; -import { - SpanBarOptionsData, - Trace, - TracePageHeader, - NewTracePageHeader, - TraceTimelineViewer, - TTraceTimeline, -} from './components'; +import { SpanBarOptionsData, Trace, TracePageHeader, TraceTimelineViewer, TTraceTimeline } from './components'; import SpanGraph from './components/TracePageHeader/SpanGraph'; import { TopOfViewRefType } from './components/TraceTimelineViewer/VirtualizedTraceView'; import { createSpanLinkFactory } from './createSpanLink'; import { useChildrenState } from './useChildrenState'; import { useDetailState } from './useDetailState'; import { useHoverIndentGuide } from './useHoverIndentGuide'; -import { useSearchNewTraceViewHeader } from './useSearch'; +import { useSearch } from './useSearch'; import { useViewRange } from './useViewRange'; const getStyles = (theme: GrafanaTheme2) => ({ @@ -66,9 +59,6 @@ type Props = { scrollElement?: Element; scrollElementClass?: string; traceProp: Trace; - spanFindMatches?: Set; - search: string; - focusedSpanIdForSearch: string; queryResponse: PanelData; datasource: DataSourceApi | undefined; topOfViewRef: RefObject; @@ -76,7 +66,7 @@ type Props = { }; export function TraceView(props: Props) { - const { spanFindMatches, traceProp, datasource, topOfViewRef, topOfViewRefType, exploreId } = props; + const { traceProp, datasource, topOfViewRef, topOfViewRefType, exploreId } = props; const { detailStates, @@ -94,13 +84,11 @@ export function TraceView(props: Props) { const { removeHoverIndentGuideId, addHoverIndentGuideId, hoverIndentGuideIds } = useHoverIndentGuide(); const { viewRange, updateViewRangeTime, updateNextViewRangeTime } = useViewRange(); const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState(); - const { newTraceViewHeaderSearch, setNewTraceViewHeaderSearch, spanFilterMatches } = useSearchNewTraceViewHeader( - traceProp?.spans - ); - const [newTraceViewHeaderFocusedSpanIdForSearch, setNewTraceViewHeaderFocusedSpanIdForSearch] = useState(''); + const { search, setSearch, spanFilterMatches } = useSearch(traceProp?.spans); + const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState(''); const [showSpanFilters, setShowSpanFilters] = useToggle(false); const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false); - const [headerHeight, setHeaderHeight] = useState(0); + const [headerHeight, setHeaderHeight] = useState(100); const styles = useStyles2(getStyles); @@ -154,43 +142,31 @@ export function TraceView(props: Props) { <> {props.dataFrames?.length && traceProp ? ( <> - {config.featureToggles.newTraceViewHeader ? ( - <> - - - - ) : ( - - )} + + []} - uiFind={props.search} createSpanLink={createSpanLink} scrollElement={scrollElement} focusedSpanId={focusedSpanId} - focusedSpanIdForSearch={ - config.featureToggles.newTraceViewHeader - ? newTraceViewHeaderFocusedSpanIdForSearch - : props.focusedSpanIdForSearch! - } + focusedSpanIdForSearch={focusedSpanIdForSearch} showSpanFilterMatchesOnly={showSpanFilterMatchesOnly} createFocusSpanLink={createFocusSpanLink} topOfViewRef={topOfViewRef} diff --git a/public/app/features/explore/TraceView/TraceViewContainer.test.tsx b/public/app/features/explore/TraceView/TraceViewContainer.test.tsx index 696d4325ec7..da155cec531 100644 --- a/public/app/features/explore/TraceView/TraceViewContainer.test.tsx +++ b/public/app/features/explore/TraceView/TraceViewContainer.test.tsx @@ -4,7 +4,6 @@ import React, { createRef } from 'react'; import { Provider } from 'react-redux'; import { getDefaultTimeRange, LoadingState } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { configureStore } from '../../../store/configureStore'; @@ -86,55 +85,7 @@ describe('TraceViewContainer', () => { expect(screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' }).length).toBe(3); }); - it('searches for spans', async () => { - renderTraceViewContainer(); - await user.type(screen.getByPlaceholderText('Find...'), '1ed38015486087ca'); - expect( - screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' })[0].parentElement!.className - ).toContain('rowMatchingFilter'); - }); - it('can select next/prev results', async () => { - renderTraceViewContainer(); - await user.type(screen.getByPlaceholderText('Find...'), 'logproto'); - const nextResultButton = screen.getByRole('button', { name: 'Next results button' }); - const prevResultButton = screen.getByRole('button', { name: 'Prev results button' }); - const suffix = screen.getByLabelText('Search bar suffix'); - - await user.click(nextResultButton); - expect(suffix.textContent).toBe('1 of 2'); - expect( - screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' })[1].parentElement!.className - ).toContain('rowFocused'); - await user.click(nextResultButton); - expect(suffix.textContent).toBe('2 of 2'); - expect( - screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' })[2].parentElement!.className - ).toContain('rowFocused'); - await user.click(nextResultButton); - expect(suffix.textContent).toBe('1 of 2'); - expect( - screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' })[1].parentElement!.className - ).toContain('rowFocused'); - await user.click(prevResultButton); - expect(suffix.textContent).toBe('2 of 2'); - expect( - screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' })[2].parentElement!.className - ).toContain('rowFocused'); - await user.click(prevResultButton); - expect(suffix.textContent).toBe('1 of 2'); - expect( - screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' })[1].parentElement!.className - ).toContain('rowFocused'); - await user.click(prevResultButton); - expect(suffix.textContent).toBe('2 of 2'); - expect( - screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' })[2].parentElement!.className - ).toContain('rowFocused'); - }); - - it('can select next/prev results', async () => { - config.featureToggles.newTraceViewHeader = true; renderTraceViewContainer(); const spanFiltersButton = screen.getByRole('button', { name: 'Span Filters 3 spans Prev Next' }); await user.click(spanFiltersButton); @@ -184,7 +135,6 @@ describe('TraceViewContainer', () => { }); it('show matches only works as expected', async () => { - config.featureToggles.newTraceViewHeader = true; renderTraceViewContainer(); const spanFiltersButton = screen.getByRole('button', { name: 'Span Filters 3 spans Prev Next' }); await user.click(spanFiltersButton); diff --git a/public/app/features/explore/TraceView/TraceViewContainer.tsx b/public/app/features/explore/TraceView/TraceViewContainer.tsx index b7b17f9d550..2f37c70d31b 100644 --- a/public/app/features/explore/TraceView/TraceViewContainer.tsx +++ b/public/app/features/explore/TraceView/TraceViewContainer.tsx @@ -1,14 +1,11 @@ -import React, { RefObject, useMemo, useState } from 'react'; +import React, { RefObject, useMemo } from 'react'; -import { DataFrame, PanelData, SplitOpen } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { DataFrame, SplitOpen, PanelData } from '@grafana/data'; import { PanelChrome } from '@grafana/ui/src/components/PanelChrome/PanelChrome'; import { StoreState, useSelector } from 'app/types'; import { TraceView } from './TraceView'; -import TracePageSearchBar from './components/TracePageHeader/SearchBar/TracePageSearchBar'; import { TopOfViewRefType } from './components/TraceTimelineViewer/VirtualizedTraceView'; -import { useSearch } from './useSearch'; import { transformDataFrames } from './utils/transform'; interface Props { @@ -25,47 +22,22 @@ export function TraceViewContainer(props: Props) { const frame = props.dataFrames[0]; const { dataFrames, splitOpenFn, exploreId, scrollElement, topOfViewRef, queryResponse } = props; const traceProp = useMemo(() => transformDataFrames(frame), [frame]); - const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans); - const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState(''); - const [searchBarSuffix, setSearchBarSuffix] = useState(''); const datasource = useSelector( (state: StoreState) => state.explore.panes[props.exploreId]?.datasourceInstance ?? undefined ); - const datasourceType = datasource ? datasource?.type : 'unknown'; if (!traceProp) { return null; } return ( - - ) - } - > + { - const defaultProps = { - trace, - timeZone: '', - search: defaultFilters, - setSearch: jest.fn(), - showSpanFilters: true, - setShowSpanFilters: jest.fn(), - showSpanFilterMatchesOnly: false, - setShowSpanFilterMatchesOnly: jest.fn(), - spanFilterMatches: undefined, - setFocusedSpanIdForSearch: jest.fn(), - datasourceType: 'tempo', - setHeaderHeight: jest.fn(), - data: new MutableDataFrame(), - }; - - return render(); -}; - -describe('NewTracePageHeader test', () => { - it('should render the new trace header', () => { - config.featureToggles.newTraceViewHeader = true; - setup(); - - const header = document.querySelector('header'); - const method = getByText(header!, 'POST'); - const status = getByText(header!, '200'); - const url = getByText(header!, '/v2/gamma/792edh2w897y2huehd2h89'); - const duration = getByText(header!, '2.36s'); - const timestampPart1 = getByText(header!, '2023-02-05 08:50'); - const timestampPart2 = getByText(header!, ':56.289'); - expect(method).toBeInTheDocument(); - expect(status).toBeInTheDocument(); - expect(url).toBeInTheDocument(); - expect(duration).toBeInTheDocument(); - expect(timestampPart1).toBeInTheDocument(); - expect(timestampPart2).toBeInTheDocument(); - }); -}); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx deleted file mode 100644 index ea591d13016..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageHeader.tsx +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { css } from '@emotion/css'; -import cx from 'classnames'; -import React, { memo, useEffect, useMemo } from 'react'; - -import { CoreApp, DataFrame, GrafanaTheme2 } from '@grafana/data'; -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'; -import { getHeaderTags, getTraceName } from '../model/trace-viewer'; -import { Trace } from '../types'; -import { formatDuration } from '../utils/date'; - -import TracePageActions from './Actions/TracePageActions'; -import { SpanFilters } from './SpanFilters/SpanFilters'; -import { timestamp, getStyles } from './TracePageHeader'; - -export type TracePageHeaderProps = { - trace: Trace | null; - data: DataFrame; - app?: CoreApp; - timeZone: TimeZone; - search: SearchProps; - setSearch: React.Dispatch>; - showSpanFilters: boolean; - setShowSpanFilters: (isOpen: boolean) => void; - showSpanFilterMatchesOnly: boolean; - setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void; - setFocusedSpanIdForSearch: React.Dispatch>; - spanFilterMatches: Set | undefined; - datasourceType: string; - setHeaderHeight: (height: number) => void; -}; - -export const NewTracePageHeader = memo((props: TracePageHeaderProps) => { - const { - trace, - data, - app, - timeZone, - search, - setSearch, - showSpanFilters, - setShowSpanFilters, - showSpanFilterMatchesOnly, - setShowSpanFilterMatchesOnly, - setFocusedSpanIdForSearch, - spanFilterMatches, - datasourceType, - setHeaderHeight, - } = props; - const styles = { ...useStyles2(getStyles), ...useStyles2(getNewStyles) }; - - useEffect(() => { - setHeaderHeight(document.querySelector('.' + styles.header)?.scrollHeight ?? 0); - }, [setHeaderHeight, showSpanFilters, styles.header]); - - const links = useMemo(() => { - if (!trace) { - return []; - } - return getTraceLinks(trace); - }, [trace]); - - if (!trace) { - return null; - } - - const title = ( -

- - {formatDuration(trace.duration)} -

- ); - - const { method, status, url } = getHeaderTags(trace.spans); - let statusColor: BadgeColor = 'green'; - if (status && status.length > 0) { - if (status[0].value.toString().charAt(0) === '4') { - statusColor = 'orange'; - } else if (status[0].value.toString().charAt(0) === '5') { - statusColor = 'red'; - } - } - - return ( -
-
- {links && links.length > 0 && } - {title} - -
- -
- {timestamp(trace, timeZone, styles)} - - {method && method.length > 0 && ( - - - - - - )} - {status && status.length > 0 && ( - - - - - - )} - {url && url.length > 0 && ( - - {url[0].value} - - )} - -
- - -
- ); -}); - -NewTracePageHeader.displayName = 'NewTracePageHeader'; - -const getNewStyles = (theme: GrafanaTheme2) => { - return { - header: css` - label: TracePageHeader; - background-color: ${theme.colors.background.primary}; - position: sticky; - top: 0; - z-index: 5; - `, - titleRow: css` - align-items: flex-start; - display: flex; - padding: 0 8px; - `, - title: css` - color: inherit; - flex: 1; - font-size: 1.7em; - line-height: 1em; - `, - subtitle: css` - flex: 1; - line-height: 1em; - margin: -0.5em 0.5em 0.75em 0.5em; - `, - tag: css` - margin: 0 0.5em 0 0; - `, - duration: css` - color: #aaa; - margin: 0 0.75em; - `, - timestamp: css` - vertical-align: middle; - `, - tagMeta: css` - margin: 0 0.75em; - vertical-align: text-top; - `, - url: css` - margin: -2.5px 0.3em; - height: 15px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - max-width: 30%; - display: inline-block; - `, - }; -}; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NewTracePageSearchBar.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NewTracePageSearchBar.test.tsx deleted file mode 100644 index f0ff1a733a9..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NewTracePageSearchBar.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { defaultFilters } from '../../../useSearch'; -import { trace } from '../TracePageHeader.test'; - -import NewTracePageSearchBar from './NewTracePageSearchBar'; - -describe('', () => { - const NewTracePageSearchBarWithProps = (props: { matches: string[] | undefined }) => { - const searchBarProps = { - trace: trace, - search: defaultFilters, - spanFilterMatches: props.matches ? new Set(props.matches) : undefined, - showSpanFilterMatchesOnly: false, - setShowSpanFilterMatchesOnly: jest.fn(), - setFocusedSpanIdForSearch: jest.fn(), - focusedSpanIndexForSearch: -1, - setFocusedSpanIndexForSearch: jest.fn(), - datasourceType: '', - clear: jest.fn(), - totalSpans: 100, - showSpanFilters: true, - }; - - return ; - }; - - it('should render', () => { - expect(() => render()).not.toThrow(); - }); - - it('renders clear filter button', () => { - render(); - const clearFiltersButton = screen.getByRole('button', { name: 'Clear filters button' }); - expect(clearFiltersButton).toBeInTheDocument(); - expect((clearFiltersButton as HTMLButtonElement)['disabled']).toBe(true); - }); - - it('renders show span filter matches only switch', async () => { - render(); - const matchesSwitch = screen.getByRole('checkbox', { name: 'Show matches only switch' }); - expect(matchesSwitch).toBeInTheDocument(); - }); -}); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NewTracePageSearchBar.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NewTracePageSearchBar.tsx deleted file mode 100644 index ad36c753ddd..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NewTracePageSearchBar.tsx +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2018 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { css } from '@emotion/css'; -import React, { memo, Dispatch, SetStateAction, useMemo } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Button, Switch, useStyles2 } from '@grafana/ui'; -import { getButtonStyles } from '@grafana/ui/src/components/Button'; - -import { SearchProps } from '../../../useSearch'; -import { Trace } from '../../types'; -import { convertTimeFilter } from '../../utils/filter-spans'; - -import NextPrevResult from './NextPrevResult'; - -export type TracePageSearchBarProps = { - trace: Trace; - search: SearchProps; - spanFilterMatches: Set | undefined; - showSpanFilterMatchesOnly: boolean; - setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void; - focusedSpanIndexForSearch: number; - setFocusedSpanIndexForSearch: Dispatch>; - setFocusedSpanIdForSearch: Dispatch>; - datasourceType: string; - clear: () => void; - showSpanFilters: boolean; -}; - -export default memo(function NewTracePageSearchBar(props: TracePageSearchBarProps) { - const { - trace, - search, - spanFilterMatches, - showSpanFilterMatchesOnly, - setShowSpanFilterMatchesOnly, - focusedSpanIndexForSearch, - setFocusedSpanIndexForSearch, - setFocusedSpanIdForSearch, - datasourceType, - clear, - showSpanFilters, - } = props; - const styles = useStyles2(getStyles); - - const clearEnabled = useMemo(() => { - return ( - (search.serviceName && search.serviceName !== '') || - (search.spanName && search.spanName !== '') || - convertTimeFilter(search.from || '') || - convertTimeFilter(search.to || '') || - search.tags.length > 1 || - search.tags.some((tag) => { - return tag.key; - }) - ); - }, [search.serviceName, search.spanName, search.from, search.to, search.tags]); - - return ( -
-
- <> -
- -
- setShowSpanFilterMatchesOnly(value.currentTarget.checked ?? false)} - label="Show matches only switch" - /> - -
-
-
- -
- -
-
- ); -}); - -export const getStyles = (theme: GrafanaTheme2) => { - const buttonStyles = getButtonStyles({ theme, variant: 'secondary', size: 'md', iconOnly: false, fill: 'outline' }); - - return { - button: css(buttonStyles.button), - buttonDisabled: css(buttonStyles.disabled, { pointerEvents: 'none', cursor: 'not-allowed' }), - container: css` - display: inline; - `, - controls: css` - display: flex; - justify-content: flex-end; - margin: 5px 0 0 0; - `, - clearButton: css` - order: 1; - `, - matchesOnly: css` - display: inline-flex; - margin: 0 0 0 10px; - vertical-align: middle; - align-items: center; - - span { - cursor: pointer; - margin: 0 0 0 5px; - } - `, - clearMatchesButton: css` - color: ${theme.colors.text.primary}; - &:hover { - background: inherit; - color: inherit; - } - `, - nextPrevResult: css` - margin-left: auto; - order: 2; - `, - }; -}; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx index 98c725b3918..d4abc58acdc 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/NextPrevResult.tsx @@ -145,8 +145,10 @@ export default memo(function NextPrevResult(props: NextPrevResultProps) { if (spanFilterMatches) { spanFilterMatches.forEach((spanID) => { - matchedServices.push(trace.processes[spanID].serviceName); - matchedDepth.push(trace.spans.find((span) => span.spanID === spanID)?.depth || 0); + if (trace.processes[spanID]) { + matchedServices.push(trace.processes[spanID].serviceName); + matchedDepth.push(trace.spans.find((span) => span.spanID === spanID)?.depth || 0); + } }); if (spanFilterMatches.size === 0) { 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 e56b03f0b93..e343e54db44 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 @@ -15,65 +15,45 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import { createTheme } from '@grafana/data'; +import { defaultFilters } from '../../../useSearch'; +import { trace } from '../TracePageHeader.test'; -import TracePageSearchBar, { getStyles, TracePageSearchBarProps } from './TracePageSearchBar'; - -const defaultProps = { - forwardedRef: React.createRef(), - navigable: true, - searchBarSuffix: 'suffix', - searchValue: 'value', -}; +import TracePageSearchBar from './TracePageSearchBar'; describe('', () => { - describe('truthy textFilter', () => { - it('renders SearchBarInput with correct props', () => { - render(); - expect((screen.getByPlaceholderText('Find...') as HTMLInputElement)['value']).toEqual('value'); - const suffix = screen.getByLabelText('Search bar suffix'); - const theme = createTheme(); - expect(suffix['className']).toBe(getStyles(theme).TracePageSearchBarSuffix); - expect(suffix.textContent).toBe('suffix'); - }); + const TracePageSearchBarWithProps = (props: { matches: string[] | undefined }) => { + const searchBarProps = { + trace: trace, + search: defaultFilters, + spanFilterMatches: props.matches ? new Set(props.matches) : undefined, + showSpanFilterMatchesOnly: false, + setShowSpanFilterMatchesOnly: jest.fn(), + setFocusedSpanIdForSearch: jest.fn(), + focusedSpanIndexForSearch: -1, + setFocusedSpanIndexForSearch: jest.fn(), + datasourceType: '', + clear: jest.fn(), + totalSpans: 100, + showSpanFilters: true, + }; - it('renders buttons', () => { - render(); - const nextResButton = screen.queryByRole('button', { name: 'Next results button' }); - const prevResButton = screen.queryByRole('button', { name: 'Prev results button' }); - expect(nextResButton).toBeInTheDocument(); - expect(prevResButton).toBeInTheDocument(); - expect((nextResButton as HTMLButtonElement)['disabled']).toBe(false); - expect((prevResButton as HTMLButtonElement)['disabled']).toBe(false); - }); + return ; + }; - it('only shows navigable buttons when navigable is true', () => { - const props = { - ...defaultProps, - navigable: false, - }; - render(); - expect(screen.queryByRole('button', { name: 'Next results button' })).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: 'Prev results button' })).not.toBeInTheDocument(); - }); + it('should render', () => { + expect(() => render()).not.toThrow(); }); - describe('falsy textFilter', () => { - beforeEach(() => { - const props = { - ...defaultProps, - searchValue: '', - }; - render(); - }); + it('renders clear filter button', () => { + render(); + const clearFiltersButton = screen.getByRole('button', { name: 'Clear filters button' }); + expect(clearFiltersButton).toBeInTheDocument(); + expect((clearFiltersButton as HTMLButtonElement)['disabled']).toBe(true); + }); - it('does not render suffix', () => { - expect(screen.queryByLabelText('Search bar suffix')).not.toBeInTheDocument(); - }); - - it('renders buttons', () => { - expect(screen.getByRole('button', { name: 'Next results button' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Prev results button' })).toBeInTheDocument(); - }); + it('renders show span filter matches only switch', async () => { + render(); + const matchesSwitch = screen.getByRole('checkbox', { name: 'Show matches only switch' }); + expect(matchesSwitch).toBeInTheDocument(); }); }); 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 fb025d54150..1502373efc0 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar.tsx @@ -13,181 +13,147 @@ // limitations under the License. import { css } from '@emotion/css'; -import cx from 'classnames'; -import React, { memo, Dispatch, SetStateAction } from 'react'; +import React, { memo, Dispatch, SetStateAction, useMemo } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { config, reportInteraction } from '@grafana/runtime'; -import { Button, useStyles2 } from '@grafana/ui'; +import { Button, Switch, useStyles2 } from '@grafana/ui'; +import { getButtonStyles } from '@grafana/ui/src/components/Button'; -import SearchBarInput from '../../common/SearchBarInput'; -import { ubFlexAuto, ubJustifyEnd } from '../../uberUtilityStyles'; +import { SearchProps } from '../../../useSearch'; +import { Trace } from '../../types'; +import { convertTimeFilter } from '../../utils/filter-spans'; -// eslint-disable-next-line no-duplicate-imports - -export const getStyles = (theme: GrafanaTheme2) => { - return { - TracePageSearchBar: css` - label: TracePageSearchBar; - float: right; - position: absolute; - top: 0; - right: 0; - padding: 8px; - margin-right: 2px; - `, - TracePageSearchBarBar: css` - label: TracePageSearchBarBar; - max-width: 20rem; - transition: max-width 0.5s; - &:focus-within { - max-width: 100%; - } - `, - TracePageSearchBarSuffix: css` - label: TracePageSearchBarSuffix; - opacity: 0.6; - `, - TracePageSearchBarBtn: css` - label: TracePageSearchBarBtn; - margin-left: 8px; - `, - }; -}; +import NextPrevResult from './NextPrevResult'; export type TracePageSearchBarProps = { - navigable: boolean; - searchValue: string; - setSearch: (value: string) => void; - searchBarSuffix: string; - spanFindMatches: Set | undefined; - focusedSpanIdForSearch: string; - setSearchBarSuffix: Dispatch>; + trace: Trace; + search: SearchProps; + spanFilterMatches: Set | undefined; + showSpanFilterMatchesOnly: boolean; + setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void; + focusedSpanIndexForSearch: number; + setFocusedSpanIndexForSearch: Dispatch>; setFocusedSpanIdForSearch: Dispatch>; datasourceType: string; + clear: () => void; + showSpanFilters: boolean; }; export default memo(function TracePageSearchBar(props: TracePageSearchBarProps) { const { - navigable, - setSearch, - searchValue, - searchBarSuffix, - spanFindMatches, - focusedSpanIdForSearch, - setSearchBarSuffix, + trace, + search, + spanFilterMatches, + showSpanFilterMatchesOnly, + setShowSpanFilterMatchesOnly, + focusedSpanIndexForSearch, + setFocusedSpanIndexForSearch, setFocusedSpanIdForSearch, datasourceType, + clear, + showSpanFilters, } = props; const styles = useStyles2(getStyles); - const suffix = searchValue ? ( - - {searchBarSuffix} - - ) : null; - - const SearchBarInputProps = { - className: cx(styles.TracePageSearchBarBar, ubFlexAuto), - name: 'search', - suffix, - }; - - const setTraceSearch = (value: string) => { - setFocusedSpanIdForSearch(''); - setSearchBarSuffix(''); - setSearch(value); - }; - - const nextResult = () => { - reportInteraction('grafana_traces_trace_view_find_next_prev_clicked', { - datasourceType: datasourceType, - grafana_version: config.buildInfo.version, - direction: 'next', - }); - - const spanMatches = Array.from(spanFindMatches!); - const prevMatchedIndex = spanMatches.indexOf(focusedSpanIdForSearch) - ? spanMatches.indexOf(focusedSpanIdForSearch) - : 0; - - // new query || at end, go to start - if (prevMatchedIndex === -1 || prevMatchedIndex === spanMatches.length - 1) { - setFocusedSpanIdForSearch(spanMatches[0]); - setSearchBarSuffix(getSearchBarSuffix(1)); - return; - } - - // get next - setFocusedSpanIdForSearch(spanMatches[prevMatchedIndex + 1]); - setSearchBarSuffix(getSearchBarSuffix(prevMatchedIndex + 2)); - }; - - const prevResult = () => { - reportInteraction('grafana_traces_trace_view_find_next_prev_clicked', { - datasourceType: datasourceType, - grafana_version: config.buildInfo.version, - direction: 'prev', - }); - - const spanMatches = Array.from(spanFindMatches!); - const prevMatchedIndex = spanMatches.indexOf(focusedSpanIdForSearch) - ? spanMatches.indexOf(focusedSpanIdForSearch) - : 0; - - // new query || at start, go to end - if (prevMatchedIndex === -1 || prevMatchedIndex === 0) { - setFocusedSpanIdForSearch(spanMatches[spanMatches.length - 1]); - setSearchBarSuffix(getSearchBarSuffix(spanMatches.length)); - return; - } - - // get prev - setFocusedSpanIdForSearch(spanMatches[prevMatchedIndex - 1]); - setSearchBarSuffix(getSearchBarSuffix(prevMatchedIndex)); - }; - - const getSearchBarSuffix = (index: number): string => { - if (spanFindMatches?.size && spanFindMatches?.size > 0) { - return index + ' of ' + spanFindMatches?.size; - } - return ''; - }; + const clearEnabled = useMemo(() => { + return ( + (search.serviceName && search.serviceName !== '') || + (search.spanName && search.spanName !== '') || + convertTimeFilter(search.from || '') || + convertTimeFilter(search.to || '') || + search.tags.length > 1 || + search.tags.some((tag) => { + return tag.key; + }) + ); + }, [search.serviceName, search.spanName, search.from, search.to, search.tags]); return ( -
- - +
+
<> - {navigable && ( - <> - +
+ setShowSpanFilterMatchesOnly(value.currentTarget.checked ?? false)} + label="Show matches only switch" /> +
+
+
+ +
- +
); }); + +export const getStyles = (theme: GrafanaTheme2) => { + const buttonStyles = getButtonStyles({ theme, variant: 'secondary', size: 'md', iconOnly: false, fill: 'outline' }); + + return { + button: css(buttonStyles.button), + buttonDisabled: css(buttonStyles.disabled, { pointerEvents: 'none', cursor: 'not-allowed' }), + container: css` + display: inline; + `, + controls: css` + display: flex; + justify-content: flex-end; + margin: 5px 0 0 0; + `, + clearButton: css` + order: 1; + `, + matchesOnly: css` + display: inline-flex; + margin: 0 0 0 10px; + vertical-align: middle; + align-items: center; + + span { + cursor: pointer; + margin: 0 0 0 5px; + } + `, + clearMatchesButton: css` + color: ${theme.colors.text.primary}; + &:hover { + background: inherit; + color: inherit; + } + `, + nextPrevResult: css` + margin-left: auto; + order: 2; + `, + }; +}; 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 add4cd20652..24b47da0757 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx @@ -25,8 +25,8 @@ import { IntervalInput } from 'app/core/components/IntervalInput/IntervalInput'; import { defaultFilters, randomId, SearchProps, Tag } from '../../../useSearch'; import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE, ID } from '../../constants/span'; import { Trace } from '../../types'; -import NewTracePageSearchBar from '../SearchBar/NewTracePageSearchBar'; import NextPrevResult from '../SearchBar/NextPrevResult'; +import TracePageSearchBar from '../SearchBar/TracePageSearchBar'; export type SpanFilterProps = { trace: Trace; @@ -447,7 +447,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => { - { + const defaultProps = { + trace, + timeZone: '', + search: defaultFilters, + setSearch: jest.fn(), + showSpanFilters: true, + setShowSpanFilters: jest.fn(), + showSpanFilterMatchesOnly: false, + setShowSpanFilterMatchesOnly: jest.fn(), + spanFilterMatches: undefined, + setFocusedSpanIdForSearch: jest.fn(), + datasourceType: 'tempo', + setHeaderHeight: jest.fn(), + data: new MutableDataFrame(), + }; + + return render(); +}; + +describe('TracePageHeader test', () => { + it('should render the new trace header', () => { + setup(); + + const header = document.querySelector('header'); + const method = getByText(header!, 'POST'); + const status = getByText(header!, '200'); + const url = getByText(header!, '/v2/gamma/792edh2w897y2huehd2h89'); + const duration = getByText(header!, '2.36s'); + const timestampPart1 = getByText(header!, '2023-02-05 08:50'); + const timestampPart2 = getByText(header!, ':56.289'); + expect(method).toBeInTheDocument(); + expect(status).toBeInTheDocument(); + expect(url).toBeInTheDocument(); + expect(duration).toBeInTheDocument(); + expect(timestampPart1).toBeInTheDocument(); + expect(timestampPart2).toBeInTheDocument(); + }); +}); export const trace = { services: [{ name: 'serviceA', numberOfSpans: 1 }], @@ -144,72 +186,3 @@ export const trace = { startTime: 1675605056289000, endTime: 1675605058644515, }; - -const setup = (propOverrides?: TracePageHeaderProps) => { - const defaultProps = { - trace, - timeZone: '', - viewRange: { time: { current: [10, 20] as [number, number] } }, - updateNextViewRangeTime: () => {}, - updateViewRangeTime: () => {}, - ...propOverrides, - }; - - return render(); -}; - -describe('TracePageHeader test', () => { - it('should render a header ', () => { - setup(); - expect(screen.getByRole('banner')).toBeInTheDocument(); - }); - - it('should render nothing if a trace is not present', () => { - setup({ trace: null } as TracePageHeaderProps); - expect(screen.queryByRole('banner')).not.toBeInTheDocument(); - expect(screen.queryAllByRole('listitem')).toHaveLength(0); - expect(screen.queryByText(/Reset Selection/)).not.toBeInTheDocument(); - }); - - it('should render the trace title', () => { - setup(); - expect( - screen.getByRole('heading', { - name: (content) => content.replace(/ /g, '').startsWith(getTraceName(trace!.spans).replace(/ /g, '')), - }) - ).toBeInTheDocument(); - }); - - it('should render the header items', () => { - setup(); - - const headerItems = screen.queryAllByRole('listitem'); - - expect(headerItems).toHaveLength(5); - // Year-month-day hour-minute-second - expect(headerItems[0].textContent?.match(/Trace Start:\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}/g)).toBeTruthy(); - expect(headerItems[1].textContent?.match(/Duration:[\d|\.][\.|\d|s][\.|\d|s]?[\d]?/)).toBeTruthy(); - expect(headerItems[2].textContent?.match(/Services:\d\d?/g)).toBeTruthy(); - expect(headerItems[3].textContent?.match(/Depth:\d\d?/)).toBeTruthy(); - expect(headerItems[4].textContent?.match(/Total Spans:\d\d?\d?\d?/)).toBeTruthy(); - }); - - it('should render a ', () => { - setup(); - expect(screen.getByText(/Reset Selection/)).toBeInTheDocument(); - }); - - it('shows the summary', () => { - const { rerender } = setup(); - - rerender( - - ); - expect(screen.queryAllByRole('listitem')).toHaveLength(5); - }); -}); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx index 1fa478fd7e4..1935ac0a0ea 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx @@ -14,38 +14,158 @@ import { css } from '@emotion/css'; import cx from 'classnames'; -import { get as _get, maxBy as _maxBy, values as _values } from 'lodash'; -import React from 'react'; +import React, { memo, useEffect, useMemo } from 'react'; -import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data'; -import { useStyles2 } from '@grafana/ui'; +import { CoreApp, DataFrame, dateTimeFormat, GrafanaTheme2 } from '@grafana/data'; +import { TimeZone } from '@grafana/schema'; +import { Badge, BadgeColor, Tooltip, useStyles2 } from '@grafana/ui'; +import { SearchProps } from '../../useSearch'; import ExternalLinks from '../common/ExternalLinks'; -import LabeledList from '../common/LabeledList'; import TraceName from '../common/TraceName'; -import { autoColor, TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../index'; import { getTraceLinks } from '../model/link-patterns'; -import { getTraceName } from '../model/trace-viewer'; +import { getHeaderTags, getTraceName } from '../model/trace-viewer'; import { Trace } from '../types'; -import { uTxMuted } from '../uberUtilityStyles'; import { formatDuration } from '../utils/date'; -import SpanGraph from './SpanGraph'; +import TracePageActions from './Actions/TracePageActions'; +import { SpanFilters } from './SpanFilters/SpanFilters'; -export const getStyles = (theme: GrafanaTheme2) => { +export type TracePageHeaderProps = { + trace: Trace | null; + data: DataFrame; + app?: CoreApp; + timeZone: TimeZone; + search: SearchProps; + setSearch: React.Dispatch>; + showSpanFilters: boolean; + setShowSpanFilters: (isOpen: boolean) => void; + showSpanFilterMatchesOnly: boolean; + setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void; + setFocusedSpanIdForSearch: React.Dispatch>; + spanFilterMatches: Set | undefined; + datasourceType: string; + setHeaderHeight: (height: number) => void; +}; + +export const TracePageHeader = memo((props: TracePageHeaderProps) => { + const { + trace, + data, + app, + timeZone, + search, + setSearch, + showSpanFilters, + setShowSpanFilters, + showSpanFilterMatchesOnly, + setShowSpanFilterMatchesOnly, + setFocusedSpanIdForSearch, + spanFilterMatches, + datasourceType, + setHeaderHeight, + } = props; + const styles = useStyles2(getNewStyles); + + useEffect(() => { + setHeaderHeight(document.querySelector('.' + styles.header)?.scrollHeight ?? 0); + }, [setHeaderHeight, showSpanFilters, styles.header]); + + const links = useMemo(() => { + if (!trace) { + return []; + } + return getTraceLinks(trace); + }, [trace]); + + if (!trace) { + return null; + } + + const timestamp = (trace: Trace, timeZone: TimeZone) => { + // Convert date from micro to milli seconds + const dateStr = dateTimeFormat(trace.startTime / 1000, { timeZone, defaultWithMS: true }); + const match = dateStr.match(/^(.+)(:\d\d\.\d+)$/); + return match ? ( + + {match[1]} + {match[2]} + + ) : ( + dateStr + ); + }; + + const title = ( +

+ + {formatDuration(trace.duration)} +

+ ); + + const { method, status, url } = getHeaderTags(trace.spans); + let statusColor: BadgeColor = 'green'; + if (status && status.length > 0) { + if (status[0].value.toString().charAt(0) === '4') { + statusColor = 'orange'; + } else if (status[0].value.toString().charAt(0) === '5') { + statusColor = 'red'; + } + } + + return ( +
+
+ {links && links.length > 0 && } + {title} + +
+ +
+ {timestamp(trace, timeZone)} + + {method && method.length > 0 && ( + + + + + + )} + {status && status.length > 0 && ( + + + + + + )} + {url && url.length > 0 && ( + + {url[0].value} + + )} + +
+ + +
+ ); +}); + +TracePageHeader.displayName = 'TracePageHeader'; + +const getNewStyles = (theme: GrafanaTheme2) => { return { - theme, - TracePageHeader: css` - label: TracePageHeader; - & > :last-child { - border-bottom: 1px solid ${autoColor(theme, '#ccc')}; - } - `, - TracePageHeaderTitleRow: css` - label: TracePageHeaderTitleRow; - align-items: center; - display: flex; - `, TracePageHeaderBack: css` label: TracePageHeaderBack; align-items: center; @@ -63,21 +183,6 @@ export const getStyles = (theme: GrafanaTheme2) => { border-color: #ccc; } `, - TracePageHeaderTitle: css` - label: TracePageHeaderTitle; - color: inherit; - flex: 1; - font-size: 1.7em; - line-height: 1em; - margin: 0 0 0 0.3em; - padding-bottom: 0.5em; - `, - TracePageHeaderOverviewItems: css` - label: TracePageHeaderOverviewItems; - background-color: ${autoColor(theme, '#eee')}; - border-bottom: 1px solid ${autoColor(theme, '#e4e4e4')}; - padding: 0.25rem 0.5rem !important; - `, TracePageHeaderOverviewItemValueDetail: cx( css` label: TracePageHeaderOverviewItemValueDetail; @@ -91,107 +196,57 @@ export const getStyles = (theme: GrafanaTheme2) => { color: unset; } `, + header: css` + label: TracePageHeader; + background-color: ${theme.colors.background.primary}; + padding: 0.5em 0 0 0; + position: sticky; + top: 0; + z-index: 5; + `, + titleRow: css` + align-items: flex-start; + display: flex; + padding: 0 8px; + `, + title: css` + color: inherit; + flex: 1; + font-size: 1.7em; + line-height: 1em; + `, + subtitle: css` + flex: 1; + line-height: 1em; + margin: -0.5em 0.5em 0.75em 0.5em; + `, + tag: css` + margin: 0 0.5em 0 0; + `, + duration: css` + color: #aaa; + margin: 0 0.75em; + `, + timestamp: css` + vertical-align: middle; + `, + tagMeta: css` + margin: 0 0.75em; + vertical-align: text-top; + `, + url: css` + margin: -2.5px 0.3em; + height: 15px; + overflow: hidden; + word-break: break-all; + line-height: 20px; + `, TracePageHeaderTraceId: css` label: TracePageHeaderTraceId; white-space: nowrap; - `, - titleBorderBottom: css` - border-bottom: 1px solid ${autoColor(theme, '#e8e8e8')}; + text-overflow: ellipsis; + max-width: 30%; + display: inline-block; `, }; }; - -export type TracePageHeaderProps = { - trace: Trace | null; - updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; - updateViewRangeTime: TUpdateViewRangeTimeFunction; - viewRange: ViewRange; - timeZone: TimeZone; -}; - -export const timestamp = (trace: Trace, timeZone: TimeZone, styles: ReturnType) => { - // Convert date from micro to milli seconds - const dateStr = dateTimeFormat(trace.startTime / 1000, { timeZone, defaultWithMS: true }); - const match = dateStr.match(/^(.+)(:\d\d\.\d+)$/); - return match ? ( - - {match[1]} - {match[2]} - - ) : ( - dateStr - ); -}; - -export const HEADER_ITEMS = [ - { - key: 'timestamp', - label: 'Trace Start:', - renderer: timestamp, - }, - { - key: 'duration', - label: 'Duration:', - renderer: (trace: Trace) => formatDuration(trace.duration), - }, - { - key: 'service-count', - label: 'Services:', - renderer: (trace: Trace) => new Set(_values(trace.processes).map((p) => p.serviceName)).size, - }, - { - key: 'depth', - label: 'Depth:', - renderer: (trace: Trace) => _get(_maxBy(trace.spans, 'depth'), 'depth', 0) + 1, - }, - { - key: 'span-count', - label: 'Total Spans:', - renderer: (trace: Trace) => trace.spans.length, - }, -]; - -export default function TracePageHeader(props: TracePageHeaderProps) { - const { trace, updateNextViewRangeTime, updateViewRangeTime, viewRange, timeZone } = props; - - const styles = useStyles2(getStyles); - const links = React.useMemo(() => { - if (!trace) { - return []; - } - return getTraceLinks(trace); - }, [trace]); - - if (!trace) { - return null; - } - - const summaryItems = HEADER_ITEMS.map((item) => { - const { renderer, ...rest } = item; - return { ...rest, value: renderer(trace, timeZone, styles) }; - }); - - const title = ( -

- {' '} - {trace.traceID} -

- ); - - return ( -
-
- {links && links.length > 0 && } - {title} -
- {summaryItems && } - - -
- ); -} diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/index.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/index.tsx index afa360a0a8e..4f828881708 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/index.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/index.tsx @@ -12,5 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -export { default } from './TracePageHeader'; -export { NewTracePageHeader } from './NewTracePageHeader'; +export { TracePageHeader } from './TracePageHeader'; diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.test.tsx index 94019a615a4..2e0010255e7 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.test.tsx @@ -39,7 +39,6 @@ let props = { findMatchesIDs: null, registerAccessors: jest.fn(), setSpanNameColumnWidth: jest.fn(), - setTrace: jest.fn(), spanNameColumnWidth: 0.5, trace, uiFind: 'uiFind', @@ -97,12 +96,4 @@ describe('', () => { }) ).toBeInTheDocument(); }); - - it('sets the trace for global state.traceTimeline', () => { - const traceID = 'some-other-id'; - const _trace = { ...trace, traceID }; - props = { ...props, trace: _trace }; - render(); - expect(jest.mocked(props.setTrace).mock.calls).toEqual([[_trace, props.uiFind]]); - }); }); diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx index 5ae53b75f27..4bc7dca204d 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -42,10 +42,6 @@ import { ViewedBoundsFunctionType, } from './utils'; -type TExtractUiFindFromStateReturn = { - uiFind: string | undefined; -}; - const getStyles = stylesFactory((props: TVirtualizedTraceViewOwnProps) => { const { topOfViewRefType } = props; const position = topOfViewRefType === TopOfViewRefType.Explore ? 'fixed' : 'absolute'; @@ -102,7 +98,6 @@ type TVirtualizedTraceViewOwnProps = { detailTagsToggle: (spanID: string) => void; detailToggle: (spanID: string) => void; setSpanNameColumnWidth: (width: number) => void; - setTrace: (trace: Trace | TNil, uiFind: string | TNil) => void; hoverIndentGuideIds: Set; addHoverIndentGuideId: (spanID: string) => void; removeHoverIndentGuideId: (spanID: string) => void; @@ -119,7 +114,7 @@ type TVirtualizedTraceViewOwnProps = { headerHeight: number; }; -export type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TExtractUiFindFromStateReturn & TTraceTimeline; +export type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TTraceTimeline; // export for tests export const DEFAULT_HEIGHTS = { @@ -206,12 +201,7 @@ const memoizedGetClipping = memoizeOne(getClipping, isEqual); // export from tests export class UnthemedVirtualizedTraceView extends React.Component { listView: ListView | TNil; - - constructor(props: VirtualizedTraceViewProps) { - super(props); - const { setTrace, trace, uiFind } = props; - setTrace(trace, uiFind); - } + hasScrolledToSpan = false; componentDidMount() { this.scrollToSpan(this.props.headerHeight, this.props.focusedSpanId); @@ -229,24 +219,23 @@ export class UnthemedVirtualizedTraceView extends React.Component) { - const { registerAccessors, trace, headerHeight } = prevProps; + const { registerAccessors } = prevProps; const { registerAccessors: nextRegisterAccessors, - setTrace, - trace: nextTrace, - uiFind, + headerHeight, focusedSpanId, focusedSpanIdForSearch, } = this.props; - if (trace !== nextTrace) { - setTrace(nextTrace, uiFind); - } - if (this.listView && registerAccessors !== nextRegisterAccessors) { nextRegisterAccessors(this.getAccessors()); } + if (!this.hasScrolledToSpan) { + this.scrollToSpan(headerHeight, focusedSpanId); + this.hasScrolledToSpan = true; + } + if (focusedSpanId !== prevProps.focusedSpanId) { this.scrollToSpan(headerHeight, focusedSpanId); } diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.test.tsx index bfff1eea628..0a7f4322438 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.test.tsx @@ -51,7 +51,6 @@ describe('', () => { expandOne: jest.fn(), registerAccessors: jest.fn(), collapseOne: jest.fn(), - setTrace: jest.fn(), theme: createTheme(), history: { replace: () => {}, diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx index 031b9f95335..cfcd661f908 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx @@ -31,10 +31,6 @@ import TimelineHeaderRow from './TimelineHeaderRow'; import VirtualizedTraceView, { TopOfViewRefType } from './VirtualizedTraceView'; import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types'; -type TExtractUiFindFromStateReturn = { - uiFind: string | undefined; -}; - const getStyles = stylesFactory((theme: GrafanaTheme2) => { return { TraceTimelineViewer: css` @@ -71,7 +67,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => { }; }); -export type TProps = TExtractUiFindFromStateReturn & { +export type TProps = { registerAccessors: (accessors: Accessors) => void; findMatchesIDs: Set | TNil; traceTimeline: TTraceTimeline; @@ -99,7 +95,6 @@ export type TProps = TExtractUiFindFromStateReturn & { detailProcessToggle: (spanID: string) => void; detailTagsToggle: (spanID: string) => void; detailToggle: (spanID: string) => void; - setTrace: (trace: Trace | TNil, uiFind: string | TNil) => void; addHoverIndentGuideId: (spanID: string) => void; removeHoverIndentGuideId: (spanID: string) => void; linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[]; diff --git a/public/app/features/explore/TraceView/components/index.ts b/public/app/features/explore/TraceView/components/index.ts index 5412992f8d8..51b22738e75 100644 --- a/public/app/features/explore/TraceView/components/index.ts +++ b/public/app/features/explore/TraceView/components/index.ts @@ -1,10 +1,9 @@ export { default as TraceTimelineViewer } from './TraceTimelineViewer'; -export { default as TracePageHeader } from './TracePageHeader'; -export { NewTracePageHeader } from './TracePageHeader'; +export { TracePageHeader } from './TracePageHeader'; export { default as SpanBarSettings } from './settings/SpanBarSettings'; export * from './types'; export * from './TraceTimelineViewer/types'; export { default as DetailState } from './TraceTimelineViewer/SpanDetail/DetailState'; export { default as transformTraceData } from './model/transform-trace-data'; -export { filterSpansNewTraceViewHeader, filterSpans } from './utils/filter-spans'; +export { filterSpans } from './utils/filter-spans'; export * from './Theme'; 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 09075f03d58..c5652e4b43d 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 @@ -15,7 +15,7 @@ import { defaultFilters, defaultTagFilter } from '../../useSearch'; import { TraceSpan } from '../types'; -import { filterSpans, filterSpansNewTraceViewHeader } from './filter-spans'; +import { filterSpans } from './filter-spans'; describe('filterSpans', () => { // span0 contains strings that end in 0 or 1 @@ -122,125 +122,94 @@ describe('filterSpans', () => { const spans = [span0, span2] as TraceSpan[]; it('should return `undefined` if spans is falsy', () => { - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, spanName: 'operationName' }, null)).toBe(undefined); + expect(filterSpans({ ...defaultFilters, spanName: 'operationName' }, null)).toBe(undefined); }); // Service / span name it('should return spans whose serviceName match a filter', () => { - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, serviceName: 'serviceName0' }, spans)).toEqual( + 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(filterSpansNewTraceViewHeader({ ...defaultFilters, serviceName: 'serviceName2' }, spans)).toEqual( - new Set([spanID2]) - ); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, serviceName: 'serviceName2', serviceNameOperator: '!=' }, - spans - ) - ).toEqual(new Set([spanID0])); }); it('should return spans whose operationName match a filter', () => { - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, spanName: 'operationName0' }, spans)).toEqual( + 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( new Set([spanID0]) ); - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, spanName: 'operationName2' }, spans)).toEqual( - new Set([spanID2]) - ); - expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, spanName: 'operationName2', spanNameOperator: '!=' }, spans) - ).toEqual(new Set([spanID0])); }); // Durations it('should return spans whose duration match a filter', () => { - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, from: '2ns' }, spans)).toEqual( + 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( new Set([spanID0, spanID2]) ); - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, from: '2us' }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, from: '2ms' }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, from: '3.05ms' }, spans)).toEqual(new Set([spanID2])); - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, from: '3.05ms', fromOperator: '>=' }, spans)).toEqual( - new Set([spanID0, spanID2]) - ); - expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, from: '3.05ms', fromOperator: '>=', to: '4ms' }, spans) - ).toEqual(new Set([spanID0])); - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, to: '4ms' }, spans)).toEqual(new Set([spanID0])); - expect(filterSpansNewTraceViewHeader({ ...defaultFilters, to: '5ms', toOperator: '<=' }, spans)).toEqual( - new Set([spanID0, spanID2]) + expect(filterSpans({ ...defaultFilters, 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])); }); // Tags it('should return spans whose tags kv.key match a filter', () => { + expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1' }] }, spans)).toEqual( + new Set([spanID0, spanID2]) + ); + expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, spans)).toEqual( + new Set([spanID0]) + ); + expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2' }] }, spans)).toEqual( + new Set([spanID2]) + ); expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1' }] }, spans) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, spans) - ).toEqual(new Set([spanID0])); - expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2' }] }, spans) - ).toEqual(new Set([spanID2])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, 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( + new Set([spanID0, spanID2]) + ); expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind' }] }, spans) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', value: 'kind0' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', value: 'kind0' }] }, spans) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', operator: '!=', value: 'kind0' }] }, spans ) ).toEqual(new Set([spanID2])); + expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status' }] }, spans)).toEqual( + new Set([spanID0, spanID2]) + ); expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status' }] }, spans) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', value: 'unset' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', value: 'unset' }] }, spans) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, 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( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message' }] }, - spans - ) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message', value: 'statusMessage0' }] }, spans ) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message', operator: '!=', value: 'statusMessage0' }], @@ -248,17 +217,17 @@ describe('filterSpans', () => { spans ) ).toEqual(new Set([spanID2])); + expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name' }] }, spans)).toEqual( + new Set([spanID0, spanID2]) + ); expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name' }] }, spans) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name', value: 'libraryName' }] }, spans ) ).toEqual(new Set([spanID0, spanID2])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name', operator: '!=', value: 'libraryName' }], @@ -266,20 +235,17 @@ describe('filterSpans', () => { spans ) ).toEqual(new Set([])); + expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version' }] }, spans)).toEqual( + new Set([spanID0, spanID2]) + ); expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version' }] }, - spans - ) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version', value: 'libraryVersion0' }] }, spans ) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version', operator: '!=', value: 'libraryVersion0' }], @@ -287,17 +253,17 @@ describe('filterSpans', () => { spans ) ).toEqual(new Set([spanID2])); + expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state' }] }, spans)).toEqual( + new Set([spanID0, spanID2]) + ); expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state' }] }, spans) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state', value: 'traceState0' }] }, spans ) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state', operator: '!=', value: 'traceState0' }], @@ -305,17 +271,14 @@ describe('filterSpans', () => { spans ) ).toEqual(new Set([spanID2])); + expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id' }] }, spans)).toEqual( + new Set([spanID0, spanID2]) + ); expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id' }] }, spans) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', value: 'span-id-0' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', value: 'span-id-0' }] }, spans) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', operator: '!=', value: 'span-id-0' }] }, spans ) @@ -323,54 +286,39 @@ describe('filterSpans', () => { }); 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( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey1' }] }, - spans - ) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey0' }] }, - spans - ) - ).toEqual(new Set([spanID0])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey2' }] }, - spans - ) - ).toEqual(new Set([spanID2])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey2', operator: '!=' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, 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( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey1' }] }, spans) - ).toEqual(new Set([spanID0, spanID2])); - expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey0' }] }, spans) - ).toEqual(new Set([spanID0])); - expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey2' }] }, spans) - ).toEqual(new Set([spanID2])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey2', operator: '!=' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey2', operator: '!=' }] }, spans) ).toEqual(new Set([spanID0])); }); it('should return no spans when logs is null', () => { const nullSpan = { ...span0, logs: null }; expect( - filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey1' }] }, [ + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey1' }] }, [ nullSpan, ] as unknown as TraceSpan[]) ).toEqual(new Set([])); @@ -378,13 +326,10 @@ describe('filterSpans', () => { it("should return spans whose tags' kv.key and kv.value match a filter", () => { expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }] }, spans) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '!=' }] }, spans ) @@ -393,19 +338,13 @@ describe('filterSpans', () => { it("should not return spans whose tags' kv.key match a filter but kv.value/operator does not match", () => { expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '!=' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '!=' }] }, spans) ).toEqual(new Set()); expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }] }, - spans - ) + filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }] }, spans) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '!=' }] }, spans ) @@ -415,7 +354,7 @@ describe('filterSpans', () => { it('should return spans with multiple tag filters', () => { // tags in same span expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -427,7 +366,7 @@ describe('filterSpans', () => { ) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -439,7 +378,7 @@ describe('filterSpans', () => { ) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -453,7 +392,7 @@ describe('filterSpans', () => { // tags in different spans expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -465,7 +404,7 @@ describe('filterSpans', () => { ) ).toEqual(new Set()); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -479,7 +418,7 @@ describe('filterSpans', () => { // values in different spans expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -491,7 +430,7 @@ describe('filterSpans', () => { ) ).toEqual(new Set()); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -503,7 +442,7 @@ describe('filterSpans', () => { ) ).toEqual(new Set()); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -515,7 +454,7 @@ describe('filterSpans', () => { ) ).toEqual(new Set()); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, tags: [ @@ -531,20 +470,14 @@ describe('filterSpans', () => { // 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( - filterSpansNewTraceViewHeader( - { ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0' }, - spans - ) - ).toEqual(new Set([spanID0])); - expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName2' }, - spans - ) - ).toEqual(new Set([])); - expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName2', spanNameOperator: '!=' }, spans ) @@ -552,51 +485,42 @@ describe('filterSpans', () => { // service name + span name + duration expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', from: '2ms' }, - spans - ) + filterSpans({ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', from: '2ms' }, spans) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', to: '2ms' }, - spans - ) + filterSpans({ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', to: '2ms' }, spans) ).toEqual(new Set([])); expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms' }, - spans - ) + filterSpans({ ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms' }, spans) ).toEqual(new Set([spanID2])); // service name + tag key expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, serviceName: 'serviceName0', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, spans ) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, serviceName: 'serviceName0', tags: [{ ...defaultTagFilter, key: 'tagKey1' }] }, spans ) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, serviceName: 'serviceName2', tags: [{ ...defaultTagFilter, key: 'tagKey1' }] }, spans ) ).toEqual(new Set([spanID2])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, serviceName: 'serviceName2', tags: [{ ...defaultTagFilter, key: 'tagKey2' }] }, spans ) ).toEqual(new Set([spanID2])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, serviceName: 'serviceName0', @@ -608,13 +532,10 @@ describe('filterSpans', () => { // duration + tag expect( - filterSpansNewTraceViewHeader( - { ...defaultFilters, from: '2ms', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, - spans - ) + filterSpans({ ...defaultFilters, from: '2ms', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, spans) ).toEqual(new Set([spanID0])); expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, to: '5ms', toOperator: '<=', tags: [{ ...defaultTagFilter, key: 'tagKey2' }] }, spans ) @@ -622,7 +543,7 @@ describe('filterSpans', () => { // all expect( - filterSpansNewTraceViewHeader( + filterSpans( { ...defaultFilters, serviceName: 'serviceName0', @@ -637,96 +558,4 @@ describe('filterSpans', () => { ) ).toEqual(new Set([spanID0])); }); - - it('should return `undefined` if spans is falsy', () => { - expect(filterSpans('operationName', null)).toBe(undefined); - }); - - it('should return spans whose spanID exactly match a filter', () => { - expect(filterSpans('spanID', spans)).toEqual(new Set([])); - expect(filterSpans(spanID0, spans)).toEqual(new Set([spanID0])); - expect(filterSpans(spanID2, spans)).toEqual(new Set([spanID2])); - }); - - it('should return spans whose operationName match a filter', () => { - expect(filterSpans('operationName', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('operationName0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('operationName2', spans)).toEqual(new Set([spanID2])); - }); - - it('should return spans whose serviceName match a filter', () => { - expect(filterSpans('serviceName', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('serviceName0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('serviceName2', spans)).toEqual(new Set([spanID2])); - }); - - it("should return spans whose tags' kv.key match a filter", () => { - expect(filterSpans('tagKey1', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('tagKey0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('tagKey2', spans)).toEqual(new Set([spanID2])); - }); - - it("should return spans whose tags' kv.value match a filter", () => { - expect(filterSpans('tagValue1', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('tagValue0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('tagValue2', spans)).toEqual(new Set([spanID2])); - }); - - it("should exclude span whose tags' kv.value or kv.key match a filter if the key matches an excludeKey", () => { - expect(filterSpans('tagValue1 -tagKey2', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('tagValue1 -tagKey1', spans)).toEqual(new Set([spanID2])); - }); - - it('should return spans whose kind, statusCode, statusMessage, libraryName, libraryVersion or traceState value match a filter', () => { - expect(filterSpans('kind0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('error', spans)).toEqual(new Set([spanID2])); - expect(filterSpans('statusMessage0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('libraryName', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('libraryVersion2', spans)).toEqual(new Set([spanID2])); - expect(filterSpans('traceState0', spans)).toEqual(new Set([spanID0])); - }); - - it('should return spans whose logs have a field whose kv.key match a filter', () => { - expect(filterSpans('logFieldKey1', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('logFieldKey0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('logFieldKey2', spans)).toEqual(new Set([spanID2])); - }); - - it('should return spans whose logs have a field whose kv.value match a filter', () => { - expect(filterSpans('logFieldValue1', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('logFieldValue0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('logFieldValue2', spans)).toEqual(new Set([spanID2])); - }); - - it('should exclude span whose logs have a field whose kv.value or kv.key match a filter if the key matches an excludeKey', () => { - expect(filterSpans('logFieldValue1 -logFieldKey2', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('logFieldValue1 -logFieldKey1', spans)).toEqual(new Set([spanID2])); - }); - - it("should return spans whose process.tags' kv.key match a filter", () => { - expect(filterSpans('processTagKey1', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('processTagKey0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('processTagKey2', spans)).toEqual(new Set([spanID2])); - }); - - it("should return spans whose process.processTags' kv.value match a filter", () => { - expect(filterSpans('processTagValue1', spans)).toEqual(new Set([spanID0, spanID2])); - expect(filterSpans('processTagValue0', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('processTagValue2', spans)).toEqual(new Set([spanID2])); - }); - - it("should exclude span whose process.processTags' kv.value or kv.key match a filter if the key matches an excludeKey", () => { - expect(filterSpans('processTagValue1 -processTagKey2', spans)).toEqual(new Set([spanID0])); - expect(filterSpans('processTagValue1 -processTagKey1', spans)).toEqual(new Set([spanID2])); - }); - - // This test may false positive if other tests are failing - it('should return an empty set if no spans match the filter', () => { - expect(filterSpans('-processTagKey1', spans)).toEqual(new Set()); - }); - - it('should return no spans when logs is null', () => { - const nullSpan = { ...span0, logs: null }; - expect(filterSpans('logFieldKey1', [nullSpan] as unknown as TraceSpan[])).toEqual(new Set([])); - }); }); 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 beb6465e044..03ed3fd85db 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx @@ -20,7 +20,7 @@ import { TNil, TraceKeyValuePair, TraceSpan } from '../types'; // 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 filterSpansNewTraceViewHeader(searchProps: SearchProps, spans: TraceSpan[] | TNil) { +export function filterSpans(searchProps: SearchProps, spans: TraceSpan[] | TNil) { if (!spans) { return undefined; } @@ -174,61 +174,3 @@ export const convertTimeFilter = (time: string) => { } return undefined; }; - -// legacy code that will be removed when the Header feature flag is removed -export function filterSpans(textFilter: string, spans: TraceSpan[] | TNil) { - if (!spans) { - return undefined; - } - - // if a span field includes at least one filter in includeFilters, the span is a match - const includeFilters: string[] = []; - - // values with keys that include text in any one of the excludeKeys will be ignored - const excludeKeys: string[] = []; - - // split textFilter by whitespace, remove empty strings, and extract includeFilters and excludeKeys - textFilter - .split(/\s+/) - .filter(Boolean) - .forEach((w) => { - if (w[0] === '-') { - excludeKeys.push(w.slice(1).toLowerCase()); - } else { - includeFilters.push(w.toLowerCase()); - } - }); - - const isTextInFilters = (filters: string[], text: string) => - filters.some((filter) => text.toLowerCase().includes(filter)); - - const isTextInKeyValues = (kvs: TraceKeyValuePair[]) => - kvs - ? kvs.some((kv) => { - // ignore checking key and value for a match if key is in excludeKeys - if (isTextInFilters(excludeKeys, kv.key)) { - return false; - } - // match if key or value matches an item in includeFilters - return isTextInFilters(includeFilters, kv.key) || isTextInFilters(includeFilters, kv.value.toString()); - }) - : false; - - const isSpanAMatch = (span: TraceSpan) => - isTextInFilters(includeFilters, span.operationName) || - isTextInFilters(includeFilters, span.process.serviceName) || - isTextInKeyValues(span.tags) || - (span.kind && isTextInFilters(includeFilters, span.kind)) || - (span.statusCode !== undefined && isTextInFilters(includeFilters, SpanStatusCode[span.statusCode])) || - (span.statusMessage && isTextInFilters(includeFilters, span.statusMessage)) || - (span.instrumentationLibraryName && isTextInFilters(includeFilters, span.instrumentationLibraryName)) || - (span.instrumentationLibraryVersion && isTextInFilters(includeFilters, span.instrumentationLibraryVersion)) || - (span.traceState && isTextInFilters(includeFilters, span.traceState)) || - (span.logs !== null && span.logs.some((log) => isTextInKeyValues(log.fields))) || - isTextInKeyValues(span.process.tags) || - includeFilters.some((filter) => filter === span.spanID); - - // declare as const because need to disambiguate the type - const rv: Set = new Set(spans.filter(isSpanAMatch).map((span: TraceSpan) => span.spanID)); - return rv; -} diff --git a/public/app/features/explore/TraceView/useSearch.test.ts b/public/app/features/explore/TraceView/useSearch.test.ts index 683de3c11fe..138710c8b79 100644 --- a/public/app/features/explore/TraceView/useSearch.test.ts +++ b/public/app/features/explore/TraceView/useSearch.test.ts @@ -1,7 +1,7 @@ import { act, renderHook } from '@testing-library/react'; import { TraceSpan } from './components'; -import { defaultFilters, useSearch, useSearchNewTraceViewHeader } from './useSearch'; +import { defaultFilters, useSearch } from './useSearch'; describe('useSearch', () => { const spans = [ @@ -28,28 +28,15 @@ describe('useSearch', () => { ]; it('returns matching span IDs', async () => { - const { result } = renderHook(() => useSearchNewTraceViewHeader(spans)); - act(() => result.current.setNewTraceViewHeaderSearch({ ...defaultFilters, serviceName: 'service1' })); + const { result } = renderHook(() => useSearch(spans)); + act(() => result.current.setSearch({ ...defaultFilters, 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(() => useSearchNewTraceViewHeader()); - act(() => result.current.setNewTraceViewHeaderSearch({ ...defaultFilters, serviceName: 'service1' })); + const { result } = renderHook(() => useSearch()); + act(() => result.current.setSearch({ ...defaultFilters, serviceName: 'service1' })); expect(result.current.spanFilterMatches).toBe(undefined); }); - - it('returns matching span IDs', async () => { - const { result } = renderHook(() => useSearch(spans)); - act(() => result.current.setSearch('service1')); - expect(result.current.spanFindMatches?.size).toBe(1); - expect(result.current.spanFindMatches?.has('span1')).toBe(true); - }); - - it('works without spans', async () => { - const { result } = renderHook(() => useSearch()); - act(() => result.current.setSearch('service1')); - expect(result.current.spanFindMatches).toBe(undefined); - }); }); diff --git a/public/app/features/explore/TraceView/useSearch.ts b/public/app/features/explore/TraceView/useSearch.ts index a67327eb72e..59469f2af99 100644 --- a/public/app/features/explore/TraceView/useSearch.ts +++ b/public/app/features/explore/TraceView/useSearch.ts @@ -1,7 +1,7 @@ import { useMemo, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; -import { filterSpansNewTraceViewHeader, filterSpans, TraceSpan } from './components'; +import { filterSpans, TraceSpan } from './components'; export interface SearchProps { serviceName?: string; @@ -41,21 +41,11 @@ export const defaultFilters = { * Controls the state of search input that highlights spans if they match the search string. * @param spans */ -export function useSearchNewTraceViewHeader(spans?: TraceSpan[]) { - const [newTraceViewHeaderSearch, setNewTraceViewHeaderSearch] = useState(defaultFilters); - const spanFilterMatches: Set | undefined = useMemo(() => { - return spans && filterSpansNewTraceViewHeader(newTraceViewHeaderSearch, spans); - }, [newTraceViewHeaderSearch, spans]); - - return { newTraceViewHeaderSearch, setNewTraceViewHeaderSearch, spanFilterMatches }; -} - -// legacy code that will be removed when the newTraceViewHeader feature flag is removed export function useSearch(spans?: TraceSpan[]) { - const [search, setSearch] = useState(''); - const spanFindMatches: Set | undefined = useMemo(() => { - return search && spans ? filterSpans(search, spans) : undefined; + const [search, setSearch] = useState(defaultFilters); + const spanFilterMatches: Set | undefined = useMemo(() => { + return spans && filterSpans(search, spans); }, [search, spans]); - return { search, setSearch, spanFindMatches }; + return { search, setSearch, spanFilterMatches }; } diff --git a/public/app/plugins/panel/traces/TracesPanel.tsx b/public/app/plugins/panel/traces/TracesPanel.tsx index a8e4b65b386..3a91bdb3a32 100644 --- a/public/app/plugins/panel/traces/TracesPanel.tsx +++ b/public/app/plugins/panel/traces/TracesPanel.tsx @@ -1,13 +1,11 @@ import { css } from '@emotion/css'; -import React, { useMemo, useState, createRef } from 'react'; +import React, { useMemo, createRef } from 'react'; import { useAsync } from 'react-use'; import { PanelProps } from '@grafana/data'; -import { config, getDataSourceSrv } from '@grafana/runtime'; +import { getDataSourceSrv } from '@grafana/runtime'; import { TraceView } from 'app/features/explore/TraceView/TraceView'; -import TracePageSearchBar from 'app/features/explore/TraceView/components/TracePageHeader/SearchBar/TracePageSearchBar'; import { TopOfViewRefType } from 'app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView'; -import { useSearch } from 'app/features/explore/TraceView/useSearch'; import { transformDataFrames } from 'app/features/explore/TraceView/utils/transform'; const styles = { @@ -20,13 +18,9 @@ const styles = { export const TracesPanel = ({ data }: PanelProps) => { const topOfViewRef = createRef(); const traceProp = useMemo(() => transformDataFrames(data.series[0]), [data.series]); - const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans); - const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState(''); - const [searchBarSuffix, setSearchBarSuffix] = useState(''); const dataSource = useAsync(async () => { return await getDataSourceSrv().get(data.request?.targets[0].datasource?.uid); }); - const datasourceType = dataSource && dataSource.value ? dataSource.value.type : 'unknown'; if (!data || !data.series.length || !traceProp) { return ( @@ -39,27 +33,10 @@ export const TracesPanel = ({ data }: PanelProps) => { return (
- {!config.featureToggles.newTraceViewHeader ? ( - - ) : null} -