Added migration for group_dependent_text_modules - renamed table text_modules_groups to groups_text_modules.

This commit is contained in:
Denny Bresch 2019-07-03 18:14:28 +02:00 committed by Thorsten Eckel
parent a295a84771
commit c788c3e053
18 changed files with 280 additions and 17 deletions

View file

@ -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()

View file

@ -15,16 +15,16 @@ class Index extends App.ControllerSubContent
deleteOption: true deleteOption: true
) )
pageData: pageData:
home: 'text_modules' home: 'text_modules'
object: 'TextModule' object: 'TextModule'
objects: 'Text modules' objects: 'Text modules'
navupdate: '#text_modules' navupdate: '#text_modules'
notes: [ notes: [
'Text modules are ...' 'Text modules are ...'
] ]
buttons: [ buttons: [
{ name: 'Import', 'data-type': 'import', class: 'btn' } { name: 'Import', 'data-type': 'import', class: 'btn' }
{ name: 'New text module', 'data-type': 'new', class: 'btn--success' } { name: 'New text module', 'data-type': 'new', class: 'btn--success' }
] ]
container: @el.closest('.content') container: @el.closest('.content')
) )

View file

@ -236,8 +236,9 @@ class App.TicketZoomArticleNew extends App.Controller
el: @$('.js-textarea').parent() el: @$('.js-textarea').parent()
data: data:
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(

View file

@ -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')

View file

@ -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')

View file

@ -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
attributes = item.attributes() if item.active isnt true
attributes.content = App.Utils.replaceTags(attributes.content, @data) continue
all.push attributes
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 # 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

View file

@ -3,8 +3,8 @@ class App.TextModule extends App.Model
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/text_modules' @url: @apiPath + '/text_modules'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, { 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: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, null: true },
{ name: 'content', display: 'Content', tag: 'richtext', limit: 2000, null: false, plugins: [ { name: 'content', display: 'Content', tag: 'richtext', limit: 2000, null: false, plugins: [
{ {
controller: 'WidgetPlaceholder' controller: 'WidgetPlaceholder'
@ -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

View file

@ -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

View 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>

View file

@ -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

View file

@ -0,0 +1,5 @@
class GroupDependentTextModules < ActiveRecord::Migration[5.1]
def change
rename_table :text_modules_groups, :groups_text_modules
end
end

View 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)
});

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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