diff --git a/packages/jaeger-ui/package.json b/packages/jaeger-ui/package.json
index c5bc5e9a88..888a11eeba 100644
--- a/packages/jaeger-ui/package.json
+++ b/packages/jaeger-ui/package.json
@@ -9,8 +9,8 @@
"babel-plugin-import": "1.11.0",
"bluebird": "^3.5.0",
"customize-cra": "0.2.9",
- "enzyme": "^3.2.0",
- "enzyme-adapter-react-16": "^1.1.0",
+ "enzyme": "^3.8.0",
+ "enzyme-adapter-react-16": "^1.2.0",
"enzyme-to-json": "^3.3.0",
"http-proxy-middleware": "^0.19.1",
"less": "3.9.0",
diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/__snapshots__/DiffSelection.test.js.snap b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/__snapshots__/DiffSelection.test.js.snap
index 727c219f47..f9aa7600bd 100644
--- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/__snapshots__/DiffSelection.test.js.snap
+++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/__snapshots__/DiffSelection.test.js.snap
@@ -25,25 +25,23 @@ exports[`DiffSelection renders a trace as expected 1`] = `
-
-
-
- 1
- Selected for comparison
-
-
+
+
+ 1
+ Selected for comparison
+
`;
@@ -97,30 +95,28 @@ exports[`DiffSelection renders multiple traces as expected 1`] = `
-
-
-
-
-
+
-
+ Compare Traces
+
+
+
+ 2
+ Selected for comparison
+
`;
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiff.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiff.js
index 15fa072b2d..569877873c 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/TraceDiff.js
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiff.js
@@ -48,22 +48,22 @@ type State = {
graphTopOffset: number,
};
-function syncStates(urlSt, reduxSt, forceState) {
- const { a: urlA, b: urlB } = urlSt;
- const { a: reduxA, b: reduxB } = reduxSt;
+function syncStates(urlValues, reduxValues, forceState) {
+ const { a: urlA, b: urlB } = urlValues;
+ const { a: reduxA, b: reduxB } = reduxValues;
if (urlA !== reduxA || urlB !== reduxB) {
- forceState(urlSt);
+ forceState(urlValues);
return;
}
- const urlCohort = new Set(urlSt.cohort || []);
- const reduxCohort = new Set(reduxSt.cohort || []);
+ const urlCohort = new Set(urlValues.cohort);
+ const reduxCohort = new Set(reduxValues.cohort || []);
if (urlCohort.size !== reduxCohort.size) {
- forceState(urlSt);
+ forceState(urlValues);
return;
}
const needSync = Array.from(urlCohort).some(id => !reduxCohort.has(id));
if (needSync) {
- forceState(urlSt);
+ forceState(urlValues);
}
}
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiff.test.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiff.test.js
new file mode 100644
index 0000000000..c59a3a27f0
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiff.test.js
@@ -0,0 +1,370 @@
+// Copyright (c) 2019 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import * as redux from 'redux';
+
+import { mapStateToProps, mapDispatchToProps, TraceDiffImpl } from './TraceDiff';
+import TraceDiffHeader from './TraceDiffHeader';
+import { actions as diffActions } from './duck';
+import * as TraceDiffUrl from './url';
+import * as jaegerApiActions from '../../actions/jaeger-api';
+import { fetchedState, TOP_NAV_HEIGHT } from '../../constants';
+
+describe('TraceDiff', () => {
+ const defaultA = 'trace-id-a';
+ const defaultB = 'trace-id-b';
+ const defaultCohortIds = ['trace-id-cohort-0', 'trace-id-cohort-1', 'trace-id-cohort-2'];
+ const defaultCohort = [defaultA, defaultB, ...defaultCohortIds];
+ const fetchMultipleTracesMock = jest.fn();
+ const forceStateMock = jest.fn();
+ const historyPushMock = jest.fn();
+ const defaultProps = {
+ a: defaultA,
+ b: defaultB,
+ cohort: defaultCohort,
+ fetchMultipleTraces: fetchMultipleTracesMock,
+ forceState: forceStateMock,
+ history: {
+ push: historyPushMock,
+ },
+ tracesData: new Map(defaultCohort.map(id => [id, { id, state: fetchedState.DONE }])),
+ traceDiffState: {
+ a: defaultA,
+ b: defaultB,
+ cohort: defaultCohort,
+ },
+ };
+ const newAValue = 'newAValue';
+ const newBValue = 'newBValue';
+ const nonDefaultCohortId = 'non-default-cohort-id';
+ const getUrlSpyMockReturnValue = 'getUrlSpyMockReturnValue';
+ let getUrlSpy;
+ let wrapper;
+
+ beforeAll(() => {
+ getUrlSpy = jest.spyOn(TraceDiffUrl, 'getUrl').mockReturnValue(getUrlSpyMockReturnValue);
+ });
+
+ beforeEach(() => {
+ fetchMultipleTracesMock.mockClear();
+ forceStateMock.mockClear();
+ getUrlSpy.mockClear();
+ historyPushMock.mockClear();
+ wrapper = shallow();
+ });
+
+ describe('syncStates', () => {
+ it('forces state if a is inconsistent between url and reduxState', () => {
+ wrapper.setProps({ a: newAValue });
+ expect(forceStateMock).toHaveBeenLastCalledWith({
+ a: newAValue,
+ b: defaultProps.b,
+ cohort: defaultProps.cohort,
+ });
+ });
+
+ it('forces state if b is inconsistent between url and reduxState', () => {
+ wrapper.setProps({ b: newBValue });
+ expect(forceStateMock).toHaveBeenLastCalledWith({
+ a: defaultProps.a,
+ b: newBValue,
+ cohort: defaultProps.cohort,
+ });
+ });
+
+ it('forces state if cohort size has changed', () => {
+ const newCohort = [...defaultProps.cohort, nonDefaultCohortId];
+ wrapper.setProps({ cohort: newCohort });
+ expect(forceStateMock).toHaveBeenLastCalledWith({
+ a: defaultProps.a,
+ b: defaultProps.b,
+ cohort: newCohort,
+ });
+
+ wrapper.setProps({
+ cohort: defaultProps.cohort,
+ traceDiffState: { ...defaultProps.traceDiffState, cohort: null },
+ });
+ expect(forceStateMock).toHaveBeenLastCalledWith({
+ a: defaultProps.a,
+ b: defaultProps.b,
+ cohort: defaultProps.cohort,
+ });
+ });
+
+ it('forces state if cohort entry has changed', () => {
+ const newCohort = [...defaultProps.cohort.slice(1), nonDefaultCohortId];
+ wrapper.setProps({ cohort: newCohort });
+ expect(forceStateMock).toHaveBeenLastCalledWith({
+ a: defaultProps.a,
+ b: defaultProps.b,
+ cohort: newCohort,
+ });
+ });
+
+ it('does not force state if cohorts have same values in differing orders', () => {
+ wrapper.setProps({
+ traceDiffState: {
+ ...defaultProps.traceDiffState,
+ cohort: defaultProps.traceDiffState.cohort.slice().reverse(),
+ },
+ });
+ expect(forceStateMock).not.toHaveBeenCalled();
+ });
+ });
+
+ it('requests traces lacking a state', () => {
+ const newId0 = 'new-id-0';
+ const newId1 = 'new-id-1';
+ expect(fetchMultipleTracesMock).toHaveBeenCalledTimes(0);
+ wrapper.setProps({ cohort: [...defaultProps.cohort, newId0, newId1] });
+ expect(fetchMultipleTracesMock).toHaveBeenCalledWith([newId0, newId1]);
+ expect(fetchMultipleTracesMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not request traces if all traces have a state', () => {
+ const newId0 = 'new-id-0';
+ const newId1 = 'new-id-1';
+ expect(fetchMultipleTracesMock).toHaveBeenCalledTimes(0);
+ const cohort = [...defaultProps.cohort, newId0, newId1];
+ const tracesData = new Map(defaultProps.tracesData);
+ tracesData.set(newId0, { id: newId0, state: fetchedState.ERROR });
+ tracesData.set(newId1, { id: newId0, state: fetchedState.LOADING });
+ wrapper.setProps({ cohort, tracesData });
+ expect(fetchMultipleTracesMock).not.toHaveBeenCalled();
+ });
+
+ it('updates url when TraceDiffHeader sets a or b', () => {
+ wrapper.find(TraceDiffHeader).prop('diffSetA')(newAValue);
+ expect(getUrlSpy).toHaveBeenLastCalledWith({
+ a: newAValue.toLowerCase(),
+ b: defaultProps.b,
+ cohort: defaultProps.cohort,
+ });
+
+ wrapper.find(TraceDiffHeader).prop('diffSetB')(newBValue);
+ expect(getUrlSpy).toHaveBeenLastCalledWith({
+ a: defaultProps.a,
+ b: newBValue.toLowerCase(),
+ cohort: defaultProps.cohort,
+ });
+
+ wrapper.find(TraceDiffHeader).prop('diffSetA')('');
+ expect(getUrlSpy).toHaveBeenLastCalledWith({
+ a: defaultProps.a,
+ b: defaultProps.b,
+ cohort: defaultProps.cohort,
+ });
+
+ wrapper.find(TraceDiffHeader).prop('diffSetB')('');
+ expect(getUrlSpy).toHaveBeenLastCalledWith({
+ a: defaultProps.a,
+ b: defaultProps.b,
+ cohort: defaultProps.cohort,
+ });
+
+ expect(historyPushMock).toHaveBeenCalledTimes(4);
+ });
+
+ describe('render', () => {
+ it('renders as expected', () => {
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('handles a and b not in props.tracesData', () => {
+ const tracesData = new Map(defaultProps.tracesData);
+ tracesData.delete(defaultA);
+ tracesData.delete(defaultB);
+ wrapper.setProps({ tracesData });
+ expect(wrapper.find(TraceDiffHeader).props()).toEqual(
+ expect.objectContaining({
+ a: { id: defaultA },
+ b: { id: defaultB },
+ })
+ );
+ });
+
+ it('handles absent a and b', () => {
+ wrapper.setProps({ a: null, b: null });
+ expect(wrapper.find(TraceDiffHeader).props()).toEqual(expect.objectContaining({ a: null, b: null }));
+ });
+ });
+
+ describe('TraceDiff--graphWrapper top offset', () => {
+ const arbitraryHeight = TOP_NAV_HEIGHT * 2;
+
+ it('initializes as TOP_NAV_HEIGHT', () => {
+ expect(wrapper.state().graphTopOffset).toBe(TOP_NAV_HEIGHT);
+ });
+
+ it('defaults to TOP_NAV_HEIGHT', () => {
+ wrapper.setState({ graphTopOffset: arbitraryHeight });
+ wrapper.instance().headerWrapperRef(null);
+ expect(wrapper.state().graphTopOffset).toBe(TOP_NAV_HEIGHT);
+ });
+
+ it('adjusts TraceDiff--graphWrapper top offset based on TraceDiffHeader height', () => {
+ wrapper.instance().headerWrapperRef({ clientHeight: arbitraryHeight });
+ expect(wrapper.state().graphTopOffset).toBe(TOP_NAV_HEIGHT + arbitraryHeight);
+ });
+ });
+
+ describe('mapStateToProps', () => {
+ const getOwnProps = ({ a = defaultA, b = defaultB } = {}) => ({
+ match: {
+ params: {
+ a,
+ b,
+ },
+ },
+ });
+ const makeTestReduxState = ({ cohortIds = defaultCohortIds } = {}) => ({
+ router: {
+ location: {
+ search: cohortIds.reduce((search, curr, i) => `${search}${i ? '&' : '?'}cohort=${curr}`, ''),
+ },
+ },
+ trace: {
+ traces: cohortIds.reduce((traces, id) => ({ ...traces, [id]: { id, state: fetchedState.DONE } }), {}),
+ },
+ traceDiff: {
+ a: 'trace-diff-a',
+ b: 'trace-diff-b',
+ },
+ });
+
+ it('gets a and b from ownProps', () => {
+ expect(mapStateToProps(makeTestReduxState(), getOwnProps())).toEqual(
+ expect.objectContaining({
+ a: defaultA,
+ b: defaultB,
+ })
+ );
+ });
+
+ it('defaults cohort to empty array if a, b, and cohort are not available', () => {
+ expect(
+ mapStateToProps(makeTestReduxState({ cohortIds: [] }), getOwnProps({ a: null, b: null })).cohort
+ ).toEqual([]);
+ });
+
+ it('gets cohort from ownProps and state.router.location.search', () => {
+ expect(mapStateToProps(makeTestReduxState(), getOwnProps()).cohort).toEqual([
+ defaultA,
+ defaultB,
+ ...defaultCohortIds,
+ ]);
+ });
+
+ it('filters falsy values from cohort', () => {
+ expect(mapStateToProps(makeTestReduxState(), getOwnProps({ a: null })).cohort).toEqual([
+ defaultB,
+ ...defaultCohortIds,
+ ]);
+
+ expect(mapStateToProps(makeTestReduxState(), getOwnProps({ b: null })).cohort).toEqual([
+ defaultA,
+ ...defaultCohortIds,
+ ]);
+
+ expect(
+ mapStateToProps(
+ makeTestReduxState({ cohortIds: [...defaultCohortIds, '', nonDefaultCohortId] }),
+ getOwnProps()
+ ).cohort
+ ).toEqual([defaultA, defaultB, ...defaultCohortIds, nonDefaultCohortId]);
+ });
+
+ it('filters redundant values from cohort', () => {
+ expect(
+ mapStateToProps(
+ makeTestReduxState({ cohortIds: [...defaultCohortIds, nonDefaultCohortId] }),
+ getOwnProps({ a: nonDefaultCohortId })
+ ).cohort
+ ).toEqual([nonDefaultCohortId, defaultB, ...defaultCohortIds]);
+
+ expect(
+ mapStateToProps(
+ makeTestReduxState({ cohortIds: [...defaultCohortIds, nonDefaultCohortId] }),
+ getOwnProps({ b: nonDefaultCohortId })
+ ).cohort
+ ).toEqual([defaultA, nonDefaultCohortId, ...defaultCohortIds]);
+
+ expect(
+ mapStateToProps(
+ makeTestReduxState({ cohortIds: [...defaultCohortIds, nonDefaultCohortId, nonDefaultCohortId] }),
+ getOwnProps()
+ ).cohort
+ ).toEqual([defaultA, defaultB, ...defaultCohortIds, nonDefaultCohortId]);
+ });
+
+ // This test may false negative if previous tests are failing
+ it('builds tracesData Map from cohort and state.trace.traces', () => {
+ const { tracesData, cohort: { length: expectedSize } } = mapStateToProps(
+ makeTestReduxState(),
+ getOwnProps()
+ );
+ defaultCohortIds.forEach(id => {
+ expect(tracesData.get(id)).toEqual({
+ id,
+ state: fetchedState.DONE,
+ });
+ });
+ expect(tracesData.get(defaultA)).toEqual({
+ id: defaultA,
+ state: null,
+ });
+ expect(tracesData.get(defaultB)).toEqual({
+ id: defaultB,
+ state: null,
+ });
+ expect(tracesData.size).toBe(expectedSize);
+ });
+
+ it('includes state.traceDiff as traceDiffState', () => {
+ const testReduxState = makeTestReduxState();
+ const { traceDiffState } = mapStateToProps(testReduxState, getOwnProps());
+ expect(traceDiffState).toBe(testReduxState.traceDiff);
+ });
+ });
+
+ describe('mapDispatchToProps', () => {
+ let bindActionCreatorsSpy;
+
+ beforeAll(() => {
+ bindActionCreatorsSpy = jest.spyOn(redux, 'bindActionCreators').mockImplementation(actions => {
+ if (actions === jaegerApiActions) {
+ return { fetchMultipleTraces: fetchMultipleTracesMock };
+ } else if (actions === diffActions) {
+ return { forceState: forceStateMock };
+ }
+ return {};
+ });
+ });
+
+ afterAll(() => {
+ bindActionCreatorsSpy.mockRestore();
+ });
+
+ it('correctly binds actions to dispatch', () => {
+ const dispatchMock = () => {};
+ const result = mapDispatchToProps(dispatchMock);
+ expect(result.fetchMultipleTraces).toBe(fetchMultipleTracesMock);
+ expect(result.forceState).toBe(forceStateMock);
+ expect(bindActionCreatorsSpy.mock.calls[0][1]).toBe(dispatchMock);
+ });
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.js
index 9c0ae041cb..b8401b7a8b 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.js
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.js
@@ -39,7 +39,7 @@ type Props = {
const { classNameIsSmall } = DirectedGraph.propsFactories;
-class TraceDiffGraph extends React.PureComponent {
+export class UnconnectedTraceDiffGraph extends React.PureComponent {
props: Props;
layoutManager: LayoutManager;
@@ -126,4 +126,4 @@ class TraceDiffGraph extends React.PureComponent {
}
}
-export default connect(extractUiFindFromState)(TraceDiffGraph);
+export default connect(extractUiFindFromState)(UnconnectedTraceDiffGraph);
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.test.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.test.js
new file mode 100644
index 0000000000..28bf70d558
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/TraceDiffGraph.test.js
@@ -0,0 +1,155 @@
+// Copyright (c) 2019 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 * as React from 'react';
+import { shallow } from 'enzyme';
+// import _mapValues from 'lodash/mapValues';
+
+import { UnconnectedTraceDiffGraph as TraceDiffGraph } from './TraceDiffGraph';
+import ErrorMessage from '../../common/ErrorMessage';
+import LoadingIndicator from '../../common/LoadingIndicator';
+import { fetchedState } from '../../../constants';
+
+describe('TraceDiffGraph', () => {
+ const props = {
+ a: {
+ data: {
+ spans: [],
+ traceID: 'trace-id-a',
+ },
+ error: null,
+ id: 'trace-id-a',
+ state: fetchedState.DONE,
+ },
+ b: {
+ data: {
+ spans: [],
+ traceID: 'trace-id-b',
+ },
+ error: null,
+ id: 'trace-id-b',
+ state: fetchedState.DONE,
+ },
+ };
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallow();
+ });
+
+ it('renders warning when a or b are not provided', () => {
+ expect(wrapper.find('h1').length).toBe(0);
+
+ wrapper.setProps({ a: undefined });
+ expect(wrapper.find('h1').length).toBe(1);
+ expect(wrapper.find('h1').text()).toBe('At least two Traces are needed');
+
+ wrapper.setProps({ b: undefined });
+ expect(wrapper.find('h1').length).toBe(1);
+ expect(wrapper.find('h1').text()).toBe('At least two Traces are needed');
+
+ wrapper.setProps({ a: props.a });
+ expect(wrapper.find('h1').length).toBe(1);
+ expect(wrapper.find('h1').text()).toBe('At least two Traces are needed');
+ });
+
+ it('renders warning when a or b have errored', () => {
+ expect(wrapper.find(ErrorMessage).length).toBe(0);
+
+ const errorA = 'some error text for trace a';
+ wrapper.setProps({
+ a: {
+ ...props.a,
+ error: errorA,
+ },
+ });
+
+ expect(wrapper.find(ErrorMessage).length).toBe(1);
+ expect(wrapper.find(ErrorMessage).props()).toEqual(
+ expect.objectContaining({
+ error: errorA,
+ })
+ );
+ const errorB = 'some error text for trace a';
+ wrapper.setProps({
+ b: {
+ ...props.b,
+ error: errorB,
+ },
+ });
+
+ expect(wrapper.find(ErrorMessage).length).toBe(2);
+ expect(
+ wrapper
+ .find(ErrorMessage)
+ .at(1)
+ .props()
+ ).toEqual(
+ expect.objectContaining({
+ error: errorB,
+ })
+ );
+ wrapper.setProps({
+ a: props.a,
+ });
+ expect(wrapper.find(ErrorMessage).length).toBe(1);
+ expect(wrapper.find(ErrorMessage).props()).toEqual(
+ expect.objectContaining({
+ error: errorB,
+ })
+ );
+ });
+
+ it('renders a loading indicator when a or b are loading', () => {
+ expect(wrapper.find(LoadingIndicator).length).toBe(0);
+
+ wrapper.setProps({
+ a: {
+ state: fetchedState.LOADING,
+ },
+ });
+ expect(wrapper.find(LoadingIndicator).length).toBe(1);
+
+ wrapper.setProps({
+ b: {
+ state: fetchedState.LOADING,
+ },
+ });
+ expect(wrapper.find(LoadingIndicator).length).toBe(1);
+
+ wrapper.setProps({ a: props.a });
+ expect(wrapper.find(LoadingIndicator).length).toBe(1);
+ });
+
+ it('renders an empty div when a or b lack data', () => {
+ expect(wrapper.children().length).not.toBe(0);
+
+ const { data: unusedAData, ...aWithoutData } = props.a;
+ wrapper.setProps({ a: aWithoutData });
+ expect(wrapper.children().length).toBe(0);
+
+ const { data: unusedBData, ...bWithoutData } = props.b;
+ wrapper.setProps({ b: bWithoutData });
+ expect(wrapper.children().length).toBe(0);
+
+ wrapper.setProps({ a: props.a });
+ expect(wrapper.children().length).toBe(0);
+ });
+
+ it('cleans up layoutManager before unmounting', () => {
+ const layoutManager = jest.spyOn(wrapper.instance().layoutManager, 'stopAndRelease');
+ wrapper.unmount();
+ expect(layoutManager).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/__snapshots__/drawNode.test.js.snap b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/__snapshots__/drawNode.test.js.snap
new file mode 100644
index 0000000000..7b8d1ccb15
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/__snapshots__/drawNode.test.js.snap
@@ -0,0 +1,677 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`drawNode diffNode renders as expected when props.a and props.b are the same 1`] = `
+
+
+
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+ operationName
+ |
+
+
+
+ }
+ mouseEnterDelay={0.25}
+ mouseLeaveDelay={0.1}
+ overlayClassName="DiffNode--popover is-same"
+ overlayStyle={Object {}}
+ placement="top"
+ prefixCls="ant-popover"
+ transitionName="zoom-big"
+ trigger="hover"
+>
+
+
+
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+ operationName
+ |
+
+
+
+
+`;
+
+exports[`drawNode diffNode renders as expected when props.a is 0 1`] = `
+
+
+
+
+
+ +
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+
+ +
+
+ 100
+
+ %
+
+ |
+
+ operationName
+ |
+
+
+
+ }
+ mouseEnterDelay={0.25}
+ mouseLeaveDelay={0.1}
+ overlayClassName="DiffNode--popover is-changed is-added"
+ overlayStyle={Object {}}
+ placement="top"
+ prefixCls="ant-popover"
+ transitionName="zoom-big"
+ trigger="hover"
+>
+
+
+
+
+
+ +
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+
+ +
+
+ 100
+
+ %
+
+ |
+
+ operationName
+ |
+
+
+
+
+`;
+
+exports[`drawNode diffNode renders as expected when props.a is less than props.b 1`] = `
+
+
+
+
+
+ +
+
+ 50
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+
+ +
+
+ 50
+
+ %
+
+ |
+
+ operationName
+ |
+
+
+
+ }
+ mouseEnterDelay={0.25}
+ mouseLeaveDelay={0.1}
+ overlayClassName="DiffNode--popover is-changed is-more"
+ overlayStyle={Object {}}
+ placement="top"
+ prefixCls="ant-popover"
+ transitionName="zoom-big"
+ trigger="hover"
+>
+
+
+
+
+
+ +
+
+ 50
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+
+ +
+
+ 50
+
+ %
+
+ |
+
+ operationName
+ |
+
+
+
+
+`;
+
+exports[`drawNode diffNode renders as expected when props.a is more than props.b 1`] = `
+
+
+
+
+
+ -
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+
+ -
+
+ 50
+
+ %
+
+ |
+
+ operationName
+ |
+
+
+
+ }
+ mouseEnterDelay={0.25}
+ mouseLeaveDelay={0.1}
+ overlayClassName="DiffNode--popover is-changed is-less"
+ overlayStyle={Object {}}
+ placement="top"
+ prefixCls="ant-popover"
+ transitionName="zoom-big"
+ trigger="hover"
+>
+
+
+
+
+
+ -
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+
+ -
+
+ 50
+
+ %
+
+ |
+
+ operationName
+ |
+
+
+
+
+`;
+
+exports[`drawNode diffNode renders as expected when props.b is 0 1`] = `
+
+
+
+
+
+ -
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+
+ -
+
+ 100
+
+ %
+
+ |
+
+ operationName
+ |
+
+
+
+ }
+ mouseEnterDelay={0.25}
+ mouseLeaveDelay={0.1}
+ overlayClassName="DiffNode--popover is-changed is-removed"
+ overlayStyle={Object {}}
+ placement="top"
+ prefixCls="ant-popover"
+ transitionName="zoom-big"
+ trigger="hover"
+>
+
+
+
+
+
+ -
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+
+ -
+
+ 100
+
+ %
+
+ |
+
+ operationName
+ |
+
+
+
+
+`;
+
+exports[`drawNode diffNode renders as expected when props.isUiFindMatch is true 1`] = `
+
+
+
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+ operationName
+ |
+
+
+
+ }
+ mouseEnterDelay={0.25}
+ mouseLeaveDelay={0.1}
+ overlayClassName="DiffNode--popover is-same is-ui-find-match"
+ overlayStyle={Object {}}
+ placement="top"
+ prefixCls="ant-popover"
+ transitionName="zoom-big"
+ trigger="hover"
+>
+
+
+
+
+ 100
+ |
+
+
+ serviceName
+
+
+ |
+
+
+
+ operationName
+ |
+
+
+
+
+`;
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.test.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.test.js
new file mode 100644
index 0000000000..a4796ee53f
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffGraph/drawNode.test.js
@@ -0,0 +1,111 @@
+// Copyright (c) 2019 The Jaeger Authors.
+//
+// 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 drawNodeGenerator, { DiffNode } from './drawNode';
+
+describe('drawNode', () => {
+ const members = [
+ {
+ span: {
+ spanID: 'members-span-id-0',
+ },
+ },
+ {
+ span: {
+ spanID: 'members-span-id-1',
+ },
+ },
+ ];
+ const operation = 'operationName';
+ const service = 'serviceName';
+ describe('diffNode', () => {
+ const defaultCount = 100;
+ const props = {
+ a: defaultCount,
+ b: defaultCount,
+ members,
+ operation,
+ service,
+ };
+
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallow();
+ });
+
+ it('renders as expected when props.a and props.b are the same', () => {
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('renders as expected when props.a is less than props.b', () => {
+ wrapper.setProps({ a: defaultCount / 2 });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('renders as expected when props.a is more than props.b', () => {
+ wrapper.setProps({ a: defaultCount * 2 });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('renders as expected when props.a is 0', () => {
+ wrapper.setProps({ a: 0 });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('renders as expected when props.b is 0', () => {
+ wrapper.setProps({ b: 0 });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('renders as expected when props.isUiFindMatch is true', () => {
+ wrapper.setProps({ isUiFindMatch: true });
+ expect(wrapper).toMatchSnapshot();
+ });
+ });
+
+ describe('drawNode function', () => {
+ const dataKey = 'data-key';
+ const dataValue = 'data-value';
+ const key = 'vertex key';
+ const vertex = {
+ data: {
+ data: {
+ [dataKey]: dataValue,
+ },
+ members,
+ operation,
+ service,
+ },
+ key,
+ };
+
+ it('extracts values from vertex.data', () => {
+ const drawNodeResult = drawNodeGenerator(new Set())(vertex);
+ expect(drawNodeResult.props[dataKey]).toBe(dataValue);
+ expect(drawNodeResult.props.isUiFindMatch).toBe(false);
+ expect(drawNodeResult.props.members).toBe(members);
+ expect(drawNodeResult.props.operation).toBe(operation);
+ expect(drawNodeResult.props.service).toBe(service);
+ });
+
+ it('passes isUiFindMatch as true if key is in set', () => {
+ const drawNodeResult = drawNodeGenerator(new Set([key]))(vertex);
+ expect(drawNodeResult.props.isUiFindMatch).toBe(true);
+ });
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/CohortTable.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/CohortTable.js
index e3a44153ad..d4fd088fe1 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/CohortTable.js
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/CohortTable.js
@@ -43,7 +43,7 @@ const defaultRowSelection = {
type: 'radio',
};
-const NEED_MORE_TRACES_MESSAGE = (
+export const NEED_MORE_TRACES_MESSAGE = (
Enter a Trace ID or perform a search and select from the results.
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/CohortTable.test.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/CohortTable.test.js
new file mode 100644
index 0000000000..745e4326ea
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/CohortTable.test.js
@@ -0,0 +1,241 @@
+// Copyright (c) 2019 The Jaeger Authors.
+//
+// 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 { Table, Tag } from 'antd';
+
+import CohortTable, { NEED_MORE_TRACES_MESSAGE } from './CohortTable';
+import TraceTimelineLink from './TraceTimelineLink';
+import RelativeDate from '../../common/RelativeDate';
+import TraceName from '../../common/TraceName';
+import { fetchedState } from '../../../constants';
+import * as dateUtils from '../../../utils/date';
+
+const { Column } = Table;
+
+describe('CohortTable', () => {
+ const cohort = [
+ {
+ data: {
+ traceName: 'trace name 0',
+ },
+ error: 'api error',
+ id: 'trace-id-0',
+ state: fetchedState.ERROR,
+ },
+ {
+ id: 'trace-id-1',
+ },
+ {
+ id: 'trace-id-2',
+ },
+ ];
+ const selectTrace = jest.fn();
+ const props = {
+ cohort,
+ current: cohort[0].id,
+ selection: {
+ [cohort[0].id]: {
+ label: 'selected index 0',
+ },
+ },
+ selectTrace,
+ };
+
+ let formatDurationSpy;
+ let wrapper;
+
+ /**
+ * Creates a new wrapper with default props and specified props. It is necessary to create a new wrapper
+ * when props change because enzyme does not support wrapper.setProps for classes that render an array of
+ * elements.
+ *
+ * @param {Object} [specifiedProps={}] - Props to set that are different from props defined above.
+ * @returns {Object} - New wrapper.
+ */
+ function setProps(specifiedProps = {}) {
+ wrapper = shallow();
+ }
+
+ beforeAll(() => {
+ formatDurationSpy = jest.spyOn(dateUtils, 'formatDuration');
+ });
+
+ beforeEach(() => {
+ selectTrace.mockReset();
+ formatDurationSpy.mockReset();
+ setProps();
+ });
+
+ it('renders as expected', () => {
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ describe('row selection', () => {
+ let rowSelection;
+
+ function updateRowSelection() {
+ rowSelection = wrapper.find(Table).prop('rowSelection');
+ }
+
+ beforeEach(() => {
+ updateRowSelection();
+ });
+
+ it('defaults selectedRowKeys to empty array', () => {
+ setProps({ current: undefined });
+ updateRowSelection();
+ expect(rowSelection.selectedRowKeys).toEqual([]);
+ });
+
+ it('calls props.selectTrace on row selection', () => {
+ rowSelection.onChange([cohort[1].id, cohort[2].id]);
+ expect(selectTrace).toHaveBeenCalledWith(cohort[1].id);
+ });
+
+ it('calculates checkbox props for selected and current record with error', () => {
+ expect(rowSelection.getCheckboxProps(cohort[0])).toEqual({ disabled: true });
+ });
+
+ it('calculates checkbox props for selected and current record without error', () => {
+ expect(rowSelection.getCheckboxProps({ ...cohort[0], state: fetchedState.DONE })).toEqual({});
+ });
+
+ it('calculates checkbox props for selected but not current record without error', () => {
+ setProps({
+ selection: {
+ ...props.selecetion,
+ [cohort[1].id]: {
+ label: 'selected index 1',
+ },
+ },
+ });
+ updateRowSelection();
+ expect(rowSelection.getCheckboxProps(cohort[1])).toEqual({ disabled: true });
+ });
+
+ it('calculates checkbox props for not selected record', () => {
+ expect(rowSelection.getCheckboxProps(cohort[1])).toEqual({});
+ });
+ });
+
+ it('renders shortened id', () => {
+ const idRenderer = wrapper
+ .find(Column)
+ .find('[dataIndex="id"]')
+ .prop('render');
+ const traceID = 'trace-id-longer-than-eight-characters';
+ const renderedId = shallow(idRenderer(traceID));
+ expect(renderedId.hasClass('u-tx-muted')).toBe(true);
+ expect(renderedId.text()).toBe(traceID.slice(0, 7));
+ });
+
+ it('renders TraceName fragment when given complete data', () => {
+ const traceNameColumnRenderer = wrapper
+ .find(Column)
+ .find('[dataIndex="data.traceName"]')
+ .prop('render');
+ const testTrace = cohort[0];
+ const { id, error, state, data: { traceName } } = testTrace;
+ const renderedTraceNameColumn = shallow(
+ // traceNameRenderer returns a React Fragment, wrapper div helps enzyme
+ {traceNameColumnRenderer('unused argument', testTrace)}
+ );
+
+ const tag = renderedTraceNameColumn.find(Tag);
+ expect(tag.length).toBe(1);
+ expect(tag.html().includes(props.selection[id].label)).toBe(true);
+
+ const renderedTraceName = renderedTraceNameColumn.find(TraceName);
+ expect(renderedTraceName.length).toBe(1);
+ expect(renderedTraceName.props()).toEqual(
+ expect.objectContaining({
+ error,
+ state,
+ traceName,
+ })
+ );
+ });
+
+ it('renders TraceName fragment when given minimal data', () => {
+ const traceNameColumnRenderer = wrapper
+ .find(Column)
+ .find('[dataIndex="data.traceName"]')
+ .prop('render');
+ const testTrace = cohort[1];
+ const renderedTraceNameColumn = shallow(
+ // traceNameRenderer returns a React Fragment, wrapper div helps enzyme
+ {traceNameColumnRenderer('unused argument', testTrace)}
+ );
+
+ expect(renderedTraceNameColumn.find(Tag).length).toBe(0);
+ expect(renderedTraceNameColumn.find(TraceName).length).toBe(1);
+ });
+
+ it('renders date iff record state is fetchedState.DONE', () => {
+ const dateRenderer = wrapper
+ .find(Column)
+ .find('[dataIndex="data.startTime"]')
+ .prop('render');
+ const date = 1548689901403;
+
+ expect(dateRenderer(date, { state: fetchedState.ERROR })).toBe(false);
+ const renderedDate = dateRenderer(date, { state: fetchedState.DONE });
+ expect(renderedDate.type).toBe(RelativeDate);
+ expect(renderedDate.props).toEqual({
+ fullMonthName: true,
+ includeTime: true,
+ value: date / 1000,
+ });
+ });
+
+ it('renders duration iff record state is fetchedState.DONE', () => {
+ const durationRenderer = wrapper
+ .find(Column)
+ .find('[dataIndex="data.duration"]')
+ .prop('render');
+ const duration = 150;
+ const formatDurationSpyMockReturnValue = 'formatDurationSpyMockReturnValue';
+ formatDurationSpy.mockReturnValue(formatDurationSpyMockReturnValue);
+
+ expect(durationRenderer(duration, { state: fetchedState.ERROR })).toBe(false);
+ expect(formatDurationSpy).toHaveBeenCalledTimes(0);
+
+ expect(durationRenderer(duration, { state: fetchedState.DONE })).toBe(formatDurationSpyMockReturnValue);
+ expect(formatDurationSpy).toHaveBeenCalledTimes(1);
+ expect(formatDurationSpy).toHaveBeenCalledWith(duration);
+ });
+
+ it('renders link', () => {
+ const linkRenderer = wrapper
+ .find(Column)
+ .find('[dataIndex="data.traceID"]')
+ .prop('render');
+ const traceID = 'trace-id';
+ const renderedLink = linkRenderer(traceID);
+ expect(renderedLink.type).toBe(TraceTimelineLink);
+ expect(renderedLink.props).toEqual({
+ traceID,
+ });
+ });
+
+ it('renders NEED_MORE_TRACES_MESSAGE if cohort is too small', () => {
+ expect(wrapper.contains(NEED_MORE_TRACES_MESSAGE)).toBe(false);
+ setProps({ cohort: cohort.slice(0, 1) });
+ expect(wrapper.contains(NEED_MORE_TRACES_MESSAGE)).toBe(true);
+ setProps({ cohort: [] });
+ expect(wrapper.contains(NEED_MORE_TRACES_MESSAGE)).toBe(true);
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceDiffHeader.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceDiffHeader.js
index 59bce3ee40..81e515fa94 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceDiffHeader.js
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceDiffHeader.js
@@ -76,10 +76,9 @@ export default class TraceDiffHeader extends React.PureComponent {
const { tableVisible } = this.state;
const { data: aData = {}, id: aId, state: aState, error: aError } = a || {};
const { data: bData = {}, id: bId, state: bState, error: bError } = b || {};
- const selection = {
- [aId || '_']: { label: 'A' },
- [bId || '__']: { label: 'B' },
- };
+ const selection = {};
+ if (aId) selection[aId] = { label: 'A' };
+ if (bId) selection[bId] = { label: 'B' };
const cohortTableA = (
);
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceDiffHeader.test.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceDiffHeader.test.js
new file mode 100644
index 0000000000..3a126b4a54
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceDiffHeader.test.js
@@ -0,0 +1,238 @@
+// Copyright (c) 2019 The Jaeger Authors.
+//
+// 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 { Popover } from 'antd';
+
+import TraceDiffHeader from './TraceDiffHeader';
+import { fetchedState } from '../../../constants';
+
+describe('TraceDiffHeader', () => {
+ const cohort = [
+ {
+ data: {
+ duration: 0,
+ // purposefully missing spans
+ startTime: 0,
+ traceName: 'cohort-trace-name-0',
+ },
+ error: 'error 0',
+ id: 'cohort-id-0',
+ state: fetchedState.ERROR,
+ },
+ {
+ data: {
+ duration: 100,
+ spans: [
+ {
+ spanID: 'trace-1-span-0',
+ },
+ ],
+ startTime: 100,
+ traceName: 'cohort-trace-name-1',
+ },
+ error: 'error 1',
+ id: 'cohort-id-1',
+ state: fetchedState.DONE,
+ },
+ {
+ data: {
+ duration: 200,
+ spans: [
+ {
+ spanID: 'trace-2-span-1',
+ },
+ {
+ spanID: 'trace-2-span-2',
+ },
+ ],
+ startTime: 200,
+ traceName: 'cohort-trace-name-2',
+ },
+ error: 'error 2',
+ id: 'cohort-id-2',
+ state: fetchedState.DONE,
+ },
+ {
+ data: {
+ duration: 300,
+ spans: [
+ {
+ spanID: 'trace-3-span-1',
+ },
+ {
+ spanID: 'trace-3-span-2',
+ },
+ {
+ spanID: 'trace-3-span-3',
+ },
+ ],
+ startTime: 300,
+ traceName: 'cohort-trace-name-3',
+ },
+ error: 'error 3',
+ id: 'cohort-id-3',
+ state: fetchedState.DONE,
+ },
+ ];
+ const diffSetA = jest.fn();
+ const diffSetB = jest.fn();
+ const props = {
+ a: cohort[1],
+ b: cohort[2],
+ cohort,
+ diffSetA,
+ diffSetB,
+ };
+
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallow();
+ });
+
+ it('renders as expected', () => {
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('handles trace without spans', () => {
+ wrapper.setProps({ a: cohort[0] });
+ });
+
+ it('handles absent a', () => {
+ wrapper.setProps({ a: null });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('handles absent b', () => {
+ wrapper.setProps({ b: null });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('handles absent a & b', () => {
+ wrapper.setProps({ a: null, b: null });
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ it('manages visibility correctly', () => {
+ expect(wrapper.state().tableVisible).toBe(null);
+ const popovers = wrapper.find(Popover);
+ expect(popovers.length).toBe(2);
+ popovers.forEach(popover => expect(popover.prop('visible')).toBe(false));
+
+ wrapper
+ .find(Popover)
+ .at(0)
+ .prop('onVisibleChange')(true);
+ expect(
+ wrapper
+ .find(Popover)
+ .at(0)
+ .prop('visible')
+ ).toBe(true);
+ expect(
+ wrapper
+ .find(Popover)
+ .at(1)
+ .prop('visible')
+ ).toBe(false);
+
+ wrapper
+ .find(Popover)
+ .at(1)
+ .prop('onVisibleChange')(true);
+ expect(
+ wrapper
+ .find(Popover)
+ .at(0)
+ .prop('visible')
+ ).toBe(false);
+ expect(
+ wrapper
+ .find(Popover)
+ .at(1)
+ .prop('visible')
+ ).toBe(true);
+
+ // repeat onVisibleChange call to test that visibility remains correct
+ wrapper
+ .find(Popover)
+ .at(1)
+ .prop('onVisibleChange')(true);
+ expect(
+ wrapper
+ .find(Popover)
+ .at(0)
+ .prop('visible')
+ ).toBe(false);
+ expect(
+ wrapper
+ .find(Popover)
+ .at(1)
+ .prop('visible')
+ ).toBe(true);
+
+ wrapper
+ .find(Popover)
+ .at(1)
+ .prop('onVisibleChange')(false);
+ wrapper.find(Popover).forEach(popover => expect(popover.prop('visible')).toBe(false));
+ });
+
+ it('creates bound functions to set a & b and passes them to Popover JSX props correctly', () => {
+ const cohortTableASelectionID = 'cohortTableASelectionID';
+ const traceIdInputASelectionID = 'traceIdInputASelectionID';
+ expect(props.diffSetA).not.toHaveBeenCalled();
+ const cohortTableBSelectionID = 'cohortTableBSelectionID';
+ const traceIdInputBSelectionID = 'traceIdInputBSelectionID';
+ expect(props.diffSetB).not.toHaveBeenCalled();
+
+ wrapper.setState({ tableVisible: 'a' });
+ wrapper
+ .find(Popover)
+ .at(0)
+ .prop('content')
+ .props.selectTrace(cohortTableASelectionID);
+ expect(props.diffSetA).toHaveBeenLastCalledWith(cohortTableASelectionID);
+ expect(wrapper.state().tableVisible).toBe(null);
+
+ wrapper.setState({ tableVisible: 'a' });
+ wrapper
+ .find(Popover)
+ .at(0)
+ .prop('title')
+ .props.selectTrace(traceIdInputASelectionID);
+ expect(props.diffSetA).toHaveBeenLastCalledWith(traceIdInputASelectionID);
+ expect(wrapper.state().tableVisible).toBe(null);
+
+ wrapper.setState({ tableVisible: 'b' });
+ wrapper
+ .find(Popover)
+ .at(1)
+ .prop('content')
+ .props.selectTrace(cohortTableBSelectionID);
+ expect(props.diffSetB).toHaveBeenLastCalledWith(cohortTableBSelectionID);
+ expect(wrapper.state().tableVisible).toBe(null);
+
+ wrapper.setState({ tableVisible: 'b' });
+ wrapper
+ .find(Popover)
+ .at(1)
+ .prop('title')
+ .props.selectTrace(traceIdInputBSelectionID);
+ expect(props.diffSetB).toHaveBeenLastCalledWith(traceIdInputBSelectionID);
+ expect(wrapper.state().tableVisible).toBe(null);
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceIdInput.test.js b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceIdInput.test.js
new file mode 100644
index 0000000000..7c143b2063
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/TraceIdInput.test.js
@@ -0,0 +1,32 @@
+// Copyright (c) 2019 The Jaeger Authors.
+//
+// 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 { Input } from 'antd';
+
+import TraceIdInput from './TraceIdInput';
+
+describe('TraceIdInput', () => {
+ const props = {
+ selectTrace: jest.fn(),
+ };
+ const { Search } = Input;
+
+ it('renders as expected', () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.find(Search).prop('onSearch')).toBe(props.selectTrace);
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/CohortTable.test.js.snap b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/CohortTable.test.js.snap
new file mode 100644
index 0000000000..93bbf93060
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/CohortTable.test.js.snap
@@ -0,0 +1,87 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CohortTable renders as expected 1`] = `
+Array [
+ ,
+ "",
+]
+`;
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/TraceDiffHeader.test.js.snap b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/TraceDiffHeader.test.js.snap
new file mode 100644
index 0000000000..fb2acc4d8e
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/TraceDiffHeader.test.js.snap
@@ -0,0 +1,1007 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TraceDiffHeader handles absent a & b 1`] = `
+
+
+
+ A
+
+
+
+ }
+ mouseEnterDelay={0.1}
+ mouseLeaveDelay={0.1}
+ onVisibleChange={[Function]}
+ overlayClassName="TraceDiffHeader--popover"
+ overlayStyle={Object {}}
+ placement="bottomLeft"
+ prefixCls="ant-popover"
+ title={
+
+ }
+ transitionName="zoom-big"
+ trigger="click"
+ visible={false}
+ >
+
+
+
+
+
+
+ VS
+
+
+
+
+ B
+
+
+
+ }
+ mouseEnterDelay={0.1}
+ mouseLeaveDelay={0.1}
+ onVisibleChange={[Function]}
+ overlayClassName="TraceDiffHeader--popover"
+ overlayStyle={Object {}}
+ placement="bottomLeft"
+ prefixCls="ant-popover"
+ title={
+
+ }
+ transitionName="zoom-big"
+ trigger="click"
+ visible={false}
+ >
+
+
+
+
+
+`;
+
+exports[`TraceDiffHeader handles absent a 1`] = `
+
+
+
+ A
+
+
+
+ }
+ mouseEnterDelay={0.1}
+ mouseLeaveDelay={0.1}
+ onVisibleChange={[Function]}
+ overlayClassName="TraceDiffHeader--popover"
+ overlayStyle={Object {}}
+ placement="bottomLeft"
+ prefixCls="ant-popover"
+ title={
+
+ }
+ transitionName="zoom-big"
+ trigger="click"
+ visible={false}
+ >
+
+
+
+
+
+
+ VS
+
+
+
+
+ B
+
+
+
+ }
+ mouseEnterDelay={0.1}
+ mouseLeaveDelay={0.1}
+ onVisibleChange={[Function]}
+ overlayClassName="TraceDiffHeader--popover"
+ overlayStyle={Object {}}
+ placement="bottomLeft"
+ prefixCls="ant-popover"
+ title={
+
+ }
+ transitionName="zoom-big"
+ trigger="click"
+ visible={false}
+ >
+
+
+
+
+
+`;
+
+exports[`TraceDiffHeader handles absent b 1`] = `
+
+
+
+ A
+
+
+
+ }
+ mouseEnterDelay={0.1}
+ mouseLeaveDelay={0.1}
+ onVisibleChange={[Function]}
+ overlayClassName="TraceDiffHeader--popover"
+ overlayStyle={Object {}}
+ placement="bottomLeft"
+ prefixCls="ant-popover"
+ title={
+
+ }
+ transitionName="zoom-big"
+ trigger="click"
+ visible={false}
+ >
+
+
+
+
+
+
+ VS
+
+
+
+
+ B
+
+
+
+ }
+ mouseEnterDelay={0.1}
+ mouseLeaveDelay={0.1}
+ onVisibleChange={[Function]}
+ overlayClassName="TraceDiffHeader--popover"
+ overlayStyle={Object {}}
+ placement="bottomLeft"
+ prefixCls="ant-popover"
+ title={
+
+ }
+ transitionName="zoom-big"
+ trigger="click"
+ visible={false}
+ >
+
+
+
+
+
+`;
+
+exports[`TraceDiffHeader renders as expected 1`] = `
+
+
+
+ A
+
+
+
+ }
+ mouseEnterDelay={0.1}
+ mouseLeaveDelay={0.1}
+ onVisibleChange={[Function]}
+ overlayClassName="TraceDiffHeader--popover"
+ overlayStyle={Object {}}
+ placement="bottomLeft"
+ prefixCls="ant-popover"
+ title={
+
+ }
+ transitionName="zoom-big"
+ trigger="click"
+ visible={false}
+ >
+
+
+
+
+
+
+ VS
+
+
+
+
+ B
+
+
+
+ }
+ mouseEnterDelay={0.1}
+ mouseLeaveDelay={0.1}
+ onVisibleChange={[Function]}
+ overlayClassName="TraceDiffHeader--popover"
+ overlayStyle={Object {}}
+ placement="bottomLeft"
+ prefixCls="ant-popover"
+ title={
+
+ }
+ transitionName="zoom-big"
+ trigger="click"
+ visible={false}
+ >
+
+
+
+
+
+`;
diff --git a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/TraceHeader.test.js.snap b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/TraceHeader.test.js.snap
index 03dd240773..06470e0eb1 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/TraceHeader.test.js.snap
+++ b/packages/jaeger-ui/src/components/TraceDiff/TraceDiffHeader/__snapshots__/TraceHeader.test.js.snap
@@ -106,29 +106,27 @@ exports[`TraceHeader renders as expected 1`] = `
className="TraecDiffHeader--traceTitle"
>
-
-
-
-
- trace-i
-
-
-
+ }
+ key="name"
+ state={null}
+ traceName="trace name"
+ />
+
+
+ trace-i
+
+
-
-
-
-
- trace-i
-
-
-
+ }
+ key="name"
+ state="FETCH_DONE"
+ traceName="trace name"
+ />
+
+
+ trace-i
+
+
+`;
diff --git a/packages/jaeger-ui/src/components/TraceDiff/__snapshots__/TraceDiff.test.js.snap b/packages/jaeger-ui/src/components/TraceDiff/__snapshots__/TraceDiff.test.js.snap
new file mode 100644
index 0000000000..e24f00eb34
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/__snapshots__/TraceDiff.test.js.snap
@@ -0,0 +1,75 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TraceDiff render renders as expected 1`] = `
+
+
+
+
+
+
+
+
+`;
diff --git a/packages/jaeger-ui/src/components/TraceDiff/duck.js b/packages/jaeger-ui/src/components/TraceDiff/duck.js
index 22cdbc7732..f82a353d0b 100644
--- a/packages/jaeger-ui/src/components/TraceDiff/duck.js
+++ b/packages/jaeger-ui/src/components/TraceDiff/duck.js
@@ -52,21 +52,21 @@ export const actions = fullActions.jaegerUi.traceDiff;
function cohortAddTrace(state, { payload }) {
const { traceID } = payload;
- const cohort = state.cohort.slice();
- if (cohort.indexOf(traceID) >= 0) {
+ if (state.cohort.indexOf(traceID) >= 0) {
return state;
}
+ const cohort = state.cohort.slice();
cohort.push(traceID);
return { ...state, cohort };
}
function cohortRemoveTrace(state, { payload }) {
const { traceID } = payload;
- const cohort = state.cohort.slice();
- const i = cohort.indexOf(traceID);
+ const i = state.cohort.indexOf(traceID);
if (i < 0) {
return state;
}
+ const cohort = state.cohort.slice();
cohort.splice(i, 1);
const a = state.a === traceID ? null : state.a;
const b = state.b === traceID ? null : state.b;
diff --git a/packages/jaeger-ui/src/components/TraceDiff/duck.test.js b/packages/jaeger-ui/src/components/TraceDiff/duck.test.js
new file mode 100644
index 0000000000..0d948d6821
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/duck.test.js
@@ -0,0 +1,135 @@
+// 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 { createStore } from 'redux';
+
+import reducer, { actions, newInitialState } from './duck';
+
+describe('TraceDiff/duck', () => {
+ const initialCohort = ['trace-id-0', 'trace-id-1', 'trace-id-2'];
+ const newTraceId = 'new-trace-id';
+ let store;
+
+ beforeEach(() => {
+ store = createStore(reducer, {
+ a: initialCohort[0],
+ b: initialCohort[1],
+ cohort: initialCohort,
+ });
+ });
+
+ describe('newInitialState', () => {
+ it('creates an empty set', () => {
+ expect(newInitialState()).toEqual({
+ a: null,
+ b: null,
+ cohort: [],
+ });
+ });
+ });
+
+ describe('cohortAddTrace', () => {
+ it('adds trace that does not already exist in state', () => {
+ const oldCohort = store.getState().cohort;
+ expect(oldCohort.includes(newTraceId)).toBe(false);
+
+ store.dispatch(actions.cohortAddTrace(newTraceId));
+ const newCohort = store.getState().cohort;
+ expect(newCohort).not.toBe(oldCohort);
+ expect(newCohort.includes(newTraceId)).toBe(true);
+ expect(newCohort).toEqual(expect.arrayContaining(oldCohort));
+ });
+
+ it('returns original state if traceID already exists in state', () => {
+ const state = store.getState();
+ store.dispatch(actions.cohortAddTrace(initialCohort[0]));
+ expect(store.getState()).toBe(state);
+ });
+ });
+
+ describe('cohortRemoveTrace', () => {
+ it('removes trace that exists in state.cohort', () => {
+ const oldCohort = store.getState().cohort;
+ store.dispatch(actions.cohortRemoveTrace(initialCohort[2]));
+ const newCohort = store.getState().cohort;
+ expect(newCohort).not.toBe(oldCohort);
+ expect(newCohort.includes(initialCohort[2])).toBe(false);
+ expect(newCohort).toEqual(oldCohort.slice(0, 2));
+ });
+
+ it('removes state.a', () => {
+ const oldState = store.getState();
+ const oldCohort = oldState.cohort;
+ store.dispatch(actions.cohortRemoveTrace(oldState.a));
+ const newState = store.getState();
+ const newCohort = newState.cohort;
+ expect(newState.a).toBe(null);
+ expect(newCohort).not.toBe(oldCohort);
+ expect(newCohort.includes(oldState.a)).toBe(false);
+ expect(newCohort).toEqual(oldCohort.slice(1));
+ });
+
+ it('removes state.b', () => {
+ const oldState = store.getState();
+ const oldCohort = oldState.cohort;
+ store.dispatch(actions.cohortRemoveTrace(oldState.b));
+ const newState = store.getState();
+ const newCohort = newState.cohort;
+ expect(newState.b).toBe(null);
+ expect(newCohort).not.toBe(oldCohort);
+ expect(newCohort.includes(oldState.b)).toBe(false);
+ expect(newCohort).toEqual(oldCohort.filter(entry => entry !== oldState.b));
+ });
+
+ it('returns original state if traceID already exists in state', () => {
+ const state = store.getState();
+ store.dispatch(actions.cohortRemoveTrace(newTraceId));
+ expect(store.getState()).toBe(state);
+ });
+ });
+
+ describe('diffSetA', () => {
+ it('set a to provided traceId', () => {
+ const oldState = store.getState();
+ store.dispatch(actions.diffSetA(newTraceId));
+ const newState = store.getState();
+ expect(newState).not.toBe(oldState);
+ expect(newState).toEqual({
+ ...oldState,
+ a: newTraceId,
+ });
+ });
+ });
+
+ describe('diffSetB', () => {
+ it('set b to provided traceId', () => {
+ const oldState = store.getState();
+ store.dispatch(actions.diffSetB(newTraceId));
+ const newState = store.getState();
+ expect(newState).not.toBe(oldState);
+ expect(newState).toEqual({
+ ...oldState,
+ b: newTraceId,
+ });
+ });
+ });
+
+ describe('forceState', () => {
+ it('returns given state', () => {
+ const newState = newInitialState();
+ store.dispatch(actions.forceState(newState));
+ expect(store.getState()).toBe(newState);
+ });
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TraceDiff/getValidState.test.js b/packages/jaeger-ui/src/components/TraceDiff/getValidState.test.js
new file mode 100644
index 0000000000..52c1dc5e02
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/getValidState.test.js
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 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 getValidState from './getValidState';
+
+describe('getValidState', () => {
+ const a = 'a string';
+ const b = 'b string';
+ const cohort = ['first string', 'second string', 'third string'];
+
+ it('handles absent argument', () => {
+ expect(getValidState()).toEqual({
+ a: undefined,
+ b: undefined,
+ cohort: [],
+ });
+ });
+
+ it('uses cohort kwarg when a and b are missing', () => {
+ expect(getValidState({ cohort })).toEqual({
+ a: cohort[0],
+ b: cohort[1],
+ cohort,
+ });
+ });
+
+ it('uses a and b when provided', () => {
+ expect(getValidState({ a, b, cohort })).toEqual({
+ a,
+ b,
+ cohort: [a, b, ...cohort],
+ });
+ });
+
+ it('uses b as a and cohort[0] for b when only b is provided', () => {
+ expect(getValidState({ b, cohort })).toEqual({
+ a: b,
+ b: cohort[0],
+ cohort: [b, ...cohort],
+ });
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TraceDiff/url.test.js b/packages/jaeger-ui/src/components/TraceDiff/url.test.js
new file mode 100644
index 0000000000..8997bde879
--- /dev/null
+++ b/packages/jaeger-ui/src/components/TraceDiff/url.test.js
@@ -0,0 +1,64 @@
+// Copyright (c) 2019 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 * as reactRouterDom from 'react-router-dom';
+
+import { ROUTE_PATH, matches, getUrl } from './url';
+
+describe('TraceDiff/url', () => {
+ describe('matches', () => {
+ const path = 'path argument';
+ let matchPathSpy;
+
+ beforeAll(() => {
+ matchPathSpy = jest.spyOn(reactRouterDom, 'matchPath');
+ });
+
+ it('calls matchPath with expected arguments', () => {
+ matches(path);
+ expect(matchPathSpy).toHaveBeenLastCalledWith(path, {
+ path: ROUTE_PATH,
+ strict: true,
+ exact: true,
+ });
+ });
+
+ it("returns truthiness of matchPath's return value", () => {
+ matchPathSpy.mockReturnValueOnce(null);
+ expect(matches(path)).toBe(false);
+ matchPathSpy.mockReturnValueOnce({});
+ expect(matches(path)).toBe(true);
+ });
+ });
+
+ describe('getUrl', () => {
+ it('handles an empty state', () => {
+ expect(getUrl()).toEqual('/trace/...');
+ });
+
+ it('handles a single traceId', () => {
+ const cohort = ['first'];
+ expect(getUrl({ cohort })).toEqual(`/trace/${cohort[0]}...?cohort=${cohort[0]}`);
+ });
+
+ it('handles multiple traceIds', () => {
+ const cohort = ['first', 'second', 'third'];
+ const result = getUrl({ cohort });
+ expect(result).toMatch(`${cohort[0]}...${cohort[1]}`);
+ cohort.forEach(cohortEntry => {
+ expect(result).toMatch(`cohort=${cohortEntry}`);
+ });
+ });
+ });
+});
diff --git a/packages/jaeger-ui/src/components/TracePage/index.test.js b/packages/jaeger-ui/src/components/TracePage/index.test.js
index c601a7a014..42a36d132d 100644
--- a/packages/jaeger-ui/src/components/TracePage/index.test.js
+++ b/packages/jaeger-ui/src/components/TracePage/index.test.js
@@ -20,8 +20,10 @@ jest.mock('./scroll-page');
jest.mock('../../utils/filter-spans');
jest.mock('../../utils/update-ui-find');
// mock these to enable mount()
+jest.mock('./TraceGraph/TraceGraph');
jest.mock('./TracePageHeader/SpanGraph');
jest.mock('./TracePageHeader/TracePageHeader.track');
+jest.mock('./TracePageHeader/TracePageSearchBar');
jest.mock('./TraceTimelineViewer');
import React from 'react';
@@ -37,6 +39,7 @@ import {
VIEW_MIN_RANGE,
} from './index';
import * as track from './index.track';
+import ArchiveNotifier from './ArchiveNotifier';
import { reset as resetShortcuts } from './keyboard-shortcuts';
import { cancel as cancelScroll } from './scroll-page';
import SpanGraph from './TracePageHeader/SpanGraph';
@@ -79,6 +82,7 @@ describe('', () => {
const trace = transformTraceData(traceGenerator.trace({}));
const defaultProps = {
+ acknowledgeArchive: () => {},
fetchTrace() {},
id: trace.traceID,
history: {
@@ -152,10 +156,6 @@ describe('', () => {
expect(filterSpansSpy).toHaveBeenLastCalledWith(uiFind, newTrace.data.spans);
});
- it.skip('renders a ', () => {
- expect(wrapper.find(TracePageHeader).get(0)).toBeTruthy();
- });
-
it('renders a a loading indicator when not provided a fetched trace', () => {
wrapper.setProps({ trace: null });
const loading = wrapper.find(LoadingIndicator);
@@ -173,6 +173,37 @@ describe('', () => {
expect(loading.length).toBe(1);
});
+ it('forces lowercase id', () => {
+ const replaceMock = jest.fn();
+ const props = {
+ ...defaultProps,
+ id: trace.traceID.toUpperCase(),
+ history: {
+ replace: replaceMock,
+ },
+ };
+ shallow();
+ expect(replaceMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ pathname: expect.stringContaining(trace.traceID),
+ })
+ );
+ });
+
+ it('focuses on search bar when there is a search bar and focusOnSearchBar is called', () => {
+ const focus = jest.fn();
+ wrapper.instance()._searchBar.current = {
+ focus,
+ };
+ wrapper.instance().focusOnSearchBar();
+ expect(focus).toHaveBeenCalledTimes(1);
+ });
+
+ it('handles absent search bar when there is not a search bar and focusOnSearchBar is called', () => {
+ expect(wrapper.instance()._searchBar.current).toBe(null);
+ wrapper.instance().focusOnSearchBar();
+ });
+
it('fetches the trace if necessary', () => {
const fetchTrace = sinon.spy();
wrapper = mount();
@@ -180,13 +211,13 @@ describe('', () => {
expect(fetchTrace.calledWith(trace.traceID)).toBe(true);
});
- it.skip("doesn't fetch the trace if already present", () => {
+ it("doesn't fetch the trace if already present", () => {
const fetchTrace = sinon.spy();
wrapper = mount();
expect(fetchTrace.called).toBeFalsy();
});
- it.skip('resets the view range when the trace changes', () => {
+ it('resets the view range when the trace changes', () => {
const altTrace = { ...trace, traceID: 'some-other-id' };
// mount because `.componentDidUpdate()`
wrapper = mount();
@@ -214,6 +245,130 @@ describe('', () => {
expect(cancelScroll.mock.calls).toEqual([[]]);
});
+ describe('TracePageHeader props', () => {
+ describe('canCollapse', () => {
+ it('is true if !embedded', () => {
+ expect(wrapper.find(TracePageHeader).prop('canCollapse')).toBe(true);
+ });
+
+ it('is true if either of embedded.timeline.hideSummary and embedded.timeline.hideMinimap are false', () => {
+ [true, false].forEach(hideSummary => {
+ [true, false].forEach(hideMinimap => {
+ const embedded = {
+ timeline: {
+ hideSummary,
+ hideMinimap,
+ },
+ };
+ wrapper.setProps({ embedded });
+ expect(wrapper.find(TracePageHeader).prop('canCollapse')).toBe(!hideSummary || !hideMinimap);
+ });
+ });
+ });
+ });
+
+ describe('calculates hideMap correctly', () => {
+ it('is true if on traceGraphView', () => {
+ wrapper.instance().traceDagEV = { vertices: [], nodes: [] };
+ wrapper.setState({ traceGraphView: true });
+ expect(wrapper.find(TracePageHeader).prop('hideMap')).toBe(true);
+ });
+
+ it('is true if embedded indicates it should be', () => {
+ wrapper.setProps({
+ embedded: {
+ timeline: {
+ hideMinimap: false,
+ },
+ },
+ });
+ expect(wrapper.find(TracePageHeader).prop('hideMap')).toBe(false);
+ wrapper.setProps({
+ embedded: {
+ timeline: {
+ hideMinimap: true,
+ },
+ },
+ });
+ expect(wrapper.find(TracePageHeader).prop('hideMap')).toBe(true);
+ });
+ });
+
+ describe('calculates hideSummary correctly', () => {
+ it('is false if embedded is not provided', () => {
+ expect(wrapper.find(TracePageHeader).prop('hideSummary')).toBe(false);
+ });
+
+ it('is true if embedded indicates it should be', () => {
+ wrapper.setProps({
+ embedded: {
+ timeline: {
+ hideSummary: false,
+ },
+ },
+ });
+ expect(wrapper.find(TracePageHeader).prop('hideSummary')).toBe(false);
+ wrapper.setProps({
+ embedded: {
+ timeline: {
+ hideSummary: true,
+ },
+ },
+ });
+ expect(wrapper.find(TracePageHeader).prop('hideSummary')).toBe(true);
+ });
+ });
+
+ describe('showArchiveButton', () => {
+ it('is true when not embedded and archive is enabled', () => {
+ [{ timeline: {} }, undefined].forEach(embedded => {
+ [true, false].forEach(archiveEnabled => {
+ wrapper.setProps({ embedded, archiveEnabled });
+ expect(wrapper.find(TracePageHeader).prop('showArchiveButton')).toBe(!embedded && archiveEnabled);
+ });
+ });
+ });
+ });
+
+ describe('resultCount', () => {
+ it('is the size of findMatchesIDs when available', () => {
+ expect(wrapper.find(TracePageHeader).prop('resultCount')).toBe(0);
+
+ const size = 20;
+ filterSpansSpy.mockReturnValueOnce({ size });
+ wrapper.setProps({ uiFind: 'new ui find to bust memo' });
+ expect(wrapper.find(TracePageHeader).prop('resultCount')).toBe(size);
+ });
+
+ it('defaults to 0', () => {
+ filterSpansSpy.mockReturnValueOnce(null);
+ wrapper.setProps({ uiFind: 'new ui find to bust memo' });
+ expect(wrapper.find(TracePageHeader).prop('resultCount')).toBe(0);
+ });
+ });
+
+ describe('isEmbedded derived props', () => {
+ it('toggles derived props when embedded is provided', () => {
+ expect(wrapper.find(TracePageHeader).props()).toEqual(
+ expect.objectContaining({
+ showShortcutsHelp: true,
+ showStandaloneLink: false,
+ showViewOptions: true,
+ })
+ );
+
+ wrapper.setProps({ embedded: { timeline: {} } });
+ expect(wrapper.find(TracePageHeader).props()).toEqual(
+ expect.objectContaining({
+ showShortcutsHelp: false,
+ showStandaloneLink: true,
+ showViewOptions: false,
+ })
+ );
+ });
+ });
+ });
+
describe('_adjustViewRange()', () => {
let instance;
let time;
@@ -277,6 +432,28 @@ describe('', () => {
});
});
+ describe('Archive', () => {
+ it('renders ArchiveNotifier if props.archiveEnabled is true', () => {
+ expect(wrapper.find(ArchiveNotifier).length).toBe(0);
+ wrapper.setProps({ archiveEnabled: true });
+ expect(wrapper.find(ArchiveNotifier).length).toBe(1);
+ });
+
+ it('calls props.acknowledgeArchive when ArchiveNotifier acknowledges', () => {
+ const acknowledgeArchive = jest.fn();
+ wrapper.setProps({ acknowledgeArchive, archiveEnabled: true });
+ wrapper.find(ArchiveNotifier).prop('acknowledge')();
+ expect(acknowledgeArchive).toHaveBeenCalledWith(defaultProps.id);
+ });
+
+ it("calls props.archiveTrace when TracePageHeader's archive button is clicked", () => {
+ const archiveTrace = jest.fn();
+ wrapper.setProps({ archiveTrace });
+ wrapper.find(TracePageHeader).prop('onArchiveClicked')();
+ expect(archiveTrace).toHaveBeenCalledWith(defaultProps.id);
+ });
+ });
+
describe('manages various UI state', () => {
let header;
let spanGraph;
@@ -296,7 +473,7 @@ describe('', () => {
refreshWrappers();
});
- it.skip('propagates headerHeight changes', () => {
+ it('propagates headerHeight changes', () => {
const h = 100;
const { setHeaderHeight } = wrapper.instance();
// use the method directly because it is a `ref` prop
@@ -313,17 +490,7 @@ describe('', () => {
expect(sections.length).toBe(0);
});
- it.skip('propagates textFilter changes', () => {
- const s = 'abc';
- const { updateTextFilter } = header.props();
- expect(header.prop('textFilter')).toBe('');
- updateTextFilter(s);
- wrapper.update();
- refreshWrappers();
- expect(header.prop('textFilter')).toBe(s);
- });
-
- it.skip('propagates slimView changes', () => {
+ it('propagates slimView changes', () => {
const { onSlimViewClicked } = header.props();
expect(header.prop('slimView')).toBe(false);
expect(spanGraph.type()).toBeDefined();
@@ -334,7 +501,24 @@ describe('', () => {
expect(spanGraph.length).toBe(0);
});
- it.skip('propagates viewRange changes', () => {
+ it('propagates textFilter changes', () => {
+ const s = 'abc';
+ expect(header.prop('textFilter')).toBeUndefined();
+ wrapper.setProps({ uiFind: s });
+ refreshWrappers();
+ expect(header.prop('textFilter')).toBe(s);
+ });
+
+ it('propagates traceGraphView changes', () => {
+ const { onTraceGraphViewClicked } = header.props();
+ expect(header.prop('traceGraphView')).toBe(false);
+ onTraceGraphViewClicked();
+ wrapper.update();
+ refreshWrappers();
+ expect(header.prop('traceGraphView')).toBe(true);
+ });
+
+ it('propagates viewRange changes', () => {
const viewRange = {
time: { current: [0, 1] },
};
@@ -375,7 +559,7 @@ describe('', () => {
refreshWrappers();
});
- it.skip('tracks setting the header to slim-view', () => {
+ it('tracks setting the header to slim-view', () => {
const { onSlimViewClicked } = header.props();
trackSlimHeaderToggle.mockReset();
onSlimViewClicked(true);
@@ -383,15 +567,7 @@ describe('', () => {
expect(trackSlimHeaderToggle.mock.calls).toEqual([[true], [false]]);
});
- it.skip('tracks setting or clearing the filter', () => {
- const { updateTextFilter } = header.props();
- track.trackFilter.mockClear();
- updateTextFilter('abc');
- updateTextFilter('');
- expect(track.trackFilter.mock.calls).toEqual([['abc'], ['']]);
- });
-
- it.skip('tracks changes to the viewRange', () => {
+ it('tracks changes to the viewRange', () => {
const src = 'some-source';
const { updateViewRangeTime } = spanGraph.props();
track.trackRange.mockClear();
@@ -453,6 +629,23 @@ describe('mapStateToProps()', () => {
});
});
+ it('handles falsy ownProps.match.params.id', () => {
+ const props = mapStateToProps(state, {
+ match: {
+ params: {
+ id: '',
+ },
+ },
+ });
+ expect(props).toEqual(
+ expect.objectContaining({
+ archiveTraceState: null,
+ id: '',
+ trace: null,
+ })
+ );
+ });
+
it('propagates fromSearch correctly', () => {
const fakeUrl = 'fake-url';
state.router.location.state = { fromSearch: fakeUrl };
diff --git a/packages/jaeger-ui/src/utils/plexus/set-on-graph.js b/packages/jaeger-ui/src/utils/plexus/set-on-graph.js
index 70b006734e..5f04315a01 100644
--- a/packages/jaeger-ui/src/utils/plexus/set-on-graph.js
+++ b/packages/jaeger-ui/src/utils/plexus/set-on-graph.js
@@ -1,6 +1,6 @@
// @flow
-// Copyright (c) 2018 Uber Technologies, Inc.
+// Copyright (c) 2019 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.
@@ -17,7 +17,7 @@
import _get from 'lodash/get';
const BASE_MATCH_SIZE = 8;
-const SCALABLE_MATCH_SIZE = 8;
+const SCALABLE_MATCH_SIZE = 4;
export function setOnEdgesContainer(state: Object) {
const { zoomTransform } = state;
diff --git a/packages/jaeger-ui/src/utils/plexus/set-on-graph.test.js b/packages/jaeger-ui/src/utils/plexus/set-on-graph.test.js
new file mode 100644
index 0000000000..d481e891e5
--- /dev/null
+++ b/packages/jaeger-ui/src/utils/plexus/set-on-graph.test.js
@@ -0,0 +1,72 @@
+// Copyright (c) 2019 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 { setOnNodesContainer, setOnEdgesContainer, setOnNode } from './set-on-graph';
+
+describe('Set on graph utils', () => {
+ describe('setOnNodesContainer', () => {
+ function getComputedWidth(k) {
+ const { style } = setOnNodesContainer({ zoomTransform: k != undefined ? { k } : undefined }); // eslint-disable-line eqeqeq
+ return Number.parseInt(style.outline.split(' ')[2].split('px')[0], 10);
+ }
+
+ const SIZE_IDENTITY = 12;
+
+ it('defaults style object with outline width off of 2 if zoomTransform.k is not provided', () => {
+ expect(getComputedWidth(null)).toBe(SIZE_IDENTITY);
+ expect(getComputedWidth(undefined)).toBe(SIZE_IDENTITY);
+ });
+
+ it('calculates style object with outline width at default size if zoomTransform.k is 1', () => {
+ expect(getComputedWidth(1)).toBe(SIZE_IDENTITY);
+ });
+
+ it('calculates style object with outline width one third larger if zoomTransform.k is .5', () => {
+ expect(getComputedWidth(0.5)).toBe(4 / 3 * SIZE_IDENTITY);
+ });
+
+ it('calculates style object with outline width two thirds larger if zoomTransform.k is .33', () => {
+ expect(getComputedWidth(0.33)).toBe(5 / 3 * SIZE_IDENTITY);
+ });
+
+ it('calculates style object with outline width twice as large if zoomTransform.k is .25', () => {
+ expect(getComputedWidth(0.25)).toBe(2 * SIZE_IDENTITY);
+ });
+ });
+
+ describe('setOnEdgesContainer', () => {
+ it('returns null if zoomTransform kwarg is falsy', () => {
+ expect(setOnEdgesContainer({ zoomTransform: null })).toBe(null);
+ expect(setOnEdgesContainer({ zoomTransform: undefined })).toBe(null);
+ });
+
+ it('calculates style object with opacity off of zoomTransform.k', () => {
+ expect(setOnEdgesContainer({ zoomTransform: { k: 0.0 } }).style.opacity).toBe(0.1);
+ expect(setOnEdgesContainer({ zoomTransform: { k: 0.3 } }).style.opacity).toBe(0.37);
+ expect(setOnEdgesContainer({ zoomTransform: { k: 0.5 } }).style.opacity).toBe(0.55);
+ expect(setOnEdgesContainer({ zoomTransform: { k: 0.7 } }).style.opacity).toBe(0.73);
+ expect(setOnEdgesContainer({ zoomTransform: { k: 1.0 } }).style.opacity).toBe(1);
+ });
+ });
+
+ describe('setOnNode', () => {
+ it("inherits container's outline", () => {
+ expect(setOnNode()).toEqual({
+ style: {
+ outline: 'inherit',
+ },
+ });
+ });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index f1208e8d4c..5852325951 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1551,6 +1551,15 @@ array-unique@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
+array.prototype.flat@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#812db8f02cad24d3fab65dd67eabe3b8903494a4"
+ integrity sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.10.0"
+ function-bind "^1.1.1"
+
arraybuffer.slice@~0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
@@ -4985,24 +4994,28 @@ entities@^1.1.1, entities@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
-enzyme-adapter-react-16@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.0.tgz#86c5db7c10f0be6ec25d54ca41b59f2abb397cf4"
+enzyme-adapter-react-16@^1.2.0:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.7.1.tgz#c37c4cb0fd75e88a063154a7a88096474914496a"
+ integrity sha512-OQXKgfHWyHN3sFu2nKj3mhgRcqIPIJX6aOzq5AHVFES4R9Dw/vCBZFMPyaG81g2AZ5DogVh39P3MMNUbqNLTcw==
dependencies:
- enzyme-adapter-utils "^1.1.0"
- lodash "^4.17.4"
- object.assign "^4.0.4"
+ enzyme-adapter-utils "^1.9.0"
+ function.prototype.name "^1.1.0"
+ object.assign "^4.1.0"
object.values "^1.0.4"
- prop-types "^15.5.10"
+ prop-types "^15.6.2"
+ react-is "^16.6.1"
react-test-renderer "^16.0.0-0"
-enzyme-adapter-utils@^1.1.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.2.0.tgz#7f4471ee0a70b91169ec8860d2bf0a6b551664b2"
+enzyme-adapter-utils@^1.9.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.9.1.tgz#68196fdaf2a9f51f31603cbae874618661233d72"
+ integrity sha512-LWc88BbKztLXlpRf5Ba/pSMJRaNezAwZBvis3N/IuB65ltZEh2E2obWU9B36pAbw7rORYeBUuqc79OL17ZzN1A==
dependencies:
- lodash "^4.17.4"
- object.assign "^4.0.4"
- prop-types "^15.5.10"
+ function.prototype.name "^1.1.0"
+ object.assign "^4.1.0"
+ prop-types "^15.6.2"
+ semver "^5.6.0"
enzyme-to-json@^3.3.0:
version "3.3.0"
@@ -5010,21 +5023,30 @@ enzyme-to-json@^3.3.0:
dependencies:
lodash "^4.17.4"
-enzyme@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.2.0.tgz#998bdcda0fc71b8764a0017f7cc692c943f54a7a"
+enzyme@^3.8.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.8.0.tgz#646d2d5d0798cb98fdec39afcee8a53237b47ad5"
+ integrity sha512-bfsWo5nHyZm1O1vnIsbwdfhU989jk+squU9NKvB+Puwo5j6/Wg9pN5CO0YJelm98Dao3NPjkDZk+vvgwpMwYxw==
dependencies:
+ array.prototype.flat "^1.2.1"
cheerio "^1.0.0-rc.2"
- function.prototype.name "^1.0.3"
- has "^1.0.1"
+ function.prototype.name "^1.1.0"
+ has "^1.0.3"
+ is-boolean-object "^1.0.0"
+ is-callable "^1.1.4"
+ is-number-object "^1.0.3"
+ is-string "^1.0.4"
is-subset "^0.1.1"
- lodash "^4.17.4"
+ lodash.escape "^4.0.1"
+ lodash.isequal "^4.5.0"
+ object-inspect "^1.6.0"
object-is "^1.0.1"
- object.assign "^4.0.4"
+ object.assign "^4.1.0"
object.entries "^1.0.4"
object.values "^1.0.4"
raf "^3.4.0"
rst-selector-parser "^2.2.3"
+ string.prototype.trim "^1.1.2"
errno@^0.1.1, errno@~0.1.7:
version "0.1.7"
@@ -5050,7 +5072,7 @@ error-stack-parser@1.3.6:
dependencies:
stackframe "^0.3.1"
-es-abstract@^1.11.0:
+es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.5.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
dependencies:
@@ -6184,12 +6206,13 @@ function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
-function.prototype.name@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.0.3.tgz#0099ae5572e9dd6f03c97d023fd92bcc5e639eac"
+function.prototype.name@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327"
+ integrity sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==
dependencies:
define-properties "^1.1.2"
- function-bind "^1.1.0"
+ function-bind "^1.1.1"
is-callable "^1.1.3"
functional-red-black-tree@^1.0.1:
@@ -8729,6 +8752,11 @@ lodash.debounce@^4.0.0, lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+lodash.escape@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
+ integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
+
lodash.flattendeep@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
@@ -8745,6 +8773,11 @@ lodash.isarray@^3.0.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
+lodash.isequal@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+ integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+
lodash.isplainobject@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz#9a8238ae16b200432960cd7346512d0123fbf4c5"
@@ -9904,32 +9937,29 @@ object-inspect@^1.1.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.5.0.tgz#9d876c11e40f485c79215670281b767488f9bfe3"
+object-inspect@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
+ integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==
+
object-is@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
-object-keys@^1.0.10, object-keys@^1.0.8, object-keys@^1.0.9:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
-
object-keys@^1.0.11, object-keys@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
+object-keys@^1.0.8, object-keys@^1.0.9:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
+
object-visit@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
dependencies:
isobject "^3.0.0"
-object.assign@^4.0.4:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc"
- dependencies:
- define-properties "^1.1.2"
- function-bind "^1.1.0"
- object-keys "^1.0.10"
-
object.assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
@@ -11909,6 +11939,11 @@ react-input-autosize@^2.1.0:
dependencies:
prop-types "^15.5.8"
+react-is@^16.6.1:
+ version "16.7.0"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.7.0.tgz#c1bd21c64f1f1364c6f70695ec02d69392f41bfa"
+ integrity sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g==
+
react-lazy-load@^3.0.12:
version "3.0.13"
resolved "https://registry.yarnpkg.com/react-lazy-load/-/react-lazy-load-3.0.13.tgz#3b0a92d336d43d3f0d73cbe6f35b17050b08b824"
@@ -13584,6 +13619,15 @@ string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
+string.prototype.trim@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
+ integrity sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.5.0"
+ function-bind "^1.0.2"
+
string_decoder@^1.0.0, string_decoder@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"