Fixes #2102 - Read-only custom objects.

This commit is contained in:
Rolf Schmidt 2021-09-20 18:12:33 +02:00 committed by Thorsten Eckel
parent 85cee64c73
commit d5d5b3b7b8
11 changed files with 261 additions and 25 deletions

View file

@ -371,6 +371,9 @@ class App.ControllerForm extends App.Controller
@fieldIsRemoved: (field) -> @fieldIsRemoved: (field) ->
return field.closest('.form-group').hasClass('is-removed') return field.closest('.form-group').hasClass('is-removed')
@fieldIsReadonly: (field) ->
return field.closest('.form-group').hasClass('is-readonly')
attributeIsMandatory: (name) -> attributeIsMandatory: (name) ->
field_by_name = @constructor.findFieldByName(name, @form) field_by_name = @constructor.findFieldByName(name, @form)
if field_by_name.length > 0 if field_by_name.length > 0
@ -438,6 +441,34 @@ class App.ControllerForm extends App.Controller
field_by_data.parents('.form-group').find('label span').html('') field_by_data.parents('.form-group').find('label span').html('')
field_by_data.closest('.form-group').removeClass('is-required') field_by_data.closest('.form-group').removeClass('is-required')
readonly: (name, el = @form) ->
if !_.isArray(name)
name = [name]
for key in name
field_by_name = @constructor.findFieldByName(key, el)
field_by_data = @constructor.findFieldByData(key, el)
if !@constructor.fieldIsReadonly(field_by_name)
field_by_name.closest('.form-group').find('input, select').attr('readonly', true)
field_by_name.closest('.form-group').addClass('is-readonly')
if !@constructor.fieldIsReadonly(field_by_data)
field_by_data.closest('.form-group').find('input, select').attr('readonly', true)
field_by_data.closest('.form-group').addClass('is-readonly')
changeable: (name, el = @form) ->
if !_.isArray(name)
name = [name]
for key in name
field_by_name = @constructor.findFieldByName(key, el)
field_by_data = @constructor.findFieldByData(key, el)
if @constructor.fieldIsReadonly(field_by_name)
field_by_name.closest('.form-group').find('input, select').attr('readonly', false)
field_by_name.closest('.form-group').removeClass('is-readonly')
if @constructor.fieldIsReadonly(field_by_data)
field_by_data.closest('.form-group').find('input, select').attr('readonly', false)
field_by_data.closest('.form-group').removeClass('is-readonly')
validate: (params) -> validate: (params) ->
App.Model.validate( App.Model.validate(
model: @model model: @model

View file

@ -37,17 +37,17 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec
delete groups[key] delete groups[key]
operatorsType = operatorsType =
'boolean$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to'] 'boolean$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'set_fixed_to']
'integer$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional'] 'integer$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly']
'^select$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select'] '^select$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select']
'^tree_select$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select'] '^tree_select$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select']
'^input$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'fill_in', 'fill_in_empty'] '^input$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'fill_in', 'fill_in_empty']
operatorsName = operatorsName =
'_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select'] '_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select']
'_ids$': ['show', 'hide', 'set_mandatory', 'set_optional'] '_ids$': ['show', 'hide', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly']
'organization_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option'] 'organization_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option']
'owner_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'select', 'auto_select'] 'owner_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'select', 'auto_select']
# merge config # merge config
elements = {} elements = {}

View file

@ -205,6 +205,16 @@ class App.FormHandlerCoreWorkflow
else else
ui.optional(field, form) ui.optional(field, form)
# changes the mandatory flag of form elements
@changeReadonly: (form, ui, data) ->
return if _.isEmpty(data)
for field, state of data
if state
ui.readonly(field, form)
else
ui.changeable(field, form)
# executes individual js commands of the Core Workflow engine # executes individual js commands of the Core Workflow engine
@executeEval: (form, ui, data) -> @executeEval: (form, ui, data) ->
return if _.isEmpty(data) return if _.isEmpty(data)
@ -226,6 +236,7 @@ class App.FormHandlerCoreWorkflow
App.FormHandlerCoreWorkflow.fillIn(classname, form, ui, attributes, params, data.fill_in) App.FormHandlerCoreWorkflow.fillIn(classname, form, ui, attributes, params, data.fill_in)
App.FormHandlerCoreWorkflow.changeVisibility(form, ui, data.visibility) App.FormHandlerCoreWorkflow.changeVisibility(form, ui, data.visibility)
App.FormHandlerCoreWorkflow.changeMandatory(form, ui, data.mandatory, data.visibility) App.FormHandlerCoreWorkflow.changeMandatory(form, ui, data.mandatory, data.visibility)
App.FormHandlerCoreWorkflow.changeReadonly(form, ui, data.readonly)
App.FormHandlerCoreWorkflow.executeEval(form, ui, data.eval) App.FormHandlerCoreWorkflow.executeEval(form, ui, data.eval)
App.FormHandlerCoreWorkflow.runCallbacks(ui) App.FormHandlerCoreWorkflow.runCallbacks(ui)

View file

@ -1782,6 +1782,11 @@ fieldset > .form-group {
&.form-group--inactive { &.form-group--inactive {
opacity: 0.5; opacity: 0.5;
} }
&.is-readonly {
pointer-events: none;
cursor: not-allowed !important;
}
} }
.date.form-group .controls { .date.form-group .controls {

View file

@ -71,8 +71,12 @@ class CoreWorkflow::Attributes
attribute[:screens].dig(@payload['screen'], type) attribute[:screens].dig(@payload['screen'], type)
end end
def request_id_default
payload['request_id']
end
# dont cache this else the result object will work with references and cache bugs occur # dont cache this else the result object will work with references and cache bugs occur
def shown_default def visibility_default
object_elements.each_with_object({}) do |attribute, result| object_elements.each_with_object({}) do |attribute, result|
result[ attribute[:name] ] = if @payload['request_id'] == 'ChecksCoreWorkflow.validate_workflows' result[ attribute[:name] ] = if @payload['request_id'] == 'ChecksCoreWorkflow.validate_workflows'
'show' 'show'
@ -106,6 +110,33 @@ class CoreWorkflow::Attributes
end end
end end
# dont cache this else the result object will work with references and cache bugs occur
def readonly_default
object_elements.each_with_object({}) do |attribute, result|
result[ attribute[:name] ] = false
end
end
def select_default
@result_object.result[:select] || {}
end
def fill_in_default
@result_object.result[:fill_in] || {}
end
def eval_default
[]
end
def matched_workflows_default
@result_object.result[:matched_workflows] || []
end
def rerun_count_default
@result_object.result[:rerun_count] || 0
end
def options_array(options) def options_array(options)
result = [] result = []

View file

@ -28,17 +28,10 @@ class CoreWorkflow::Result
def set_default def set_default
@rerun = false @rerun = false
@result = { @result[:restrict_values] = {}
request_id: payload['request_id'], %i[request_id visibility mandatory readonly select fill_in eval matched_workflows rerun_count].each do |group|
restrict_values: {}, @result[group] = attributes.send(:"#{group}_default")
visibility: attributes.shown_default, end
mandatory: attributes.mandatory_default,
select: @result[:select] || {},
fill_in: @result[:fill_in] || {},
eval: [],
matched_workflows: @result[:matched_workflows] || [],
rerun_count: @result[:rerun_count] || 0,
}
# restrict init defaults to make sure param values to removed if not allowed # restrict init defaults to make sure param values to removed if not allowed
attributes.restrict_values_default.each do |field, values| attributes.restrict_values_default.each do |field, values|

View file

@ -0,0 +1,8 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class CoreWorkflow::Result::SetReadonly < CoreWorkflow::Result::Backend
def run
@result_object.result[:readonly][field] = true
true
end
end

View file

@ -0,0 +1,8 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class CoreWorkflow::Result::UnsetReadonly < CoreWorkflow::Result::Backend
def run
@result_object.result[:readonly][field] = false
true
end
end

View file

@ -56,10 +56,9 @@ RSpec.describe CoreWorkflow::Attributes, type: :model do
end end
end end
describe '#shown_default' do describe '#visibility_default' do
it 'priority should be shown by default' do it 'priority should be shown by default' do
expect(result.shown_default['priority_id']).to eq('show') expect(result.visibility_default['priority_id']).to eq('show')
end end
end end
end end

View file

@ -1561,4 +1561,46 @@ RSpec.describe CoreWorkflow, type: :model do
expect(result[:visibility]['priority_id']).to eq('hide') expect(result[:visibility]['priority_id']).to eq('hide')
end end
end end
describe '.perform - Readonly' do
let!(:workflow1) do
create(:core_workflow,
object: 'Ticket',
perform: {
'ticket.group_id': {
operator: 'set_readonly',
set_readonly: 'true'
},
})
end
it 'does match workflow' do
expect(result[:matched_workflows]).to include(workflow1.id)
end
it 'does set group readonly' do
expect(result[:readonly]['group_id']).to eq(true)
end
context 'when readonly unset' do
let!(:workflow2) do
create(:core_workflow,
object: 'Ticket',
perform: {
'ticket.group_id': {
operator: 'unset_readonly',
unset_readonly: 'true'
},
})
end
it 'does match workflows' do
expect(result[:matched_workflows]).to include(workflow1.id, workflow2.id)
end
it 'does set group readonly' do
expect(result[:readonly]['group_id']).to eq(false)
end
end
end
end end

View file

@ -122,6 +122,42 @@ RSpec.shared_examples 'core workflow' do
end end
end end
describe 'action - unset_readonly' do
before do
create(:core_workflow,
object: object_name,
perform: {
"#{object_name.downcase}.#{field_name}": {
operator: 'unset_readonly',
unset_readonly: 'true'
},
})
end
it 'does perform' do
before_it.call
expect(page).to have_no_selector("div[data-attribute-name='#{field_name}'].is-readonly", wait: 10)
end
end
describe 'action - set_readonly' do
before do
create(:core_workflow,
object: object_name,
perform: {
"#{object_name.downcase}.#{field_name}": {
operator: 'set_readonly',
set_readonly: 'true'
},
})
end
it 'does perform' do
before_it.call
expect(page).to have_selector("div[data-attribute-name='#{field_name}'].is-readonly", wait: 10)
end
end
describe 'action - fill_in' do describe 'action - fill_in' do
before do before do
create(:core_workflow, create(:core_workflow,
@ -284,6 +320,42 @@ RSpec.shared_examples 'core workflow' do
end end
end end
describe 'action - unset_readonly' do
before do
create(:core_workflow,
object: object_name,
perform: {
"#{object_name.downcase}.#{field_name}": {
operator: 'unset_readonly',
unset_readonly: 'true'
},
})
end
it 'does perform' do
before_it.call
expect(page).to have_no_selector("div[data-attribute-name='#{field_name}'].is-readonly", wait: 10)
end
end
describe 'action - set_readonly' do
before do
create(:core_workflow,
object: object_name,
perform: {
"#{object_name.downcase}.#{field_name}": {
operator: 'set_readonly',
set_readonly: 'true'
},
})
end
it 'does perform' do
before_it.call
expect(page).to have_selector("div[data-attribute-name='#{field_name}'].is-readonly", wait: 10)
end
end
describe 'action - restrict values' do describe 'action - restrict values' do
before do before do
create(:core_workflow, create(:core_workflow,
@ -607,6 +679,42 @@ RSpec.shared_examples 'core workflow' do
end end
end end
describe 'action - unset_readonly' do
before do
create(:core_workflow,
object: object_name,
perform: {
"#{object_name.downcase}.#{field_name}": {
operator: 'unset_readonly',
unset_readonly: 'true'
},
})
end
it 'does perform' do
before_it.call
expect(page).to have_no_selector("div[data-attribute-name='#{field_name}'].is-readonly", wait: 10)
end
end
describe 'action - set_readonly' do
before do
create(:core_workflow,
object: object_name,
perform: {
"#{object_name.downcase}.#{field_name}": {
operator: 'set_readonly',
set_readonly: 'true'
},
})
end
it 'does perform' do
before_it.call
expect(page).to have_selector("div[data-attribute-name='#{field_name}'].is-readonly", wait: 10)
end
end
describe 'action - restrict values' do describe 'action - restrict values' do
before do before do
create(:core_workflow, create(:core_workflow,