diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue index 756d9b42e99cde519694ee6157487dd4dfbfb790..79d14b5f2d00de2fe4e51d5e7fd3282552eb7059 100644 --- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue +++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue @@ -9,6 +9,7 @@ import { } from '@gitlab/ui'; import { s__, __ } from '~/locale'; import { visitUrl } from '~/lib/utils/url_utility'; +import Tracking from '~/tracking'; import ConfirmForkModal from '~/vue_shared/components/web_ide/confirm_fork_modal.vue'; import { KEY_EDIT, KEY_WEB_IDE, KEY_GITPOD, KEY_PIPELINE_EDITOR } from './constants'; @@ -28,6 +29,8 @@ export const i18n = { toggleText: __('Edit'), }; +const TRACKING_ACTION_NAME = 'click_consolidated_edit'; + export default { name: 'CEWebIdeLink', components: { @@ -40,6 +43,7 @@ export default { ConfirmForkModal, }, i18n, + mixins: [Tracking.mixin()], props: { isFork: { type: Boolean, @@ -181,9 +185,9 @@ export default { key: KEY_EDIT, text: __('Edit single file'), secondaryText: __('Edit this file only.'), - attrs: { - 'data-track-action': 'click_consolidated_edit', - 'data-track-label': 'edit', + tracking: { + action: TRACKING_ACTION_NAME, + label: 'single_file', }, ...handleOptions, }; @@ -223,9 +227,9 @@ export default { key: KEY_WEB_IDE, text: this.webIdeActionText, secondaryText: this.$options.i18n.webIdeText, - attrs: { - 'data-track-action': 'click_consolidated_edit_ide', - 'data-track-label': 'web_ide', + tracking: { + action: TRACKING_ACTION_NAME, + label: 'web_ide', }, ...handleOptions, }; @@ -253,9 +257,9 @@ export default { text: __('Edit in pipeline editor'), secondaryText, href: this.pipelineEditorUrl, - attrs: { - 'data-track-action': 'click_consolidated_pipeline_editor', - 'data-track-label': 'pipeline_editor', + tracking: { + action: TRACKING_ACTION_NAME, + label: 'pipeline_editor', }, }; }, @@ -277,6 +281,10 @@ export default { key: KEY_GITPOD, text: this.gitpodActionText, secondaryText, + tracking: { + action: TRACKING_ACTION_NAME, + label: 'gitpod', + }, ...handleOptions, }; }, @@ -311,6 +319,7 @@ export default { this[dataKey] = true; }, executeAction(action) { + this.track(action.tracking.action, { label: action.tracking.label }); action.handle?.(); }, }, @@ -335,7 +344,6 @@ export default { <gl-disclosure-dropdown-item v-for="action in actions" :key="action.key" - v-bind="action.attrs" :item="action" :data-qa-selector="`${action.key}_menu_item`" @action="executeAction(action)" diff --git a/config/events/20211215022206_default_web_ide_click_consolidated_edit_ide.yml b/config/events/20211215022206_default_web_ide_click_consolidated_edit_ide.yml deleted file mode 100644 index 45b621cb404fd2e918610285d6e3078d5be19dae..0000000000000000000000000000000000000000 --- a/config/events/20211215022206_default_web_ide_click_consolidated_edit_ide.yml +++ /dev/null @@ -1,20 +0,0 @@ -description: "Edit multiple files with Web IDE" -category: default -action: click_consolidated_edit_ide -label_description: "`web_ide`" -property_description: "" -value_description: "" -extra_properties: -identifiers: -product_section: dev -product_stage: create -product_group: group::editor -milestone: "14.1" -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64179 -distributions: -- ce -- ee -tiers: -- free -- premium -- ultimate diff --git a/config/events/20211215022206_default_edit_click_consolidated_edit.yml b/config/events/20230727180523_default_click_consolidated_edit.yml similarity index 51% rename from config/events/20211215022206_default_edit_click_consolidated_edit.yml rename to config/events/20230727180523_default_click_consolidated_edit.yml index ffa88b0a384ca3afb2fff761966f01a319045e49..06da6ac1257fb112bc3bad6b869438f73dd44dc8 100644 --- a/config/events/20211215022206_default_edit_click_consolidated_edit.yml +++ b/config/events/20230727180523_default_click_consolidated_edit.yml @@ -1,16 +1,17 @@ -description: "Edit a single file" +--- +description: "Selects an editor in the Edit dropdown menu" category: default action: click_consolidated_edit -label_description: "`edit`" -property_description: "" -value_description: "" +label_description: "The editor selected in the Edit dropdown menu" +property_description: +value_description: extra_properties: identifiers: product_section: dev product_stage: create -product_group: group::editor -milestone: "14.1" -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64179 +product_group: group::ide +milestone: "16.3" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127163 distributions: - ce - ee @@ -18,3 +19,4 @@ tiers: - free - premium - ultimate + diff --git a/ee/app/assets/javascripts/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item.vue b/ee/app/assets/javascripts/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item.vue index b643597dc3b338a77147eb81a7f41fa29d609b1b..ba6464a7f4c3a29e2e5f17f747e8158ae23ba6c6 100644 --- a/ee/app/assets/javascripts/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item.vue +++ b/ee/app/assets/javascripts/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item.vue @@ -1,5 +1,6 @@ <script> import { GlDisclosureDropdownItem } from '@gitlab/ui'; +import Tracking from '~/tracking'; import WorkspaceStateIndicator from '../common/workspace_state_indicator.vue'; import WorkspaceActions from '../common/workspace_actions.vue'; @@ -9,6 +10,7 @@ export default { WorkspaceStateIndicator, WorkspaceActions, }, + mixins: [Tracking.mixin()], props: { workspace: { type: Object, @@ -23,10 +25,15 @@ export default { }; }, }, + methods: { + trackOpenWorkspace() { + this.track('click_consolidated_edit', { label: 'workspace' }); + }, + }, }; </script> <template> - <gl-disclosure-dropdown-item class="gl-my-0" :item="dropdownItem"> + <gl-disclosure-dropdown-item class="gl-my-0" :item="dropdownItem" @action="trackOpenWorkspace"> <template #list-item> <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center"> <span class="gl-display-inline-flex gl-align-items-center"> diff --git a/ee/spec/frontend/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item_spec.js b/ee/spec/frontend/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item_spec.js index 35b2c35e49de5cb65a9db8a0a85ae1bf1eba7a7f..82695443704b93f0260b3a72ad6ca6489e9ae4ff 100644 --- a/ee/spec/frontend/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item_spec.js +++ b/ee/spec/frontend/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item_spec.js @@ -1,3 +1,5 @@ +import { GlDisclosureDropdownItem } from '@gitlab/ui'; +import { mockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import WorkspaceDropdownItem from 'ee/remote_development/components/workspaces_dropdown_group/workspace_dropdown_item.vue'; import WorkspaceStateIndicator from 'ee/remote_development/components/common/workspace_state_indicator.vue'; @@ -7,6 +9,7 @@ import { WORKSPACE } from '../../mock_data'; describe('remote_development/components/workspaces_dropdown_group/workspace_dropdown_item.vue', () => { let wrapper; + let trackingSpy; const createWrapper = () => { wrapper = shallowMountExtended(WorkspaceDropdownItem, { @@ -14,9 +17,12 @@ describe('remote_development/components/workspaces_dropdown_group/workspace_drop workspace: WORKSPACE, }, }); + + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }; const findWorkspaceStateIndicator = () => wrapper.findComponent(WorkspaceStateIndicator); const findWorkspaceActions = () => wrapper.findComponent(WorkspaceActions); + const findDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem); describe('default', () => { beforeEach(() => { @@ -31,6 +37,13 @@ describe('remote_development/components/workspaces_dropdown_group/workspace_drop expect(wrapper.text()).toContain(WORKSPACE.name); }); + it('passes workspace URL to the dropdown item', () => { + expect(findDropdownItem().props().item).toEqual({ + text: WORKSPACE.name, + href: WORKSPACE.url, + }); + }); + it('displays workspace actions', () => { expect(findWorkspaceActions().props()).toEqual({ actualState: WORKSPACE.actualState, @@ -40,6 +53,20 @@ describe('remote_development/components/workspaces_dropdown_group/workspace_drop }); }); + describe('when the dropdown item emits "action" event', () => { + beforeEach(() => { + createWrapper(); + + findDropdownItem().vm.$emit('action'); + }); + + it('tracks event', () => { + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_consolidated_edit', { + label: 'workspace', + }); + }); + }); + describe('when workspaces action is clicked', () => { it('emits updateWorkspace event with the desiredState provided by the action', () => { createWrapper(); diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js index 063f1d793ef7c3ddff16e4987b2698ab81679276..56d89d428f71abf2965eecf426a9fc891a6e87aa 100644 --- a/spec/frontend/vue_shared/components/web_ide_link_spec.js +++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js @@ -1,4 +1,5 @@ import { GlModal, GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui'; +import { omit } from 'lodash'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -6,13 +7,14 @@ import getWritableForksResponse from 'test_fixtures/graphql/vue_shared/component import WebIdeLink, { i18n } from '~/vue_shared/components/web_ide_link.vue'; import ConfirmForkModal from '~/vue_shared/components/web_ide/confirm_fork_modal.vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; import { stubComponent } from 'helpers/stub_component'; +import { mockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended, mountExtended, extendedWrapper, } from 'helpers/vue_test_utils_helper'; -import createMockApollo from 'helpers/mock_apollo_helper'; import { visitUrl } from '~/lib/utils/url_utility'; import getWritableForksQuery from '~/vue_shared/components/web_ide/get_writable_forks.query.graphql'; @@ -34,8 +36,10 @@ const ACTION_EDIT = { secondaryText: 'Edit this file only.', attrs: { 'data-qa-selector': 'edit_menu_item', - 'data-track-action': 'click_consolidated_edit', - 'data-track-label': 'edit', + }, + tracking: { + action: 'click_consolidated_edit', + label: 'single_file', }, }; const ACTION_EDIT_CONFIRM_FORK = { @@ -48,11 +52,13 @@ const ACTION_WEB_IDE = { text: 'Web IDE', attrs: { 'data-qa-selector': 'webide_menu_item', - 'data-track-action': 'click_consolidated_edit_ide', - 'data-track-label': 'web_ide', }, href: undefined, handle: expect.any(Function), + tracking: { + action: 'click_consolidated_edit', + label: 'web_ide', + }, }; const ACTION_WEB_IDE_CONFIRM_FORK = { ...ACTION_WEB_IDE, @@ -67,6 +73,10 @@ const ACTION_GITPOD = { attrs: { 'data-qa-selector': 'gitpod_menu_item', }, + tracking: { + action: 'click_consolidated_edit', + label: 'gitpod', + }, }; const ACTION_GITPOD_ENABLE = { ...ACTION_GITPOD, @@ -79,8 +89,10 @@ const ACTION_PIPELINE_EDITOR = { text: 'Edit in pipeline editor', attrs: { 'data-qa-selector': 'pipeline_editor_menu_item', - 'data-track-action': 'click_consolidated_pipeline_editor', - 'data-track-label': 'pipeline_editor', + }, + tracking: { + action: 'click_consolidated_edit', + label: 'pipeline_editor', }, }; @@ -88,6 +100,7 @@ describe('vue_shared/components/web_ide_link', () => { Vue.use(VueApollo); let wrapper; + let trackingSpy; function createComponent(props, { mountFn = shallowMountExtended, slots = {} } = {}) { const fakeApollo = createMockApollo([ @@ -116,6 +129,8 @@ describe('vue_shared/components/web_ide_link', () => { }, apolloProvider: fakeApollo, }); + + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); } const findDisclosureDropdown = () => wrapper.findComponent(GlDisclosureDropdown); @@ -135,13 +150,12 @@ describe('vue_shared/components/web_ide_link', () => { handle: props.item.handle, attrs: { 'data-qa-selector': attributes['data-qa-selector'], - 'data-track-action': attributes['data-track-action'], - 'data-track-label': attributes['data-track-label'], }, }; }); + const omitTrackingParams = (actions) => actions.map((action) => omit(action, 'tracking')); - it.each([ + describe.each([ { props: {}, expectedActions: [ACTION_WEB_IDE, ACTION_EDIT], @@ -231,10 +245,27 @@ describe('vue_shared/components/web_ide_link', () => { props: { showEditButton: false }, expectedActions: [ACTION_WEB_IDE], }, - ])('renders actions with appropriately for given props', ({ props, expectedActions }) => { - createComponent(props); + ])('for a set of props', ({ props, expectedActions }) => { + beforeEach(() => { + createComponent(props); + }); + + it('renders the appropiate actions', () => { + // omit tracking property because it is not included in the dropdown item + expect(getDropdownItemsAsData()).toEqual(omitTrackingParams(expectedActions)); + }); + + describe('when an action is clicked', () => { + it('tracks event', () => { + expectedActions.forEach((action, index) => { + findDisclosureDropdownItems().at(index).vm.$emit('action'); - expect(getDropdownItemsAsData()).toEqual(expectedActions); + expect(trackingSpy).toHaveBeenCalledWith(undefined, action.tracking.action, { + label: action.tracking.label, + }); + }); + }); + }); }); it('bubbles up shown and hidden events triggered by actions button component', () => { @@ -272,11 +303,9 @@ describe('vue_shared/components/web_ide_link', () => { }); it('displays Pipeline Editor as the first action', () => { - expect(getDropdownItemsAsData()).toEqual([ - ACTION_PIPELINE_EDITOR, - ACTION_WEB_IDE, - ACTION_GITPOD, - ]); + expect(getDropdownItemsAsData()).toEqual( + omitTrackingParams([ACTION_PIPELINE_EDITOR, ACTION_WEB_IDE, ACTION_GITPOD]), + ); }); it('when web ide button is clicked it opens in a new tab', async () => {