diff --git a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue index 09ae9a38cdfe28990b145b53281d3b62d87b8682..aeeb2654836b05c9c7ae0f597ccc612ae9f78d1d 100644 --- a/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue +++ b/app/assets/javascripts/sidebar/components/reviewers/sidebar_reviewers.vue @@ -7,6 +7,7 @@ import { GlButton } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { TYPE_ISSUE } from '~/issues/constants'; import { __ } from '~/locale'; +import { InternalEvents } from '~/tracking'; import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch'; import ReviewerDrawer from '~/merge_requests/components/reviewers/reviewer_drawer.vue'; @@ -35,6 +36,7 @@ export default { ApprovalSummary: () => import('ee_component/merge_requests/components/reviewers/approval_summary.vue'), }, + mixins: [InternalEvents.mixin()], props: { mediator: { type: Object, @@ -191,6 +193,10 @@ export default { }, toggleDrawerOpen(drawerOpen = !this.drawerOpen) { this.drawerOpen = drawerOpen; + + if (drawerOpen) { + this.trackEvent('open_reviewer_sidebar_panel_in_mr'); + } }, }, }; @@ -213,7 +219,7 @@ export default { category="tertiary" variant="confirm" class="gl-ml-2 !gl-text-sm" - data-testid="sidebar-reviewers-assign-buton" + data-testid="sidebar-reviewers-assign-button" @click="toggleDrawerOpen()" > {{ __('Assign') }} diff --git a/ee/config/events/open_reviewer_sidebar_panel_in_mr.yml b/ee/config/events/open_reviewer_sidebar_panel_in_mr.yml new file mode 100644 index 0000000000000000000000000000000000000000..8a945025371641dd8469ee178e3c42b1721d36bb --- /dev/null +++ b/ee/config/events/open_reviewer_sidebar_panel_in_mr.yml @@ -0,0 +1,16 @@ +--- +description: User opens the reviewer sidebar panel on an MR +internal_events: true +action: open_reviewer_sidebar_panel_in_mr +identifiers: +- project +- namespace +- user +product_group: code_review +product_categories: +- code_review_workflow +milestone: '17.10' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183774 +tiers: +- premium +- ultimate diff --git a/ee/config/metrics/counts_all/count_total_open_reviewer_sidebar_panel_in_mr.yml b/ee/config/metrics/counts_all/count_total_open_reviewer_sidebar_panel_in_mr.yml new file mode 100644 index 0000000000000000000000000000000000000000..6d5d4150e484685555de83d0d12a01377a28248b --- /dev/null +++ b/ee/config/metrics/counts_all/count_total_open_reviewer_sidebar_panel_in_mr.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.count_total_open_reviewer_sidebar_panel_in_mr +description: Count of the reviewer sidebar panel in an MR being opened by a user +product_group: code_review +product_categories: +- code_review_workflow +performance_indicator_type: [] +value_type: number +status: active +milestone: '17.10' +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183774 +time_frame: +- 28d +- 7d +- all +data_source: internal_events +data_category: optional +tiers: +- premium +- ultimate +events: +- name: open_reviewer_sidebar_panel_in_mr diff --git a/ee/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js b/ee/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f60c057e0ed9d14aef5d6bda19089993413c00f4 --- /dev/null +++ b/ee/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js @@ -0,0 +1,88 @@ +import Vue from 'vue'; +import axios from 'axios'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import VueApollo from 'vue-apollo'; +import { createMockSubscription as createMockApolloSubscription } from 'mock-apollo-client'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import stubChildren from 'helpers/stub_children'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper'; +import getMergeRequestReviewersQuery from '~/sidebar/queries/get_merge_request_reviewers.query.graphql'; +import mergeRequestReviewersUpdatedSubscription from '~/sidebar/queries/merge_request_reviewers.subscription.graphql'; +import SidebarReviewers from '~/sidebar/components/reviewers/sidebar_reviewers.vue'; +import SidebarMediator from '~/sidebar/sidebar_mediator'; +import { mockGetMergeRequestReviewers } from '../../mock_data'; + +const { bindInternalEventDocument } = useMockInternalEventsTracking(); + +Vue.use(VueApollo); + +describe('sidebar reviewers', () => { + const mockGQLQueries = [ + [getMergeRequestReviewersQuery, jest.fn().mockResolvedValue(mockGetMergeRequestReviewers)], + ]; + + const mockedSubscription = createMockApolloSubscription(); + const apolloMock = createMockApollo(mockGQLQueries); + let trackEventSpy; + let wrapper; + let mediator; + let axiosMock; + + apolloMock.defaultClient.setRequestHandler( + mergeRequestReviewersUpdatedSubscription, + () => mockedSubscription, + ); + + const findAssignButton = () => wrapper.findByTestId('sidebar-reviewers-assign-button'); + + const createComponent = ({ props = {} } = {}) => { + wrapper = mountExtended(SidebarReviewers, { + apolloProvider: apolloMock, + propsData: { + issuableIid: '1', + issuableId: 1, + mediator, + field: '', + projectPath: 'projectPath', + changing: false, + ...props, + }, + provide: { + projectPath: 'projectPath', + issuableId: 1, + issuableIid: 1, + multipleApprovalRulesAvailable: false, + }, + stubs: { + ...stubChildren(SidebarReviewers), + GlButton: false, + }, + // Attaching to document is required because this component emits something from the parent element :/ + attachTo: document.body, + }); + + ({ trackEventSpy } = bindInternalEventDocument(wrapper.element)); + }; + + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + mediator = new SidebarMediator({ currentUser: {} }); + }); + + afterEach(() => { + axiosMock.restore(); + }); + + it('sends the telemetry event when the reviewers panel is opened', async () => { + createComponent(); + await waitForPromises(); + + const assign = findAssignButton(); + + assign.trigger('click'); + + expect(trackEventSpy).toHaveBeenCalledWith('open_reviewer_sidebar_panel_in_mr', {}, undefined); + }); +}); diff --git a/ee/spec/frontend/sidebar/mock_data.js b/ee/spec/frontend/sidebar/mock_data.js index 35a4f8325785d854ade8a11fbf697fbd8c738ac1..c3ea18e298f9175738ce9a771e9cc0c5565ed88c 100644 --- a/ee/spec/frontend/sidebar/mock_data.js +++ b/ee/spec/frontend/sidebar/mock_data.js @@ -447,3 +447,35 @@ export const getHealthStatusQueryResponse = ({ state = 'opened', healthStatus = }, }; }; + +export const mockGetMergeRequestReviewers = { + data: { + workspace: { + id: 'gid://gitlab/Project/1', + issuable: { + id: 'gid://gitlab/MergeRequest/1', + reviewers: { + nodes: [ + { + id: 'gid://gitlab/User/1', + avatarUrl: 'image', + name: 'User', + username: 'username', + webUrl: 'https://example.com/username', + webPath: '/username', + status: null, + mergeRequestInteraction: { + canMerge: true, + canUpdate: true, + approved: true, + reviewState: 'APPROVED', + applicableApprovalRules: [{ id: 'gid://gitlab/ApprovalMergeRequestRule/1' }], + }, + }, + ], + }, + userPermissions: { adminMergeRequest: true }, + }, + }, + }, +}; diff --git a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js index 2e9126da81558581d92c8f26d4e5e481ad82478d..c541f0357bcc69193790c5092924472f903cacf2 100644 --- a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js +++ b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js @@ -1,9 +1,9 @@ -import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import axios from 'axios'; import AxiosMockAdapter from 'axios-mock-adapter'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import SidebarReviewers from '~/sidebar/components/reviewers/sidebar_reviewers.vue'; import SidebarService from '~/sidebar/services/sidebar_service'; import SidebarMediator from '~/sidebar/sidebar_mediator'; @@ -21,8 +21,10 @@ describe('sidebar reviewers', () => { let mediator; let axiosMock; + const findAssignButton = () => wrapper.findByTestId('sidebar-reviewers-assign-button'); + const createComponent = (props) => { - wrapper = shallowMount(SidebarReviewers, { + wrapper = shallowMountExtended(SidebarReviewers, { apolloProvider: apolloMock, propsData: { issuableIid: '1', @@ -67,7 +69,7 @@ describe('sidebar reviewers', () => { ${'shows'} | ${true} | ${true} ${'does not show'} | ${false} | ${false} `('$copy Assign button when canUpdate is $canUpdate', ({ canUpdate, expected }) => { - wrapper = shallowMount(SidebarReviewers, { + wrapper = shallowMountExtended(SidebarReviewers, { apolloProvider: apolloMock, propsData: { issuableIid: '1', @@ -93,7 +95,7 @@ describe('sidebar reviewers', () => { }, }); - expect(wrapper.find('[data-testid="sidebar-reviewers-assign-buton"]').exists()).toBe(expected); + expect(findAssignButton().exists()).toBe(expected); }); it('calls the mediator when it saves the reviewers', () => {