diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee
index 9245d82b1..e7decf400 100644
--- a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee
+++ b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee
@@ -358,6 +358,7 @@ class App.TicketCreate extends App.Controller
data:
config: App.Config.all()
user: App.Session.get()
+ taskKey: @taskKey
)
$('#tags').tokenfield()
diff --git a/app/assets/javascripts/app/controllers/text_module.coffee b/app/assets/javascripts/app/controllers/text_module.coffee
index f6c989134..2aa86fa2b 100644
--- a/app/assets/javascripts/app/controllers/text_module.coffee
+++ b/app/assets/javascripts/app/controllers/text_module.coffee
@@ -15,16 +15,16 @@ class Index extends App.ControllerSubContent
deleteOption: true
)
pageData:
- home: 'text_modules'
- object: 'TextModule'
- objects: 'Text modules'
+ home: 'text_modules'
+ object: 'TextModule'
+ objects: 'Text modules'
navupdate: '#text_modules'
- notes: [
+ notes: [
'Text modules are ...'
]
buttons: [
- { name: 'Import', 'data-type': 'import', class: 'btn' }
- { name: 'New text module', 'data-type': 'new', class: 'btn--success' }
+ { name: 'Import', 'data-type': 'import', class: 'btn' }
+ { name: 'New text module', 'data-type': 'new', class: 'btn--success' }
]
container: @el.closest('.content')
)
diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
index d4e66b40b..4a600ac68 100644
--- a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
+++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
@@ -236,8 +236,9 @@ class App.TicketZoomArticleNew extends App.Controller
el: @$('.js-textarea').parent()
data:
ticket: ticket
- user: App.Session.get()
+ user: App.Session.get()
config: App.Config.all()
+ taskKey: @taskKey
)
callback = (ticket) ->
textModule.reload(
diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_text_module.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_text_module.coffee
new file mode 100644
index 000000000..667cd5bd7
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_text_module.coffee
@@ -0,0 +1,12 @@
+class TicketZoomFormHandlerTextModule
+
+ # central method, is getting called on every ticket form change
+ # but only trigger event for group_id changes
+ @run: (params, attribute, attributes, classname, form, ui) ->
+
+ return if attribute.name isnt 'group_id'
+
+ App.Event.trigger('TextModulePreconditionUpdate', { taskKey: ui.taskKey, params: params })
+
+App.Config.set('110-ticketFormTextModule', TicketZoomFormHandlerTextModule, 'TicketZoomFormHandler')
+App.Config.set('110-ticketFormTextModule', TicketZoomFormHandlerTextModule, 'TicketCreateFormHandler')
diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee
index cc7c8f57b..fd345caa8 100644
--- a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee
+++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee
@@ -29,6 +29,7 @@ class Edit extends App.ObserverController
formMeta: @formMeta
params: defaults
isDisabled: !ticket.editable()
+ taskKey: @taskKey
#bookmarkable: true
)
else
@@ -41,6 +42,7 @@ class Edit extends App.ObserverController
formMeta: @formMeta
params: defaults
isDisabled: ticket.editable()
+ taskKey: @taskKey
#bookmarkable: true
)
@@ -122,6 +124,7 @@ class SidebarTicket extends App.Controller
taskGet: @taskGet
formMeta: @formMeta
markForm: @markForm
+ taskKey: @taskKey
)
if @permissionCheck('ticket.agent')
diff --git a/app/assets/javascripts/app/controllers/widget/text_module.coffee b/app/assets/javascripts/app/controllers/widget/text_module.coffee
index 09dc5468c..a9e8df217 100644
--- a/app/assets/javascripts/app/controllers/widget/text_module.coffee
+++ b/app/assets/javascripts/app/controllers/widget/text_module.coffee
@@ -1,4 +1,5 @@
class App.WidgetTextModule extends App.Controller
+ searchCondition: {}
constructor: ->
super
@@ -18,6 +19,12 @@ class App.WidgetTextModule extends App.Controller
@subscribeId = App.TextModule.subscribe(@update, initFetch: true)
+ @bind('TextModulePreconditionUpdate', (data) =>
+ return if data.taskKey isnt @taskKey
+ @searchCondition = data.params
+ @update()
+ )
+
release: =>
App.TextModule.unsubscribe(@subscribeId)
@@ -26,17 +33,27 @@ class App.WidgetTextModule extends App.Controller
@data = data
@update()
+ currentCollection: =>
+ @all
+
update: =>
allRaw = App.TextModule.all()
- all = []
+ @all = []
+
for item in allRaw
- if item.active is true
- attributes = item.attributes()
- attributes.content = App.Utils.replaceTags(attributes.content, @data)
- all.push attributes
+
+ if item.active isnt true
+ continue
+
+ if !_.isEmpty(item.group_ids) && @searchCondition.group_id && !_.includes(item.group_ids, parseInt(@searchCondition.group_id))
+ continue
+
+ attributes = item.attributes()
+ attributes.content = App.Utils.replaceTags(attributes.content, @data)
+ @all.push attributes
# set new data
if @bindElements[0]
for element in @bindElements
if $(element).data().plugin_textmodule
- $(element).data().plugin_textmodule.collection = all
+ $(element).data().plugin_textmodule.collection = @all
diff --git a/app/assets/javascripts/app/models/text_module.coffee b/app/assets/javascripts/app/models/text_module.coffee
index e09958de0..5bb1f223d 100644
--- a/app/assets/javascripts/app/models/text_module.coffee
+++ b/app/assets/javascripts/app/models/text_module.coffee
@@ -3,8 +3,8 @@ class App.TextModule extends App.Model
@extend Spine.Model.Ajax
@url: @apiPath + '/text_modules'
@configure_attributes = [
- { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
- { name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, null: true },
+ { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
+ { name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, null: true },
{ name: 'content', display: 'Content', tag: 'richtext', limit: 2000, null: false, plugins: [
{
controller: 'WidgetPlaceholder'
@@ -24,6 +24,7 @@ class App.TextModule extends App.Model
}
], note: 'To select placeholders from a list, just enter "::".'},
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
+ { name: 'group_ids', display: 'Groups', tag: 'column_select', relation: 'Group', null: true },
{ name: 'active', display: 'Active', tag: 'active', default: true },
]
@configure_delete = true
@@ -32,6 +33,7 @@ class App.TextModule extends App.Model
'name',
'keywords',
'content',
+ 'group_ids',
]
# coffeelint: disable=no_interpolation_in_single_quotes
diff --git a/app/models/text_module.rb b/app/models/text_module.rb
index 574fa2aa1..89f49d2e4 100644
--- a/app/models/text_module.rb
+++ b/app/models/text_module.rb
@@ -15,6 +15,8 @@ class TextModule < ApplicationModel
csv_delete_possible true
+ has_and_belongs_to_many :groups, after_add: :cache_update, after_remove: :cache_update, class_name: 'Group'
+
=begin
load text modules from online
diff --git a/app/views/tests/text_module.html.erb b/app/views/tests/text_module.html.erb
new file mode 100644
index 000000000..e41a8208d
--- /dev/null
+++ b/app/views/tests/text_module.html.erb
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/routes/test.rb b/config/routes/test.rb
index e6c0afb63..cb74f7deb 100644
--- a/config/routes/test.rb
+++ b/config/routes/test.rb
@@ -21,6 +21,7 @@ Zammad::Application.routes.draw do
match '/tests_html_utils', to: 'tests#html_utils', via: :get
match '/tests_ticket_selector', to: 'tests#ticket_selector', via: :get
match '/tests_taskbar', to: 'tests#taskbar', via: :get
+ match '/tests_text_module', to: 'tests#text_module', via: :get
match '/tests/wait/:sec', to: 'tests#wait', via: :get
match '/tests/unprocessable_entity', to: 'tests#error_unprocessable_entity', via: :get
match '/tests/not_authorized', to: 'tests#error_not_authorized', via: :get
diff --git a/db/migrate/20190613000001_group_dependent_text_modules.rb b/db/migrate/20190613000001_group_dependent_text_modules.rb
new file mode 100644
index 000000000..1fa123508
--- /dev/null
+++ b/db/migrate/20190613000001_group_dependent_text_modules.rb
@@ -0,0 +1,5 @@
+class GroupDependentTextModules < ActiveRecord::Migration[5.1]
+ def change
+ rename_table :text_modules_groups, :groups_text_modules
+ end
+end
diff --git a/public/assets/tests/text_module.js b/public/assets/tests/text_module.js
new file mode 100644
index 000000000..06d023a4e
--- /dev/null
+++ b/public/assets/tests/text_module.js
@@ -0,0 +1,103 @@
+// text module
+test('test text module behaviour with group_ids', function() {
+
+ // active textmodule without group_ids
+ App.TextModule.refresh([
+ {
+ id: 1,
+ name: 'main',
+ keywords: 'keywordsmain',
+ content: 'contentmain',
+ active: true,
+ },
+ {
+ id: 2,
+ name: 'test2',
+ keywords: 'keywords2',
+ content: 'content2',
+ active: false,
+ },
+ {
+ id: 3,
+ name: 'test3',
+ keywords: 'keywords3',
+ content: 'content3',
+ active: true,
+ group_ids: [1,2],
+ },
+ {
+ id: 4,
+ name: 'test4',
+ keywords: 'keywords4',
+ content: 'content4',
+ active: false,
+ group_ids: [1,2],
+ },
+ ])
+
+ var textModule = new App.WidgetTextModule({
+ el: $('.js-textarea').parent(),
+ data:{
+ user: App.Session.get(),
+ config: App.Config.all(),
+ },
+ taskKey: 'test1',
+ })
+
+ var currentCollection = textModule.currentCollection();
+
+ equal(currentCollection.length, 2, 'active textmodule')
+ equal(currentCollection[0].id, 1)
+ equal(currentCollection[1].id, 3)
+
+ // trigered TextModulePreconditionUpdate with group_id
+
+ var params = {
+ group_id: 1
+ }
+ App.Event.trigger('TextModulePreconditionUpdate', { taskKey: 'test1', params: params })
+
+ currentCollection = textModule.currentCollection();
+
+ equal(currentCollection.length, 2, 'trigered TextModulePreconditionUpdate with group_id')
+ equal(currentCollection[0].id, 1)
+ equal(currentCollection[1].id, 3)
+
+ // trigered TextModulePreconditionUpdate with wrong group_id
+
+ params = {
+ group_id: 3
+ }
+ App.Event.trigger('TextModulePreconditionUpdate', { taskKey: 'test1', params: params })
+
+ currentCollection = textModule.currentCollection();
+
+ equal(currentCollection.length, 1, 'trigered TextModulePreconditionUpdate with wrong group_id')
+ equal(currentCollection[0].id, 1)
+
+ // trigered TextModulePreconditionUpdate with group_id but wrong taskKey
+
+ params = {
+ group_id: 3
+ }
+ App.Event.trigger('TextModulePreconditionUpdate', { taskKey: 'test2', params: params })
+
+ currentCollection = textModule.currentCollection();
+
+ equal(currentCollection.length, 1, 'trigered TextModulePreconditionUpdate with group_id but wrong taskKey - nothing has changed')
+ equal(currentCollection[0].id, 1)
+
+ // trigered TextModulePreconditionUpdate without group_id
+
+ params = {
+ owner_id: 2
+ }
+ App.Event.trigger('TextModulePreconditionUpdate', { taskKey: 'test1', params: params })
+
+ currentCollection = textModule.currentCollection();
+
+ equal(currentCollection.length, 2, 'trigered TextModulePreconditionUpdate without group_id')
+ equal(currentCollection[0].id, 1)
+ equal(currentCollection[1].id, 3)
+
+});
diff --git a/spec/factories/text_module.rb b/spec/factories/text_module.rb
new file mode 100644
index 000000000..88d47afec
--- /dev/null
+++ b/spec/factories/text_module.rb
@@ -0,0 +1,9 @@
+FactoryBot.define do
+ factory :text_module do
+ name { 'text module ' + Faker::Number.unique.number(3) }
+ keywords { Faker::Superhero.prefix }
+ content { Faker::Lorem.sentence }
+ updated_by_id { 1 }
+ created_by_id { 1 }
+ end
+end
diff --git a/spec/support/capybara/common_actions.rb b/spec/support/capybara/common_actions.rb
index f9194a39b..60193e1e6 100644
--- a/spec/support/capybara/common_actions.rb
+++ b/spec/support/capybara/common_actions.rb
@@ -66,6 +66,25 @@ module CommonActions
find('.user-menu .user a')[:title]
end
+ # Returns the User record for the currently logged in user.
+ #
+ # @example
+ # current_user.login
+ # => 'master@example.com'
+ #
+ # @example
+ # current_user do |user|
+ # user.group_names_access_map = group_names_access_map
+ # user.save!
+ # end
+ #
+ # @return [User] the current user record.
+ def current_user
+ ::User.find_by(login: current_login).tap do |user|
+ yield user if block_given?
+ end
+ end
+
# Logs out the currently logged in user.
#
# @example
diff --git a/spec/support/capybara/selectors.rb b/spec/support/capybara/selectors.rb
index 58bb2fccb..0e2c7da51 100644
--- a/spec/support/capybara/selectors.rb
+++ b/spec/support/capybara/selectors.rb
@@ -17,5 +17,5 @@ Capybara.add_selector(:clues_close) do
end
Capybara.add_selector(:richtext) do
- css { |name| "div[data-name=#{name}]" }
+ css { |name| "div[data-name=#{name || 'body'}]" }
end
diff --git a/spec/system/examples/text_modules_group_dependency_examples.rb b/spec/system/examples/text_modules_group_dependency_examples.rb
new file mode 100644
index 000000000..3d716bcdb
--- /dev/null
+++ b/spec/system/examples/text_modules_group_dependency_examples.rb
@@ -0,0 +1,51 @@
+RSpec.shared_examples 'group-dependent text modules' do |path:|
+
+ let!(:group1) { create :group }
+ let!(:group2) { create :group }
+ let!(:text_module_without_group) { create :text_module }
+ let!(:text_module_group1) { create :text_module, groups: [group1] }
+ let!(:text_module_group2) { create :text_module, groups: [group2] }
+
+ it 'supports group-dependent text modules' do
+
+ # give user access to all groups including those created
+ # by using FactoryBot outside of the example
+ group_names_access_map = Group.all.pluck(:name).each_with_object({}) do |group_name, result|
+ result[group_name] = 'full'.freeze
+ end
+
+ current_user do |user|
+ user.group_names_access_map = group_names_access_map
+ user.save!
+ end
+
+ visit path
+
+ within(:active_content) do
+
+ selector_group_select = 'select[name="group_id"]'
+ selector_text_module_selection = '.shortcut'
+ selector_text_module_item = ".shortcut > ul > li[data-id='%s']"
+
+ # exercise
+ find(selector_group_select).find(:option, group1.name).select_option
+ find(:richtext).send_keys('::')
+
+ # expectations
+ expect(page).to have_css(selector_text_module_selection, wait: 3)
+ expect(page).to have_css(format(selector_text_module_item, text_module_without_group.id))
+ expect(page).to have_css(format(selector_text_module_item, text_module_group1.id))
+ expect(page).to have_no_css(format(selector_text_module_item, text_module_group2.id))
+
+ # exercise
+ find(selector_group_select).find(:option, group2.name).select_option
+ find(:richtext).send_keys('::')
+
+ # expectations
+ expect(page).to have_css(selector_text_module_selection, wait: 3)
+ expect(page).to have_css(format(selector_text_module_item, text_module_without_group.id))
+ expect(page).to have_no_css(format(selector_text_module_item, text_module_group1.id))
+ expect(page).to have_css(format(selector_text_module_item, text_module_group2.id))
+ end
+ end
+end
diff --git a/spec/system/ticket/create_spec.rb b/spec/system/ticket/create_spec.rb
index 5a6c48295..f04bd1ed7 100644
--- a/spec/system/ticket/create_spec.rb
+++ b/spec/system/ticket/create_spec.rb
@@ -1,5 +1,7 @@
require 'rails_helper'
+require 'system/examples/text_modules_group_dependency_examples'
+
RSpec.describe 'Ticket Create', type: :system do
context 'when applying ticket templates' do
# Regression test for issue #2424 - Unavailable ticket template attributes get applied
@@ -30,4 +32,8 @@ RSpec.describe 'Ticket Create', type: :system do
expect(page).not_to have_selector 'select[name="group_id"]'
end
end
+
+ context 'when using text modules' do
+ include_examples 'group-dependent text modules', path: 'ticket/create'
+ end
end
diff --git a/spec/system/ticket/update_spec.rb b/spec/system/ticket/update_spec.rb
index 9f664782c..6f8838c0a 100644
--- a/spec/system/ticket/update_spec.rb
+++ b/spec/system/ticket/update_spec.rb
@@ -1,5 +1,7 @@
require 'rails_helper'
+require 'system/examples/text_modules_group_dependency_examples'
+
RSpec.describe 'Ticket Update', type: :system do
let(:group) { Group.find_by(name: 'Users') }
@@ -83,7 +85,7 @@ RSpec.describe 'Ticket Update', type: :system do
})
# refresh browser to get macro accessable
- page.driver.browser.navigate.refresh
+ refresh
# create a new ticket and attempt to update its state without the required select attribute
ticket = create(:ticket, group: group)
@@ -135,4 +137,8 @@ RSpec.describe 'Ticket Update', type: :system do
end
end
end
+
+ context 'when using text modules' do
+ include_examples 'group-dependent text modules', path: "#ticket/zoom/#{Ticket.first.id}"
+ end
end