Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support upload trace in Zipkin Lens UI #2447

Merged
merged 11 commits into from
Mar 21, 2019
Merged
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions zipkin-lens/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zipkin-lens/package.json
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@
"react": "^16.4.1",
"react-chartjs-2": "^2.7.4",
"react-dom": "^16.4.1",
"react-modal": "^3.8.1",
"react-numeric-input": "^2.2.3",
"react-redux": "^5.0.7",
"react-router": "^4.3.1",
13 changes: 0 additions & 13 deletions zipkin-lens/scss/components/_condition-trace-id.scss

This file was deleted.

80 changes: 80 additions & 0 deletions zipkin-lens/scss/components/_global-dropdown-menu.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
.global-dropdown-menu {
height: 50px;
display: flex;
align-items: center;
}

.global-dropdown-menu__modal-wrapper {
position:relative;
}

.global-dropdown-menu__button {
width: 35px;
height: 35px;
font-size: 24px;
border-radius: 3px;
border: solid 1px $gray-7;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(to bottom, $gray-10, $gray-9);
}

.global-dropdown-menu__modal {
}

.global-dropdown-menu__overlay {
position: absolute;
right: 0;
top: 20px;
background-color: $white;
border: solid $gray-7 1px;
border-radius: 3px;
overflow: hidden;
}

.global-dropdown-menu__trace-id {
width: 100%;
padding: 3px 4px 6px 4px;
box-sizing: border-box;
border-bottom: 1px solid $gray-7;
}

.global-dropdown-menu__trace-id-label {
font-size: 16px;
}

.global-dropdown-menu__trace-id-search {
display: flex;
}

.global-dropdown-menu__trace-id-input {
border: solid $gray-8 1px;
border-radius: 2px;
height: 18px;
width: 160px;
margin-right: 3px;
}

.global-dropdown-menu__trace-id-button {
width: 20px;
height: 20px;
background: linear-gradient(to bottom, $gray-10, $gray-9);
display: flex;
justify-content: center;
align-items: center;
border-radius: 3px;
border: solid 1px $gray-7;
cursor: pointer;
}

.global-dropdown-menu__trace-json {
font-size: 16px;
padding: 3px 4px 3px 4px;
box-sizing: border-box;
cursor: pointer;
&:hover {
background-color: $gray-10;
}
}
4 changes: 4 additions & 0 deletions zipkin-lens/scss/components/_global-search.scss
Original file line number Diff line number Diff line change
@@ -72,6 +72,10 @@
background: linear-gradient(to bottom, $gray-10, $gray-9);
}

.global-search__dropdown-menu-wrapper {
margin-left: 10px;
}

.global-search__find-button {
width: 28px;
height: 28px;
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
.mini-trace-viewer {
.mini-timeline {
width: 96%;
padding: 2px;
margin: 5px;
}

.mini-trace-viewer__time-markers-wrapper {
.mini-timeline__time-marker-labels-wrapper {
position: relative;
height: 14px;
}

.mini-trace-viewer__time-marker {
.mini-timeline__time-marker {
position: absolute;
}

.mini-trace-viewer__time-marker-label {
.mini-timeline__time-marker-label {
color: $dark-2;
font-size: $font-size-xs;
position: absolute;
left: -20px;

&.first {
&--first {
left: 2px;
position: absolute;
}

&.last {
&--last {
left: initial;
right: 2px;
position: absolute;
}
}

.mini-trace-viewer__graph {
.mini-timeline__graph {
padding: 2px;
background-color: $gray-10;
height: 75px;
3 changes: 3 additions & 0 deletions zipkin-lens/scss/components/_trace-page.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.trace-page {

}
12 changes: 12 additions & 0 deletions zipkin-lens/scss/custom/_react-modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.ReactModal__Overlay {
opacity: 0;
transition: opacity 200ms ease-in-out;
}

.ReactModal__Overlay--after-open {
opacity: 1;
}

.ReactModal__Overlay--before-close {
opacity: 0;
}
6 changes: 4 additions & 2 deletions zipkin-lens/scss/main.scss
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@

@import 'custom/rc-calendar';
@import 'custom/rc-time-picker';
@import 'custom/react-modal';
@import 'custom/react-select';
@import 'custom/vizceral';

@@ -19,19 +20,20 @@
@import 'components/condition-lookback';
@import 'components/condition-name';
@import 'components/condition-tags';
@import 'components/condition-trace-id';
@import 'components/date-picker';
@import 'components/dependencies-sidebar';
@import 'components/dependencies';
@import 'components/detailed-trace-summary';
@import 'components/global-dropdown-menu';
@import 'components/global-search';
@import 'components/loading-overlay';
@import 'components/mini-trace-viewer';
@import 'components/mini-timeline';
@import 'components/search-condition';
@import 'components/service-name-badge';
@import 'components/sidebar';
@import 'components/timeline';
@import 'components/timeline-header';
@import 'components/timeline-span';
@import 'components/timeline-span-data';
@import 'components/trace-page';
@import 'components/trace-summary';
6 changes: 6 additions & 0 deletions zipkin-lens/src/actions/trace-viewer-action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as types from '../constants/action-types';

export const setTrace = trace => ({
type: types.TRACE_VIEWER_SET_TRACE,
trace,
});
13 changes: 13 additions & 0 deletions zipkin-lens/src/actions/trace-viewer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as actions from './trace-viewer-action';
import * as types from '../constants/action-types';

describe('trace viewer actions', () => {
it('should create an action to set the trace', () => {
const trace = {};
const expectedAction = {
type: types.TRACE_VIEWER_SET_TRACE,
trace,
};
expect(actions.setTrace(trace)).toEqual(expectedAction);
});
});
12 changes: 10 additions & 2 deletions zipkin-lens/src/components/App/index.js
Original file line number Diff line number Diff line change
@@ -4,8 +4,9 @@ import { BrowserRouter, Route } from 'react-router-dom';

import Layout from './Layout';
import BrowserContainer from '../../containers/Browser/BrowserContainer';
import DetailedTraceSummaryContainer from '../../containers/DetailedTraceSummary/DetailedTraceSummaryContainer';
import TracePageContainer from '../../containers/TracePage/TracePageContainer';
import DependenciesContainer from '../../containers/Dependencies/DependenciesContainer';
import TraceViewerContainer from '../../containers/TraceViewer/TraceViewerContainer';
import configureStore from '../../store/configure-store';

const applicationTitle = 'Zipkin';
@@ -31,7 +32,7 @@ class App extends React.Component {
exact
path="/zipkin/traces/:traceId"
render={props => (
<DetailedTraceSummaryContainer {...props} />
<TracePageContainer {...props} />
)}
/>
<Route
@@ -41,6 +42,13 @@ class App extends React.Component {
<DependenciesContainer {...props} />
)}
/>
<Route
exact
path="/zipkin/traceViewer"
render={props => (
<TraceViewerContainer {...props} />
)}
/>
</Layout>
</BrowserRouter>
</Provider>
90 changes: 32 additions & 58 deletions zipkin-lens/src/components/DetailedTraceSummary/index.js
Original file line number Diff line number Diff line change
@@ -2,43 +2,24 @@ import PropTypes from 'prop-types';
import React from 'react';

import Timeline from '../Timeline';
import LoadingOverlay from '../Common/LoadingOverlay';
import MiniTraceViewer from './MiniTraceViewer';
import MiniTimeline from '../MiniTimeline';
import { detailedTraceSummaryPropTypes } from '../../prop-types';

const propTypes = {
isLoading: PropTypes.bool.isRequired,
traceId: PropTypes.string.isRequired, /* From url parameter */
traceSummary: PropTypes.shape({}),
fetchTrace: PropTypes.func.isRequired,
startTs: PropTypes.number,
endTs: PropTypes.number,
onStartAndEndTsChange: PropTypes.func.isRequired,
traceSummary: detailedTraceSummaryPropTypes.isRequired,
};

const defaultProps = {
traceSummary: null,
startTs: null,
endTs: null,
};

class DetailedTraceSummary extends React.Component {
constructor(props) {
super(props);
this.state = {
startTs: null,
endTs: null,
};
this.handleStartAndEndTsChange = this.handleStartAndEndTsChange.bind(this);
}

componentDidMount() {
const { fetchTrace, traceId, traceSummary } = this.props;
if (!traceSummary || traceSummary.traceId !== traceId) {
fetchTrace(traceId);
}
}

handleStartAndEndTsChange(startTs, endTs) {
this.setState({ startTs, endTs });
}

renderHeader() {
const { traceId, traceSummary } = this.props;
const { traceSummary } = this.props;
const {
durationStr,
serviceNameAndSpanCounts,
@@ -49,7 +30,7 @@ class DetailedTraceSummary extends React.Component {
return (
<div className="detailed-trace-summary__header">
<div className="detailed-trace-summary__trace-id">
{traceId}
{traceSummary.traceId}
</div>
<div className="detailed-trace-summary__trace-data-list">
{
@@ -78,37 +59,30 @@ class DetailedTraceSummary extends React.Component {
}

render() {
const { startTs, endTs } = this.state;
const { isLoading, traceId, traceSummary } = this.props;
const {
startTs,
endTs,
onStartAndEndTsChange,
traceSummary,
} = this.props;

return (
<div>
<LoadingOverlay active={isLoading} />
<div className="detailed-trace-summary">
{
(!traceSummary || traceSummary.traceId !== traceId)
? null
: (
<div>
{this.renderHeader()}
<div className="detailed-trace-summary__mini-trace-viewer-wrapper">
<MiniTraceViewer
startTs={startTs || 0}
endTs={endTs || traceSummary.duration}
traceSummary={traceSummary}
onStartAndEndTsChange={this.handleStartAndEndTsChange}
/>
</div>
<div className="detailed-trace-summary__timeline-wrapper">
<Timeline
startTs={startTs || 0}
endTs={endTs || traceSummary.duration}
traceSummary={traceSummary}
/>
</div>
</div>
)
}
<div className="detailed-trace-summary">
{this.renderHeader()}
<div className="detailed-trace-summary__mini-trace-viewer-wrapper">
<MiniTimeline
startTs={startTs || 0}
endTs={endTs || traceSummary.duration}
traceSummary={traceSummary}
onStartAndEndTsChange={onStartAndEndTsChange}
/>
</div>
<div className="detailed-trace-summary__timeline-wrapper">
<Timeline
startTs={startTs || 0}
endTs={endTs || traceSummary.duration}
traceSummary={traceSummary}
/>
</div>
</div>
);
65 changes: 0 additions & 65 deletions zipkin-lens/src/components/GlobalSearch/ConditionTraceId.js

This file was deleted.

139 changes: 139 additions & 0 deletions zipkin-lens/src/components/GlobalSearch/GlobalDropdownMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import PropTypes from 'prop-types';
import React from 'react';
import { withRouter } from 'react-router';
import Modal from 'react-modal';

const propTypes = {
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}).isRequired,
setTrace: PropTypes.func.isRequired,
};

// This selector (class name) is used to specify a modal parent component.
const modalWrapperClass = 'global-dropdown-menu__modal-wrapper';

class GlobalDropdownMenu extends React.Component {
constructor(props) {
super(props);
this.fileInpueElement = undefined;
this.state = {
isModalOpened: false,
traceId: '',
};
this.handleOpenModalToggle = this.handleOpenModalToggle.bind(this);
this.handleTraceIdButtonClick = this.handleTraceIdButtonClick.bind(this);
this.handleTraceIdChange = this.handleTraceIdChange.bind(this);
this.handleTraceJsonChange = this.handleTraceJsonChange.bind(this);
}

handleOpenModalToggle() {
const { isModalOpened } = this.state;
this.setState({ isModalOpened: !isModalOpened });
}

handleTraceIdButtonClick(event) {
const { history } = this.props;
const { traceId } = this.state;
history.push({
pathname: `/zipkin/traces/${traceId}`,
});
this.setState({ isModalOpened: false });
event.stopPropagation();
}

handleTraceIdChange(event) {
this.setState({
traceId: event.target.value,
});
}

handleTraceJsonChange(event) {
const { history, setTrace } = this.props;

const [file] = event.target.files;
const fileReader = new FileReader();

fileReader.onload = () => {
const { result } = fileReader;
let rawTrace;
try {
rawTrace = JSON.parse(result);
setTrace(rawTrace);
history.push({
pathname: '/zipkin/traceViewer',
});
} catch (error) {
// Do nothing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe alert the json is malformed? I assume this catch will result in someone uploading an incorrect file (ex just one span) and getting no feedback about it. correct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.
Do you think that it is better to display the alert dialog?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old UI did something like this..

Screen Shot 2019-03-19 at 9 25 51 AM

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, Thank you.
I'll do so

}
};
fileReader.readAsText(file);
this.setState({ isModalOpened: false });
}

renderModal() {
const { isModalOpened, traceId } = this.state;
return (
<Modal
className="global-dropdown-menu__modal"
overlayClassName="global-dropdown-menu__overlay"
isOpen={isModalOpened}
parentSelector={() => document.querySelector(`.${modalWrapperClass}`)}
>
<div className="global-dropdown-menu__trace-id">
<div className="global-dropdown-menu__trace-id-label">Trace ID</div>
<div className="global-dropdown-menu__trace-id-search">
<input
className="global-dropdown-menu__trace-id-input"
type="text"
value={traceId}
onChange={this.handleTraceIdChange}
/>
<span
className="global-dropdown-menu__trace-id-button"
role="presentation"
onClick={this.handleTraceIdButtonClick}
>
<i className="fas fa-search" />
</span>
</div>
</div>
<div className="global-dropdown-menu__trace-json">
<input
type="file"
style={{ display: 'none' }}
ref={(element) => { this.fileInpueElement = element; }}
onChange={this.handleTraceJsonChange}
/>
<div
role="presentation"
onClick={() => { this.fileInpueElement.click(); }}
>
Choose JSON file...
</div>
</div>
</Modal>
);
}

render() {
return (
<div className="global-dropdown-menu">
<div
className="global-dropdown-menu__button"
onClick={this.handleOpenModalToggle}
role="presentation"
>
<i className="fas fa-bars" />
</div>
<div className={modalWrapperClass}>
{this.renderModal()}
</div>
</div>
);
}
}

GlobalDropdownMenu.propTypes = propTypes;

export default withRouter(GlobalDropdownMenu);
26 changes: 4 additions & 22 deletions zipkin-lens/src/components/GlobalSearch/index.js
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@ import SearchCondition from './SearchCondition';
import ConditionDuration from './ConditionDuration';
import ConditionLimit from './ConditionLimit';
import ConditionName from './ConditionName';
import ConditionTraceId from './ConditionTraceId';
import ConditionTags from './ConditionTags';
import ConditionLookback from './ConditionLookback';
import GlobalDropdownMenuContainer from '../../containers/GlobalSearch/GlobalDropdownMenuContainer';
import {
isAutocompleteKey,
defaultConditionValues,
@@ -169,15 +169,6 @@ class GlobalSearch extends React.Component {
history, conditions, lookbackCondition, limitCondition,
} = this.props;

// If traceId is specified, jump to the trace page.
const traceIdCondition = conditions.find(condition => condition.key === 'traceId');
if (traceIdCondition) {
history.push({
pathname: `/zipkin/traces/${traceIdCondition.value}`,
});
return;
}

const queryParams = buildQueryParametersWithConditions(
conditions,
lookbackCondition,
@@ -297,18 +288,6 @@ class GlobalSearch extends React.Component {
isFocused={isFocused}
/>
);
case 'traceId':
return ({
onFocus, onBlur, setNextFocusRef, isFocused,
}) => (
<ConditionTraceId
{...commonProps}
onFocus={onFocus}
onBlur={onBlur}
setNextFocusRef={setNextFocusRef}
isFocused={isFocused}
/>
);
case 'tags':
return ({
onFocus, onBlur, setNextFocusRef, isFocused,
@@ -438,6 +417,9 @@ class GlobalSearch extends React.Component {
onLookbackChange={this.handleLookbackChange}
/>
</div>
<div className="global-search__dropdown-menu-wrapper">
<GlobalDropdownMenuContainer />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -3,43 +3,41 @@ import React from 'react';

import { formatDuration } from '../../util/timestamp';
import { getServiceNameColor } from '../../util/color';
import { detailedTraceSummaryPropTypes } from '../../prop-types';

const propTypes = {
startTs: PropTypes.number.isRequired,
endTs: PropTypes.number.isRequired,
traceSummary: PropTypes.shape({}).isRequired,
traceSummary: detailedTraceSummaryPropTypes.isRequired,
onStartAndEndTsChange: PropTypes.func.isRequired,
};

const GRAPH_HEIGHT = 75;
const NUM_TICKS = 5;
const LEFT_MOUSE_BUTTON = 0;
const graphHeight = 75;
const numTimeMarkers = 5;
const leftMouseButton = 0;

const renderTickLines = () => {
const renderTimeMarkers = () => {
const timeMarkers = [];
for (let i = 1; i < NUM_TICKS - 1; i += 1) {
const portion = 100 / (NUM_TICKS - 1) * i;
for (let i = 1; i < numTimeMarkers - 1; i += 1) {
const portion = 100 / (numTimeMarkers - 1) * i;
timeMarkers.push(
<line
key={portion}
x1={`${portion}%`}
x2={`${portion}%`}
y1="0"
y2={GRAPH_HEIGHT}
y2={graphHeight}
/>,
);
}
return (
<g
stroke="#888"
strokeWidth="1"
>
<g stroke="#888" strokeWidth="1">
{timeMarkers}
</g>
);
};

class MiniTraceViewer extends React.Component {
class MiniTimeline extends React.Component {
constructor(props) {
super(props);
this.state = {
@@ -66,7 +64,7 @@ class MiniTraceViewer extends React.Component {
}

handleMouseDown(event) {
if (event.button !== LEFT_MOUSE_BUTTON) {
if (event.button !== leftMouseButton) {
return;
}
const currentX = this.getPosition(event.clientX);
@@ -86,18 +84,9 @@ class MiniTraceViewer extends React.Component {
}

handleMouseUp(event) {
const {
traceSummary,
onStartAndEndTsChange,
} = this.props;

const {
dragStartX,
} = this.state;

this.setState({
isDragging: false,
});
const { traceSummary, onStartAndEndTsChange } = this.props;
const { dragStartX } = this.state;
this.setState({ isDragging: false });

let startTs;
let endTs;
@@ -116,41 +105,37 @@ class MiniTraceViewer extends React.Component {
}

handleDoubleClick() {
const {
traceSummary,
onStartAndEndTsChange,
} = this.props;

const { traceSummary, onStartAndEndTsChange } = this.props;
onStartAndEndTsChange(0, traceSummary.duration);
}

renderTicks() {
const {
traceSummary,
} = this.props;
renderTimeMarkerLabels() {
const { traceSummary } = this.props;

const timeMarkers = [];
for (let i = 0; i < NUM_TICKS; i += 1) {
const label = formatDuration((i / (NUM_TICKS - 1)) * (traceSummary.duration));
for (let i = 0; i < numTimeMarkers; i += 1) {
const label = formatDuration((i / (numTimeMarkers - 1)) * (traceSummary.duration));

const portion = i / (NUM_TICKS - 1);
const portion = i / (numTimeMarkers - 1);

let portionClassName = '';
let modifier = '';
if (portion === 0) {
portionClassName = 'first';
modifier = '--first';
} else if (portion >= 1) {
portionClassName = 'last';
modifier = '--last';
}

timeMarkers.push(
<div
key={portion}
className="mini-trace-viewer__time-marker"
className="mini-timeline__time-marker"
style={{
left: `${portion * 100}%`,
}}
>
<span className={`mini-trace-viewer__time-marker-label ${portionClassName}`}>
<span className={
`mini-timeline__time-marker-label mini-timeline__time-marker-label${modifier}`}
>
{label}
</span>
</div>,
@@ -164,40 +149,25 @@ class MiniTraceViewer extends React.Component {
}

render() {
const {
traceSummary,
startTs,
endTs,
} = this.props;

const {
isDragging,
dragStartX,
dragCurrentX,
} = this.state;

const {
spans,
} = traceSummary;

const lineHeight = GRAPH_HEIGHT / spans.length;
const { traceSummary, startTs, endTs } = this.props;
const { isDragging, dragStartX, dragCurrentX } = this.state;
const { spans } = traceSummary;
const lineHeight = graphHeight / spans.length;

return (
<div className="mini-trace-viewer">
<div className="mini-trace-viewer__time-markers-wrapper">
{this.renderTicks()}
<div className="mini-timeline">
<div className="mini-timeline__time-marker-labels-wrapper">
{this.renderTimeMarkerLabels()}
</div>
<div
className="mini-trace-viewer__graph"
className="mini-timeline__graph"
ref={this.setGraphElement}
role="presentation"
onMouseDown={this.handleMouseDown}
onDoubleClick={this.handleDoubleClick}
>
<svg version="1.1" width="100%" height={GRAPH_HEIGHT} xmlns="http://www.w3.org/2000/svg">
{
renderTickLines()
}
<svg version="1.1" width="100%" height={graphHeight} xmlns="http://www.w3.org/2000/svg">
{renderTimeMarkers()}
{
spans.map((span, i) => (
<rect
@@ -218,20 +188,20 @@ class MiniTraceViewer extends React.Component {
x1={`${dragStartX * 100}%`}
x2={`${dragStartX * 100}%`}
y1={0}
y2={GRAPH_HEIGHT}
y2={graphHeight}
/>
<line
x1={`${dragStartX * 100}%`}
x2={`${dragCurrentX * 100}%`}
y1={GRAPH_HEIGHT / 2}
y2={GRAPH_HEIGHT / 2}
y1={graphHeight / 2}
y2={graphHeight / 2}

/>
<line
x1={`${dragCurrentX * 100}%`}
x2={`${dragCurrentX * 100}%`}
y1={0}
y2={GRAPH_HEIGHT}
y2={graphHeight}
/>
</g>
)
@@ -242,7 +212,7 @@ class MiniTraceViewer extends React.Component {
? (
<rect
width={`${startTs / traceSummary.duration * 100}%`}
height={GRAPH_HEIGHT}
height={graphHeight}
x="0"
y="0"
fill="rgba(50, 50, 50, 0.2)"
@@ -255,7 +225,7 @@ class MiniTraceViewer extends React.Component {
? (
<rect
width={`${(traceSummary.duration - endTs) / traceSummary.duration * 100}%`}
height={GRAPH_HEIGHT}
height={graphHeight}
x={`${endTs / traceSummary.duration * 100}%`}
y="0"
fill="rgba(50, 50, 50, 0.2)"
@@ -270,6 +240,6 @@ class MiniTraceViewer extends React.Component {
}
}

MiniTraceViewer.propTypes = propTypes;
MiniTimeline.propTypes = propTypes;

export default MiniTraceViewer;
export default MiniTimeline;
68 changes: 68 additions & 0 deletions zipkin-lens/src/components/TracePage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import PropTypes from 'prop-types';
import React from 'react';

import LoadingOverlay from '../Common/LoadingOverlay';
import DetailedTraceSummary from '../DetailedTraceSummary';
import { detailedTraceSummaryPropTypes } from '../../prop-types';

const propTypes = {
isLoading: PropTypes.bool.isRequired,
traceId: PropTypes.string.isRequired,
traceSummary: detailedTraceSummaryPropTypes,
fetchTrace: PropTypes.func.isRequired,
};

const defaultProps = {
traceSummary: null,
};

class TracePage extends React.Component {
constructor(props) {
super(props);
this.state = {
startTs: null,
endTs: null,
};
this.handleStartAndEndTsChange = this.handleStartAndEndTsChange.bind(this);
}

componentDidMount() {
const { fetchTrace, traceId, traceSummary } = this.props;
if (!traceSummary || traceSummary.traceId !== traceId) {
fetchTrace(traceId);
}
}

handleStartAndEndTsChange(startTs, endTs) {
this.setState({ startTs, endTs });
}

render() {
const { startTs, endTs } = this.state;
const { isLoading, traceId, traceSummary } = this.props;
return (
<div className="trace-page">
<LoadingOverlay active={isLoading} />
<div className="trace-page__detailed-trace-summary-wrapper">
{
(!traceSummary || traceSummary.traceId !== traceId)
? null
: (
<DetailedTraceSummary
startTs={startTs}
endTs={endTs}
onStartAndEndTsChange={this.handleStartAndEndTsChange}
traceSummary={traceSummary}
/>
)
}
</div>
</div>
);
}
}

TracePage.propTypes = propTypes;
TracePage.defaultProps = defaultProps;

export default TracePage;
51 changes: 51 additions & 0 deletions zipkin-lens/src/components/TraceViewer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';

import DetailedTraceSummary from '../DetailedTraceSummary';
import { detailedTraceSummaryPropTypes } from '../../prop-types';

const propTypes = {
traceSummary: detailedTraceSummaryPropTypes,
};

const defaultProps = {
traceSummary: null,
};

class TraceViewer extends React.Component {
constructor(props) {
super(props);
this.state = {
startTs: null,
endTs: null,
};
this.handleStartAndEndTsChange = this.handleStartAndEndTsChange.bind(this);
}

handleStartAndEndTsChange(startTs, endTs) {
this.setState({ startTs, endTs });
}

render() {
const { startTs, endTs } = this.state;
const { traceSummary } = this.props;
return (
<div className="trace-viewer">
{
traceSummary ? (
<DetailedTraceSummary
startTs={startTs}
endTs={endTs}
onStartAndEndTsChange={this.handleStartAndEndTsChange}
traceSummary={traceSummary}
/>
) : null
}
</div>
);
}
}

TraceViewer.propTypes = propTypes;
TraceViewer.defaultProps = defaultProps;

export default TraceViewer;
2 changes: 2 additions & 0 deletions zipkin-lens/src/constants/action-types.js
Original file line number Diff line number Diff line change
@@ -35,3 +35,5 @@ export const GLOBAL_SEARCH_ADD_CONDITION = 'GLOBAL_SEARCH_ADD_CONDITION';
export const GLOBAL_SEARCH_DELETE_CONDITION = 'GLOBAL_SEARCH_DELETE_CONDITION';
export const GLOBAL_SEARCH_CHANGE_CONDITION_KEY = 'GLOBAL_SEARCH_CHANGE_CONDITION_KEY';
export const GLOBAL_SEARCH_CHANGE_CONDITION_VALUE = 'GLOBAL_SEARCH_CHANGE_CONDITION_VALUE';

export const TRACE_VIEWER_SET_TRACE = 'TRACE_VIEWER_SET_TRACE';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { connect } from 'react-redux';

import GlobalDropdownMenu from '../../components/GlobalSearch/GlobalDropdownMenu';
import { setTrace } from '../../actions/trace-viewer-action';

const mapDispatchToProps = dispatch => ({
setTrace: trace => dispatch(setTrace(trace)),
});

const GlobalDropdownMenuContainer = connect(
null,
mapDispatchToProps,
)(GlobalDropdownMenu);

export default GlobalDropdownMenuContainer;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { connect } from 'react-redux';

import { fetchTrace } from '../../actions/trace-action';
import DetailedTraceSummary from '../../components/DetailedTraceSummary';
import TracePage from '../../components/TracePage';
import { treeCorrectedForClockSkew, detailedTraceSummary } from '../../zipkin';

const mapStateToProps = (state, ownProps) => {
@@ -22,9 +22,9 @@ const mapDispatchToProps = dispatch => ({
fetchTrace: traceId => dispatch(fetchTrace(traceId)),
});

const DetailedTraceSummaryContainer = connect(
const TracePageContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(DetailedTraceSummary);
)(TracePage);

export default DetailedTraceSummaryContainer;
export default TracePageContainer;
23 changes: 23 additions & 0 deletions zipkin-lens/src/containers/TraceViewer/TraceViewerContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { connect } from 'react-redux';

import TraceViewer from '../../components/TraceViewer';
import { treeCorrectedForClockSkew, detailedTraceSummary } from '../../zipkin';

const mapStateToProps = (state) => {
if (state.traceViewer.trace) {
return {
traceSummary: detailedTraceSummary(
treeCorrectedForClockSkew(state.traceViewer.trace),
),
};
}
return {
traceSummary: null,
};
};

const TraceViewerContainer = connect(
mapStateToProps,
)(TraceViewer);

export default TraceViewerContainer;
3 changes: 3 additions & 0 deletions zipkin-lens/src/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Modal from 'react-modal';

import App from './components/App';

import '../scss/main.scss';

require('babel-polyfill');

Modal.setAppElement('body');

ReactDOM.render(
<App />,
document.getElementById('app'),
2 changes: 2 additions & 0 deletions zipkin-lens/src/reducers/index.js
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import dependencies from './dependencies';
import globalSearch from './global-search';
import autocompleteKeys from './autocomplete-keys';
import autocompleteValues from './autocomplete-values';
import traceViewer from './trace-viewer';

const reducer = combineReducers({
spans,
@@ -18,6 +19,7 @@ const reducer = combineReducers({
globalSearch,
autocompleteKeys,
autocompleteValues,
traceViewer,
});

export default reducer;
19 changes: 19 additions & 0 deletions zipkin-lens/src/reducers/trace-viewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as types from '../constants/action-types';

const initialState = {
trace: null,
};

const traceViewer = (state = initialState, action) => {
switch (action.type) {
case types.TRACE_VIEWER_SET_TRACE:
return {
...state,
trace: action.trace,
};
default:
return state;
}
};

export default traceViewer;
6 changes: 0 additions & 6 deletions zipkin-lens/src/util/global-search.js
Original file line number Diff line number Diff line change
@@ -16,7 +16,6 @@ export const orderedConditionKeyList = autocompleteKeys => ([
'minDuration',
'maxDuration',
...autocompleteKeys,
'traceId',
'tags',
]);

@@ -26,7 +25,6 @@ export const isAutocompleteKey = (conditionKey) => {
case 'spanName':
case 'minDuration':
case 'maxDuration':
case 'traceId':
case 'tags':
return false;
default:
@@ -44,8 +42,6 @@ export const defaultConditionValues = (conditionKey) => {
return 10;
case 'maxDuration':
return 100;
case 'traceId':
return '';
case 'tags':
return '';
default: // autocompleteKeys
@@ -85,8 +81,6 @@ export const buildQueryParametersWithConditions = (
case 'maxDuration':
conditionMap[condition.key] = condition.value;
break;
case 'traceId':
break; // ignore traceId
case 'tags':
tagsConditions.push(condition.value);
break;
2 changes: 0 additions & 2 deletions zipkin-lens/src/util/global-search.test.js
Original file line number Diff line number Diff line change
@@ -27,7 +27,6 @@ describe('nextInitialConditionKey', () => {
{ key: 'spanName' },
{ key: 'minDuration' },
{ key: 'maxDuration' },
{ key: 'traceId' },
{ key: 'tags' },
], [])).toEqual('tags');
});
@@ -277,7 +276,6 @@ describe('getConditionKeyListWithAvailability', () => {
{ conditionKey: 'spanName', isAvailable: true },
{ conditionKey: 'minDuration', isAvailable: true },
{ conditionKey: 'maxDuration', isAvailable: false },
{ conditionKey: 'traceId', isAvailable: true },
{ conditionKey: 'tags', isAvailable: true }, // always true
{ conditionKey: 'instanceId', isAvailable: true },
{ conditionKey: 'environment', isAvailable: false },