Skip to content

Commit

Permalink
Track trace alt views (jaegertracing#512)
Browse files Browse the repository at this point in the history
* Track trace alt views, TODO:test AltViewOptions

Signed-off-by: Everett Ross <reverett@uber.com>

* Fix year

Signed-off-by: Everett Ross <reverett@uber.com>

* Test AltViewOptions.tsx, rename toggle ddg prop

Signed-off-by: Everett Ross <reverett@uber.com>

Signed-off-by: vvvprabhakar <vvvprabhakar@gmail.com>
  • Loading branch information
everett980 committed Jan 14, 2020
1 parent 8b80b81 commit b09a5da
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('AltViewOptions', () => {
const getLabel = (btnIndex = 0) => getBtn(btnIndex).prop('children');
const props = {
traceResultsView: true,
onTraceGraphViewClicked: jest.fn(),
onDdgViewClicked: jest.fn(),
};

beforeAll(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { getUrl, getUrlState } from '../../DeepDependencies/url';
import { getConfigValue } from '../../../utils/config/get-config';

type Props = {
onTraceGraphViewClicked: () => void;
onDdgViewClicked: () => void;
traceResultsView: boolean;
};

Expand All @@ -31,9 +31,9 @@ function viewAllDep({ ctrlKey, metaKey }: React.MouseEvent<HTMLButtonElement>) {
}

export default function AltViewOptions(props: Props) {
const { onTraceGraphViewClicked, traceResultsView } = props;
const { onDdgViewClicked, traceResultsView } = props;
const toggleBtn = (
<Button className="ub-ml2" htmlType="button" onClick={onTraceGraphViewClicked}>
<Button className="ub-ml2" htmlType="button" onClick={onDdgViewClicked}>
{traceResultsView ? 'Deep Dependency Graph' : 'Trace Results'}
</Button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class UnconnectedSearchResults extends React.PureComponent<SearchResultsP
}
};

onTraceGraphViewClicked = () => {
onDdgViewClicked = () => {
const { location, history } = this.props;
const urlState = queryString.parse(location.search);
const view = urlState.view && urlState.view === 'ddg' ? 'traces' : 'ddg';
Expand Down Expand Up @@ -177,10 +177,7 @@ export class UnconnectedSearchResults extends React.PureComponent<SearchResultsP
{traces.length} Trace{traces.length > 1 && 's'}
</h2>
{traceResultsView && <SelectSort />}
<AltViewOptions
traceResultsView={traceResultsView}
onTraceGraphViewClicked={this.onTraceGraphViewClicked}
/>
<AltViewOptions traceResultsView={traceResultsView} onDdgViewClicked={this.onDdgViewClicked} />
{showStandaloneLink && (
<Link
className="u-tx-inherit ub-nowrap ub-ml3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe('<SearchResults>', () => {
const push = jest.fn();
wrapper.setProps({ history: { push }, location: { search: otherSearch } });

const toggle = wrapper.find(AltViewOptions).prop('onTraceGraphViewClicked');
const toggle = wrapper.find(AltViewOptions).prop('onDdgViewClicked');
toggle();
expect(push).toHaveBeenLastCalledWith(getUrl({ [otherParam]: otherValue, [searchParam]: viewDdg }));
expect(trackAltViewSpy).toHaveBeenLastCalledWith(viewDdg);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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 { Button, Dropdown } from 'antd';
import { Link } from 'react-router-dom';

import AltViewOptions from './AltViewOptions';
import * as track from './TracePageHeader.track';

describe('AltViewOptions', () => {
let trackGanttView;
let trackGraphView;
let trackJsonView;
let trackRawJsonView;

let wrapper;
const getLink = text => {
const menu = shallow(wrapper.find(Dropdown).prop('overlay'));
const links = menu.find(Link);
for (let i = 0; i < links.length; i++) {
const link = links.at(i);
if (link.children().text() === text) return link;
}
const link = menu.find('a');
if (link.children().text() === text) return link;
throw new Error(`Could not find "${text}"`);
};
const props = {
traceGraphView: true,
traceID: 'test trace ID',
onTraceGraphViewClicked: jest.fn(),
};

beforeAll(() => {
trackGanttView = jest.spyOn(track, 'trackGanttView');
trackGraphView = jest.spyOn(track, 'trackGraphView');
trackJsonView = jest.spyOn(track, 'trackJsonView');
trackRawJsonView = jest.spyOn(track, 'trackRawJsonView');
});

beforeEach(() => {
jest.clearAllMocks();
wrapper = shallow(<AltViewOptions {...props} />);
});

it('renders correctly', () => {
expect(wrapper).toMatchSnapshot();
});

it('tracks viewing JSONs', () => {
expect(trackJsonView).not.toHaveBeenCalled();
getLink('Trace JSON').simulate('click');
expect(trackJsonView).toHaveBeenCalledTimes(1);

expect(trackRawJsonView).not.toHaveBeenCalled();
getLink('Trace JSON (unadjusted)').simulate('click');
expect(trackRawJsonView).toHaveBeenCalledTimes(1);

expect(trackJsonView).toHaveBeenCalledTimes(1);
expect(trackGanttView).not.toHaveBeenCalled();
expect(trackGraphView).not.toHaveBeenCalled();
});

it('toggles and tracks toggle', () => {
expect(trackGanttView).not.toHaveBeenCalled();
expect(props.onTraceGraphViewClicked).not.toHaveBeenCalled();
getLink('Trace Timeline').simulate('click');
expect(trackGanttView).toHaveBeenCalledTimes(1);
expect(props.onTraceGraphViewClicked).toHaveBeenCalledTimes(1);

wrapper.setProps({ traceGraphView: false });
expect(trackGraphView).not.toHaveBeenCalled();
getLink('Trace Graph').simulate('click');
expect(trackGraphView).toHaveBeenCalledTimes(1);
expect(props.onTraceGraphViewClicked).toHaveBeenCalledTimes(2);

wrapper.setProps({ traceGraphView: true });
expect(trackGanttView).toHaveBeenCalledTimes(1);
wrapper.find(Button).simulate('click');
expect(trackGanttView).toHaveBeenCalledTimes(2);
expect(props.onTraceGraphViewClicked).toHaveBeenCalledTimes(3);

wrapper.setProps({ traceGraphView: false });
expect(trackGraphView).toHaveBeenCalledTimes(1);
wrapper.find(Button).simulate('click');
expect(trackGraphView).toHaveBeenCalledTimes(2);
expect(props.onTraceGraphViewClicked).toHaveBeenCalledTimes(4);

expect(trackGanttView).toHaveBeenCalledTimes(2);
expect(trackJsonView).not.toHaveBeenCalled();
expect(trackRawJsonView).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import * as React from 'react';
import { Button, Dropdown, Icon, Menu } from 'antd';
import { Link } from 'react-router-dom';

import { trackAltViewOpen } from './TracePageHeader.track';
import { trackGanttView, trackGraphView, trackJsonView, trackRawJsonView } from './TracePageHeader.track';
import prefixUrl from '../../../utils/prefix-url';

type Props = {
Expand All @@ -27,10 +27,15 @@ type Props = {

export default function AltViewOptions(props: Props) {
const { onTraceGraphViewClicked, traceGraphView, traceID } = props;
const handleToggleView = () => {
if (traceGraphView) trackGanttView();
else trackGraphView();
onTraceGraphViewClicked();
};
const menu = (
<Menu>
<Menu.Item>
<a onClick={onTraceGraphViewClicked} role="button">
<a onClick={handleToggleView} role="button">
{traceGraphView ? 'Trace Timeline' : 'Trace Graph'}
</a>
</Menu.Item>
Expand All @@ -39,7 +44,7 @@ export default function AltViewOptions(props: Props) {
to={prefixUrl(`/api/traces/${traceID}?prettyPrint=true`)}
rel="noopener noreferrer"
target="_blank"
onClick={trackAltViewOpen}
onClick={trackJsonView}
>
Trace JSON
</Link>
Expand All @@ -49,7 +54,7 @@ export default function AltViewOptions(props: Props) {
to={prefixUrl(`/api/traces/${traceID}?raw=true&prettyPrint=true`)}
rel="noopener noreferrer"
target="_blank"
onClick={trackAltViewOpen}
onClick={trackRawJsonView}
>
Trace JSON (unadjusted)
</Link>
Expand All @@ -58,8 +63,8 @@ export default function AltViewOptions(props: Props) {
);
return (
<Dropdown overlay={menu}>
<Button className="ub-mr2" htmlType="button" onClick={onTraceGraphViewClicked}>
{traceGraphView ? 'Trace Graph' : 'Trace Timeline'} <Icon type="down" />
<Button className="ub-mr2" htmlType="button" onClick={handleToggleView}>
Alternate Views <Icon type="down" />
</Button>
</Dropdown>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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.

/* eslint-disable import/first */

jest.mock('../../../utils/tracking');

import * as track from './TracePageHeader.track'; /* {
CATEGORY_ALT_VIEW,
CATEGORY_SLIM_HEADER,
ACTION_GANTT,
ACTION_GRAPH,
ACTION_JSON,
ACTION_RAW_JSON,
} from './index.track'; */
import { trackEvent } from '../../../utils/tracking';
import { OPEN, CLOSE } from '../../../utils/tracking/common';

describe('TracePageHeader.track', () => {
beforeEach(trackEvent.mockClear);

const cases = [
{
action: track.ACTION_GANTT,
category: track.CATEGORY_ALT_VIEW,
msg: 'tracks a GA event for viewing gantt chart',
fn: 'trackGanttView',
},
{
action: track.ACTION_GRAPH,
category: track.CATEGORY_ALT_VIEW,
msg: 'tracks a GA event for viewing trace graph',
fn: 'trackGraphView',
},
{
action: track.ACTION_JSON,
category: track.CATEGORY_ALT_VIEW,
msg: 'tracks a GA event for viewing trace JSON',
fn: 'trackJsonView',
},
{
action: track.ACTION_RAW_JSON,
category: track.CATEGORY_ALT_VIEW,
msg: 'tracks a GA event for viewing trace JSON (raw)',
fn: 'trackRawJsonView',
},
{
action: OPEN,
arg: false,
category: track.CATEGORY_SLIM_HEADER,
msg: 'tracks a GA event for opening slim header',
fn: 'trackSlimHeaderToggle',
},
{
action: CLOSE,
arg: true,
category: track.CATEGORY_SLIM_HEADER,
msg: 'tracks a GA event for closing slim header',
fn: 'trackSlimHeaderToggle',
},
];

cases.forEach(({ action, arg, msg, fn, category }) => {
it(msg, () => {
track[fn](arg);
expect(trackEvent.mock.calls.length).toBe(1);
expect(trackEvent.mock.calls[0]).toEqual([category, action]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { getToggleValue, OPEN } from '../../../utils/tracking/common';
import { getToggleValue } from '../../../utils/tracking/common';
import { trackEvent } from '../../../utils/tracking';

const CATEGORY_ALT_VIEW = 'jaeger/ux/trace/alt-view';
const CATEGORY_SLIM_HEADER = 'jaeger/ux/trace/slim-header';
// export for tests
export const CATEGORY_ALT_VIEW = 'jaeger/ux/trace/alt-view';
export const CATEGORY_SLIM_HEADER = 'jaeger/ux/trace/slim-header';

// export for tests
export const ACTION_GANTT = 'gantt';
export const ACTION_GRAPH = 'graph';
export const ACTION_JSON = 'json';
export const ACTION_RAW_JSON = 'rawJson';

// use a closure instead of bind to prevent forwarding any arguments to trackEvent()
export const trackAltViewOpen = () => trackEvent(CATEGORY_ALT_VIEW, OPEN);
export const trackGanttView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_GANTT);
export const trackGraphView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_GRAPH);
export const trackJsonView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_JSON);
export const trackRawJsonView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_RAW_JSON);

export const trackSlimHeaderToggle = (isOpen: boolean) =>
trackEvent(CATEGORY_SLIM_HEADER, getToggleValue(isOpen));
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`AltViewOptions renders correctly 1`] = `
<Dropdown
mouseEnterDelay={0.15}
mouseLeaveDelay={0.1}
overlay={
<Menu
className=""
focusable={false}
prefixCls="ant-menu"
theme="light"
>
<MenuItem>
<a
onClick={[Function]}
role="button"
>
Trace Timeline
</a>
</MenuItem>
<MenuItem>
<Link
onClick={[MockFunction]}
rel="noopener noreferrer"
replace={false}
target="_blank"
to="/api/traces/test trace ID?prettyPrint=true"
>
Trace JSON
</Link>
</MenuItem>
<MenuItem>
<Link
onClick={[MockFunction]}
rel="noopener noreferrer"
replace={false}
target="_blank"
to="/api/traces/test trace ID?raw=true&prettyPrint=true"
>
Trace JSON (unadjusted)
</Link>
</MenuItem>
</Menu>
}
placement="bottomLeft"
prefixCls="ant-dropdown"
>
<Button
block={false}
className="ub-mr2"
ghost={false}
htmlType="button"
loading={false}
onClick={[Function]}
prefixCls="ant-btn"
>
Alternate Views
<Icon
type="down"
/>
</Button>
</Dropdown>
`;

0 comments on commit b09a5da

Please sign in to comment.