Fixes #3757 - escaped 'Set fixed' workflows don't refresh set values on active ticket sessions.

This commit is contained in:
Rolf Schmidt 2021-09-29 10:03:04 +02:00 committed by Thorsten Eckel
parent b85e402807
commit 5f2181d8a3
9 changed files with 139 additions and 29 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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