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:
|
||||
config: App.Config.all()
|
||||
user: App.Session.get()
|
||||
taskKey: @taskKey
|
||||
)
|
||||
|
||||
$('#tags').tokenfield()
|
||||
|
|
|
@ -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')
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
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_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
|
||||
|
|
|
@ -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]
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 '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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue