summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2017-02-10 16:23:48 +0200
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2017-02-13 12:20:08 +0200
commit45177e78b2dd06d60c89093d0d45651631623766 (patch)
treef438b9a46ed38c037c343912c23202c164f764cf
parentde65731631c741db65cf6e6297715c936a7aaf10 (diff)
downloadgitlab-ce-dz-nested-groups-gfm.tar.gz
Add GFM support to nested groupsdz-nested-groups-gfm
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
-rw-r--r--app/models/concerns/mentionable.rb3
-rw-r--r--app/models/group.rb9
-rw-r--r--app/services/projects/participants_service.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb2
-rw-r--r--lib/banzai/filter/group_reference_filter.rb54
-rw-r--r--lib/banzai/filter/user_reference_filter.rb56
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/single_line_pipeline.rb1
-rw-r--r--lib/banzai/reference_parser/group_parser.rb7
-rw-r--r--lib/gitlab/reference_extractor.rb2
-rw-r--r--lib/gitlab/regex.rb1
-rw-r--r--spec/lib/banzai/filter/group_reference_filter_spec.rb59
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb25
13 files changed, 159 insertions, 63 deletions
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index ef2c1e5d414..25efde47353 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -62,7 +62,8 @@ module Mentionable
end
def mentioned_users(current_user = nil)
- all_references(current_user).users
+ refs = all_references(current_user)
+ refs.users + refs.groups
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
diff --git a/app/models/group.rb b/app/models/group.rb
index a5b92283daa..31878dfd1b0 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -62,7 +62,12 @@ class Group < Namespace
end
def reference_pattern
- User.reference_pattern
+ x = Gitlab::Regex::FULL_PATH_REGEX_STR
+
+ %r{
+ #{Regexp.escape(reference_prefix)}
+ (?<group>#{x})
+ }x
end
def visible_to_user(user)
@@ -81,7 +86,7 @@ class Group < Namespace
end
def to_reference(_from_project = nil, full: nil)
- "#{self.class.reference_prefix}#{name}"
+ "#{self.class.reference_prefix}#{full_path}"
end
def web_url
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 96c363c8d1a..e6193fcacee 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -36,7 +36,7 @@ module Projects
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
- { username: group.path, name: group.name, count: count, avatar_url: group.avatar_url }
+ { username: group.full_path, name: group.full_name, count: count, avatar_url: group.avatar_url }
end
end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index a3d495a5da0..955d857c679 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -285,7 +285,7 @@ module Banzai
end
def current_project_namespace_path
- @current_project_namespace_path ||= project.namespace.path
+ @current_project_namespace_path ||= project.namespace.full_path
end
private
diff --git a/lib/banzai/filter/group_reference_filter.rb b/lib/banzai/filter/group_reference_filter.rb
new file mode 100644
index 00000000000..f9a3886f3f0
--- /dev/null
+++ b/lib/banzai/filter/group_reference_filter.rb
@@ -0,0 +1,54 @@
+module Banzai
+ module Filter
+ # HTML filter that replaces user or group references with links.
+ #
+ # A special `@all` reference is also supported.
+ class GroupReferenceFilter < UserReferenceFilter
+ self.reference_type = :group
+
+ def self.reference_pattern
+ Group.reference_pattern
+ end
+
+ # Replace `@group` group references in text with links to the referenced
+ # group's landing page.
+ #
+ # text - String text to replace references in.
+ # link_content - Original content of the link being replaced.
+ #
+ # Returns a String with `@group` references replaced with links. All links
+ # have `gfm` and `gfm-project_member` class names attached for styling.
+ def user_link_filter(text, link_content: nil)
+ self.class.references_in(text) do |match, username|
+ if group = groups[username]
+ link_to_group(group, link_content: link_content) || match
+ else
+ match
+ end
+ end
+ end
+
+ # Returns a Hash containing all Group objects for the username
+ # references in the current document.
+ #
+ # The keys of this Hash are the group paths, the values the
+ # corresponding Group objects.
+ def groups
+ @groups ||=
+ Group.where_full_path_in(usernames).each_with_object({}) do |row, hash|
+ hash[row.full_path] = row
+ end
+ end
+
+ private
+
+ def link_to_group(group, link_content: nil)
+ url = urls.group_url(group, only_path: context[:only_path])
+ data = data_attribute(group: group.id)
+ content = link_content || Group.reference_prefix + group.full_path
+
+ link_tag(url, data, content, group.name)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 1aa9355b256..d589288eecc 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -6,6 +6,10 @@ module Banzai
class UserReferenceFilter < ReferenceFilter
self.reference_type = :user
+ def self.reference_pattern
+ User.reference_pattern
+ end
+
# Public: Find `@user` user references in text
#
# UserReferenceFilter.references_in(text) do |match, username|
@@ -18,15 +22,15 @@ module Banzai
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
- text.gsub(User.reference_pattern) do |match|
- yield match, $~[:user]
+ text.gsub(reference_pattern) do |match|
+ yield match, $~[reference_type]
end
end
def call
return doc if project.nil? && !skip_project_check?
- ref_pattern = User.reference_pattern
+ ref_pattern = self.class.reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/
nodes.each do |node|
@@ -60,23 +64,23 @@ module Banzai
self.class.references_in(text) do |match, username|
if username == 'all' && !skip_project_check?
link_to_all(link_content: link_content)
- elsif namespace = namespaces[username]
- link_to_namespace(namespace, link_content: link_content) || match
+ elsif user = users[username]
+ link_to_user(user, link_content: link_content) || match
else
match
end
end
end
- # Returns a Hash containing all Namespace objects for the username
+ # Returns a Hash containing all User objects for the username
# references in the current document.
#
- # The keys of this Hash are the namespace paths, the values the
- # corresponding Namespace objects.
- def namespaces
- @namespaces ||=
- Namespace.where(path: usernames).each_with_object({}) do |row, hash|
- hash[row.path] = row
+ # The keys of this Hash are the user paths, the values the
+ # corresponding User objects.
+ def users
+ @users ||=
+ User.where(username: usernames).each_with_object({}) do |row, hash|
+ hash[row.username] = row
end
end
@@ -85,8 +89,8 @@ module Banzai
refs = Set.new
nodes.each do |node|
- node.to_html.scan(User.reference_pattern) do
- refs << $~[:user]
+ node.to_html.scan(self.class.reference_pattern) do
+ refs << $~[self.class.reference_type]
end
end
@@ -120,28 +124,12 @@ module Banzai
end
end
- def link_to_namespace(namespace, link_content: nil)
- if namespace.is_a?(Group)
- link_to_group(namespace.path, namespace, link_content: link_content)
- else
- link_to_user(namespace.path, namespace, link_content: link_content)
- end
- end
-
- def link_to_group(group, namespace, link_content: nil)
- url = urls.group_url(group, only_path: context[:only_path])
- data = data_attribute(group: namespace.id)
- content = link_content || Group.reference_prefix + group
-
- link_tag(url, data, content, namespace.name)
- end
-
- def link_to_user(user, namespace, link_content: nil)
+ def link_to_user(user, link_content: nil)
url = urls.user_url(user, only_path: context[:only_path])
- data = data_attribute(user: namespace.owner_id)
- content = link_content || User.reference_prefix + user
+ data = data_attribute(user: user.id)
+ content = link_content || User.reference_prefix + user.username
- link_tag(url, data, content, namespace.owner_name)
+ link_tag(url, data, content, user.name)
end
def link_tag(url, data, link_content, title)
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index b25d6f18d59..091c831fa89 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -23,6 +23,7 @@ module Banzai
Filter::ExternalLinkFilter,
Filter::UserReferenceFilter,
+ Filter::GroupReferenceFilter,
Filter::IssueReferenceFilter,
Filter::ExternalIssueReferenceFilter,
Filter::MergeRequestReferenceFilter,
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
index 1929099931b..5880b8f9246 100644
--- a/lib/banzai/pipeline/single_line_pipeline.rb
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -11,6 +11,7 @@ module Banzai
Filter::ExternalLinkFilter,
Filter::UserReferenceFilter,
+ Filter::GroupReferenceFilter,
Filter::IssueReferenceFilter,
Filter::ExternalIssueReferenceFilter,
Filter::MergeRequestReferenceFilter,
diff --git a/lib/banzai/reference_parser/group_parser.rb b/lib/banzai/reference_parser/group_parser.rb
new file mode 100644
index 00000000000..df74cebebba
--- /dev/null
+++ b/lib/banzai/reference_parser/group_parser.rb
@@ -0,0 +1,7 @@
+module Banzai
+ module ReferenceParser
+ class GroupParser < UserParser
+ self.reference_type = :group
+ end
+ end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 11c0b01f0dc..d764d60438d 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,7 +1,7 @@
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 group)
attr_accessor :project, :current_user, :author
def initialize(project, current_user = nil)
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index a3fa7c1331a..e8406b46468 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -9,6 +9,7 @@ module Gitlab
# `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze
+ FULL_PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.\/]*'.freeze
NAMESPACE_REGEX_STR_SIMPLE = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?<!\.git|\.atom)'.freeze
PROJECT_REGEX_STR = PATH_REGEX_STR + '(?<!\.git|\.atom)'.freeze
diff --git a/spec/lib/banzai/filter/group_reference_filter_spec.rb b/spec/lib/banzai/filter/group_reference_filter_spec.rb
new file mode 100644
index 00000000000..52905bc7fd5
--- /dev/null
+++ b/spec/lib/banzai/filter/group_reference_filter_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe Banzai::Filter::GroupReferenceFilter, lib: true do
+ include FilterSpecHelper
+
+ let(:project) { create(:empty_project, :public) }
+ let(:group) { create(:group) }
+ let(:reference) { group.to_reference }
+
+ context 'mentioning a group' do
+ it_behaves_like 'a reference containing an element node'
+
+ let(:group) { create(:group) }
+ let(:reference) { group.to_reference }
+
+ it 'links to the Group' do
+ doc = reference_filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+ end
+
+ it 'includes a data-group attribute' do
+ doc = reference_filter("Hey #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-group')
+ expect(link.attr('data-group')).to eq group.id.to_s
+ end
+ end
+
+ context 'mentioning a nested group' do
+ it_behaves_like 'a reference containing an element node'
+
+ let(:group) { create(:group, :nested) }
+ let(:reference) { group.to_reference }
+
+ it 'links to the nested group' do
+ doc = reference_filter("Hey #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+ end
+ end
+
+ it 'supports an :only_path context' do
+ doc = reference_filter("Hey #{reference}", only_path: true)
+ link = doc.css('a').first.attr('href')
+
+ expect(link).not_to match %r(https?://)
+ expect(link).to eq urls.group_path(group)
+ end
+
+ describe '#groups' do
+ it 'returns a Hash containing all groups' do
+ document = Nokogiri::HTML.fragment("<p>#{group.to_reference}</p>")
+ filter = described_class.new(document, project: project)
+
+ expect(filter.groups).to eq({ group.path => group })
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 3e1ac9fb2b2..ac0ca7e2c64 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -92,26 +92,6 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end
end
- context 'mentioning a group' do
- it_behaves_like 'a reference containing an element node'
-
- let(:group) { create(:group) }
- let(:reference) { group.to_reference }
-
- it 'links to the Group' do
- doc = reference_filter("Hey #{reference}")
- expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
- end
-
- it 'includes a data-group attribute' do
- doc = reference_filter("Hey #{reference}")
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-group')
- expect(link.attr('data-group')).to eq group.id.to_s
- end
- end
-
it 'links with adjacent text' do
doc = reference_filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
@@ -176,13 +156,12 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end
end
- describe '#namespaces' do
+ describe '#users' do
it 'returns a Hash containing all Namespaces' do
document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
filter = described_class.new(document, project: project)
- ns = user.namespace
- expect(filter.namespaces).to eq({ ns.path => ns })
+ expect(filter.users).to eq({ user.username => user })
end
end