Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
GitLab Community Edition
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Locked Files
Issues
14,492
Issues
14,492
List
Boards
Labels
Service Desk
Milestones
Merge Requests
811
Merge Requests
811
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
GitLab.org
GitLab Community Edition
Commits
3a23639b
Unverified
Commit
3a23639b
authored
Dec 05, 2016
by
Ershad Kunnakkadan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create directly_addressed Todos when mentioned in beginning of a line
parent
11d33873
Changes
14
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
411 additions
and
24 deletions
+411
-24
todos_helper.rb
app/helpers/todos_helper.rb
+3
-1
mentionable.rb
app/models/concerns/mentionable.rb
+15
-4
directly_addressed_user.rb
app/models/directly_addressed_user.rb
+7
-0
todo.rb
app/models/todo.rb
+9
-7
todo_service.rb
app/services/todo_service.rb
+16
-2
24976-start-of-line-mention.yml
changelogs/unreleased/24976-start-of-line-mention.yml
+4
-0
querying.rb
lib/banzai/querying.rb
+51
-5
reference_extractor.rb
lib/banzai/reference_extractor.rb
+5
-0
base_parser.rb
lib/banzai/reference_parser/base_parser.rb
+3
-2
directly_addressed_user_parser.rb
...banzai/reference_parser/directly_addressed_user_parser.rb
+8
-0
reference_extractor.rb
lib/gitlab/reference_extractor.rb
+6
-2
todos.rb
spec/factories/todos.rb
+4
-0
reference_extractor_spec.rb
spec/lib/gitlab/reference_extractor_spec.rb
+75
-0
todo_service_spec.rb
spec/services/todo_service_spec.rb
+205
-1
No files found.
app/helpers/todos_helper.rb
View file @
3a23639b
...
...
@@ -15,6 +15,7 @@ module TodosHelper
when
Todo
::
MARKED
then
'added a todo for'
when
Todo
::
APPROVAL_REQUIRED
then
'set you as an approver for'
when
Todo
::
UNMERGEABLE
then
'Could not merge'
when
Todo
::
DIRECTLY_ADDRESSED
then
'directly addressed you on'
end
end
...
...
@@ -88,7 +89,8 @@ module TodosHelper
{
id:
Todo
::
ASSIGNED
,
text:
'Assigned'
},
{
id:
Todo
::
MENTIONED
,
text:
'Mentioned'
},
{
id:
Todo
::
MARKED
,
text:
'Added'
},
{
id:
Todo
::
BUILD_FAILED
,
text:
'Pipelines'
}
{
id:
Todo
::
BUILD_FAILED
,
text:
'Pipelines'
},
{
id:
Todo
::
DIRECTLY_ADDRESSED
,
text:
'Directly addressed'
}
]
end
...
...
app/models/concerns/mentionable.rb
View file @
3a23639b
...
...
@@ -44,8 +44,15 @@ module Mentionable
end
def
all_references
(
current_user
=
nil
,
extractor:
nil
)
extractor
||=
Gitlab
::
ReferenceExtractor
.
new
(
project
,
current_user
)
# Use custom extractor if it's passed in the function parameters.
if
extractor
@extractor
=
extractor
else
@extractor
||=
Gitlab
::
ReferenceExtractor
.
new
(
project
,
current_user
)
@extractor
.
reset_memoized_values
end
self
.
class
.
mentionable_attrs
.
each
do
|
attr
,
options
|
text
=
__send__
(
attr
)
...
...
@@ -55,16 +62,20 @@ module Mentionable
skip_project_check:
skip_project_check?
)
extractor
.
analyze
(
text
,
options
)
@
extractor
.
analyze
(
text
,
options
)
end
extractor
@
extractor
end
def
mentioned_users
(
current_user
=
nil
)
all_references
(
current_user
).
users
end
def
directly_addressed_users
(
current_user
=
nil
)
all_references
(
current_user
).
directly_addressed_users
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def
referenced_mentionables
(
current_user
=
self
.
author
)
refs
=
all_references
(
current_user
)
...
...
app/models/directly_addressed_user.rb
0 → 100644
View file @
3a23639b
class
DirectlyAddressedUser
class
<<
self
def
reference_pattern
User
.
reference_pattern
end
end
end
app/models/todo.rb
View file @
3a23639b
class
Todo
<
ActiveRecord
::
Base
include
Sortable
ASSIGNED
=
1
MENTIONED
=
2
BUILD_FAILED
=
3
MARKED
=
4
APPROVAL_REQUIRED
=
5
# This is an EE-only feature
UNMERGEABLE
=
6
ASSIGNED
=
1
MENTIONED
=
2
BUILD_FAILED
=
3
MARKED
=
4
APPROVAL_REQUIRED
=
5
# This is an EE-only feature
UNMERGEABLE
=
6
DIRECTLY_ADDRESSED
=
7
ACTION_NAMES
=
{
ASSIGNED
=>
:assigned
,
...
...
@@ -14,7 +15,8 @@ class Todo < ActiveRecord::Base
BUILD_FAILED
=>
:build_failed
,
MARKED
=>
:marked
,
APPROVAL_REQUIRED
=>
:approval_required
,
UNMERGEABLE
=>
:unmergeable
UNMERGEABLE
=>
:unmergeable
,
DIRECTLY_ADDRESSED
=>
:directly_addressed
}
belongs_to
:author
,
class_name:
"User"
...
...
app/services/todo_service.rb
View file @
3a23639b
...
...
@@ -243,6 +243,12 @@ class TodoService
end
def
create_mention_todos
(
project
,
target
,
author
,
note
=
nil
)
# Create Todos for directly addressed users
directly_addressed_users
=
filter_directly_addressed_users
(
project
,
note
||
target
,
author
)
attributes
=
attributes_for_todo
(
project
,
target
,
author
,
Todo
::
DIRECTLY_ADDRESSED
,
note
)
create_todos
(
directly_addressed_users
,
attributes
)
# Create Todos for mentioned users
mentioned_users
=
filter_mentioned_users
(
project
,
note
||
target
,
author
)
attributes
=
attributes_for_todo
(
project
,
target
,
author
,
Todo
::
MENTIONED
,
note
)
create_todos
(
mentioned_users
,
attributes
)
...
...
@@ -282,10 +288,18 @@ class TodoService
)
end
def
filter_todo_users
(
users
,
project
,
target
)
reject_users_without_access
(
users
,
project
,
target
).
uniq
end
def
filter_mentioned_users
(
project
,
target
,
author
)
mentioned_users
=
target
.
mentioned_users
(
author
)
mentioned_users
=
reject_users_without_access
(
mentioned_users
,
project
,
target
)
mentioned_users
.
uniq
filter_todo_users
(
mentioned_users
,
project
,
target
)
end
def
filter_directly_addressed_users
(
project
,
target
,
author
)
directly_addressed_users
=
target
.
directly_addressed_users
(
author
)
filter_todo_users
(
directly_addressed_users
,
project
,
target
)
end
def
reject_users_without_access
(
users
,
project
,
target
)
...
...
changelogs/unreleased/24976-start-of-line-mention.yml
0 → 100644
View file @
3a23639b
---
title
:
Added a feature to create a 'directly addressed' Todo when mentioned in the beginning of a line.
merge_request
:
7926
author
:
Ershad Kunnakkadan
lib/banzai/querying.rb
View file @
3a23639b
module
Banzai
module
Querying
module_function
# Searches a Nokogiri document using a CSS query, optionally optimizing it
# whenever possible.
#
# document - A document/element to search.
# query - The CSS query to use.
# document - A document/element to search.
# query - The CSS query to use.
# reference_options - A hash with nodes filter options
#
# Returns a Nokogiri::XML::NodeSet.
def
self
.
css
(
document
,
query
)
# Returns an array of Nokogiri::XML::Element objects if location is specified
# in reference_options. Otherwise it would a Nokogiri::XML::NodeSet.
def
css
(
document
,
query
,
reference_options
=
{})
# When using "a.foo" Nokogiri compiles this to "//a[...]" but
# "descendant::a[...]" is quite a bit faster and achieves the same result.
xpath
=
Nokogiri
::
CSS
.
xpath_for
(
query
)[
0
].
gsub
(
%r{^//}
,
'descendant::'
)
xpath
=
restrict_to_p_nodes_at_root
(
xpath
)
if
filter_nodes_at_beginning?
(
reference_options
)
nodes
=
document
.
xpath
(
xpath
)
filter_nodes
(
nodes
,
reference_options
)
end
def
restrict_to_p_nodes_at_root
(
xpath
)
xpath
.
gsub
(
'descendant::'
,
'./p/'
)
end
def
filter_nodes
(
nodes
,
reference_options
)
if
filter_nodes_at_beginning?
(
reference_options
)
filter_nodes_at_beginning
(
nodes
)
else
nodes
end
end
def
filter_nodes_at_beginning?
(
reference_options
)
reference_options
&&
reference_options
[
:location
]
==
:beginning
end
# Selects child nodes if they are present in the beginning among other siblings.
#
# nodes - A Nokogiri::XML::NodeSet.
#
# Returns an array of Nokogiri::XML::Element objects.
def
filter_nodes_at_beginning
(
nodes
)
parents_and_nodes
=
nodes
.
group_by
(
&
:parent
)
filtered_nodes
=
[]
parents_and_nodes
.
each
do
|
parent
,
nodes
|
children
=
parent
.
children
nodes
=
nodes
.
to_a
children
.
each
do
|
child
|
next
if
child
.
text
.
blank?
node
=
nodes
.
shift
break
unless
node
==
child
filtered_nodes
<<
node
end
end
document
.
xpath
(
xpath
)
filtered_nodes
end
end
end
lib/banzai/reference_extractor.rb
View file @
3a23639b
...
...
@@ -16,6 +16,11 @@ module Banzai
processor
.
process
(
html_documents
)
end
def
reset_memoized_values
@html_documents
=
nil
@texts_and_contexts
=
[]
end
private
def
html_documents
...
...
lib/banzai/reference_parser/base_parser.rb
View file @
3a23639b
...
...
@@ -33,7 +33,7 @@ module Banzai
# they have access to.
class
BaseParser
class
<<
self
attr_accessor
:reference_type
attr_accessor
:reference_type
,
:reference_options
end
# Returns the attribute name containing the value for every object to be
...
...
@@ -182,9 +182,10 @@ module Banzai
# the references.
def
process
(
documents
)
type
=
self
.
class
.
reference_type
reference_options
=
self
.
class
.
reference_options
nodes
=
documents
.
flat_map
do
|
document
|
Querying
.
css
(
document
,
"a[data-reference-type='
#{
type
}
'].gfm"
).
to_a
Querying
.
css
(
document
,
"a[data-reference-type='
#{
type
}
'].gfm"
,
reference_options
).
to_a
end
gather_references
(
nodes
)
...
...
lib/banzai/reference_parser/directly_addressed_user_parser.rb
0 → 100644
View file @
3a23639b
module
Banzai
module
ReferenceParser
class
DirectlyAddressedUserParser
<
UserParser
self
.
reference_type
=
:user
self
.
reference_options
=
{
location: :beginning
}
end
end
end
lib/gitlab/reference_extractor.rb
View file @
3a23639b
module
Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class
ReferenceExtractor
<
Banzai
::
ReferenceExtractor
REFERABLES
=
%i(user issue label milestone merge_request snippet commit commit_range)
REFERABLES
=
%i(user issue label milestone merge_request snippet commit commit_range
directly_addressed_user
)
attr_accessor
:project
,
:current_user
,
:author
def
initialize
(
project
,
current_user
=
nil
)
@project
=
project
@current_user
=
current_user
@references
=
{}
super
()
...
...
@@ -21,6 +20,11 @@ module Gitlab
super
(
type
,
project
,
current_user
)
end
def
reset_memoized_values
@references
=
{}
super
()
end
REFERABLES
.
each
do
|
type
|
define_method
(
"
#{
type
}
s"
)
do
@references
[
type
]
||=
references
(
type
)
...
...
spec/factories/todos.rb
View file @
3a23639b
...
...
@@ -14,6 +14,10 @@ FactoryGirl.define do
action
{
Todo
::
MENTIONED
}
end
trait
:directly_addressed
do
action
{
Todo
::
DIRECTLY_ADDRESSED
}
end
trait
:on_commit
do
commit_id
RepoHelpers
.
sample_commit
.
id
target_type
"Commit"
...
...
spec/lib/gitlab/reference_extractor_spec.rb
View file @
3a23639b
...
...
@@ -42,14 +42,85 @@ describe Gitlab::ReferenceExtractor, lib: true do
> @offteam
}
)
expect
(
subject
.
users
).
to
match_array
([])
end
describe
'directly addressed users'
do
before
do
@u_foo
=
create
(
:user
,
username:
'foo'
)
@u_foo2
=
create
(
:user
,
username:
'foo2'
)
@u_foo3
=
create
(
:user
,
username:
'foo3'
)
@u_foo4
=
create
(
:user
,
username:
'foo4'
)
@u_foo5
=
create
(
:user
,
username:
'foo5'
)
@u_bar
=
create
(
:user
,
username:
'bar'
)
@u_bar2
=
create
(
:user
,
username:
'bar2'
)
@u_bar3
=
create
(
:user
,
username:
'bar3'
)
@u_bar4
=
create
(
:user
,
username:
'bar4'
)
@u_tom
=
create
(
:user
,
username:
'tom'
)
@u_tom2
=
create
(
:user
,
username:
'tom2'
)
end
context
'when a user is directly addressed'
do
it
'accesses the user object which is mentioned in the beginning of the line'
do
subject
.
analyze
(
'@foo What do you think? cc: @bar, @tom'
)
expect
(
subject
.
directly_addressed_users
).
to
match_array
([
@u_foo
])
end
it
"doesn't access the user object if it's not mentioned in the beginning of the line"
do
subject
.
analyze
(
'What do you think? cc: @bar'
)
expect
(
subject
.
directly_addressed_users
).
to
be_empty
end
end
context
'when multiple users are addressed'
do
it
'accesses the user objects which are mentioned in the beginning of the line'
do
subject
.
analyze
(
'@foo @bar What do you think? cc: @tom'
)
expect
(
subject
.
directly_addressed_users
).
to
match_array
([
@u_foo
,
@u_bar
])
end
it
"doesn't access the user objects if they are not mentioned in the beginning of the line"
do
subject
.
analyze
(
'What do you think? cc: @foo @bar @tom'
)
expect
(
subject
.
directly_addressed_users
).
to
be_empty
end
end
context
'when multiple users are addressed in different paragraphs'
do
it
'accesses user objects which are mentioned in the beginning of each paragraph'
do
subject
.
analyze
<<-
NOTE
.
strip_heredoc
@foo What do you think? cc: @tom
- @bar can you please have a look?
>>>
@foo2 what do you think? cc: @bar2
>>>
@foo3 @foo4 thank you!
> @foo5 well done!
1. @bar3 Can you please check? cc: @tom2
2. @bar4 What do you this of this MR?
NOTE
expect
(
subject
.
directly_addressed_users
).
to
match_array
([
@u_foo
,
@u_foo3
,
@u_foo4
])
end
end
end
it
'accesses valid issue objects'
do
@i0
=
create
(
:issue
,
project:
project
)
@i1
=
create
(
:issue
,
project:
project
)
subject
.
analyze
(
"
#{
@i0
.
to_reference
}
,
#{
@i1
.
to_reference
}
, and
#{
Issue
.
reference_prefix
}
999."
)
expect
(
subject
.
issues
).
to
match_array
([
@i0
,
@i1
])
end
...
...
@@ -58,6 +129,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@m1
=
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
source_branch:
'feature_conflict'
)
subject
.
analyze
(
"!999, !
#{
@m1
.
iid
}
, and !
#{
@m0
.
iid
}
."
)
expect
(
subject
.
merge_requests
).
to
match_array
([
@m1
,
@m0
])
end
...
...
@@ -67,6 +139,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@l2
=
create
(
:label
)
subject
.
analyze
(
"~
#{
@l0
.
id
}
, ~999, ~
#{
@l2
.
id
}
, ~
#{
@l1
.
id
}
"
)
expect
(
subject
.
labels
).
to
match_array
([
@l0
,
@l1
])
end
...
...
@@ -76,6 +149,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
@s2
=
create
(
:project_snippet
)
subject
.
analyze
(
"$
#{
@s0
.
id
}
, $999, $
#{
@s2
.
id
}
, $
#{
@s1
.
id
}
"
)
expect
(
subject
.
snippets
).
to
match_array
([
@s0
,
@s1
])
end
...
...
@@ -127,6 +201,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
it
'handles project issue references'
do
subject
.
analyze
(
"this refers issue
#{
issue
.
to_reference
(
project
)
}
"
)
extracted
=
subject
.
issues
expect
(
extracted
.
size
).
to
eq
(
1
)
expect
(
extracted
).
to
match_array
([
issue
])
...
...
spec/services/todo_service_spec.rb
View file @
3a23639b
This diff is collapsed.
Click to expand it.
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