mirror of https://github.com/grafana/grafana.git
				
				
				
			Tracing: Dark theme styling for TraceView (#23406)
* Add integration with Jeager Add Jaeger datasource and modify derived fields in loki to allow for opening a trace in Jager in separate split. Modifies build so that this branch docker images are pushed to docker hub Add a traceui dir with docker-compose and provision files for demoing.:wq * Enable docker logger plugin to send logs to loki * Add placeholder zipkin datasource * Fixed rebase issues, added enhanceDataFrame to non-legacy code path * Trace selector for jaeger query field * Fix logs default mode for Loki * Fix loading jaeger query field services on split * Updated grafana image in traceui/compose file * Fix prettier error * Hide behind feature flag, clean up unused code. * Fix tests * Fix tests * Cleanup code and review feedback * Remove traceui directory * Remove circle build changes * Fix feature toggles object * Fix merge issues * Add trace ui in Explore * WIP * WIP * WIP * Make jaeger datasource return trace data instead of link * Allow js in jest tests * Return data from Jaeger datasource * Take yarn.lock from master * Fix missing component * Update yarn lock * Fix some ts and lint errors * Fix merge * Fix type errors * Make tests pass again * Add tests * Fix es5 compatibility * Add header with minimap * Fix sizing issue due to column resizer handle * Fix issues with sizing, search functionality, duplicate react, tests * Refactor TraceView component, fix tests * Fix type errors * Add dark theme styling * Add tests for hooks * More color changes * Fix tests to deal with additional theme wrappers. Co-authored-by: David Kaltschmidt <david.kaltschmidt@gmail.com>
This commit is contained in:
		
							parent
							
								
									754dfdfa87
								
							
						
					
					
						commit
						cf1ebd5a3d
					
				|  | @ -36,6 +36,7 @@ | |||
|     "moment": "^2.18.1", | ||||
|     "react-icons": "2.2.7", | ||||
|     "recompose": "^0.25.0", | ||||
|     "tinycolor2": "1.4.1", | ||||
|     "tween-functions": "^1.2.0" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -12,20 +12,32 @@ | |||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import React from 'react'; | ||||
| import React, { useContext } from 'react'; | ||||
| import hoistNonReactStatics from 'hoist-non-react-statics'; | ||||
| import memoizeOne from 'memoize-one'; | ||||
| import tinycolor from 'tinycolor2'; | ||||
| 
 | ||||
| export type ThemeOptions = Partial<Theme>; | ||||
| 
 | ||||
| export enum ThemeType { | ||||
|   Dark, | ||||
|   Light, | ||||
| } | ||||
| 
 | ||||
| export type Theme = { | ||||
|   type: ThemeType; | ||||
|   borderStyle: string; | ||||
| }; | ||||
| 
 | ||||
| export const defaultTheme: Theme = { | ||||
|   type: ThemeType.Light, | ||||
|   borderStyle: '1px solid #bbb', | ||||
| }; | ||||
| 
 | ||||
| export function isLight(theme: Theme) { | ||||
|   return theme.type === ThemeType.Light; | ||||
| } | ||||
| 
 | ||||
| const ThemeContext = React.createContext<ThemeOptions | undefined>(undefined); | ||||
| ThemeContext.displayName = 'ThemeContext'; | ||||
| 
 | ||||
|  | @ -60,14 +72,16 @@ export const withTheme = <Props extends { theme: Theme }, Statics extends {} = { | |||
|   let WithTheme: React.ComponentType<Omit<Props, 'theme'>> = props => { | ||||
|     return ( | ||||
|       <ThemeConsumer> | ||||
|         {(theme: Theme) => ( | ||||
|         {(theme: Theme) => { | ||||
|           return ( | ||||
|             <Component | ||||
|               {...({ | ||||
|                 ...props, | ||||
|                 theme, | ||||
|               } as Props & { theme: Theme })} | ||||
|             /> | ||||
|         )} | ||||
|           ); | ||||
|         }} | ||||
|       </ThemeConsumer> | ||||
|     ); | ||||
|   }; | ||||
|  | @ -81,6 +95,51 @@ export const withTheme = <Props extends { theme: Theme }, Statics extends {} = { | |||
|   return WithTheme as WrappedWithThemeComponent<Props>; | ||||
| }; | ||||
| 
 | ||||
| export function useTheme(): Theme { | ||||
|   const theme = useContext(ThemeContext); | ||||
|   return { | ||||
|     ...defaultTheme, | ||||
|     ...theme, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export const createStyle = <Fn extends (this: any, ...newArgs: any[]) => ReturnType<Fn>>(fn: Fn) => { | ||||
|   return memoizeOne(fn); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Tries to get a dark variant color. Either by simply inverting the luminosity and darkening or lightening the color | ||||
|  * a bit, or if base is provided, tries 2 variants of lighter and darker colors and checks which is more readable with | ||||
|  * the base. | ||||
|  * @param theme | ||||
|  * @param hex | ||||
|  * @param base | ||||
|  */ | ||||
| export function autoColor(theme: Theme, hex: string, base?: string) { | ||||
|   if (isLight(theme)) { | ||||
|     return hex; | ||||
|   } else { | ||||
|     if (base) { | ||||
|       const color = tinycolor(hex); | ||||
|       return tinycolor | ||||
|         .mostReadable( | ||||
|           base, | ||||
|           [ | ||||
|             color.clone().lighten(25), | ||||
|             color.clone().lighten(10), | ||||
|             color, | ||||
|             color.clone().darken(10), | ||||
|             color.clone().darken(25), | ||||
|           ], | ||||
|           { | ||||
|             includeFallbackColors: false, | ||||
|           } | ||||
|         ) | ||||
|         .toHex8String(); | ||||
|     } | ||||
|     const color = tinycolor(hex).toHsl(); | ||||
|     color.l = 1 - color.l; | ||||
|     const newColor = tinycolor(color); | ||||
|     return newColor.isLight() ? newColor.darken(5).toHex8String() : newColor.lighten(5).toHex8String(); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -15,12 +15,13 @@ | |||
| import React from 'react'; | ||||
| import { shallow } from 'enzyme'; | ||||
| 
 | ||||
| import CanvasSpanGraph from './CanvasSpanGraph'; | ||||
| import { UnthemedCanvasSpanGraph } from './CanvasSpanGraph'; | ||||
| import { defaultTheme } from '../../Theme'; | ||||
| 
 | ||||
| describe('<CanvasSpanGraph>', () => { | ||||
|   it('renders without exploding', () => { | ||||
|     const items = [{ valueWidth: 1, valueOffset: 1, serviceName: 'service-name-0' }]; | ||||
|     const wrapper = shallow(<CanvasSpanGraph items={[]} valueWidth={4000} />); | ||||
|     const wrapper = shallow(<UnthemedCanvasSpanGraph items={[]} valueWidth={4000} theme={defaultTheme} />); | ||||
|     expect(wrapper).toBeDefined(); | ||||
|     wrapper.instance()._setCanvasRef({ | ||||
|       getContext: () => ({ | ||||
|  |  | |||
|  | @ -16,16 +16,16 @@ import * as React from 'react'; | |||
| import { css } from 'emotion'; | ||||
| 
 | ||||
| import renderIntoCanvas from './render-into-canvas'; | ||||
| import colorGenerator from '../../utils/color-generator'; | ||||
| import { getRgbColorByKey } from '../../utils/color-generator'; | ||||
| import { TNil } from '../../types'; | ||||
| 
 | ||||
| import { createStyle } from '../../Theme'; | ||||
| import { autoColor, createStyle, Theme, withTheme } from '../../Theme'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     CanvasSpanGraph: css` | ||||
|       label: CanvasSpanGraph; | ||||
|       background: #fafafa; | ||||
|       background: ${autoColor(theme, '#fafafa')}; | ||||
|       height: 60px; | ||||
|       position: absolute; | ||||
|       width: 100%; | ||||
|  | @ -36,11 +36,10 @@ const getStyles = createStyle(() => { | |||
| type CanvasSpanGraphProps = { | ||||
|   items: Array<{ valueWidth: number; valueOffset: number; serviceName: string }>; | ||||
|   valueWidth: number; | ||||
|   theme: Theme; | ||||
| }; | ||||
| 
 | ||||
| const getColor = (hex: string) => colorGenerator.getRgbColorByKey(hex); | ||||
| 
 | ||||
| export default class CanvasSpanGraph extends React.PureComponent<CanvasSpanGraphProps> { | ||||
| export class UnthemedCanvasSpanGraph extends React.PureComponent<CanvasSpanGraphProps> { | ||||
|   _canvasElm: HTMLCanvasElement | TNil; | ||||
| 
 | ||||
|   constructor(props: CanvasSpanGraphProps) { | ||||
|  | @ -48,6 +47,8 @@ export default class CanvasSpanGraph extends React.PureComponent<CanvasSpanGraph | |||
|     this._canvasElm = undefined; | ||||
|   } | ||||
| 
 | ||||
|   getColor = (key: string) => getRgbColorByKey(key, this.props.theme); | ||||
| 
 | ||||
|   componentDidMount() { | ||||
|     this._draw(); | ||||
|   } | ||||
|  | @ -63,11 +64,13 @@ export default class CanvasSpanGraph extends React.PureComponent<CanvasSpanGraph | |||
|   _draw() { | ||||
|     if (this._canvasElm) { | ||||
|       const { valueWidth: totalValueWidth, items } = this.props; | ||||
|       renderIntoCanvas(this._canvasElm, items, totalValueWidth, getColor); | ||||
|       renderIntoCanvas(this._canvasElm, items, totalValueWidth, this.getColor, autoColor(this.props.theme, '#fff')); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     return <canvas className={getStyles().CanvasSpanGraph} ref={this._setCanvasRef} />; | ||||
|     return <canvas className={getStyles(this.props.theme).CanvasSpanGraph} ref={this._setCanvasRef} />; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withTheme(UnthemedCanvasSpanGraph); | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import Scrubber from './Scrubber'; | |||
| import ViewingLayer, { dragTypes, getStyles } from './ViewingLayer'; | ||||
| import { EUpdateTypes } from '../../utils/DraggableManager'; | ||||
| import { polyfill as polyfillAnimationFrame } from '../../utils/test/requestAnimationFrame'; | ||||
| import { defaultTheme } from '../../Theme'; | ||||
| 
 | ||||
| function getViewRange(viewStart, viewEnd) { | ||||
|   return { | ||||
|  | @ -43,13 +44,19 @@ describe('<SpanGraph>', () => { | |||
|       updateViewRangeTime: jest.fn(), | ||||
|       viewRange: getViewRange(0, 1), | ||||
|     }; | ||||
|     wrapper = shallow(<ViewingLayer {...props} />); | ||||
|     wrapper = shallow(<ViewingLayer {...props} />) | ||||
|       .dive() | ||||
|       .dive() | ||||
|       .dive(); | ||||
|   }); | ||||
| 
 | ||||
|   describe('_getDraggingBounds()', () => { | ||||
|     beforeEach(() => { | ||||
|       props = { ...props, viewRange: getViewRange(0.1, 0.9) }; | ||||
|       wrapper = shallow(<ViewingLayer {...props} />); | ||||
|       wrapper = shallow(<ViewingLayer {...props} />) | ||||
|         .dive() | ||||
|         .dive() | ||||
|         .dive(); | ||||
|       wrapper.instance()._setRoot({ | ||||
|         getBoundingClientRect() { | ||||
|           return { left: 10, width: 100 }; | ||||
|  | @ -122,7 +129,10 @@ describe('<SpanGraph>', () => { | |||
|           const anchor = 0.1; | ||||
|           const time = { ...props.viewRange.time, reframe: { anchor } }; | ||||
|           props = { ...props, viewRange: { time } }; | ||||
|           wrapper = shallow(<ViewingLayer {...props} />); | ||||
|           wrapper = shallow(<ViewingLayer {...props} />) | ||||
|             .dive() | ||||
|             .dive() | ||||
|             .dive(); | ||||
|           wrapper.instance()._handleReframeDragUpdate({ value }); | ||||
|           const calls = props.updateNextViewRangeTime.mock.calls; | ||||
|           expect(calls).toEqual([[{ reframe: { anchor, shift: value } }]]); | ||||
|  | @ -149,7 +159,10 @@ describe('<SpanGraph>', () => { | |||
|           const anchor = 0.6; | ||||
|           const time = { ...props.viewRange.time, reframe: { anchor } }; | ||||
|           props = { ...props, viewRange: { time } }; | ||||
|           wrapper = shallow(<ViewingLayer {...props} />); | ||||
|           wrapper = shallow(<ViewingLayer {...props} />) | ||||
|             .dive() | ||||
|             .dive() | ||||
|             .dive(); | ||||
|           wrapper.instance()._handleReframeDragEnd({ manager, value }); | ||||
| 
 | ||||
|           expect(manager.resetBounds.mock.calls).toEqual([[]]); | ||||
|  | @ -162,7 +175,10 @@ describe('<SpanGraph>', () => { | |||
|           const anchor = 0.4; | ||||
|           const time = { ...props.viewRange.time, reframe: { anchor } }; | ||||
|           props = { ...props, viewRange: { time } }; | ||||
|           wrapper = shallow(<ViewingLayer {...props} />); | ||||
|           wrapper = shallow(<ViewingLayer {...props} />) | ||||
|             .dive() | ||||
|             .dive() | ||||
|             .dive(); | ||||
|           wrapper.instance()._handleReframeDragEnd({ manager, value }); | ||||
| 
 | ||||
|           expect(manager.resetBounds.mock.calls).toEqual([[]]); | ||||
|  | @ -258,28 +274,28 @@ describe('<SpanGraph>', () => { | |||
| 
 | ||||
|     describe('.ViewingLayer--resetZoom', () => { | ||||
|       it('should not render .ViewingLayer--resetZoom if props.viewRange.time.current = [0,1]', () => { | ||||
|         expect(wrapper.find(`.${getStyles().ViewingLayerResetZoom}`).length).toBe(0); | ||||
|         expect(wrapper.find(`.${getStyles(defaultTheme).ViewingLayerResetZoom}`).length).toBe(0); | ||||
|         wrapper.setProps({ viewRange: { time: { current: [0, 1] } } }); | ||||
|         expect(wrapper.find(`.${getStyles().ViewingLayerResetZoom}`).length).toBe(0); | ||||
|         expect(wrapper.find(`.${getStyles(defaultTheme).ViewingLayerResetZoom}`).length).toBe(0); | ||||
|       }); | ||||
| 
 | ||||
|       it('should render ViewingLayer--resetZoom if props.viewRange.time.current[0] !== 0', () => { | ||||
|         // If the test fails on the following expect statement, this may be a false negative
 | ||||
|         expect(wrapper.find(`.${getStyles().ViewingLayerResetZoom}`).length).toBe(0); | ||||
|         expect(wrapper.find(`.${getStyles(defaultTheme).ViewingLayerResetZoom}`).length).toBe(0); | ||||
|         wrapper.setProps({ viewRange: { time: { current: [0.1, 1] } } }); | ||||
|         expect(wrapper.find(`.${getStyles().ViewingLayerResetZoom}`).length).toBe(1); | ||||
|         expect(wrapper.find(`.${getStyles(defaultTheme).ViewingLayerResetZoom}`).length).toBe(1); | ||||
|       }); | ||||
| 
 | ||||
|       it('should render ViewingLayer--resetZoom if props.viewRange.time.current[1] !== 1', () => { | ||||
|         // If the test fails on the following expect statement, this may be a false negative
 | ||||
|         expect(wrapper.find(`.${getStyles().ViewingLayerResetZoom}`).length).toBe(0); | ||||
|         expect(wrapper.find(`.${getStyles(defaultTheme).ViewingLayerResetZoom}`).length).toBe(0); | ||||
|         wrapper.setProps({ viewRange: { time: { current: [0, 0.9] } } }); | ||||
|         expect(wrapper.find(`.${getStyles().ViewingLayerResetZoom}`).length).toBe(1); | ||||
|         expect(wrapper.find(`.${getStyles(defaultTheme).ViewingLayerResetZoom}`).length).toBe(1); | ||||
|       }); | ||||
| 
 | ||||
|       it('should call props.updateViewRangeTime when clicked', () => { | ||||
|         wrapper.setProps({ viewRange: { time: { current: [0.1, 0.9] } } }); | ||||
|         const resetZoomButton = wrapper.find(`.${getStyles().ViewingLayerResetZoom}`); | ||||
|         const resetZoomButton = wrapper.find(`.${getStyles(defaultTheme).ViewingLayerResetZoom}`); | ||||
|         // If the test fails on the following expect statement, this may be a false negative caused
 | ||||
|         // by a regression to rendering.
 | ||||
|         expect(resetZoomButton.length).toBe(1); | ||||
|  | @ -296,9 +312,12 @@ describe('<SpanGraph>', () => { | |||
| 
 | ||||
|   it('renders a filtering box if leftBound exists', () => { | ||||
|     const _props = { ...props, viewRange: getViewRange(0.2, 1) }; | ||||
|     wrapper = shallow(<ViewingLayer {..._props} />); | ||||
|     wrapper = shallow(<ViewingLayer {..._props} />) | ||||
|       .dive() | ||||
|       .dive() | ||||
|       .dive(); | ||||
| 
 | ||||
|     const leftBox = wrapper.find(`.${getStyles().ViewingLayerInactive}`); | ||||
|     const leftBox = wrapper.find(`.${getStyles(defaultTheme).ViewingLayerInactive}`); | ||||
|     expect(leftBox.length).toBe(1); | ||||
|     const width = Number(leftBox.prop('width').slice(0, -1)); | ||||
|     const x = leftBox.prop('x'); | ||||
|  | @ -308,9 +327,12 @@ describe('<SpanGraph>', () => { | |||
| 
 | ||||
|   it('renders a filtering box if rightBound exists', () => { | ||||
|     const _props = { ...props, viewRange: getViewRange(0, 0.8) }; | ||||
|     wrapper = shallow(<ViewingLayer {..._props} />); | ||||
|     wrapper = shallow(<ViewingLayer {..._props} />) | ||||
|       .dive() | ||||
|       .dive() | ||||
|       .dive(); | ||||
| 
 | ||||
|     const rightBox = wrapper.find(`.${getStyles().ViewingLayerInactive}`); | ||||
|     const rightBox = wrapper.find(`.${getStyles(defaultTheme).ViewingLayerInactive}`); | ||||
|     expect(rightBox.length).toBe(1); | ||||
|     const width = Number(rightBox.prop('width').slice(0, -1)); | ||||
|     const x = Number(rightBox.prop('x').slice(0, -1)); | ||||
|  |  | |||
|  | @ -19,12 +19,13 @@ import { css } from 'emotion'; | |||
| import GraphTicks from './GraphTicks'; | ||||
| import Scrubber from './Scrubber'; | ||||
| import { TUpdateViewRangeTimeFunction, UIButton, ViewRange, ViewRangeTimeUpdate } from '../..'; | ||||
| import { withTheme, Theme, autoColor } from '../../Theme'; | ||||
| import { TNil } from '../..'; | ||||
| import DraggableManager, { DraggableBounds, DraggingUpdate, EUpdateTypes } from '../../utils/DraggableManager'; | ||||
| 
 | ||||
| import { createStyle } from '../../Theme'; | ||||
| 
 | ||||
| export const getStyles = createStyle(() => { | ||||
| export const getStyles = createStyle((theme: Theme) => { | ||||
|   // Need this cause emotion will merge emotion generated classes into single className if used with cx from emotion
 | ||||
|   // package and the selector won't work
 | ||||
|   const ViewingLayerResetZoomHoverClassName = 'JaegerUiComponents__ViewingLayerResetZoomHoverClassName'; | ||||
|  | @ -48,7 +49,7 @@ export const getStyles = createStyle(() => { | |||
|     `,
 | ||||
|     ViewingLayerGraph: css` | ||||
|       label: ViewingLayerGraph; | ||||
|       border: 1px solid #999; | ||||
|       border: 1px solid ${autoColor(theme, '#999')}; | ||||
|       /* need !important here to overcome something from semantic UI */ | ||||
|       overflow: visible !important; | ||||
|       position: relative; | ||||
|  | @ -57,11 +58,11 @@ export const getStyles = createStyle(() => { | |||
|     `,
 | ||||
|     ViewingLayerInactive: css` | ||||
|       label: ViewingLayerInactive; | ||||
|       fill: rgba(214, 214, 214, 0.5); | ||||
|       fill: ${autoColor(theme, 'rgba(214, 214, 214, 0.5)')}; | ||||
|     `,
 | ||||
|     ViewingLayerCursorGuide: css` | ||||
|       label: ViewingLayerCursorGuide; | ||||
|       stroke: #f44; | ||||
|       stroke: ${autoColor(theme, '#f44')}; | ||||
|       stroke-width: 1; | ||||
|     `,
 | ||||
|     ViewingLayerDraggedShift: css` | ||||
|  | @ -70,7 +71,7 @@ export const getStyles = createStyle(() => { | |||
|     `,
 | ||||
|     ViewingLayerDrag: css` | ||||
|       label: ViewingLayerDrag; | ||||
|       fill: #44f; | ||||
|       fill: ${autoColor(theme, '#44f')}; | ||||
|     `,
 | ||||
|     ViewingLayerFullOverlay: css` | ||||
|       label: ViewingLayerFullOverlay; | ||||
|  | @ -93,6 +94,7 @@ type ViewingLayerProps = { | |||
|   updateViewRangeTime: TUpdateViewRangeTimeFunction; | ||||
|   updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; | ||||
|   viewRange: ViewRange; | ||||
|   theme: Theme; | ||||
| }; | ||||
| 
 | ||||
| type ViewingLayerState = { | ||||
|  | @ -140,7 +142,7 @@ function getNextViewLayout(start: number, position: number) { | |||
|  * `ViewingLayer` is rendered on top of the Canvas rendering of the minimap and | ||||
|  * handles showing the current view range and handles mouse UX for modifying it. | ||||
|  */ | ||||
| export default class ViewingLayer extends React.PureComponent<ViewingLayerProps, ViewingLayerState> { | ||||
| export class UnthemedViewingLayer extends React.PureComponent<ViewingLayerProps, ViewingLayerState> { | ||||
|   state: ViewingLayerState; | ||||
| 
 | ||||
|   _root: Element | TNil; | ||||
|  | @ -298,7 +300,7 @@ export default class ViewingLayer extends React.PureComponent<ViewingLayerProps, | |||
|    * @returns React.Node[] | ||||
|    */ | ||||
|   _getMarkers(from: number, to: number) { | ||||
|     const styles = getStyles(); | ||||
|     const styles = getStyles(this.props.theme); | ||||
|     const layout = getNextViewLayout(from, to); | ||||
|     return [ | ||||
|       <rect | ||||
|  | @ -321,7 +323,7 @@ export default class ViewingLayer extends React.PureComponent<ViewingLayerProps, | |||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     const { height, viewRange, numTicks } = this.props; | ||||
|     const { height, viewRange, numTicks, theme } = this.props; | ||||
|     const { preventCursorLine } = this.state; | ||||
|     const { current, cursor, shiftStart, shiftEnd, reframe } = viewRange.time; | ||||
|     const haveNextTimeRange = shiftStart != null || shiftEnd != null || reframe != null; | ||||
|  | @ -338,7 +340,7 @@ export default class ViewingLayer extends React.PureComponent<ViewingLayerProps, | |||
|     if (!haveNextTimeRange && cursor != null && !preventCursorLine) { | ||||
|       cursorPosition = `${cursor * 100}%`; | ||||
|     } | ||||
|     const styles = getStyles(); | ||||
|     const styles = getStyles(theme); | ||||
| 
 | ||||
|     return ( | ||||
|       <div aria-hidden className={styles.ViewingLayer} style={{ height }}> | ||||
|  | @ -406,3 +408,5 @@ export default class ViewingLayer extends React.PureComponent<ViewingLayerProps, | |||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withTheme(UnthemedViewingLayer); | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ | |||
| import { TNil } from '../..'; | ||||
| 
 | ||||
| // exported for tests
 | ||||
| export const BG_COLOR = '#fff'; | ||||
| export const ITEM_ALPHA = 0.8; | ||||
| export const MIN_ITEM_HEIGHT = 2; | ||||
| export const MAX_TOTAL_HEIGHT = 200; | ||||
|  | @ -27,7 +26,8 @@ export default function renderIntoCanvas( | |||
|   canvas: HTMLCanvasElement, | ||||
|   items: Array<{ valueWidth: number; valueOffset: number; serviceName: string }>, | ||||
|   totalValueWidth: number, | ||||
|   getFillColor: (serviceName: string) => [number, number, number] | ||||
|   getFillColor: (serviceName: string) => [number, number, number], | ||||
|   bgColor: string | ||||
| ) { | ||||
|   const fillCache: Map<string, string | TNil> = new Map(); | ||||
|   const cHeight = items.length < MIN_TOTAL_HEIGHT ? MIN_TOTAL_HEIGHT : Math.min(items.length, MAX_TOTAL_HEIGHT); | ||||
|  | @ -40,7 +40,7 @@ export default function renderIntoCanvas( | |||
|   const itemYChange = cHeight / items.length; | ||||
| 
 | ||||
|   const ctx = canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D; | ||||
|   ctx.fillStyle = BG_COLOR; | ||||
|   ctx.fillStyle = bgColor; | ||||
|   ctx.fillRect(0, 0, cWidth, cHeight); | ||||
|   for (let i = 0; i < items.length; i++) { | ||||
|     const { valueWidth, valueOffset, serviceName } = items[i]; | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ import cx from 'classnames'; | |||
| 
 | ||||
| import SpanGraph from './SpanGraph'; | ||||
| import TracePageSearchBar from './TracePageSearchBar'; | ||||
| import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '..'; | ||||
| import { autoColor, Theme, TUpdateViewRangeTimeFunction, useTheme, ViewRange, ViewRangeTimeUpdate } from '..'; | ||||
| import LabeledList from '../common/LabeledList'; | ||||
| import TraceName from '../common/TraceName'; | ||||
| import { getTraceName } from '../model/trace-viewer'; | ||||
|  | @ -35,7 +35,7 @@ import ExternalLinks from '../common/ExternalLinks'; | |||
| import { createStyle } from '../Theme'; | ||||
| import { uTxMuted } from '../uberUtilityStyles'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   const TracePageHeaderOverviewItemValueDetail = css` | ||||
|     label: TracePageHeaderOverviewItemValueDetail; | ||||
|     color: #aaa; | ||||
|  | @ -44,21 +44,19 @@ const getStyles = createStyle(() => { | |||
|     TracePageHeader: css` | ||||
|       label: TracePageHeader; | ||||
|       & > :first-child { | ||||
|         border-bottom: 1px solid #e8e8e8; | ||||
|         border-bottom: 1px solid ${autoColor(theme, '#e8e8e8')}; | ||||
|       } | ||||
|       & > :nth-child(2) { | ||||
|         background-color: #eee; | ||||
|         border-bottom: 1px solid #e4e4e4; | ||||
|         background-color: ${autoColor(theme, '#eee')}; | ||||
|         border-bottom: 1px solid ${autoColor(theme, '#e4e4e4')}; | ||||
|       } | ||||
|       & > :last-child { | ||||
|         background-color: #f8f8f8; | ||||
|         border-bottom: 1px solid #ccc; | ||||
|         border-bottom: 1px solid ${autoColor(theme, '#ccc')}; | ||||
|       } | ||||
|     `,
 | ||||
|     TracePageHeaderTitleRow: css` | ||||
|       label: TracePageHeaderTitleRow; | ||||
|       align-items: center; | ||||
|       background-color: #fff; | ||||
|       display: flex; | ||||
|     `,
 | ||||
|     TracePageHeaderBack: css` | ||||
|  | @ -81,7 +79,6 @@ const getStyles = createStyle(() => { | |||
|     TracePageHeaderTitleLink: css` | ||||
|       label: TracePageHeaderTitleLink; | ||||
|       align-items: center; | ||||
|       color: rgba(0, 0, 0, 0.85); | ||||
|       display: flex; | ||||
|       flex: 1; | ||||
| 
 | ||||
|  | @ -118,7 +115,7 @@ const getStyles = createStyle(() => { | |||
|     TracePageHeaderOverviewItems: css` | ||||
|       label: TracePageHeaderOverviewItems; | ||||
|       border-bottom: 1px solid #e4e4e4; | ||||
|       padding: 0.25rem 0.5rem; | ||||
|       padding: 0.25rem 0.5rem !important; | ||||
|     `,
 | ||||
|     TracePageHeaderOverviewItemValueDetail, | ||||
|     TracePageHeaderOverviewItemValue: css` | ||||
|  | @ -163,7 +160,7 @@ export const HEADER_ITEMS = [ | |||
|     key: 'timestamp', | ||||
|     label: 'Trace Start', | ||||
|     renderer: (trace: Trace) => { | ||||
|       const styles = getStyles(); | ||||
|       const styles = getStyles(useTheme()); | ||||
|       const dateStr = formatDatetime(trace.startTime); | ||||
|       const match = dateStr.match(/^(.+)(:\d\d\.\d+)$/); | ||||
|       return match ? ( | ||||
|  | @ -235,7 +232,7 @@ export default function TracePageHeader(props: TracePageHeaderEmbedProps) { | |||
|       return { ...rest, value: renderer(trace) }; | ||||
|     }); | ||||
| 
 | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
| 
 | ||||
|   const title = ( | ||||
|     <h1 className={cx(styles.TracePageHeaderTitle, canCollapse && styles.TracePageHeaderTitleCollapsible)}> | ||||
|  |  | |||
|  | @ -59,6 +59,7 @@ describe('<SpanBarRow>', () => { | |||
|   beforeEach(() => { | ||||
|     props.onDetailToggled.mockReset(); | ||||
|     props.onChildrenToggled.mockReset(); | ||||
|     SpanTreeOffset.mockReturnValue(() => {}); | ||||
|     wrapper = mount(<SpanBarRow {...props} />); | ||||
|   }); | ||||
| 
 | ||||
|  | @ -105,7 +106,7 @@ describe('<SpanBarRow>', () => { | |||
|       props.span | ||||
|     ); | ||||
| 
 | ||||
|     const spanRow = shallow(<SpanBarRow {...props} span={span} />); | ||||
|     const spanRow = shallow(<SpanBarRow {...props} span={span} />).dive().dive().dive(); | ||||
|     const refButton = spanRow.find(ReferencesButton); | ||||
|     expect(refButton.length).toEqual(1); | ||||
|     expect(refButton.at(0).props().tooltipText).toEqual('Contains multiple references'); | ||||
|  | @ -127,7 +128,7 @@ describe('<SpanBarRow>', () => { | |||
|       }, | ||||
|       props.span | ||||
|     ); | ||||
|     const spanRow = shallow(<SpanBarRow {...props} span={span} />); | ||||
|     const spanRow = shallow(<SpanBarRow {...props} span={span} />).dive().dive().dive(); | ||||
|     const refButton = spanRow.find(ReferencesButton); | ||||
|     expect(refButton.length).toEqual(1); | ||||
|     expect(refButton.at(0).props().tooltipText).toEqual('This span is referenced by another span'); | ||||
|  | @ -157,7 +158,7 @@ describe('<SpanBarRow>', () => { | |||
|       }, | ||||
|       props.span | ||||
|     ); | ||||
|     const spanRow = shallow(<SpanBarRow {...props} span={span} />); | ||||
|     const spanRow = shallow(<SpanBarRow {...props} span={span} />).dive().dive().dive(); | ||||
|     const refButton = spanRow.find(ReferencesButton); | ||||
|     expect(refButton.length).toEqual(1); | ||||
|     expect(refButton.at(0).props().tooltipText).toEqual('This span is referenced by multiple other spans'); | ||||
|  |  | |||
|  | @ -29,9 +29,9 @@ import Ticks from './Ticks'; | |||
| 
 | ||||
| import { TNil } from '../types'; | ||||
| import { Span } from '../types/trace'; | ||||
| import { createStyle } from '../Theme'; | ||||
| import { autoColor, createStyle, Theme, withTheme } from '../Theme'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   const spanBar = css` | ||||
|     label: spanBar; | ||||
|   `;
 | ||||
|  | @ -40,12 +40,12 @@ const getStyles = createStyle(() => { | |||
|   `;
 | ||||
|   const nameWrapper = css` | ||||
|     label: nameWrapper; | ||||
|     background: #f8f8f8; | ||||
|     background: ${autoColor(theme, '#f8f8f8')}; | ||||
|     line-height: 27px; | ||||
|     overflow: hidden; | ||||
|     display: flex; | ||||
|     &:hover { | ||||
|       border-right: 1px solid #bbb; | ||||
|       border-right: 1px solid ${autoColor(theme, '#bbb')}; | ||||
|       float: left; | ||||
|       min-width: calc(100% + 1px); | ||||
|       overflow: visible; | ||||
|  | @ -54,12 +54,12 @@ const getStyles = createStyle(() => { | |||
| 
 | ||||
|   const nameWrapperMatchingFilter = css` | ||||
|     label: nameWrapperMatchingFilter; | ||||
|     background-color: #fffce4; | ||||
|     background-color: ${autoColor(theme, '#fffce4')}; | ||||
|   `;
 | ||||
| 
 | ||||
|   const endpointName = css` | ||||
|     label: endpointName; | ||||
|     color: #808080; | ||||
|     color: ${autoColor(theme, '#808080')}; | ||||
|   `;
 | ||||
| 
 | ||||
|   const view = css` | ||||
|  | @ -69,14 +69,14 @@ const getStyles = createStyle(() => { | |||
| 
 | ||||
|   const viewExpanded = css` | ||||
|     label: viewExpanded; | ||||
|     background: #f8f8f8; | ||||
|     outline: 1px solid #ddd; | ||||
|     background: ${autoColor(theme, '#f8f8f8')}; | ||||
|     outline: 1px solid ${autoColor(theme, '#ddd')}; | ||||
|   `;
 | ||||
| 
 | ||||
|   const viewExpandedAndMatchingFilter = css` | ||||
|     label: viewExpandedAndMatchingFilter; | ||||
|     background: #fff3d7; | ||||
|     outline: 1px solid #ddd; | ||||
|     background: ${autoColor(theme, '#fff3d7')}; | ||||
|     outline: 1px solid ${autoColor(theme, '#ddd')}; | ||||
|   `;
 | ||||
| 
 | ||||
|   const nameColumn = css` | ||||
|  | @ -105,15 +105,20 @@ const getStyles = createStyle(() => { | |||
|         opacity: 1; | ||||
|       } | ||||
|       &:hover .${spanBarLabel} { | ||||
|         color: #000; | ||||
|         color: ${autoColor(theme, '#000')}; | ||||
|       } | ||||
|       &:hover .${nameWrapper} { | ||||
|         background: #f8f8f8; | ||||
|         background: linear-gradient(90deg, #fafafa, #f8f8f8 75%, #eee); | ||||
|         background: linear-gradient( | ||||
|           90deg, | ||||
|           ${autoColor(theme, '#fafafa')}, | ||||
|           ${autoColor(theme, '#f8f8f8')} 75%, | ||||
|           ${autoColor(theme, '#eee')} | ||||
|         ); | ||||
|       } | ||||
|       &:hover .${view} { | ||||
|         background-color: #f5f5f5; | ||||
|         outline: 1px solid #ddd; | ||||
|         background-color: ${autoColor(theme, '#f5f5f5')}; | ||||
|         outline: 1px solid ${autoColor(theme, '#ddd')}; | ||||
|       } | ||||
|     `,
 | ||||
|     rowClippingLeft: css` | ||||
|  | @ -123,7 +128,11 @@ const getStyles = createStyle(() => { | |||
|         height: 100%; | ||||
|         position: absolute; | ||||
|         width: 6px; | ||||
|         background-image: linear-gradient(to right, rgba(25, 25, 25, 0.25), rgba(32, 32, 32, 0)); | ||||
|         background-image: linear-gradient( | ||||
|           to right, | ||||
|           ${autoColor(theme, 'rgba(25, 25, 25, 0.25)')}, | ||||
|           ${autoColor(theme, 'rgba(32, 32, 32, 0)')} | ||||
|         ); | ||||
|         left: 100%; | ||||
|         z-index: -1; | ||||
|       } | ||||
|  | @ -135,7 +144,11 @@ const getStyles = createStyle(() => { | |||
|         height: 100%; | ||||
|         position: absolute; | ||||
|         width: 6px; | ||||
|         background-image: linear-gradient(to left, rgba(25, 25, 25, 0.25), rgba(32, 32, 32, 0)); | ||||
|         background-image: linear-gradient( | ||||
|           to left, | ||||
|           ${autoColor(theme, 'rgba(25, 25, 25, 0.25)')}, | ||||
|           ${autoColor(theme, 'rgba(25, 25, 25, 0.25)')} | ||||
|         ); | ||||
|         right: 0%; | ||||
|         z-index: 1; | ||||
|       } | ||||
|  | @ -146,41 +159,46 @@ const getStyles = createStyle(() => { | |||
|         opacity: 1; | ||||
|       } | ||||
|       & .${spanBarLabel} { | ||||
|         color: #000; | ||||
|         color: ${autoColor(theme, '#000')}; | ||||
|       } | ||||
|       & .${nameWrapper}, &:hover .${nameWrapper} { | ||||
|         background: #f0f0f0; | ||||
|         box-shadow: 0 1px 0 #ddd; | ||||
|         background: ${autoColor(theme, '#f0f0f0')}; | ||||
|         box-shadow: 0 1px 0 ${autoColor(theme, '#ddd')}; | ||||
|       } | ||||
|       & .${nameWrapperMatchingFilter} { | ||||
|         background: #fff3d7; | ||||
|         background: ${autoColor(theme, '#fff3d7')}; | ||||
|       } | ||||
|       &:hover .${view} { | ||||
|         background: #eee; | ||||
|         background: ${autoColor(theme, '#eee')}; | ||||
|       } | ||||
|     `,
 | ||||
|     rowMatchingFilter: css` | ||||
|       label: rowMatchingFilter; | ||||
|       background-color: #fffce4; | ||||
|       background-color: ${autoColor(theme, '#fffce4')}; | ||||
|       &:hover .${nameWrapper} { | ||||
|         background: linear-gradient(90deg, #fff5e1, #fff5e1 75%, #ffe6c9); | ||||
|         background: linear-gradient( | ||||
|           90deg, | ||||
|           ${autoColor(theme, '#fff5e1')}, | ||||
|           ${autoColor(theme, '#fff5e1')} 75%, | ||||
|           ${autoColor(theme, '#ffe6c9')} | ||||
|         ); | ||||
|       } | ||||
|       &:hover .${view} { | ||||
|         background-color: #fff3d7; | ||||
|         outline: 1px solid #ddd; | ||||
|         background-color: ${autoColor(theme, '#fff3d7')}; | ||||
|         outline: 1px solid ${autoColor(theme, '#ddd')}; | ||||
|       } | ||||
|     `,
 | ||||
| 
 | ||||
|     rowExpandedAndMatchingFilter: css` | ||||
|       label: rowExpandedAndMatchingFilter; | ||||
|       &:hover .${view} { | ||||
|         background: #ffeccf; | ||||
|         background: ${autoColor(theme, '#ffeccf')}; | ||||
|       } | ||||
|     `,
 | ||||
| 
 | ||||
|     name: css` | ||||
|       label: name; | ||||
|       color: #000; | ||||
|       color: ${autoColor(theme, '#000')}; | ||||
|       cursor: pointer; | ||||
|       flex: 1 1 auto; | ||||
|       outline: none; | ||||
|  | @ -213,7 +231,7 @@ const getStyles = createStyle(() => { | |||
|         text-decoration: none; | ||||
|       } | ||||
|       &:hover > .${endpointName} { | ||||
|         color: #000; | ||||
|         color: ${autoColor(theme, '#000')}; | ||||
|       } | ||||
|     `,
 | ||||
|     nameDetailExpanded: css` | ||||
|  | @ -234,9 +252,9 @@ const getStyles = createStyle(() => { | |||
|     `,
 | ||||
|     errorIcon: css` | ||||
|       label: errorIcon; | ||||
|       background: #db2828; | ||||
|       background: ${autoColor(theme, '#db2828')}; | ||||
|       border-radius: 6.5px; | ||||
|       color: #fff; | ||||
|       color: ${autoColor(theme, '#fff')}; | ||||
|       font-size: 0.85em; | ||||
|       margin-right: 0.25rem; | ||||
|       padding: 1px; | ||||
|  | @ -265,6 +283,7 @@ const getStyles = createStyle(() => { | |||
| 
 | ||||
| type SpanBarRowProps = { | ||||
|   className?: string; | ||||
|   theme: Theme; | ||||
|   color: string; | ||||
|   columnDivision: number; | ||||
|   isChildrenExpanded: boolean; | ||||
|  | @ -302,7 +321,8 @@ type SpanBarRowProps = { | |||
|  * handlers to the onClick props. E.g. for now, the PureComponent is more | ||||
|  * performance than the stateless function. | ||||
|  */ | ||||
| export default class SpanBarRow extends React.PureComponent<SpanBarRowProps> { | ||||
| export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> { | ||||
|   static displayName = 'UnthemedSpanBarRow'; | ||||
|   static defaultProps: Partial<SpanBarRowProps> = { | ||||
|     className: '', | ||||
|     rpc: null, | ||||
|  | @ -336,6 +356,7 @@ export default class SpanBarRow extends React.PureComponent<SpanBarRowProps> { | |||
|       removeHoverIndentGuideId, | ||||
|       clippingLeft, | ||||
|       clippingRight, | ||||
|       theme, | ||||
|     } = this.props; | ||||
|     const { | ||||
|       duration, | ||||
|  | @ -347,7 +368,7 @@ export default class SpanBarRow extends React.PureComponent<SpanBarRowProps> { | |||
|     const viewBounds = getViewedBounds(span.startTime, span.startTime + span.duration); | ||||
|     const viewStart = viewBounds.start; | ||||
|     const viewEnd = viewBounds.end; | ||||
|     const styles = getStyles(); | ||||
|     const styles = getStyles(theme); | ||||
| 
 | ||||
|     const labelDetail = `${serviceName}::${operationName}`; | ||||
|     let longLabel; | ||||
|  | @ -459,3 +480,5 @@ export default class SpanBarRow extends React.PureComponent<SpanBarRowProps> { | |||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withTheme(UnthemedSpanBarRow); | ||||
|  |  | |||
|  | @ -22,53 +22,61 @@ import * as markers from './AccordianKeyValues.markers'; | |||
| import KeyValuesTable from './KeyValuesTable'; | ||||
| import { TNil } from '../../types'; | ||||
| import { KeyValuePair, Link } from '../../types/trace'; | ||||
| import { createStyle } from '../../Theme'; | ||||
| import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; | ||||
| import { uAlignIcon, uTxEllipsis } from '../../uberUtilityStyles'; | ||||
| 
 | ||||
| export const getStyles = createStyle(() => { | ||||
| export const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     header: css` | ||||
|       label: header; | ||||
|       cursor: pointer; | ||||
|       overflow: hidden; | ||||
|       padding: 0.25em 0.1em; | ||||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
|       &:hover { | ||||
|         background: #e8e8e8; | ||||
|         background: ${autoColor(theme, '#e8e8e8')}; | ||||
|       } | ||||
|     `,
 | ||||
|     headerEmpty: css` | ||||
|       label: headerEmpty; | ||||
|       background: none; | ||||
|       cursor: initial; | ||||
|     `,
 | ||||
|     headerHighContrast: css` | ||||
|       label: headerHighContrast; | ||||
|       &:hover { | ||||
|         background: #ddd; | ||||
|         background: ${autoColor(theme, '#ddd')}; | ||||
|       } | ||||
|     `,
 | ||||
|     emptyIcon: css` | ||||
|       color: #aaa; | ||||
|       label: emptyIcon; | ||||
|       color: ${autoColor(theme, '#aaa')}; | ||||
|     `,
 | ||||
|     summary: css` | ||||
|       label: summary; | ||||
|       display: inline; | ||||
|       list-style: none; | ||||
|       padding: 0; | ||||
|     `,
 | ||||
|     summaryItem: css` | ||||
|       label: summaryItem; | ||||
|       display: inline; | ||||
|       margin-left: 0.7em; | ||||
|       padding-right: 0.5rem; | ||||
|       border-right: 1px solid #ddd; | ||||
|       border-right: 1px solid ${autoColor(theme, '#ddd')}; | ||||
|       &:last-child { | ||||
|         padding-right: 0; | ||||
|         border-right: none; | ||||
|       } | ||||
|     `,
 | ||||
|     summaryLabel: css` | ||||
|       color: #777; | ||||
|       label: summaryLabel; | ||||
|       color: ${autoColor(theme, '#777')}; | ||||
|     `,
 | ||||
|     summaryDelim: css` | ||||
|       color: #bbb; | ||||
|       label: summaryDelim; | ||||
|       color: ${autoColor(theme, '#bbb')}; | ||||
|       padding: 0 0.2em; | ||||
|     `,
 | ||||
|   }; | ||||
|  | @ -91,7 +99,7 @@ export function KeyValuesSummary(props: { data?: KeyValuePair[] }) { | |||
|   if (!Array.isArray(data) || !data.length) { | ||||
|     return null; | ||||
|   } | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   return ( | ||||
|     <ul className={styles.summary}> | ||||
|       {data.map((item, i) => ( | ||||
|  | @ -113,7 +121,7 @@ KeyValuesSummary.defaultProps = { | |||
| export default function AccordianKeyValues(props: AccordianKeyValuesProps) { | ||||
|   const { className, data, highContrast, interactive, isOpen, label, linksGetter, onToggle } = props; | ||||
|   const isEmpty = !Array.isArray(data) || !data.length; | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   const iconCls = cx(uAlignIcon, { [styles.emptyIcon]: isEmpty }); | ||||
|   let arrow: React.ReactNode | null = null; | ||||
|   let headerProps: {} | null = null; | ||||
|  |  | |||
|  | @ -22,32 +22,36 @@ import AccordianKeyValues from './AccordianKeyValues'; | |||
| import { formatDuration } from '../utils'; | ||||
| import { TNil } from '../../types'; | ||||
| import { Log, KeyValuePair, Link } from '../../types/trace'; | ||||
| import { createStyle } from '../../Theme'; | ||||
| import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; | ||||
| import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     AccordianLogs: css` | ||||
|       border: 1px solid #d8d8d8; | ||||
|       label: AccordianLogs; | ||||
|       border: 1px solid ${autoColor(theme, '#d8d8d8')}; | ||||
|       position: relative; | ||||
|       margin-bottom: 0.25rem; | ||||
|     `,
 | ||||
|     header: css` | ||||
|       background: #e4e4e4; | ||||
|     AccordianLogsHeader: css` | ||||
|       label: AccordianLogsHeader; | ||||
|       background: ${autoColor(theme, '#e4e4e4')}; | ||||
|       color: inherit; | ||||
|       display: block; | ||||
|       padding: 0.25rem 0.5rem; | ||||
|       &:hover { | ||||
|         background: #dadada; | ||||
|         background: ${autoColor(theme, '#dadada')}; | ||||
|       } | ||||
|     `,
 | ||||
|     content: css` | ||||
|       background: #f0f0f0; | ||||
|       border-top: 1px solid #d8d8d8; | ||||
|     AccordianLogsContent: css` | ||||
|       label: AccordianLogsContent; | ||||
|       background: ${autoColor(theme, '#f0f0f0')}; | ||||
|       border-top: 1px solid ${autoColor(theme, '#d8d8d8')}; | ||||
|       padding: 0.5rem 0.5rem 0.25rem 0.5rem; | ||||
|     `,
 | ||||
|     footer: css` | ||||
|       color: #999; | ||||
|     AccordianLogsFooter: css` | ||||
|       label: AccordianLogsFooter; | ||||
|       color: ${autoColor(theme, '#999')}; | ||||
|     `,
 | ||||
|   }; | ||||
| }); | ||||
|  | @ -78,14 +82,14 @@ export default function AccordianLogs(props: AccordianLogsProps) { | |||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   return ( | ||||
|     <div className={styles.AccordianLogs}> | ||||
|       <HeaderComponent className={styles.header} {...headerProps}> | ||||
|       <HeaderComponent className={styles.AccordianLogsHeader} {...headerProps}> | ||||
|         {arrow} <strong>Logs</strong> ({logs.length}) | ||||
|       </HeaderComponent> | ||||
|       {isOpen && ( | ||||
|         <div className={styles.content}> | ||||
|         <div className={styles.AccordianLogsContent}> | ||||
|           {_sortBy(logs, 'timestamp').map((log, i) => ( | ||||
|             <AccordianKeyValues | ||||
|               // `i` is necessary in the key because timestamps can repeat
 | ||||
|  | @ -100,7 +104,9 @@ export default function AccordianLogs(props: AccordianLogsProps) { | |||
|               onToggle={interactive && onItemToggle ? () => onItemToggle(log) : null} | ||||
|             /> | ||||
|           ))} | ||||
|           <small className={styles.footer}>Log timestamps are relative to the start time of the full trace.</small> | ||||
|           <small className={styles.AccordianLogsFooter}> | ||||
|             Log timestamps are relative to the start time of the full trace. | ||||
|           </small> | ||||
|         </div> | ||||
|       )} | ||||
|     </div> | ||||
|  |  | |||
|  | @ -20,10 +20,10 @@ import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right'; | |||
| import TextList from './TextList'; | ||||
| import { TNil } from '../../types'; | ||||
| import { getStyles as getAccordianKeyValuesStyles } from './AccordianKeyValues'; | ||||
| import { createStyle } from '../../Theme'; | ||||
| import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; | ||||
| import { uAlignIcon } from '../../uberUtilityStyles'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     header: css` | ||||
|       cursor: pointer; | ||||
|  | @ -32,7 +32,7 @@ const getStyles = createStyle(() => { | |||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
|       &:hover { | ||||
|         background: #e8e8e8; | ||||
|         background: ${autoColor(theme, '#e8e8e8')}; | ||||
|       } | ||||
|     `,
 | ||||
|   }; | ||||
|  | @ -52,7 +52,7 @@ type AccordianTextProps = { | |||
| export default function AccordianText(props: AccordianTextProps) { | ||||
|   const { className, data, headerClassName, interactive, isOpen, label, onToggle } = props; | ||||
|   const isEmpty = !Array.isArray(data) || !data.length; | ||||
|   const accordianKeyValuesStyles = getAccordianKeyValuesStyles(); | ||||
|   const accordianKeyValuesStyles = getAccordianKeyValuesStyles(useTheme()); | ||||
|   const iconCls = cx(uAlignIcon, { [accordianKeyValuesStyles.emptyIcon]: isEmpty }); | ||||
|   let arrow: React.ReactNode | null = null; | ||||
|   let headerProps: {} | null = null; | ||||
|  | @ -64,7 +64,7 @@ export default function AccordianText(props: AccordianTextProps) { | |||
|       role: 'switch', | ||||
|     }; | ||||
|   } | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   return ( | ||||
|     <div className={className || ''}> | ||||
|       <div className={cx(styles.header, headerClassName)} {...headerProps} data-test-id="AccordianText--header"> | ||||
|  |  | |||
|  | @ -19,7 +19,8 @@ import CopyIcon from '../../common/CopyIcon'; | |||
| 
 | ||||
| import KeyValuesTable, { LinkValue, getStyles } from './KeyValuesTable'; | ||||
| import { UIDropdown, UIIcon } from '../../uiElementsContext'; | ||||
| import {ubInlineBlock} from "../../uberUtilityStyles"; | ||||
| import { ubInlineBlock } from '../../uberUtilityStyles'; | ||||
| import { defaultTheme } from '../../Theme'; | ||||
| 
 | ||||
| describe('LinkValue', () => { | ||||
|   const title = 'titleValue'; | ||||
|  | @ -38,7 +39,7 @@ describe('LinkValue', () => { | |||
|   }); | ||||
| 
 | ||||
|   it('renders correct Icon', () => { | ||||
|     const styles = getStyles(); | ||||
|     const styles = getStyles(defaultTheme); | ||||
|     expect(wrapper.find(UIIcon).hasClass(styles.linkIcon)).toBe(true); | ||||
|     expect(wrapper.find(UIIcon).prop('type')).toBe('export'); | ||||
|   }); | ||||
|  |  | |||
|  | @ -22,18 +22,18 @@ import CopyIcon from '../../common/CopyIcon'; | |||
| import { TNil } from '../../types'; | ||||
| import { KeyValuePair, Link } from '../../types/trace'; | ||||
| import { UIDropdown, UIIcon, UIMenu, UIMenuItem } from '../../uiElementsContext'; | ||||
| import { createStyle } from '../../Theme'; | ||||
| import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; | ||||
| import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles'; | ||||
| 
 | ||||
| export const getStyles = createStyle(() => { | ||||
| export const getStyles = createStyle((theme: Theme) => { | ||||
|   const copyIcon = css` | ||||
|     label: copyIcon; | ||||
|   `;
 | ||||
|   return { | ||||
|     KeyValueTable: css` | ||||
|       label: KeyValueTable; | ||||
|       background: #fff; | ||||
|       border: 1px solid #ddd; | ||||
|       background: ${autoColor(theme, '#fff')}; | ||||
|       border: 1px solid ${autoColor(theme, '#ddd')}; | ||||
|       margin-bottom: 0.7em; | ||||
|       max-height: 450px; | ||||
|       overflow: auto; | ||||
|  | @ -50,7 +50,7 @@ export const getStyles = createStyle(() => { | |||
|         vertical-align: top; | ||||
|       } | ||||
|       &:nth-child(2n) > td { | ||||
|         background: #f5f5f5; | ||||
|         background: ${autoColor(theme, '#f5f5f5')}; | ||||
|       } | ||||
|       &:not(:hover) .${copyIcon} { | ||||
|         display: none; | ||||
|  | @ -58,7 +58,7 @@ export const getStyles = createStyle(() => { | |||
|     `,
 | ||||
|     keyColumn: css` | ||||
|       label: keyColumn; | ||||
|       color: #888; | ||||
|       color: ${autoColor(theme, '#888')}; | ||||
|       white-space: pre; | ||||
|       width: 125px; | ||||
|     `,
 | ||||
|  | @ -90,7 +90,7 @@ function parseIfComplexJson(value: any) { | |||
| } | ||||
| 
 | ||||
| export const LinkValue = (props: { href: string; title?: string; children: React.ReactNode }) => { | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   return ( | ||||
|     <a href={props.href} title={props.title} target="_blank" rel="noopener noreferrer"> | ||||
|       {props.children} <UIIcon className={styles.linkIcon} type="export" /> | ||||
|  | @ -120,7 +120,7 @@ type KeyValuesTableProps = { | |||
| 
 | ||||
| export default function KeyValuesTable(props: KeyValuesTableProps) { | ||||
|   const { data, linksGetter } = props; | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   return ( | ||||
|     <div className={cx(styles.KeyValueTable)} data-test-id="KeyValueTable"> | ||||
|       <table className={uWidth100}> | ||||
|  |  | |||
|  | @ -27,53 +27,72 @@ import LabeledList from '../../common/LabeledList'; | |||
| import { TNil } from '../../types'; | ||||
| import { KeyValuePair, Link, Log, Span } from '../../types/trace'; | ||||
| import AccordianReferences from './AccordianReferences'; | ||||
| import { createStyle } from '../../Theme'; | ||||
| import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; | ||||
| import { UIDivider } from '../../uiElementsContext'; | ||||
| import { ubFlex, ubFlexAuto, ubItemsCenter, ubM0, ubMb1, ubMy1, ubTxRightAlign } from '../../uberUtilityStyles'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     divider: css` | ||||
|       background: #ddd; | ||||
|       label: divider; | ||||
|       background: ${autoColor(theme, '#ddd')}; | ||||
|     `,
 | ||||
|     dividerVertical: css` | ||||
|       label: dividerVertical; | ||||
|       display: block; | ||||
|       height: 1px; | ||||
|       width: 100%; | ||||
|       margin: 24px 0; | ||||
|       clear: both; | ||||
|       vertical-align: middle; | ||||
|       position: relative; | ||||
|       top: -0.06em; | ||||
|     `,
 | ||||
|     debugInfo: css` | ||||
|       label: debugInfo; | ||||
|       display: block; | ||||
|       letter-spacing: 0.25px; | ||||
|       margin: 0.5em 0 -0.75em; | ||||
|       text-align: right; | ||||
|     `,
 | ||||
|     debugLabel: css` | ||||
|       label: debugLabel; | ||||
|       &::before { | ||||
|         color: #bbb; | ||||
|         color: ${autoColor(theme, '#bbb')}; | ||||
|         content: attr(data-label); | ||||
|       } | ||||
|     `,
 | ||||
|     debugValue: css` | ||||
|       label: debugValue; | ||||
|       background-color: inherit; | ||||
|       border: none; | ||||
|       color: #888; | ||||
|       color: ${autoColor(theme, '#888')}; | ||||
|       cursor: pointer; | ||||
|       &:hover { | ||||
|         color: #333; | ||||
|         color: ${autoColor(theme, '#333')}; | ||||
|       } | ||||
|     `,
 | ||||
|     AccordianWarnings: css` | ||||
|       background: #fafafa; | ||||
|       border: 1px solid #e4e4e4; | ||||
|       label: AccordianWarnings; | ||||
|       background: ${autoColor(theme, '#fafafa')}; | ||||
|       border: 1px solid ${autoColor(theme, '#e4e4e4')}; | ||||
|       margin-bottom: 0.25rem; | ||||
|     `,
 | ||||
|     AccordianWarningsHeader: css` | ||||
|       background: #fff7e6; | ||||
|       label: AccordianWarningsHeader; | ||||
|       background: ${autoColor(theme, '#fff7e6')}; | ||||
|       padding: 0.25rem 0.5rem; | ||||
|       &:hover { | ||||
|         background: #ffe7ba; | ||||
|         background: ${autoColor(theme, '#ffe7ba')}; | ||||
|       } | ||||
|     `,
 | ||||
|     AccordianWarningsHeaderOpen: css` | ||||
|       border-bottom: 1px solid #e8e8e8; | ||||
|       label: AccordianWarningsHeaderOpen; | ||||
|       border-bottom: 1px solid ${autoColor(theme, '#e8e8e8')}; | ||||
|     `,
 | ||||
|     AccordianWarningsLabel: css` | ||||
|       color: #d36c08; | ||||
|       label: AccordianWarningsLabel; | ||||
|       color: ${autoColor(theme, '#d36c08')}; | ||||
|     `,
 | ||||
|   }; | ||||
| }); | ||||
|  | @ -126,7 +145,7 @@ export default function SpanDetail(props: SpanDetailProps) { | |||
|     }, | ||||
|   ]; | ||||
|   const deepLinkCopyText = `${window.location.origin}${window.location.pathname}?uiFind=${spanID}`; | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
| 
 | ||||
|   return ( | ||||
|     <div> | ||||
|  | @ -134,7 +153,7 @@ export default function SpanDetail(props: SpanDetailProps) { | |||
|         <h2 className={cx(ubFlexAuto, ubM0)}>{operationName}</h2> | ||||
|         <LabeledList className={ubTxRightAlign} dividerClassName={styles.divider} items={overviewItems} /> | ||||
|       </div> | ||||
|       <UIDivider className={cx(styles.divider, ubMy1)} /> | ||||
|       <UIDivider className={cx(styles.divider, styles.dividerVertical, ubMy1)} /> | ||||
|       <div> | ||||
|         <div> | ||||
|           <AccordianKeyValues | ||||
|  |  | |||
|  | @ -48,7 +48,7 @@ describe('<SpanDetailRow>', () => { | |||
|     props.logsToggle.mockReset(); | ||||
|     props.processToggle.mockReset(); | ||||
|     props.tagsToggle.mockReset(); | ||||
|     wrapper = shallow(<SpanDetailRow {...props} />); | ||||
|     wrapper = shallow(<SpanDetailRow {...props} />).dive().dive().dive(); | ||||
|   }); | ||||
| 
 | ||||
|   it('renders without exploding', () => { | ||||
|  |  | |||
|  | @ -19,11 +19,11 @@ import SpanDetail from './SpanDetail'; | |||
| import DetailState from './SpanDetail/DetailState'; | ||||
| import SpanTreeOffset from './SpanTreeOffset'; | ||||
| import TimelineRow from './TimelineRow'; | ||||
| import { createStyle } from '../Theme'; | ||||
| import { autoColor, createStyle, Theme, withTheme } from '../Theme'; | ||||
| 
 | ||||
| import { Log, Span, KeyValuePair, Link } from '../types/trace'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     expandedAccent: css` | ||||
|       cursor: pointer; | ||||
|  | @ -57,8 +57,8 @@ const getStyles = createStyle(() => { | |||
|       } | ||||
|     `,
 | ||||
|     infoWrapper: css` | ||||
|       background: #f5f5f5; | ||||
|       border: 1px solid #d3d3d3; | ||||
|       background: ${autoColor(theme, '#f5f5f5')}; | ||||
|       border: 1px solid ${autoColor(theme, '#d3d3d3')}; | ||||
|       border-top: 3px solid; | ||||
|       padding: 0.75rem; | ||||
|     `,
 | ||||
|  | @ -83,9 +83,10 @@ type SpanDetailRowProps = { | |||
|   hoverIndentGuideIds: Set<string>; | ||||
|   addHoverIndentGuideId: (spanID: string) => void; | ||||
|   removeHoverIndentGuideId: (spanID: string) => void; | ||||
|   theme: Theme; | ||||
| }; | ||||
| 
 | ||||
| export default class SpanDetailRow extends React.PureComponent<SpanDetailRowProps> { | ||||
| export class UnthemedSpanDetailRow extends React.PureComponent<SpanDetailRowProps> { | ||||
|   _detailToggle = () => { | ||||
|     this.props.onDetailToggled(this.props.span.spanID); | ||||
|   }; | ||||
|  | @ -112,8 +113,9 @@ export default class SpanDetailRow extends React.PureComponent<SpanDetailRowProp | |||
|       hoverIndentGuideIds, | ||||
|       addHoverIndentGuideId, | ||||
|       removeHoverIndentGuideId, | ||||
|       theme, | ||||
|     } = this.props; | ||||
|     const styles = getStyles(); | ||||
|     const styles = getStyles(theme); | ||||
|     return ( | ||||
|       <TimelineRow> | ||||
|         <TimelineRow.Cell width={columnDivision}> | ||||
|  | @ -156,3 +158,5 @@ export default class SpanDetailRow extends React.PureComponent<SpanDetailRowProp | |||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withTheme(UnthemedSpanDetailRow); | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down'; | |||
| 
 | ||||
| import SpanTreeOffset, { getStyles } from './SpanTreeOffset'; | ||||
| import spanAncestorIdsSpy from '../utils/span-ancestor-ids'; | ||||
| import {defaultTheme} from "../Theme"; | ||||
| 
 | ||||
| jest.mock('../utils/span-ancestor-ids'); | ||||
| 
 | ||||
|  | @ -42,13 +43,13 @@ describe('SpanTreeOffset', () => { | |||
|         spanID: ownSpanID, | ||||
|       }, | ||||
|     }; | ||||
|     wrapper = shallow(<SpanTreeOffset {...props} />); | ||||
|     wrapper = shallow(<SpanTreeOffset {...props} />).dive().dive().dive(); | ||||
|   }); | ||||
| 
 | ||||
|   describe('.SpanTreeOffset--indentGuide', () => { | ||||
|     it('renders only one .SpanTreeOffset--indentGuide for entire trace if span has no ancestors', () => { | ||||
|       spanAncestorIdsSpy.mockReturnValue([]); | ||||
|       wrapper = shallow(<SpanTreeOffset {...props} />); | ||||
|       wrapper = shallow(<SpanTreeOffset {...props} />).dive().dive().dive(); | ||||
|       const indentGuides = wrapper.find('[data-test-id="SpanTreeOffset--indentGuide"]'); | ||||
|       expect(indentGuides.length).toBe(1); | ||||
|       expect(indentGuides.prop('data-ancestor-id')).toBe(specialRootID); | ||||
|  | @ -64,8 +65,8 @@ describe('SpanTreeOffset', () => { | |||
| 
 | ||||
|     it('adds .is-active to correct indentGuide', () => { | ||||
|       props.hoverIndentGuideIds = new Set([parentSpanID]); | ||||
|       wrapper = shallow(<SpanTreeOffset {...props} />); | ||||
|       const styles = getStyles(); | ||||
|       wrapper = shallow(<SpanTreeOffset {...props} />).dive().dive().dive(); | ||||
|       const styles = getStyles(defaultTheme); | ||||
|       const activeIndentGuide = wrapper.find(`.${styles.indentGuideActive}`); | ||||
|       expect(activeIndentGuide.length).toBe(1); | ||||
|       expect(activeIndentGuide.prop('data-ancestor-id')).toBe(parentSpanID); | ||||
|  |  | |||
|  | @ -22,19 +22,19 @@ import cx from 'classnames'; | |||
| import { Span } from '../types/trace'; | ||||
| import spanAncestorIds from '../utils/span-ancestor-ids'; | ||||
| 
 | ||||
| import { createStyle } from '../Theme'; | ||||
| import { autoColor, createStyle, Theme, withTheme } from '../Theme'; | ||||
| 
 | ||||
| export const getStyles = createStyle(() => { | ||||
| export const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     SpanTreeOffset: css` | ||||
|       label: SpanTreeOffset; | ||||
|       color: #000; | ||||
|       color: ${autoColor(theme, '#000')}; | ||||
|       position: relative; | ||||
|     `,
 | ||||
|     SpanTreeOffsetParent: css` | ||||
|       label: SpanTreeOffsetParent; | ||||
|       &:hover { | ||||
|         background-color: #e8e8e8; | ||||
|         background-color: ${autoColor(theme, '#e8e8e8')}; | ||||
|         cursor: pointer; | ||||
|       } | ||||
|     `,
 | ||||
|  | @ -48,7 +48,7 @@ export const getStyles = createStyle(() => { | |||
|       &::before { | ||||
|         content: ''; | ||||
|         padding-left: 1px; | ||||
|         background-color: lightgrey; | ||||
|         background-color: ${autoColor(theme, 'lightgrey')}; | ||||
|       } | ||||
|     `,
 | ||||
|     indentGuideActive: css` | ||||
|  | @ -58,7 +58,7 @@ export const getStyles = createStyle(() => { | |||
|       &::before { | ||||
|         content: ''; | ||||
|         padding-left: 3px; | ||||
|         background-color: darkgrey; | ||||
|         background-color: ${autoColor(theme, 'darkgrey')}; | ||||
|       } | ||||
|     `,
 | ||||
|     iconWrapper: css` | ||||
|  | @ -78,9 +78,12 @@ type TProps = { | |||
|   hoverIndentGuideIds: Set<string>; | ||||
|   addHoverIndentGuideId: (spanID: string) => void; | ||||
|   removeHoverIndentGuideId: (spanID: string) => void; | ||||
|   theme: Theme; | ||||
| }; | ||||
| 
 | ||||
| export default class SpanTreeOffset extends React.PureComponent<TProps> { | ||||
| export class UnthemedSpanTreeOffset extends React.PureComponent<TProps> { | ||||
|   static displayName = 'UnthemedSpanTreeOffset'; | ||||
| 
 | ||||
|   ancestorIds: string[]; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|  | @ -134,11 +137,11 @@ export default class SpanTreeOffset extends React.PureComponent<TProps> { | |||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const { childrenVisible, onClick, showChildrenIcon, span } = this.props; | ||||
|     const { childrenVisible, onClick, showChildrenIcon, span, theme } = this.props; | ||||
|     const { hasChildren, spanID } = span; | ||||
|     const wrapperProps = hasChildren ? { onClick, role: 'switch', 'aria-checked': childrenVisible } : null; | ||||
|     const icon = showChildrenIcon && hasChildren && (childrenVisible ? <IoIosArrowDown /> : <IoChevronRight />); | ||||
|     const styles = getStyles(); | ||||
|     const styles = getStyles(theme); | ||||
|     return ( | ||||
|       <span className={cx(styles.SpanTreeOffset, { [styles.SpanTreeOffsetParent]: hasChildren })} {...wrapperProps}> | ||||
|         {this.ancestorIds.map(ancestorId => ( | ||||
|  | @ -167,3 +170,5 @@ export default class SpanTreeOffset extends React.PureComponent<TProps> { | |||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withTheme(UnthemedSpanTreeOffset); | ||||
|  |  | |||
|  | @ -18,27 +18,31 @@ import cx from 'classnames'; | |||
| 
 | ||||
| import { formatDuration } from './utils'; | ||||
| import { TNil } from '../types'; | ||||
| import { createStyle } from '../Theme'; | ||||
| import { autoColor, createStyle, Theme, useTheme } from '../Theme'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     Ticks: css` | ||||
|       label: Ticks; | ||||
|       pointer-events: none; | ||||
|     `,
 | ||||
|     tick: css` | ||||
|     TicksTick: css` | ||||
|       label: TicksTick; | ||||
|       position: absolute; | ||||
|       height: 100%; | ||||
|       width: 1px; | ||||
|       background: #d8d8d8; | ||||
|       background: ${autoColor(theme, '#d8d8d8')}; | ||||
|       &:last-child { | ||||
|         width: 0; | ||||
|       } | ||||
|     `,
 | ||||
|     tickLabel: css` | ||||
|     TicksTickLabel: css` | ||||
|       label: TicksTickLabel; | ||||
|       left: 0.25rem; | ||||
|       position: absolute; | ||||
|     `,
 | ||||
|     tickLabelEndAnchor: css` | ||||
|     TicksTickLabelEndAnchor: css` | ||||
|       label: TicksTickLabelEndAnchor; | ||||
|       left: initial; | ||||
|       right: 0.25rem; | ||||
|     `,
 | ||||
|  | @ -64,20 +68,22 @@ export default function Ticks(props: TicksProps) { | |||
|       labels.push(formatDuration(durationAtTick)); | ||||
|     } | ||||
|   } | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   const ticks: React.ReactNode[] = []; | ||||
|   for (let i = 0; i < numTicks; i++) { | ||||
|     const portion = i / (numTicks - 1); | ||||
|     ticks.push( | ||||
|       <div | ||||
|         key={portion} | ||||
|         className={styles.tick} | ||||
|         className={styles.TicksTick} | ||||
|         style={{ | ||||
|           left: `${portion * 100}%`, | ||||
|         }} | ||||
|       > | ||||
|         {labels && ( | ||||
|           <span className={cx(styles.tickLabel, { [styles.tickLabelEndAnchor]: portion >= 1 })}>{labels[i]}</span> | ||||
|           <span className={cx(styles.TicksTickLabel, { [styles.TicksTickLabelEndAnchor]: portion >= 1 })}> | ||||
|             {labels[i]} | ||||
|           </span> | ||||
|         )} | ||||
|       </div> | ||||
|     ); | ||||
|  |  | |||
|  | @ -58,7 +58,7 @@ describe('<TimelineHeaderRow>', () => { | |||
|   }); | ||||
| 
 | ||||
|   it('renders the title', () => { | ||||
|     expect(wrapper.find('h3').text()).toMatch(/Service.*?Operation/); | ||||
|     expect(wrapper.find('h4').text()).toMatch(/Service.*?Operation/); | ||||
|   }); | ||||
| 
 | ||||
|   it('renders the TimelineViewingLayer', () => { | ||||
|  | @ -88,12 +88,7 @@ describe('<TimelineHeaderRow>', () => { | |||
| 
 | ||||
|   it('renders the TimelineColumnResizer', () => { | ||||
|     const elm = ( | ||||
|       <TimelineColumnResizer | ||||
|         position={nameColumnWidth} | ||||
|         onChange={props.onColummWidthChange} | ||||
|         min={0.2} | ||||
|         max={0.85} | ||||
|       /> | ||||
|       <TimelineColumnResizer position={nameColumnWidth} onChange={props.onColummWidthChange} min={0.2} max={0.85} /> | ||||
|     ); | ||||
|     expect(wrapper.containsMatchingElement(elm)).toBe(true); | ||||
|   }); | ||||
|  |  | |||
|  | @ -22,27 +22,33 @@ import TimelineViewingLayer from './TimelineViewingLayer'; | |||
| import Ticks from '../Ticks'; | ||||
| import TimelineRow from '../TimelineRow'; | ||||
| import { TUpdateViewRangeTimeFunction, ViewRangeTime, ViewRangeTimeUpdate } from '../types'; | ||||
| import { createStyle } from '../../Theme'; | ||||
| import { autoColor, createStyle, Theme, useTheme } from '../../Theme'; | ||||
| import { ubFlex, ubPx2 } from '../../uberUtilityStyles'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     TimelineHeaderRow: css` | ||||
|       background: #ececec; | ||||
|       border-bottom: 1px solid #ccc; | ||||
|       label: TimelineHeaderRow; | ||||
|       background: ${autoColor(theme, '#ececec')}; | ||||
|       border-bottom: 1px solid ${autoColor(theme, '#ccc')}; | ||||
|       height: 38px; | ||||
|       line-height: 38px; | ||||
|       width: 100%; | ||||
|       z-index: 4; | ||||
|       position: relative; | ||||
|     `,
 | ||||
|     title: css` | ||||
|     TimelineHeaderRowTitle: css` | ||||
|       label: TimelineHeaderRowTitle; | ||||
|       flex: 1; | ||||
|       overflow: hidden; | ||||
|       margin: 0; | ||||
|       text-overflow: ellipsis; | ||||
|       white-space: nowrap; | ||||
|     `,
 | ||||
|     TimelineHeaderWrapper: css` | ||||
|       label: TimelineHeaderWrapper; | ||||
|       align-items: center; | ||||
|     `,
 | ||||
|   }; | ||||
| }); | ||||
| 
 | ||||
|  | @ -77,11 +83,11 @@ export default function TimelineHeaderRow(props: TimelineHeaderRowProps) { | |||
|     columnResizeHandleHeight, | ||||
|   } = props; | ||||
|   const [viewStart, viewEnd] = viewRangeTime.current; | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   return ( | ||||
|     <TimelineRow className={styles.TimelineHeaderRow} data-test-id="TimelineHeaderRow"> | ||||
|       <TimelineRow.Cell className={cx(ubFlex, ubPx2)} width={nameColumnWidth}> | ||||
|         <h3 className={styles.TimelineHeaderRow}>Service & Operation</h3> | ||||
|       <TimelineRow.Cell className={cx(ubFlex, ubPx2, styles.TimelineHeaderWrapper)} width={nameColumnWidth}> | ||||
|         <h4 className={styles.TimelineHeaderRowTitle}>Service & Operation</h4> | ||||
|         <TimelineCollapser | ||||
|           onCollapseAll={onCollapseAll} | ||||
|           onExpandAll={onExpandAll} | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ import { createStyle } from '../../Theme'; | |||
| export const getStyles = createStyle(() => { | ||||
|   return { | ||||
|     TimelineViewingLayer: css` | ||||
|       label: TimelineViewingLayer; | ||||
|       bottom: 0; | ||||
|       cursor: vertical-text; | ||||
|       left: 0; | ||||
|  | @ -31,7 +32,8 @@ export const getStyles = createStyle(() => { | |||
|       right: 0; | ||||
|       top: 0; | ||||
|     `,
 | ||||
|     cursorGuide: css` | ||||
|     TimelineViewingLayerCursorGuide: css` | ||||
|       label: TimelineViewingLayerCursorGuide; | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       bottom: 0; | ||||
|  | @ -39,26 +41,32 @@ export const getStyles = createStyle(() => { | |||
|       width: 1px; | ||||
|       background-color: red; | ||||
|     `,
 | ||||
|     dragged: css` | ||||
|     TimelineViewingLayerDragged: css` | ||||
|       label: TimelineViewingLayerDragged; | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       bottom: 0; | ||||
|     `,
 | ||||
|     draggedDraggingLeft: css` | ||||
|     TimelineViewingLayerDraggedDraggingLeft: css` | ||||
|       label: TimelineViewingLayerDraggedDraggingLeft; | ||||
|       border-left: 1px solid; | ||||
|     `,
 | ||||
|     draggedDraggingRight: css` | ||||
|     TimelineViewingLayerDraggedDraggingRight: css` | ||||
|       label: TimelineViewingLayerDraggedDraggingRight; | ||||
|       border-right: 1px solid; | ||||
|     `,
 | ||||
|     draggedShiftDrag: css` | ||||
|     TimelineViewingLayerDraggedShiftDrag: css` | ||||
|       label: TimelineViewingLayerDraggedShiftDrag; | ||||
|       background-color: rgba(68, 68, 255, 0.2); | ||||
|       border-color: #44f; | ||||
|     `,
 | ||||
|     draggedReframeDrag: css` | ||||
|     TimelineViewingLayerDraggedReframeDrag: css` | ||||
|       label: TimelineViewingLayerDraggedReframeDrag; | ||||
|       background-color: rgba(255, 68, 68, 0.2); | ||||
|       border-color: #f44; | ||||
|     `,
 | ||||
|     fullOverlay: css` | ||||
|     TimelineViewingLayerFullOverlay: css` | ||||
|       label: TimelineViewingLayerFullOverlay; | ||||
|       bottom: 0; | ||||
|       cursor: col-resize; | ||||
|       left: 0; | ||||
|  | @ -151,13 +159,13 @@ function getMarkers(viewStart: number, viewEnd: number, from: number, to: number | |||
|   const { isDraggingLeft, left, width } = layout; | ||||
|   const styles = getStyles(); | ||||
|   const cls = cx({ | ||||
|     [styles.draggedDraggingRight]: !isDraggingLeft, | ||||
|     [styles.draggedReframeDrag]: !isShift, | ||||
|     [styles.draggedShiftDrag]: isShift, | ||||
|     [styles.TimelineViewingLayerDraggedDraggingRight]: !isDraggingLeft, | ||||
|     [styles.TimelineViewingLayerDraggedReframeDrag]: !isShift, | ||||
|     [styles.TimelineViewingLayerDraggedShiftDrag]: isShift, | ||||
|   }); | ||||
|   return ( | ||||
|     <div | ||||
|       className={cx(styles.dragged, styles.draggedDraggingLeft, cls)} | ||||
|       className={cx(styles.TimelineViewingLayerDragged, styles.TimelineViewingLayerDraggedDraggingLeft, cls)} | ||||
|       style={{ left, width }} | ||||
|       data-test-id="Dragged" | ||||
|     /> | ||||
|  | @ -260,7 +268,7 @@ export default class TimelineViewingLayer extends React.PureComponent<TimelineVi | |||
|       > | ||||
|         {cusrorPosition != null && ( | ||||
|           <div | ||||
|             className={styles.cursorGuide} | ||||
|             className={styles.TimelineViewingLayerCursorGuide} | ||||
|             style={{ left: cusrorPosition }} | ||||
|             data-test-id="TimelineViewingLayer--cursorGuide" | ||||
|           /> | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import SpanDetailRow from './SpanDetailRow'; | |||
| import VirtualizedTraceView, { DEFAULT_HEIGHTS } from './VirtualizedTraceView'; | ||||
| import traceGenerator from '../demo/trace-generators'; | ||||
| import transformTraceData from '../model/transform-trace-data'; | ||||
| import SpanTreeOffset from './SpanTreeOffset'; | ||||
| 
 | ||||
| jest.mock('./SpanTreeOffset'); | ||||
| 
 | ||||
|  | @ -82,12 +83,16 @@ describe('<VirtualizedTraceViewImpl>', () => { | |||
|   } | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     SpanTreeOffset.mockReturnValue(<div />); | ||||
|     Object.keys(props).forEach(key => { | ||||
|       if (typeof props[key] === 'function') { | ||||
|         props[key].mockReset(); | ||||
|       } | ||||
|     }); | ||||
|     wrapper = shallow(<VirtualizedTraceView {...props} />); | ||||
|     wrapper = shallow(<VirtualizedTraceView {...props} />) | ||||
|       .dive() | ||||
|       .dive() | ||||
|       .dive(); | ||||
|     instance = wrapper.instance(); | ||||
|   }); | ||||
| 
 | ||||
|  | @ -366,9 +371,7 @@ describe('<VirtualizedTraceViewImpl>', () => { | |||
| 
 | ||||
|     describe('shouldComponentUpdate', () => { | ||||
|       it('returns true if props.shouldScrollToFirstUiFindMatch changes to true', () => { | ||||
|         expect(wrapper.instance().shouldComponentUpdate(propsWithTrueShouldScrollToFirstUiFindMatch)).toBe( | ||||
|           true | ||||
|         ); | ||||
|         expect(wrapper.instance().shouldComponentUpdate(propsWithTrueShouldScrollToFirstUiFindMatch)).toBe(true); | ||||
|       }); | ||||
| 
 | ||||
|       it('returns true if props.shouldScrollToFirstUiFindMatch changes to false and another props change', () => { | ||||
|  |  | |||
|  | @ -27,12 +27,12 @@ import { | |||
|   ViewedBoundsFunctionType, | ||||
| } from './utils'; | ||||
| import { Accessors } from '../ScrollManager'; | ||||
| import colorGenerator from '../utils/color-generator'; | ||||
| import { getColorByKey } from '../utils/color-generator'; | ||||
| import { TNil } from '../types'; | ||||
| import { Log, Span, Trace, KeyValuePair, Link } from '../types/trace'; | ||||
| import TTraceTimeline from '../types/TTraceTimeline'; | ||||
| 
 | ||||
| import { createStyle } from '../Theme'; | ||||
| import { createStyle, Theme, withTheme } from '../Theme'; | ||||
| 
 | ||||
| type TExtractUiFindFromStateReturn = { | ||||
|   uiFind: string | undefined; | ||||
|  | @ -63,8 +63,6 @@ type TVirtualizedTraceViewOwnProps = { | |||
|   trace: Trace; | ||||
|   focusSpan: (uiFind: string) => void; | ||||
|   linksGetter: (span: Span, items: KeyValuePair[], itemIndex: number) => Link[]; | ||||
| 
 | ||||
|   // was from redux
 | ||||
|   childrenToggle: (spanID: string) => void; | ||||
|   clearShouldScrollToFirstUiFindMatch: () => void; | ||||
|   detailLogItemToggle: (spanID: string, log: Log) => void; | ||||
|  | @ -79,6 +77,7 @@ type TVirtualizedTraceViewOwnProps = { | |||
|   hoverIndentGuideIds: Set<string>; | ||||
|   addHoverIndentGuideId: (spanID: string) => void; | ||||
|   removeHoverIndentGuideId: (spanID: string) => void; | ||||
|   theme: Theme; | ||||
| }; | ||||
| 
 | ||||
| type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TExtractUiFindFromStateReturn & TTraceTimeline; | ||||
|  | @ -144,7 +143,7 @@ function getClipping(currentViewRange: [number, number]) { | |||
| } | ||||
| 
 | ||||
| // export from tests
 | ||||
| export default class VirtualizedTraceView extends React.Component<VirtualizedTraceViewProps> { | ||||
| export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTraceViewProps> { | ||||
|   clipping: { left: boolean; right: boolean }; | ||||
|   listView: ListView | TNil; | ||||
|   rowStates: RowState[]; | ||||
|  | @ -329,12 +328,13 @@ export default class VirtualizedTraceView extends React.Component<VirtualizedTra | |||
|       hoverIndentGuideIds, | ||||
|       addHoverIndentGuideId, | ||||
|       removeHoverIndentGuideId, | ||||
|       theme, | ||||
|     } = this.props; | ||||
|     // to avert flow error
 | ||||
|     if (!trace) { | ||||
|       return null; | ||||
|     } | ||||
|     const color = colorGenerator.getColorByKey(serviceName); | ||||
|     const color = getColorByKey(serviceName, theme); | ||||
|     const isCollapsed = childrenHiddenIDs.has(spanID); | ||||
|     const isDetailExpanded = detailStates.has(spanID); | ||||
|     const isMatchingFilter = findMatchesIDs ? findMatchesIDs.has(spanID) : false; | ||||
|  | @ -347,7 +347,7 @@ export default class VirtualizedTraceView extends React.Component<VirtualizedTra | |||
|       if (rpcSpan) { | ||||
|         const rpcViewBounds = this.getViewedBounds(rpcSpan.startTime, rpcSpan.startTime + rpcSpan.duration); | ||||
|         rpc = { | ||||
|           color: colorGenerator.getColorByKey(rpcSpan.process.serviceName), | ||||
|           color: getColorByKey(rpcSpan.process.serviceName, theme), | ||||
|           operationName: rpcSpan.operationName, | ||||
|           serviceName: rpcSpan.process.serviceName, | ||||
|           viewEnd: rpcViewBounds.end, | ||||
|  | @ -402,12 +402,13 @@ export default class VirtualizedTraceView extends React.Component<VirtualizedTra | |||
|       addHoverIndentGuideId, | ||||
|       removeHoverIndentGuideId, | ||||
|       linksGetter, | ||||
|       theme, | ||||
|     } = this.props; | ||||
|     const detailState = detailStates.get(spanID); | ||||
|     if (!trace || !detailState) { | ||||
|       return null; | ||||
|     } | ||||
|     const color = colorGenerator.getColorByKey(serviceName); | ||||
|     const color = getColorByKey(serviceName, theme); | ||||
|     const styles = getStyles(); | ||||
|     return ( | ||||
|       <div className={styles.row} key={key} style={{ ...style, zIndex: 1 }} {...attrs}> | ||||
|  | @ -454,3 +455,5 @@ export default class VirtualizedTraceView extends React.Component<VirtualizedTra | |||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withTheme(UnthemedVirtualizedTraceView); | ||||
|  |  | |||
|  | @ -46,22 +46,10 @@ describe('<TraceTimelineViewer>', () => { | |||
|       search: null, | ||||
|     }, | ||||
|   }; | ||||
|   const options = { | ||||
|     context: { | ||||
|       store: { | ||||
|         getState() { | ||||
|           return { traceTimeline: { spanNameColumnWidth: 0.25 } }; | ||||
|         }, | ||||
|         subscribe() {}, | ||||
|         dispatch() {}, | ||||
|       }, | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   let wrapper; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     wrapper = shallow(<TraceTimelineViewer {...props} />, options); | ||||
|     wrapper = shallow(<TraceTimelineViewer {...props} />).dive().dive().dive(); | ||||
|   }); | ||||
| 
 | ||||
|   it('it does not explode', () => { | ||||
|  |  | |||
|  | @ -23,18 +23,18 @@ import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './ | |||
| import { TNil } from '../types'; | ||||
| import { Span, Trace, Log, KeyValuePair, Link } from '../types/trace'; | ||||
| import TTraceTimeline from '../types/TTraceTimeline'; | ||||
| import { createStyle } from '../Theme'; | ||||
| import { autoColor, createStyle, Theme, withTheme } from '../Theme'; | ||||
| import ExternalLinkContext from '../url/externalLinkContext'; | ||||
| 
 | ||||
| type TExtractUiFindFromStateReturn = { | ||||
|   uiFind: string | undefined; | ||||
| }; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     TraceTimelineViewer: css` | ||||
|       label: TraceTimelineViewer; | ||||
|       border-bottom: 1px solid #bbb; | ||||
|       border-bottom: 1px solid ${autoColor(theme, '#bbb')}; | ||||
| 
 | ||||
|       & .json-markup { | ||||
|         line-height: 17px; | ||||
|  | @ -48,19 +48,19 @@ const getStyles = createStyle(() => { | |||
|       } | ||||
| 
 | ||||
|       & .json-markup-bool { | ||||
|         color: firebrick; | ||||
|         color: ${autoColor(theme, 'firebrick')}; | ||||
|       } | ||||
| 
 | ||||
|       & .json-markup-string { | ||||
|         color: teal; | ||||
|         color: ${autoColor(theme, 'teal')}; | ||||
|       } | ||||
| 
 | ||||
|       & .json-markup-null { | ||||
|         color: teal; | ||||
|         color: ${autoColor(theme, 'teal')}; | ||||
|       } | ||||
| 
 | ||||
|       & .json-markup-number { | ||||
|         color: blue; | ||||
|         color: ${autoColor(theme, 'blue', 'black')}; | ||||
|       } | ||||
|     `,
 | ||||
|   }; | ||||
|  | @ -97,6 +97,7 @@ type TProps = TExtractUiFindFromStateReturn & { | |||
|   addHoverIndentGuideId: (spanID: string) => void; | ||||
|   removeHoverIndentGuideId: (spanID: string) => void; | ||||
|   linksGetter: (span: Span, items: KeyValuePair[], itemIndex: number) => Link[]; | ||||
|   theme: Theme; | ||||
| }; | ||||
| 
 | ||||
| type State = { | ||||
|  | @ -112,7 +113,7 @@ const NUM_TICKS = 5; | |||
|  * re-render the ListView every time the cursor is moved on the trace minimap | ||||
|  * or `TimelineHeaderRow`. | ||||
|  */ | ||||
| export default class TraceTimelineViewer extends React.PureComponent<TProps, State> { | ||||
| export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, State> { | ||||
|   constructor(props: TProps) { | ||||
|     super(props); | ||||
|     this.state = { height: 0 }; | ||||
|  | @ -151,10 +152,11 @@ export default class TraceTimelineViewer extends React.PureComponent<TProps, Sta | |||
|       viewRange, | ||||
|       createLinkToExternalSpan, | ||||
|       traceTimeline, | ||||
|       theme, | ||||
|       ...rest | ||||
|     } = this.props; | ||||
|     const { trace } = rest; | ||||
|     const styles = getStyles(); | ||||
|     const styles = getStyles(theme); | ||||
| 
 | ||||
|     return ( | ||||
|       <ExternalLinkContext.Provider value={createLinkToExternalSpan}> | ||||
|  | @ -187,3 +189,5 @@ export default class TraceTimelineViewer extends React.PureComponent<TProps, Sta | |||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default withTheme(UnthemedTraceTimelineViewer); | ||||
|  |  | |||
|  | @ -16,10 +16,10 @@ import * as React from 'react'; | |||
| import { css } from 'emotion'; | ||||
| import cx from 'classnames'; | ||||
| 
 | ||||
| import { createStyle } from '../Theme'; | ||||
| import { createStyle, isLight, Theme, useTheme } from '../Theme'; | ||||
| import { UIDivider } from '../uiElementsContext'; | ||||
| 
 | ||||
| const getStyles = createStyle(() => { | ||||
| const getStyles = createStyle((theme: Theme) => { | ||||
|   return { | ||||
|     LabeledList: css` | ||||
|       label: LabeledList; | ||||
|  | @ -33,7 +33,7 @@ const getStyles = createStyle(() => { | |||
|     `,
 | ||||
|     LabeledListLabel: css` | ||||
|       label: LabeledListLabel; | ||||
|       color: #999; | ||||
|       color: ${isLight(theme) ? '#999' : '#666'}; | ||||
|       margin-right: 0.25rem; | ||||
|     `,
 | ||||
|   }; | ||||
|  | @ -47,7 +47,7 @@ type LabeledListProps = { | |||
| 
 | ||||
| export default function LabeledList(props: LabeledListProps) { | ||||
|   const { className, dividerClassName, items } = props; | ||||
|   const styles = getStyles(); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   return ( | ||||
|     <ul className={cx(styles.LabeledList, className)}> | ||||
|       {items.map(({ key, label, value }, i) => { | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ export * from './TraceTimelineViewer/types'; | |||
| export { default as DetailState } from './TraceTimelineViewer/SpanDetail/DetailState'; | ||||
| export { default as transformTraceData } from './model/transform-trace-data'; | ||||
| export { default as filterSpans } from './utils/filter-spans'; | ||||
| export * from './Theme'; | ||||
| 
 | ||||
| import { onlyUpdateForKeys } from 'recompose'; | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,26 +12,27 @@ | |||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import colorGenerator from './color-generator'; | ||||
| import { getColorByKey, clear } from './color-generator'; | ||||
| import { defaultTheme } from '../Theme'; | ||||
| 
 | ||||
| it('gives the same color for the same key', () => { | ||||
|   colorGenerator.clear(); | ||||
|   const colorOne = colorGenerator.getColorByKey('serviceA'); | ||||
|   const colorTwo = colorGenerator.getColorByKey('serviceA'); | ||||
|   clear(); | ||||
|   const colorOne = getColorByKey('serviceA', defaultTheme); | ||||
|   const colorTwo = getColorByKey('serviceA', defaultTheme); | ||||
|   expect(colorOne).toBe(colorTwo); | ||||
| }); | ||||
| 
 | ||||
| it('gives different colors for each for each key', () => { | ||||
|   colorGenerator.clear(); | ||||
|   const colorOne = colorGenerator.getColorByKey('serviceA'); | ||||
|   const colorTwo = colorGenerator.getColorByKey('serviceB'); | ||||
|   clear(); | ||||
|   const colorOne = getColorByKey('serviceA', defaultTheme); | ||||
|   const colorTwo = getColorByKey('serviceB', defaultTheme); | ||||
|   expect(colorOne).not.toBe(colorTwo); | ||||
| }); | ||||
| 
 | ||||
| it('should clear cache', () => { | ||||
|   colorGenerator.clear(); | ||||
|   const colorOne = colorGenerator.getColorByKey('serviceA'); | ||||
|   colorGenerator.clear(); | ||||
|   const colorTwo = colorGenerator.getColorByKey('serviceB'); | ||||
|   clear(); | ||||
|   const colorOne = getColorByKey('serviceA', defaultTheme); | ||||
|   clear(); | ||||
|   const colorTwo = getColorByKey('serviceB', defaultTheme); | ||||
|   expect(colorOne).toBe(colorTwo); | ||||
| }); | ||||
|  |  | |||
|  | @ -12,6 +12,8 @@ | |||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { isLight, Theme } from '../Theme'; | ||||
| 
 | ||||
| const COLORS_HEX = [ | ||||
|   '#17B8BE', | ||||
|   '#F8DCA1', | ||||
|  | @ -35,6 +37,27 @@ const COLORS_HEX = [ | |||
|   '#776E57', | ||||
| ]; | ||||
| 
 | ||||
| const COLORS_HEX_DARK = [ | ||||
|   '#17B8BE', | ||||
|   '#F8DCA1', | ||||
|   '#B7885E', | ||||
|   '#FFCB99', | ||||
|   '#F89570', | ||||
|   '#829AE3', | ||||
|   '#E79FD5', | ||||
|   '#1E96BE', | ||||
|   '#89DAC1', | ||||
|   '#B3AD9E', | ||||
|   '#12939A', | ||||
|   '#DDB27C', | ||||
|   '#88572C', | ||||
|   '#FF9833', | ||||
|   '#EF5D28', | ||||
|   '#DA70BF', | ||||
|   '#4DC19C', | ||||
|   '#776E57', | ||||
| ]; | ||||
| 
 | ||||
| // TS needs the precise return type
 | ||||
| function strToRgb(s: string): [number, number, number] { | ||||
|   if (s.length !== 7) { | ||||
|  | @ -46,13 +69,13 @@ function strToRgb(s: string): [number, number, number] { | |||
|   return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)]; | ||||
| } | ||||
| 
 | ||||
| export class ColorGenerator { | ||||
| class ColorGenerator { | ||||
|   colorsHex: string[]; | ||||
|   colorsRgb: Array<[number, number, number]>; | ||||
|   cache: Map<string, number>; | ||||
|   currentIdx: number; | ||||
| 
 | ||||
|   constructor(colorsHex: string[] = COLORS_HEX) { | ||||
|   constructor(colorsHex: string[]) { | ||||
|     this.colorsHex = colorsHex; | ||||
|     this.colorsRgb = colorsHex.map(strToRgb); | ||||
|     this.cache = new Map(); | ||||
|  | @ -95,4 +118,18 @@ export class ColorGenerator { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| export default new ColorGenerator(); | ||||
| let darkGenerator = new ColorGenerator(COLORS_HEX_DARK); | ||||
| let lightGenerator = new ColorGenerator(COLORS_HEX); | ||||
| 
 | ||||
| export function clear() { | ||||
|   darkGenerator = new ColorGenerator(COLORS_HEX_DARK); | ||||
|   lightGenerator = new ColorGenerator(COLORS_HEX); | ||||
| } | ||||
| 
 | ||||
| export function getColorByKey(key: string, theme: Theme) { | ||||
|   return (isLight(theme) ? lightGenerator : darkGenerator).getColorByKey(key); | ||||
| } | ||||
| 
 | ||||
| export function getRgbColorByKey(key: string, theme: Theme): [number, number, number] { | ||||
|   return (isLight(theme) ? lightGenerator : darkGenerator).getRgbColorByKey(key); | ||||
| } | ||||
|  |  | |||
|  | @ -3,14 +3,16 @@ import { | |||
|   KeyValuePair, | ||||
|   Link, | ||||
|   Span, | ||||
|   Trace, | ||||
|   TraceTimelineViewer, | ||||
|   TTraceTimeline, | ||||
|   UIElementsContext, | ||||
|   transformTraceData, | ||||
|   SpanData, | ||||
|   ThemeProvider, | ||||
|   ThemeType, | ||||
|   Trace, | ||||
|   TraceData, | ||||
|   TracePageHeader, | ||||
|   TraceTimelineViewer, | ||||
|   transformTraceData, | ||||
|   TTraceTimeline, | ||||
|   UIElementsContext, | ||||
| } from '@jaegertracing/jaeger-ui-components'; | ||||
| import { UIElements } from './uiElements'; | ||||
| import { useViewRange } from './useViewRange'; | ||||
|  | @ -18,6 +20,7 @@ import { useSearch } from './useSearch'; | |||
| import { useChildrenState } from './useChildrenState'; | ||||
| import { useDetailState } from './useDetailState'; | ||||
| import { useHoverIndentGuide } from './useHoverIndentGuide'; | ||||
| import { useTheme } from '@grafana/ui'; | ||||
| 
 | ||||
| type Props = { | ||||
|   trace: TraceData & { spans: SpanData[] }; | ||||
|  | @ -49,8 +52,14 @@ export function TraceView(props: Props) { | |||
| 
 | ||||
|   const traceProp = transformTraceData(props.trace); | ||||
|   const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans); | ||||
|   const theme = useTheme(); | ||||
| 
 | ||||
|   return ( | ||||
|     <ThemeProvider | ||||
|       value={{ | ||||
|         type: theme.isDark ? ThemeType.Dark : ThemeType.Light, | ||||
|       }} | ||||
|     > | ||||
|       <UIElementsContext.Provider value={UIElements}> | ||||
|         <TracePageHeader | ||||
|           canCollapse={true} | ||||
|  | @ -115,5 +124,6 @@ export function TraceView(props: Props) { | |||
|           uiFind={search} | ||||
|         /> | ||||
|       </UIElementsContext.Provider> | ||||
|     </ThemeProvider> | ||||
|   ); | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,9 @@ | |||
| import React from 'react'; | ||||
| import { ButtonProps, Elements } from '@jaegertracing/jaeger-ui-components'; | ||||
| import { Button, Input } from '@grafana/ui'; | ||||
| import { Button, Input, stylesFactory, useTheme } from '@grafana/ui'; | ||||
| import { css } from 'emotion'; | ||||
| import { GrafanaTheme } from '@grafana/data'; | ||||
| import cx from 'classnames'; | ||||
| 
 | ||||
| /** | ||||
|  * Right now Jaeger components need some UI elements to be injected. This is to get rid of AntD UI library that was | ||||
|  | @ -29,17 +32,20 @@ export const UIElements: Elements = { | |||
|   ), | ||||
| }; | ||||
| 
 | ||||
| const getStyles = stylesFactory((theme: GrafanaTheme) => { | ||||
|   return { | ||||
|     Divider: css` | ||||
|       display: inline-block; | ||||
|       background: ${theme.isDark ? '#242424' : '#e8e8e8'}; | ||||
|       width: 1px; | ||||
|       height: 0.9em; | ||||
|       margin: 0 8px; | ||||
|       vertical-align: middle; | ||||
|     `,
 | ||||
|   }; | ||||
| }); | ||||
| 
 | ||||
| function Divider({ className }: { className?: string }) { | ||||
|   return ( | ||||
|     <div | ||||
|       style={{ | ||||
|         display: 'inline-block', | ||||
|         background: '#e8e8e8', | ||||
|         width: '1px', | ||||
|         height: '0.9em', | ||||
|         margin: '0 8px', | ||||
|       }} | ||||
|       className={className} | ||||
|     /> | ||||
|   ); | ||||
|   const styles = getStyles(useTheme()); | ||||
|   return <div style={{}} className={cx(styles.Divider, className)} />; | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue