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
0
Merge Requests
0
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
Tatuya Kamada
gitlab-ce
Commits
0c350b79
Commit
0c350b79
authored
Jan 20, 2017
by
Jarka Kadlecova
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
address comments
parent
bf708e55
Changes
16
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
141 additions
and
58 deletions
+141
-58
app/models/ability.rb
app/models/ability.rb
+1
-1
app/models/concerns/cache_markdown_field.rb
app/models/concerns/cache_markdown_field.rb
+5
-3
app/models/concerns/mentionable.rb
app/models/concerns/mentionable.rb
+5
-1
app/models/concerns/participable.rb
app/models/concerns/participable.rb
+2
-1
app/models/note.rb
app/models/note.rb
+9
-1
app/services/notes/post_process_service.rb
app/services/notes/post_process_service.rb
+5
-4
app/services/notes/slash_commands_service.rb
app/services/notes/slash_commands_service.rb
+1
-1
app/services/notification_service.rb
app/services/notification_service.rb
+9
-14
app/views/notify/note_personal_snippet_email.html.haml
app/views/notify/note_personal_snippet_email.html.haml
+1
-1
app/views/notify/note_personal_snippet_email.text.erb
app/views/notify/note_personal_snippet_email.text.erb
+8
-0
lib/banzai/filter/user_reference_filter.rb
lib/banzai/filter/user_reference_filter.rb
+1
-0
spec/lib/banzai/filter/user_reference_filter_spec.rb
spec/lib/banzai/filter/user_reference_filter_spec.rb
+3
-0
spec/models/ability_spec.rb
spec/models/ability_spec.rb
+13
-16
spec/models/concerns/mentionable_spec.rb
spec/models/concerns/mentionable_spec.rb
+2
-5
spec/models/note_spec.rb
spec/models/note_spec.rb
+67
-1
spec/services/notes/create_service_spec.rb
spec/services/notes/create_service_spec.rb
+9
-9
No files found.
app/models/ability.rb
View file @
0c350b79
...
@@ -29,7 +29,7 @@ class Ability
...
@@ -29,7 +29,7 @@ class Ability
when
Snippet
::
INTERNAL
,
Snippet
::
PUBLIC
when
Snippet
::
INTERNAL
,
Snippet
::
PUBLIC
users
users
when
Snippet
::
PRIVATE
when
Snippet
::
PRIVATE
users
.
select
{
|
user
|
snippet
.
author
==
user
}
users
.
include?
(
snippet
.
author
)
?
[
snippet
.
author
]
:
[]
end
end
end
end
...
...
app/models/concerns/cache_markdown_field.rb
View file @
0c350b79
...
@@ -51,6 +51,10 @@ module CacheMarkdownField
...
@@ -51,6 +51,10 @@ module CacheMarkdownField
CACHING_CLASSES
.
map
(
&
:constantize
)
CACHING_CLASSES
.
map
(
&
:constantize
)
end
end
def
skip_project_check?
false
end
extend
ActiveSupport
::
Concern
extend
ActiveSupport
::
Concern
included
do
included
do
...
@@ -112,9 +116,7 @@ module CacheMarkdownField
...
@@ -112,9 +116,7 @@ module CacheMarkdownField
invalidation_method
=
"
#{
html_field
}
_invalidated?"
.
to_sym
invalidation_method
=
"
#{
html_field
}
_invalidated?"
.
to_sym
define_method
(
cache_method
)
do
define_method
(
cache_method
)
do
options
=
{
options
=
{
skip_project_check:
skip_project_check?
}
skip_project_check:
is_a?
(
Note
)
&&
for_personal_snippet?
}
html
=
Banzai
::
Renderer
.
cacheless_render_field
(
self
,
markdown_field
,
options
)
html
=
Banzai
::
Renderer
.
cacheless_render_field
(
self
,
markdown_field
,
options
)
__send__
(
"
#{
html_field
}
="
,
html
)
__send__
(
"
#{
html_field
}
="
,
html
)
true
true
...
...
app/models/concerns/mentionable.rb
View file @
0c350b79
...
@@ -52,7 +52,7 @@ module Mentionable
...
@@ -52,7 +52,7 @@ module Mentionable
options
=
options
.
merge
(
options
=
options
.
merge
(
cache_key:
[
self
,
attr
],
cache_key:
[
self
,
attr
],
author:
author
,
author:
author
,
skip_project_check:
is_a?
(
Note
)
&&
for_personal_snippet
?
skip_project_check:
skip_project_check
?
)
)
extractor
.
analyze
(
text
,
options
)
extractor
.
analyze
(
text
,
options
)
...
@@ -125,4 +125,8 @@ module Mentionable
...
@@ -125,4 +125,8 @@ module Mentionable
def
cross_reference_exists?
(
target
)
def
cross_reference_exists?
(
target
)
SystemNoteService
.
cross_reference_exists?
(
target
,
local_reference
)
SystemNoteService
.
cross_reference_exists?
(
target
,
local_reference
)
end
end
def
skip_project_check?
false
end
end
end
app/models/concerns/participable.rb
View file @
0c350b79
...
@@ -96,7 +96,8 @@ module Participable
...
@@ -96,7 +96,8 @@ module Participable
participants
.
merge
(
ext
.
users
)
participants
.
merge
(
ext
.
users
)
if
self
.
is_a?
(
PersonalSnippet
)
case
self
when
PersonalSnippet
Ability
.
users_that_can_read_personal_snippet
(
participants
.
to_a
,
self
)
Ability
.
users_that_can_read_personal_snippet
(
participants
.
to_a
,
self
)
else
else
Ability
.
users_that_can_read_project
(
participants
.
to_a
,
project
)
Ability
.
users_that_can_read_project
(
participants
.
to_a
,
project
)
...
...
app/models/note.rb
View file @
0c350b79
...
@@ -167,7 +167,11 @@ class Note < ActiveRecord::Base
...
@@ -167,7 +167,11 @@ class Note < ActiveRecord::Base
end
end
def
for_personal_snippet?
def
for_personal_snippet?
noteable_type
==
"Snippet"
&&
noteable
.
type
==
'PersonalSnippet'
noteable
.
is_a?
(
PersonalSnippet
)
end
def
skip_project_check?
for_personal_snippet?
end
end
# override to return commits, which are not active record
# override to return commits, which are not active record
...
@@ -225,6 +229,10 @@ class Note < ActiveRecord::Base
...
@@ -225,6 +229,10 @@ class Note < ActiveRecord::Base
note
.
match
(
Banzai
::
Filter
::
EmojiFilter
.
emoji_pattern
)[
1
]
note
.
match
(
Banzai
::
Filter
::
EmojiFilter
.
emoji_pattern
)[
1
]
end
end
def
to_ability_name
for_personal_snippet?
?
'personal_snippet'
:
noteable_type
.
underscore
end
private
private
def
keep_around_commit
def
keep_around_commit
...
...
app/services/notes/post_process_service.rb
View file @
0c350b79
...
@@ -10,10 +10,11 @@ module Notes
...
@@ -10,10 +10,11 @@ module Notes
# Skip system notes, like status changes and cross-references and awards
# Skip system notes, like status changes and cross-references and awards
unless
@note
.
system?
unless
@note
.
system?
EventCreateService
.
new
.
leave_note
(
@note
,
@note
.
author
)
EventCreateService
.
new
.
leave_note
(
@note
,
@note
.
author
)
unless
@note
.
for_personal_snippet?
@note
.
create_cross_references!
return
if
@note
.
for_personal_snippet?
execute_note_hooks
end
@note
.
create_cross_references!
execute_note_hooks
end
end
end
end
...
...
app/services/notes/slash_commands_service.rb
View file @
0c350b79
...
@@ -12,7 +12,7 @@ module Notes
...
@@ -12,7 +12,7 @@ module Notes
def
self
.
supported?
(
note
,
current_user
)
def
self
.
supported?
(
note
,
current_user
)
noteable_update_service
(
note
)
&&
noteable_update_service
(
note
)
&&
current_user
&&
current_user
&&
current_user
.
can?
(
:"update_
#{
note
.
noteable_type
.
underscor
e
}
"
,
note
.
noteable
)
current_user
.
can?
(
:"update_
#{
note
.
to_ability_nam
e
}
"
,
note
.
noteable
)
end
end
def
supported?
(
note
)
def
supported?
(
note
)
...
...
app/services/notification_service.rb
View file @
0c350b79
...
@@ -179,14 +179,14 @@ class NotificationService
...
@@ -179,14 +179,14 @@ class NotificationService
mentioned_users
=
note
.
mentioned_users
mentioned_users
=
note
.
mentioned_users
if
note
.
for_personal_snippet?
ability
,
subject
=
if
note
.
for_personal_snippet?
mentioned_users
.
select!
do
|
user
|
[
:read_personal_snippet
,
note
.
noteable
]
user
.
can?
(
:read_personal_snippet
,
note
.
noteable
)
else
end
[
:read_project
,
note
.
project
]
else
end
mentioned_users
.
select!
do
|
user
|
user
.
can?
(
:read_project
,
note
.
project
)
mentioned_users
.
select!
do
|
user
|
end
user
.
can?
(
ability
,
subject
)
end
end
# Add all users participating in the thread (author, assignee, comment authors)
# Add all users participating in the thread (author, assignee, comment authors)
...
@@ -220,12 +220,7 @@ class NotificationService
...
@@ -220,12 +220,7 @@ class NotificationService
recipients
.
delete
(
note
.
author
)
recipients
.
delete
(
note
.
author
)
recipients
=
recipients
.
uniq
recipients
=
recipients
.
uniq
# build notify method like 'note_commit_email'
notify_method
=
"note_
#{
note
.
to_ability_name
}
_email"
.
to_sym
if
note
.
for_personal_snippet?
notify_method
=
"note_personal_snippet_email"
.
to_sym
else
notify_method
=
"note_
#{
note
.
noteable_type
.
underscore
}
_email"
.
to_sym
end
recipients
.
each
do
|
recipient
|
recipients
.
each
do
|
recipient
|
mailer
.
send
(
notify_method
,
recipient
.
id
,
note
.
id
).
deliver_later
mailer
.
send
(
notify_method
,
recipient
.
id
,
note
.
id
).
deliver_later
...
...
app/views/notify/note_personal_snippet_email.html.haml
View file @
0c350b79
render 'note_message'
=
render
'note_message'
app/views/notify/note_personal_snippet_email.text.erb
0 → 100644
View file @
0c350b79
New comment for Snippet
<%=
@snippet
.
id
%>
<%=
url_for
(
snippet_url
(
@snippet
,
anchor:
"note_
#{
@note
.
id
}
"
))
%>
Author:
<%=
@note
.
author_name
%>
<%=
@note
.
note
%>
lib/banzai/filter/user_reference_filter.rb
View file @
0c350b79
...
@@ -28,6 +28,7 @@ module Banzai
...
@@ -28,6 +28,7 @@ module Banzai
ref_pattern
=
User
.
reference_pattern
ref_pattern
=
User
.
reference_pattern
ref_pattern_start
=
/\A
#{
ref_pattern
}
\z/
ref_pattern_start
=
/\A
#{
ref_pattern
}
\z/
nodes
.
each
do
|
node
|
nodes
.
each
do
|
node
|
if
text_node?
(
node
)
if
text_node?
(
node
)
replace_text_when_pattern_matches
(
node
,
ref_pattern
)
do
|
content
|
replace_text_when_pattern_matches
(
node
,
ref_pattern
)
do
|
content
|
...
...
spec/lib/banzai/filter/user_reference_filter_spec.rb
View file @
0c350b79
...
@@ -157,17 +157,20 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
...
@@ -157,17 +157,20 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
it
'does not link a User'
do
it
'does not link a User'
do
doc
=
reference_filter
(
"Hey
#{
reference
}
"
)
doc
=
reference_filter
(
"Hey
#{
reference
}
"
)
expect
(
doc
).
not_to
include
(
'a'
)
expect
(
doc
).
not_to
include
(
'a'
)
end
end
context
'when skip_project_check set to true'
do
context
'when skip_project_check set to true'
do
it
'links to a User'
do
it
'links to a User'
do
doc
=
reference_filter
(
"Hey
#{
reference
}
"
,
skip_project_check:
true
)
doc
=
reference_filter
(
"Hey
#{
reference
}
"
,
skip_project_check:
true
)
expect
(
doc
.
css
(
'a'
).
first
.
attr
(
'href'
)).
to
eq
urls
.
user_url
(
user
)
expect
(
doc
.
css
(
'a'
).
first
.
attr
(
'href'
)).
to
eq
urls
.
user_url
(
user
)
end
end
it
'does not link users using @all reference'
do
it
'does not link users using @all reference'
do
doc
=
reference_filter
(
"Hey
#{
User
.
reference_prefix
}
all"
,
skip_project_check:
true
)
doc
=
reference_filter
(
"Hey
#{
User
.
reference_prefix
}
all"
,
skip_project_check:
true
)
expect
(
doc
).
not_to
include
(
'a'
)
expect
(
doc
).
not_to
include
(
'a'
)
end
end
end
end
...
...
spec/models/ability_spec.rb
View file @
0c350b79
...
@@ -172,32 +172,29 @@ describe Ability, lib: true do
...
@@ -172,32 +172,29 @@ describe Ability, lib: true do
end
end
describe
'.users_that_can_read_personal_snippet'
do
describe
'.users_that_can_read_personal_snippet'
do
subject
{
Ability
.
users_that_can_read_personal_snippet
(
users
,
snippet
)
}
def
users_for_snippet
(
snippet
)
described_class
.
users_that_can_read_personal_snippet
(
users
,
snippet
)
end
let
(
:users
)
{
create_list
(
:user
,
3
)
}
let
(
:users
)
{
create_list
(
:user
,
3
)
}
let
(
:author
)
{
users
[
0
]
}
let
(
:author
)
{
users
[
0
]
}
context
'private snippet
'
do
it
'private snippet is readable only by its author
'
do
let
(
:snippet
)
{
create
(
:personal_snippet
,
:private
,
author:
author
)
}
snippet
=
create
(
:personal_snippet
,
:private
,
author:
author
)
it
'is readable only by its author'
do
expect
(
users_for_snippet
(
snippet
)).
to
match_array
([
author
])
expect
(
subject
).
to
match_array
([
author
])
end
end
end
context
'internal snippet
'
do
it
'internal snippet is readable by all registered users
'
do
let
(
:snippet
)
{
create
(
:personal_snippet
,
:public
,
author:
author
)
}
snippet
=
create
(
:personal_snippet
,
:public
,
author:
author
)
it
'is readable by all registered users'
do
expect
(
users_for_snippet
(
snippet
)).
to
match_array
(
users
)
expect
(
subject
).
to
match_array
(
users
)
end
end
end
context
'public snippet
'
do
it
'public snippet is readable by all users
'
do
let
(
:snippet
)
{
create
(
:personal_snippet
,
:public
,
author:
author
)
}
snippet
=
create
(
:personal_snippet
,
:public
,
author:
author
)
it
'is readable by all users'
do
expect
(
users_for_snippet
(
snippet
)).
to
match_array
(
users
)
expect
(
subject
).
to
match_array
(
users
)
end
end
end
end
end
...
...
spec/models/concerns/mentionable_spec.rb
View file @
0c350b79
...
@@ -35,17 +35,14 @@ describe Issue, "Mentionable" do
...
@@ -35,17 +35,14 @@ describe Issue, "Mentionable" do
subject
{
issue
.
mentioned_users
}
subject
{
issue
.
mentioned_users
}
it
{
is_expected
.
to
include
(
user
)
}
it
{
expect
(
subject
).
to
contain_exactly
(
user
)
}
it
{
is_expected
.
not_to
include
(
user2
)
}
context
'when a note on personal snippet'
do
context
'when a note on personal snippet'
do
let!
(
:note
)
{
create
(
:note_on_personal_snippet
,
note:
"
#{
user
.
to_reference
}
mentioned
#{
user3
.
to_reference
}
"
)
}
let!
(
:note
)
{
create
(
:note_on_personal_snippet
,
note:
"
#{
user
.
to_reference
}
mentioned
#{
user3
.
to_reference
}
"
)
}
subject
{
note
.
mentioned_users
}
subject
{
note
.
mentioned_users
}
it
{
is_expected
.
to
include
(
user
)
}
it
{
expect
(
subject
).
to
contain_exactly
(
user
,
user3
)
}
it
{
is_expected
.
to
include
(
user3
)
}
it
{
is_expected
.
not_to
include
(
user2
)
}
end
end
end
end
...
...
spec/models/note_spec.rb
View file @
0c350b79
...
@@ -59,7 +59,7 @@ describe Note, models: true do
...
@@ -59,7 +59,7 @@ describe Note, models: true do
end
end
context
'when noteable is a personal snippet'
do
context
'when noteable is a personal snippet'
do
subject
{
create
(
:note_on_personal_snippet
)
}
subject
{
build
(
:note_on_personal_snippet
)
}
it
'is valid without project'
do
it
'is valid without project'
do
is_expected
.
to
be_valid
is_expected
.
to
be_valid
...
@@ -321,4 +321,70 @@ describe Note, models: true do
...
@@ -321,4 +321,70 @@ describe Note, models: true do
end
end
end
end
end
end
describe
'#for_personal_snippet?'
do
it
'returns false for a project snippet note'
do
expect
(
build
(
:note_on_project_snippet
).
for_personal_snippet?
).
to
be_falsy
end
it
'returns true for a personal snippet note'
do
expect
(
build
(
:note_on_personal_snippet
).
for_personal_snippet?
).
to
be_truthy
end
end
describe
'#to_ability_name'
do
it
'returns snippet for a project snippet note'
do
expect
(
build
(
:note_on_project_snippet
).
to_ability_name
).
to
eq
(
'snippet'
)
end
it
'returns personal_snippet for a personal snippet note'
do
expect
(
build
(
:note_on_personal_snippet
).
to_ability_name
).
to
eq
(
'personal_snippet'
)
end
it
'returns merge_request for an MR note'
do
expect
(
build
(
:note_on_merge_request
).
to_ability_name
).
to
eq
(
'merge_request'
)
end
it
'returns issue for an issue note'
do
expect
(
build
(
:note_on_issue
).
to_ability_name
).
to
eq
(
'issue'
)
end
it
'returns issue for a commit note'
do
expect
(
build
(
:note_on_commit
).
to_ability_name
).
to
eq
(
'commit'
)
end
end
describe
'#cache_markdown_field'
do
let
(
:html
)
{
'<p>some html</p>'
}
context
'note for a project snippet'
do
let
(
:note
)
{
build
(
:note_on_project_snippet
)
}
before
do
expect
(
Banzai
::
Renderer
).
to
receive
(
:cacheless_render_field
).
with
(
note
,
:note
,
{
skip_project_check:
false
}).
and_return
(
html
)
note
.
save
end
it
'creates a note'
do
expect
(
note
.
note_html
).
to
eq
(
html
)
end
end
context
'note for a personal snippet'
do
let
(
:note
)
{
build
(
:note_on_personal_snippet
)
}
before
do
expect
(
Banzai
::
Renderer
).
to
receive
(
:cacheless_render_field
).
with
(
note
,
:note
,
{
skip_project_check:
true
}).
and_return
(
html
)
note
.
save
end
it
'creates a note'
do
expect
(
note
.
note_html
).
to
eq
(
html
)
end
end
end
end
end
spec/services/notes/create_service_spec.rb
View file @
0c350b79
...
@@ -15,25 +15,25 @@ describe Notes::CreateService, services: true do
...
@@ -15,25 +15,25 @@ describe Notes::CreateService, services: true do
context
"valid params"
do
context
"valid params"
do
it
'returns a valid note'
do
it
'returns a valid note'
do
note
=
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
note
=
described_class
.
new
(
project
,
user
,
opts
).
execute
expect
(
note
).
to
be_valid
expect
(
note
).
to
be_valid
end
end
it
'returns a persisted note'
do
it
'returns a persisted note'
do
note
=
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
note
=
described_class
.
new
(
project
,
user
,
opts
).
execute
expect
(
note
).
to
be_persisted
expect
(
note
).
to
be_persisted
end
end
it
'note has valid content'
do
it
'note has valid content'
do
note
=
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
note
=
described_class
.
new
(
project
,
user
,
opts
).
execute
expect
(
note
.
note
).
to
eq
(
opts
[
:note
])
expect
(
note
.
note
).
to
eq
(
opts
[
:note
])
end
end
it
'note belongs to the correct project'
do
it
'note belongs to the correct project'
do
note
=
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
note
=
described_class
.
new
(
project
,
user
,
opts
).
execute
expect
(
note
.
project
).
to
eq
(
project
)
expect
(
note
.
project
).
to
eq
(
project
)
end
end
...
@@ -44,7 +44,7 @@ describe Notes::CreateService, services: true do
...
@@ -44,7 +44,7 @@ describe Notes::CreateService, services: true do
expect_any_instance_of
(
TodoService
).
to
receive
(
:new_note
).
with
(
note
,
user
)
expect_any_instance_of
(
TodoService
).
to
receive
(
:new_note
).
with
(
note
,
user
)
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
described_class
.
new
(
project
,
user
,
opts
).
execute
end
end
it
'enqueues NewNoteWorker'
do
it
'enqueues NewNoteWorker'
do
...
@@ -53,7 +53,7 @@ describe Notes::CreateService, services: true do
...
@@ -53,7 +53,7 @@ describe Notes::CreateService, services: true do
expect
(
NewNoteWorker
).
to
receive
(
:perform_async
).
with
(
note
.
id
)
expect
(
NewNoteWorker
).
to
receive
(
:perform_async
).
with
(
note
.
id
)
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
described_class
.
new
(
project
,
user
,
opts
).
execute
end
end
end
end
...
@@ -115,7 +115,7 @@ describe Notes::CreateService, services: true do
...
@@ -115,7 +115,7 @@ describe Notes::CreateService, services: true do
noteable_type:
'Issue'
,
noteable_type:
'Issue'
,
noteable_id:
issue
.
id
noteable_id:
issue
.
id
}
}
note
=
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
note
=
described_class
.
new
(
project
,
user
,
opts
).
execute
expect
(
note
).
to
be_valid
expect
(
note
).
to
be_valid
expect
(
note
.
name
).
to
eq
(
'smile'
)
expect
(
note
.
name
).
to
eq
(
'smile'
)
...
@@ -127,7 +127,7 @@ describe Notes::CreateService, services: true do
...
@@ -127,7 +127,7 @@ describe Notes::CreateService, services: true do
noteable_type:
'Issue'
,
noteable_type:
'Issue'
,
noteable_id:
issue
.
id
noteable_id:
issue
.
id
}
}
note
=
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
note
=
described_class
.
new
(
project
,
user
,
opts
).
execute
expect
(
note
).
to
be_valid
expect
(
note
).
to
be_valid
expect
(
note
.
note
).
to
eq
(
opts
[
:note
])
expect
(
note
.
note
).
to
eq
(
opts
[
:note
])
...
@@ -142,7 +142,7 @@ describe Notes::CreateService, services: true do
...
@@ -142,7 +142,7 @@ describe Notes::CreateService, services: true do
expect_any_instance_of
(
TodoService
).
to
receive
(
:new_award_emoji
).
with
(
issue
,
user
)
expect_any_instance_of
(
TodoService
).
to
receive
(
:new_award_emoji
).
with
(
issue
,
user
)
Notes
::
CreateService
.
new
(
project
,
user
,
opts
).
execute
described_class
.
new
(
project
,
user
,
opts
).
execute
end
end
end
end
end
end
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