Просмотр исходного кода

Maintenance: Improve Mentions error messages and error handling

Mantas Masalskis 1 год назад
Родитель
Сommit
63e0778489

+ 1 - 0
app/controllers/concerns/creates_ticket_articles.rb

@@ -53,6 +53,7 @@ module CreatesTicketArticles # rubocop:disable Metrics/ModuleLength
 
 
     article = Ticket::Article.new(clean_params)
     article = Ticket::Article.new(clean_params)
     article.ticket_id = ticket.id
     article.ticket_id = ticket.id
+    article.check_mentions_raises_error = true
 
 
     # store dataurl images to store
     # store dataurl images to store
     attachments_inline = []
     attachments_inline = []

+ 4 - 2
app/controllers/tickets_controller.rb

@@ -174,11 +174,13 @@ class TicketsController < ApplicationController
         end
         end
       end
       end
 
 
-      # create mentions if given
+      # This mentions handling is used by custom API calls only
+      # Mentions created in UI are handled by Ticket::Article#check_mentions
       if params[:mentions].present?
       if params[:mentions].present?
         authorize!(ticket, :create_mentions?)
         authorize!(ticket, :create_mentions?)
+
         Array(params[:mentions]).each do |user_id|
         Array(params[:mentions]).each do |user_id|
-          Mention.where(mentionable: ticket, user_id: user_id).first_or_create(mentionable: ticket, user_id: user_id)
+          Mention.subscribe! ticket, User.find(user_id)
         end
         end
       end
       end
 
 

+ 1 - 3
app/models/mention.rb

@@ -66,9 +66,7 @@ class Mention < ApplicationModel
   # @param user
   # @param user
   # @return Boolean
   # @return Boolean
   def self.subscribe!(object, user)
   def self.subscribe!(object, user)
-    object
-      .mentions
-      .find_or_create_by! user: user
+    object.mentions.create!(user: user) if !subscribed?(object, user)
 
 
     true
     true
   end
   end

+ 16 - 6
app/models/ticket/article.rb

@@ -63,7 +63,7 @@ class Ticket::Article < ApplicationModel
                              :to,
                              :to,
                              :cc
                              :cc
 
 
-  attr_accessor :should_clone_inline_attachments
+  attr_accessor :should_clone_inline_attachments, :check_mentions_raises_error
 
 
   alias should_clone_inline_attachments? should_clone_inline_attachments
   alias should_clone_inline_attachments? should_clone_inline_attachments
 
 
@@ -357,16 +357,26 @@ returns
 
 
     return if mention_user_ids.blank?
     return if mention_user_ids.blank?
 
 
-    if !TicketPolicy.new(updated_by, ticket).create_mentions?
+    begin
+      Pundit.authorize updated_by, ticket, :create_mentions?
+    rescue Pundit::NotAuthorizedError => e
       return if ApplicationHandleInfo.postmaster?
       return if ApplicationHandleInfo.postmaster?
       return if updated_by.id == 1
       return if updated_by.id == 1
+      return if !check_mentions_raises_error
 
 
-      raise "User #{updated_by_id} has no permission to mention other users!"
+      raise e
     end
     end
 
 
-    user_ids = User.where(id: mention_user_ids).pluck(:id)
-    user_ids.each do |user_id|
-      Mention.where(mentionable: ticket, user_id: user_id).first_or_create(mentionable: ticket, user_id: user_id)
+    mention_user_ids.each do |user_id|
+      begin
+        Mention.subscribe! ticket, User.find(user_id)
+      rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid => e
+        next if ApplicationHandleInfo.postmaster?
+        next if updated_by.id == 1
+        next if !check_mentions_raises_error
+
+        raise e
+      end
     end
     end
   end
   end
 
 

+ 3 - 1
app/policies/ticket_policy.rb

@@ -58,7 +58,9 @@ class TicketPolicy < ApplicationPolicy
   end
   end
 
 
   def create_mentions?
   def create_mentions?
-    agent_read_access?
+    return true if agent_read_access?
+
+    not_authorized __('You have insufficient permissions to mention other users.')
   end
   end
 
 
   private
   private

+ 2 - 0
app/services/service/ticket/article/create.rb

@@ -11,6 +11,8 @@ class Service::Ticket::Article::Create < Service::BaseWithCurrentUser
     preprocess_article_data(article_data, ticket)
     preprocess_article_data(article_data, ticket)
 
 
     ticket.articles.new(article_data).tap do |article|
     ticket.articles.new(article_data).tap do |article|
+      article.check_mentions_raises_error = true
+
       transform_article(article, attachments_raw, subtype)
       transform_article(article, attachments_raw, subtype)
 
 
       article.save!
       article.save!

+ 8 - 4
i18n/zammad.pot

@@ -509,6 +509,10 @@ msgstr ""
 msgid "A list of active import backends that gets scheduled automatically."
 msgid "A list of active import backends that gets scheduled automatically."
 msgstr ""
 msgstr ""
 
 
+#: lib/validations/mention_validator.rb
+msgid "A mentioned user has no agent access to this ticket"
+msgstr ""
+
 #: app/assets/javascripts/app/controllers/_plugin/session_taken_over.coffee
 #: app/assets/javascripts/app/controllers/_plugin/session_taken_over.coffee
 msgid "A new session was created with your account. This session will be stopped to prevent a conflict."
 msgid "A new session was created with your account. This session will be stopped to prevent a conflict."
 msgstr ""
 msgstr ""
@@ -14386,6 +14390,10 @@ msgstr ""
 msgid "You have exceeded the character limit by %s"
 msgid "You have exceeded the character limit by %s"
 msgstr ""
 msgstr ""
 
 
+#: app/policies/ticket_policy.rb
+msgid "You have insufficient permissions to mention other users."
+msgstr ""
+
 #: app/frontend/apps/mobile/entities/organization/composables/useOrganizationDetail.ts
 #: app/frontend/apps/mobile/entities/organization/composables/useOrganizationDetail.ts
 msgid "You have insufficient rights to view this organization."
 msgid "You have insufficient rights to view this organization."
 msgstr ""
 msgstr ""
@@ -15065,10 +15073,6 @@ msgstr ""
 msgid "has changed"
 msgid "has changed"
 msgstr ""
 msgstr ""
 
 
-#: lib/validations/mention_validator.rb
-msgid "has no agent access to this ticket"
-msgstr ""
-
 #: app/assets/javascripts/app/controllers/_ui_element/_application_selector.coffee
 #: app/assets/javascripts/app/controllers/_ui_element/_application_selector.coffee
 #: app/assets/javascripts/app/controllers/_ui_element/object_selector.coffee
 #: app/assets/javascripts/app/controllers/_ui_element/object_selector.coffee
 msgid "has reached"
 msgid "has reached"

+ 1 - 1
lib/validations/mention_validator.rb

@@ -4,6 +4,6 @@ class Validations::MentionValidator < ActiveModel::Validator
   def validate(record)
   def validate(record)
     return if Mention.mentionable? record.mentionable, record.user
     return if Mention.mentionable? record.mentionable, record.user
 
 
-    record.errors.add :user, __('has no agent access to this ticket')
+    record.errors.add :base, __('A mentioned user has no agent access to this ticket')
   end
   end
 end
 end

+ 4 - 1
spec/models/mention_spec.rb

@@ -8,7 +8,10 @@ RSpec.describe Mention, type: :model do
 
 
   describe 'validation' do
   describe 'validation' do
     it 'does not allow mentions for customers' do
     it 'does not allow mentions for customers' do
-      expect { create(:mention, mentionable: ticket, user: create(:customer)) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: User has no agent access to this ticket')
+      record = build(:mention, mentionable: ticket, user: create(:customer))
+      record.save
+
+      expect(record.errors.full_messages).to include('A mentioned user has no agent access to this ticket')
     end
     end
   end
   end
 
 

+ 139 - 0
spec/models/ticket/article_spec.rb

@@ -523,6 +523,145 @@ RSpec.describe Ticket::Article, type: :model do
         end
         end
       end
       end
     end
     end
+
+    describe '#check_mentions' do
+      def text_blob_with(user)
+        "Lorem ipsum dolor <a data-mention-user-id='#{user.id}'>#{user.fullname}</a>"
+      end
+
+      let(:ticket)              { create(:ticket) }
+      let(:agent_with_access)   { create(:agent, groups: [ticket.group]) }
+      let(:user_without_access) { create(:agent) }
+
+      let(:passing_body) { text_blob_with(agent_with_access) }
+      let(:failing_body) { text_blob_with(user_without_access) }
+      let(:partial_body) { text_blob_with(user_without_access) + text_blob_with(agent_with_access) }
+
+      context 'when created in email parsing' do
+        before { ApplicationHandleInfo.current = 'postmaster' }
+
+        it 'silently ignores mentions if agent cannot mention users' do
+          UserInfo.current_user_id = user_without_access.id
+          record = create(:ticket_article, body: passing_body)
+
+          expect(record).to be_persisted
+          expect(Mention.count).to be_zero
+        end
+
+        it 'silently ignores mentions if given users cannot be mentioned' do
+          UserInfo.current_user_id = agent_with_access.id
+          article = build(:ticket_article, ticket: ticket, body: failing_body)
+          article.save
+
+          expect(article).to be_persisted
+          expect(Mention.count).to eq(0)
+        end
+
+        it 'silently saves passing user while failing user is skipped' do
+          UserInfo.current_user_id = agent_with_access.id
+
+          article = create(:ticket_article, ticket: ticket, body: partial_body)
+
+          expect(article).to be_persisted
+          expect(Mention.count).to eq(1)
+        end
+
+        it 'mentioned user is added' do
+          UserInfo.current_user_id = agent_with_access.id
+
+          create(:ticket_article, ticket: ticket, body: passing_body)
+
+          expect(article).to be_persisted
+          expect(Mention.count).to eq(1)
+        end
+      end
+
+      context 'when created with check_mentions_raises_error set to true' do
+        it 'raises an error if agent cannot mention users' do
+          UserInfo.current_user_id = create(:customer).id
+
+          article = build(:ticket_article, ticket: ticket, body: passing_body)
+          article.check_mentions_raises_error = true
+
+          expect { article.save! }
+            .to raise_error(Pundit::NotAuthorizedError)
+          expect(Mention.count).to eq(0)
+        end
+
+        it 'raises an error if given users cannot be mentioned' do
+          UserInfo.current_user_id = agent_with_access.id
+
+          article = build(:ticket_article, ticket: ticket, body: failing_body)
+          article.check_mentions_raises_error = true
+
+          expect { article.save! }
+            .to raise_error(ActiveRecord::RecordInvalid)
+          expect(Mention.count).to eq(0)
+        end
+
+        it 'raises an error if one if given users cannot be mentioned' do
+          UserInfo.current_user_id = agent_with_access.id
+
+          article = build(:ticket_article, ticket: ticket, body: partial_body)
+          article.check_mentions_raises_error = true
+
+          expect { article.save! }
+            .to raise_error(ActiveRecord::RecordInvalid)
+          expect(Mention.count).to eq(0)
+        end
+
+        it 'mentioned user is added' do
+          UserInfo.current_user_id = agent_with_access.id
+
+          article = build(:ticket_article, ticket: ticket, body: passing_body)
+          article.check_mentions_raises_error = true
+          article.save!
+
+          expect(article).to be_persisted
+          expect(Mention.count).to eq(1)
+        end
+      end
+
+      context 'when created with check_mentions_raises_error set to false' do
+        it 'silently ignores mentions if agent cannot mention users' do
+          UserInfo.current_user_id = create(:customer).id
+
+          article = build(:ticket_article, ticket: ticket, body: failing_body)
+          article.save
+
+          expect(article).to be_persisted
+          expect(Mention.count).to eq(0)
+        end
+
+        it 'silently ignores mentions if given users cannot be mentioned' do
+          UserInfo.current_user_id = agent_with_access.id
+
+          article = build(:ticket_article, ticket: ticket, body: failing_body)
+          article.save
+
+          expect(article).to be_persisted
+          expect(Mention.count).to eq(0)
+        end
+
+        it 'silently saves passing user while failing user is skipped' do
+          UserInfo.current_user_id = agent_with_access.id
+
+          article = create(:ticket_article, ticket: ticket, body: partial_body)
+
+          expect(article).to be_persisted
+          expect(Mention.count).to eq(1)
+        end
+
+        it 'mentioned user is added' do
+          UserInfo.current_user_id = agent_with_access.id
+
+          create(:ticket_article, ticket: ticket, body: passing_body)
+
+          expect(article).to be_persisted
+          expect(Mention.count).to eq(1)
+        end
+      end
+    end
   end
   end
 
 
   describe 'clone attachments' do
   describe 'clone attachments' do

Некоторые файлы не были показаны из-за большого количества измененных файлов