Commit 17ac753e authored by Nick Kipling's avatar Nick Kipling

Add UI tracking for package install instructions

Update code_installation to support tracking
Add tracking to npm and maven tabs
Add tracking for npm and maven commands
Add tests to cover new tracking
Update snapshot
Added changelog entry

Removed setting of category to rely on default
Removed category const
Updated tests
Removed empty object in track method
Removed manual event binding
Removed unncessary ref
parent 3eb67d94
---
title: Added event tracking to the package details installation components
merge_request: 20967
author:
type: changed
...@@ -17,7 +17,7 @@ import { numberToHumanSize } from '~/lib/utils/number_utils'; ...@@ -17,7 +17,7 @@ import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import PackageType from '../constants'; import { PackageType } from '../constants';
export default { export default {
name: 'PackagesApp', name: 'PackagesApp',
......
<script> <script>
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Tracking from '~/tracking';
import { TrackingLabels } from '../constants';
export default { export default {
name: 'CodeInstruction', name: 'CodeInstruction',
components: { components: {
ClipboardButton, ClipboardButton,
}, },
mixins: [
Tracking.mixin({
label: TrackingLabels.CODE_INSTRUCTION,
}),
],
props: { props: {
instruction: { instruction: {
type: String, type: String,
...@@ -20,19 +27,37 @@ export default { ...@@ -20,19 +27,37 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
trackingAction: {
type: String,
required: false,
default: '',
},
},
methods: {
trackCopy() {
if (this.trackingAction) {
this.track(this.trackingAction);
}
},
}, },
}; };
</script> </script>
<template> <template>
<div v-if="!multiline" class="input-group append-bottom-10"> <div v-if="!multiline" class="input-group append-bottom-10">
<input :value="instruction" type="text" class="form-control monospace" readonly /> <input
<span class="input-group-append"> :value="instruction"
type="text"
class="form-control monospace js-instruction-input"
readonly
@copy="trackCopy"
/>
<span class="input-group-append js-instruction-button" @click="trackCopy">
<clipboard-button :text="instruction" :title="copyText" class="input-group-text" /> <clipboard-button :text="instruction" :title="copyText" class="input-group-text" />
</span> </span>
</div> </div>
<div v-else> <div v-else>
<pre>{{ instruction }}</pre> <pre class="js-instruction-pre" @copy="trackCopy">{{ instruction }}</pre>
</div> </div>
</template> </template>
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
import { GlTab, GlTabs } from '@gitlab/ui'; import { GlTab, GlTabs } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import CodeInstruction from './code_instruction.vue'; import CodeInstruction from './code_instruction.vue';
import Tracking from '~/tracking';
import { TrackingActions, TrackingLabels } from '../constants';
export default { export default {
name: 'MavenInstallation', name: 'MavenInstallation',
...@@ -10,6 +12,11 @@ export default { ...@@ -10,6 +12,11 @@ export default {
GlTab, GlTab,
GlTabs, GlTabs,
}, },
mixins: [
Tracking.mixin({
label: TrackingLabels.MAVEN_INSTALLATION,
}),
],
props: { props: {
heading: { heading: {
type: String, type: String,
...@@ -110,13 +117,20 @@ export default { ...@@ -110,13 +117,20 @@ export default {
false, false,
), ),
}, },
methods: {
onTabChanged(tabIndex) {
const action = tabIndex === 0 ? TrackingActions.INSTALLATION : TrackingActions.REGISTRY_SETUP;
this.track(action);
},
},
trackingActions: { ...TrackingActions },
}; };
</script> </script>
<template> <template>
<div class="append-bottom-default"> <div class="append-bottom-default">
<gl-tabs> <gl-tabs @input="onTabChanged">
<gl-tab :title="s__('PackageRegistry|Installation')"> <gl-tab :title="s__('PackageRegistry|Installation')" title-item-class="js-installation-tab">
<div class="prepend-left-default append-right-default"> <div class="prepend-left-default append-right-default">
<p class="prepend-top-8 font-weight-bold">{{ s__('PackageRegistry|Maven XML') }}</p> <p class="prepend-top-8 font-weight-bold">{{ s__('PackageRegistry|Maven XML') }}</p>
<p v-html="$options.i18n.xmlText"></p> <p v-html="$options.i18n.xmlText"></p>
...@@ -125,6 +139,7 @@ export default { ...@@ -125,6 +139,7 @@ export default {
:copy-text="s__('PackageRegistry|Copy Maven XML')" :copy-text="s__('PackageRegistry|Copy Maven XML')"
class="js-maven-xml" class="js-maven-xml"
multiline multiline
:tracking-action="$options.trackingActions.COPY_MAVEN_XML"
/> />
<p class="prepend-top-default font-weight-bold"> <p class="prepend-top-default font-weight-bold">
...@@ -134,10 +149,11 @@ export default { ...@@ -134,10 +149,11 @@ export default {
:instruction="mavenCommand" :instruction="mavenCommand"
:copy-text="s__('PackageRegistry|Copy Maven command')" :copy-text="s__('PackageRegistry|Copy Maven command')"
class="js-maven-command" class="js-maven-command"
:tracking-action="$options.trackingActions.COPY_MAVEN_COMMAND"
/> />
</div> </div>
</gl-tab> </gl-tab>
<gl-tab :title="s__('PackageRegistry|Registry Setup')"> <gl-tab :title="s__('PackageRegistry|Registry Setup')" title-item-class="js-setup-tab">
<div class="prepend-left-default append-right-default"> <div class="prepend-left-default append-right-default">
<p v-html="$options.i18n.setupText"></p> <p v-html="$options.i18n.setupText"></p>
<code-instruction <code-instruction
...@@ -145,6 +161,7 @@ export default { ...@@ -145,6 +161,7 @@ export default {
:copy-text="s__('PackageRegistry|Copy Maven registry XML')" :copy-text="s__('PackageRegistry|Copy Maven registry XML')"
class="js-maven-setup-xml" class="js-maven-setup-xml"
multiline multiline
:tracking-action="$options.trackingActions.COPY_MAVEN_SETUP"
/> />
<p v-html="helpText"></p> <p v-html="helpText"></p>
</div> </div>
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
import { GlTab, GlTabs } from '@gitlab/ui'; import { GlTab, GlTabs } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import CodeInstruction from './code_instruction.vue'; import CodeInstruction from './code_instruction.vue';
import Tracking from '~/tracking';
import { TrackingActions, TrackingLabels } from '../constants';
export default { export default {
name: 'NpmInstallation', name: 'NpmInstallation',
...@@ -10,6 +12,11 @@ export default { ...@@ -10,6 +12,11 @@ export default {
GlTab, GlTab,
GlTabs, GlTabs,
}, },
mixins: [
Tracking.mixin({
label: TrackingLabels.NPM_INSTALLATION,
}),
],
props: { props: {
name: { name: {
type: String, type: String,
...@@ -63,19 +70,27 @@ export default { ...@@ -63,19 +70,27 @@ export default {
); );
}, },
}, },
methods: {
onTabChanged(tabIndex) {
const action = tabIndex === 0 ? TrackingActions.INSTALLATION : TrackingActions.REGISTRY_SETUP;
this.track(action);
},
},
trackingActions: { ...TrackingActions },
}; };
</script> </script>
<template> <template>
<div class="append-bottom-default"> <div class="append-bottom-default">
<gl-tabs> <gl-tabs @input="onTabChanged">
<gl-tab :title="s__('PackageRegistry|Installation')"> <gl-tab :title="s__('PackageRegistry|Installation')" title-item-class="js-installation-tab">
<div class="prepend-left-default append-right-default"> <div class="prepend-left-default append-right-default">
<p class="prepend-top-8 font-weight-bold">{{ s__('PackageRegistry|npm') }}</p> <p class="prepend-top-8 font-weight-bold">{{ s__('PackageRegistry|npm') }}</p>
<code-instruction <code-instruction
:instruction="npmCommand" :instruction="npmCommand"
:copy-text="s__('PackageRegistry|Copy npm command')" :copy-text="s__('PackageRegistry|Copy npm command')"
class="js-npm-install" class="js-npm-install"
:tracking-action="$options.trackingActions.COPY_NPM_INSTALL_COMMAND"
/> />
<p class="prepend-top-default font-weight-bold">{{ s__('PackageRegistry|yarn') }}</p> <p class="prepend-top-default font-weight-bold">{{ s__('PackageRegistry|yarn') }}</p>
...@@ -83,16 +98,18 @@ export default { ...@@ -83,16 +98,18 @@ export default {
:instruction="yarnCommand" :instruction="yarnCommand"
:copy-text="s__('PackageRegistry|Copy yarn command')" :copy-text="s__('PackageRegistry|Copy yarn command')"
class="js-yarn-install" class="js-yarn-install"
:tracking-action="$options.trackingActions.COPY_YARN_INSTALL_COMMAND"
/> />
</div> </div>
</gl-tab> </gl-tab>
<gl-tab :title="s__('PackageRegistry|Registry Setup')"> <gl-tab :title="s__('PackageRegistry|Registry Setup')" title-item-class="js-setup-tab">
<div class="prepend-left-default append-right-default"> <div class="prepend-left-default append-right-default">
<p class="prepend-top-8 font-weight-bold">{{ s__('PackageRegistry|npm') }}</p> <p class="prepend-top-8 font-weight-bold">{{ s__('PackageRegistry|npm') }}</p>
<code-instruction <code-instruction
:instruction="npmSetupCommand" :instruction="npmSetupCommand"
:copy-text="s__('PackageRegistry|Copy npm setup command')" :copy-text="s__('PackageRegistry|Copy npm setup command')"
class="js-npm-setup" class="js-npm-setup"
:tracking-action="$options.trackingActions.COPY_NPM_SETUP_COMMAND"
/> />
<p class="prepend-top-default font-weight-bold">{{ s__('PackageRegistry|yarn') }}</p> <p class="prepend-top-default font-weight-bold">{{ s__('PackageRegistry|yarn') }}</p>
...@@ -100,6 +117,7 @@ export default { ...@@ -100,6 +117,7 @@ export default {
:instruction="yarnSetupCommand" :instruction="yarnSetupCommand"
:copy-text="s__('PackageRegistry|Copy yarn setup command')" :copy-text="s__('PackageRegistry|Copy yarn setup command')"
class="js-yarn-setup" class="js-yarn-setup"
:tracking-action="$options.trackingActions.COPY_YARN_SETUP_COMMAND"
/> />
<p v-html="helpText"></p> <p v-html="helpText"></p>
......
const PackageType = { export const PackageType = {
MAVEN: 'maven', MAVEN: 'maven',
NPM: 'npm', NPM: 'npm',
}; };
export default PackageType; export const TrackingLabels = {
CODE_INSTRUCTION: 'code_instruction',
MAVEN_INSTALLATION: 'maven_installation',
NPM_INSTALLATION: 'npm_installation',
};
export const TrackingActions = {
INSTALLATION: 'installation',
REGISTRY_SETUP: 'registry_setup',
COPY_MAVEN_XML: 'copy_maven_xml',
COPY_MAVEN_COMMAND: 'copy_maven_command',
COPY_MAVEN_SETUP: 'copy_maven_setup_xml',
COPY_NPM_INSTALL_COMMAND: 'copy_npm_install_command',
COPY_NPM_SETUP_COMMAND: 'copy_npm_setup_command',
COPY_YARN_INSTALL_COMMAND: 'copy_yarn_install_command',
COPY_YARN_SETUP_COMMAND: 'copy_yarn_setup_command',
};
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
exports[`Package code instruction multiline to match the snapshot 1`] = ` exports[`Package code instruction multiline to match the snapshot 1`] = `
<div> <div>
<pre> <pre
class="js-instruction-pre"
>
this is some this is some
multiline text multiline text
</pre> </pre>
...@@ -14,13 +16,13 @@ exports[`Package code instruction single line to match the default snapshot 1`] ...@@ -14,13 +16,13 @@ exports[`Package code instruction single line to match the default snapshot 1`]
class="input-group append-bottom-10" class="input-group append-bottom-10"
> >
<input <input
class="form-control monospace" class="form-control monospace js-instruction-input"
readonly="readonly" readonly="readonly"
type="text" type="text"
/> />
<span <span
class="input-group-append" class="input-group-append js-instruction-button"
> >
<button <button
class="btn input-group-text btn-secondary btn-default" class="btn input-group-text btn-secondary btn-default"
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import CodeInstruction from 'ee/packages/details/components/code_instruction.vue'; import CodeInstruction from 'ee/packages/details/components/code_instruction.vue';
import { TrackingLabels } from 'ee/packages/details/constants';
import Tracking from '~/tracking';
describe('Package code instruction', () => { describe('Package code instruction', () => {
let wrapper; let wrapper;
...@@ -18,6 +20,10 @@ describe('Package code instruction', () => { ...@@ -18,6 +20,10 @@ describe('Package code instruction', () => {
}); });
} }
const findInstructionInput = () => wrapper.find('.js-instruction-input');
const findInstructionPre = () => wrapper.find('.js-instruction-pre');
const findInstructionButton = () => wrapper.find('.js-instruction-button');
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -43,4 +49,62 @@ describe('Package code instruction', () => { ...@@ -43,4 +49,62 @@ describe('Package code instruction', () => {
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
}); });
describe('tracking', () => {
let eventSpy;
const trackingAction = 'test_action';
const label = TrackingLabels.CODE_INSTRUCTION;
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
});
it('should not track when no trackingAction is provided', () => {
createComponent();
findInstructionButton().trigger('click');
expect(eventSpy).toHaveBeenCalledTimes(0);
});
describe('when trackingAction is provided for single line', () => {
beforeEach(() =>
createComponent({
trackingAction,
}),
);
it('should track when copying from the input', () => {
findInstructionInput().trigger('copy');
expect(eventSpy).toHaveBeenCalledWith(undefined, trackingAction, {
label,
});
});
it('should track when the copy button is pressed', () => {
findInstructionButton().trigger('click');
expect(eventSpy).toHaveBeenCalledWith(undefined, trackingAction, {
label,
});
});
});
describe('when trackingAction is provided for multiline', () => {
beforeEach(() =>
createComponent({
trackingAction,
multiline: true,
}),
);
it('should track when copying from the multiline pre element', () => {
findInstructionPre().trigger('copy');
expect(eventSpy).toHaveBeenCalledWith(undefined, trackingAction, {
label,
});
});
});
});
}); });
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import MavenInstallation from 'ee/packages/details/components/maven_installation.vue'; import MavenInstallation from 'ee/packages/details/components/maven_installation.vue';
import { TrackingActions, TrackingLabels } from 'ee/packages/details/constants';
import { import {
generateMavenCommand, generateMavenCommand,
generateXmlCodeBlock, generateXmlCodeBlock,
...@@ -7,6 +8,7 @@ import { ...@@ -7,6 +8,7 @@ import {
mavenMetadata, mavenMetadata,
registryUrl, registryUrl,
} from '../mock_data'; } from '../mock_data';
import Tracking from '~/tracking';
describe('MavenInstallation', () => { describe('MavenInstallation', () => {
let wrapper; let wrapper;
...@@ -21,6 +23,8 @@ describe('MavenInstallation', () => { ...@@ -21,6 +23,8 @@ describe('MavenInstallation', () => {
const xmlCodeBlock = generateXmlCodeBlock(mavenMetadata); const xmlCodeBlock = generateXmlCodeBlock(mavenMetadata);
const mavenSetupXml = generateMavenSetupXml(); const mavenSetupXml = generateMavenSetupXml();
const installationTab = () => wrapper.find('.js-installation-tab > a');
const setupTab = () => wrapper.find('.js-setup-tab > a');
const xmlCode = () => wrapper.find('.js-maven-xml > pre'); const xmlCode = () => wrapper.find('.js-maven-xml > pre');
const mavenCommand = () => wrapper.find('.js-maven-command > input'); const mavenCommand = () => wrapper.find('.js-maven-command > input');
const xmlSetup = () => wrapper.find('.js-maven-setup-xml > pre'); const xmlSetup = () => wrapper.find('.js-maven-setup-xml > pre');
...@@ -79,4 +83,30 @@ describe('MavenInstallation', () => { ...@@ -79,4 +83,30 @@ describe('MavenInstallation', () => {
expect(xmlSetup().text()).toBe(mavenSetupXml); expect(xmlSetup().text()).toBe(mavenSetupXml);
}); });
}); });
describe('tab change tracking', () => {
let eventSpy;
const label = TrackingLabels.MAVEN_INSTALLATION;
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
});
it('should track when the setup tab is clicked', () => {
setupTab().trigger('click');
expect(eventSpy).toHaveBeenCalledWith(undefined, TrackingActions.REGISTRY_SETUP, {
label,
});
});
it('should track when the installation tab is clicked', () => {
setupTab().trigger('click');
installationTab().trigger('click');
expect(eventSpy).toHaveBeenCalledWith(undefined, TrackingActions.INSTALLATION, {
label,
});
});
});
}); });
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import NpmInstallation from 'ee/packages/details/components/npm_installation.vue'; import NpmInstallation from 'ee/packages/details/components/npm_installation.vue';
import { TrackingActions, TrackingLabels } from 'ee/packages/details/constants';
import Tracking from '~/tracking';
describe('NpmInstallation', () => { describe('NpmInstallation', () => {
let wrapper; let wrapper;
...@@ -20,6 +22,8 @@ describe('NpmInstallation', () => { ...@@ -20,6 +22,8 @@ describe('NpmInstallation', () => {
const yarnInstall = `yarn add ${packageScopeName}`; const yarnInstall = `yarn add ${packageScopeName}`;
const yarnSetup = `echo \\"${packageScope}:registry\\" \\"${registryUrl}\\" >> .yarnrc`; const yarnSetup = `echo \\"${packageScope}:registry\\" \\"${registryUrl}\\" >> .yarnrc`;
const installationTab = () => wrapper.find('.js-installation-tab > a');
const setupTab = () => wrapper.find('.js-setup-tab > a');
const installCommand = type => wrapper.find(`.js-${type}-install > input`); const installCommand = type => wrapper.find(`.js-${type}-install > input`);
const setupCommand = type => wrapper.find(`.js-${type}-setup > input`); const setupCommand = type => wrapper.find(`.js-${type}-setup > input`);
...@@ -73,4 +77,31 @@ describe('NpmInstallation', () => { ...@@ -73,4 +77,31 @@ describe('NpmInstallation', () => {
expect(setupCommand('yarn').element.value).toBe(yarnSetup); expect(setupCommand('yarn').element.value).toBe(yarnSetup);
}); });
}); });
describe('tab change tracking', () => {
let eventSpy;
const label = TrackingLabels.NPM_INSTALLATION;
beforeEach(() => {
eventSpy = jest.spyOn(Tracking, 'event');
createComponent();
});
it('should track when the setup tab is clicked', () => {
setupTab().trigger('click');
expect(eventSpy).toHaveBeenCalledWith(undefined, TrackingActions.REGISTRY_SETUP, {
label,
});
});
it('should track when the installation tab is clicked', () => {
setupTab().trigger('click');
installationTab().trigger('click');
expect(eventSpy).toHaveBeenCalledWith(undefined, TrackingActions.INSTALLATION, {
label,
});
});
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment