diff --git a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.tsx b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.tsx index ac66389f83..5fa19795e2 100644 --- a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.tsx @@ -23,6 +23,7 @@ import { Link } from 'react-router-dom'; import { Helmet } from 'react-helmet'; import AltViewOptions from './AltViewOptions'; import KeyboardShortcutsHelp from './KeyboardShortcutsHelp'; +import TracePageSettings from './TracePageSettings'; import SpanGraph from './SpanGraph'; import TracePageSearchBar from './TracePageSearchBar'; import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate, ETraceViewType } from '../types'; @@ -58,6 +59,9 @@ type TracePageHeaderEmbedProps = { showViewOptions: boolean; slimView: boolean; textFilter: string | TNil; + availableTagKeys: string[]; + selectedMarkerColorKey: string[]; + setMarkerColorKey: (tagKey: string[]) => void; toSearch: string | null; trace: Trace; viewType: ETraceViewType; @@ -127,6 +131,9 @@ export function TracePageHeaderFn(props: TracePageHeaderEmbedProps & { forwarded disableJsonView, slimView, textFilter, + availableTagKeys, + selectedMarkerColorKey, + setMarkerColorKey, toSearch, trace, viewType, @@ -190,6 +197,12 @@ export function TracePageHeaderFn(props: TracePageHeaderEmbedProps & { forwarded textFilter={textFilter} navigable={viewType === ETraceViewType.TraceTimelineViewer} /> + {showShortcutsHelp && } {showViewOptions && ( void; + className: string; +}; + +type State = { + visible: boolean; +}; + +type DataRecord = { + key: string; + kbds: React.JSX.Element; + description: string; +}; + +function renderTagKey(tag: string) { + if (tag === '') { + return (Empty string); + } + + return {tag}; +} + +function getMarkerColorKeyFormItem(props: Props) { + const items = []; + const callbacks: { [key: string]: boolean } = {}; // whether the key should be added or removed + + for (const key of props.selectedMarkerColorKey) { + callbacks[key] = false; + items.push({ + key: key, + label: ( + + {renderTagKey(key)} + + + ), + }); + } + + for (const key of props.availableTagKeys) { + if (callbacks.hasOwnProperty(key)) { + continue; + } + + callbacks[key] = true; + items.push({ + key: key, + label: renderTagKey(key), + }); + } + + const toggle = key => { + console.log(callbacks); + console.log(key, callbacks[key]); + let newList: string[]; + if (callbacks[key]) { + newList = props.selectedMarkerColorKey.concat([key]); + newList.sort(); + } else { + newList = props.selectedMarkerColorKey.filter(item => item != key); + } + console.log(newList); + props.onMarkerColorKeyChange(newList); + }; + + return ( + + <> + {props.selectedMarkerColorKey.map(key => ( + } + onClose={e => { + e.preventDefault(); + toggle(key); + }} + > + {renderTagKey(key)} + + ))} + + toggle(key) }}> + + + + + + ); +} + +export default class TracePageSettings extends React.PureComponent { + state = { + visible: false, + }; + + onCtaClicked = () => { + track(); + this.setState({ visible: true }); + }; + + onCloserClicked = () => this.setState({ visible: false }); + + render() { + const { className } = this.props; + return ( + + + +
{getMarkerColorKeyFormItem(this.props)}
+
+
+ ); + } +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.css index adc08aa338..c02dd399c6 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.css +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.css @@ -101,7 +101,6 @@ limitations under the License. } .SpanBar--logMarker { - background-color: rgba(0, 0, 0, 0.5); cursor: pointer; height: 60%; min-width: 1px; diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.tsx index 7f9447ef8a..8c01484e94 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.tsx @@ -21,6 +21,7 @@ import AccordianLogs from './SpanDetail/AccordianLogs'; import { ViewedBoundsFunctionType } from './utils'; import { TNil } from '../../../types'; import { Span, criticalPathSection } from '../../../types/trace'; +import { ColorGenerator, ColorGeneratorView, Rgb } from '../../../utils/color-generator'; import './SpanBar.css'; @@ -44,6 +45,8 @@ type TCommonProps = { span: Span; longLabel: string; shortLabel: string; + colorGenerator: ColorGenerator; + markerColorKey: string[]; }; function toPercent(value: number) { @@ -68,6 +71,8 @@ function SpanBar(props: TCommonProps) { span, shortLabel, longLabel, + colorGenerator, + markerColorKey, } = props; // group logs based on timestamps const logGroups = _groupBy(span.logs, log => { @@ -124,7 +129,15 @@ function SpanBar(props: TCommonProps) {
))} @@ -171,4 +184,33 @@ function SpanBar(props: TCommonProps) { ); } +function darkenRgb(rgb: Rgb): Rgb { + return rgb.map(comp => Math.floor((comp * 2) / 3)); +} + +function logMarkerImage(colorView: ColorGeneratorView, tags: string[], logs: Log[]): string { + if (tags.length === 0) { + return '#000000'; + } + + const tagSet = {}; + for (const tag of tags) { + tagSet[tag] = true; + } + + const sections = []; + + for (const log of logs) { + const values = log.fields.filter(({ key }) => tagSet.hasOwnProperty(key)).map(({ value }) => value); + const color = values.length === 0 ? '#000000' : colorView.getColorByKey(JSON.stringify(values)); + + const start = Math.floor((sections.length * 100) / logs.length); + const end = Math.floor(((sections.length + 1) * 100) / logs.length); + + sections.push(`${color} ${start}%`, `${color} ${end}%`); + } + + return `linear-gradient(${sections.join(', ')})`; +} + export default SpanBar; diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx index 310d6aec57..45909d9dfd 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx @@ -22,6 +22,7 @@ import SpanBar from './SpanBar'; import Ticks from './Ticks'; import { TNil } from '../../../types'; +import { ColorGenerator } from '../../../utils/color-generator'; import { criticalPathSection, Span } from '../../../types/trace'; import './SpanBarRow.css'; @@ -55,6 +56,8 @@ type SpanBarRowProps = { showErrorIcon: boolean; getViewedBounds: ViewedBoundsFunctionType; traceStartTime: number; + colorGenerator: ColorGenerator; + markerColorKey: string[]; span: Span; focusSpan: (spanID: string) => void; }; @@ -98,6 +101,8 @@ export default class SpanBarRow extends React.PureComponent { traceStartTime, span, focusSpan, + colorGenerator, + markerColorKey, } = this.props; const { duration, @@ -211,6 +216,8 @@ export default class SpanBarRow extends React.PureComponent { hintSide={hintSide} traceStartTime={traceStartTime} span={span} + colorGenerator={colorGenerator} + markerColorKey={markerColorKey} /> diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx index c6dafc7bb0..c094777017 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -62,6 +62,7 @@ type TVirtualizedTraceViewOwnProps = { registerAccessors: (accesors: Accessors) => void; trace: Trace; criticalPath: criticalPathSection[]; + markerColorKey: string[]; }; type TDispatchProps = { @@ -386,6 +387,7 @@ export class VirtualizedTraceViewImpl extends React.Component
); diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.tsx index 597f77046d..24bd5c8242 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.tsx @@ -45,6 +45,7 @@ type TProps = TDispatchProps & { updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; updateViewRangeTime: TUpdateViewRangeTimeFunction; viewRange: IViewRange; + markerColorKey: string[]; }; const NUM_TICKS = 5; diff --git a/packages/jaeger-ui/src/components/TracePage/index.tsx b/packages/jaeger-ui/src/components/TracePage/index.tsx index 1a887e713d..02d306a34b 100644 --- a/packages/jaeger-ui/src/components/TracePage/index.tsx +++ b/packages/jaeger-ui/src/components/TracePage/index.tsx @@ -19,6 +19,7 @@ import _clamp from 'lodash/clamp'; import _get from 'lodash/get'; import _mapValues from 'lodash/mapValues'; import _memoize from 'lodash/memoize'; +import _keys from 'lodash/keys'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; @@ -97,6 +98,7 @@ type TState = { slimView: boolean; viewType: ETraceViewType; viewRange: IViewRange; + selectedMarkerColorKey: string | null; }; // export for tests @@ -104,6 +106,8 @@ export const VIEW_MIN_RANGE = 0.01; const VIEW_CHANGE_BASE = 0.005; const VIEW_CHANGE_FAST = 0.05; +const MARKER_COLOR_TAG_STORAGE_KEY = 'logMarkerColorKey'; + // export for tests export const shortcutConfig: { [name: string]: [number, number] } = { panLeft: [-VIEW_CHANGE_BASE, -VIEW_CHANGE_BASE], @@ -140,6 +144,12 @@ export class TracePageImpl extends React.PureComponent { constructor(props: TProps) { super(props); const { embedded, trace } = props; + + const selectedMarkerColorKeyJson = window.localStorage.getItem(MARKER_COLOR_TAG_STORAGE_KEY); + const selectedMarkerColorKey: string[] = selectedMarkerColorKeyJson + ? JSON.parse(selectedMarkerColorKeyJson).tagKeys + : []; + this.state = { headerHeight: null, slimView: Boolean(embedded && embedded.timeline.collapseTitle), @@ -149,6 +159,7 @@ export class TracePageImpl extends React.PureComponent { current: [0, 1], }, }, + selectedMarkerColorKey, }; this._headerElm = null; this._filterSpans = _memoize( @@ -324,6 +335,11 @@ export class TracePageImpl extends React.PureComponent { this._scrollManager.scrollToPrevVisibleSpan(); }; + setMarkerColorKey = (tagKeys: string[]) => { + window.localStorage.setItem(MARKER_COLOR_TAG_STORAGE_KEY, JSON.stringify({ tagKeys })); + this.setState({ selectedMarkerColorKey: tagKeys }); + }; + render() { const { archiveEnabled, @@ -338,7 +354,7 @@ export class TracePageImpl extends React.PureComponent { traceGraphConfig, location: { state: locationState }, } = this.props; - const { slimView, viewType, headerHeight, viewRange } = this.state; + const { slimView, viewType, headerHeight, viewRange, selectedMarkerColorKey } = this.state; if (!trace || trace.state === fetchedState.LOADING) { return ; } @@ -391,6 +407,9 @@ export class TracePageImpl extends React.PureComponent { trace: data, updateNextViewRangeTime: this.updateNextViewRangeTime, updateViewRangeTime: this.updateViewRangeTime, + availableTagKeys: trace ? collectTagKeys(trace.data) : [], + selectedMarkerColorKey, + setMarkerColorKey: this.setMarkerColorKey, }; let view; @@ -406,6 +425,7 @@ export class TracePageImpl extends React.PureComponent { updateNextViewRangeTime={this.updateNextViewRangeTime} updateViewRangeTime={this.updateViewRangeTime} viewRange={viewRange} + markerColorKey={selectedMarkerColorKey} /> ); } else if (ETraceViewType.TraceGraph === viewType && headerHeight) { @@ -440,6 +460,22 @@ export class TracePageImpl extends React.PureComponent { } } +function collectTagKeys(trace: Trace): string[] { + const keys = {}; + + for (const span of trace.spans) { + for (const log of span.logs) { + for (const { key } of log.fields) { + keys[key] = true; + } + } + } + + const keysArray = _keys(keys); + keysArray.sort(); + return keysArray; +} + // export for tests export function mapStateToProps(state: ReduxState, ownProps: TOwnProps): TReduxProps { const { id } = ownProps.params; diff --git a/packages/jaeger-ui/src/utils/color-generator.tsx b/packages/jaeger-ui/src/utils/color-generator.tsx index 7c915f4be3..af871c8c86 100644 --- a/packages/jaeger-ui/src/utils/color-generator.tsx +++ b/packages/jaeger-ui/src/utils/color-generator.tsx @@ -36,7 +36,7 @@ const COLORS_HEX = [ ]; // TS needs the precise return type -function strToRgb(s: string): [number, number, number] { +function strToRgb(s: string): Rgb { if (s.length !== 7) { return [0, 0, 0]; } @@ -46,9 +46,13 @@ function strToRgb(s: string): [number, number, number] { return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)]; } +function rgbToStr(rgb: Rgb): string { + return `rgb(${rgb.map(c => c.toString()).join(', ')})`; +} + export class ColorGenerator { colorsHex: string[]; - colorsRgb: [number, number, number][]; + colorsRgb: Rgb[]; cache: Map; currentIdx: number; @@ -84,7 +88,7 @@ export class ColorGenerator { * it with a color if the key is not recognized. * @return {number[]} An array of three ints [0, 255] representing a color. */ - getRgbColorByKey(key: string): [number, number, number] { + getRgbColorByKey(key: string): Rgb { const i = this._getColorIndex(key); return this.colorsRgb[i]; } @@ -95,4 +99,31 @@ export class ColorGenerator { } } +export type Rgb = [number, number, number]; +export type RgbMapper = (Rgb) => Rgb; + +/** + * An alternative view of a ColorGenerator with mapped colors. + * Shares the same underlying cache. + */ +export class ColorGeneratorView { + base: ColorGenerator; + mapper: RgbMapper; + + constructor(base: ColorGenerator, mapRgb: RgbMapper) { + this.base = base; + this.mapper = mapRgb; + } + + getRgbColorByKey(key: string): Rgb { + const rgb = this.base.getRgbColorByKey(key); + return this.mapper(rgb); + } + + getColorByKey(key: string): string { + const rgb = this.getRgbColorByKey(key); + return rgbToStr(rgb); + } +} + export default new ColorGenerator();