Commit 84755869 authored by Doug Stull's avatar Doug Stull

Add source to invite modal calls

- for telemetry of who triggered an invite.
parent 43239d5a
...@@ -29,8 +29,7 @@ export default { ...@@ -29,8 +29,7 @@ export default {
}, },
triggerSource: { triggerSource: {
type: String, type: String,
required: false, required: true,
default: 'unknown',
}, },
trackExperiment: { trackExperiment: {
type: String, type: String,
......
...@@ -248,7 +248,7 @@ export default { ...@@ -248,7 +248,7 @@ export default {
> >
<template #footer> <template #footer>
<gl-dropdown-item v-if="directlyInviteMembers"> <gl-dropdown-item v-if="directlyInviteMembers">
<sidebar-invite-members /> <sidebar-invite-members :issuable-type="issuableType" />
</gl-dropdown-item> </template </gl-dropdown-item> </template
></user-select> ></user-select>
</template> </template>
......
...@@ -9,6 +9,17 @@ export default { ...@@ -9,6 +9,17 @@ export default {
components: { components: {
InviteMembersTrigger, InviteMembersTrigger,
}, },
props: {
issuableType: {
type: String,
required: true,
},
},
computed: {
triggerSource() {
return `${this.issuableType}-assignee-dropdown`;
},
},
}; };
</script> </script>
...@@ -18,6 +29,7 @@ export default { ...@@ -18,6 +29,7 @@ export default {
:display-text="$options.displayText" :display-text="$options.displayText"
:event="$options.dataTrackEvent" :event="$options.dataTrackEvent"
:label="$options.dataTrackLabel" :label="$options.dataTrackLabel"
:trigger-source="triggerSource"
classes="gl-display-block gl-pl-6 gl-hover-text-decoration-none gl-hover-text-blue-800!" classes="gl-display-block gl-pl-6 gl-hover-text-decoration-none gl-hover-text-blue-800!"
/> />
</template> </template>
...@@ -15,7 +15,10 @@ ...@@ -15,7 +15,10 @@
.gl-w-half.gl-xs-w-full .gl-w-half.gl-xs-w-full
.gl-display-flex.gl-flex-wrap.gl-justify-content-end.gl-mb-3 .gl-display-flex.gl-flex-wrap.gl-justify-content-end.gl-mb-3
.js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite a group') } } .js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite a group') } }
.js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } } .js-invite-members-trigger{ data: { variant: 'success',
classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3',
trigger_source: 'group-members-page',
display_text: _('Invite members') } }
= render 'groups/invite_members_modal', group: @group = render 'groups/invite_members_modal', group: @group
- if can_manage_members? && Feature.disabled?(:invite_members_group_modal, @group) - if can_manage_members? && Feature.disabled?(:invite_members_group_modal, @group)
%hr.gl-mt-4 %hr.gl-mt-4
......
...@@ -26,7 +26,10 @@ ...@@ -26,7 +26,10 @@
- if @project.allowed_to_share_with_group? - if @project.allowed_to_share_with_group?
.js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite a group') } } .js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite a group') } }
- if can_manage_project_members?(@project) && !membership_locked? - if can_manage_project_members?(@project) && !membership_locked?
.js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } } .js-invite-members-trigger{ data: { variant: 'success',
classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3',
trigger_source: 'project-members-page',
display_text: _('Invite members') } }
= render 'projects/invite_members_modal', project: @project = render 'projects/invite_members_modal', project: @project
- else - else
......
...@@ -45,4 +45,5 @@ ...@@ -45,4 +45,5 @@
= render 'shared/issuable/sidebar_user_dropdown', = render 'shared/issuable/sidebar_user_dropdown',
options: options, options: options,
wrapper_class: 'js-sidebar-assignee-dropdown', wrapper_class: 'js-sidebar-assignee-dropdown',
track_label: 'edit_assignee' track_label: 'edit_assignee',
trigger_source: "#{issuable_type}-assignee-dropdown"
...@@ -42,4 +42,5 @@ ...@@ -42,4 +42,5 @@
= render 'shared/issuable/sidebar_user_dropdown', = render 'shared/issuable/sidebar_user_dropdown',
options: options, options: options,
wrapper_class: 'js-sidebar-reviewer-dropdown', wrapper_class: 'js-sidebar-reviewer-dropdown',
track_label: 'edit_reviewer' track_label: 'edit_reviewer',
trigger_source: "#{issuable_type}-reviewer-dropdown"
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
.js-invite-members-trigger{ data: { trigger_element: 'anchor', .js-invite-members-trigger{ data: { trigger_element: 'anchor',
display_text: _('Invite Members'), display_text: _('Invite Members'),
event: 'click_invite_members', event: 'click_invite_members',
trigger_source: local_assigns.fetch(:trigger_source),
label: data['track-label'] } } label: data['track-label'] } }
- else - else
= dropdown_tag(data['dropdown-title'], options: options) = dropdown_tag(data['dropdown-title'], options: options)
...@@ -23,7 +23,7 @@ module API ...@@ -23,7 +23,7 @@ module API
requires :email, types: [String, Array[String]], email_or_email_list: true, desc: 'The email address to invite, or multiple emails separated by comma' requires :email, types: [String, Array[String]], email_or_email_list: true, desc: 'The email address to invite, or multiple emails separated by comma'
requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)' requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'api' optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api'
end end
post ":id/invitations" do post ":id/invitations" do
params[:source] = find_source(source_type, params[:id]) params[:source] = find_source(source_type, params[:id])
......
...@@ -93,7 +93,7 @@ module API ...@@ -93,7 +93,7 @@ module API
requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)' requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)'
requires :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.' requires :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'api' optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post ":id/members" do post ":id/members" do
......
...@@ -80,7 +80,7 @@ RSpec.describe 'Groups > Members > Manage members' do ...@@ -80,7 +80,7 @@ RSpec.describe 'Groups > Members > Manage members' do
expect_snowplow_event( expect_snowplow_event(
category: 'Members::CreateService', category: 'Members::CreateService',
action: 'create_member', action: 'create_member',
label: 'unknown', label: 'group-members-page',
property: 'existing_user', property: 'existing_user',
user: user1 user: user1
) )
...@@ -189,7 +189,7 @@ RSpec.describe 'Groups > Members > Manage members' do ...@@ -189,7 +189,7 @@ RSpec.describe 'Groups > Members > Manage members' do
expect_snowplow_event( expect_snowplow_event(
category: 'Members::InviteService', category: 'Members::InviteService',
action: 'create_member', action: 'create_member',
label: 'unknown', label: 'group-members-page',
property: 'net_new_user', property: 'net_new_user',
user: user1 user: user1
) )
......
...@@ -59,7 +59,7 @@ RSpec.describe 'Project members list', :js do ...@@ -59,7 +59,7 @@ RSpec.describe 'Project members list', :js do
expect_snowplow_event( expect_snowplow_event(
category: 'Members::CreateService', category: 'Members::CreateService',
action: 'create_member', action: 'create_member',
label: 'unknown', label: 'project-members-page',
property: 'existing_user', property: 'existing_user',
user: user1 user: user1
) )
...@@ -117,7 +117,7 @@ RSpec.describe 'Project members list', :js do ...@@ -117,7 +117,7 @@ RSpec.describe 'Project members list', :js do
expect_snowplow_event( expect_snowplow_event(
category: 'Members::InviteService', category: 'Members::InviteService',
action: 'create_member', action: 'create_member',
label: 'unknown', label: 'project-members-page',
property: 'net_new_user', property: 'net_new_user',
user: user1 user: user1
) )
......
...@@ -7,6 +7,8 @@ import eventHub from '~/invite_members/event_hub'; ...@@ -7,6 +7,8 @@ import eventHub from '~/invite_members/event_hub';
jest.mock('~/experimentation/experiment_tracking'); jest.mock('~/experimentation/experiment_tracking');
const displayText = 'Invite team members'; const displayText = 'Invite team members';
const triggerSource = '_trigger_source_';
let wrapper; let wrapper;
let triggerProps; let triggerProps;
let findButton; let findButton;
...@@ -26,7 +28,7 @@ const createComponent = (props = {}) => { ...@@ -26,7 +28,7 @@ const createComponent = (props = {}) => {
}; };
describe.each(['button', 'anchor'])('with triggerElement as %s', (triggerElement) => { describe.each(['button', 'anchor'])('with triggerElement as %s', (triggerElement) => {
triggerProps = { triggerElement }; triggerProps = { triggerElement, triggerSource };
findButton = () => wrapper.findComponent(triggerComponent[triggerElement]); findButton = () => wrapper.findComponent(triggerComponent[triggerElement]);
afterEach(() => { afterEach(() => {
...@@ -48,22 +50,14 @@ describe.each(['button', 'anchor'])('with triggerElement as %s', (triggerElement ...@@ -48,22 +50,14 @@ describe.each(['button', 'anchor'])('with triggerElement as %s', (triggerElement
spy = jest.spyOn(eventHub, '$emit'); spy = jest.spyOn(eventHub, '$emit');
}); });
it('emits openModal from an unknown source', () => {
createComponent();
findButton().vm.$emit('click');
expect(spy).toHaveBeenCalledWith('openModal', { inviteeType: 'members', source: 'unknown' });
});
it('emits openModal from a named source', () => { it('emits openModal from a named source', () => {
createComponent({ triggerSource: '_trigger_source_' }); createComponent();
findButton().vm.$emit('click'); findButton().vm.$emit('click');
expect(spy).toHaveBeenCalledWith('openModal', { expect(spy).toHaveBeenCalledWith('openModal', {
inviteeType: 'members', inviteeType: 'members',
source: '_trigger_source_', source: triggerSource,
}); });
}); });
}); });
......
...@@ -4,11 +4,16 @@ import SidebarInviteMembers from '~/sidebar/components/assignees/sidebar_invite_ ...@@ -4,11 +4,16 @@ import SidebarInviteMembers from '~/sidebar/components/assignees/sidebar_invite_
describe('Sidebar invite members component', () => { describe('Sidebar invite members component', () => {
let wrapper; let wrapper;
const issuableType = 'issue';
const findDirectInviteLink = () => wrapper.findComponent(InviteMembersTrigger); const findDirectInviteLink = () => wrapper.findComponent(InviteMembersTrigger);
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(SidebarInviteMembers); wrapper = shallowMount(SidebarInviteMembers, {
propsData: {
issuableType,
},
});
}; };
afterEach(() => { afterEach(() => {
...@@ -23,5 +28,9 @@ describe('Sidebar invite members component', () => { ...@@ -23,5 +28,9 @@ describe('Sidebar invite members component', () => {
it('renders a direct link to project members path', () => { it('renders a direct link to project members path', () => {
expect(findDirectInviteLink().exists()).toBe(true); expect(findDirectInviteLink().exists()).toBe(true);
}); });
it('has expected attributes on the trigger', () => {
expect(findDirectInviteLink().props('triggerSource')).toBe('issue-assignee-dropdown');
});
}); });
}); });
...@@ -161,7 +161,7 @@ RSpec.describe API::Invitations do ...@@ -161,7 +161,7 @@ RSpec.describe API::Invitations do
expect_snowplow_event( expect_snowplow_event(
category: 'Members::InviteService', category: 'Members::InviteService',
action: 'create_member', action: 'create_member',
label: 'api', label: 'invitations-api',
property: 'net_new_user', property: 'net_new_user',
user: maintainer user: maintainer
) )
......
...@@ -265,7 +265,7 @@ RSpec.describe API::Members do ...@@ -265,7 +265,7 @@ RSpec.describe API::Members do
expect_snowplow_event( expect_snowplow_event(
category: 'Members::CreateService', category: 'Members::CreateService',
action: 'create_member', action: 'create_member',
label: 'api', label: 'members-api',
property: 'existing_user', property: 'existing_user',
user: maintainer user: maintainer
) )
...@@ -322,7 +322,7 @@ RSpec.describe API::Members do ...@@ -322,7 +322,7 @@ RSpec.describe API::Members do
expect_snowplow_event( expect_snowplow_event(
category: 'Members::CreateService', category: 'Members::CreateService',
action: 'create_member', action: 'create_member',
label: 'api', label: 'members-api',
property: 'existing_user', property: 'existing_user',
user: maintainer user: maintainer
) )
......
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