Fixes #3757 - escaped 'Set fixed' workflows don't refresh set values on active ticket sessions.
This commit is contained in:
parent
b85e402807
commit
5f2181d8a3
9 changed files with 139 additions and 29 deletions
|
@ -128,9 +128,10 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec
|
||||||
@buildValueConfigMultiple: (config, meta) ->
|
@buildValueConfigMultiple: (config, meta) ->
|
||||||
if _.contains(['add_option', 'remove_option', 'set_fixed_to'], meta.operator)
|
if _.contains(['add_option', 'remove_option', 'set_fixed_to'], meta.operator)
|
||||||
config.multiple = true
|
config.multiple = true
|
||||||
|
config.nulloption = true
|
||||||
else
|
else
|
||||||
config.multiple = false
|
config.multiple = false
|
||||||
config.nulloption = false
|
config.nulloption = false
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@HasPreCondition: ->
|
@HasPreCondition: ->
|
||||||
|
|
|
@ -119,7 +119,9 @@ class App.FormHandlerCoreWorkflow
|
||||||
|
|
||||||
valueFound = false
|
valueFound = false
|
||||||
for value in values
|
for value in values
|
||||||
if value && paramValue
|
|
||||||
|
# false values are valid values e.g. for boolean fields (be careful)
|
||||||
|
if value isnt undefined && paramValue isnt undefined
|
||||||
if value.toString() == paramValue.toString()
|
if value.toString() == paramValue.toString()
|
||||||
valueFound = true
|
valueFound = true
|
||||||
break
|
break
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
class Edit extends App.ControllerObserver
|
# No usage of a ControllerObserver here because we want to use
|
||||||
model: 'Ticket'
|
# the data of the ticket zoom ajax request which is using the all=true parameter
|
||||||
observeNot:
|
# and contain the core workflow information as well. Without observer we also
|
||||||
created_at: true
|
# dont have double rendering because of the zoom (all=true) and observer (full=true) render callback
|
||||||
updated_at: true
|
class Edit extends App.Controller
|
||||||
globalRerender: false
|
constructor: (params) ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
|
||||||
render: (ticket, diff) =>
|
render: =>
|
||||||
defaults = ticket.attributes()
|
defaults = @ticket.attributes()
|
||||||
delete defaults.article # ignore article infos
|
delete defaults.article # ignore article infos
|
||||||
followUpPossible = App.Group.find(defaults.group_id).follow_up_possible
|
followUpPossible = App.Group.find(defaults.group_id).follow_up_possible
|
||||||
ticketState = App.TicketState.find(defaults.state_id).name
|
ticketState = App.TicketState.find(defaults.state_id).name
|
||||||
|
@ -19,7 +21,7 @@ class Edit extends App.ControllerObserver
|
||||||
|
|
||||||
if followUpPossible == 'new_ticket' && ticketState != 'closed' ||
|
if followUpPossible == 'new_ticket' && ticketState != 'closed' ||
|
||||||
followUpPossible != 'new_ticket' ||
|
followUpPossible != 'new_ticket' ||
|
||||||
@permissionCheck('admin') || ticket.currentView() is 'agent'
|
@permissionCheck('admin') || @ticket.currentView() is 'agent'
|
||||||
@controllerFormSidebarTicket = new App.ControllerForm(
|
@controllerFormSidebarTicket = new App.ControllerForm(
|
||||||
elReplace: @el
|
elReplace: @el
|
||||||
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
model: { className: 'Ticket', configure_attributes: @formMeta.configure_attributes || App.Ticket.configure_attributes }
|
||||||
|
@ -28,7 +30,7 @@ class Edit extends App.ControllerObserver
|
||||||
filter: @formMeta.filter
|
filter: @formMeta.filter
|
||||||
formMeta: @formMeta
|
formMeta: @formMeta
|
||||||
params: defaults
|
params: defaults
|
||||||
isDisabled: !ticket.editable()
|
isDisabled: !@ticket.editable()
|
||||||
taskKey: @taskKey
|
taskKey: @taskKey
|
||||||
core_workflow: {
|
core_workflow: {
|
||||||
callbacks: [@markForm]
|
callbacks: [@markForm]
|
||||||
|
@ -44,7 +46,7 @@ class Edit extends App.ControllerObserver
|
||||||
filter: @formMeta.filter
|
filter: @formMeta.filter
|
||||||
formMeta: @formMeta
|
formMeta: @formMeta
|
||||||
params: defaults
|
params: defaults
|
||||||
isDisabled: ticket.editable()
|
isDisabled: @ticket.editable()
|
||||||
taskKey: @taskKey
|
taskKey: @taskKey
|
||||||
core_workflow: {
|
core_workflow: {
|
||||||
callbacks: [@markForm]
|
callbacks: [@markForm]
|
||||||
|
@ -57,8 +59,8 @@ class Edit extends App.ControllerObserver
|
||||||
return if @resetBind
|
return if @resetBind
|
||||||
@resetBind = true
|
@resetBind = true
|
||||||
@controllerBind('ui::ticket::taskReset', (data) =>
|
@controllerBind('ui::ticket::taskReset', (data) =>
|
||||||
return if data.ticket_id.toString() isnt ticket.id.toString()
|
return if data.ticket_id.toString() isnt @ticket.id.toString()
|
||||||
@render(ticket)
|
@render()
|
||||||
)
|
)
|
||||||
|
|
||||||
class SidebarTicket extends App.Controller
|
class SidebarTicket extends App.Controller
|
||||||
|
@ -128,6 +130,7 @@ class SidebarTicket extends App.Controller
|
||||||
|
|
||||||
@edit = new Edit(
|
@edit = new Edit(
|
||||||
object_id: @ticket.id
|
object_id: @ticket.id
|
||||||
|
ticket: @ticket
|
||||||
el: localEl.find('.edit')
|
el: localEl.find('.edit')
|
||||||
taskGet: @taskGet
|
taskGet: @taskGet
|
||||||
formMeta: @formMeta
|
formMeta: @formMeta
|
||||||
|
|
|
@ -30,10 +30,26 @@ class CoreWorkflow::Attributes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def selectable_field?(key)
|
||||||
|
return if key == 'id'
|
||||||
|
return if !@payload['params'].key?(key)
|
||||||
|
|
||||||
|
# some objects have no attributes like "CoreWorkflow"-object as well.
|
||||||
|
# attributes only exists in the frontend so we skip this check
|
||||||
|
return true if object_elements.blank?
|
||||||
|
|
||||||
|
object_elements_hash.key?(key)
|
||||||
|
end
|
||||||
|
|
||||||
def overwrite_selected(result)
|
def overwrite_selected(result)
|
||||||
selected_attributes = selected_only.attributes
|
selected_attributes = selected_only.attributes
|
||||||
selected_attributes.each_key do |key|
|
selected_attributes.each_key do |key|
|
||||||
next if selected_attributes[key].nil?
|
next if !selectable_field?(key)
|
||||||
|
|
||||||
|
# special behaviour for owner id
|
||||||
|
if key == 'owner_id' && selected_attributes[key].nil?
|
||||||
|
selected_attributes[key] = 1
|
||||||
|
end
|
||||||
|
|
||||||
result[key.to_sym] = selected_attributes[key]
|
result[key.to_sym] = selected_attributes[key]
|
||||||
end
|
end
|
||||||
|
@ -55,7 +71,10 @@ class CoreWorkflow::Attributes
|
||||||
# dont use lookup here because the cache will not
|
# dont use lookup here because the cache will not
|
||||||
# know about new attributes and make crashes
|
# know about new attributes and make crashes
|
||||||
@saved_only ||= payload_class.find_by(id: @payload['params']['id'])
|
@saved_only ||= payload_class.find_by(id: @payload['params']['id'])
|
||||||
@saved_only.dup
|
|
||||||
|
# we use marshal here because clone still uses references and dup can't
|
||||||
|
# detect changes for the rails object
|
||||||
|
Marshal.load(Marshal.dump(@saved_only))
|
||||||
end
|
end
|
||||||
|
|
||||||
def saved
|
def saved
|
||||||
|
@ -68,6 +87,10 @@ class CoreWorkflow::Attributes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def object_elements_hash
|
||||||
|
@object_elements_hash ||= object_elements.index_by { |x| x[:name] }
|
||||||
|
end
|
||||||
|
|
||||||
def screen_value(attribute, type)
|
def screen_value(attribute, type)
|
||||||
attribute[:screens].dig(@payload['screen'], type)
|
attribute[:screens].dig(@payload['screen'], type)
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,4 +18,26 @@ class CoreWorkflow::Result::Backend
|
||||||
def result(backend, field, value = nil)
|
def result(backend, field, value = nil)
|
||||||
@result_object.run_backend_value(backend, field, value)
|
@result_object.run_backend_value(backend, field, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def saved_value
|
||||||
|
|
||||||
|
# make sure we have a saved object
|
||||||
|
return if @result_object.attributes.saved_only.blank?
|
||||||
|
|
||||||
|
# we only want to have the saved value in the restrictions
|
||||||
|
# if no changes happend to the form. If the users does changes
|
||||||
|
# to the form then also the saved value should get removed
|
||||||
|
return if @result_object.attributes.selected.changed?
|
||||||
|
|
||||||
|
# attribute can be blank e.g. in custom development
|
||||||
|
# or if attribute is only available in the frontend but not
|
||||||
|
# in the backend
|
||||||
|
return if attribute.blank?
|
||||||
|
|
||||||
|
@result_object.attributes.saved_attribute_value(attribute).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def attribute
|
||||||
|
@attribute ||= @result_object.attributes.object_elements_hash[field]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,8 +3,14 @@
|
||||||
class CoreWorkflow::Result::RemoveOption < CoreWorkflow::Result::BaseOption
|
class CoreWorkflow::Result::RemoveOption < CoreWorkflow::Result::BaseOption
|
||||||
def run
|
def run
|
||||||
@result_object.result[:restrict_values][field] ||= Array(@result_object.payload['params'][field])
|
@result_object.result[:restrict_values][field] ||= Array(@result_object.payload['params'][field])
|
||||||
@result_object.result[:restrict_values][field] -= Array(@perform_config['remove_option'])
|
@result_object.result[:restrict_values][field] -= Array(config_value)
|
||||||
remove_excluded_param_values
|
remove_excluded_param_values
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def config_value
|
||||||
|
result = Array(@perform_config['remove_option'])
|
||||||
|
result -= Array(saved_value)
|
||||||
|
result
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,21 +5,23 @@ class CoreWorkflow::Result::SetFixedTo < CoreWorkflow::Result::BaseOption
|
||||||
@result_object.result[:restrict_values][field] = if restriction_set?
|
@result_object.result[:restrict_values][field] = if restriction_set?
|
||||||
restrict_values
|
restrict_values
|
||||||
else
|
else
|
||||||
replace_values
|
config_value
|
||||||
end
|
end
|
||||||
remove_excluded_param_values
|
remove_excluded_param_values
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def config_value
|
||||||
|
result = Array(@perform_config['set_fixed_to'])
|
||||||
|
result |= Array(saved_value)
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
def restriction_set?
|
def restriction_set?
|
||||||
@result_object.result[:restrict_values][field]
|
@result_object.result[:restrict_values][field]
|
||||||
end
|
end
|
||||||
|
|
||||||
def restrict_values
|
def restrict_values
|
||||||
@result_object.result[:restrict_values][field].reject { |v| Array(@perform_config['set_fixed_to']).exclude?(v) }
|
@result_object.result[:restrict_values][field].reject { |v| config_value.exclude?(v) }
|
||||||
end
|
|
||||||
|
|
||||||
def replace_values
|
|
||||||
Array(@perform_config['set_fixed_to'])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -525,15 +525,15 @@ RSpec.shared_examples 'core workflow' do
|
||||||
perform: {
|
perform: {
|
||||||
"#{object_name.downcase}.#{field_name}": {
|
"#{object_name.downcase}.#{field_name}": {
|
||||||
operator: 'set_fixed_to',
|
operator: 'set_fixed_to',
|
||||||
set_fixed_to: %w[true]
|
set_fixed_to: %w[false]
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does perform' do
|
it 'does perform' do
|
||||||
before_it.call
|
before_it.call
|
||||||
expect(page).to have_selector("select[name='#{field_name}'] option[value='true']", wait: 10)
|
expect(page).to have_selector("select[name='#{field_name}'] option[value='false']", wait: 10)
|
||||||
expect(page).to have_no_selector("select[name='#{field_name}'] option[value='false']", wait: 10)
|
expect(page).to have_no_selector("select[name='#{field_name}'] option[value='true']", wait: 10)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -562,7 +562,7 @@ RSpec.shared_examples 'core workflow' do
|
||||||
perform: {
|
perform: {
|
||||||
"#{object_name.downcase}.#{field_name}": {
|
"#{object_name.downcase}.#{field_name}": {
|
||||||
operator: 'set_fixed_to',
|
operator: 'set_fixed_to',
|
||||||
set_fixed_to: ['', 'true'],
|
set_fixed_to: ['', 'false'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
create(:core_workflow,
|
create(:core_workflow,
|
||||||
|
@ -577,7 +577,7 @@ RSpec.shared_examples 'core workflow' do
|
||||||
|
|
||||||
it 'does perform' do
|
it 'does perform' do
|
||||||
before_it.call
|
before_it.call
|
||||||
expect(page).to have_selector("select[name='#{field_name}'] option[value='true'][selected]", wait: 10)
|
expect(page).to have_selector("select[name='#{field_name}'] option[value='false'][selected]", wait: 10)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2083,4 +2083,55 @@ RSpec.describe 'Ticket zoom', type: :system do
|
||||||
expect(ticket.reload.owner_id).to eq(admin.id)
|
expect(ticket.reload.owner_id).to eq(admin.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "escaped 'Set fixed' workflows don't refresh set values on active ticket sessions #3757", authenticated_as: :authenticate, db_strategy: :reset do
|
||||||
|
let(:field_name) { SecureRandom.uuid }
|
||||||
|
let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), field_name => false) }
|
||||||
|
|
||||||
|
def authenticate
|
||||||
|
workflow
|
||||||
|
create :object_manager_attribute_boolean, name: field_name, display: field_name, screens: attributes_for(:required_screen)
|
||||||
|
ObjectManager::Attribute.migration_execute
|
||||||
|
ticket
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
visit "#ticket/zoom/#{ticket.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when operator set_fixed_to' do
|
||||||
|
let(:workflow) do
|
||||||
|
create(:core_workflow,
|
||||||
|
object: 'Ticket',
|
||||||
|
perform: { "ticket.#{field_name}" => { 'operator' => 'set_fixed_to', 'set_fixed_to' => ['false'] } })
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when saved value is removed by set_fixed_to operator' do
|
||||||
|
it 'does show up the saved value if it would not be possible because of the restriction' do
|
||||||
|
expect(page.find("select[name='#{field_name}']").value).to eq('false')
|
||||||
|
ticket.update(field_name => true)
|
||||||
|
wait(10, interval: 0.5).until { page.find("select[name='#{field_name}']").value == 'true' }
|
||||||
|
expect(page.find("select[name='#{field_name}']").value).to eq('true')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when operator remove_option' do
|
||||||
|
let(:workflow) do
|
||||||
|
create(:core_workflow,
|
||||||
|
object: 'Ticket',
|
||||||
|
perform: { "ticket.#{field_name}" => { 'operator' => 'remove_option', 'remove_option' => ['true'] } })
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when saved value is removed by set_fixed_to operator' do
|
||||||
|
it 'does show up the saved value if it would not be possible because of the restriction' do
|
||||||
|
expect(page.find("select[name='#{field_name}']").value).to eq('false')
|
||||||
|
ticket.update(field_name => true)
|
||||||
|
wait(10, interval: 0.5).until { page.find("select[name='#{field_name}']").value == 'true' }
|
||||||
|
expect(page.find("select[name='#{field_name}']").value).to eq('true')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue