From c113d3ce72b0f99f91d9f7e37f05b4de3f991af9 Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Tue, 2 Mar 2021 13:59:35 +0100 Subject: [PATCH] Tracing: Specify type of the data frame that is expected for TraceView (#31465) * Use dataframe API for jeager * Move types around * Fix imports * Simplify the data frame type * Add comment * Move the transform to separate file * Fix logs timestamp * Add/update tests for trace view * Fix lint * Add test to compare old and new format rendering * Fix test imports * Update data source tests --- .../src/dataframe/MutableDataFrame.ts | 2 +- packages/grafana-data/src/types/trace.ts | 98 +++-------- .../src/ScrollManager.tsx | 2 +- .../src/TracePageHeader/SpanGraph/index.tsx | 2 +- .../src/TracePageHeader/TracePageHeader.tsx | 2 +- .../TraceTimelineViewer/ReferencesButton.tsx | 2 +- .../src/TraceTimelineViewer/SpanBar.tsx | 2 +- .../src/TraceTimelineViewer/SpanBarRow.tsx | 2 +- .../SpanDetail/AccordianKeyValues.tsx | 2 +- .../SpanDetail/AccordianLogs.tsx | 2 +- .../SpanDetail/AccordianReferences.tsx | 2 +- .../SpanDetail/DetailState.tsx | 2 +- .../SpanDetail/KeyValuesTable.tsx | 2 +- .../TraceTimelineViewer/SpanDetail/index.tsx | 2 +- .../src/TraceTimelineViewer/SpanDetailRow.tsx | 2 +- .../TraceTimelineViewer/SpanTreeOffset.tsx | 2 +- .../VirtualizedTraceView.tsx | 2 +- .../src/TraceTimelineViewer/index.tsx | 2 +- .../src/TraceTimelineViewer/types.tsx | 2 +- .../src/TraceTimelineViewer/utils.tsx | 2 +- .../src/model/link-patterns.tsx | 2 +- .../jaeger-ui-components/src/model/span.tsx | 2 +- .../src/model/trace-viewer.ts | 2 +- .../src/model/transform-trace-data.tsx | 4 +- .../jaeger-ui-components/src/types/index.tsx | 4 +- .../jaeger-ui-components/src/types/trace.ts | 94 +++++++++++ .../src/url/ReferenceLink.tsx | 2 +- .../src/utils/filter-spans.tsx | 2 +- .../src/utils/span-ancestor-ids.tsx | 2 +- public/app/features/explore/Explore.tsx | 10 +- .../explore/TraceView/TraceView.test.tsx | 153 ++++++++++++++++-- .../features/explore/TraceView/TraceView.tsx | 55 ++++++- .../explore/TraceView/createSpanLink.tsx | 3 +- .../TraceView/useChildrenState.test.ts | 2 +- .../explore/TraceView/useChildrenState.ts | 2 +- .../explore/TraceView/useDetailState.test.ts | 2 +- .../explore/TraceView/useDetailState.ts | 2 +- .../explore/TraceView/useSearch.test.ts | 2 +- .../features/explore/TraceView/useSearch.ts | 3 +- .../plugins/datasource/jaeger/QueryField.tsx | 7 +- .../datasource/jaeger/datasource.test.ts | 71 +++++--- .../plugins/datasource/jaeger/datasource.ts | 80 ++++----- .../jaeger/responseTransform.test.ts | 49 ++++++ .../datasource/jaeger/responseTransform.ts | 54 +++++++ .../plugins/datasource/jaeger/testResponse.ts | 49 ++++++ public/app/plugins/datasource/jaeger/types.ts | 49 ++++++ .../datasource/zipkin/utils/testData.ts | 4 +- .../datasource/zipkin/utils/transforms.ts | 24 +-- 48 files changed, 651 insertions(+), 220 deletions(-) create mode 100644 packages/jaeger-ui-components/src/types/trace.ts create mode 100644 public/app/plugins/datasource/jaeger/responseTransform.test.ts create mode 100644 public/app/plugins/datasource/jaeger/responseTransform.ts create mode 100644 public/app/plugins/datasource/jaeger/testResponse.ts create mode 100644 public/app/plugins/datasource/jaeger/types.ts diff --git a/packages/grafana-data/src/dataframe/MutableDataFrame.ts b/packages/grafana-data/src/dataframe/MutableDataFrame.ts index c6700e4e997..81d17a8826e 100644 --- a/packages/grafana-data/src/dataframe/MutableDataFrame.ts +++ b/packages/grafana-data/src/dataframe/MutableDataFrame.ts @@ -191,7 +191,7 @@ export class MutableDataFrame extends FunctionalVector implements Da } /** - * Add all properties of the value as fields on the frame + * Add values from an object to corresponding fields. Similar to appendRow but does not create new fields. */ add(value: T) { // Will add one value for every field diff --git a/packages/grafana-data/src/types/trace.ts b/packages/grafana-data/src/types/trace.ts index 3af2dbdb4a2..d372cfb15eb 100644 --- a/packages/grafana-data/src/types/trace.ts +++ b/packages/grafana-data/src/types/trace.ts @@ -1,94 +1,36 @@ -// 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. - -/** - * All timestamps are in microseconds - */ - -// TODO: Everett Tech Debt: Fix KeyValuePair types -export type TraceKeyValuePair = { +type TraceKeyValuePair = { key: string; - type?: string; value: any; }; -export type TraceLink = { - url: string; - text: string; -}; - -export type TraceLog = { +type TraceLog = { + // Millisecond epoch time timestamp: number; fields: TraceKeyValuePair[]; }; -export type TraceProcess = { - serviceName: string; - tags: TraceKeyValuePair[]; -}; - -export type TraceSpanReference = { - refType: 'CHILD_OF' | 'FOLLOWS_FROM'; - // eslint-disable-next-line no-use-before-define - span?: TraceSpan | null | undefined; - spanID: string; +/** + * This describes the structure of the dataframe that should be returned from a tracing data source to show trace + * in a TraceView component. + */ +export interface TraceSpanRow { traceID: string; -}; - -export type TraceSpanData = { spanID: string; - traceID: string; - processID: string; + parentSpanID?: string; operationName: string; - // Times are in microseconds + serviceName: string; + serviceTags: TraceKeyValuePair[]; + // Millisecond epoch time startTime: number; + // Milliseconds duration: number; - logs: TraceLog[]; + logs?: TraceLog[]; + + // Note: To mark spen as having error add tag error: true tags?: TraceKeyValuePair[]; - references?: TraceSpanReference[]; - warnings?: string[] | null; + warnings?: string[]; stackTraces?: string[]; - flags: number; + + // Specify custom color of the error icon errorIconColor?: string; -}; - -export type TraceSpan = TraceSpanData & { - depth: number; - hasChildren: boolean; - process: TraceProcess; - relativeStartTime: number; - tags: NonNullable; - references: NonNullable; - warnings: NonNullable; - subsidiarilyReferencedBy: TraceSpanReference[]; -}; - -export type TraceData = { - processes: Record; - traceID: string; - warnings?: string[] | null; -}; - -export type TraceViewData = TraceData & { - spans: TraceSpanData[]; -}; - -export type Trace = TraceData & { - duration: number; - endTime: number; - spans: TraceSpan[]; - startTime: number; - traceName: string; - services: Array<{ name: string; numberOfSpans: number }>; -}; +} diff --git a/packages/jaeger-ui-components/src/ScrollManager.tsx b/packages/jaeger-ui-components/src/ScrollManager.tsx index e911521936f..262896f21f6 100644 --- a/packages/jaeger-ui-components/src/ScrollManager.tsx +++ b/packages/jaeger-ui-components/src/ScrollManager.tsx @@ -13,7 +13,7 @@ // limitations under the License. import { TNil } from './types'; -import { TraceSpan, TraceSpanReference, Trace } from '@grafana/data'; +import { TraceSpan, TraceSpanReference, Trace } from './types/trace'; /** * `Accessors` is necessary because `ScrollManager` needs to be created by diff --git a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx index 3874d056fae..0677463300f 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/SpanGraph/index.tsx @@ -19,7 +19,7 @@ import CanvasSpanGraph from './CanvasSpanGraph'; import TickLabels from './TickLabels'; import ViewingLayer from './ViewingLayer'; import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../..'; -import { TraceSpan, Trace } from '@grafana/data'; +import { TraceSpan, Trace } from '../../types/trace'; import { ubPb2, ubPx2, ubRelative } from '../../uberUtilityStyles'; const DEFAULT_HEIGHT = 60; diff --git a/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.tsx b/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.tsx index ffcc1743e4b..aa8c7e89f7b 100644 --- a/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.tsx +++ b/packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.tsx @@ -27,7 +27,7 @@ import LabeledList from '../common/LabeledList'; import TraceName from '../common/TraceName'; import { getTraceName } from '../model/trace-viewer'; import { TNil } from '../types'; -import { Trace } from '@grafana/data'; +import { Trace } from '../types/trace'; import { formatDatetime, formatDuration } from '../utils/date'; import { getTraceLinks } from '../model/link-patterns'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx index 51e41ec0e4a..5a84f7a7e15 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/ReferencesButton.tsx @@ -15,7 +15,7 @@ import React from 'react'; import { css } from 'emotion'; import NewWindowIcon from '../common/NewWindowIcon'; -import { TraceSpanReference } from '@grafana/data'; +import { TraceSpanReference } from '../types/trace'; import { UITooltip, UIDropdown, UIMenuItem, UIMenu, TooltipPlacement } from '../uiElementsContext'; import ReferenceLink from '../url/ReferenceLink'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx index d26634ab62e..a7a2de7d89b 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBar.tsx @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '@grafana/data'; import cx from 'classnames'; import { css } from 'emotion'; import _groupBy from 'lodash/groupBy'; import React from 'react'; import { compose, onlyUpdateForKeys, withProps, withState } from 'recompose'; import { autoColor, createStyle, Theme } from '../Theme'; +import { TraceSpan } from '../types/trace'; import { TNil } from '../types'; import { UIPopover } from '../uiElementsContext'; import AccordianLogs from './SpanDetail/AccordianLogs'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx index efec14744b1..254bb1a1d35 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanBarRow.tsx @@ -28,7 +28,7 @@ import SpanBar from './SpanBar'; import Ticks from './Ticks'; import { TNil } from '../types'; -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '../types/trace'; import { autoColor, createStyle, Theme, withTheme } from '../Theme'; const getStyles = createStyle((theme: Theme) => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx index 8d15ddec78a..b9b1e7f3fc2 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx @@ -21,7 +21,7 @@ import cx from 'classnames'; import * as markers from './AccordianKeyValues.markers'; import KeyValuesTable from './KeyValuesTable'; import { TNil } from '../../types'; -import { TraceKeyValuePair, TraceLink } from '@grafana/data'; +import { TraceKeyValuePair, TraceLink } from '../../types/trace'; import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; import { uAlignIcon, uTxEllipsis } from '../../uberUtilityStyles'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx index d81758f2402..7d16dbe1af8 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx @@ -21,7 +21,7 @@ import { css } from 'emotion'; import AccordianKeyValues from './AccordianKeyValues'; import { formatDuration } from '../utils'; import { TNil } from '../../types'; -import { TraceLog, TraceKeyValuePair, TraceLink } from '@grafana/data'; +import { TraceLog, TraceKeyValuePair, TraceLink } from '../../types/trace'; import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx index 84b77bcbb75..38d87b9b8a8 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx @@ -18,7 +18,7 @@ import cx from 'classnames'; import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right'; -import { TraceSpanReference } from '@grafana/data'; +import { TraceSpanReference } from '../../types/trace'; import ReferenceLink from '../../url/ReferenceLink'; import { createStyle } from '../../Theme'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/DetailState.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/DetailState.tsx index f56153a25c6..22459037132 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/DetailState.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/DetailState.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceLog } from '@grafana/data'; +import { TraceLog } from '../../types/trace'; /** * Which items of a {@link SpanDetail} component are expanded. diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx index ea54b14c505..f99f6945d7b 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx @@ -20,7 +20,7 @@ import cx from 'classnames'; import CopyIcon from '../../common/CopyIcon'; import { TNil } from '../../types'; -import { TraceKeyValuePair, TraceLink } from '@grafana/data'; +import { TraceKeyValuePair, TraceLink } from '../../types/trace'; import { UIDropdown, UIIcon, UIMenu, UIMenuItem } from '../../uiElementsContext'; import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx index bfde2b39915..50c97c6359f 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetail/index.tsx @@ -25,7 +25,7 @@ import CopyIcon from '../../common/CopyIcon'; import LabeledList from '../../common/LabeledList'; import { TNil } from '../../types'; -import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan } from '@grafana/data'; +import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan } from '../../types/trace'; import AccordianReferences from './AccordianReferences'; import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; import { UIDivider } from '../../uiElementsContext'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx index 05656198976..edf8514a15a 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanDetailRow.tsx @@ -21,7 +21,7 @@ import SpanTreeOffset from './SpanTreeOffset'; import TimelineRow from './TimelineRow'; import { autoColor, createStyle, Theme, withTheme } from '../Theme'; -import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink } from '@grafana/data'; +import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink } from '../types/trace'; import { CreateSpanLink } from './types'; const getStyles = createStyle((theme: Theme) => { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.tsx index 7e3f237d4e0..c6e49aaa59c 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/SpanTreeOffset.tsx @@ -19,7 +19,7 @@ import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; import { css } from 'emotion'; import cx from 'classnames'; -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '../types/trace'; import spanAncestorIds from '../utils/span-ancestor-ids'; import { autoColor, createStyle, Theme, withTheme } from '../Theme'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx index 0104e3a4bd8..f88d022a323 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -29,7 +29,7 @@ import { import { Accessors } from '../ScrollManager'; import { getColorByKey } from '../utils/color-generator'; import { TNil } from '../types'; -import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink } from '@grafana/data'; +import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink } from '../types/trace'; import TTraceTimeline from '../types/TTraceTimeline'; import { createStyle, Theme, withTheme } from '../Theme'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx index c2d229004bc..6844f96b7be 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/index.tsx @@ -21,7 +21,7 @@ import { merge as mergeShortcuts } from '../keyboard-shortcuts'; import { Accessors } from '../ScrollManager'; import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types'; import { TNil } from '../types'; -import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink } from '@grafana/data'; +import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink } from '../types/trace'; import TTraceTimeline from '../types/TTraceTimeline'; import { autoColor, createStyle, Theme, withTheme } from '../Theme'; import ExternalLinkContext from '../url/externalLinkContext'; diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/types.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/types.tsx index f7854cd275f..65785ba1252 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/types.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/types.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '../types/trace'; import { TNil } from '../types'; interface TimeCursorUpdate { diff --git a/packages/jaeger-ui-components/src/TraceTimelineViewer/utils.tsx b/packages/jaeger-ui-components/src/TraceTimelineViewer/utils.tsx index 258d797bf1b..77a52c9fe54 100644 --- a/packages/jaeger-ui-components/src/TraceTimelineViewer/utils.tsx +++ b/packages/jaeger-ui-components/src/TraceTimelineViewer/utils.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '../types/trace'; export type ViewedBoundsFunctionType = (start: number, end: number) => { start: number; end: number }; /** diff --git a/packages/jaeger-ui-components/src/model/link-patterns.tsx b/packages/jaeger-ui-components/src/model/link-patterns.tsx index 61da3412348..44a8d3ff6fc 100644 --- a/packages/jaeger-ui-components/src/model/link-patterns.tsx +++ b/packages/jaeger-ui-components/src/model/link-patterns.tsx @@ -17,7 +17,7 @@ import memoize from 'lru-memoize'; import { getConfigValue } from '../utils/config/get-config'; import { getParent } from './span'; import { TNil } from '../types'; -import { TraceSpan, TraceLink, TraceKeyValuePair, Trace } from '@grafana/data'; +import { TraceSpan, TraceLink, TraceKeyValuePair, Trace } from '../types/trace'; const parameterRegExp = /#\{([^{}]*)\}/g; diff --git a/packages/jaeger-ui-components/src/model/span.tsx b/packages/jaeger-ui-components/src/model/span.tsx index 9647583b9cf..1450c357faf 100644 --- a/packages/jaeger-ui-components/src/model/span.tsx +++ b/packages/jaeger-ui-components/src/model/span.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '../types/trace'; /** * Searches the span.references to find 'CHILD_OF' reference type or returns null. diff --git a/packages/jaeger-ui-components/src/model/trace-viewer.ts b/packages/jaeger-ui-components/src/model/trace-viewer.ts index e976df2bed6..69107c80a21 100644 --- a/packages/jaeger-ui-components/src/model/trace-viewer.ts +++ b/packages/jaeger-ui-components/src/model/trace-viewer.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '../types/trace'; export function getTraceName(spans: TraceSpan[]): string { const span = spans.filter((sp) => !sp.references || !sp.references.length)[0]; diff --git a/packages/jaeger-ui-components/src/model/transform-trace-data.tsx b/packages/jaeger-ui-components/src/model/transform-trace-data.tsx index 860ecbfbd67..5e61b4ed2a4 100644 --- a/packages/jaeger-ui-components/src/model/transform-trace-data.tsx +++ b/packages/jaeger-ui-components/src/model/transform-trace-data.tsx @@ -17,7 +17,7 @@ import _isEqual from 'lodash/isEqual'; // @ts-ignore import { getTraceSpanIdsAsTree } from '../selectors/trace'; import { getConfigValue } from '../utils/config/get-config'; -import { TraceKeyValuePair, TraceSpan, Trace, TraceViewData } from '@grafana/data'; +import { TraceKeyValuePair, TraceSpan, Trace, TraceResponse } from '../types/trace'; // @ts-ignore import TreeNode from '../utils/TreeNode'; @@ -71,7 +71,7 @@ export function orderTags(spanTags: TraceKeyValuePair[], topPrefixes?: string[]) * NOTE: Mutates `data` - Transform the HTTP response data into the form the app * generally requires. */ -export default function transformTraceData(data: TraceViewData | undefined): Trace | null { +export default function transformTraceData(data: TraceResponse | undefined): Trace | null { if (!data?.traceID) { return null; } diff --git a/packages/jaeger-ui-components/src/types/index.tsx b/packages/jaeger-ui-components/src/types/index.tsx index f972d613284..ff5809c278e 100644 --- a/packages/jaeger-ui-components/src/types/index.tsx +++ b/packages/jaeger-ui-components/src/types/index.tsx @@ -13,7 +13,9 @@ // limitations under the License. import { ApiError } from './api-error'; -import { Trace } from '@grafana/data'; +import { Trace } from './trace'; + +export { TraceSpan, TraceResponse, Trace, TraceProcess, TraceKeyValuePair, TraceLink } from './trace'; export { default as TTraceTimeline } from './TTraceTimeline'; export { default as TNil } from './TNil'; diff --git a/packages/jaeger-ui-components/src/types/trace.ts b/packages/jaeger-ui-components/src/types/trace.ts new file mode 100644 index 00000000000..a0fb2b3db83 --- /dev/null +++ b/packages/jaeger-ui-components/src/types/trace.ts @@ -0,0 +1,94 @@ +// 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. + +/** + * All timestamps are in microseconds + */ + +// TODO: Everett Tech Debt: Fix KeyValuePair types +export type TraceKeyValuePair = { + key: string; + type?: string; + value: any; +}; + +export type TraceLink = { + url: string; + text: string; +}; + +export type TraceLog = { + timestamp: number; + fields: TraceKeyValuePair[]; +}; + +export type TraceProcess = { + serviceName: string; + tags: TraceKeyValuePair[]; +}; + +export type TraceSpanReference = { + refType: 'CHILD_OF' | 'FOLLOWS_FROM'; + // eslint-disable-next-line no-use-before-define + span?: TraceSpan | null | undefined; + spanID: string; + traceID: string; +}; + +export type TraceSpanData = { + spanID: string; + traceID: string; + processID: string; + operationName: string; + // Times are in microseconds + startTime: number; + duration: number; + logs: TraceLog[]; + tags?: TraceKeyValuePair[]; + references?: TraceSpanReference[]; + warnings?: string[] | null; + stackTraces?: string[]; + flags: number; + errorIconColor?: string; +}; + +export type TraceSpan = TraceSpanData & { + depth: number; + hasChildren: boolean; + process: TraceProcess; + relativeStartTime: number; + tags: NonNullable; + references: NonNullable; + warnings: NonNullable; + subsidiarilyReferencedBy: TraceSpanReference[]; +}; + +export type TraceData = { + processes: Record; + traceID: string; + warnings?: string[] | null; +}; + +export type TraceResponse = TraceData & { + spans: TraceSpanData[]; +}; + +export type Trace = TraceData & { + duration: number; + endTime: number; + spans: TraceSpan[]; + startTime: number; + traceName: string; + services: Array<{ name: string; numberOfSpans: number }>; +}; diff --git a/packages/jaeger-ui-components/src/url/ReferenceLink.tsx b/packages/jaeger-ui-components/src/url/ReferenceLink.tsx index d254167de50..b6149269ff7 100644 --- a/packages/jaeger-ui-components/src/url/ReferenceLink.tsx +++ b/packages/jaeger-ui-components/src/url/ReferenceLink.tsx @@ -13,7 +13,7 @@ // limitations under the License. import React from 'react'; -import { TraceSpanReference } from '@grafana/data'; +import { TraceSpanReference } from '../types/trace'; import ExternalLinkContext from './externalLinkContext'; type ReferenceLinkProps = { diff --git a/packages/jaeger-ui-components/src/utils/filter-spans.tsx b/packages/jaeger-ui-components/src/utils/filter-spans.tsx index 702f3468f72..0eb1615d2fa 100644 --- a/packages/jaeger-ui-components/src/utils/filter-spans.tsx +++ b/packages/jaeger-ui-components/src/utils/filter-spans.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { TraceKeyValuePair, TraceSpan } from '@grafana/data'; +import { TraceKeyValuePair, TraceSpan } from '../types/trace'; import { TNil } from '../types'; export default function filterSpans(textFilter: string, spans: TraceSpan[] | TNil) { diff --git a/packages/jaeger-ui-components/src/utils/span-ancestor-ids.tsx b/packages/jaeger-ui-components/src/utils/span-ancestor-ids.tsx index 6c22418e994..c464e58d623 100644 --- a/packages/jaeger-ui-components/src/utils/span-ancestor-ids.tsx +++ b/packages/jaeger-ui-components/src/utils/span-ancestor-ids.tsx @@ -16,7 +16,7 @@ import _find from 'lodash/find'; import _get from 'lodash/get'; import { TNil } from '../types'; -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '../types/trace'; function getFirstAncestor(span: TraceSpan): TraceSpan | TNil { return _get( diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 6faeb125642..5f42faa061a 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -17,7 +17,6 @@ import { RawTimeRange, TimeZone, LogsModel, - TraceViewData, DataFrame, } from '@grafana/data'; @@ -292,15 +291,8 @@ export class Explore extends React.PureComponent { const dataFrames = queryResponse.series.filter((series) => series.meta?.preferredVisualisationType === 'trace'); return ( - // We expect only one trace at the moment to be in the dataframe // If there is no data (like 404) we show a separate error so no need to show anything here - dataFrames[0] && ( - - ) + dataFrames.length && ); } diff --git a/public/app/features/explore/TraceView/TraceView.test.tsx b/public/app/features/explore/TraceView/TraceView.test.tsx index e9913b56e9d..1484fc2192e 100644 --- a/public/app/features/explore/TraceView/TraceView.test.tsx +++ b/public/app/features/explore/TraceView/TraceView.test.tsx @@ -1,18 +1,28 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { render } from '@testing-library/react'; +import { render, prettyDOM } from '@testing-library/react'; import { TraceView } from './TraceView'; import { TracePageHeader, TraceTimelineViewer } from '@jaegertracing/jaeger-ui-components'; -import { TraceSpanData, TraceData } from '@grafana/data'; import { setDataSourceSrv } from '@grafana/runtime'; import { ExploreId } from 'app/types'; +import { TraceData, TraceSpanData } from '@jaegertracing/jaeger-ui-components/src/types/trace'; +import { MutableDataFrame } from '@grafana/data'; jest.mock('react-redux', () => ({ useSelector: jest.fn(() => undefined), })); function renderTraceView() { - const wrapper = shallow( {}} />); + const wrapper = shallow( {}} />); + return { + timeline: wrapper.find(TraceTimelineViewer), + header: wrapper.find(TracePageHeader), + wrapper, + }; +} + +function renderTraceViewNew() { + const wrapper = shallow( {}} />); return { timeline: wrapper.find(TraceTimelineViewer), header: wrapper.find(TracePageHeader), @@ -35,15 +45,30 @@ describe('TraceView', () => { expect(header).toHaveLength(1); }); + it('renders TraceTimelineViewer with new format', () => { + const { timeline, header } = renderTraceViewNew(); + expect(timeline).toHaveLength(1); + expect(header).toHaveLength(1); + }); + + it('renders renders the same for old and new format', () => { + const { baseElement } = render( + {}} /> + ); + const { baseElement: baseElementOld } = render( + {}} /> + ); + expect(prettyDOM(baseElement)).toEqual(prettyDOM(baseElementOld)); + }); + it('does not render anything on missing trace', () => { // Simulating Explore's access to empty response data - const trace = [][0]; - const { container } = render( {}} />); + const { container } = render( {}} />); expect(container.hasChildNodes()).toBeFalsy(); }); it('toggles detailState', () => { - let { timeline, wrapper } = renderTraceView(); + let { timeline, wrapper } = renderTraceViewNew(); expect(timeline.props().traceTimeline.detailStates.size).toBe(0); timeline.props().detailToggle('1'); @@ -57,7 +82,7 @@ describe('TraceView', () => { }); it('toggles children visibility', () => { - let { timeline, wrapper } = renderTraceView(); + let { timeline, wrapper } = renderTraceViewNew(); expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0); timeline.props().childrenToggle('1'); @@ -71,7 +96,7 @@ describe('TraceView', () => { }); it('toggles adds and removes hover indent guides', () => { - let { timeline, wrapper } = renderTraceView(); + let { timeline, wrapper } = renderTraceViewNew(); expect(timeline.props().traceTimeline.hoverIndentGuideIds.size).toBe(0); timeline.props().addHoverIndentGuideId('1'); @@ -85,7 +110,7 @@ describe('TraceView', () => { }); it('toggles collapses and expands one level of spans', () => { - let { timeline, wrapper } = renderTraceView(); + let { timeline, wrapper } = renderTraceViewNew(); expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0); const spans = timeline.props().trace.spans; @@ -100,7 +125,7 @@ describe('TraceView', () => { }); it('toggles collapses and expands all levels', () => { - let { timeline, wrapper } = renderTraceView(); + let { timeline, wrapper } = renderTraceViewNew(); expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0); const spans = timeline.props().trace.spans; @@ -116,7 +141,7 @@ describe('TraceView', () => { }); it('searches for spans', () => { - let { wrapper, header } = renderTraceView(); + let { wrapper, header } = renderTraceViewNew(); header.props().onSearchValueChange('HTTP POST - api_prom_push'); const timeline = wrapper.find(TraceTimelineViewer); @@ -124,7 +149,7 @@ describe('TraceView', () => { }); it('change viewRange', () => { - let { header, timeline, wrapper } = renderTraceView(); + let { header, timeline, wrapper } = renderTraceViewNew(); const defaultRange = { time: { current: [0, 1] } }; expect(timeline.props().viewRange).toEqual(defaultRange); expect(header.props().viewRange).toEqual(defaultRange); @@ -237,3 +262,107 @@ const response: TraceData & { spans: TraceSpanData[] } = { }, warnings: null as any, }; + +const frameOld = new MutableDataFrame({ + fields: [ + { + name: 'trace', + values: [response], + }, + ], + meta: { + preferredVisualisationType: 'trace', + }, +}); + +const frameNew = new MutableDataFrame({ + fields: [ + { name: 'traceID', values: ['1ed38015486087ca', '1ed38015486087ca', '1ed38015486087ca'] }, + { name: 'spanID', values: ['1ed38015486087ca', '3fb050342773d333', '35118c298fc91f68'] }, + { name: 'parentSpanID', values: [undefined, '1ed38015486087ca', '3fb050342773d333'] }, + { name: 'operationName', values: ['HTTP POST - api_prom_push', '/logproto.Pusher/Push', '/logproto.Pusher/Push'] }, + { name: 'serviceName', values: ['loki-all', 'loki-all', 'loki-all'] }, + { + name: 'serviceTags', + values: [ + [ + { key: 'client-uuid', value: '2a59d08899ef6a8a' }, + { key: 'hostname', value: '0080b530fae3' }, + { key: 'ip', value: '172.18.0.6' }, + { key: 'jaeger.version', value: 'Go-2.20.1' }, + ], + [ + { key: 'client-uuid', value: '2a59d08899ef6a8a' }, + { key: 'hostname', value: '0080b530fae3' }, + { key: 'ip', value: '172.18.0.6' }, + { key: 'jaeger.version', value: 'Go-2.20.1' }, + ], + [ + { key: 'client-uuid', value: '2a59d08899ef6a8a' }, + { key: 'hostname', value: '0080b530fae3' }, + { key: 'ip', value: '172.18.0.6' }, + { key: 'jaeger.version', value: 'Go-2.20.1' }, + ], + ], + }, + { name: 'startTime', values: [1585244579835.187, 1585244579835.341, 1585244579836.04] }, + { name: 'duration', values: [1.098, 0.921, 0.036] }, + { + name: 'logs', + values: [ + [ + { + timestamp: 1585244579835.229, + fields: [{ key: 'event', value: 'util.ParseProtoRequest[start reading]' }], + }, + { + timestamp: 1585244579835.241, + fields: [ + { key: 'event', value: 'util.ParseProtoRequest[decompress]' }, + { key: 'size', value: 315 }, + ], + }, + { + timestamp: 1585244579835.245, + fields: [ + { key: 'event', value: 'util.ParseProtoRequest[unmarshal]' }, + { key: 'size', value: 446 }, + ], + }, + ], + [], + [], + ], + }, + { + name: 'tags', + values: [ + [ + { key: 'sampler.type', value: 'const' }, + { key: 'sampler.param', value: true }, + { key: 'span.kind', value: 'server' }, + { key: 'http.method', value: 'POST' }, + { key: 'http.url', value: '/api/prom/push' }, + { key: 'component', value: 'net/http' }, + { key: 'http.status_code', value: 204 }, + { key: 'internal.span.format', value: 'proto' }, + ], + [ + { key: 'span.kind', value: 'client' }, + { key: 'component', value: 'gRPC' }, + { key: 'internal.span.format', value: 'proto' }, + ], + [ + { key: 'span.kind', value: 'server' }, + { key: 'component', value: 'gRPC' }, + { key: 'internal.span.format', value: 'proto' }, + ], + ], + }, + { name: 'warnings', values: [undefined, undefined] }, + { name: 'stackTraces', values: [undefined, undefined] }, + ], + meta: { + preferredVisualisationType: 'trace', + }, +}); diff --git a/public/app/features/explore/TraceView/TraceView.tsx b/public/app/features/explore/TraceView/TraceView.tsx index e70bcb4c61a..0696ab327ec 100644 --- a/public/app/features/explore/TraceView/TraceView.tsx +++ b/public/app/features/explore/TraceView/TraceView.tsx @@ -3,7 +3,13 @@ import { ThemeOptions, ThemeProvider, ThemeType, + Trace, + TraceKeyValuePair, + TraceLink, TracePageHeader, + TraceProcess, + TraceResponse, + TraceSpan, TraceTimelineViewer, transformTraceData, TTraceTimeline, @@ -16,7 +22,7 @@ import { useChildrenState } from './useChildrenState'; import { useDetailState } from './useDetailState'; import { useHoverIndentGuide } from './useHoverIndentGuide'; import { colors, useTheme } from '@grafana/ui'; -import { TraceViewData, Trace, TraceSpan, TraceKeyValuePair, TraceLink } from '@grafana/data'; +import { DataFrame, DataFrameView, TraceSpanRow } from '@grafana/data'; import { createSpanLinkFactory } from './createSpanLink'; import { useSelector } from 'react-redux'; import { StoreState } from 'app/types'; @@ -25,13 +31,13 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { TraceToLogsData } from 'app/core/components/TraceToLogsSettings'; type Props = { - trace?: TraceViewData; + dataFrames: DataFrame[]; splitOpenFn: SplitOpen; exploreId: ExploreId; }; export function TraceView(props: Props) { - if (!props.trace?.traceID) { + if (!props.dataFrames.length) { return null; } const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState(); @@ -58,7 +64,7 @@ export function TraceView(props: Props) { */ const [slim, setSlim] = useState(false); - const traceProp = useMemo(() => transformTraceData(props.trace), [props.trace]); + const traceProp = useMemo(() => transformDataFrames(props.dataFrames), [props.dataFrames]); const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans); const dataSourceName = useSelector((state: StoreState) => state.explore[props.exploreId]?.datasourceInstance?.name); const traceToLogsOptions = (getDatasourceSrv().getInstanceSettings(dataSourceName)?.jsonData as TraceToLogsData) @@ -167,3 +173,44 @@ export function TraceView(props: Props) { ); } + +function transformDataFrames(frames: DataFrame[]): Trace | null { + // At this point we only show single trace. + const frame = frames[0]; + let data: TraceResponse = + frame.fields.length === 1 + ? // For backward compatibility when we sent whole json response in a single field/value + frame.fields[0].values.get(0) + : transformTraceDataFrame(frame); + return transformTraceData(data); +} + +function transformTraceDataFrame(frame: DataFrame): TraceResponse { + const view = new DataFrameView(frame); + const processes: Record = {}; + for (let i = 0; i < view.length; i++) { + const span = view.get(i); + if (!processes[span.serviceName]) { + processes[span.serviceName] = { + serviceName: span.serviceName, + tags: span.serviceTags, + }; + } + } + + return { + traceID: view.get(0).traceID, + processes, + spans: view.toArray().map((s) => { + return { + ...s, + duration: s.duration * 1000, + startTime: s.startTime * 1000, + processID: s.serviceName, + flags: 0, + references: s.parentSpanID ? [{ refType: 'CHILD_OF', spanID: s.parentSpanID, traceID: s.traceID }] : undefined, + logs: s.logs?.map((l) => ({ ...l, timestamp: l.timestamp * 1000 })) || [], + }; + }), + }; +} diff --git a/public/app/features/explore/TraceView/createSpanLink.tsx b/public/app/features/explore/TraceView/createSpanLink.tsx index 46133f20c35..882f029dc02 100644 --- a/public/app/features/explore/TraceView/createSpanLink.tsx +++ b/public/app/features/explore/TraceView/createSpanLink.tsx @@ -1,4 +1,4 @@ -import { DataLink, dateTime, Field, mapInternalLinkToExplore, TimeRange, TraceSpan } from '@grafana/data'; +import { DataLink, dateTime, Field, mapInternalLinkToExplore, TimeRange } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; import { Icon } from '@grafana/ui'; import { SplitOpen } from 'app/types/explore'; @@ -6,6 +6,7 @@ import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import React from 'react'; import { LokiQuery } from '../../../plugins/datasource/loki/types'; +import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; /** * This is a factory for the link creator. It returns the function mainly so it can return undefined in which case diff --git a/public/app/features/explore/TraceView/useChildrenState.test.ts b/public/app/features/explore/TraceView/useChildrenState.test.ts index bf38ed7a897..4871401d57a 100644 --- a/public/app/features/explore/TraceView/useChildrenState.test.ts +++ b/public/app/features/explore/TraceView/useChildrenState.test.ts @@ -1,6 +1,6 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useChildrenState } from './useChildrenState'; -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; describe('useChildrenState', () => { describe('childrenToggle', () => { diff --git a/public/app/features/explore/TraceView/useChildrenState.ts b/public/app/features/explore/TraceView/useChildrenState.ts index 42c96213e64..431c8946224 100644 --- a/public/app/features/explore/TraceView/useChildrenState.ts +++ b/public/app/features/explore/TraceView/useChildrenState.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react'; -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; /** * Children state means whether spans are collapsed or not. Also provides some functions to manipulate that state. diff --git a/public/app/features/explore/TraceView/useDetailState.test.ts b/public/app/features/explore/TraceView/useDetailState.test.ts index 99d591e1a77..012c50ec5c1 100644 --- a/public/app/features/explore/TraceView/useDetailState.test.ts +++ b/public/app/features/explore/TraceView/useDetailState.test.ts @@ -1,6 +1,6 @@ import { act, renderHook } from '@testing-library/react-hooks'; -import { TraceLog } from '@grafana/data'; import { useDetailState } from './useDetailState'; +import { TraceLog } from '@jaegertracing/jaeger-ui-components/src/types/trace'; describe('useDetailState', () => { it('toggles detail', async () => { diff --git a/public/app/features/explore/TraceView/useDetailState.ts b/public/app/features/explore/TraceView/useDetailState.ts index cb3c291ead2..2c6822b8f70 100644 --- a/public/app/features/explore/TraceView/useDetailState.ts +++ b/public/app/features/explore/TraceView/useDetailState.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; import { DetailState } from '@jaegertracing/jaeger-ui-components'; -import { TraceLog } from '@grafana/data'; +import { TraceLog } from '@jaegertracing/jaeger-ui-components/src/types/trace'; /** * Keeps state of the span detail. This means whether span details are open but also state of each detail subitem diff --git a/public/app/features/explore/TraceView/useSearch.test.ts b/public/app/features/explore/TraceView/useSearch.test.ts index aa60f3872e3..af146a799d3 100644 --- a/public/app/features/explore/TraceView/useSearch.test.ts +++ b/public/app/features/explore/TraceView/useSearch.test.ts @@ -1,6 +1,6 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { useSearch } from './useSearch'; -import { TraceSpan } from '@grafana/data'; +import { TraceSpan } from '@jaegertracing/jaeger-ui-components'; describe('useSearch', () => { it('returns matching span IDs', async () => { diff --git a/public/app/features/explore/TraceView/useSearch.ts b/public/app/features/explore/TraceView/useSearch.ts index 4f71acb2af9..b49315459fb 100644 --- a/public/app/features/explore/TraceView/useSearch.ts +++ b/public/app/features/explore/TraceView/useSearch.ts @@ -1,6 +1,5 @@ import { useMemo, useState } from 'react'; -import { filterSpans } from '@jaegertracing/jaeger-ui-components'; -import { TraceSpan } from '@grafana/data'; +import { filterSpans, TraceSpan } from '@jaegertracing/jaeger-ui-components'; /** * Controls the state of search input that highlights spans if they match the search string. diff --git a/public/app/plugins/datasource/jaeger/QueryField.tsx b/public/app/plugins/datasource/jaeger/QueryField.tsx index f7655762d0a..cb15be5bb21 100644 --- a/public/app/plugins/datasource/jaeger/QueryField.tsx +++ b/public/app/plugins/datasource/jaeger/QueryField.tsx @@ -1,9 +1,10 @@ -import { AppEvents, ExploreQueryFieldProps, TraceData, TraceSpan } from '@grafana/data'; +import { AppEvents, ExploreQueryFieldProps } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { ButtonCascader, CascaderOption } from '@grafana/ui'; import React from 'react'; import { appEvents } from '../../../core/core'; import { JaegerDatasource, JaegerQuery } from './datasource'; +import { Span, TraceResponse } from './types'; const ALL_OPERATIONS_KEY = '__ALL__'; const NO_TRACES_KEY = '__NO_TRACES__'; @@ -13,11 +14,11 @@ interface State { serviceOptions: CascaderOption[]; } -function findRootSpan(spans: TraceSpan[]): TraceSpan | undefined { +function findRootSpan(spans: Span[]): Span | undefined { return spans.find((s) => !s.references?.length); } -function getLabelFromTrace(trace: TraceData & { spans: TraceSpan[] }): string { +function getLabelFromTrace(trace: TraceResponse): string { const rootSpan = findRootSpan(trace.spans); if (rootSpan) { return `${rootSpan.operationName} [${rootSpan.duration / 1000} ms]`; diff --git a/public/app/plugins/datasource/jaeger/datasource.test.ts b/public/app/plugins/datasource/jaeger/datasource.test.ts index 3941ec12174..1d63001c6e0 100644 --- a/public/app/plugins/datasource/jaeger/datasource.test.ts +++ b/public/app/plugins/datasource/jaeger/datasource.test.ts @@ -1,18 +1,60 @@ import { JaegerDatasource, JaegerQuery } from './datasource'; -import { DataQueryRequest, DataSourceInstanceSettings, FieldType, PluginType, dateTime } from '@grafana/data'; +import { + DataQueryRequest, + DataSourceInstanceSettings, + FieldType, + PluginType, + dateTime, + ArrayVector, +} from '@grafana/data'; import { BackendSrv, BackendSrvRequest, getBackendSrv, setBackendSrv } from '@grafana/runtime'; +import { testResponse } from './testResponse'; describe('JaegerDatasource', () => { it('returns trace when queried', async () => { await withMockedBackendSrv(makeBackendSrvMock('12345'), async () => { const ds = new JaegerDatasource(defaultSettings); const response = await ds.query(defaultQuery).toPromise(); - const field = response.data[0].fields[0]; - expect(field.name).toBe('trace'); - expect(field.type).toBe(FieldType.trace); - expect(field.values.get(0)).toEqual({ - traceId: '12345', - }); + expect(response.data[0].fields).toMatchObject( + [ + { name: 'traceID', values: ['3fa414edcef6ad90', '3fa414edcef6ad90'] }, + { name: 'spanID', values: ['3fa414edcef6ad90', '0f5c1808567e4403'] }, + { name: 'parentSpanID', values: [undefined, '3fa414edcef6ad90'] }, + { name: 'operationName', values: ['HTTP GET - api_traces_traceid', '/tempopb.Querier/FindTraceByID'] }, + { name: 'serviceName', values: ['tempo-querier', 'tempo-querier'] }, + { + name: 'serviceTags', + values: [ + [ + { key: 'cluster', type: 'string', value: 'ops-tools1' }, + { key: 'container', type: 'string', value: 'tempo-query' }, + ], + [ + { key: 'cluster', type: 'string', value: 'ops-tools1' }, + { key: 'container', type: 'string', value: 'tempo-query' }, + ], + ], + }, + { name: 'startTime', values: [1605873894680.409, 1605873894680.587] }, + { name: 'duration', values: [1049.141, 1.847] }, + { name: 'logs', values: [[], []] }, + { + name: 'tags', + values: [ + [ + { key: 'sampler.type', type: 'string', value: 'probabilistic' }, + { key: 'sampler.param', type: 'float64', value: 1 }, + ], + [ + { key: 'component', type: 'string', value: 'gRPC' }, + { key: 'span.kind', type: 'string', value: 'client' }, + ], + ], + }, + { name: 'warnings', values: [undefined, undefined] }, + { name: 'stackTraces', values: [undefined, undefined] }, + ].map((f) => ({ ...f, values: new ArrayVector(f.values) })) + ); }); }); @@ -28,13 +70,8 @@ describe('JaegerDatasource', () => { }, ], }; - const response = await ds.query(query).toPromise(); - const field = response.data[0].fields[0]; - expect(field.name).toBe('trace'); - expect(field.type).toBe(FieldType.trace); - expect(field.values.get(0)).toEqual({ - traceId: 'a/b', - }); + await ds.query(query).toPromise(); + // there is expect makeBackendSrvMock checking correct encoding }); }); @@ -158,11 +195,7 @@ function makeBackendSrvMock(traceId: string) { ); return Promise.resolve({ data: { - data: [ - { - traceId, - }, - ], + data: [testResponse], }, }); }, diff --git a/public/app/plugins/datasource/jaeger/datasource.ts b/public/app/plugins/datasource/jaeger/datasource.ts index 34dbfc5095d..a7716a6282c 100644 --- a/public/app/plugins/datasource/jaeger/datasource.ts +++ b/public/app/plugins/datasource/jaeger/datasource.ts @@ -1,20 +1,21 @@ import { - dateMath, - DateTime, - MutableDataFrame, - DataSourceApi, - DataSourceInstanceSettings, + DataQuery, DataQueryRequest, DataQueryResponse, - DataQuery, + DataSourceApi, + DataSourceInstanceSettings, + dateMath, + DateTime, FieldType, + MutableDataFrame, } from '@grafana/data'; -import { getBackendSrv, BackendSrvRequest } from '@grafana/runtime'; -import { Observable, from, of } from 'rxjs'; +import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime'; +import { from, Observable, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { serializeParams } from 'app/core/utils/fetch'; +import { createTraceFrame } from './responseTransform'; export type JaegerQuery = { query: string; @@ -34,46 +35,18 @@ export class JaegerDatasource extends DataSourceApi { // At this moment we expect only one target. In case we somehow change the UI to be able to show multiple // traces at one we need to change this. const id = options.targets[0]?.query; - if (id) { - // TODO: this api is internal, used in jaeger ui. Officially they have gRPC api that should be used. - return this._request(`/api/traces/${encodeURIComponent(id)}`).pipe( - map((response) => { - return { - data: [ - new MutableDataFrame({ - fields: [ - { - name: 'trace', - type: FieldType.trace, - values: response?.data?.data || [], - }, - ], - meta: { - preferredVisualisationType: 'trace', - }, - }), - ], - }; - }) - ); - } else { - return of({ - data: [ - new MutableDataFrame({ - fields: [ - { - name: 'trace', - type: FieldType.trace, - values: [], - }, - ], - meta: { - preferredVisualisationType: 'trace', - }, - }), - ], - }); + if (!id) { + return of({ data: [emptyTraceDataFrame] }); } + + // TODO: this api is internal, used in jaeger ui. Officially they have gRPC api that should be used. + return this._request(`/api/traces/${encodeURIComponent(id)}`).pipe( + map((response) => { + return { + data: [createTraceFrame(response?.data?.data?.[0] || [])], + }; + }) + ); } async testDatasource(): Promise { @@ -146,3 +119,16 @@ function getTime(date: string | DateTime, roundUp: boolean) { } return date.valueOf() * 1000; } + +const emptyTraceDataFrame = new MutableDataFrame({ + fields: [ + { + name: 'trace', + type: FieldType.trace, + values: [], + }, + ], + meta: { + preferredVisualisationType: 'trace', + }, +}); diff --git a/public/app/plugins/datasource/jaeger/responseTransform.test.ts b/public/app/plugins/datasource/jaeger/responseTransform.test.ts new file mode 100644 index 00000000000..83b0540f258 --- /dev/null +++ b/public/app/plugins/datasource/jaeger/responseTransform.test.ts @@ -0,0 +1,49 @@ +import { createTraceFrame } from './responseTransform'; +import { ArrayVector } from '@grafana/data'; +import { testResponse } from './testResponse'; + +describe('createTraceFrame', () => { + it('creates data frame from jaeger response', () => { + const dataFrame = createTraceFrame(testResponse); + expect(dataFrame.fields).toMatchObject( + [ + { name: 'traceID', values: ['3fa414edcef6ad90', '3fa414edcef6ad90'] }, + { name: 'spanID', values: ['3fa414edcef6ad90', '0f5c1808567e4403'] }, + { name: 'parentSpanID', values: [undefined, '3fa414edcef6ad90'] }, + { name: 'operationName', values: ['HTTP GET - api_traces_traceid', '/tempopb.Querier/FindTraceByID'] }, + { name: 'serviceName', values: ['tempo-querier', 'tempo-querier'] }, + { + name: 'serviceTags', + values: [ + [ + { key: 'cluster', type: 'string', value: 'ops-tools1' }, + { key: 'container', type: 'string', value: 'tempo-query' }, + ], + [ + { key: 'cluster', type: 'string', value: 'ops-tools1' }, + { key: 'container', type: 'string', value: 'tempo-query' }, + ], + ], + }, + { name: 'startTime', values: [1605873894680.409, 1605873894680.587] }, + { name: 'duration', values: [1049.141, 1.847] }, + { name: 'logs', values: [[], []] }, + { + name: 'tags', + values: [ + [ + { key: 'sampler.type', type: 'string', value: 'probabilistic' }, + { key: 'sampler.param', type: 'float64', value: 1 }, + ], + [ + { key: 'component', type: 'string', value: 'gRPC' }, + { key: 'span.kind', type: 'string', value: 'client' }, + ], + ], + }, + { name: 'warnings', values: [undefined, undefined] }, + { name: 'stackTraces', values: [undefined, undefined] }, + ].map((f) => ({ ...f, values: new ArrayVector(f.values) })) + ); + }); +}); diff --git a/public/app/plugins/datasource/jaeger/responseTransform.ts b/public/app/plugins/datasource/jaeger/responseTransform.ts new file mode 100644 index 00000000000..17bddd9b0bb --- /dev/null +++ b/public/app/plugins/datasource/jaeger/responseTransform.ts @@ -0,0 +1,54 @@ +import { DataFrame, FieldType, MutableDataFrame, TraceSpanRow } from '@grafana/data'; + +import { Span, TraceProcess, TraceResponse } from './types'; + +export function createTraceFrame(data: TraceResponse): DataFrame { + const spans = data.spans.map((s) => toSpanRow(s, data.processes)); + + const frame = new MutableDataFrame({ + fields: [ + { name: 'traceID', type: FieldType.string }, + { name: 'spanID', type: FieldType.string }, + { name: 'parentSpanID', type: FieldType.string }, + { name: 'operationName', type: FieldType.string }, + { name: 'serviceName', type: FieldType.string }, + { name: 'serviceTags', type: FieldType.other }, + { name: 'startTime', type: FieldType.number }, + { name: 'duration', type: FieldType.number }, + { name: 'logs', type: FieldType.other }, + { name: 'tags', type: FieldType.other }, + { name: 'warnings', type: FieldType.other }, + { name: 'stackTraces', type: FieldType.other }, + ], + meta: { + preferredVisualisationType: 'trace', + }, + }); + + for (const span of spans) { + frame.add(span); + } + + return frame; +} + +function toSpanRow(span: Span, processes: Record): TraceSpanRow { + return { + spanID: span.spanID, + traceID: span.traceID, + parentSpanID: span.references?.find((r) => r.refType === 'CHILD_OF')?.spanID, + operationName: span.operationName, + // from micro to millis + startTime: span.startTime / 1000, + duration: span.duration / 1000, + logs: span.logs.map((l) => ({ + ...l, + timestamp: l.timestamp / 1000, + })), + tags: span.tags, + warnings: span.warnings ?? undefined, + stackTraces: span.stackTraces, + serviceName: processes[span.processID].serviceName, + serviceTags: processes[span.processID].tags, + }; +} diff --git a/public/app/plugins/datasource/jaeger/testResponse.ts b/public/app/plugins/datasource/jaeger/testResponse.ts new file mode 100644 index 00000000000..3a85a2975a1 --- /dev/null +++ b/public/app/plugins/datasource/jaeger/testResponse.ts @@ -0,0 +1,49 @@ +import { TraceResponse } from './types'; + +export const testResponse: TraceResponse = { + traceID: '3fa414edcef6ad90', + spans: [ + { + traceID: '3fa414edcef6ad90', + spanID: '3fa414edcef6ad90', + operationName: 'HTTP GET - api_traces_traceid', + references: [], + startTime: 1605873894680409, + duration: 1049141, + tags: [ + { key: 'sampler.type', type: 'string', value: 'probabilistic' }, + { key: 'sampler.param', type: 'float64', value: 1 }, + ], + logs: [], + processID: 'p1', + warnings: null, + flags: 0, + }, + { + traceID: '3fa414edcef6ad90', + spanID: '0f5c1808567e4403', + operationName: '/tempopb.Querier/FindTraceByID', + references: [{ refType: 'CHILD_OF', traceID: '3fa414edcef6ad90', spanID: '3fa414edcef6ad90' }], + startTime: 1605873894680587, + duration: 1847, + tags: [ + { key: 'component', type: 'string', value: 'gRPC' }, + { key: 'span.kind', type: 'string', value: 'client' }, + ], + logs: [], + processID: 'p1', + warnings: null, + flags: 0, + }, + ], + processes: { + p1: { + serviceName: 'tempo-querier', + tags: [ + { key: 'cluster', type: 'string', value: 'ops-tools1' }, + { key: 'container', type: 'string', value: 'tempo-query' }, + ], + }, + }, + warnings: null, +}; diff --git a/public/app/plugins/datasource/jaeger/types.ts b/public/app/plugins/datasource/jaeger/types.ts new file mode 100644 index 00000000000..caf358a47a2 --- /dev/null +++ b/public/app/plugins/datasource/jaeger/types.ts @@ -0,0 +1,49 @@ +export type TraceKeyValuePair = { + key: string; + type?: string; + value: any; +}; + +export type TraceLink = { + url: string; + text: string; +}; + +export type TraceLog = { + timestamp: number; + fields: TraceKeyValuePair[]; +}; + +export type TraceProcess = { + serviceName: string; + tags: TraceKeyValuePair[]; +}; + +export type TraceSpanReference = { + refType: 'CHILD_OF' | 'FOLLOWS_FROM'; + spanID: string; + traceID: string; +}; + +export type Span = { + spanID: string; + traceID: string; + processID: string; + operationName: string; + // Times are in microseconds + startTime: number; + duration: number; + logs: TraceLog[]; + tags?: TraceKeyValuePair[]; + references?: TraceSpanReference[]; + warnings?: string[] | null; + stackTraces?: string[]; + flags: number; +}; + +export type TraceResponse = { + processes: Record; + traceID: string; + warnings?: string[] | null; + spans: Span[]; +}; diff --git a/public/app/plugins/datasource/zipkin/utils/testData.ts b/public/app/plugins/datasource/zipkin/utils/testData.ts index 0ffbc842db4..8143b937ed6 100644 --- a/public/app/plugins/datasource/zipkin/utils/testData.ts +++ b/public/app/plugins/datasource/zipkin/utils/testData.ts @@ -1,5 +1,5 @@ -import { TraceSpanData, TraceData } from '@grafana/data'; import { ZipkinSpan } from '../types'; +import { TraceResponse } from '../../jaeger/types'; export const zipkinResponse: ZipkinSpan[] = [ { @@ -59,7 +59,7 @@ export const zipkinResponse: ZipkinSpan[] = [ }, ]; -export const jaegerTrace: TraceData & { spans: TraceSpanData[] } = { +export const jaegerTrace: TraceResponse = { processes: { 'service 1': { serviceName: 'service 1', diff --git a/public/app/plugins/datasource/zipkin/utils/transforms.ts b/public/app/plugins/datasource/zipkin/utils/transforms.ts index d0888e66c32..c3df2456513 100644 --- a/public/app/plugins/datasource/zipkin/utils/transforms.ts +++ b/public/app/plugins/datasource/zipkin/utils/transforms.ts @@ -1,11 +1,11 @@ import { identity, keyBy } from 'lodash'; import { ZipkinAnnotation, ZipkinEndpoint, ZipkinSpan } from '../types'; -import { TraceKeyValuePair, TraceLog, TraceProcess, TraceSpanData, TraceData } from '@grafana/data'; +import * as Jaeger from '../../jaeger/types'; /** * Transforms response to format similar to Jaegers as we use Jaeger ui on the frontend. */ -export function transformResponse(zSpans: ZipkinSpan[]): TraceData & { spans: TraceSpanData[] } { +export function transformResponse(zSpans: ZipkinSpan[]): Jaeger.TraceResponse { return { processes: gatherProcesses(zSpans), traceID: zSpans[0].traceId, @@ -14,8 +14,8 @@ export function transformResponse(zSpans: ZipkinSpan[]): TraceData & { spans: Tr }; } -function transformSpan(span: ZipkinSpan): TraceSpanData { - const jaegerSpan: TraceSpanData = { +function transformSpan(span: ZipkinSpan): Jaeger.Span { + const jaegerSpan: Jaeger.Span = { duration: span.duration, // TODO: not sure what this is flags: 1, @@ -62,7 +62,7 @@ function transformSpan(span: ZipkinSpan): TraceSpanData { * Maps annotations as a Jaeger log as that seems to be the closest thing. * See https://zipkin.io/zipkin-api/#/default/get_trace__traceId_ */ -function transformAnnotation(annotation: ZipkinAnnotation): TraceLog { +function transformAnnotation(annotation: ZipkinAnnotation): Jaeger.TraceLog { return { timestamp: annotation.timestamp, fields: [ @@ -75,7 +75,7 @@ function transformAnnotation(annotation: ZipkinAnnotation): TraceLog { }; } -function gatherProcesses(zSpans: ZipkinSpan[]): Record { +function gatherProcesses(zSpans: ZipkinSpan[]): Record { const processes = zSpans.reduce((acc, span) => { if (span.localEndpoint) { acc.push(endpointToProcess(span.localEndpoint)); @@ -84,22 +84,26 @@ function gatherProcesses(zSpans: ZipkinSpan[]): Record { acc.push(endpointToProcess(span.remoteEndpoint)); } return acc; - }, [] as TraceProcess[]); + }, [] as Jaeger.TraceProcess[]); return keyBy(processes, 'serviceName'); } -function endpointToProcess(endpoint: ZipkinEndpoint): TraceProcess { +function endpointToProcess(endpoint: ZipkinEndpoint): Jaeger.TraceProcess { return { serviceName: endpoint.serviceName, tags: [ valueToTag('ipv4', endpoint.ipv4, 'string'), valueToTag('ipv6', endpoint.ipv6, 'string'), valueToTag('port', endpoint.port, 'number'), - ].filter(identity) as TraceKeyValuePair[], + ].filter(identity) as Jaeger.TraceKeyValuePair[], }; } -function valueToTag(key: string, value: string | number | undefined, type: string): TraceKeyValuePair | undefined { +function valueToTag( + key: string, + value: string | number | undefined, + type: string +): Jaeger.TraceKeyValuePair | undefined { if (!value) { return undefined; }