diff options
author | Douwe Maan <douwe@gitlab.com> | 2018-05-07 11:26:44 +0300 |
---|---|---|
committer | Douwe Maan <douwe@gitlab.com> | 2018-05-07 11:26:44 +0300 |
commit | 67c9f822dd60ef55323082cd0b17ccd6108a24c6 (patch) | |
tree | 716d434e45d4b43161e889d99c37b31415e22049 /app | |
parent | 6a052a14e938ef073977afd808dd12a801f44369 (diff) | |
parent | cfa92112d189dce1028bce7145a5cbe609c389a8 (diff) |
Merge branch '4084-epics-username-autocomplete-ce' into 'master'
Backport CE changes from "autocomplete usernames in Epic comments/description"
See merge request gitlab-org/gitlab-ce!18605
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/gfm_auto_complete.js | 9 | ||||
-rw-r--r-- | app/assets/javascripts/notes/components/comment_form.vue | 6 | ||||
-rw-r--r-- | app/helpers/application_helper.rb | 13 | ||||
-rw-r--r-- | app/models/ability.rb | 8 | ||||
-rw-r--r-- | app/models/concerns/participable.rb | 4 | ||||
-rw-r--r-- | app/models/group.rb | 31 | ||||
-rw-r--r-- | app/models/namespace.rb | 7 | ||||
-rw-r--r-- | app/services/concerns/users/participable_service.rb | 41 | ||||
-rw-r--r-- | app/services/projects/participants_service.rb | 32 | ||||
-rw-r--r-- | app/views/layouts/_init_auto_complete.html.haml | 15 |
10 files changed, 118 insertions, 48 deletions
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 7e9770a9ea2..9de57db48fd 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -408,7 +408,10 @@ class GfmAutoComplete { fetchData($input, at) { if (this.isLoadingData[at]) return; + this.isLoadingData[at] = true; + const dataSource = this.dataSources[GfmAutoComplete.atTypeMap[at]]; + if (this.cachedData[at]) { this.loadData($input, at, this.cachedData[at]); } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') { @@ -418,12 +421,14 @@ class GfmAutoComplete { GfmAutoComplete.glEmojiTag = glEmojiTag; }) .catch(() => { this.isLoadingData[at] = false; }); - } else { - AjaxCache.retrieve(this.dataSources[GfmAutoComplete.atTypeMap[at]], true) + } else if (dataSource) { + AjaxCache.retrieve(dataSource, true) .then((data) => { this.loadData($input, at, data); }) .catch(() => { this.isLoadingData[at] = false; }); + } else { + this.isLoadingData[at] = false; } } diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 396a675b4ac..48642c4a086 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -99,10 +99,6 @@ export default { 'js-note-target-reopen': !this.isOpen, }; }, - supportQuickActions() { - // Disable quick actions support for Epics - return this.noteableType !== constants.EPIC_NOTEABLE_TYPE; - }, markdownDocsPath() { return this.getNotesData.markdownDocsPath; }, @@ -359,7 +355,7 @@ Please check your network connection and try again.`; name="note[note]" class="note-textarea js-vue-comment-form js-gfm-input js-autosize markdown-area js-vue-textarea" - :data-supports-quick-actions="supportQuickActions" + data-supports-quick-actions="true" aria-label="Description" v-model="note" ref="textarea" diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6aa307b4db4..aa4569500b8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -258,4 +258,17 @@ module ApplicationHelper _('You are on a read-only GitLab instance.') end + + def autocomplete_data_sources(object, noteable_type) + return {} unless object && noteable_type + + { + members: members_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), + issues: issues_project_autocomplete_sources_path(object), + merge_requests: merge_requests_project_autocomplete_sources_path(object), + labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), + milestones: milestones_project_autocomplete_sources_path(object), + commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]) + } + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 618d4af4272..bb600eaccba 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -10,6 +10,14 @@ class Ability end end + # Given a list of users and a group this method returns the users that can + # read the given group. + def users_that_can_read_group(users, group) + DeclarativePolicy.subject_scope do + users.select { |u| allowed?(u, :read_group, group) } + end + end + # Given a list of users and a snippet this method returns the users that can # read the given snippet. def users_that_can_read_personal_snippet(users, snippet) diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index e48bc0be410..01b1ef9f82c 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -98,6 +98,10 @@ module Participable participants.merge(ext.users) + filter_by_ability(participants) + end + + def filter_by_ability(participants) case self when PersonalSnippet Ability.users_that_can_read_personal_snippet(participants.to_a, self) diff --git a/app/models/group.rb b/app/models/group.rb index f493836a92e..cefca316399 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -241,6 +241,13 @@ class Group < Namespace .where(source_id: self_and_descendants.reorder(nil).select(:id)) end + # Returns all members that are part of the group, it's subgroups, and ancestor groups + def direct_and_indirect_members + GroupMember + .active_without_invites_and_requests + .where(source_id: self_and_hierarchy.reorder(nil).select(:id)) + end + def users_with_parents User .where(id: members_with_parents.select(:user_id)) @@ -253,6 +260,30 @@ class Group < Namespace .reorder(nil) end + # Returns all users that are members of the group because: + # 1. They belong to the group + # 2. They belong to a project that belongs to the group + # 3. They belong to a sub-group or project in such sub-group + # 4. They belong to an ancestor group + def direct_and_indirect_users + union = Gitlab::SQL::Union.new([ + User + .where(id: direct_and_indirect_members.select(:user_id)) + .reorder(nil), + project_users_with_descendants + ]) + + User.from("(#{union.to_sql}) #{User.table_name}") + end + + # Returns all users that are members of projects + # belonging to the current group or sub-groups + def project_users_with_descendants + User + .joins(projects: :group) + .where(namespaces: { id: self_and_descendants.select(:id) }) + end + def max_member_access_for_user(user) return GroupMember::OWNER if user.admin? diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 5621eeba7c4..3dad4277713 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -166,6 +166,13 @@ class Namespace < ActiveRecord::Base projects.with_shared_runners.any? end + # Returns all ancestors, self, and descendants of the current namespace. + def self_and_hierarchy + Gitlab::GroupHierarchy + .new(self.class.where(id: id)) + .all_groups + end + # Returns all the ancestors of the current namespaces. def ancestors return self.class.none unless parent_id diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb new file mode 100644 index 00000000000..bf60b96938d --- /dev/null +++ b/app/services/concerns/users/participable_service.rb @@ -0,0 +1,41 @@ +module Users + module ParticipableService + extend ActiveSupport::Concern + + included do + attr_reader :noteable + end + + def noteable_owner + return [] unless noteable && noteable.author.present? + + [as_hash(noteable.author)] + end + + def participants_in_noteable + return [] unless noteable + + users = noteable.participants(current_user) + sorted(users) + end + + def sorted(users) + users.uniq.to_a.compact.sort_by(&:username).map do |user| + as_hash(user) + end + end + + def groups + current_user.authorized_groups.sort_by(&:path).map do |group| + count = group.users.count + { username: group.full_path, name: group.full_name, count: count, avatar_url: group.avatar_url } + end + end + + private + + def as_hash(user) + { username: user.username, name: user.name, avatar_url: user.avatar_url } + end + end +end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index e6193fcacee..eb0472c6024 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -1,6 +1,6 @@ module Projects class ParticipantsService < BaseService - attr_reader :noteable + include Users::ParticipableService def execute(noteable) @noteable = noteable @@ -10,36 +10,6 @@ module Projects participants.uniq end - def noteable_owner - return [] unless noteable && noteable.author.present? - - [{ - name: noteable.author.name, - username: noteable.author.username, - avatar_url: noteable.author.avatar_url - }] - end - - def participants_in_noteable - return [] unless noteable - - users = noteable.participants(current_user) - sorted(users) - end - - def sorted(users) - users.uniq.to_a.compact.sort_by(&:username).map do |user| - { username: user.username, name: user.name, avatar_url: user.avatar_url } - end - end - - def groups - current_user.authorized_groups.sort_by(&:path).map do |group| - count = group.users.count - { username: group.full_path, name: group.full_name, count: count, avatar_url: group.avatar_url } - end - end - def all_members count = project.team.members.flatten.count [{ username: "all", name: "All Project and Group Members", count: count }] diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 4276e6ee4bb..240e03a5d53 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,16 +1,11 @@ -- project = @target_project || @project +- object = @target_project || @project || @group - noteable_type = @noteable.class if @noteable.present? -- if project +- datasources = autocomplete_data_sources(object, noteable_type) + +- if object -# haml-lint:disable InlineJavaScript :javascript gl = window.gl || {}; gl.GfmAutoComplete = gl.GfmAutoComplete || {}; - gl.GfmAutoComplete.dataSources = { - members: "#{members_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}", - issues: "#{issues_project_autocomplete_sources_path(project)}", - mergeRequests: "#{merge_requests_project_autocomplete_sources_path(project)}", - labels: "#{labels_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}", - milestones: "#{milestones_project_autocomplete_sources_path(project)}", - commands: "#{commands_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}" - }; + gl.GfmAutoComplete.dataSources = #{datasources.to_json}; |