diff --git a/CHANGELOG.md b/CHANGELOG.md
index b11ae5473f..47cdc2e541 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/packages/jaeger-ui/src/components/TracePage/KeyboardShortcutsHelp.js b/packages/jaeger-ui/src/components/TracePage/KeyboardShortcutsHelp.js
index f2037ecf61..30328eef43 100644
--- a/packages/jaeger-ui/src/components/TracePage/KeyboardShortcutsHelp.js
+++ b/packages/jaeger-ui/src/components/TracePage/KeyboardShortcutsHelp.js
@@ -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[][] {
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.css
new file mode 100644
index 0000000000..6be2e0d87a
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.css
@@ -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);
+}
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.js b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.js
new file mode 100644
index 0000000000..2e905d72d5
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.js
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js
new file mode 100644
index 0000000000..56b8bff95a
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.test.js
@@ -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('', () => {
+ it('renders without exploding', () => {
+ const props = {
+ onCollapseAll: () => {},
+ onCollapseOne: () => {},
+ onExpandAll: () => {},
+ onExpandOne: () => {},
+ };
+ const wrapper = shallow();
+ expect(wrapper).toBeDefined();
+ expect(wrapper.find('.TimelineCollapser').length).toBe(1);
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css
index fe615e0762..45eb9c5c2d 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css
@@ -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;
}
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.js b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.js
index ba3fc2838a..9c0a1e922a 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.js
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.js
@@ -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';
@@ -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,
@@ -39,7 +44,11 @@ export default function TimelineHeaderRow(props: TimelineHeaderRowProps) {
duration,
nameColumnWidth,
numTicks,
+ onCollapseAll,
+ onCollapseOne,
onColummWidthChange,
+ onExpandAll,
+ onExpandOne,
updateViewRangeTime,
updateNextViewRangeTime,
viewRangeTime,
@@ -49,6 +58,12 @@ export default function TimelineHeaderRow(props: TimelineHeaderRowProps) {
Service & Operation
+
', () => {
let wrapper;
@@ -28,7 +29,11 @@ describe('', () => {
nameColumnWidth,
duration: 1234,
numTicks: 5,
+ onCollapseAll: () => {},
+ onCollapseOne: () => {},
onColummWidthChange: () => {},
+ onExpandAll: () => {},
+ onExpandOne: () => {},
updateNextViewRangeTime: () => {},
updateViewRangeTime: () => {},
viewRangeTime: {
@@ -92,4 +97,16 @@ describe('', () => {
);
expect(wrapper.containsMatchingElement(elm)).toBe(true);
});
+
+ it('renders the TimelineCollapser', () => {
+ const elm = (
+
+ );
+ expect(wrapper.containsMatchingElement(elm)).toBe(true);
+ });
});
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.js b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.js
index ed8cda9300..d4e1db9b49 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.js
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.js
@@ -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',
@@ -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 }),
@@ -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);
@@ -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,
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.test.js b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.test.js
index b63c488a4d..499a668fb3 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.test.js
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/duck.test.js
@@ -14,7 +14,7 @@
import { createStore } from 'redux';
-import reducer, { actions, newInitialState } from './duck';
+import reducer, { actions, newInitialState, collapseAll, collapseOne, expandAll, expandOne } from './duck';
import DetailState from './SpanDetail/DetailState';
import transformTraceData from '../../../model/transform-trace-data';
import traceGenerator from '../../../demo/trace-generators';
@@ -129,6 +129,110 @@ describe('TraceTimelineViewer/duck', () => {
});
});
+ describe('expands and collapses all spans', () => {
+ // 0
+ // - 1
+ // --- 2
+ // - 3
+ // --- 4
+ const spans = [
+ { spanID: 0, depth: 0, hasChildren: true },
+ { spanID: 1, depth: 1, hasChildren: true },
+ { spanID: 2, depth: 2, hasChildren: false },
+ { spanID: 3, depth: 1, hasChildren: true },
+ { spanID: 4, depth: 2, hasChildren: false },
+ ];
+
+ const oneSpanCollapsed = new Set([1]);
+ const allSpansCollapsed = new Set([0, 1, 3]);
+ const oneLevelCollapsed = new Set([1, 3]);
+
+ // Tests for corner cases of reducers
+ const tests = [
+ {
+ msg: 'expand all',
+ action: expandAll,
+ initial: allSpansCollapsed,
+ resultant: new Set(),
+ },
+ {
+ msg: 'collapse all, no-op',
+ action: collapseAll,
+ initial: allSpansCollapsed,
+ resultant: allSpansCollapsed,
+ },
+ {
+ msg: 'expand one',
+ action: expandOne,
+ initial: allSpansCollapsed,
+ resultant: oneLevelCollapsed,
+ },
+ {
+ msg: 'expand one, one collapsed',
+ action: expandOne,
+ initial: oneSpanCollapsed,
+ resultant: new Set(),
+ },
+ {
+ msg: 'collapse one, no-op',
+ action: collapseOne,
+ initial: allSpansCollapsed,
+ resultant: allSpansCollapsed,
+ },
+ {
+ msg: 'collapse one, one collapsed',
+ action: collapseOne,
+ initial: oneSpanCollapsed,
+ resultant: oneLevelCollapsed,
+ },
+ ];
+
+ tests.forEach(info => {
+ const { msg, action, initial, resultant } = info;
+
+ it(msg, () => {
+ const { childrenHiddenIDs } = action({ childrenHiddenIDs: initial }, { payload: { spans } });
+ expect(childrenHiddenIDs).toEqual(resultant);
+ });
+ });
+
+ // Tests to verify correct behaviour of actions
+ const dispatchTests = [
+ {
+ msg: 'expand all, no-op',
+ action: actions.expandAll(),
+ resultant: new Set(),
+ },
+ {
+ msg: 'collapse all',
+ action: actions.collapseAll(spans),
+ resultant: allSpansCollapsed,
+ },
+ {
+ msg: 'expand one, no-op',
+ action: actions.expandOne(spans),
+ resultant: new Set(),
+ },
+ {
+ msg: 'collapse one',
+ action: actions.collapseOne(spans),
+ resultant: oneLevelCollapsed,
+ },
+ ];
+
+ dispatchTests.forEach(info => {
+ const { msg, action, resultant } = info;
+
+ it(msg, () => {
+ const st0 = store.getState();
+ store.dispatch(action);
+ const st1 = store.getState();
+ expect(st0.childrenHiddenIDs).toEqual(new Set());
+ expect(st1.childrenHiddenIDs).toEqual(resultant);
+ });
+ });
+ });
+
describe("toggles a detail's sub-sections", () => {
const id = trace.spans[0].spanID;
const baseDetail = new DetailState();
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.js b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.js
index c3bc9a0469..28e4e92095 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.js
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.js
@@ -16,19 +16,25 @@
import React from 'react';
import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
import { actions } from './duck';
import TimelineHeaderRow from './TimelineHeaderRow';
import VirtualizedTraceView from './VirtualizedTraceView';
+import { merge as mergeShortcuts } from '../keyboard-shortcuts';
import type { Accessors } from '../ScrollManager';
import type { ViewRange, ViewRangeTimeUpdate } from '../types';
-import type { Trace } from '../../../types';
+import type { Span, Trace } from '../../../types';
import './index.css';
type TraceTimelineViewerProps = {
registerAccessors: Accessors => void,
setSpanNameColumnWidth: number => void,
+ collapseAll: (Span[]) => void,
+ collapseOne: (Span[]) => void,
+ expandAll: () => void,
+ expandOne: (Span[]) => void,
spanNameColumnWidth: number,
textFilter: ?string,
trace: Trace,
@@ -45,23 +51,63 @@ const NUM_TICKS = 5;
* re-render the ListView every time the cursor is moved on the trace minimap
* or `TimelineHeaderRow`.
*/
-function TraceTimelineViewer(props: TraceTimelineViewerProps) {
- const { setSpanNameColumnWidth, updateNextViewRangeTime, updateViewRangeTime, viewRange, ...rest } = props;
- const { spanNameColumnWidth, trace } = rest;
- return (
-
-
-
-
- );
+export class TraceTimelineViewerImpl extends React.PureComponent {
+ props: TraceTimelineViewerProps;
+
+ componentDidMount() {
+ mergeShortcuts({
+ collapseAll: this.collapseAll,
+ expandAll: this.expandAll,
+ collapseOne: this.collapseOne,
+ expandOne: this.expandOne,
+ });
+ }
+
+ collapseAll = () => {
+ this.props.collapseAll(this.props.trace.spans);
+ };
+
+ collapseOne = () => {
+ this.props.collapseOne(this.props.trace.spans);
+ };
+
+ expandAll = () => {
+ this.props.expandAll();
+ };
+
+ expandOne = () => {
+ this.props.expandOne(this.props.trace.spans);
+ };
+
+ render() {
+ const {
+ setSpanNameColumnWidth,
+ updateNextViewRangeTime,
+ updateViewRangeTime,
+ viewRange,
+ ...rest
+ } = this.props;
+ const { spanNameColumnWidth, trace } = rest;
+
+ return (
+
+
+
+
+ );
+ }
}
function mapStateToProps(state, ownProps) {
@@ -70,11 +116,11 @@ function mapStateToProps(state, ownProps) {
}
function mapDispatchToProps(dispatch) {
- const setSpanNameColumnWidth = (...args) => {
- const action = actions.setSpanNameColumnWidth(...args);
- return dispatch(action);
- };
- return { setSpanNameColumnWidth };
+ const { setSpanNameColumnWidth, expandAll, expandOne, collapseAll, collapseOne } = bindActionCreators(
+ actions,
+ dispatch
+ );
+ return { setSpanNameColumnWidth, expandAll, expandOne, collapseAll, collapseOne };
}
-export default connect(mapStateToProps, mapDispatchToProps)(TraceTimelineViewer);
+export default connect(mapStateToProps, mapDispatchToProps)(TraceTimelineViewerImpl);
diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.test.js b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.test.js
index a0a3ebd8a7..01f86a1690 100644
--- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.test.js
+++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/index.test.js
@@ -15,9 +15,10 @@
import React from 'react';
import { shallow } from 'enzyme';
-import TraceTimelineViewer from './index';
+import TraceTimelineViewer, { TraceTimelineViewerImpl } from './index';
import traceGenerator from '../../../demo/trace-generators';
import transformTraceData from '../../../model/transform-trace-data';
+import TimelineHeaderRow from './TimelineHeaderRow';
describe('', () => {
const trace = transformTraceData(traceGenerator.trace({}));
@@ -29,6 +30,11 @@ describe('', () => {
current: [0, 1],
},
},
+ spanNameColumnWidth: 0.5,
+ expandAll: jest.fn(),
+ collapseAll: jest.fn(),
+ expandOne: jest.fn(),
+ collapseOne: jest.fn(),
};
const options = {
context: {
@@ -37,17 +43,33 @@ describe('', () => {
return { traceTimeline: { spanNameColumnWidth: 0.25 } };
},
subscribe() {},
+ dispatch() {},
},
},
};
let wrapper;
+ let connectedWrapper;
beforeEach(() => {
- wrapper = shallow(, options);
+ wrapper = shallow(, options);
+ connectedWrapper = shallow(, options);
});
it('it does not explode', () => {
expect(wrapper).toBeDefined();
+ expect(connectedWrapper).toBeDefined();
+ });
+
+ it('it sets up actions', () => {
+ const headerRow = wrapper.find(TimelineHeaderRow);
+ headerRow.props().onCollapseAll();
+ headerRow.props().onExpandAll();
+ headerRow.props().onExpandOne();
+ headerRow.props().onCollapseOne();
+ expect(props.collapseAll.mock.calls.length).toBe(1);
+ expect(props.expandAll.mock.calls.length).toBe(1);
+ expect(props.expandOne.mock.calls.length).toBe(1);
+ expect(props.collapseOne.mock.calls.length).toBe(1);
});
});
diff --git a/packages/jaeger-ui/src/components/TracePage/index.js b/packages/jaeger-ui/src/components/TracePage/index.js
index 6344585ec5..faec98da1b 100644
--- a/packages/jaeger-ui/src/components/TracePage/index.js
+++ b/packages/jaeger-ui/src/components/TracePage/index.js
@@ -27,7 +27,7 @@ import ArchiveNotifier from './ArchiveNotifier';
import { actions as archiveActions } from './ArchiveNotifier/duck';
import { trackFilter, trackRange } from './index.track';
import type { CombokeysHandler, ShortcutCallbacks } from './keyboard-shortcuts';
-import { init as initShortcuts, reset as resetShortcuts } from './keyboard-shortcuts';
+import { merge as mergeShortcuts, reset as resetShortcuts } from './keyboard-shortcuts';
import { cancel as cancelScroll, scrollBy, scrollTo } from './scroll-page';
import ScrollManager from './ScrollManager';
import SpanGraph from './SpanGraph';
@@ -117,6 +117,7 @@ export default class TracePage extends React.PureComponent', () => {
});
it('performs misc cleanup when unmounting', () => {
+ resetShortcuts.mockReset();
wrapper = shallow();
const scrollManager = wrapper.instance()._scrollManager;
scrollManager.destroy = jest.fn();
wrapper.unmount();
expect(scrollManager.destroy.mock.calls).toEqual([[]]);
- expect(resetShortcuts.mock.calls).toEqual([[]]);
+ expect(resetShortcuts.mock.calls).toEqual([[], []]);
expect(cancelScroll.mock.calls).toEqual([[]]);
});
diff --git a/packages/jaeger-ui/src/components/TracePage/keyboard-shortcuts.js b/packages/jaeger-ui/src/components/TracePage/keyboard-shortcuts.js
index eef93268a4..b8d2281486 100644
--- a/packages/jaeger-ui/src/components/TracePage/keyboard-shortcuts.js
+++ b/packages/jaeger-ui/src/components/TracePage/keyboard-shortcuts.js
@@ -27,19 +27,24 @@ type CombokeysType = {
};
export type ShortcutCallbacks = {
- scrollPageDown: CombokeysHandler,
- scrollPageUp: CombokeysHandler,
- scrollToNextVisibleSpan: CombokeysHandler,
- scrollToPrevVisibleSpan: CombokeysHandler,
+ scrollPageDown?: CombokeysHandler,
+ scrollPageUp?: CombokeysHandler,
+ scrollToNextVisibleSpan?: CombokeysHandler,
+ scrollToPrevVisibleSpan?: CombokeysHandler,
// view range
- panLeft: CombokeysHandler,
- panLeftFast: CombokeysHandler,
- panRight: CombokeysHandler,
- panRightFast: CombokeysHandler,
- zoomIn: CombokeysHandler,
- zoomInFast: CombokeysHandler,
- zoomOut: CombokeysHandler,
- zoomOutFast: CombokeysHandler,
+ panLeft?: CombokeysHandler,
+ panLeftFast?: CombokeysHandler,
+ panRight?: CombokeysHandler,
+ panRightFast?: CombokeysHandler,
+ zoomIn?: CombokeysHandler,
+ zoomInFast?: CombokeysHandler,
+ zoomOut?: CombokeysHandler,
+ zoomOutFast?: CombokeysHandler,
+ // collapse/expand
+ collapseAll?: CombokeysHandler,
+ expandAll?: CombokeysHandler,
+ collapseOne?: CombokeysHandler,
+ expandOne?: CombokeysHandler,
};
export const kbdMappings = {
@@ -55,6 +60,10 @@ export const kbdMappings = {
zoomInFast: 'shift+up',
zoomOut: 'down',
zoomOutFast: 'shift+down',
+ collapseAll: ']',
+ expandAll: '[',
+ collapseOne: 'p',
+ expandOne: 'o',
};
let instance: ?CombokeysType;
@@ -66,11 +75,13 @@ function getInstance(): CombokeysType {
return instance;
}
-export function init(callbacks: ShortcutCallbacks) {
- const combokeys = getInstance();
- combokeys.reset();
- Object.keys(kbdMappings).forEach(name => {
- combokeys.bind(kbdMappings[name], callbacks[name]);
+export function merge(callbacks: ShortcutCallbacks) {
+ const inst = getInstance();
+ Object.keys(callbacks).forEach(name => {
+ const keysHandler = callbacks[name];
+ if (keysHandler) {
+ inst.bind(kbdMappings[name], keysHandler);
+ }
});
}