mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
27107225ec
commit
090b8d61e2
@ -304,11 +304,6 @@ If the file has multiple traces, Grafana visualizes its first trace.
|
|||||||
|
|
||||||
## Span Filters
|
## 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 %}}
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
@ -370,11 +370,6 @@ To open a query in Tempo with the span name of that row automatically set in the
|
|||||||
|
|
||||||
## Span Filters
|
## 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 %}}
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
@ -273,11 +273,6 @@ If the file has multiple traces, Grafana visualizes its first trace.
|
|||||||
|
|
||||||
## Span Filters
|
## 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 %}}
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
@ -59,11 +59,6 @@ Shows condensed view or the trace timeline. Drag your mouse over the minimap to
|
|||||||
|
|
||||||
### Span Filters
|
### 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 %}}
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
@ -78,7 +78,6 @@ Experimental features might be changed or removed without prior notice.
|
|||||||
| `queryOverLive` | Use Grafana Live WebSocket to execute backend queries |
|
| `queryOverLive` | Use Grafana Live WebSocket to execute backend queries |
|
||||||
| `lokiExperimentalStreaming` | Support new streaming approach for loki (prototype, needs special loki build) |
|
| `lokiExperimentalStreaming` | Support new streaming approach for loki (prototype, needs special loki build) |
|
||||||
| `storage` | Configurable storage for dashboards, datasources, and resources |
|
| `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 |
|
| `datasourceQueryMultiStatus` | Introduce HTTP 207 Multi Status for api/ds/query |
|
||||||
| `traceToMetrics` | Enable trace to metrics links |
|
| `traceToMetrics` | Enable trace to metrics links |
|
||||||
| `prometheusWideSeries` | Enable wide series responses in the Prometheus datasource |
|
| `prometheusWideSeries` | Enable wide series responses in the Prometheus datasource |
|
||||||
|
@ -29,7 +29,6 @@ export interface FeatureToggles {
|
|||||||
featureHighlights?: boolean;
|
featureHighlights?: boolean;
|
||||||
migrationLocking?: boolean;
|
migrationLocking?: boolean;
|
||||||
storage?: boolean;
|
storage?: boolean;
|
||||||
newTraceViewHeader?: boolean;
|
|
||||||
correlations?: boolean;
|
correlations?: boolean;
|
||||||
datasourceQueryMultiStatus?: boolean;
|
datasourceQueryMultiStatus?: boolean;
|
||||||
traceToMetrics?: boolean;
|
traceToMetrics?: boolean;
|
||||||
|
@ -79,13 +79,6 @@ var (
|
|||||||
Stage: FeatureStageExperimental,
|
Stage: FeatureStageExperimental,
|
||||||
Owner: grafanaAppPlatformSquad,
|
Owner: grafanaAppPlatformSquad,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "newTraceViewHeader",
|
|
||||||
Description: "Shows the new trace view header",
|
|
||||||
Stage: FeatureStageExperimental,
|
|
||||||
FrontendOnly: true,
|
|
||||||
Owner: grafanaObservabilityTracesAndProfilingSquad,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "correlations",
|
Name: "correlations",
|
||||||
Description: "Correlations page",
|
Description: "Correlations page",
|
||||||
|
@ -10,7 +10,6 @@ lokiExperimentalStreaming,experimental,@grafana/observability-logs,false,false,f
|
|||||||
featureHighlights,GA,@grafana/grafana-as-code,false,false,false,false
|
featureHighlights,GA,@grafana/grafana-as-code,false,false,false,false
|
||||||
migrationLocking,preview,@grafana/backend-platform,false,false,false,false
|
migrationLocking,preview,@grafana/backend-platform,false,false,false,false
|
||||||
storage,experimental,@grafana/grafana-app-platform-squad,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
|
correlations,preview,@grafana/explore-squad,false,false,false,false
|
||||||
datasourceQueryMultiStatus,experimental,@grafana/plugins-platform-backend,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
|
traceToMetrics,experimental,@grafana/observability-traces-and-profiling,false,false,false,true
|
||||||
|
|
@ -51,10 +51,6 @@ const (
|
|||||||
// Configurable storage for dashboards, datasources, and resources
|
// Configurable storage for dashboards, datasources, and resources
|
||||||
FlagStorage = "storage"
|
FlagStorage = "storage"
|
||||||
|
|
||||||
// FlagNewTraceViewHeader
|
|
||||||
// Shows the new trace view header
|
|
||||||
FlagNewTraceViewHeader = "newTraceViewHeader"
|
|
||||||
|
|
||||||
// FlagCorrelations
|
// FlagCorrelations
|
||||||
// Correlations page
|
// Correlations page
|
||||||
FlagCorrelations = "correlations"
|
FlagCorrelations = "correlations"
|
||||||
|
@ -29,8 +29,6 @@ function getTraceView(frames: DataFrame[]) {
|
|||||||
dataFrames={frames}
|
dataFrames={frames}
|
||||||
splitOpenFn={() => {}}
|
splitOpenFn={() => {}}
|
||||||
traceProp={transformDataFrames(frames[0])!}
|
traceProp={transformDataFrames(frames[0])!}
|
||||||
search=""
|
|
||||||
focusedSpanIdForSearch=""
|
|
||||||
queryResponse={mockPanelData}
|
queryResponse={mockPanelData}
|
||||||
datasource={undefined}
|
datasource={undefined}
|
||||||
topOfViewRef={topOfViewRef}
|
topOfViewRef={topOfViewRef}
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
PanelData,
|
PanelData,
|
||||||
SplitOpen,
|
SplitOpen,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { config, getTemplateSrv } from '@grafana/runtime';
|
import { getTemplateSrv } from '@grafana/runtime';
|
||||||
import { DataQuery } from '@grafana/schema';
|
import { DataQuery } from '@grafana/schema';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { getTraceToLogsOptions, TraceToLogsData } from 'app/core/components/TraceToLogs/TraceToLogsSettings';
|
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 { changePanelState } from '../state/explorePane';
|
||||||
|
|
||||||
import {
|
import { SpanBarOptionsData, Trace, TracePageHeader, TraceTimelineViewer, TTraceTimeline } from './components';
|
||||||
SpanBarOptionsData,
|
|
||||||
Trace,
|
|
||||||
TracePageHeader,
|
|
||||||
NewTracePageHeader,
|
|
||||||
TraceTimelineViewer,
|
|
||||||
TTraceTimeline,
|
|
||||||
} from './components';
|
|
||||||
import SpanGraph from './components/TracePageHeader/SpanGraph';
|
import SpanGraph from './components/TracePageHeader/SpanGraph';
|
||||||
import { TopOfViewRefType } from './components/TraceTimelineViewer/VirtualizedTraceView';
|
import { TopOfViewRefType } from './components/TraceTimelineViewer/VirtualizedTraceView';
|
||||||
import { createSpanLinkFactory } from './createSpanLink';
|
import { createSpanLinkFactory } from './createSpanLink';
|
||||||
import { useChildrenState } from './useChildrenState';
|
import { useChildrenState } from './useChildrenState';
|
||||||
import { useDetailState } from './useDetailState';
|
import { useDetailState } from './useDetailState';
|
||||||
import { useHoverIndentGuide } from './useHoverIndentGuide';
|
import { useHoverIndentGuide } from './useHoverIndentGuide';
|
||||||
import { useSearchNewTraceViewHeader } from './useSearch';
|
import { useSearch } from './useSearch';
|
||||||
import { useViewRange } from './useViewRange';
|
import { useViewRange } from './useViewRange';
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
@ -66,9 +59,6 @@ type Props = {
|
|||||||
scrollElement?: Element;
|
scrollElement?: Element;
|
||||||
scrollElementClass?: string;
|
scrollElementClass?: string;
|
||||||
traceProp: Trace;
|
traceProp: Trace;
|
||||||
spanFindMatches?: Set<string>;
|
|
||||||
search: string;
|
|
||||||
focusedSpanIdForSearch: string;
|
|
||||||
queryResponse: PanelData;
|
queryResponse: PanelData;
|
||||||
datasource: DataSourceApi<DataQuery, DataSourceJsonData, {}> | undefined;
|
datasource: DataSourceApi<DataQuery, DataSourceJsonData, {}> | undefined;
|
||||||
topOfViewRef: RefObject<HTMLDivElement>;
|
topOfViewRef: RefObject<HTMLDivElement>;
|
||||||
@ -76,7 +66,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function TraceView(props: Props) {
|
export function TraceView(props: Props) {
|
||||||
const { spanFindMatches, traceProp, datasource, topOfViewRef, topOfViewRefType, exploreId } = props;
|
const { traceProp, datasource, topOfViewRef, topOfViewRefType, exploreId } = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
detailStates,
|
detailStates,
|
||||||
@ -94,13 +84,11 @@ export function TraceView(props: Props) {
|
|||||||
const { removeHoverIndentGuideId, addHoverIndentGuideId, hoverIndentGuideIds } = useHoverIndentGuide();
|
const { removeHoverIndentGuideId, addHoverIndentGuideId, hoverIndentGuideIds } = useHoverIndentGuide();
|
||||||
const { viewRange, updateViewRangeTime, updateNextViewRangeTime } = useViewRange();
|
const { viewRange, updateViewRangeTime, updateNextViewRangeTime } = useViewRange();
|
||||||
const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState();
|
const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState();
|
||||||
const { newTraceViewHeaderSearch, setNewTraceViewHeaderSearch, spanFilterMatches } = useSearchNewTraceViewHeader(
|
const { search, setSearch, spanFilterMatches } = useSearch(traceProp?.spans);
|
||||||
traceProp?.spans
|
const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState('');
|
||||||
);
|
|
||||||
const [newTraceViewHeaderFocusedSpanIdForSearch, setNewTraceViewHeaderFocusedSpanIdForSearch] = useState('');
|
|
||||||
const [showSpanFilters, setShowSpanFilters] = useToggle(false);
|
const [showSpanFilters, setShowSpanFilters] = useToggle(false);
|
||||||
const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false);
|
const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false);
|
||||||
const [headerHeight, setHeaderHeight] = useState(0);
|
const [headerHeight, setHeaderHeight] = useState(100);
|
||||||
|
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
@ -154,43 +142,31 @@ export function TraceView(props: Props) {
|
|||||||
<>
|
<>
|
||||||
{props.dataFrames?.length && traceProp ? (
|
{props.dataFrames?.length && traceProp ? (
|
||||||
<>
|
<>
|
||||||
{config.featureToggles.newTraceViewHeader ? (
|
<TracePageHeader
|
||||||
<>
|
trace={traceProp}
|
||||||
<NewTracePageHeader
|
data={props.dataFrames[0]}
|
||||||
trace={traceProp}
|
timeZone={timeZone}
|
||||||
data={props.dataFrames[0]}
|
search={search}
|
||||||
timeZone={timeZone}
|
setSearch={setSearch}
|
||||||
search={newTraceViewHeaderSearch}
|
showSpanFilters={showSpanFilters}
|
||||||
setSearch={setNewTraceViewHeaderSearch}
|
setShowSpanFilters={setShowSpanFilters}
|
||||||
showSpanFilters={showSpanFilters}
|
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
||||||
setShowSpanFilters={setShowSpanFilters}
|
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
||||||
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
||||||
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
spanFilterMatches={spanFilterMatches}
|
||||||
setFocusedSpanIdForSearch={setNewTraceViewHeaderFocusedSpanIdForSearch}
|
datasourceType={datasourceType}
|
||||||
spanFilterMatches={spanFilterMatches}
|
setHeaderHeight={setHeaderHeight}
|
||||||
datasourceType={datasourceType}
|
app={exploreId ? CoreApp.Explore : CoreApp.Unknown}
|
||||||
setHeaderHeight={setHeaderHeight}
|
/>
|
||||||
app={exploreId ? CoreApp.Explore : CoreApp.Unknown}
|
<SpanGraph
|
||||||
/>
|
trace={traceProp}
|
||||||
<SpanGraph
|
viewRange={viewRange}
|
||||||
trace={traceProp}
|
updateNextViewRangeTime={updateNextViewRangeTime}
|
||||||
viewRange={viewRange}
|
updateViewRangeTime={updateViewRangeTime}
|
||||||
updateNextViewRangeTime={updateNextViewRangeTime}
|
/>
|
||||||
updateViewRangeTime={updateViewRangeTime}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<TracePageHeader
|
|
||||||
trace={traceProp}
|
|
||||||
updateNextViewRangeTime={updateNextViewRangeTime}
|
|
||||||
updateViewRangeTime={updateViewRangeTime}
|
|
||||||
viewRange={viewRange}
|
|
||||||
timeZone={timeZone}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<TraceTimelineViewer
|
<TraceTimelineViewer
|
||||||
registerAccessors={noop}
|
registerAccessors={noop}
|
||||||
findMatchesIDs={config.featureToggles.newTraceViewHeader ? spanFilterMatches : spanFindMatches}
|
findMatchesIDs={spanFilterMatches}
|
||||||
trace={traceProp}
|
trace={traceProp}
|
||||||
datasourceType={datasourceType}
|
datasourceType={datasourceType}
|
||||||
spanBarOptions={spanBarOptions?.spanBar}
|
spanBarOptions={spanBarOptions?.spanBar}
|
||||||
@ -214,19 +190,13 @@ export function TraceView(props: Props) {
|
|||||||
detailProcessToggle={detailProcessToggle}
|
detailProcessToggle={detailProcessToggle}
|
||||||
detailTagsToggle={detailTagsToggle}
|
detailTagsToggle={detailTagsToggle}
|
||||||
detailToggle={toggleDetail}
|
detailToggle={toggleDetail}
|
||||||
setTrace={noop}
|
|
||||||
addHoverIndentGuideId={addHoverIndentGuideId}
|
addHoverIndentGuideId={addHoverIndentGuideId}
|
||||||
removeHoverIndentGuideId={removeHoverIndentGuideId}
|
removeHoverIndentGuideId={removeHoverIndentGuideId}
|
||||||
linksGetter={() => []}
|
linksGetter={() => []}
|
||||||
uiFind={props.search}
|
|
||||||
createSpanLink={createSpanLink}
|
createSpanLink={createSpanLink}
|
||||||
scrollElement={scrollElement}
|
scrollElement={scrollElement}
|
||||||
focusedSpanId={focusedSpanId}
|
focusedSpanId={focusedSpanId}
|
||||||
focusedSpanIdForSearch={
|
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
||||||
config.featureToggles.newTraceViewHeader
|
|
||||||
? newTraceViewHeaderFocusedSpanIdForSearch
|
|
||||||
: props.focusedSpanIdForSearch!
|
|
||||||
}
|
|
||||||
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
||||||
createFocusSpanLink={createFocusSpanLink}
|
createFocusSpanLink={createFocusSpanLink}
|
||||||
topOfViewRef={topOfViewRef}
|
topOfViewRef={topOfViewRef}
|
||||||
|
@ -4,7 +4,6 @@ import React, { createRef } from 'react';
|
|||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { getDefaultTimeRange, LoadingState } from '@grafana/data';
|
import { getDefaultTimeRange, LoadingState } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
|
||||||
|
|
||||||
import { configureStore } from '../../../store/configureStore';
|
import { configureStore } from '../../../store/configureStore';
|
||||||
|
|
||||||
@ -86,55 +85,7 @@ describe('TraceViewContainer', () => {
|
|||||||
expect(screen.queryAllByText('', { selector: 'div[data-testid="span-view"]' }).length).toBe(3);
|
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 () => {
|
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();
|
renderTraceViewContainer();
|
||||||
const spanFiltersButton = screen.getByRole('button', { name: 'Span Filters 3 spans Prev Next' });
|
const spanFiltersButton = screen.getByRole('button', { name: 'Span Filters 3 spans Prev Next' });
|
||||||
await user.click(spanFiltersButton);
|
await user.click(spanFiltersButton);
|
||||||
@ -184,7 +135,6 @@ describe('TraceViewContainer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('show matches only works as expected', async () => {
|
it('show matches only works as expected', async () => {
|
||||||
config.featureToggles.newTraceViewHeader = true;
|
|
||||||
renderTraceViewContainer();
|
renderTraceViewContainer();
|
||||||
const spanFiltersButton = screen.getByRole('button', { name: 'Span Filters 3 spans Prev Next' });
|
const spanFiltersButton = screen.getByRole('button', { name: 'Span Filters 3 spans Prev Next' });
|
||||||
await user.click(spanFiltersButton);
|
await user.click(spanFiltersButton);
|
||||||
|
@ -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 { DataFrame, SplitOpen, PanelData } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
|
||||||
import { PanelChrome } from '@grafana/ui/src/components/PanelChrome/PanelChrome';
|
import { PanelChrome } from '@grafana/ui/src/components/PanelChrome/PanelChrome';
|
||||||
import { StoreState, useSelector } from 'app/types';
|
import { StoreState, useSelector } from 'app/types';
|
||||||
|
|
||||||
import { TraceView } from './TraceView';
|
import { TraceView } from './TraceView';
|
||||||
import TracePageSearchBar from './components/TracePageHeader/SearchBar/TracePageSearchBar';
|
|
||||||
import { TopOfViewRefType } from './components/TraceTimelineViewer/VirtualizedTraceView';
|
import { TopOfViewRefType } from './components/TraceTimelineViewer/VirtualizedTraceView';
|
||||||
import { useSearch } from './useSearch';
|
|
||||||
import { transformDataFrames } from './utils/transform';
|
import { transformDataFrames } from './utils/transform';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -25,47 +22,22 @@ export function TraceViewContainer(props: Props) {
|
|||||||
const frame = props.dataFrames[0];
|
const frame = props.dataFrames[0];
|
||||||
const { dataFrames, splitOpenFn, exploreId, scrollElement, topOfViewRef, queryResponse } = props;
|
const { dataFrames, splitOpenFn, exploreId, scrollElement, topOfViewRef, queryResponse } = props;
|
||||||
const traceProp = useMemo(() => transformDataFrames(frame), [frame]);
|
const traceProp = useMemo(() => transformDataFrames(frame), [frame]);
|
||||||
const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans);
|
|
||||||
const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState('');
|
|
||||||
const [searchBarSuffix, setSearchBarSuffix] = useState('');
|
|
||||||
const datasource = useSelector(
|
const datasource = useSelector(
|
||||||
(state: StoreState) => state.explore.panes[props.exploreId]?.datasourceInstance ?? undefined
|
(state: StoreState) => state.explore.panes[props.exploreId]?.datasourceInstance ?? undefined
|
||||||
);
|
);
|
||||||
const datasourceType = datasource ? datasource?.type : 'unknown';
|
|
||||||
|
|
||||||
if (!traceProp) {
|
if (!traceProp) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelChrome
|
<PanelChrome padding="none" title="Trace">
|
||||||
padding="none"
|
|
||||||
title="Trace"
|
|
||||||
actions={
|
|
||||||
!config.featureToggles.newTraceViewHeader && (
|
|
||||||
<TracePageSearchBar
|
|
||||||
navigable={true}
|
|
||||||
searchValue={search}
|
|
||||||
setSearch={setSearch}
|
|
||||||
spanFindMatches={spanFindMatches}
|
|
||||||
searchBarSuffix={searchBarSuffix}
|
|
||||||
setSearchBarSuffix={setSearchBarSuffix}
|
|
||||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
|
||||||
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
|
||||||
datasourceType={datasourceType}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TraceView
|
<TraceView
|
||||||
exploreId={exploreId}
|
exploreId={exploreId}
|
||||||
dataFrames={dataFrames}
|
dataFrames={dataFrames}
|
||||||
splitOpenFn={splitOpenFn}
|
splitOpenFn={splitOpenFn}
|
||||||
scrollElement={scrollElement}
|
scrollElement={scrollElement}
|
||||||
traceProp={traceProp}
|
traceProp={traceProp}
|
||||||
spanFindMatches={spanFindMatches}
|
|
||||||
search={search}
|
|
||||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
|
||||||
queryResponse={queryResponse}
|
queryResponse={queryResponse}
|
||||||
datasource={datasource}
|
datasource={datasource}
|
||||||
topOfViewRef={topOfViewRef}
|
topOfViewRef={topOfViewRef}
|
||||||
|
@ -1,65 +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 { getByText, render } from '@testing-library/react';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { MutableDataFrame } from '@grafana/data';
|
|
||||||
import config from 'app/core/config';
|
|
||||||
|
|
||||||
import { defaultFilters } from '../../useSearch';
|
|
||||||
|
|
||||||
import { NewTracePageHeader } from './NewTracePageHeader';
|
|
||||||
import { trace } from './TracePageHeader.test';
|
|
||||||
|
|
||||||
const setup = () => {
|
|
||||||
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(<NewTracePageHeader {...defaultProps} />);
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -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<React.SetStateAction<SearchProps>>;
|
|
||||||
showSpanFilters: boolean;
|
|
||||||
setShowSpanFilters: (isOpen: boolean) => void;
|
|
||||||
showSpanFilterMatchesOnly: boolean;
|
|
||||||
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
|
||||||
setFocusedSpanIdForSearch: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
spanFilterMatches: Set<string> | 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 = (
|
|
||||||
<h1 className={cx(styles.title)}>
|
|
||||||
<TraceName traceName={getTraceName(trace.spans)} />
|
|
||||||
<small className={styles.duration}>{formatDuration(trace.duration)}</small>
|
|
||||||
</h1>
|
|
||||||
);
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<header className={styles.header}>
|
|
||||||
<div className={styles.titleRow}>
|
|
||||||
{links && links.length > 0 && <ExternalLinks links={links} className={styles.TracePageHeaderBack} />}
|
|
||||||
{title}
|
|
||||||
<TracePageActions traceId={trace.traceID} data={data} app={app} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.subtitle}>
|
|
||||||
<span className={styles.timestamp}>{timestamp(trace, timeZone, styles)}</span>
|
|
||||||
<span className={styles.tagMeta}>
|
|
||||||
{method && method.length > 0 && (
|
|
||||||
<Tooltip content={'http.method'} interactive={true}>
|
|
||||||
<span className={styles.tag}>
|
|
||||||
<Badge text={method[0].value} color="blue" />
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{status && status.length > 0 && (
|
|
||||||
<Tooltip content={'http.status_code'} interactive={true}>
|
|
||||||
<span className={styles.tag}>
|
|
||||||
<Badge text={status[0].value} color={statusColor} />
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{url && url.length > 0 && (
|
|
||||||
<Tooltip content={'http.url or http.target or http.path'} interactive={true}>
|
|
||||||
<span className={styles.url}>{url[0].value}</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SpanFilters
|
|
||||||
trace={trace}
|
|
||||||
showSpanFilters={showSpanFilters}
|
|
||||||
setShowSpanFilters={setShowSpanFilters}
|
|
||||||
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
|
||||||
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
|
||||||
search={search}
|
|
||||||
setSearch={setSearch}
|
|
||||||
spanFilterMatches={spanFilterMatches}
|
|
||||||
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
|
||||||
datasourceType={datasourceType}
|
|
||||||
/>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
};
|
|
@ -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('<NewTracePageSearchBar>', () => {
|
|
||||||
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 <NewTracePageSearchBar {...searchBarProps} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
expect(() => render(<NewTracePageSearchBarWithProps matches={[]} />)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders clear filter button', () => {
|
|
||||||
render(<NewTracePageSearchBarWithProps matches={[]} />);
|
|
||||||
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(<NewTracePageSearchBarWithProps matches={[]} />);
|
|
||||||
const matchesSwitch = screen.getByRole('checkbox', { name: 'Show matches only switch' });
|
|
||||||
expect(matchesSwitch).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -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<string> | undefined;
|
|
||||||
showSpanFilterMatchesOnly: boolean;
|
|
||||||
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
|
||||||
focusedSpanIndexForSearch: number;
|
|
||||||
setFocusedSpanIndexForSearch: Dispatch<SetStateAction<number>>;
|
|
||||||
setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>;
|
|
||||||
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 (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.controls}>
|
|
||||||
<>
|
|
||||||
<div className={styles.clearButton}>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
disabled={!clearEnabled}
|
|
||||||
type="button"
|
|
||||||
fill="outline"
|
|
||||||
aria-label="Clear filters button"
|
|
||||||
onClick={clear}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</Button>
|
|
||||||
<div className={styles.matchesOnly}>
|
|
||||||
<Switch
|
|
||||||
value={showSpanFilterMatchesOnly}
|
|
||||||
onChange={(value) => setShowSpanFilterMatchesOnly(value.currentTarget.checked ?? false)}
|
|
||||||
label="Show matches only switch"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={() => setShowSpanFilterMatchesOnly(!showSpanFilterMatchesOnly)}
|
|
||||||
className={styles.clearMatchesButton}
|
|
||||||
variant="secondary"
|
|
||||||
fill="text"
|
|
||||||
>
|
|
||||||
Show matches only
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.nextPrevResult}>
|
|
||||||
<NextPrevResult
|
|
||||||
trace={trace}
|
|
||||||
spanFilterMatches={spanFilterMatches}
|
|
||||||
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
|
||||||
focusedSpanIndexForSearch={focusedSpanIndexForSearch}
|
|
||||||
setFocusedSpanIndexForSearch={setFocusedSpanIndexForSearch}
|
|
||||||
datasourceType={datasourceType}
|
|
||||||
showSpanFilters={showSpanFilters}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
};
|
|
@ -145,8 +145,10 @@ export default memo(function NextPrevResult(props: NextPrevResultProps) {
|
|||||||
|
|
||||||
if (spanFilterMatches) {
|
if (spanFilterMatches) {
|
||||||
spanFilterMatches.forEach((spanID) => {
|
spanFilterMatches.forEach((spanID) => {
|
||||||
matchedServices.push(trace.processes[spanID].serviceName);
|
if (trace.processes[spanID]) {
|
||||||
matchedDepth.push(trace.spans.find((span) => span.spanID === spanID)?.depth || 0);
|
matchedServices.push(trace.processes[spanID].serviceName);
|
||||||
|
matchedDepth.push(trace.spans.find((span) => span.spanID === spanID)?.depth || 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (spanFilterMatches.size === 0) {
|
if (spanFilterMatches.size === 0) {
|
||||||
|
@ -15,65 +15,45 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import React from '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';
|
import TracePageSearchBar from './TracePageSearchBar';
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
forwardedRef: React.createRef(),
|
|
||||||
navigable: true,
|
|
||||||
searchBarSuffix: 'suffix',
|
|
||||||
searchValue: 'value',
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<TracePageSearchBar>', () => {
|
describe('<TracePageSearchBar>', () => {
|
||||||
describe('truthy textFilter', () => {
|
const TracePageSearchBarWithProps = (props: { matches: string[] | undefined }) => {
|
||||||
it('renders SearchBarInput with correct props', () => {
|
const searchBarProps = {
|
||||||
render(<TracePageSearchBar {...(defaultProps as unknown as TracePageSearchBarProps)} />);
|
trace: trace,
|
||||||
expect((screen.getByPlaceholderText('Find...') as HTMLInputElement)['value']).toEqual('value');
|
search: defaultFilters,
|
||||||
const suffix = screen.getByLabelText('Search bar suffix');
|
spanFilterMatches: props.matches ? new Set(props.matches) : undefined,
|
||||||
const theme = createTheme();
|
showSpanFilterMatchesOnly: false,
|
||||||
expect(suffix['className']).toBe(getStyles(theme).TracePageSearchBarSuffix);
|
setShowSpanFilterMatchesOnly: jest.fn(),
|
||||||
expect(suffix.textContent).toBe('suffix');
|
setFocusedSpanIdForSearch: jest.fn(),
|
||||||
});
|
focusedSpanIndexForSearch: -1,
|
||||||
|
setFocusedSpanIndexForSearch: jest.fn(),
|
||||||
|
datasourceType: '',
|
||||||
|
clear: jest.fn(),
|
||||||
|
totalSpans: 100,
|
||||||
|
showSpanFilters: true,
|
||||||
|
};
|
||||||
|
|
||||||
it('renders buttons', () => {
|
return <TracePageSearchBar {...searchBarProps} />;
|
||||||
render(<TracePageSearchBar {...(defaultProps as unknown as TracePageSearchBarProps)} />);
|
};
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('only shows navigable buttons when navigable is true', () => {
|
it('should render', () => {
|
||||||
const props = {
|
expect(() => render(<TracePageSearchBarWithProps matches={[]} />)).not.toThrow();
|
||||||
...defaultProps,
|
|
||||||
navigable: false,
|
|
||||||
};
|
|
||||||
render(<TracePageSearchBar {...(props as unknown as TracePageSearchBarProps)} />);
|
|
||||||
expect(screen.queryByRole('button', { name: 'Next results button' })).not.toBeInTheDocument();
|
|
||||||
expect(screen.queryByRole('button', { name: 'Prev results button' })).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('falsy textFilter', () => {
|
it('renders clear filter button', () => {
|
||||||
beforeEach(() => {
|
render(<TracePageSearchBarWithProps matches={[]} />);
|
||||||
const props = {
|
const clearFiltersButton = screen.getByRole('button', { name: 'Clear filters button' });
|
||||||
...defaultProps,
|
expect(clearFiltersButton).toBeInTheDocument();
|
||||||
searchValue: '',
|
expect((clearFiltersButton as HTMLButtonElement)['disabled']).toBe(true);
|
||||||
};
|
});
|
||||||
render(<TracePageSearchBar {...(props as unknown as TracePageSearchBarProps)} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not render suffix', () => {
|
it('renders show span filter matches only switch', async () => {
|
||||||
expect(screen.queryByLabelText('Search bar suffix')).not.toBeInTheDocument();
|
render(<TracePageSearchBarWithProps matches={[]} />);
|
||||||
});
|
const matchesSwitch = screen.getByRole('checkbox', { name: 'Show matches only switch' });
|
||||||
|
expect(matchesSwitch).toBeInTheDocument();
|
||||||
it('renders buttons', () => {
|
|
||||||
expect(screen.getByRole('button', { name: 'Next results button' })).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole('button', { name: 'Prev results button' })).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -13,181 +13,147 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import cx from 'classnames';
|
import React, { memo, Dispatch, SetStateAction, useMemo } from 'react';
|
||||||
import React, { memo, Dispatch, SetStateAction } from 'react';
|
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { config, reportInteraction } from '@grafana/runtime';
|
import { Button, Switch, useStyles2 } from '@grafana/ui';
|
||||||
import { Button, useStyles2 } from '@grafana/ui';
|
import { getButtonStyles } from '@grafana/ui/src/components/Button';
|
||||||
|
|
||||||
import SearchBarInput from '../../common/SearchBarInput';
|
import { SearchProps } from '../../../useSearch';
|
||||||
import { ubFlexAuto, ubJustifyEnd } from '../../uberUtilityStyles';
|
import { Trace } from '../../types';
|
||||||
|
import { convertTimeFilter } from '../../utils/filter-spans';
|
||||||
|
|
||||||
// eslint-disable-next-line no-duplicate-imports
|
import NextPrevResult from './NextPrevResult';
|
||||||
|
|
||||||
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;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TracePageSearchBarProps = {
|
export type TracePageSearchBarProps = {
|
||||||
navigable: boolean;
|
trace: Trace;
|
||||||
searchValue: string;
|
search: SearchProps;
|
||||||
setSearch: (value: string) => void;
|
spanFilterMatches: Set<string> | undefined;
|
||||||
searchBarSuffix: string;
|
showSpanFilterMatchesOnly: boolean;
|
||||||
spanFindMatches: Set<string> | undefined;
|
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
||||||
focusedSpanIdForSearch: string;
|
focusedSpanIndexForSearch: number;
|
||||||
setSearchBarSuffix: Dispatch<SetStateAction<string>>;
|
setFocusedSpanIndexForSearch: Dispatch<SetStateAction<number>>;
|
||||||
setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>;
|
setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>;
|
||||||
datasourceType: string;
|
datasourceType: string;
|
||||||
|
clear: () => void;
|
||||||
|
showSpanFilters: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(function TracePageSearchBar(props: TracePageSearchBarProps) {
|
export default memo(function TracePageSearchBar(props: TracePageSearchBarProps) {
|
||||||
const {
|
const {
|
||||||
navigable,
|
trace,
|
||||||
setSearch,
|
search,
|
||||||
searchValue,
|
spanFilterMatches,
|
||||||
searchBarSuffix,
|
showSpanFilterMatchesOnly,
|
||||||
spanFindMatches,
|
setShowSpanFilterMatchesOnly,
|
||||||
focusedSpanIdForSearch,
|
focusedSpanIndexForSearch,
|
||||||
setSearchBarSuffix,
|
setFocusedSpanIndexForSearch,
|
||||||
setFocusedSpanIdForSearch,
|
setFocusedSpanIdForSearch,
|
||||||
datasourceType,
|
datasourceType,
|
||||||
|
clear,
|
||||||
|
showSpanFilters,
|
||||||
} = props;
|
} = props;
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const suffix = searchValue ? (
|
const clearEnabled = useMemo(() => {
|
||||||
<span className={styles.TracePageSearchBarSuffix} aria-label="Search bar suffix">
|
return (
|
||||||
{searchBarSuffix}
|
(search.serviceName && search.serviceName !== '') ||
|
||||||
</span>
|
(search.spanName && search.spanName !== '') ||
|
||||||
) : null;
|
convertTimeFilter(search.from || '') ||
|
||||||
|
convertTimeFilter(search.to || '') ||
|
||||||
const SearchBarInputProps = {
|
search.tags.length > 1 ||
|
||||||
className: cx(styles.TracePageSearchBarBar, ubFlexAuto),
|
search.tags.some((tag) => {
|
||||||
name: 'search',
|
return tag.key;
|
||||||
suffix,
|
})
|
||||||
};
|
);
|
||||||
|
}, [search.serviceName, search.spanName, search.from, search.to, search.tags]);
|
||||||
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 '';
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.TracePageSearchBar}>
|
<div className={styles.container}>
|
||||||
<span className={ubJustifyEnd} style={{ display: 'flex' }}>
|
<div className={styles.controls}>
|
||||||
<SearchBarInput
|
|
||||||
onChange={setTraceSearch}
|
|
||||||
value={searchValue}
|
|
||||||
inputProps={SearchBarInputProps}
|
|
||||||
allowClear={true}
|
|
||||||
/>
|
|
||||||
<>
|
<>
|
||||||
{navigable && (
|
<div className={styles.clearButton}>
|
||||||
<>
|
<Button
|
||||||
<Button
|
variant="destructive"
|
||||||
className={styles.TracePageSearchBarBtn}
|
disabled={!clearEnabled}
|
||||||
variant="secondary"
|
type="button"
|
||||||
disabled={!searchValue}
|
fill="outline"
|
||||||
type="button"
|
aria-label="Clear filters button"
|
||||||
icon="arrow-down"
|
onClick={clear}
|
||||||
aria-label="Next results button"
|
>
|
||||||
onClick={nextResult}
|
Clear
|
||||||
|
</Button>
|
||||||
|
<div className={styles.matchesOnly}>
|
||||||
|
<Switch
|
||||||
|
value={showSpanFilterMatchesOnly}
|
||||||
|
onChange={(value) => setShowSpanFilterMatchesOnly(value.currentTarget.checked ?? false)}
|
||||||
|
label="Show matches only switch"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={styles.TracePageSearchBarBtn}
|
onClick={() => setShowSpanFilterMatchesOnly(!showSpanFilterMatchesOnly)}
|
||||||
|
className={styles.clearMatchesButton}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
disabled={!searchValue}
|
fill="text"
|
||||||
type="button"
|
>
|
||||||
icon="arrow-up"
|
Show matches only
|
||||||
aria-label="Prev results button"
|
</Button>
|
||||||
onClick={prevResult}
|
</div>
|
||||||
/>
|
</div>
|
||||||
</>
|
<div className={styles.nextPrevResult}>
|
||||||
)}
|
<NextPrevResult
|
||||||
|
trace={trace}
|
||||||
|
spanFilterMatches={spanFilterMatches}
|
||||||
|
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
||||||
|
focusedSpanIndexForSearch={focusedSpanIndexForSearch}
|
||||||
|
setFocusedSpanIndexForSearch={setFocusedSpanIndexForSearch}
|
||||||
|
datasourceType={datasourceType}
|
||||||
|
showSpanFilters={showSpanFilters}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -25,8 +25,8 @@ import { IntervalInput } from 'app/core/components/IntervalInput/IntervalInput';
|
|||||||
import { defaultFilters, randomId, SearchProps, Tag } from '../../../useSearch';
|
import { defaultFilters, randomId, SearchProps, Tag } from '../../../useSearch';
|
||||||
import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE, ID } from '../../constants/span';
|
import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE, ID } from '../../constants/span';
|
||||||
import { Trace } from '../../types';
|
import { Trace } from '../../types';
|
||||||
import NewTracePageSearchBar from '../SearchBar/NewTracePageSearchBar';
|
|
||||||
import NextPrevResult from '../SearchBar/NextPrevResult';
|
import NextPrevResult from '../SearchBar/NextPrevResult';
|
||||||
|
import TracePageSearchBar from '../SearchBar/TracePageSearchBar';
|
||||||
|
|
||||||
export type SpanFilterProps = {
|
export type SpanFilterProps = {
|
||||||
trace: Trace;
|
trace: Trace;
|
||||||
@ -447,7 +447,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
|||||||
</InlineField>
|
</InlineField>
|
||||||
</InlineFieldRow>
|
</InlineFieldRow>
|
||||||
|
|
||||||
<NewTracePageSearchBar
|
<TracePageSearchBar
|
||||||
trace={trace}
|
trace={trace}
|
||||||
search={search}
|
search={search}
|
||||||
spanFilterMatches={spanFilterMatches}
|
spanFilterMatches={spanFilterMatches}
|
||||||
|
@ -12,12 +12,54 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { render, screen } from '@testing-library/react';
|
import { getByText, render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getTraceName } from '../model/trace-viewer';
|
import { MutableDataFrame } from '@grafana/data';
|
||||||
|
|
||||||
import TracePageHeader, { TracePageHeaderProps } from './TracePageHeader';
|
import { defaultFilters } from '../../useSearch';
|
||||||
|
|
||||||
|
import { TracePageHeader } from './TracePageHeader';
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
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(<TracePageHeader {...defaultProps} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = {
|
export const trace = {
|
||||||
services: [{ name: 'serviceA', numberOfSpans: 1 }],
|
services: [{ name: 'serviceA', numberOfSpans: 1 }],
|
||||||
@ -144,72 +186,3 @@ export const trace = {
|
|||||||
startTime: 1675605056289000,
|
startTime: 1675605056289000,
|
||||||
endTime: 1675605058644515,
|
endTime: 1675605058644515,
|
||||||
};
|
};
|
||||||
|
|
||||||
const setup = (propOverrides?: TracePageHeaderProps) => {
|
|
||||||
const defaultProps = {
|
|
||||||
trace,
|
|
||||||
timeZone: '',
|
|
||||||
viewRange: { time: { current: [10, 20] as [number, number] } },
|
|
||||||
updateNextViewRangeTime: () => {},
|
|
||||||
updateViewRangeTime: () => {},
|
|
||||||
...propOverrides,
|
|
||||||
};
|
|
||||||
|
|
||||||
return render(<TracePageHeader {...defaultProps} />);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 <SpanGraph>', () => {
|
|
||||||
setup();
|
|
||||||
expect(screen.getByText(/Reset Selection/)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows the summary', () => {
|
|
||||||
const { rerender } = setup();
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<TracePageHeader
|
|
||||||
{...({
|
|
||||||
trace: trace,
|
|
||||||
viewRange: { time: { current: [10, 20] } },
|
|
||||||
} as unknown as TracePageHeaderProps)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(screen.queryAllByRole('listitem')).toHaveLength(5);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@ -14,38 +14,158 @@
|
|||||||
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { get as _get, maxBy as _maxBy, values as _values } from 'lodash';
|
import React, { memo, useEffect, useMemo } from 'react';
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data';
|
import { CoreApp, DataFrame, dateTimeFormat, GrafanaTheme2 } from '@grafana/data';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { TimeZone } from '@grafana/schema';
|
||||||
|
import { Badge, BadgeColor, Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { SearchProps } from '../../useSearch';
|
||||||
import ExternalLinks from '../common/ExternalLinks';
|
import ExternalLinks from '../common/ExternalLinks';
|
||||||
import LabeledList from '../common/LabeledList';
|
|
||||||
import TraceName from '../common/TraceName';
|
import TraceName from '../common/TraceName';
|
||||||
import { autoColor, TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../index';
|
|
||||||
import { getTraceLinks } from '../model/link-patterns';
|
import { getTraceLinks } from '../model/link-patterns';
|
||||||
import { getTraceName } from '../model/trace-viewer';
|
import { getHeaderTags, getTraceName } from '../model/trace-viewer';
|
||||||
import { Trace } from '../types';
|
import { Trace } from '../types';
|
||||||
import { uTxMuted } from '../uberUtilityStyles';
|
|
||||||
import { formatDuration } from '../utils/date';
|
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<React.SetStateAction<SearchProps>>;
|
||||||
|
showSpanFilters: boolean;
|
||||||
|
setShowSpanFilters: (isOpen: boolean) => void;
|
||||||
|
showSpanFilterMatchesOnly: boolean;
|
||||||
|
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
||||||
|
setFocusedSpanIdForSearch: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
spanFilterMatches: Set<string> | 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 ? (
|
||||||
|
<span className={styles.TracePageHeaderOverviewItemValue}>
|
||||||
|
{match[1]}
|
||||||
|
<span className={styles.TracePageHeaderOverviewItemValueDetail}>{match[2]}</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
dateStr
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = (
|
||||||
|
<h1 className={cx(styles.title)}>
|
||||||
|
<TraceName traceName={getTraceName(trace.spans)} />
|
||||||
|
<small className={styles.duration}>{formatDuration(trace.duration)}</small>
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<header className={styles.header}>
|
||||||
|
<div className={styles.titleRow}>
|
||||||
|
{links && links.length > 0 && <ExternalLinks links={links} className={styles.TracePageHeaderBack} />}
|
||||||
|
{title}
|
||||||
|
<TracePageActions traceId={trace.traceID} data={data} app={app} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.subtitle}>
|
||||||
|
<span className={styles.timestamp}>{timestamp(trace, timeZone)}</span>
|
||||||
|
<span className={styles.tagMeta}>
|
||||||
|
{method && method.length > 0 && (
|
||||||
|
<Tooltip content={'http.method'} interactive={true}>
|
||||||
|
<span className={styles.tag}>
|
||||||
|
<Badge text={method[0].value} color="blue" />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{status && status.length > 0 && (
|
||||||
|
<Tooltip content={'http.status_code'} interactive={true}>
|
||||||
|
<span className={styles.tag}>
|
||||||
|
<Badge text={status[0].value} color={statusColor} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{url && url.length > 0 && (
|
||||||
|
<Tooltip content={'http.url or http.target or http.path'} interactive={true}>
|
||||||
|
<span className={styles.url}>{url[0].value}</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SpanFilters
|
||||||
|
trace={trace}
|
||||||
|
showSpanFilters={showSpanFilters}
|
||||||
|
setShowSpanFilters={setShowSpanFilters}
|
||||||
|
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
||||||
|
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
||||||
|
search={search}
|
||||||
|
setSearch={setSearch}
|
||||||
|
spanFilterMatches={spanFilterMatches}
|
||||||
|
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
||||||
|
datasourceType={datasourceType}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TracePageHeader.displayName = 'TracePageHeader';
|
||||||
|
|
||||||
|
const getNewStyles = (theme: GrafanaTheme2) => {
|
||||||
return {
|
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`
|
TracePageHeaderBack: css`
|
||||||
label: TracePageHeaderBack;
|
label: TracePageHeaderBack;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -63,21 +183,6 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
border-color: #ccc;
|
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(
|
TracePageHeaderOverviewItemValueDetail: cx(
|
||||||
css`
|
css`
|
||||||
label: TracePageHeaderOverviewItemValueDetail;
|
label: TracePageHeaderOverviewItemValueDetail;
|
||||||
@ -91,107 +196,57 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
color: unset;
|
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`
|
TracePageHeaderTraceId: css`
|
||||||
label: TracePageHeaderTraceId;
|
label: TracePageHeaderTraceId;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`,
|
text-overflow: ellipsis;
|
||||||
titleBorderBottom: css`
|
max-width: 30%;
|
||||||
border-bottom: 1px solid ${autoColor(theme, '#e8e8e8')};
|
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<typeof getStyles>) => {
|
|
||||||
// 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 ? (
|
|
||||||
<span className={styles.TracePageHeaderOverviewItemValue}>
|
|
||||||
{match[1]}
|
|
||||||
<span className={styles.TracePageHeaderOverviewItemValueDetail}>{match[2]}</span>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
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 = (
|
|
||||||
<h1 className={styles.TracePageHeaderTitle}>
|
|
||||||
<TraceName traceName={getTraceName(trace.spans)} />{' '}
|
|
||||||
<small className={cx(styles.TracePageHeaderTraceId, uTxMuted)}>{trace.traceID}</small>
|
|
||||||
</h1>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<header className={styles.TracePageHeader}>
|
|
||||||
<div className={cx(styles.TracePageHeaderTitleRow, styles.titleBorderBottom)}>
|
|
||||||
{links && links.length > 0 && <ExternalLinks links={links} className={styles.TracePageHeaderBack} />}
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
{summaryItems && <LabeledList className={styles.TracePageHeaderOverviewItems} items={summaryItems} />}
|
|
||||||
|
|
||||||
<SpanGraph
|
|
||||||
trace={trace}
|
|
||||||
viewRange={viewRange}
|
|
||||||
updateNextViewRangeTime={updateNextViewRangeTime}
|
|
||||||
updateViewRangeTime={updateViewRangeTime}
|
|
||||||
/>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -12,5 +12,4 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
export { default } from './TracePageHeader';
|
export { TracePageHeader } from './TracePageHeader';
|
||||||
export { NewTracePageHeader } from './NewTracePageHeader';
|
|
||||||
|
@ -39,7 +39,6 @@ let props = {
|
|||||||
findMatchesIDs: null,
|
findMatchesIDs: null,
|
||||||
registerAccessors: jest.fn(),
|
registerAccessors: jest.fn(),
|
||||||
setSpanNameColumnWidth: jest.fn(),
|
setSpanNameColumnWidth: jest.fn(),
|
||||||
setTrace: jest.fn(),
|
|
||||||
spanNameColumnWidth: 0.5,
|
spanNameColumnWidth: 0.5,
|
||||||
trace,
|
trace,
|
||||||
uiFind: 'uiFind',
|
uiFind: 'uiFind',
|
||||||
@ -97,12 +96,4 @@ describe('<VirtualizedTraceViewImpl>', () => {
|
|||||||
})
|
})
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the trace for global state.traceTimeline', () => {
|
|
||||||
const traceID = 'some-other-id';
|
|
||||||
const _trace = { ...trace, traceID };
|
|
||||||
props = { ...props, trace: _trace };
|
|
||||||
render(<VirtualizedTraceView {...props} />);
|
|
||||||
expect(jest.mocked(props.setTrace).mock.calls).toEqual([[_trace, props.uiFind]]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -42,10 +42,6 @@ import {
|
|||||||
ViewedBoundsFunctionType,
|
ViewedBoundsFunctionType,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
type TExtractUiFindFromStateReturn = {
|
|
||||||
uiFind: string | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyles = stylesFactory((props: TVirtualizedTraceViewOwnProps) => {
|
const getStyles = stylesFactory((props: TVirtualizedTraceViewOwnProps) => {
|
||||||
const { topOfViewRefType } = props;
|
const { topOfViewRefType } = props;
|
||||||
const position = topOfViewRefType === TopOfViewRefType.Explore ? 'fixed' : 'absolute';
|
const position = topOfViewRefType === TopOfViewRefType.Explore ? 'fixed' : 'absolute';
|
||||||
@ -102,7 +98,6 @@ type TVirtualizedTraceViewOwnProps = {
|
|||||||
detailTagsToggle: (spanID: string) => void;
|
detailTagsToggle: (spanID: string) => void;
|
||||||
detailToggle: (spanID: string) => void;
|
detailToggle: (spanID: string) => void;
|
||||||
setSpanNameColumnWidth: (width: number) => void;
|
setSpanNameColumnWidth: (width: number) => void;
|
||||||
setTrace: (trace: Trace | TNil, uiFind: string | TNil) => void;
|
|
||||||
hoverIndentGuideIds: Set<string>;
|
hoverIndentGuideIds: Set<string>;
|
||||||
addHoverIndentGuideId: (spanID: string) => void;
|
addHoverIndentGuideId: (spanID: string) => void;
|
||||||
removeHoverIndentGuideId: (spanID: string) => void;
|
removeHoverIndentGuideId: (spanID: string) => void;
|
||||||
@ -119,7 +114,7 @@ type TVirtualizedTraceViewOwnProps = {
|
|||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TExtractUiFindFromStateReturn & TTraceTimeline;
|
export type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TTraceTimeline;
|
||||||
|
|
||||||
// export for tests
|
// export for tests
|
||||||
export const DEFAULT_HEIGHTS = {
|
export const DEFAULT_HEIGHTS = {
|
||||||
@ -206,12 +201,7 @@ const memoizedGetClipping = memoizeOne(getClipping, isEqual);
|
|||||||
// export from tests
|
// export from tests
|
||||||
export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTraceViewProps> {
|
export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTraceViewProps> {
|
||||||
listView: ListView | TNil;
|
listView: ListView | TNil;
|
||||||
|
hasScrolledToSpan = false;
|
||||||
constructor(props: VirtualizedTraceViewProps) {
|
|
||||||
super(props);
|
|
||||||
const { setTrace, trace, uiFind } = props;
|
|
||||||
setTrace(trace, uiFind);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.scrollToSpan(this.props.headerHeight, this.props.focusedSpanId);
|
this.scrollToSpan(this.props.headerHeight, this.props.focusedSpanId);
|
||||||
@ -229,24 +219,23 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Readonly<VirtualizedTraceViewProps>) {
|
componentDidUpdate(prevProps: Readonly<VirtualizedTraceViewProps>) {
|
||||||
const { registerAccessors, trace, headerHeight } = prevProps;
|
const { registerAccessors } = prevProps;
|
||||||
const {
|
const {
|
||||||
registerAccessors: nextRegisterAccessors,
|
registerAccessors: nextRegisterAccessors,
|
||||||
setTrace,
|
headerHeight,
|
||||||
trace: nextTrace,
|
|
||||||
uiFind,
|
|
||||||
focusedSpanId,
|
focusedSpanId,
|
||||||
focusedSpanIdForSearch,
|
focusedSpanIdForSearch,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (trace !== nextTrace) {
|
|
||||||
setTrace(nextTrace, uiFind);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.listView && registerAccessors !== nextRegisterAccessors) {
|
if (this.listView && registerAccessors !== nextRegisterAccessors) {
|
||||||
nextRegisterAccessors(this.getAccessors());
|
nextRegisterAccessors(this.getAccessors());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.hasScrolledToSpan) {
|
||||||
|
this.scrollToSpan(headerHeight, focusedSpanId);
|
||||||
|
this.hasScrolledToSpan = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (focusedSpanId !== prevProps.focusedSpanId) {
|
if (focusedSpanId !== prevProps.focusedSpanId) {
|
||||||
this.scrollToSpan(headerHeight, focusedSpanId);
|
this.scrollToSpan(headerHeight, focusedSpanId);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,6 @@ describe('<TraceTimelineViewer>', () => {
|
|||||||
expandOne: jest.fn(),
|
expandOne: jest.fn(),
|
||||||
registerAccessors: jest.fn(),
|
registerAccessors: jest.fn(),
|
||||||
collapseOne: jest.fn(),
|
collapseOne: jest.fn(),
|
||||||
setTrace: jest.fn(),
|
|
||||||
theme: createTheme(),
|
theme: createTheme(),
|
||||||
history: {
|
history: {
|
||||||
replace: () => {},
|
replace: () => {},
|
||||||
|
@ -31,10 +31,6 @@ import TimelineHeaderRow from './TimelineHeaderRow';
|
|||||||
import VirtualizedTraceView, { TopOfViewRefType } from './VirtualizedTraceView';
|
import VirtualizedTraceView, { TopOfViewRefType } from './VirtualizedTraceView';
|
||||||
import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types';
|
import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types';
|
||||||
|
|
||||||
type TExtractUiFindFromStateReturn = {
|
|
||||||
uiFind: string | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||||
return {
|
return {
|
||||||
TraceTimelineViewer: css`
|
TraceTimelineViewer: css`
|
||||||
@ -71,7 +67,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProps = TExtractUiFindFromStateReturn & {
|
export type TProps = {
|
||||||
registerAccessors: (accessors: Accessors) => void;
|
registerAccessors: (accessors: Accessors) => void;
|
||||||
findMatchesIDs: Set<string> | TNil;
|
findMatchesIDs: Set<string> | TNil;
|
||||||
traceTimeline: TTraceTimeline;
|
traceTimeline: TTraceTimeline;
|
||||||
@ -99,7 +95,6 @@ export type TProps = TExtractUiFindFromStateReturn & {
|
|||||||
detailProcessToggle: (spanID: string) => void;
|
detailProcessToggle: (spanID: string) => void;
|
||||||
detailTagsToggle: (spanID: string) => void;
|
detailTagsToggle: (spanID: string) => void;
|
||||||
detailToggle: (spanID: string) => void;
|
detailToggle: (spanID: string) => void;
|
||||||
setTrace: (trace: Trace | TNil, uiFind: string | TNil) => void;
|
|
||||||
addHoverIndentGuideId: (spanID: string) => void;
|
addHoverIndentGuideId: (spanID: string) => void;
|
||||||
removeHoverIndentGuideId: (spanID: string) => void;
|
removeHoverIndentGuideId: (spanID: string) => void;
|
||||||
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
|
linksGetter: (span: TraceSpan, items: TraceKeyValuePair[], itemIndex: number) => TraceLink[];
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
export { default as TraceTimelineViewer } from './TraceTimelineViewer';
|
export { default as TraceTimelineViewer } from './TraceTimelineViewer';
|
||||||
export { default as TracePageHeader } from './TracePageHeader';
|
export { TracePageHeader } from './TracePageHeader';
|
||||||
export { NewTracePageHeader } from './TracePageHeader';
|
|
||||||
export { default as SpanBarSettings } from './settings/SpanBarSettings';
|
export { default as SpanBarSettings } from './settings/SpanBarSettings';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './TraceTimelineViewer/types';
|
export * from './TraceTimelineViewer/types';
|
||||||
export { default as DetailState } from './TraceTimelineViewer/SpanDetail/DetailState';
|
export { default as DetailState } from './TraceTimelineViewer/SpanDetail/DetailState';
|
||||||
export { default as transformTraceData } from './model/transform-trace-data';
|
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';
|
export * from './Theme';
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
import { defaultFilters, defaultTagFilter } from '../../useSearch';
|
import { defaultFilters, defaultTagFilter } from '../../useSearch';
|
||||||
import { TraceSpan } from '../types';
|
import { TraceSpan } from '../types';
|
||||||
|
|
||||||
import { filterSpans, filterSpansNewTraceViewHeader } from './filter-spans';
|
import { filterSpans } from './filter-spans';
|
||||||
|
|
||||||
describe('filterSpans', () => {
|
describe('filterSpans', () => {
|
||||||
// span0 contains strings that end in 0 or 1
|
// span0 contains strings that end in 0 or 1
|
||||||
@ -122,125 +122,94 @@ describe('filterSpans', () => {
|
|||||||
const spans = [span0, span2] as TraceSpan[];
|
const spans = [span0, span2] as TraceSpan[];
|
||||||
|
|
||||||
it('should return `undefined` if spans is falsy', () => {
|
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
|
// Service / span name
|
||||||
it('should return spans whose serviceName match a filter', () => {
|
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])
|
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', () => {
|
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])
|
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
|
// Durations
|
||||||
it('should return spans whose duration match a filter', () => {
|
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])
|
new Set([spanID0, spanID2])
|
||||||
);
|
);
|
||||||
expect(filterSpansNewTraceViewHeader({ ...defaultFilters, from: '2us' }, spans)).toEqual(
|
expect(filterSpans({ ...defaultFilters, from: '3.05ms', fromOperator: '>=', to: '4ms' }, spans)).toEqual(
|
||||||
new Set([spanID0, spanID2])
|
new Set([spanID0])
|
||||||
);
|
|
||||||
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, to: '4ms' }, spans)).toEqual(new Set([spanID0]));
|
||||||
|
expect(filterSpans({ ...defaultFilters, to: '5ms', toOperator: '<=' }, spans)).toEqual(new Set([spanID0, spanID2]));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
it('should return spans whose tags kv.key match a filter', () => {
|
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(
|
expect(
|
||||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1' }] }, spans)
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }] }, 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
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return spans whose kind, statusCode, statusMessage, libraryName, libraryVersion, traceState, or id match a filter', () => {
|
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(
|
expect(
|
||||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind' }] }, spans)
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', value: 'kind0' }] }, spans)
|
||||||
).toEqual(new Set([spanID0, spanID2]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', value: 'kind0' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', operator: '!=', value: 'kind0' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'kind', operator: '!=', value: 'kind0' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID2]));
|
).toEqual(new Set([spanID2]));
|
||||||
|
expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status' }] }, spans)).toEqual(
|
||||||
|
new Set([spanID0, spanID2])
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status' }] }, spans)
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', value: 'unset' }] }, spans)
|
||||||
).toEqual(new Set([spanID0, spanID2]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', value: 'unset' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', operator: '!=', value: 'unset' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status', operator: '!=', value: 'unset' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID2]));
|
).toEqual(new Set([spanID2]));
|
||||||
|
expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message' }] }, spans)).toEqual(
|
||||||
|
new Set([spanID0, spanID2])
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0, spanID2]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message', value: 'statusMessage0' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'status.message', value: 'statusMessage0' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [{ ...defaultTagFilter, key: 'status.message', operator: '!=', value: 'statusMessage0' }],
|
tags: [{ ...defaultTagFilter, key: 'status.message', operator: '!=', value: 'statusMessage0' }],
|
||||||
@ -248,17 +217,17 @@ describe('filterSpans', () => {
|
|||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID2]));
|
).toEqual(new Set([spanID2]));
|
||||||
|
expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name' }] }, spans)).toEqual(
|
||||||
|
new Set([spanID0, spanID2])
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name' }] }, spans)
|
filterSpans(
|
||||||
).toEqual(new Set([spanID0, spanID2]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name', value: 'libraryName' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.name', value: 'libraryName' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0, spanID2]));
|
).toEqual(new Set([spanID0, spanID2]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [{ ...defaultTagFilter, key: 'library.name', operator: '!=', value: 'libraryName' }],
|
tags: [{ ...defaultTagFilter, key: 'library.name', operator: '!=', value: 'libraryName' }],
|
||||||
@ -266,20 +235,17 @@ describe('filterSpans', () => {
|
|||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([]));
|
).toEqual(new Set([]));
|
||||||
|
expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version' }] }, spans)).toEqual(
|
||||||
|
new Set([spanID0, spanID2])
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0, spanID2]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version', value: 'libraryVersion0' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'library.version', value: 'libraryVersion0' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [{ ...defaultTagFilter, key: 'library.version', operator: '!=', value: 'libraryVersion0' }],
|
tags: [{ ...defaultTagFilter, key: 'library.version', operator: '!=', value: 'libraryVersion0' }],
|
||||||
@ -287,17 +253,17 @@ describe('filterSpans', () => {
|
|||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID2]));
|
).toEqual(new Set([spanID2]));
|
||||||
|
expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state' }] }, spans)).toEqual(
|
||||||
|
new Set([spanID0, spanID2])
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state' }] }, spans)
|
filterSpans(
|
||||||
).toEqual(new Set([spanID0, spanID2]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state', value: 'traceState0' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'trace.state', value: 'traceState0' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [{ ...defaultTagFilter, key: 'trace.state', operator: '!=', value: 'traceState0' }],
|
tags: [{ ...defaultTagFilter, key: 'trace.state', operator: '!=', value: 'traceState0' }],
|
||||||
@ -305,17 +271,14 @@ describe('filterSpans', () => {
|
|||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID2]));
|
).toEqual(new Set([spanID2]));
|
||||||
|
expect(filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id' }] }, spans)).toEqual(
|
||||||
|
new Set([spanID0, spanID2])
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id' }] }, spans)
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', value: 'span-id-0' }] }, spans)
|
||||||
).toEqual(new Set([spanID0, spanID2]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', value: 'span-id-0' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', operator: '!=', value: 'span-id-0' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'id', operator: '!=', value: 'span-id-0' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
@ -323,54 +286,39 @@ describe('filterSpans', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return spans whose process.tags kv.key match a filter', () => {
|
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(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'processTagKey2', operator: '!=' }] }, spans)
|
||||||
{ ...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
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return spans whose logs have a field whose kv.key match a filter', () => {
|
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(
|
expect(
|
||||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey1' }] }, spans)
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey2', operator: '!=' }] }, 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
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return no spans when logs is null', () => {
|
it('should return no spans when logs is null', () => {
|
||||||
const nullSpan = { ...span0, logs: null };
|
const nullSpan = { ...span0, logs: null };
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey1' }] }, [
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'logFieldKey1' }] }, [
|
||||||
nullSpan,
|
nullSpan,
|
||||||
] as unknown as TraceSpan[])
|
] as unknown as TraceSpan[])
|
||||||
).toEqual(new Set([]));
|
).toEqual(new Set([]));
|
||||||
@ -378,13 +326,10 @@ describe('filterSpans', () => {
|
|||||||
|
|
||||||
it("should return spans whose tags' kv.key and kv.value match a filter", () => {
|
it("should return spans whose tags' kv.key and kv.value match a filter", () => {
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }] }, spans)
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '!=' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '!=' }] },
|
||||||
spans
|
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", () => {
|
it("should not return spans whose tags' kv.key match a filter but kv.value/operator does not match", () => {
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '!=' }] }, spans)
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', operator: '!=' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set());
|
).toEqual(new Set());
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans({ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }] }, spans)
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey2', operator: '!=' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '!=' }] },
|
{ ...defaultFilters, tags: [{ ...defaultTagFilter, key: 'tagKey1', value: 'tagValue1', operator: '!=' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
@ -415,7 +354,7 @@ describe('filterSpans', () => {
|
|||||||
it('should return spans with multiple tag filters', () => {
|
it('should return spans with multiple tag filters', () => {
|
||||||
// tags in same span
|
// tags in same span
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -427,7 +366,7 @@ describe('filterSpans', () => {
|
|||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -439,7 +378,7 @@ describe('filterSpans', () => {
|
|||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -453,7 +392,7 @@ describe('filterSpans', () => {
|
|||||||
|
|
||||||
// tags in different spans
|
// tags in different spans
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -465,7 +404,7 @@ describe('filterSpans', () => {
|
|||||||
)
|
)
|
||||||
).toEqual(new Set());
|
).toEqual(new Set());
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -479,7 +418,7 @@ describe('filterSpans', () => {
|
|||||||
|
|
||||||
// values in different spans
|
// values in different spans
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -491,7 +430,7 @@ describe('filterSpans', () => {
|
|||||||
)
|
)
|
||||||
).toEqual(new Set());
|
).toEqual(new Set());
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -503,7 +442,7 @@ describe('filterSpans', () => {
|
|||||||
)
|
)
|
||||||
).toEqual(new Set());
|
).toEqual(new Set());
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -515,7 +454,7 @@ describe('filterSpans', () => {
|
|||||||
)
|
)
|
||||||
).toEqual(new Set());
|
).toEqual(new Set());
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
tags: [
|
tags: [
|
||||||
@ -531,20 +470,14 @@ describe('filterSpans', () => {
|
|||||||
// Multiple
|
// Multiple
|
||||||
it('should return spans with multiple filters', () => {
|
it('should return spans with multiple filters', () => {
|
||||||
// service name + span name
|
// 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(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0' },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName2' },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([]));
|
|
||||||
expect(
|
|
||||||
filterSpansNewTraceViewHeader(
|
|
||||||
{ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName2', spanNameOperator: '!=' },
|
{ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName2', spanNameOperator: '!=' },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
@ -552,51 +485,42 @@ describe('filterSpans', () => {
|
|||||||
|
|
||||||
// service name + span name + duration
|
// service name + span name + duration
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans({ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', from: '2ms' }, spans)
|
||||||
{ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', from: '2ms' },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans({ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', to: '2ms' }, spans)
|
||||||
{ ...defaultFilters, serviceName: 'serviceName0', spanName: 'operationName0', to: '2ms' },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([]));
|
).toEqual(new Set([]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans({ ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms' }, spans)
|
||||||
{ ...defaultFilters, serviceName: 'serviceName2', spanName: 'operationName2', to: '6ms' },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID2]));
|
).toEqual(new Set([spanID2]));
|
||||||
|
|
||||||
// service name + tag key
|
// service name + tag key
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, serviceName: 'serviceName0', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] },
|
{ ...defaultFilters, serviceName: 'serviceName0', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, serviceName: 'serviceName0', tags: [{ ...defaultTagFilter, key: 'tagKey1' }] },
|
{ ...defaultFilters, serviceName: 'serviceName0', tags: [{ ...defaultTagFilter, key: 'tagKey1' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, serviceName: 'serviceName2', tags: [{ ...defaultTagFilter, key: 'tagKey1' }] },
|
{ ...defaultFilters, serviceName: 'serviceName2', tags: [{ ...defaultTagFilter, key: 'tagKey1' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID2]));
|
).toEqual(new Set([spanID2]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, serviceName: 'serviceName2', tags: [{ ...defaultTagFilter, key: 'tagKey2' }] },
|
{ ...defaultFilters, serviceName: 'serviceName2', tags: [{ ...defaultTagFilter, key: 'tagKey2' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
).toEqual(new Set([spanID2]));
|
).toEqual(new Set([spanID2]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
serviceName: 'serviceName0',
|
serviceName: 'serviceName0',
|
||||||
@ -608,13 +532,10 @@ describe('filterSpans', () => {
|
|||||||
|
|
||||||
// duration + tag
|
// duration + tag
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans({ ...defaultFilters, from: '2ms', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] }, spans)
|
||||||
{ ...defaultFilters, from: '2ms', tags: [{ ...defaultTagFilter, key: 'tagKey0' }] },
|
|
||||||
spans
|
|
||||||
)
|
|
||||||
).toEqual(new Set([spanID0]));
|
).toEqual(new Set([spanID0]));
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{ ...defaultFilters, to: '5ms', toOperator: '<=', tags: [{ ...defaultTagFilter, key: 'tagKey2' }] },
|
{ ...defaultFilters, to: '5ms', toOperator: '<=', tags: [{ ...defaultTagFilter, key: 'tagKey2' }] },
|
||||||
spans
|
spans
|
||||||
)
|
)
|
||||||
@ -622,7 +543,7 @@ describe('filterSpans', () => {
|
|||||||
|
|
||||||
// all
|
// all
|
||||||
expect(
|
expect(
|
||||||
filterSpansNewTraceViewHeader(
|
filterSpans(
|
||||||
{
|
{
|
||||||
...defaultFilters,
|
...defaultFilters,
|
||||||
serviceName: 'serviceName0',
|
serviceName: 'serviceName0',
|
||||||
@ -637,96 +558,4 @@ describe('filterSpans', () => {
|
|||||||
)
|
)
|
||||||
).toEqual(new Set([spanID0]));
|
).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([]));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -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
|
// 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
|
// 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) {
|
if (!spans) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -174,61 +174,3 @@ export const convertTimeFilter = (time: string) => {
|
|||||||
}
|
}
|
||||||
return undefined;
|
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<string> = new Set(spans.filter(isSpanAMatch).map((span: TraceSpan) => span.spanID));
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { act, renderHook } from '@testing-library/react';
|
import { act, renderHook } from '@testing-library/react';
|
||||||
|
|
||||||
import { TraceSpan } from './components';
|
import { TraceSpan } from './components';
|
||||||
import { defaultFilters, useSearch, useSearchNewTraceViewHeader } from './useSearch';
|
import { defaultFilters, useSearch } from './useSearch';
|
||||||
|
|
||||||
describe('useSearch', () => {
|
describe('useSearch', () => {
|
||||||
const spans = [
|
const spans = [
|
||||||
@ -28,28 +28,15 @@ describe('useSearch', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
it('returns matching span IDs', async () => {
|
it('returns matching span IDs', async () => {
|
||||||
const { result } = renderHook(() => useSearchNewTraceViewHeader(spans));
|
const { result } = renderHook(() => useSearch(spans));
|
||||||
act(() => result.current.setNewTraceViewHeaderSearch({ ...defaultFilters, serviceName: 'service1' }));
|
act(() => result.current.setSearch({ ...defaultFilters, serviceName: 'service1' }));
|
||||||
expect(result.current.spanFilterMatches?.size).toBe(1);
|
expect(result.current.spanFilterMatches?.size).toBe(1);
|
||||||
expect(result.current.spanFilterMatches?.has('span1')).toBe(true);
|
expect(result.current.spanFilterMatches?.has('span1')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works without spans', async () => {
|
it('works without spans', async () => {
|
||||||
const { result } = renderHook(() => useSearchNewTraceViewHeader());
|
const { result } = renderHook(() => useSearch());
|
||||||
act(() => result.current.setNewTraceViewHeaderSearch({ ...defaultFilters, serviceName: 'service1' }));
|
act(() => result.current.setSearch({ ...defaultFilters, serviceName: 'service1' }));
|
||||||
expect(result.current.spanFilterMatches).toBe(undefined);
|
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { filterSpansNewTraceViewHeader, filterSpans, TraceSpan } from './components';
|
import { filterSpans, TraceSpan } from './components';
|
||||||
|
|
||||||
export interface SearchProps {
|
export interface SearchProps {
|
||||||
serviceName?: string;
|
serviceName?: string;
|
||||||
@ -41,21 +41,11 @@ export const defaultFilters = {
|
|||||||
* Controls the state of search input that highlights spans if they match the search string.
|
* Controls the state of search input that highlights spans if they match the search string.
|
||||||
* @param spans
|
* @param spans
|
||||||
*/
|
*/
|
||||||
export function useSearchNewTraceViewHeader(spans?: TraceSpan[]) {
|
|
||||||
const [newTraceViewHeaderSearch, setNewTraceViewHeaderSearch] = useState<SearchProps>(defaultFilters);
|
|
||||||
const spanFilterMatches: Set<string> | 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[]) {
|
export function useSearch(spans?: TraceSpan[]) {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState<SearchProps>(defaultFilters);
|
||||||
const spanFindMatches: Set<string> | undefined = useMemo(() => {
|
const spanFilterMatches: Set<string> | undefined = useMemo(() => {
|
||||||
return search && spans ? filterSpans(search, spans) : undefined;
|
return spans && filterSpans(search, spans);
|
||||||
}, [search, spans]);
|
}, [search, spans]);
|
||||||
|
|
||||||
return { search, setSearch, spanFindMatches };
|
return { search, setSearch, spanFilterMatches };
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { useMemo, useState, createRef } from 'react';
|
import React, { useMemo, createRef } from 'react';
|
||||||
import { useAsync } from 'react-use';
|
import { useAsync } from 'react-use';
|
||||||
|
|
||||||
import { PanelProps } from '@grafana/data';
|
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 { 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 { 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';
|
import { transformDataFrames } from 'app/features/explore/TraceView/utils/transform';
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
@ -20,13 +18,9 @@ const styles = {
|
|||||||
export const TracesPanel = ({ data }: PanelProps) => {
|
export const TracesPanel = ({ data }: PanelProps) => {
|
||||||
const topOfViewRef = createRef<HTMLDivElement>();
|
const topOfViewRef = createRef<HTMLDivElement>();
|
||||||
const traceProp = useMemo(() => transformDataFrames(data.series[0]), [data.series]);
|
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 () => {
|
const dataSource = useAsync(async () => {
|
||||||
return await getDataSourceSrv().get(data.request?.targets[0].datasource?.uid);
|
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) {
|
if (!data || !data.series.length || !traceProp) {
|
||||||
return (
|
return (
|
||||||
@ -39,27 +33,10 @@ export const TracesPanel = ({ data }: PanelProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<div ref={topOfViewRef}></div>
|
<div ref={topOfViewRef}></div>
|
||||||
{!config.featureToggles.newTraceViewHeader ? (
|
|
||||||
<TracePageSearchBar
|
|
||||||
navigable={true}
|
|
||||||
searchValue={search}
|
|
||||||
setSearch={setSearch}
|
|
||||||
spanFindMatches={spanFindMatches}
|
|
||||||
searchBarSuffix={searchBarSuffix}
|
|
||||||
setSearchBarSuffix={setSearchBarSuffix}
|
|
||||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
|
||||||
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
|
||||||
datasourceType={datasourceType}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<TraceView
|
<TraceView
|
||||||
dataFrames={data.series}
|
dataFrames={data.series}
|
||||||
scrollElementClass={styles.wrapper}
|
scrollElementClass={styles.wrapper}
|
||||||
traceProp={traceProp}
|
traceProp={traceProp}
|
||||||
spanFindMatches={spanFindMatches}
|
|
||||||
search={search}
|
|
||||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
|
||||||
queryResponse={data}
|
queryResponse={data}
|
||||||
datasource={dataSource.value}
|
datasource={dataSource.value}
|
||||||
topOfViewRef={topOfViewRef}
|
topOfViewRef={topOfViewRef}
|
||||||
|
Loading…
Reference in New Issue
Block a user