// 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 _get from 'lodash/get'; import _maxBy from 'lodash/maxBy'; import _values from 'lodash/values'; import MdKeyboardArrowRight from 'react-icons/lib/md/keyboard-arrow-right'; import { css } from 'emotion'; import cx from 'classnames'; import SpanGraph from './SpanGraph'; import TracePageSearchBar from './TracePageSearchBar'; import { autoColor, Theme, TUpdateViewRangeTimeFunction, useTheme, ViewRange, ViewRangeTimeUpdate } from '..'; import LabeledList from '../common/LabeledList'; import TraceName from '../common/TraceName'; import { getTraceName } from '../model/trace-viewer'; import { TNil } from '../types'; import { Trace } from '../types/trace'; import { formatDatetime, formatDuration } from '../utils/date'; import { getTraceLinks } from '../model/link-patterns'; import ExternalLinks from '../common/ExternalLinks'; import { createStyle } from '../Theme'; import { uTxMuted } from '../uberUtilityStyles'; const getStyles = createStyle((theme: Theme) => { return { TracePageHeader: css` label: TracePageHeader; & > :first-child { border-bottom: 1px solid ${autoColor(theme, '#e8e8e8')}; } & > :nth-child(2) { background-color: ${autoColor(theme, '#eee')}; border-bottom: 1px solid ${autoColor(theme, '#e4e4e4')}; } & > :last-child { border-bottom: 1px solid ${autoColor(theme, '#ccc')}; } `, TracePageHeaderTitleRow: css` label: TracePageHeaderTitleRow; align-items: center; display: flex; `, TracePageHeaderBack: css` label: TracePageHeaderBack; align-items: center; align-self: stretch; background-color: #fafafa; border-bottom: 1px solid #ddd; border-right: 1px solid #ddd; color: inherit; display: flex; font-size: 1.4rem; padding: 0 1rem; margin-bottom: -1px; &:hover { background-color: #f0f0f0; border-color: #ccc; } `, TracePageHeaderTitleLink: css` label: TracePageHeaderTitleLink; align-items: center; display: flex; flex: 1; &:hover * { text-decoration: underline; } &:hover > *, &:hover small { text-decoration: none; } `, TracePageHeaderDetailToggle: css` label: TracePageHeaderDetailToggle; font-size: 2.5rem; transition: transform 0.07s ease-out; `, TracePageHeaderDetailToggleExpanded: css` label: TracePageHeaderDetailToggleExpanded; transform: rotate(90deg); `, TracePageHeaderTitle: css` label: TracePageHeaderTitle; color: inherit; flex: 1; font-size: 1.7em; line-height: 1em; margin: 0 0 0 0.5em; padding: 0.5em 0; `, TracePageHeaderTitleCollapsible: css` label: TracePageHeaderTitleCollapsible; margin-left: 0; `, TracePageHeaderOverviewItems: css` label: TracePageHeaderOverviewItems; border-bottom: 1px solid #e4e4e4; padding: 0.25rem 0.5rem !important; `, TracePageHeaderOverviewItemValueDetail: cx( css` label: TracePageHeaderOverviewItemValueDetail; color: #aaa; `, 'trace-item-value-detail' ), TracePageHeaderOverviewItemValue: css` label: TracePageHeaderOverviewItemValue; &:hover > .trace-item-value-detail { color: unset; } `, TracePageHeaderArchiveIcon: css` label: TracePageHeaderArchiveIcon; font-size: 1.78em; margin-right: 0.15em; `, TracePageHeaderTraceId: css` label: TracePageHeaderTraceId; white-space: nowrap; `, }; }); type TracePageHeaderEmbedProps = { canCollapse: boolean; clearSearch: () => void; focusUiFindMatches: () => void; hideMap: boolean; hideSummary: boolean; nextResult: () => void; onSlimViewClicked: () => void; onTraceGraphViewClicked: () => void; prevResult: () => void; resultCount: number; slimView: boolean; textFilter: string | TNil; trace: Trace; traceGraphView: boolean; updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; updateViewRangeTime: TUpdateViewRangeTimeFunction; viewRange: ViewRange; searchValue: string; onSearchValueChange: (value: string) => void; hideSearchButtons?: boolean; }; export const HEADER_ITEMS = [ { key: 'timestamp', label: 'Trace Start', renderer(trace: Trace, styles: ReturnType) { const dateStr = formatDatetime(trace.startTime); const match = dateStr.match(/^(.+)(:\d\d\.\d+)$/); return match ? ( {match[1]} {match[2]} ) : ( dateStr ); }, }, { 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: TracePageHeaderEmbedProps) { const { canCollapse, clearSearch, focusUiFindMatches, hideMap, hideSummary, nextResult, onSlimViewClicked, prevResult, resultCount, slimView, textFilter, trace, traceGraphView, updateNextViewRangeTime, updateViewRangeTime, viewRange, searchValue, onSearchValueChange, hideSearchButtons, } = props; const styles = getStyles(useTheme()); const links = React.useMemo(() => { if (!trace) { return []; } return getTraceLinks(trace); }, [trace]); if (!trace) { return null; } const summaryItems = !hideSummary && !slimView && HEADER_ITEMS.map((item) => { const { renderer, ...rest } = item; return { ...rest, value: renderer(trace, styles) }; }); const title = (

{' '} {trace.traceID}

); return (
{links && links.length > 0 && } {canCollapse ? ( {title} ) : ( title )}
{summaryItems && } {!hideMap && !slimView && ( )}
); }