diff --git a/app/assets/javascripts/clusters_list/components/clusters_main_view.vue b/app/assets/javascripts/clusters_list/components/clusters_main_view.vue index 9e03093aa67738103edacee53d4cc6e816e42070..e18f70a6aff7c6eed5c0bb93585cd9cc064d467a 100644 --- a/app/assets/javascripts/clusters_list/components/clusters_main_view.vue +++ b/app/assets/javascripts/clusters_list/components/clusters_main_view.vue @@ -1,12 +1,22 @@ <script> import { GlTabs, GlTab } from '@gitlab/ui'; -import { CLUSTERS_TABS, MAX_CLUSTERS_LIST, MAX_LIST_COUNT, AGENT } from '../constants'; +import Tracking from '~/tracking'; +import { + CLUSTERS_TABS, + MAX_CLUSTERS_LIST, + MAX_LIST_COUNT, + AGENT, + EVENT_LABEL_TABS, + EVENT_ACTIONS, +} from '../constants'; import Agents from './agents.vue'; import InstallAgentModal from './install_agent_modal.vue'; import ClustersActions from './clusters_actions.vue'; import Clusters from './clusters.vue'; import ClustersViewAll from './clusters_view_all.vue'; +const trackingMixin = Tracking.mixin({ label: EVENT_LABEL_TABS }); + export default { components: { GlTabs, @@ -18,6 +28,7 @@ export default { InstallAgentModal, }, CLUSTERS_TABS, + mixins: [trackingMixin], props: { defaultBranchName: { default: '.noBranch', @@ -34,9 +45,12 @@ export default { methods: { onTabChange(tabName) { this.selectedTabIndex = CLUSTERS_TABS.findIndex((tab) => tab.queryParamValue === tabName); - this.maxAgents = tabName === AGENT ? MAX_LIST_COUNT : MAX_CLUSTERS_LIST; }, + trackTabChange(tab) { + const tabName = CLUSTERS_TABS[tab].queryParamValue; + this.track(EVENT_ACTIONS.change_tab, { property: tabName }); + }, }, }; </script> @@ -47,6 +61,7 @@ export default { sync-active-tab-with-query-params nav-class="gl-flex-grow-1 gl-align-items-center" lazy + @input="trackTabChange" > <gl-tab v-for="(tab, idx) in $options.CLUSTERS_TABS" diff --git a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue index 01f355bbf2d54156e5f03a6daf9168ed9a578149..72d5837c4fb7dd715d5c7881ce256dad6bc03535 100644 --- a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue +++ b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue @@ -11,8 +11,15 @@ import { import { helpPagePath } from '~/helpers/help_page_helper'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import CodeBlock from '~/vue_shared/components/code_block.vue'; +import Tracking from '~/tracking'; import { generateAgentRegistrationCommand } from '../clusters_util'; -import { INSTALL_AGENT_MODAL_ID, I18N_AGENT_MODAL, KAS_DISABLED_ERROR } from '../constants'; +import { + INSTALL_AGENT_MODAL_ID, + I18N_AGENT_MODAL, + KAS_DISABLED_ERROR, + EVENT_LABEL_MODAL, + EVENT_ACTIONS, +} from '../constants'; import { addAgentToStore, addAgentConfigToStore } from '../graphql/cache_update'; import createAgent from '../graphql/mutations/create_agent.mutation.graphql'; import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql'; @@ -20,8 +27,12 @@ import getAgentsQuery from '../graphql/queries/get_agents.query.graphql'; import agentConfigurations from '../graphql/queries/agent_configurations.query.graphql'; import AvailableAgentsDropdown from './available_agents_dropdown.vue'; +const trackingMixin = Tracking.mixin({ label: EVENT_LABEL_MODAL }); + export default { modalId: INSTALL_AGENT_MODAL_ID, + EVENT_ACTIONS, + EVENT_LABEL_MODAL, components: { AvailableAgentsDropdown, ClipboardButton, @@ -34,6 +45,7 @@ export default { GlModal, GlSprintf, }, + mixins: [trackingMixin], inject: ['projectPath', 'kasAddress', 'emptyStateImage'], props: { defaultBranchName: { @@ -81,7 +93,7 @@ export default { return !this.registering && this.agentName !== null; }, canCancel() { - return !this.registered && !this.registering && this.isRegisterModal; + return !this.registered && !this.registering && this.isAgentRegistrationModal; }, agentRegistrationCommand() { return generateAgentRegistrationCommand(this.agentToken, this.kasAddress); @@ -117,21 +129,24 @@ export default { return `/${this.projectPath}`; }, modalType() { - return !this.availableAgents?.length && !this.registered ? 'install' : 'register'; + return !this.availableAgents?.length && !this.registered + ? 'empty_state' + : 'agent_registration'; }, modalSize() { - return this.isInstallModal ? 'sm' : 'md'; + return this.isEmptyStateModal ? 'sm' : 'md'; }, - isInstallModal() { - return this.modalType === 'install'; + isEmptyStateModal() { + return this.modalType === 'empty_state'; }, - isRegisterModal() { - return this.modalType === 'register'; + isAgentRegistrationModal() { + return this.modalType === 'agent_registration'; }, }, methods: { setAgentName(name) { this.agentName = name; + this.track(EVENT_ACTIONS.select); }, closeModal() { this.$refs.modal.hide(); @@ -242,8 +257,9 @@ export default { static lazy @hidden="resetModal" + @show="track($options.EVENT_ACTIONS.open, { property: modalType })" > - <template v-if="isRegisterModal"> + <template v-if="isAgentRegistrationModal"> <template v-if="!registered"> <p> <strong>{{ i18n.selectAgentTitle }}</strong> @@ -347,23 +363,40 @@ export default { </template> <template #modal-footer> - <gl-button v-if="canCancel" @click="closeModal">{{ i18n.cancel }} </gl-button> + <gl-button + v-if="canCancel" + :data-track-action="$options.EVENT_ACTIONS.click" + :data-track-label="$options.EVENT_LABEL_MODAL" + data-track-property="cancel" + @click="closeModal" + >{{ i18n.cancel }} + </gl-button> - <gl-button v-if="registered" variant="confirm" category="primary" @click="closeModal" + <gl-button + v-if="registered" + variant="confirm" + category="primary" + :data-track-action="$options.EVENT_ACTIONS.click" + :data-track-label="$options.EVENT_LABEL_MODAL" + data-track-property="close" + @click="closeModal" >{{ i18n.close }} </gl-button> <gl-button - v-else-if="isRegisterModal" + v-else-if="isAgentRegistrationModal" :disabled="!nextButtonDisabled" variant="confirm" category="primary" + :data-track-action="$options.EVENT_ACTIONS.click" + :data-track-label="$options.EVENT_LABEL_MODAL" + data-track-property="register" @click="registerAgent" >{{ i18n.registerAgentButton }} </gl-button> <gl-button - v-if="isInstallModal" + v-if="isEmptyStateModal" :href="repositoryPath" variant="confirm" category="secondary" @@ -371,7 +404,14 @@ export default { >{{ i18n.secondaryButton }} </gl-button> - <gl-button v-if="isInstallModal" variant="confirm" category="primary" @click="closeModal" + <gl-button + v-if="isEmptyStateModal" + variant="confirm" + category="primary" + :data-track-action="$options.EVENT_ACTIONS.click" + :data-track-label="$options.EVENT_LABEL_MODAL" + data-track-property="done" + @click="closeModal" >{{ i18n.done }} </gl-button> </template> diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js index a740a6827cd4b2e9e054fd7c78f83925932930c3..396837cb72337c7a66a9ed18250d576dad21072e 100644 --- a/app/assets/javascripts/clusters_list/constants.js +++ b/app/assets/javascripts/clusters_list/constants.js @@ -65,7 +65,7 @@ export const STATUSES = { }; export const I18N_AGENT_MODAL = { - register: { + agent_registration: { registerAgentButton: s__('ClusterAgents|Register Agent'), close: __('Close'), cancel: __('Cancel'), @@ -104,7 +104,7 @@ export const I18N_AGENT_MODAL = { registrationErrorTitle: __('Failed to register Agent'), unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'), }, - install: { + empty_state: { modalTitle: s__('ClusterAgents|Install new Agent'), modalBody: s__( 'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.', @@ -236,3 +236,12 @@ export const CLUSTERS_ACTIONS = { export const AGENT = 'agent'; export const CERTIFICATE_BASED = 'certificate_based'; + +export const EVENT_LABEL_MODAL = 'agent_registration_modal'; +export const EVENT_LABEL_TABS = 'kubernetes_section_tabs'; +export const EVENT_ACTIONS = { + open: 'open_modal', + select: 'select_agent', + click: 'click_button', + change_tab: 'change_tab', +}; diff --git a/spec/frontend/clusters_list/components/clusters_main_view_spec.js b/spec/frontend/clusters_list/components/clusters_main_view_spec.js index c2233e5d39c2fd585398eebcf7c4b5e3f04b3c2d..596d89901f4e139298b0f1161071b9f8b461baa2 100644 --- a/spec/frontend/clusters_list/components/clusters_main_view_spec.js +++ b/spec/frontend/clusters_list/components/clusters_main_view_spec.js @@ -1,5 +1,6 @@ import { GlTabs, GlTab } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { mockTracking } from 'helpers/tracking_helper'; import ClustersMainView from '~/clusters_list/components/clusters_main_view.vue'; import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue'; import { @@ -8,12 +9,15 @@ import { CLUSTERS_TABS, MAX_CLUSTERS_LIST, MAX_LIST_COUNT, + EVENT_LABEL_TABS, + EVENT_ACTIONS, } from '~/clusters_list/constants'; const defaultBranchName = 'default-branch'; describe('ClustersMainViewComponent', () => { let wrapper; + let trackingSpy; const propsData = { defaultBranchName, @@ -23,6 +27,7 @@ describe('ClustersMainViewComponent', () => { wrapper = shallowMountExtended(ClustersMainView, { propsData, }); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }); afterEach(() => { @@ -71,6 +76,7 @@ describe('ClustersMainViewComponent', () => { beforeEach(() => { findComponent().vm.$emit('changeTab', AGENT); }); + it('changes the tab', () => { expect(findTabs().attributes('value')).toBe('1'); }); @@ -78,5 +84,13 @@ describe('ClustersMainViewComponent', () => { it('passes correct max-agents param to the modal', () => { expect(findModal().props('maxAgents')).toBe(MAX_LIST_COUNT); }); + + it('sends the correct tracking event', () => { + findTabs().vm.$emit('input', 1); + expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS.change_tab, { + label: EVENT_LABEL_TABS, + property: AGENT, + }); + }); }); }); diff --git a/spec/frontend/clusters_list/components/install_agent_modal_spec.js b/spec/frontend/clusters_list/components/install_agent_modal_spec.js index 39419e94fe66da00942e2f2824142b9af163dd7c..b3188fbcc246abdb096c4708934e89c7cb987bdf 100644 --- a/spec/frontend/clusters_list/components/install_agent_modal_spec.js +++ b/spec/frontend/clusters_list/components/install_agent_modal_spec.js @@ -2,9 +2,15 @@ import { GlAlert, GlButton, GlFormInputGroup } from '@gitlab/ui'; import { createLocalVue } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { mockTracking } from 'helpers/tracking_helper'; import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue'; import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue'; -import { I18N_AGENT_MODAL, MAX_LIST_COUNT } from '~/clusters_list/constants'; +import { + I18N_AGENT_MODAL, + MAX_LIST_COUNT, + EVENT_LABEL_MODAL, + EVENT_ACTIONS, +} from '~/clusters_list/constants'; import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql'; import getAgentConfigurations from '~/clusters_list/graphql/queries/agent_configurations.query.graphql'; import createAgentMutation from '~/clusters_list/graphql/mutations/create_agent.mutation.graphql'; @@ -34,6 +40,7 @@ const maxAgents = MAX_LIST_COUNT; describe('InstallAgentModal', () => { let wrapper; let apolloProvider; + let trackingSpy; const configurations = [{ agentName: 'agent-name' }]; const apolloQueryResponse = { @@ -56,7 +63,7 @@ describe('InstallAgentModal', () => { const findActionButton = () => findButtonByVariant('confirm'); const findCancelButton = () => findButtonByVariant('default'); const findSecondaryButton = () => wrapper.findByTestId('agent-secondary-button'); - const findImage = () => wrapper.findByRole('img', { alt: I18N_AGENT_MODAL.install.altText }); + const findImage = () => wrapper.findByRole('img', { alt: I18N_AGENT_MODAL.empty_state.altText }); const expectDisabledAttribute = (element, disabled) => { if (disabled) { @@ -121,6 +128,7 @@ describe('InstallAgentModal', () => { [getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)], ]); createWrapper(); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }); afterEach(() => { @@ -129,7 +137,7 @@ describe('InstallAgentModal', () => { }); describe('when agent configurations are present', () => { - const i18n = I18N_AGENT_MODAL.register; + const i18n = I18N_AGENT_MODAL.agent_registration; describe('initial state', () => { it('renders the dropdown for available agents', () => { @@ -150,6 +158,14 @@ describe('InstallAgentModal', () => { expect(findActionButton().text()).toBe(i18n.registerAgentButton); expectDisabledAttribute(findActionButton(), true); }); + + it('sends the event with the modalType', () => { + findModal().vm.$emit('show'); + expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS.open, { + label: EVENT_LABEL_MODAL, + property: 'agent_registration', + }); + }); }); describe('an agent is selected', () => { @@ -161,6 +177,12 @@ describe('InstallAgentModal', () => { expect(findActionButton().isVisible()).toBe(true); expectDisabledAttribute(findActionButton(), false); }); + + it('sends the correct tracking event', () => { + expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS.select, { + label: EVENT_LABEL_MODAL, + }); + }); }); describe('registering an agent', () => { @@ -247,7 +269,7 @@ describe('InstallAgentModal', () => { }); describe('when there are no agent configurations present', () => { - const i18n = I18N_AGENT_MODAL.install; + const i18n = I18N_AGENT_MODAL.empty_state; const apolloQueryEmptyResponse = { data: { project: { @@ -272,5 +294,13 @@ describe('InstallAgentModal', () => { expect(findSecondaryButton().isVisible()).toBe(true); expect(findSecondaryButton().text()).toBe(i18n.secondaryButton); }); + + it('sends the event with the modalType', () => { + findModal().vm.$emit('show'); + expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS.open, { + label: EVENT_LABEL_MODAL, + property: 'empty_state', + }); + }); }); });