Skip to content

Commit

Permalink
Timeline Expand and Collapse Features (jaegertracing#221)
Browse files Browse the repository at this point in the history
* Add expansion and collapsing features

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Use Icon component

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Use spans upstream

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Improve css

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Rotate collapse buttons

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Remove debug logging

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Remove spans from TimelineHeaderRow

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Add unit test for TimelineCollapser

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Use popover

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Add TimelineCollapser test

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Revert "Use popover"

This reverts commit 6152402

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Add tests for duck.js

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Add more tests for duck.js

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Add more tests for index.js

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Add keyboard shortcuts

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Update changelog

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

* Make review changes

Signed-off-by: Davit Yeghshatyan <davo@uber.com>

Signed-off-by: vvvprabhakar <vvvprabhakar@gmail.com>
  • Loading branch information
davit-y authored and tiffon committed Jul 6, 2018
1 parent df73678 commit 795e805
Show file tree
Hide file tree
Showing 15 changed files with 460 additions and 48 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes merged into master

### [#221](https://github.com/jaegertracing/jaeger-ui/pull/221) Timeline Expand and Collapse Features

* Partially addresses [#160](https://github.com/jaegertracing/jaeger-ui/issues/160) - Heuristics for collapsing spans

### [#191](https://github.com/jaegertracing/jaeger-ui/pull/191) Add GA event tracking for actions in trace view

* Partially addresses [#157](https://github.com/jaegertracing/jaeger-ui/issues/157) - Enhanced Google Analytics integration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ const descriptions = {
zoomInFast: 'Zoom in — Large',
zoomOut: 'Zoom out',
zoomOutFast: 'Zoom out — Large',
collapseAll: 'Collapse All',
expandAll: 'Expand All',
collapseOne: 'Collapse One Level',
expandOne: 'Expand One Level',
};

function convertKeys(keyConfig: string | string[]): string[][] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright (c) 2017 Uber Technologies, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.TimelineCollapser {
float: right;
margin: 0 0.8rem 0 0;
display: inline-block;
}

.TimelineCollapser--btn,
.TimelineCollapser--btn-expand {
margin-right: 0.3rem;
}

.TimelineCollapser--btn-expand {
transform: rotate(90deg);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// @flow

// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import React from 'react';

import { Tooltip, Icon } from 'antd';

import './TimelineCollapser.css';

type CollapserProps = {
onCollapseAll: () => void,
onCollapseOne: () => void,
onExpandOne: () => void,
onExpandAll: () => void,
};

export default function TimelineCollapser(props: CollapserProps) {
const { onExpandAll, onExpandOne, onCollapseAll, onCollapseOne } = props;
return (
<span className="TimelineCollapser">
<Tooltip title="Expand +1">
<Icon type="right" onClick={onExpandOne} className="TimelineCollapser--btn-expand" />
</Tooltip>
<Tooltip title="Collapse +1">
<Icon type="right" onClick={onCollapseOne} className="TimelineCollapser--btn" />
</Tooltip>
<Tooltip title="Expand All">
<Icon type="double-right" onClick={onExpandAll} className="TimelineCollapser--btn-expand" />
</Tooltip>
<Tooltip title="Collapse All">
<Icon type="double-right" onClick={onCollapseAll} className="TimelineCollapser--btn" />
</Tooltip>
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import React from 'react';
import { shallow } from 'enzyme';

import TimelineCollapser from './TimelineCollapser';

describe('<TimelineCollapser>', () => {
it('renders without exploding', () => {
const props = {
onCollapseAll: () => {},
onCollapseOne: () => {},
onExpandAll: () => {},
onExpandOne: () => {},
};
const wrapper = shallow(<TimelineCollapser {...props} />);
expect(wrapper).toBeDefined();
expect(wrapper.find('.TimelineCollapser').length).toBe(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ limitations under the License.
}

.TimelineHeaderRow--title {
display: inline-block;
overflow: hidden;
margin: 0 0.75rem 0 0.5rem;
margin: 0 0 0 0.5rem;
text-overflow: ellipsis;
white-space: nowrap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import * as React from 'react';

import TimelineCollapser from './TimelineCollapser';
import TimelineColumnResizer from './TimelineColumnResizer';
import TimelineViewingLayer from './TimelineViewingLayer';
import Ticks from '../Ticks';
Expand All @@ -28,7 +29,11 @@ type TimelineHeaderRowProps = {
duration: number,
nameColumnWidth: number,
numTicks: number,
onCollapseAll: () => void,
onCollapseOne: () => void,
onColummWidthChange: number => void,
onExpandAll: () => void,
onExpandOne: () => void,
updateNextViewRangeTime: ViewRangeTimeUpdate => void,
updateViewRangeTime: (number, number, ?string) => void,
viewRangeTime: ViewRangeTime,
Expand All @@ -39,7 +44,11 @@ export default function TimelineHeaderRow(props: TimelineHeaderRowProps) {
duration,
nameColumnWidth,
numTicks,
onCollapseAll,
onCollapseOne,
onColummWidthChange,
onExpandAll,
onExpandOne,
updateViewRangeTime,
updateNextViewRangeTime,
viewRangeTime,
Expand All @@ -49,6 +58,12 @@ export default function TimelineHeaderRow(props: TimelineHeaderRowProps) {
<TimelineRow className="TimelineHeaderRow">
<TimelineRow.Cell width={nameColumnWidth}>
<h3 className="TimelineHeaderRow--title">Service &amp; Operation</h3>
<TimelineCollapser
onCollapseAll={onCollapseAll}
onExpandAll={onExpandAll}
onCollapseOne={onCollapseOne}
onExpandOne={onExpandOne}
/>
</TimelineRow.Cell>
<TimelineRow.Cell width={1 - nameColumnWidth}>
<TimelineViewingLayer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import TimelineHeaderRow from './TimelineHeaderRow';
import TimelineColumnResizer from './TimelineColumnResizer';
import TimelineViewingLayer from './TimelineViewingLayer';
import Ticks from '../Ticks';
import TimelineCollapser from './TimelineCollapser';

describe('<TimelineHeaderRow>', () => {
let wrapper;
Expand All @@ -28,7 +29,11 @@ describe('<TimelineHeaderRow>', () => {
nameColumnWidth,
duration: 1234,
numTicks: 5,
onCollapseAll: () => {},
onCollapseOne: () => {},
onColummWidthChange: () => {},
onExpandAll: () => {},
onExpandOne: () => {},
updateNextViewRangeTime: () => {},
updateViewRangeTime: () => {},
viewRangeTime: {
Expand Down Expand Up @@ -92,4 +97,16 @@ describe('<TimelineHeaderRow>', () => {
);
expect(wrapper.containsMatchingElement(elm)).toBe(true);
});

it('renders the TimelineCollapser', () => {
const elm = (
<TimelineCollapser
onCollapseAll={props.onCollapseAll}
onExpandAll={props.onExpandAll}
onCollapseOne={props.onCollapseOne}
onExpandOne={props.onExpandOne}
/>
);
expect(wrapper.containsMatchingElement(elm)).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const actionTypes = generateActionTypes('@jaeger-ui/trace-timeline-viewer
'SET_TRACE',
'SET_SPAN_NAME_COLUMN_WIDTH',
'CHILDREN_TOGGLE',
'EXPAND_ALL',
'COLLAPSE_ALL',
'EXPAND_ONE',
'COLLAPSE_ONE',
'DETAIL_TOGGLE',
'DETAIL_TAGS_TOGGLE',
'DETAIL_PROCESS_TOGGLE',
Expand All @@ -61,6 +65,10 @@ const fullActions = createActions({
[actionTypes.SET_TRACE]: traceID => ({ traceID }),
[actionTypes.SET_SPAN_NAME_COLUMN_WIDTH]: width => ({ width }),
[actionTypes.CHILDREN_TOGGLE]: spanID => ({ spanID }),
[actionTypes.EXPAND_ALL]: () => ({}),
[actionTypes.EXPAND_ONE]: spans => ({ spans }),
[actionTypes.COLLAPSE_ALL]: spans => ({ spans }),
[actionTypes.COLLAPSE_ONE]: spans => ({ spans }),
[actionTypes.DETAIL_TOGGLE]: spanID => ({ spanID }),
[actionTypes.DETAIL_TAGS_TOGGLE]: spanID => ({ spanID }),
[actionTypes.DETAIL_PROCESS_TOGGLE]: spanID => ({ spanID }),
Expand Down Expand Up @@ -97,6 +105,70 @@ function childrenToggle(state, { payload }) {
return { ...state, childrenHiddenIDs };
}

function shouldDisableCollapse(allSpans, hiddenSpansIds) {
const allParentSpans = allSpans.filter(s => s.hasChildren);
return allParentSpans.length === hiddenSpansIds.size;
}

export function expandAll(state) {
const childrenHiddenIDs = new Set();
return { ...state, childrenHiddenIDs };
}

export function collapseAll(state, { payload }) {
const { spans } = payload;
if (shouldDisableCollapse(spans, state.childrenHiddenIDs)) {
return state;
}
const childrenHiddenIDs = spans.reduce((res, s) => {
if (s.hasChildren) {
res.add(s.spanID);
}
return res;
}, new Set());
return { ...state, childrenHiddenIDs };
}

export function collapseOne(state, { payload }) {
const { spans } = payload;
if (shouldDisableCollapse(spans, state.childrenHiddenIDs)) {
return state;
}
let nearestCollapsedAncestor;
const childrenHiddenIDs = spans.reduce((res, curSpan) => {
if (nearestCollapsedAncestor && curSpan.depth <= nearestCollapsedAncestor.depth) {
res.add(nearestCollapsedAncestor.spanID);
nearestCollapsedAncestor = curSpan;
} else if (curSpan.hasChildren && !res.has(curSpan.spanID)) {
nearestCollapsedAncestor = curSpan;
}
return res;
}, new Set(state.childrenHiddenIDs));
childrenHiddenIDs.add(nearestCollapsedAncestor.spanID);
return { ...state, childrenHiddenIDs };
}

export function expandOne(state, { payload }) {
const { spans } = payload;
if (state.childrenHiddenIDs.size === 0) {
return state;
}
let prevExpandedDepth = -1;
let expandNextHiddenSpan = true;
const childrenHiddenIDs = spans.reduce((res, s) => {
if (s.depth <= prevExpandedDepth) {
expandNextHiddenSpan = true;
}
if (expandNextHiddenSpan && res.has(s.spanID)) {
res.delete(s.spanID);
expandNextHiddenSpan = false;
prevExpandedDepth = s.depth;
}
return res;
}, new Set(state.childrenHiddenIDs));
return { ...state, childrenHiddenIDs };
}

function detailToggle(state, { payload }) {
const { spanID } = payload;
const detailStates = new Map(state.detailStates);
Expand Down Expand Up @@ -149,6 +221,10 @@ export default handleActions(
[actionTypes.SET_TRACE]: setTrace,
[actionTypes.SET_SPAN_NAME_COLUMN_WIDTH]: setColumnWidth,
[actionTypes.CHILDREN_TOGGLE]: childrenToggle,
[actionTypes.EXPAND_ALL]: expandAll,
[actionTypes.EXPAND_ONE]: expandOne,
[actionTypes.COLLAPSE_ALL]: collapseAll,
[actionTypes.COLLAPSE_ONE]: collapseOne,
[actionTypes.DETAIL_TOGGLE]: detailToggle,
[actionTypes.DETAIL_TAGS_TOGGLE]: detailTagsToggle,
[actionTypes.DETAIL_PROCESS_TOGGLE]: detailProcessToggle,
Expand Down
Loading

0 comments on commit 795e805

Please sign in to comment.