Commit cf1ebd5a by Andrej Ocenas Committed by GitHub

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>
parent 754dfdfa
......@@ -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) => (
<Component
{...({
...props,
theme,
} as Props & { 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 &amp; Operation</h3>
<TimelineRow.Cell className={cx(ubFlex, ubPx2, styles.TimelineHeaderWrapper)} width={nameColumnWidth}>
<h4 className={styles.TimelineHeaderRowTitle}>Service &amp; 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,
SpanData,
ThemeProvider,
ThemeType,
Trace,
TraceData,
TracePageHeader,
TraceTimelineViewer,
transformTraceData,
TTraceTimeline,
UIElementsContext,
transformTraceData,
SpanData,
TraceData,
TracePageHeader,
} 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,71 +52,78 @@ export function TraceView(props: Props) {
const traceProp = transformTraceData(props.trace);
const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans);
const theme = useTheme();
return (
<UIElementsContext.Provider value={UIElements}>
<TracePageHeader
canCollapse={true}
clearSearch={() => {}}
focusUiFindMatches={() => {}}
hideMap={false}
hideSummary={false}
nextResult={() => {}}
onSlimViewClicked={() => setSlim(!slim)}
onTraceGraphViewClicked={() => {}}
prevResult={() => {}}
resultCount={0}
slimView={slim}
textFilter={null}
trace={traceProp}
traceGraphView={false}
updateNextViewRangeTime={updateNextViewRangeTime}
updateViewRangeTime={updateViewRangeTime}
viewRange={viewRange}
searchValue={search}
onSearchValueChange={setSearch}
hideSearchButtons={true}
/>
<TraceTimelineViewer
registerAccessors={() => {}}
scrollToFirstVisibleSpan={() => {}}
findMatchesIDs={spanFindMatches}
trace={traceProp}
traceTimeline={
{
childrenHiddenIDs,
detailStates,
hoverIndentGuideIds,
shouldScrollToFirstUiFindMatch: false,
spanNameColumnWidth,
traceID: '50b96206cf81dd64',
} as TTraceTimeline
}
updateNextViewRangeTime={updateNextViewRangeTime}
updateViewRangeTime={updateViewRangeTime}
viewRange={viewRange}
focusSpan={() => {}}
createLinkToExternalSpan={() => ''}
setSpanNameColumnWidth={setSpanNameColumnWidth}
collapseAll={collapseAll}
collapseOne={collapseOne}
expandAll={expandAll}
expandOne={expandOne}
childrenToggle={childrenToggle}
clearShouldScrollToFirstUiFindMatch={() => {}}
detailLogItemToggle={detailLogItemToggle}
detailLogsToggle={detailLogsToggle}
detailWarningsToggle={detailWarningsToggle}
detailReferencesToggle={detailReferencesToggle}
detailProcessToggle={detailProcessToggle}
detailTagsToggle={detailTagsToggle}
detailToggle={toggleDetail}
setTrace={(trace: Trace | null, uiFind: string | null) => {}}
addHoverIndentGuideId={addHoverIndentGuideId}
removeHoverIndentGuideId={removeHoverIndentGuideId}
linksGetter={(span: Span, items: KeyValuePair[], itemIndex: number) => [] as Link[]}
uiFind={search}
/>
</UIElementsContext.Provider>
<ThemeProvider
value={{
type: theme.isDark ? ThemeType.Dark : ThemeType.Light,
}}
>
<UIElementsContext.Provider value={UIElements}>
<TracePageHeader
canCollapse={true}
clearSearch={() => {}}
focusUiFindMatches={() => {}}
hideMap={false}
hideSummary={false}
nextResult={() => {}}
onSlimViewClicked={() => setSlim(!slim)}
onTraceGraphViewClicked={() => {}}
prevResult={() => {}}
resultCount={0}
slimView={slim}
textFilter={null}
trace={traceProp}
traceGraphView={false}
updateNextViewRangeTime={updateNextViewRangeTime}
updateViewRangeTime={updateViewRangeTime}
viewRange={viewRange}
searchValue={search}
onSearchValueChange={setSearch}
hideSearchButtons={true}
/>
<TraceTimelineViewer
registerAccessors={() => {}}
scrollToFirstVisibleSpan={() => {}}
findMatchesIDs={spanFindMatches}
trace={traceProp}
traceTimeline={
{
childrenHiddenIDs,
detailStates,
hoverIndentGuideIds,
shouldScrollToFirstUiFindMatch: false,
spanNameColumnWidth,
traceID: '50b96206cf81dd64',
} as TTraceTimeline
}
updateNextViewRangeTime={updateNextViewRangeTime}
updateViewRangeTime={updateViewRangeTime}
viewRange={viewRange}
focusSpan={() => {}}
createLinkToExternalSpan={() => ''}
setSpanNameColumnWidth={setSpanNameColumnWidth}
collapseAll={collapseAll}
collapseOne={collapseOne}
expandAll={expandAll}
expandOne={expandOne}
childrenToggle={childrenToggle}
clearShouldScrollToFirstUiFindMatch={() => {}}
detailLogItemToggle={detailLogItemToggle}
detailLogsToggle={detailLogsToggle}
detailWarningsToggle={detailWarningsToggle}
detailReferencesToggle={detailReferencesToggle}
detailProcessToggle={detailProcessToggle}
detailTagsToggle={detailTagsToggle}
detailToggle={toggleDetail}
setTrace={(trace: Trace | null, uiFind: string | null) => {}}
addHoverIndentGuideId={addHoverIndentGuideId}
removeHoverIndentGuideId={removeHoverIndentGuideId}
linksGetter={(span: Span, items: KeyValuePair[], itemIndex: number) => [] as Link[]}
uiFind={search}
/>
</UIElementsContext.Provider>
</ThemeProvider>
);
}
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)} />;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment