Commit fa18ec94 authored by Doug Stull's avatar Doug Stull Committed by Brandon Labuschagne

Experiment: add area of focus question to invite modal

parent 5c794438
......@@ -9,13 +9,14 @@ import {
GlSprintf,
GlButton,
GlFormInput,
GlFormCheckboxGroup,
} from '@gitlab/ui';
import { partition, isString } from 'lodash';
import Api from '~/api';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { s__, sprintf } from '~/locale';
import { INVITE_MEMBERS_IN_COMMENT, GROUP_FILTERS } from '../constants';
import { INVITE_MEMBERS_IN_COMMENT, GROUP_FILTERS, MEMBER_AREAS_OF_FOCUS } from '../constants';
import eventHub from '../event_hub';
import {
responseMessageFromError,
......@@ -36,6 +37,7 @@ export default {
GlSprintf,
GlButton,
GlFormInput,
GlFormCheckboxGroup,
MembersTokenSelect,
GroupSelect,
},
......@@ -74,6 +76,14 @@ export default {
type: String,
required: true,
},
areasOfFocusOptions: {
type: Array,
required: true,
},
noSelectionAreasOfFocus: {
type: Array,
required: true,
},
},
data() {
return {
......@@ -83,6 +93,7 @@ export default {
inviteeType: 'members',
newUsersToInvite: [],
selectedDate: undefined,
selectedAreasOfFocus: [],
groupToBeSharedWith: {},
source: 'unknown',
invalidFeedbackMessage: '',
......@@ -128,10 +139,21 @@ export default {
this.newUsersToInvite.length === 0 && Object.keys(this.groupToBeSharedWith).length === 0
);
},
areasOfFocusEnabled() {
return this.areasOfFocusOptions.length !== 0;
},
areasOfFocusForPost() {
if (this.selectedAreasOfFocus.length === 0 && this.areasOfFocusEnabled) {
return this.noSelectionAreasOfFocus;
}
return this.selectedAreasOfFocus;
},
},
mounted() {
eventHub.$on('openModal', (options) => {
this.openModal(options);
this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.view);
});
},
methods: {
......@@ -152,9 +174,12 @@ export default {
this.$root.$emit(BV_SHOW_MODAL, this.modalId);
},
trackEvent(experimentName, eventName) {
const tracking = new ExperimentTracking(experimentName);
tracking.event(eventName);
},
closeModal() {
this.resetFields();
this.$root.$emit(BV_HIDE_MODAL, this.modalId);
this.$refs.modal.hide();
},
sendInvite() {
if (this.isInviteGroup) {
......@@ -165,9 +190,10 @@ export default {
},
trackInvite() {
if (this.source === INVITE_MEMBERS_IN_COMMENT) {
const tracking = new ExperimentTracking(INVITE_MEMBERS_IN_COMMENT);
tracking.event('comment_invite_success');
this.trackEvent(INVITE_MEMBERS_IN_COMMENT, 'comment_invite_success');
}
this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.submit);
},
resetFields() {
this.isLoading = false;
......@@ -176,6 +202,7 @@ export default {
this.newUsersToInvite = [];
this.groupToBeSharedWith = {};
this.invalidFeedbackMessage = '';
this.selectedAreasOfFocus = [];
},
changeSelectedItem(item) {
this.selectedAccessLevel = item;
......@@ -223,6 +250,7 @@ export default {
email: usersToInviteByEmail,
access_level: this.selectedAccessLevel,
invite_source: this.source,
areas_of_focus: this.areasOfFocusForPost,
};
},
addByUserIdPostData(usersToAddById) {
......@@ -231,6 +259,7 @@ export default {
user_id: usersToAddById,
access_level: this.selectedAccessLevel,
invite_source: this.source,
areas_of_focus: this.areasOfFocusForPost,
};
},
shareWithGroupPostData(groupToBeSharedWith) {
......@@ -304,18 +333,22 @@ export default {
inviteButtonText: s__('InviteMembersModal|Invite'),
cancelButtonText: s__('InviteMembersModal|Cancel'),
headerCloseLabel: s__('InviteMembersModal|Close invite team members'),
areasOfFocusLabel: s__(
'InviteMembersModal|What would you like new member(s) to focus on? (optional)',
),
},
membersTokenSelectLabelId: 'invite-members-input',
};
</script>
<template>
<gl-modal
ref="modal"
:modal-id="modalId"
size="sm"
data-qa-selector="invite_members_modal_content"
:title="$options.labels[inviteeType].modalTitle"
:header-close-label="$options.labels.headerCloseLabel"
@close="resetFields"
@hidden="resetFields"
>
<div>
<p ref="introText">
......@@ -351,7 +384,7 @@ export default {
/>
</gl-form-group>
<label class="gl-font-weight-bold gl-mt-3">{{ $options.labels.accessLevel }}</label>
<label class="gl-mt-3">{{ $options.labels.accessLevel }}</label>
<div class="gl-mt-2 gl-w-half gl-xs-w-full">
<gl-dropdown
class="gl-shadow-none gl-w-full"
......@@ -381,7 +414,7 @@ export default {
</gl-sprintf>
</div>
<label class="gl-font-weight-bold gl-mt-5 gl-display-block" for="expires_at">{{
<label class="gl-mt-5 gl-display-block" for="expires_at">{{
$options.labels.accessExpireDate
}}</label>
<div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
......@@ -400,6 +433,16 @@ export default {
</template>
</gl-datepicker>
</div>
<div v-if="areasOfFocusEnabled">
<label class="gl-mt-5">
{{ $options.labels.areasOfFocusLabel }}
</label>
<gl-form-checkbox-group
v-model="selectedAreasOfFocus"
:options="areasOfFocusOptions"
data-testid="area-of-focus-checks"
/>
</div>
</div>
<template #modal-footer>
......
......@@ -3,6 +3,11 @@ import { __ } from '~/locale';
export const SEARCH_DELAY = 200;
export const INVITE_MEMBERS_IN_COMMENT = 'invite_members_in_comment';
export const MEMBER_AREAS_OF_FOCUS = {
name: 'member_areas_of_focus',
view: 'view',
submit: 'submit',
};
export const GROUP_FILTERS = {
ALL: 'all',
......
......@@ -23,6 +23,8 @@ export default function initInviteMembersModal() {
defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10),
groupSelectFilter: el.dataset.groupsFilter,
groupSelectParentId: parseInt(el.dataset.parentId, 10),
areasOfFocusOptions: JSON.parse(el.dataset.areasOfFocusOptions),
noSelectionAreasOfFocus: JSON.parse(el.dataset.noSelectionAreasOfFocus),
},
}),
});
......
......@@ -39,4 +39,43 @@ module InviteMembersHelper
{}
end
end
def common_invite_modal_dataset(source)
dataset = {
id: source.id,
name: source.name,
default_access_level: Gitlab::Access::GUEST
}
experiment(:member_areas_of_focus, user: current_user) do |e|
e.publish_to_database
e.control { dataset.merge!(areas_of_focus_options: [], no_selection_areas_of_focus: []) }
e.candidate { dataset.merge!(areas_of_focus_options: member_areas_of_focus_options.to_json, no_selection_areas_of_focus: ['no_selection']) }
end
dataset
end
private
def member_areas_of_focus_options
[
{
value: 'Contribute to the codebase', text: s_('InviteMembersModal|Contribute to the codebase')
},
{
value: 'Collaborate on open issues and merge requests', text: s_('InviteMembersModal|Collaborate on open issues and merge requests')
},
{
value: 'Configure CI/CD', text: s_('InviteMembersModal|Configure CI/CD')
},
{
value: 'Configure security features', text: s_('InviteMembersModal|Configure security features')
},
{
value: 'Other', text: s_('InviteMembersModal|Other')
}
]
end
end
- return unless can_manage_members?(group)
.js-invite-members-modal{ data: { id: group.id,
name: group.name,
is_project: 'false',
.js-invite-members-modal{ data: { is_project: 'false',
access_levels: GroupMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') }.merge(group_select_data(group)) }
help_link: help_page_url('user/permissions') }.merge(group_select_data(group)).merge(common_invite_modal_dataset(group)) }
- return unless can_import_members?
.js-invite-members-modal{ data: { id: project.id,
name: project.name,
is_project: 'true',
.js-invite-members-modal{ data: { is_project: 'true',
access_levels: ProjectMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') } }
help_link: help_page_url('user/permissions') }.merge(common_invite_modal_dataset(project)) }
---
name: member_areas_of_focus
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65273
rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/406
milestone: '14.2'
type: experiment
group: group::expansion
default_enabled: false
......@@ -18032,6 +18032,18 @@ msgstr ""
msgid "InviteMembersModal|Close invite team members"
msgstr ""
msgid "InviteMembersModal|Collaborate on open issues and merge requests"
msgstr ""
msgid "InviteMembersModal|Configure CI/CD"
msgstr ""
msgid "InviteMembersModal|Configure security features"
msgstr ""
msgid "InviteMembersModal|Contribute to the codebase"
msgstr ""
msgid "InviteMembersModal|GitLab member or email address"
msgstr ""
......@@ -18047,6 +18059,9 @@ msgstr ""
msgid "InviteMembersModal|Members were successfully added"
msgstr ""
msgid "InviteMembersModal|Other"
msgstr ""
msgid "InviteMembersModal|Search for a group to invite"
msgstr ""
......@@ -18062,6 +18077,9 @@ msgstr ""
msgid "InviteMembersModal|Something went wrong"
msgstr ""
msgid "InviteMembersModal|What would you like new member(s) to focus on? (optional)"
msgstr ""
msgid "InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} group."
msgstr ""
......
......@@ -84,6 +84,33 @@ RSpec.describe 'Groups > Members > Manage members' do
property: 'existing_user',
user: user1
)
expect_no_snowplow_event(
category: 'Members::CreateService',
action: 'area_of_focus'
)
end
it 'adds a user to group with area_of_focus', :js, :snowplow, :aggregate_failures do
stub_experiments(member_areas_of_focus: :candidate)
group.add_owner(user1)
visit group_group_members_path(group)
invite_member(user2.name, role: 'Reporter', area_of_focus: true)
wait_for_requests
expect_snowplow_event(
category: 'Members::CreateService',
action: 'area_of_focus',
label: 'Contribute to the codebase',
property: group.members.last.id.to_s
)
expect_snowplow_event(
category: 'Members::CreateService',
action: 'area_of_focus',
label: 'Collaborate on open issues and merge requests',
property: group.members.last.id.to_s
)
end
it 'do not disclose email addresses', :js do
......@@ -193,9 +220,36 @@ RSpec.describe 'Groups > Members > Manage members' do
property: 'net_new_user',
user: user1
)
expect_no_snowplow_event(
category: 'Members::CreateService',
action: 'area_of_focus'
)
end
end
it 'invite user to group with area_of_focus', :js, :snowplow, :aggregate_failures do
stub_experiments(member_areas_of_focus: :candidate)
group.add_owner(user1)
visit group_group_members_path(group)
invite_member('test@example.com', role: 'Reporter', area_of_focus: true)
wait_for_requests
expect_snowplow_event(
category: 'Members::InviteService',
action: 'area_of_focus',
label: 'Contribute to the codebase',
property: group.members.last.id.to_s
)
expect_snowplow_event(
category: 'Members::InviteService',
action: 'area_of_focus',
label: 'Collaborate on open issues and merge requests',
property: group.members.last.id.to_s
)
end
context 'when user is a guest' do
before do
group.add_guest(user1)
......
......@@ -14,6 +14,56 @@ RSpec.describe InviteMembersHelper do
helper.extend(Gitlab::Experimentation::ControllerConcern)
end
describe '#common_invite_modal_dataset' do
context 'when member_areas_of_focus is enabled', :experiment do
context 'with control experience' do
before do
stub_experiments(member_areas_of_focus: :control)
end
it 'has expected attributes' do
attributes = {
areas_of_focus_options: [],
no_selection_areas_of_focus: []
}
expect(helper.common_invite_modal_dataset(project)).to include(attributes)
end
end
context 'with candidate experience' do
before do
stub_experiments(member_areas_of_focus: :candidate)
end
it 'has expected attributes', :aggregate_failures do
output = helper.common_invite_modal_dataset(project)
expect(output[:no_selection_areas_of_focus]).to eq ['no_selection']
expect(Gitlab::Json.parse(output[:areas_of_focus_options]).first['value']).to eq 'Contribute to the codebase'
end
end
end
context 'when member_areas_of_focus is disabled' do
before do
stub_feature_flags(member_areas_of_focus: false)
end
it 'has expected attributes' do
attributes = {
id: project.id,
name: project.name,
default_access_level: Gitlab::Access::GUEST,
areas_of_focus_options: [],
no_selection_areas_of_focus: []
}
expect(helper.common_invite_modal_dataset(project)).to match(attributes)
end
end
end
context 'with project' do
before do
allow(helper).to receive(:current_user) { owner }
......
......@@ -5,7 +5,7 @@ module Spec
module Helpers
module Features
module InviteMembersModalHelper
def invite_member(name, role: 'Guest', expires_at: nil)
def invite_member(name, role: 'Guest', expires_at: nil, area_of_focus: false)
click_on 'Invite members'
page.within '#invite-members-modal' do
......@@ -14,6 +14,7 @@ module Spec
wait_for_requests
click_button name
choose_options(role, expires_at)
choose_area_of_focus if area_of_focus
click_button 'Invite'
......@@ -41,7 +42,14 @@ module Spec
click_button role
end
fill_in 'YYYY-MM-DD', with: expires_at.try(:strftime, '%Y-%m-%d')
fill_in 'YYYY-MM-DD', with: expires_at.strftime('%Y-%m-%d') if expires_at
end
def choose_area_of_focus
page.within '[data-testid="area-of-focus-checks"]' do
check 'Contribute to the codebase'
check 'Collaborate on open issues and merge requests'
end
end
end
end
......
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