diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianLogs.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianLogs.css index 4af98f1b45..937d42c735 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianLogs.css +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianLogs.css @@ -24,6 +24,7 @@ limitations under the License. color: inherit; display: block; padding: 0.25rem 0.5rem; + padding-left: 0; } .AccordianLogs--header:hover { diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx index 58a5af9a5f..4560eb6e9e 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx @@ -56,7 +56,7 @@ export default function AccordianLogs(props: AccordianLogsProps) { } return ( -
+
{arrow} Logs ({logs.length}) diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianText.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianText.css new file mode 100644 index 0000000000..87c3563dbc --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianText.css @@ -0,0 +1,40 @@ +/* +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. +*/ + +.AccordianText--header { + cursor: pointer; + overflow: hidden; + padding: 0.25em 0.1em; + text-overflow: ellipsis; + white-space: nowrap; +} + +.AccordianText--header:hover { + background: #e8e8e8; +} + +.AccordianText--header.is-empty { + background: none; + cursor: initial; +} + +.AccordianText--header.is-high-contrast:not(.is-empty):hover { + background: #ddd; +} + +.AccordianText--emptyIcon { + color: #aaa; +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianText.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianText.tsx new file mode 100644 index 0000000000..d195e4eee8 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianText.tsx @@ -0,0 +1,75 @@ +// 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 * as React from 'react'; +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 TextList from './TextList'; +import { TNil } from '../../../../types'; + +import './AccordianText.css'; + +type AccordianTextProps = { + className?: string | TNil; + data: string[]; + highContrast?: boolean; + interactive?: boolean; + isOpen: boolean; + label: string; + onToggle?: null | (() => void); + prefixIcon?: null | React.ReactElement; +}; + +export default function AccordianText(props: AccordianTextProps) { + const { className, data, highContrast, interactive, isOpen, label, onToggle, prefixIcon } = props; + const isEmpty = !Array.isArray(data) || !data.length; + const iconCls = cx('u-align-icon', { 'AccordianKeyValues--emptyIcon': isEmpty }); + let arrow: React.ReactNode | null = null; + let headerProps: Object | null = null; + if (interactive) { + arrow = isOpen ? : ; + headerProps = { + 'aria-checked': isOpen, + onClick: isEmpty ? null : onToggle, + role: 'switch', + }; + } + return ( +
+
+ {arrow} + {React.isValidElement(prefixIcon) && prefixIcon} + + {label} + {isOpen || ':'} + +
+ {isOpen && } +
+ ); +} + +AccordianText.defaultProps = { + className: null, + highContrast: false, + interactive: true, + onToggle: null, +}; diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/DetailState.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/DetailState.tsx index 22835c81a0..ed822aea1d 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/DetailState.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/DetailState.tsx @@ -21,11 +21,14 @@ export default class DetailState { isTagsOpen: boolean; isProcessOpen: boolean; logs: { isOpen: boolean; openedItems: Set }; + isWarningsOpen: boolean; constructor(oldState?: DetailState) { - const { isTagsOpen, isProcessOpen, logs }: DetailState | Record = oldState || {}; + const { isTagsOpen, isProcessOpen, isWarningsOpen, logs }: DetailState | Record = + oldState || {}; this.isTagsOpen = Boolean(isTagsOpen); this.isProcessOpen = Boolean(isProcessOpen); + this.isWarningsOpen = Boolean(isWarningsOpen); this.logs = { isOpen: Boolean(logs && logs.isOpen), openedItems: logs && logs.openedItems ? new Set(logs.openedItems) : new Set(), @@ -44,6 +47,12 @@ export default class DetailState { return next; } + toggleWarnings() { + const next = new DetailState(this); + next.isWarningsOpen = !this.isWarningsOpen; + return next; + } + toggleLogs() { const next = new DetailState(this); next.logs.isOpen = !this.logs.isOpen; diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/TextList.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/TextList.css new file mode 100644 index 0000000000..9b03988138 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/TextList.css @@ -0,0 +1,40 @@ +/* +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. +*/ + +.TextListTable { + background: #fff; + border: 1px solid #ddd; + margin-bottom: 0.7em; + max-height: 450px; + overflow: auto; +} + +.TextListTable--body { + vertical-align: baseline; +} + +.TextListTable--row > td { + padding: 0.25rem 0.5rem; +} + +.TextListTable--row:nth-child(2n) > td { + background: #f5f5f5; +} + +.TextListTable--row > td { + padding: 0.25rem 0.5rem; + vertical-align: top; +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/TextList.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/TextList.tsx new file mode 100644 index 0000000000..5bb86f56a2 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/TextList.tsx @@ -0,0 +1,42 @@ +// 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 * as React from 'react'; + +import './TextList.css'; + +type TextListProps = { + data: string[]; +}; + +export default function TextList(props: TextListProps) { + const { data } = props; + return ( +
+ + + {data.map((row, i) => { + return ( + // `i` is necessary in the key because row.key can repeat + // eslint-disable-next-line react/no-array-index-key + + + + ); + })} + +
{row}
+
+ ); +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.css index 7f28ddd27d..e557fd1505 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.css +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.css @@ -44,9 +44,6 @@ limitations under the License. .SpanDetail--warnIcon { background: transparent; color: #ffa500; - font-size: 1.3em; - margin-right: 0.25rem; - padding: 1px; - display: block; - float: right; + font-size: 1em; + margin-right: 0.2rem; } diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.tsx index b0f5d8a880..54974278bf 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanDetail/index.tsx @@ -13,11 +13,12 @@ // limitations under the License. import React from 'react'; -import { Divider, Tooltip } from 'antd'; +import { Divider } from 'antd'; import IoIosWarning from 'react-icons/lib/io/android-warning'; import AccordianKeyValues from './AccordianKeyValues'; import AccordianLogs from './AccordianLogs'; +import AccordianText from './AccordianText'; import DetailState from './DetailState'; import { formatDuration } from '../utils'; import CopyIcon from '../../../common/CopyIcon'; @@ -37,6 +38,7 @@ type SpanDetailProps = { span: Span; tagsToggle: (spanID: string) => void; traceStartTime: number; + warningsToggle: (spanID: string) => void; }; export default function SpanDetail(props: SpanDetailProps) { @@ -49,9 +51,10 @@ export default function SpanDetail(props: SpanDetailProps) { span, tagsToggle, traceStartTime, + warningsToggle, } = props; - const { isTagsOpen, isProcessOpen, logs: logsState } = detailState; - const { operationName, process, duration, relativeStartTime, spanID, logs, tags } = span; + const { isTagsOpen, isProcessOpen, logs: logsState, isWarningsOpen } = detailState; + const { operationName, process, duration, relativeStartTime, spanID, logs, tags, warnings } = span; const overviewItems = [ { key: 'svc', @@ -70,11 +73,12 @@ export default function SpanDetail(props: SpanDetailProps) { }, ]; const deepLinkCopyText = `${window.location.origin}${window.location.pathname}?uiFind=${spanID}`; - const duplicatedTagsIcon = span.hasDuplicatedTags ? ( + /* const duplicatedTagsIcon = span.hasDuplicatedTags ? ( ) : null; +*/ return (
@@ -95,7 +99,6 @@ export default function SpanDetail(props: SpanDetailProps) { linksGetter={linksGetter} isOpen={isTagsOpen} onToggle={() => tagsToggle(spanID)} - rightIcon={duplicatedTagsIcon} /> {process.tags && ( )} + {warnings && + warnings.length > 0 && ( + warningsToggle(spanID)} + prefixIcon={} + /> + )} {spanID} void; logsToggle: (spanID: string) => void; processToggle: (spanID: string) => void; + warningsToggle: (spanID: string) => void; span: Span; tagsToggle: (spanID: string) => void; traceStartTime: number; @@ -55,6 +56,7 @@ export default class SpanDetailRow extends React.PureComponent void; detailLogItemToggle: (spanID: string, log: Log) => void; detailLogsToggle: (spanID: string) => void; + detailWarningsToggle: (spanID: string) => void; detailProcessToggle: (spanID: string) => void; detailTagsToggle: (spanID: string) => void; detailToggle: (spanID: string) => void; @@ -371,6 +372,7 @@ export class VirtualizedTraceViewImpl extends React.Component({ @@ -87,6 +88,7 @@ const fullActions = createActions({ [actionTypes.EXPAND_ALL]: () => ({}), [actionTypes.EXPAND_ONE]: (spans: Span[]) => ({ spans }), [actionTypes.DETAIL_PROCESS_TOGGLE]: (spanID: string) => ({ spanID }), + [actionTypes.DETAIL_WARNINGS_TOGGLE]: (spanID: string) => ({ spanID }), [actionTypes.DETAIL_TAGS_TOGGLE]: (spanID: string) => ({ spanID }), [actionTypes.DETAIL_TOGGLE]: (spanID: string) => ({ spanID }), [actionTypes.FOCUS_UI_FIND_MATCHES]: (trace: Trace, uiFind: string | TNil) => ({ trace, uiFind }), @@ -237,7 +239,7 @@ function detailToggle(state: TTraceTimeline, { spanID }: TSpanIdValue) { } function detailSubsectionToggle( - subSection: 'tags' | 'process' | 'logs', + subSection: 'tags' | 'process' | 'logs' | 'warnings', state: TTraceTimeline, { spanID }: TSpanIdValue ) { @@ -250,6 +252,8 @@ function detailSubsectionToggle( detailState = old.toggleTags(); } else if (subSection === 'process') { detailState = old.toggleProcess(); + } else if (subSection === 'warnings') { + detailState = old.toggleWarnings(); } else { detailState = old.toggleLogs(); } @@ -261,6 +265,7 @@ function detailSubsectionToggle( const detailTagsToggle = detailSubsectionToggle.bind(null, 'tags'); const detailProcessToggle = detailSubsectionToggle.bind(null, 'process'); const detailLogsToggle = detailSubsectionToggle.bind(null, 'logs'); +const detailWarningsToggle = detailSubsectionToggle.bind(null, 'warnings'); function detailLogItemToggle(state: TTraceTimeline, { spanID, logItem }: TSpanIdLogValue) { const old = state.detailStates.get(spanID); @@ -299,6 +304,7 @@ export default handleActions( [actionTypes.DETAIL_LOGS_TOGGLE]: guardReducer(detailLogsToggle), [actionTypes.DETAIL_LOG_ITEM_TOGGLE]: guardReducer(detailLogItemToggle), [actionTypes.DETAIL_PROCESS_TOGGLE]: guardReducer(detailProcessToggle), + [actionTypes.DETAIL_WARNINGS_TOGGLE]: guardReducer(detailWarningsToggle), [actionTypes.DETAIL_TAGS_TOGGLE]: guardReducer(detailTagsToggle), [actionTypes.DETAIL_TOGGLE]: guardReducer(detailToggle), [actionTypes.EXPAND_ALL]: guardReducer(expandAll), diff --git a/packages/jaeger-ui/src/model/transform-trace-data.tsx b/packages/jaeger-ui/src/model/transform-trace-data.tsx index df4965e5c5..694ae1900a 100644 --- a/packages/jaeger-ui/src/model/transform-trace-data.tsx +++ b/packages/jaeger-ui/src/model/transform-trace-data.tsx @@ -26,20 +26,17 @@ type SpanWithProcess = SpanData & { process: Process }; }; */ function deduplicateTags(spanTags: Array) { - let hasDuplicated: boolean = false; + const warningsHash: { [key: string]: string } = {}; const tags: Array = spanTags.reduce>((uniqueTags, tag) => { - if ( - !uniqueTags.some(t => { - return t.key === tag.key && t.value === tag.value; - }) - ) { + if (!uniqueTags.some(t => t.key === tag.key && t.value === tag.value)) { uniqueTags.push(tag); } else { - hasDuplicated = true; + warningsHash[`${tag.key}:${tag.value}`] = `Duplicate tag "${tag.key}:${tag.value}"`; } return uniqueTags; }, []); - return { tags, hasDuplicated }; + const warnings = Object.values(warningsHash); + return { tags, warnings }; } /** @@ -117,7 +114,7 @@ export default function transformTraceData(data: TraceData & { spans: SpanWithPr span.hasChildren = node.children.length > 0; const tagsInfo = deduplicateTags(span.tags); span.tags = tagsInfo.tags; - span.hasDuplicatedTags = tagsInfo.hasDuplicated; + span.warnings = tagsInfo.warnings; span.references.forEach(ref => { const refSpan = spanMap.get(ref.spanID) as Span; if (refSpan) { diff --git a/packages/jaeger-ui/src/types/trace.tsx b/packages/jaeger-ui/src/types/trace.tsx index 63fdcfce18..1f56c2b62e 100644 --- a/packages/jaeger-ui/src/types/trace.tsx +++ b/packages/jaeger-ui/src/types/trace.tsx @@ -54,6 +54,7 @@ export type SpanData = { logs: Array; tags: Array; references: Array; + warnings: Array; }; export type Span = SpanData & { @@ -61,7 +62,6 @@ export type Span = SpanData & { hasChildren: boolean; process: Process; relativeStartTime: number; - hasDuplicatedTags: boolean; }; export type TraceData = {