Added migration for group_dependent_text_modules - renamed table text_modules_groups to groups_text_modules.
This commit is contained in:
parent
a295a84771
commit
c788c3e053
18 changed files with 280 additions and 17 deletions
|
@ -358,6 +358,7 @@ class App.TicketCreate extends App.Controller
|
||||||
data:
|
data:
|
||||||
config: App.Config.all()
|
config: App.Config.all()
|
||||||
user: App.Session.get()
|
user: App.Session.get()
|
||||||
|
taskKey: @taskKey
|
||||||
)
|
)
|
||||||
|
|
||||||
$('#tags').tokenfield()
|
$('#tags').tokenfield()
|
||||||
|
|
|
@ -238,6 +238,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
||||||
ticket: ticket
|
ticket: ticket
|
||||||
user: App.Session.get()
|
user: App.Session.get()
|
||||||
config: App.Config.all()
|
config: App.Config.all()
|
||||||
|
taskKey: @taskKey
|
||||||
)
|
)
|
||||||
callback = (ticket) ->
|
callback = (ticket) ->
|
||||||
textModule.reload(
|
textModule.reload(
|
||||||
|
|
|
@ -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')
|
|
@ -29,6 +29,7 @@ class Edit extends App.ObserverController
|
||||||
formMeta: @formMeta
|
formMeta: @formMeta
|
||||||
params: defaults
|
params: defaults
|
||||||
isDisabled: !ticket.editable()
|
isDisabled: !ticket.editable()
|
||||||
|
taskKey: @taskKey
|
||||||
#bookmarkable: true
|
#bookmarkable: true
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
@ -41,6 +42,7 @@ class Edit extends App.ObserverController
|
||||||
formMeta: @formMeta
|
formMeta: @formMeta
|
||||||
params: defaults
|
params: defaults
|
||||||
isDisabled: ticket.editable()
|
isDisabled: ticket.editable()
|
||||||
|
taskKey: @taskKey
|
||||||
#bookmarkable: true
|
#bookmarkable: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -122,6 +124,7 @@ class SidebarTicket extends App.Controller
|
||||||
taskGet: @taskGet
|
taskGet: @taskGet
|
||||||
formMeta: @formMeta
|
formMeta: @formMeta
|
||||||
markForm: @markForm
|
markForm: @markForm
|
||||||
|
taskKey: @taskKey
|
||||||
)
|
)
|
||||||
|
|
||||||
if @permissionCheck('ticket.agent')
|
if @permissionCheck('ticket.agent')
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
class App.WidgetTextModule extends App.Controller
|
class App.WidgetTextModule extends App.Controller
|
||||||
|
searchCondition: {}
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
|
||||||
|
@ -18,6 +19,12 @@ class App.WidgetTextModule extends App.Controller
|
||||||
|
|
||||||
@subscribeId = App.TextModule.subscribe(@update, initFetch: true)
|
@subscribeId = App.TextModule.subscribe(@update, initFetch: true)
|
||||||
|
|
||||||
|
@bind('TextModulePreconditionUpdate', (data) =>
|
||||||
|
return if data.taskKey isnt @taskKey
|
||||||
|
@searchCondition = data.params
|
||||||
|
@update()
|
||||||
|
)
|
||||||
|
|
||||||
release: =>
|
release: =>
|
||||||
App.TextModule.unsubscribe(@subscribeId)
|
App.TextModule.unsubscribe(@subscribeId)
|
||||||
|
|
||||||
|
@ -26,17 +33,27 @@ class App.WidgetTextModule extends App.Controller
|
||||||
@data = data
|
@data = data
|
||||||
@update()
|
@update()
|
||||||
|
|
||||||
|
currentCollection: =>
|
||||||
|
@all
|
||||||
|
|
||||||
update: =>
|
update: =>
|
||||||
allRaw = App.TextModule.all()
|
allRaw = App.TextModule.all()
|
||||||
all = []
|
@all = []
|
||||||
|
|
||||||
for item in allRaw
|
for item in allRaw
|
||||||
if item.active is true
|
|
||||||
|
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 = item.attributes()
|
||||||
attributes.content = App.Utils.replaceTags(attributes.content, @data)
|
attributes.content = App.Utils.replaceTags(attributes.content, @data)
|
||||||
all.push attributes
|
@all.push attributes
|
||||||
|
|
||||||
# set new data
|
# set new data
|
||||||
if @bindElements[0]
|
if @bindElements[0]
|
||||||
for element in @bindElements
|
for element in @bindElements
|
||||||
if $(element).data().plugin_textmodule
|
if $(element).data().plugin_textmodule
|
||||||
$(element).data().plugin_textmodule.collection = all
|
$(element).data().plugin_textmodule.collection = @all
|
||||||
|
|
|
@ -24,6 +24,7 @@ class App.TextModule extends App.Model
|
||||||
}
|
}
|
||||||
], note: 'To select placeholders from a list, just enter "::".'},
|
], note: 'To select placeholders from a list, just enter "::".'},
|
||||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
{ 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 },
|
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||||
]
|
]
|
||||||
@configure_delete = true
|
@configure_delete = true
|
||||||
|
@ -32,6 +33,7 @@ class App.TextModule extends App.Model
|
||||||
'name',
|
'name',
|
||||||
'keywords',
|
'keywords',
|
||||||
'content',
|
'content',
|
||||||
|
'group_ids',
|
||||||
]
|
]
|
||||||
|
|
||||||
# coffeelint: disable=no_interpolation_in_single_quotes
|
# coffeelint: disable=no_interpolation_in_single_quotes
|
||||||
|
|
|
@ -15,6 +15,8 @@ class TextModule < ApplicationModel
|
||||||
|
|
||||||
csv_delete_possible true
|
csv_delete_possible true
|
||||||
|
|
||||||
|
has_and_belongs_to_many :groups, after_add: :cache_update, after_remove: :cache_update, class_name: 'Group'
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
||||||
load text modules from online
|
load text modules from online
|
||||||
|
|
25
app/views/tests/text_module.html.erb
Normal file
25
app/views/tests/text_module.html.erb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/assets/tests/qunit-1.21.0.css">
|
||||||
|
<script src="/assets/tests/qunit-1.21.0.js"></script>
|
||||||
|
<script src="/assets/tests/text_module.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="qunit" class="u-dontfold"></div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<form class="form-stacked pull-left">
|
||||||
|
<div id="forms">
|
||||||
|
<div class="js-textarea richtext-content articleNewEdit-body" contenteditable="true" data-name="body">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -21,6 +21,7 @@ Zammad::Application.routes.draw do
|
||||||
match '/tests_html_utils', to: 'tests#html_utils', via: :get
|
match '/tests_html_utils', to: 'tests#html_utils', via: :get
|
||||||
match '/tests_ticket_selector', to: 'tests#ticket_selector', via: :get
|
match '/tests_ticket_selector', to: 'tests#ticket_selector', via: :get
|
||||||
match '/tests_taskbar', to: 'tests#taskbar', 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/wait/:sec', to: 'tests#wait', via: :get
|
||||||
match '/tests/unprocessable_entity', to: 'tests#error_unprocessable_entity', via: :get
|
match '/tests/unprocessable_entity', to: 'tests#error_unprocessable_entity', via: :get
|
||||||
match '/tests/not_authorized', to: 'tests#error_not_authorized', via: :get
|
match '/tests/not_authorized', to: 'tests#error_not_authorized', via: :get
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class GroupDependentTextModules < ActiveRecord::Migration[5.1]
|
||||||
|
def change
|
||||||
|
rename_table :text_modules_groups, :groups_text_modules
|
||||||
|
end
|
||||||
|
end
|
103
public/assets/tests/text_module.js
Normal file
103
public/assets/tests/text_module.js
Normal file
|
@ -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)
|
||||||
|
|
||||||
|
});
|
9
spec/factories/text_module.rb
Normal file
9
spec/factories/text_module.rb
Normal file
|
@ -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
|
|
@ -66,6 +66,25 @@ module CommonActions
|
||||||
find('.user-menu .user a')[:title]
|
find('.user-menu .user a')[:title]
|
||||||
end
|
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.
|
# Logs out the currently logged in user.
|
||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
|
|
|
@ -17,5 +17,5 @@ Capybara.add_selector(:clues_close) do
|
||||||
end
|
end
|
||||||
|
|
||||||
Capybara.add_selector(:richtext) do
|
Capybara.add_selector(:richtext) do
|
||||||
css { |name| "div[data-name=#{name}]" }
|
css { |name| "div[data-name=#{name || 'body'}]" }
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -1,5 +1,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
|
require 'system/examples/text_modules_group_dependency_examples'
|
||||||
|
|
||||||
RSpec.describe 'Ticket Create', type: :system do
|
RSpec.describe 'Ticket Create', type: :system do
|
||||||
context 'when applying ticket templates' do
|
context 'when applying ticket templates' do
|
||||||
# Regression test for issue #2424 - Unavailable ticket template attributes get applied
|
# 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"]'
|
expect(page).not_to have_selector 'select[name="group_id"]'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when using text modules' do
|
||||||
|
include_examples 'group-dependent text modules', path: 'ticket/create'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
|
require 'system/examples/text_modules_group_dependency_examples'
|
||||||
|
|
||||||
RSpec.describe 'Ticket Update', type: :system do
|
RSpec.describe 'Ticket Update', type: :system do
|
||||||
|
|
||||||
let(:group) { Group.find_by(name: 'Users') }
|
let(:group) { Group.find_by(name: 'Users') }
|
||||||
|
@ -83,7 +85,7 @@ RSpec.describe 'Ticket Update', type: :system do
|
||||||
})
|
})
|
||||||
|
|
||||||
# refresh browser to get macro accessable
|
# 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
|
# create a new ticket and attempt to update its state without the required select attribute
|
||||||
ticket = create(:ticket, group: group)
|
ticket = create(:ticket, group: group)
|
||||||
|
@ -135,4 +137,8 @@ RSpec.describe 'Ticket Update', type: :system do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when using text modules' do
|
||||||
|
include_examples 'group-dependent text modules', path: "#ticket/zoom/#{Ticket.first.id}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue