diff --git a/.gitignore b/.gitignore index ed9048def1..9818f4233b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ coverage .DS_Store npm-debug.log .vscode +.idea yarn-error.log + +lerna-debug\.log diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.js index 3b4d385aed..f4721c891c 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.js @@ -26,10 +26,12 @@ import { Field, reduxForm, formValueSelector } from 'redux-form'; import store from 'store'; import * as markers from './SearchForm.markers'; +import { trackFormInput } from './SearchForm.track'; import VirtSelect from '../common/VirtSelect'; import * as jaegerApiActions from '../../actions/jaeger-api'; import { formatDate, formatTime } from '../../utils/date'; import reduxFormFieldAdapter from '../../utils/redux-form-field-adapter'; +import { DEFAULT_OPERATION, DEFAULT_LIMIT, DEFAULT_LOOKBACK } from '../../constants/search-form'; import './SearchForm.css'; @@ -134,9 +136,11 @@ export function submitForm(fields, searchTraces) { end = times.end; } + trackFormInput(resultsLimit, operation, tags, minDuration, maxDuration, lookback); + searchTraces({ service, - operation: operation !== 'all' ? operation : undefined, + operation: operation !== DEFAULT_OPERATION ? operation : undefined, limit: resultsLimit, lookback, start, @@ -459,13 +463,13 @@ export function mapStateToProps(state) { destroyOnUnmount: false, initialValues: { service: service || lastSearchService || '-', - resultsLimit: limit || 20, - lookback: lookback || '1h', + resultsLimit: limit || DEFAULT_LIMIT, + lookback: lookback || DEFAULT_LOOKBACK, startDate: queryStartDate || today, startDateTime: queryStartDateTime || '00:00', endDate: queryEndDate || today, endDateTime: queryEndDateTime || currentTime, - operation: operation || lastSearchOperation || 'all', + operation: operation || lastSearchOperation || DEFAULT_OPERATION, tags, minDuration: minDuration || null, maxDuration: maxDuration || null, diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.track.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.track.js new file mode 100644 index 0000000000..89702edac7 --- /dev/null +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.track.js @@ -0,0 +1,57 @@ +// @flow + +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import type { Store } from 'redux'; + +import * as constants from '../../constants/search-form'; +import { trackEvent } from '../../utils/tracking'; + +export const ACTION_SET = 'set'; +export const ACTION_CLEAR = 'clear'; +export const ACTION_DEFAULT = 'default'; + +export const CATEGORY_SORTBY = `jaeger/ux/search/results/sortby`; +export const FORM_CATEGORY_BASE = 'jaeger/ux/search/form'; +export const CATEGORY_OPERATION = `${FORM_CATEGORY_BASE}/operation`; +export const CATEGORY_LOOKBACK = `${FORM_CATEGORY_BASE}/lookback`; +export const CATEGORY_TAGS = `${FORM_CATEGORY_BASE}/tags`; +export const CATEGORY_MIN_DURATION = `${FORM_CATEGORY_BASE}/min_duration`; +export const CATEGORY_MAX_DURATION = `${FORM_CATEGORY_BASE}/max_duration`; +export const CATEGORY_LIMIT = `${FORM_CATEGORY_BASE}/limit`; + +export function trackFormInput( + resultsLimit: number, + operation: string, + tags: any, + minDuration: number, + maxDuration: number, + lookback: string +) { + trackEvent(CATEGORY_OPERATION, operation === constants.DEFAULT_OPERATION ? ACTION_DEFAULT : ACTION_SET); + trackEvent(CATEGORY_LIMIT, resultsLimit === constants.DEFAULT_LIMIT ? ACTION_DEFAULT : ACTION_SET); + trackEvent(CATEGORY_MAX_DURATION, maxDuration ? ACTION_SET : ACTION_CLEAR); + trackEvent(CATEGORY_MIN_DURATION, minDuration ? ACTION_SET : ACTION_CLEAR); + trackEvent(CATEGORY_TAGS, tags ? ACTION_SET : ACTION_CLEAR); + trackEvent(CATEGORY_LOOKBACK, lookback); +} + +export const middlewareHooks = { + [constants.FORM_CHANGE_ACTION_TYPE]: (store: Store, action: any) => { + if (action.meta.form === 'sortBy') { + trackEvent(CATEGORY_SORTBY, action.payload); + } + }, +}; diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.track.test.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.track.test.js new file mode 100644 index 0000000000..f6d29fa75d --- /dev/null +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchForm.track.test.js @@ -0,0 +1,56 @@ +// 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. + +/* eslint-disable import/first */ +jest.mock('../../utils/tracking'); + +import { + middlewareHooks, + trackFormInput, + CATEGORY_LIMIT, + CATEGORY_LOOKBACK, + CATEGORY_MAX_DURATION, + CATEGORY_MIN_DURATION, + CATEGORY_OPERATION, + CATEGORY_SORTBY, + CATEGORY_TAGS, +} from './SearchForm.track'; +import { FORM_CHANGE_ACTION_TYPE } from '../../constants/search-form'; +import { trackEvent } from '../../utils/tracking'; + +describe('GA tracking', () => { + it('tracks changing sort criteria', () => { + const action = { meta: { form: 'sortBy' }, payload: 'MOST_RECENT' }; + middlewareHooks[FORM_CHANGE_ACTION_TYPE]({}, action); + expect(trackEvent.mock.calls.length).toBe(1); + expect(trackEvent.mock.calls[0]).toEqual([CATEGORY_SORTBY, expect.any(String)]); + }); + + it('sends form input to GA', () => { + trackEvent.mockClear(); + trackFormInput(0, '', {}, 0, 0, ''); + expect(trackEvent.mock.calls.length).toBe(6); + const categoriesTracked = trackEvent.mock.calls.map(call => call[0]).sort(); + expect(categoriesTracked).toEqual( + [ + CATEGORY_OPERATION, + CATEGORY_LIMIT, + CATEGORY_TAGS, + CATEGORY_MAX_DURATION, + CATEGORY_MIN_DURATION, + CATEGORY_LOOKBACK, + ].sort() + ); + }); +}); diff --git a/packages/jaeger-ui/src/constants/search-form.js b/packages/jaeger-ui/src/constants/search-form.js new file mode 100644 index 0000000000..e716b295af --- /dev/null +++ b/packages/jaeger-ui/src/constants/search-form.js @@ -0,0 +1,19 @@ +// 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. + +export const DEFAULT_OPERATION = 'all'; +export const DEFAULT_LOOKBACK = '1h'; +export const DEFAULT_LIMIT = 20; + +export const FORM_CHANGE_ACTION_TYPE = '@@redux-form/CHANGE'; diff --git a/packages/jaeger-ui/src/middlewares/track.js b/packages/jaeger-ui/src/middlewares/track.js index 5d3ce67f46..9e4ccb04e8 100644 --- a/packages/jaeger-ui/src/middlewares/track.js +++ b/packages/jaeger-ui/src/middlewares/track.js @@ -14,9 +14,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { middlewareHooks } from '../components/TracePage/TraceTimelineViewer/duck.track'; +import { middlewareHooks as searchHooks } from '../components/SearchTracePage/SearchForm.track'; +import { middlewareHooks as timelineHooks } from '../components/TracePage/TraceTimelineViewer/duck.track'; import { isGaEnabled } from '../utils/tracking'; +const middlewareHooks = { ...timelineHooks, ...searchHooks }; + function trackingMiddleware(store: { getState: () => any }) { return function inner(next: any => void) { return function core(action: any) {