Commit 6e1ae968 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch...

Merge branch '37772-add-ui-event-tracking-for-package-details-installation-instructions' into 'master'

Add event tracking to package details installation instructions

Closes #37772

See merge request gitlab-org/gitlab!20967
parents 5d88a5eb c06aab39
---
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,9 @@ ...@@ -2,6 +2,9 @@
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';
import trackInstallationTabChange from '../utils';
export default { export default {
name: 'MavenInstallation', name: 'MavenInstallation',
...@@ -10,6 +13,12 @@ export default { ...@@ -10,6 +13,12 @@ export default {
GlTab, GlTab,
GlTabs, GlTabs,
}, },
mixins: [
Tracking.mixin({
label: TrackingLabels.MAVEN_INSTALLATION,
}),
trackInstallationTabChange,
],
props: { props: {
heading: { heading: {
type: String, type: String,
...@@ -110,13 +119,14 @@ export default { ...@@ -110,13 +119,14 @@ export default {
false, false,
), ),
}, },
trackingActions: { ...TrackingActions },
}; };
</script> </script>
<template> <template>
<div class="append-bottom-default"> <div class="append-bottom-default">
<gl-tabs> <gl-tabs @input="trackInstallationTabChange">
<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 +135,7 @@ export default { ...@@ -125,6 +135,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 +145,11 @@ export default { ...@@ -134,10 +145,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 +157,7 @@ export default { ...@@ -145,6 +157,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,9 @@ ...@@ -2,6 +2,9 @@
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';
import trackInstallationTabChange from '../utils';
export default { export default {
name: 'NpmInstallation', name: 'NpmInstallation',
...@@ -10,6 +13,12 @@ export default { ...@@ -10,6 +13,12 @@ export default {
GlTab, GlTab,
GlTabs, GlTabs,
}, },
mixins: [
Tracking.mixin({
label: TrackingLabels.NPM_INSTALLATION,
}),
trackInstallationTabChange,
],
props: { props: {
name: { name: {
type: String, type: String,
...@@ -63,19 +72,21 @@ export default { ...@@ -63,19 +72,21 @@ export default {
); );
}, },
}, },
trackingActions: { ...TrackingActions },
}; };
</script> </script>
<template> <template>
<div class="append-bottom-default"> <div class="append-bottom-default">
<gl-tabs> <gl-tabs @input="trackInstallationTabChange">
<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 +94,18 @@ export default { ...@@ -83,16 +94,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 +113,7 @@ export default { ...@@ -100,6 +113,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',
};
import { TrackingActions } from './constants';
const trackInstallationTabChange = {
methods: {
trackInstallationTabChange(tabIndex) {
const action = tabIndex === 0 ? TrackingActions.INSTALLATION : TrackingActions.REGISTRY_SETUP;
this.track(action);
},
},
};
export default trackInstallationTabChange;
...@@ -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