Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
3b4faba7
Commit
3b4faba7
authored
Mar 16, 2021
by
Doug Stull
Committed by
Phil Hughes
Mar 16, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add invite member link to comments
- guage interest
parent
a50d1713
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
350 additions
and
23 deletions
+350
-23
app/assets/javascripts/invite_members/components/invite_members_modal.vue
...cripts/invite_members/components/invite_members_modal.vue
+12
-1
app/assets/javascripts/invite_members/components/invite_members_trigger.vue
...ipts/invite_members/components/invite_members_trigger.vue
+21
-1
app/assets/javascripts/invite_members/constants.js
app/assets/javascripts/invite_members/constants.js
+2
-0
app/assets/javascripts/pages/projects/issues/show.js
app/assets/javascripts/pages/projects/issues/show.js
+2
-0
app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
.../pages/projects/merge_requests/init_merge_request_show.js
+2
-0
app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
...ts/javascripts/vue_shared/components/markdown/toolbar.vue
+21
-3
app/controllers/projects/issues_controller.rb
app/controllers/projects/issues_controller.rb
+9
-0
app/controllers/projects/merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+9
-0
app/views/projects/issues/show.html.haml
app/views/projects/issues/show.html.haml
+1
-0
app/views/projects/merge_requests/show.html.haml
app/views/projects/merge_requests/show.html.haml
+3
-0
app/views/shared/issuable/_invite_members_trigger.html.haml
app/views/shared/issuable/_invite_members_trigger.html.haml
+8
-0
config/feature_flags/experiment/invite_members_in_comment.yml
...ig/feature_flags/experiment/invite_members_in_comment.yml
+8
-0
locale/gitlab.pot
locale/gitlab.pot
+3
-0
spec/controllers/projects/issues_controller_spec.rb
spec/controllers/projects/issues_controller_spec.rb
+26
-0
spec/controllers/projects/merge_requests_controller_spec.rb
spec/controllers/projects/merge_requests_controller_spec.rb
+26
-0
spec/features/issues/user_invites_from_a_comment_spec.rb
spec/features/issues/user_invites_from_a_comment_spec.rb
+25
-0
spec/features/merge_request/user_invites_from_a_comment_spec.rb
...eatures/merge_request/user_invites_from_a_comment_spec.rb
+25
-0
spec/frontend/invite_members/components/invite_members_modal_spec.js
...nd/invite_members/components/invite_members_modal_spec.js
+46
-0
spec/frontend/invite_members/components/invite_members_trigger_spec.js
.../invite_members/components/invite_members_trigger_spec.js
+49
-6
spec/frontend/vue_shared/components/markdown/toolbar_spec.js
spec/frontend/vue_shared/components/markdown/toolbar_spec.js
+52
-12
No files found.
app/assets/javascripts/invite_members/components/invite_members_modal.vue
View file @
3b4faba7
...
@@ -11,10 +11,12 @@ import {
...
@@ -11,10 +11,12 @@ import {
}
from
'
@gitlab/ui
'
;
}
from
'
@gitlab/ui
'
;
import
{
partition
,
isString
}
from
'
lodash
'
;
import
{
partition
,
isString
}
from
'
lodash
'
;
import
Api
from
'
~/api
'
;
import
Api
from
'
~/api
'
;
import
ExperimentTracking
from
'
~/experimentation/experiment_tracking
'
;
import
GroupSelect
from
'
~/invite_members/components/group_select.vue
'
;
import
GroupSelect
from
'
~/invite_members/components/group_select.vue
'
;
import
MembersTokenSelect
from
'
~/invite_members/components/members_token_select.vue
'
;
import
MembersTokenSelect
from
'
~/invite_members/components/members_token_select.vue
'
;
import
{
BV_SHOW_MODAL
,
BV_HIDE_MODAL
}
from
'
~/lib/utils/constants
'
;
import
{
BV_SHOW_MODAL
,
BV_HIDE_MODAL
}
from
'
~/lib/utils/constants
'
;
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
import
{
s__
,
sprintf
}
from
'
~/locale
'
;
import
{
INVITE_MEMBERS_IN_COMMENT
}
from
'
../constants
'
;
import
eventHub
from
'
../event_hub
'
;
import
eventHub
from
'
../event_hub
'
;
export
default
{
export
default
{
...
@@ -122,8 +124,9 @@ export default {
...
@@ -122,8 +124,9 @@ export default {
usersToAddById
.
map
((
user
)
=>
user
.
id
).
join
(
'
,
'
),
usersToAddById
.
map
((
user
)
=>
user
.
id
).
join
(
'
,
'
),
];
];
},
},
openModal
({
inviteeType
})
{
openModal
({
inviteeType
,
source
})
{
this
.
inviteeType
=
inviteeType
;
this
.
inviteeType
=
inviteeType
;
this
.
source
=
source
;
this
.
$root
.
$emit
(
BV_SHOW_MODAL
,
this
.
modalId
);
this
.
$root
.
$emit
(
BV_SHOW_MODAL
,
this
.
modalId
);
},
},
...
@@ -138,6 +141,12 @@ export default {
...
@@ -138,6 +141,12 @@ export default {
}
}
this
.
closeModal
();
this
.
closeModal
();
},
},
trackInvite
()
{
if
(
this
.
source
===
INVITE_MEMBERS_IN_COMMENT
)
{
const
tracking
=
new
ExperimentTracking
(
INVITE_MEMBERS_IN_COMMENT
);
tracking
.
event
(
'
comment_invite_success
'
);
}
},
cancelInvite
()
{
cancelInvite
()
{
this
.
selectedAccessLevel
=
this
.
defaultAccessLevel
;
this
.
selectedAccessLevel
=
this
.
defaultAccessLevel
;
this
.
selectedDate
=
undefined
;
this
.
selectedDate
=
undefined
;
...
@@ -177,6 +186,8 @@ export default {
...
@@ -177,6 +186,8 @@ export default {
promises
.
push
(
apiAddByUserId
(
this
.
id
,
this
.
addByUserIdPostData
(
usersToAddById
)));
promises
.
push
(
apiAddByUserId
(
this
.
id
,
this
.
addByUserIdPostData
(
usersToAddById
)));
}
}
this
.
trackInvite
();
Promise
.
all
(
promises
).
then
(
this
.
showToastMessageSuccess
).
catch
(
this
.
showToastMessageError
);
Promise
.
all
(
promises
).
then
(
this
.
showToastMessageSuccess
).
catch
(
this
.
showToastMessageError
);
},
},
inviteByEmailPostData
(
usersToInviteByEmail
)
{
inviteByEmailPostData
(
usersToInviteByEmail
)
{
...
...
app/assets/javascripts/invite_members/components/invite_members_trigger.vue
View file @
3b4faba7
<
script
>
<
script
>
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
ExperimentTracking
from
'
~/experimentation/experiment_tracking
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
s__
}
from
'
~/locale
'
;
import
eventHub
from
'
../event_hub
'
;
import
eventHub
from
'
../event_hub
'
;
...
@@ -26,10 +27,29 @@ export default {
...
@@ -26,10 +27,29 @@ export default {
required
:
false
,
required
:
false
,
default
:
undefined
,
default
:
undefined
,
},
},
triggerSource
:
{
type
:
String
,
required
:
false
,
default
:
'
unknown
'
,
},
trackExperiment
:
{
type
:
String
,
required
:
false
,
default
:
undefined
,
},
},
mounted
()
{
this
.
trackExperimentOnShow
();
},
},
methods
:
{
methods
:
{
openModal
()
{
openModal
()
{
eventHub
.
$emit
(
'
openModal
'
,
{
inviteeType
:
'
members
'
});
eventHub
.
$emit
(
'
openModal
'
,
{
inviteeType
:
'
members
'
,
source
:
this
.
triggerSource
});
},
trackExperimentOnShow
()
{
if
(
this
.
trackExperiment
)
{
const
tracking
=
new
ExperimentTracking
(
this
.
trackExperiment
);
tracking
.
event
(
'
comment_invite_shown
'
);
}
},
},
},
},
};
};
...
...
app/assets/javascripts/invite_members/constants.js
View file @
3b4faba7
export
const
SEARCH_DELAY
=
200
;
export
const
SEARCH_DELAY
=
200
;
export
const
INVITE_MEMBERS_IN_COMMENT
=
'
invite_members_in_comment
'
;
app/assets/javascripts/pages/projects/issues/show.js
View file @
3b4faba7
...
@@ -3,6 +3,7 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
...
@@ -3,6 +3,7 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import
initIssuableSidebar
from
'
~/init_issuable_sidebar
'
;
import
initIssuableSidebar
from
'
~/init_issuable_sidebar
'
;
import
initInviteMemberModal
from
'
~/invite_member/init_invite_member_modal
'
;
import
initInviteMemberModal
from
'
~/invite_member/init_invite_member_modal
'
;
import
initInviteMemberTrigger
from
'
~/invite_member/init_invite_member_trigger
'
;
import
initInviteMemberTrigger
from
'
~/invite_member/init_invite_member_trigger
'
;
import
initInviteMembersModal
from
'
~/invite_members/init_invite_members_modal
'
;
import
{
IssuableType
}
from
'
~/issuable_show/constants
'
;
import
{
IssuableType
}
from
'
~/issuable_show/constants
'
;
import
Issue
from
'
~/issue
'
;
import
Issue
from
'
~/issue
'
;
import
'
~/notes/index
'
;
import
'
~/notes/index
'
;
...
@@ -34,6 +35,7 @@ export default function initShowIssue() {
...
@@ -34,6 +35,7 @@ export default function initShowIssue() {
initIssueHeaderActions
(
store
);
initIssueHeaderActions
(
store
);
initSentryErrorStackTraceApp
();
initSentryErrorStackTraceApp
();
initRelatedMergeRequestsApp
();
initRelatedMergeRequestsApp
();
initInviteMembersModal
();
import
(
/* webpackChunkName: 'design_management' */
'
~/design_management
'
)
import
(
/* webpackChunkName: 'design_management' */
'
~/design_management
'
)
.
then
((
module
)
=>
module
.
default
())
.
then
((
module
)
=>
module
.
default
())
...
...
app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
View file @
3b4faba7
...
@@ -5,6 +5,7 @@ import initPipelines from '~/commit/pipelines/pipelines_bundle';
...
@@ -5,6 +5,7 @@ import initPipelines from '~/commit/pipelines/pipelines_bundle';
import
initIssuableSidebar
from
'
~/init_issuable_sidebar
'
;
import
initIssuableSidebar
from
'
~/init_issuable_sidebar
'
;
import
initInviteMemberModal
from
'
~/invite_member/init_invite_member_modal
'
;
import
initInviteMemberModal
from
'
~/invite_member/init_invite_member_modal
'
;
import
initInviteMemberTrigger
from
'
~/invite_member/init_invite_member_trigger
'
;
import
initInviteMemberTrigger
from
'
~/invite_member/init_invite_member_trigger
'
;
import
initInviteMembersModal
from
'
~/invite_members/init_invite_members_modal
'
;
import
{
handleLocationHash
}
from
'
~/lib/utils/common_utils
'
;
import
{
handleLocationHash
}
from
'
~/lib/utils/common_utils
'
;
import
StatusBox
from
'
~/merge_request/components/status_box.vue
'
;
import
StatusBox
from
'
~/merge_request/components/status_box.vue
'
;
import
initSourcegraph
from
'
~/sourcegraph
'
;
import
initSourcegraph
from
'
~/sourcegraph
'
;
...
@@ -20,6 +21,7 @@ export default function initMergeRequestShow() {
...
@@ -20,6 +21,7 @@ export default function initMergeRequestShow() {
loadAwardsHandler
();
loadAwardsHandler
();
initInviteMemberModal
();
initInviteMemberModal
();
initInviteMemberTrigger
();
initInviteMemberTrigger
();
initInviteMembersModal
();
const
el
=
document
.
querySelector
(
'
.js-mr-status-box
'
);
const
el
=
document
.
querySelector
(
'
.js-mr-status-box
'
);
// eslint-disable-next-line no-new
// eslint-disable-next-line no-new
...
...
app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
View file @
3b4faba7
<
script
>
<
script
>
import
{
GlButton
,
GlLink
,
GlLoadingIcon
,
GlSprintf
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlLink
,
GlLoadingIcon
,
GlSprintf
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
isExperimentVariant
}
from
'
~/experimentation/utils
'
;
import
InviteMembersTrigger
from
'
~/invite_members/components/invite_members_trigger.vue
'
;
import
{
INVITE_MEMBERS_IN_COMMENT
}
from
'
~/invite_members/constants
'
;
export
default
{
export
default
{
inviteMembersInComment
:
INVITE_MEMBERS_IN_COMMENT
,
components
:
{
components
:
{
GlButton
,
GlButton
,
GlLink
,
GlLink
,
GlLoadingIcon
,
GlLoadingIcon
,
GlSprintf
,
GlSprintf
,
GlIcon
,
GlIcon
,
InviteMembersTrigger
,
},
},
props
:
{
props
:
{
markdownDocsPath
:
{
markdownDocsPath
:
{
...
@@ -29,6 +34,9 @@ export default {
...
@@ -29,6 +34,9 @@ export default {
hasQuickActionsDocsPath
()
{
hasQuickActionsDocsPath
()
{
return
this
.
quickActionsDocsPath
!==
''
;
return
this
.
quickActionsDocsPath
!==
''
;
},
},
inviteCommentEnabled
()
{
return
isExperimentVariant
(
INVITE_MEMBERS_IN_COMMENT
,
'
invite_member_link
'
);
},
},
},
};
};
</
script
>
</
script
>
...
@@ -37,9 +45,9 @@ export default {
...
@@ -37,9 +45,9 @@ export default {
<div
class=
"comment-toolbar clearfix"
>
<div
class=
"comment-toolbar clearfix"
>
<div
class=
"toolbar-text"
>
<div
class=
"toolbar-text"
>
<template
v-if=
"!hasQuickActionsDocsPath && markdownDocsPath"
>
<template
v-if=
"!hasQuickActionsDocsPath && markdownDocsPath"
>
<gl-link
:href=
"markdownDocsPath"
target=
"_blank"
>
{{
<gl-link
:href=
"markdownDocsPath"
target=
"_blank"
>
__
(
'
Markdown is supported
'
)
{{
__
(
'
Markdown is supported
'
)
}}
}}
</gl-link>
</gl-link>
</
template
>
</
template
>
<
template
v-if=
"hasQuickActionsDocsPath && markdownDocsPath"
>
<
template
v-if=
"hasQuickActionsDocsPath && markdownDocsPath"
>
<gl-sprintf
<gl-sprintf
...
@@ -59,6 +67,16 @@ export default {
...
@@ -59,6 +67,16 @@ export default {
</template>
</template>
</div>
</div>
<span
v-if=
"canAttachFile"
class=
"uploading-container"
>
<span
v-if=
"canAttachFile"
class=
"uploading-container"
>
<invite-members-trigger
v-if=
"inviteCommentEnabled"
classes=
"gl-mr-3 gl-vertical-align-text-bottom"
:display-text=
"s__('InviteMember|Invite Member')"
icon=
"assignee"
variant=
"link"
:track-experiment=
"$options.inviteMembersInComment"
:trigger-source=
"$options.inviteMembersInComment"
data-track-event=
"comment_invite_click"
/>
<span
class=
"uploading-progress-container hide"
>
<span
class=
"uploading-progress-container hide"
>
<gl-icon
name=
"media"
/>
<gl-icon
name=
"media"
/>
<span
class=
"attaching-file-message"
></span>
<span
class=
"attaching-file-message"
></span>
...
...
app/controllers/projects/issues_controller.rb
View file @
3b4faba7
...
@@ -55,6 +55,15 @@ class Projects::IssuesController < Projects::ApplicationController
...
@@ -55,6 +55,15 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag
(
:confidential_notes
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:confidential_notes
,
@project
,
default_enabled: :yaml
)
record_experiment_user
(
:invite_members_version_b
)
record_experiment_user
(
:invite_members_version_b
)
experiment
(
:invite_members_in_comment
,
namespace:
@project
.
root_ancestor
)
do
|
experiment_instance
|
experiment_instance
.
exclude!
unless
helpers
.
can_import_members?
experiment_instance
.
use
{}
experiment_instance
.
try
(
:invite_member_link
)
{}
experiment_instance
.
track
(
:view
,
property:
@project
.
root_ancestor
.
id
.
to_s
)
end
end
end
around_action
:allow_gitaly_ref_name_caching
,
only:
[
:discussions
]
around_action
:allow_gitaly_ref_name_caching
,
only:
[
:discussions
]
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
3b4faba7
...
@@ -45,6 +45,15 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
...
@@ -45,6 +45,15 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag
(
:new_pipelines_table
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:new_pipelines_table
,
@project
,
default_enabled: :yaml
)
record_experiment_user
(
:invite_members_version_b
)
record_experiment_user
(
:invite_members_version_b
)
experiment
(
:invite_members_in_comment
,
namespace:
@project
.
root_ancestor
)
do
|
experiment_instance
|
experiment_instance
.
exclude!
unless
helpers
.
can_import_members?
experiment_instance
.
use
{}
experiment_instance
.
try
(
:invite_member_link
)
{}
experiment_instance
.
track
(
:view
,
property:
@project
.
root_ancestor
.
id
.
to_s
)
end
end
end
before_action
do
before_action
do
...
...
app/views/projects/issues/show.html.haml
View file @
3b4faba7
...
@@ -4,3 +4,4 @@
...
@@ -4,3 +4,4 @@
-
page_title
"
#{
@issue
.
title
}
(
#{
@issue
.
to_reference
}
)"
,
_
(
"Issues"
)
-
page_title
"
#{
@issue
.
title
}
(
#{
@issue
.
to_reference
}
)"
,
_
(
"Issues"
)
=
render
'projects/issuable/show'
,
issuable:
@issue
=
render
'projects/issuable/show'
,
issuable:
@issue
=
render
'shared/issuable/invite_members_trigger'
,
project:
@project
app/views/projects/merge_requests/show.html.haml
View file @
3b4faba7
...
@@ -108,3 +108,6 @@
...
@@ -108,3 +108,6 @@
=
render
"projects/commit/change"
,
type:
'cherry-pick'
,
commit:
@merge_request
.
merge_commit
=
render
"projects/commit/change"
,
type:
'cherry-pick'
,
commit:
@merge_request
.
merge_commit
#js-review-bar
#js-review-bar
=
render
'shared/issuable/invite_members_trigger'
,
project:
@project
app/views/shared/issuable/_invite_members_trigger.html.haml
0 → 100644
View file @
3b4faba7
-
return
unless
can_import_members?
.js-invite-members-modal
{
data:
{
id:
project
.
id
,
name:
project
.
name
,
is_project:
'true'
,
access_levels:
ProjectMember
.
access_level_roles
.
to_json
,
default_access_level:
Gitlab
::
Access
::
GUEST
,
help_link:
help_page_url
(
'user/permissions'
)
}
}
config/feature_flags/experiment/invite_members_in_comment.yml
0 → 100644
View file @
3b4faba7
---
name
:
invite_members_in_comment
introduced_by_url
:
'
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51400'
rollout_issue_url
:
'
https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/300'
milestone
:
'
13.10'
type
:
experiment
group
:
group::expansion
default_enabled
:
false
locale/gitlab.pot
View file @
3b4faba7
...
@@ -16875,6 +16875,9 @@ msgstr ""
...
@@ -16875,6 +16875,9 @@ msgstr ""
msgid "InviteMember|Don't worry, you can always invite teammates later"
msgid "InviteMember|Don't worry, you can always invite teammates later"
msgstr ""
msgstr ""
msgid "InviteMember|Invite Member"
msgstr ""
msgid "InviteMember|Invite Members (optional)"
msgid "InviteMember|Invite Members (optional)"
msgstr ""
msgstr ""
...
...
spec/controllers/projects/issues_controller_spec.rb
View file @
3b4faba7
...
@@ -209,6 +209,32 @@ RSpec.describe Projects::IssuesController do
...
@@ -209,6 +209,32 @@ RSpec.describe Projects::IssuesController do
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
json_response
[
'issue_email_participants'
]).
to
contain_exactly
({
"email"
=>
participants
[
0
].
email
},
{
"email"
=>
participants
[
1
].
email
})
expect
(
json_response
[
'issue_email_participants'
]).
to
contain_exactly
({
"email"
=>
participants
[
0
].
email
},
{
"email"
=>
participants
[
1
].
email
})
end
end
context
'with the invite_members_in_comment experiment'
,
:experiment
do
context
'when user can invite'
do
before
do
stub_experiments
(
invite_members_in_comment: :invite_member_link
)
project
.
add_maintainer
(
user
)
end
it
'assigns the candidate experience and tracks the event'
do
expect
(
experiment
(
:invite_member_link
)).
to
track
(
:view
,
property:
project
.
root_ancestor
.
id
.
to_s
)
.
on_any_instance
.
for
(
:invite_member_link
)
.
with_context
(
namespace:
project
.
root_ancestor
)
get
:show
,
params:
{
namespace_id:
project
.
namespace
,
project_id:
project
,
id:
issue
.
iid
}
end
end
context
'when user can not invite'
do
it
'does not track the event'
do
expect
(
experiment
(
:invite_member_link
)).
not_to
track
(
:view
)
get
:show
,
params:
{
namespace_id:
project
.
namespace
,
project_id:
project
,
id:
issue
.
iid
}
end
end
end
end
end
describe
'GET #new'
do
describe
'GET #new'
do
...
...
spec/controllers/projects/merge_requests_controller_spec.rb
View file @
3b4faba7
...
@@ -40,6 +40,32 @@ RSpec.describe Projects::MergeRequestsController do
...
@@ -40,6 +40,32 @@ RSpec.describe Projects::MergeRequestsController do
get
:show
,
params:
params
.
merge
(
extra_params
)
get
:show
,
params:
params
.
merge
(
extra_params
)
end
end
context
'with the invite_members_in_comment experiment'
,
:experiment
do
context
'when user can invite'
do
before
do
stub_experiments
(
invite_members_in_comment: :invite_member_link
)
project
.
add_maintainer
(
user
)
end
it
'assigns the candidate experience and tracks the event'
do
expect
(
experiment
(
:invite_member_link
)).
to
track
(
:view
,
property:
project
.
root_ancestor
.
id
.
to_s
)
.
on_any_instance
.
for
(
:invite_member_link
)
.
with_context
(
namespace:
project
.
root_ancestor
)
go
end
end
context
'when user can not invite'
do
it
'does not track the event'
do
expect
(
experiment
(
:invite_member_link
)).
not_to
track
(
:view
)
go
end
end
end
context
'with view param'
do
context
'with view param'
do
before
do
before
do
go
(
view:
'parallel'
)
go
(
view:
'parallel'
)
...
...
spec/features/issues/user_invites_from_a_comment_spec.rb
0 → 100644
View file @
3b4faba7
# frozen_string_literal: true
require
"spec_helper"
RSpec
.
describe
"User invites from a comment"
,
:js
do
let_it_be
(
:project
)
{
create
(
:project_empty_repo
,
:public
)
}
let_it_be
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let_it_be
(
:user
)
{
project
.
owner
}
before
do
sign_in
(
user
)
end
it
"launches the invite modal from invite link on a comment"
do
stub_experiments
(
invite_members_in_comment: :invite_member_link
)
visit
project_issue_path
(
project
,
issue
)
page
.
within
(
".new-note"
)
do
click_button
'Invite Member'
end
expect
(
page
).
to
have_content
(
"You're inviting members to the"
)
end
end
spec/features/merge_request/user_invites_from_a_comment_spec.rb
0 → 100644
View file @
3b4faba7
# frozen_string_literal: true
require
"spec_helper"
RSpec
.
describe
"User invites from a comment"
,
:js
do
let_it_be
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
let_it_be
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
let_it_be
(
:user
)
{
project
.
owner
}
before
do
sign_in
(
user
)
end
it
"launches the invite modal from invite link on a comment"
do
stub_experiments
(
invite_members_in_comment: :invite_member_link
)
visit
project_merge_request_path
(
project
,
merge_request
)
page
.
within
(
".new-note"
)
do
click_button
'Invite Member'
end
expect
(
page
).
to
have_content
(
"You're inviting members to the"
)
end
end
spec/frontend/invite_members/components/invite_members_modal_spec.js
View file @
3b4faba7
...
@@ -3,7 +3,11 @@ import { shallowMount } from '@vue/test-utils';
...
@@ -3,7 +3,11 @@ import { shallowMount } from '@vue/test-utils';
import
{
stubComponent
}
from
'
helpers/stub_component
'
;
import
{
stubComponent
}
from
'
helpers/stub_component
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
Api
from
'
~/api
'
;
import
Api
from
'
~/api
'
;
import
ExperimentTracking
from
'
~/experimentation/experiment_tracking
'
;
import
InviteMembersModal
from
'
~/invite_members/components/invite_members_modal.vue
'
;
import
InviteMembersModal
from
'
~/invite_members/components/invite_members_modal.vue
'
;
import
{
INVITE_MEMBERS_IN_COMMENT
}
from
'
~/invite_members/constants
'
;
jest
.
mock
(
'
~/experimentation/experiment_tracking
'
);
const
id
=
'
1
'
;
const
id
=
'
1
'
;
const
name
=
'
test name
'
;
const
name
=
'
test name
'
;
...
@@ -303,6 +307,7 @@ describe('InviteMembersModal', () => {
...
@@ -303,6 +307,7 @@ describe('InviteMembersModal', () => {
jest
.
spyOn
(
Api
,
'
inviteGroupMembersByEmail
'
).
mockResolvedValue
({
data
:
postData
});
jest
.
spyOn
(
Api
,
'
inviteGroupMembersByEmail
'
).
mockResolvedValue
({
data
:
postData
});
jest
.
spyOn
(
Api
,
'
addGroupMembersByUserId
'
).
mockResolvedValue
({
data
:
postData
});
jest
.
spyOn
(
Api
,
'
addGroupMembersByUserId
'
).
mockResolvedValue
({
data
:
postData
});
jest
.
spyOn
(
wrapper
.
vm
,
'
showToastMessageSuccess
'
);
jest
.
spyOn
(
wrapper
.
vm
,
'
showToastMessageSuccess
'
);
jest
.
spyOn
(
wrapper
.
vm
,
'
trackInvite
'
);
clickInviteButton
();
clickInviteButton
();
});
});
...
@@ -396,5 +401,46 @@ describe('InviteMembersModal', () => {
...
@@ -396,5 +401,46 @@ describe('InviteMembersModal', () => {
});
});
});
});
});
});
describe
(
'
tracking
'
,
()
=>
{
const
postData
=
{
user_id
:
'
1
'
,
access_level
:
defaultAccessLevel
,
expires_at
:
undefined
,
format
:
'
json
'
,
};
beforeEach
(()
=>
{
wrapper
=
createComponent
({
newUsersToInvite
:
[
user3
]
});
wrapper
.
vm
.
$toast
=
{
show
:
jest
.
fn
()
};
jest
.
spyOn
(
Api
,
'
inviteGroupMembersByEmail
'
).
mockResolvedValue
({
data
:
postData
});
});
it
(
'
tracks the invite
'
,
()
=>
{
wrapper
.
vm
.
openModal
({
inviteeType
:
'
members
'
,
source
:
INVITE_MEMBERS_IN_COMMENT
});
clickInviteButton
();
expect
(
ExperimentTracking
).
toHaveBeenCalledWith
(
INVITE_MEMBERS_IN_COMMENT
);
expect
(
ExperimentTracking
.
prototype
.
event
).
toHaveBeenCalledWith
(
'
comment_invite_success
'
);
});
it
(
'
does not track invite for unknown source
'
,
()
=>
{
wrapper
.
vm
.
openModal
({
inviteeType
:
'
members
'
,
source
:
'
unknown
'
});
clickInviteButton
();
expect
(
ExperimentTracking
).
not
.
toHaveBeenCalled
();
});
it
(
'
does not track invite undefined source
'
,
()
=>
{
wrapper
.
vm
.
openModal
({
inviteeType
:
'
members
'
});
clickInviteButton
();
expect
(
ExperimentTracking
).
not
.
toHaveBeenCalled
();
});
});
});
});
});
});
spec/frontend/invite_members/components/invite_members_trigger_spec.js
View file @
3b4faba7
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
GlButton
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
ExperimentTracking
from
'
~/experimentation/experiment_tracking
'
;
import
InviteMembersTrigger
from
'
~/invite_members/components/invite_members_trigger.vue
'
;
import
InviteMembersTrigger
from
'
~/invite_members/components/invite_members_trigger.vue
'
;
import
eventHub
from
'
~/invite_members/event_hub
'
;
jest
.
mock
(
'
~/experimentation/experiment_tracking
'
);
const
displayText
=
'
Invite team members
'
;
const
displayText
=
'
Invite team members
'
;
let
wrapper
;
const
createComponent
=
(
props
=
{})
=>
{
const
createComponent
=
(
props
=
{})
=>
{
return
shallowMount
(
InviteMembersTrigger
,
{
wrapper
=
shallowMount
(
InviteMembersTrigger
,
{
propsData
:
{
propsData
:
{
displayText
,
displayText
,
...
props
,
...
props
,
...
@@ -14,7 +19,7 @@ const createComponent = (props = {}) => {
...
@@ -14,7 +19,7 @@ const createComponent = (props = {}) => {
};
};
describe
(
'
InviteMembersTrigger
'
,
()
=>
{
describe
(
'
InviteMembersTrigger
'
,
()
=>
{
let
wrapper
;
const
findButton
=
()
=>
wrapper
.
findComponent
(
GlButton
)
;
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
...
@@ -22,14 +27,52 @@ describe('InviteMembersTrigger', () => {
...
@@ -22,14 +27,52 @@ describe('InviteMembersTrigger', () => {
});
});
describe
(
'
displayText
'
,
()
=>
{
describe
(
'
displayText
'
,
()
=>
{
const
findButton
=
()
=>
wrapper
.
findComponent
(
GlButton
);
it
(
'
includes the correct displayText for the button
'
,
()
=>
{
createComponent
();
expect
(
findButton
().
text
()).
toBe
(
displayText
);
});
});
describe
(
'
clicking the link
'
,
()
=>
{
let
spy
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
(
);
spy
=
jest
.
spyOn
(
eventHub
,
'
$emit
'
);
});
});
it
(
'
includes the correct displayText for the button
'
,
()
=>
{
it
(
'
emits openModal from an unknown source
'
,
()
=>
{
expect
(
findButton
().
text
()).
toBe
(
displayText
);
createComponent
();
findButton
().
vm
.
$emit
(
'
click
'
);
expect
(
spy
).
toHaveBeenCalledWith
(
'
openModal
'
,
{
inviteeType
:
'
members
'
,
source
:
'
unknown
'
});
});
it
(
'
emits openModal from a named source
'
,
()
=>
{
createComponent
({
triggerSource
:
'
_trigger_source_
'
});
findButton
().
vm
.
$emit
(
'
click
'
);
expect
(
spy
).
toHaveBeenCalledWith
(
'
openModal
'
,
{
inviteeType
:
'
members
'
,
source
:
'
_trigger_source_
'
,
});
});
});
describe
(
'
tracking
'
,
()
=>
{
it
(
'
tracks on mounting
'
,
()
=>
{
createComponent
({
trackExperiment
:
'
_track_experiment_
'
});
expect
(
ExperimentTracking
).
toHaveBeenCalledWith
(
'
_track_experiment_
'
);
expect
(
ExperimentTracking
.
prototype
.
event
).
toHaveBeenCalledWith
(
'
comment_invite_shown
'
);
});
it
(
'
does not track on mounting
'
,
()
=>
{
createComponent
();
expect
(
ExperimentTracking
).
not
.
toHaveBeenCalledWith
(
'
_track_experiment_
'
);
});
});
});
});
});
});
spec/frontend/vue_shared/components/markdown/toolbar_spec.js
View file @
3b4faba7
import
Vue
from
'
vue
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
mountComponent
from
'
helpers/vue_mount_component_helper
'
;
import
{
isExperimentVariant
}
from
'
~/experimentation/utils
'
;
import
toolbar
from
'
~/vue_shared/components/markdown/toolbar.vue
'
;
import
InviteMembersTrigger
from
'
~/invite_members/components/invite_members_trigger.vue
'
;
import
{
INVITE_MEMBERS_IN_COMMENT
}
from
'
~/invite_members/constants
'
;
import
Toolbar
from
'
~/vue_shared/components/markdown/toolbar.vue
'
;
jest
.
mock
(
'
~/experimentation/utils
'
,
()
=>
({
isExperimentVariant
:
jest
.
fn
()
}));
describe
(
'
toolbar
'
,
()
=>
{
describe
(
'
toolbar
'
,
()
=>
{
let
vm
;
let
wrapper
;
const
Toolbar
=
Vue
.
extend
(
toolbar
);
const
props
=
{
const
createMountedWrapper
=
(
props
=
{})
=>
{
markdownDocsPath
:
''
,
wrapper
=
mount
(
Toolbar
,
{
propsData
:
{
markdownDocsPath
:
''
,
...
props
},
stubs
:
{
'
invite-members-trigger
'
:
true
},
});
};
};
afterEach
(()
=>
{
afterEach
(()
=>
{
vm
.
$destroy
();
wrapper
.
destroy
();
isExperimentVariant
.
mockReset
();
});
});
describe
(
'
user can attach file
'
,
()
=>
{
describe
(
'
user can attach file
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
Toolbar
,
props
);
createMountedWrapper
(
);
});
});
it
(
'
should render uploading-container
'
,
()
=>
{
it
(
'
should render uploading-container
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.uploading-container
'
)).
not
.
toBeNull
();
expect
(
wrapper
.
vm
.
$el
.
querySelector
(
'
.uploading-container
'
)).
not
.
toBeNull
();
});
});
});
});
describe
(
'
user cannot attach file
'
,
()
=>
{
describe
(
'
user cannot attach file
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
Toolbar
,
{
...
props
,
canAttachFile
:
false
});
createMountedWrapper
({
canAttachFile
:
false
});
});
});
it
(
'
should not render uploading-container
'
,
()
=>
{
it
(
'
should not render uploading-container
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.uploading-container
'
)).
toBeNull
();
expect
(
wrapper
.
vm
.
$el
.
querySelector
(
'
.uploading-container
'
)).
toBeNull
();
});
});
describe
(
'
user can invite member
'
,
()
=>
{
const
findInviteLink
=
()
=>
wrapper
.
find
(
InviteMembersTrigger
);
beforeEach
(()
=>
{
isExperimentVariant
.
mockReturnValue
(
true
);
createMountedWrapper
();
});
it
(
'
should render the invite members trigger
'
,
()
=>
{
expect
(
findInviteLink
().
exists
()).
toBe
(
true
);
});
it
(
'
should have correct props
'
,
()
=>
{
expect
(
findInviteLink
().
props
().
displayText
).
toBe
(
'
Invite Member
'
);
expect
(
findInviteLink
().
props
().
trackExperiment
).
toBe
(
INVITE_MEMBERS_IN_COMMENT
);
expect
(
findInviteLink
().
props
().
triggerSource
).
toBe
(
INVITE_MEMBERS_IN_COMMENT
);
});
});
describe
(
'
user can not invite member
'
,
()
=>
{
const
findInviteLink
=
()
=>
wrapper
.
find
(
InviteMembersTrigger
);
beforeEach
(()
=>
{
isExperimentVariant
.
mockReturnValue
(
false
);
createMountedWrapper
();
});
it
(
'
should render the invite members trigger
'
,
()
=>
{
expect
(
findInviteLink
().
exists
()).
toBe
(
false
);
});
});
});
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment