diff --git a/app/assets/javascripts/app/controllers/_application_controller/form.coffee b/app/assets/javascripts/app/controllers/_application_controller/form.coffee index 7102465ca..9d968be2e 100644 --- a/app/assets/javascripts/app/controllers/_application_controller/form.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller/form.coffee @@ -371,6 +371,9 @@ class App.ControllerForm extends App.Controller @fieldIsRemoved: (field) -> return field.closest('.form-group').hasClass('is-removed') + @fieldIsReadonly: (field) -> + return field.closest('.form-group').hasClass('is-readonly') + attributeIsMandatory: (name) -> field_by_name = @constructor.findFieldByName(name, @form) 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.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) -> App.Model.validate( model: @model diff --git a/app/assets/javascripts/app/controllers/_ui_element/core_workflow_perform.coffee b/app/assets/javascripts/app/controllers/_ui_element/core_workflow_perform.coffee index f0ec961b7..4d0e3110c 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/core_workflow_perform.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/core_workflow_perform.coffee @@ -37,17 +37,17 @@ class App.UiElement.core_workflow_perform extends App.UiElement.ApplicationSelec delete groups[key] operatorsType = - 'boolean$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to'] - 'integer$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional'] - '^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', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select'] - '^input$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'fill_in', 'fill_in_empty'] + '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', 'set_readonly', 'unset_readonly'] + '^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', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select'] + '^input$': ['show', 'hide', 'remove', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'fill_in', 'fill_in_empty'] operatorsName = - '_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', 'set_fixed_to', 'select', 'auto_select'] - '_ids$': ['show', 'hide', 'set_mandatory', 'set_optional'] - 'organization_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option'] - 'owner_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'add_option', 'remove_option', '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', 'set_readonly', 'unset_readonly'] + 'organization_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option'] + 'owner_id$': ['show', 'hide', 'set_mandatory', 'set_optional', 'set_readonly', 'unset_readonly', 'add_option', 'remove_option', 'select', 'auto_select'] # merge config elements = {} diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_core_workflow.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_core_workflow.coffee index 115a8fd01..e1548091c 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_core_workflow.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/form_handler_core_workflow.coffee @@ -205,6 +205,16 @@ class App.FormHandlerCoreWorkflow else 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 @executeEval: (form, ui, 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.changeVisibility(form, ui, 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.runCallbacks(ui) diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss index 4e21e5cea..1f75872e6 100644 --- a/app/assets/stylesheets/zammad.scss +++ b/app/assets/stylesheets/zammad.scss @@ -1782,6 +1782,11 @@ fieldset > .form-group { &.form-group--inactive { opacity: 0.5; } + + &.is-readonly { + pointer-events: none; + cursor: not-allowed !important; + } } .date.form-group .controls { @@ -12960,7 +12965,7 @@ span.is-disabled { padding: 6px 5px 11px; display: flex; flex-wrap: wrap; - + .tag { margin: 2px; display: inline-block; diff --git a/app/models/core_workflow/attributes.rb b/app/models/core_workflow/attributes.rb index 5f7f73457..b00efded8 100644 --- a/app/models/core_workflow/attributes.rb +++ b/app/models/core_workflow/attributes.rb @@ -71,8 +71,12 @@ class CoreWorkflow::Attributes attribute[:screens].dig(@payload['screen'], type) end + def request_id_default + payload['request_id'] + end + # 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| result[ attribute[:name] ] = if @payload['request_id'] == 'ChecksCoreWorkflow.validate_workflows' 'show' @@ -106,6 +110,33 @@ class CoreWorkflow::Attributes 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) result = [] diff --git a/app/models/core_workflow/result.rb b/app/models/core_workflow/result.rb index 1e24ca193..8d057f945 100644 --- a/app/models/core_workflow/result.rb +++ b/app/models/core_workflow/result.rb @@ -28,17 +28,10 @@ class CoreWorkflow::Result def set_default @rerun = false - @result = { - request_id: payload['request_id'], - restrict_values: {}, - visibility: attributes.shown_default, - mandatory: attributes.mandatory_default, - select: @result[:select] || {}, - fill_in: @result[:fill_in] || {}, - eval: [], - matched_workflows: @result[:matched_workflows] || [], - rerun_count: @result[:rerun_count] || 0, - } + @result[:restrict_values] = {} + %i[request_id visibility mandatory readonly select fill_in eval matched_workflows rerun_count].each do |group| + @result[group] = attributes.send(:"#{group}_default") + end # restrict init defaults to make sure param values to removed if not allowed attributes.restrict_values_default.each do |field, values| diff --git a/app/models/core_workflow/result/set_readonly.rb b/app/models/core_workflow/result/set_readonly.rb new file mode 100644 index 000000000..36ff58566 --- /dev/null +++ b/app/models/core_workflow/result/set_readonly.rb @@ -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 diff --git a/app/models/core_workflow/result/unset_readonly.rb b/app/models/core_workflow/result/unset_readonly.rb new file mode 100644 index 000000000..36f3920cd --- /dev/null +++ b/app/models/core_workflow/result/unset_readonly.rb @@ -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 diff --git a/spec/models/core_workflow/attributes_spec.rb b/spec/models/core_workflow/attributes_spec.rb index 3e60eb7a9..38fc7437c 100644 --- a/spec/models/core_workflow/attributes_spec.rb +++ b/spec/models/core_workflow/attributes_spec.rb @@ -56,10 +56,9 @@ RSpec.describe CoreWorkflow::Attributes, type: :model do end end - describe '#shown_default' do + describe '#visibility_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 diff --git a/spec/models/core_workflow_spec.rb b/spec/models/core_workflow_spec.rb index 15b6cb712..03668a83d 100644 --- a/spec/models/core_workflow_spec.rb +++ b/spec/models/core_workflow_spec.rb @@ -1561,4 +1561,46 @@ RSpec.describe CoreWorkflow, type: :model do expect(result[:visibility]['priority_id']).to eq('hide') 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 diff --git a/spec/system/examples/core_workflow_examples.rb b/spec/system/examples/core_workflow_examples.rb index 9e655739a..b507bd2a8 100644 --- a/spec/system/examples/core_workflow_examples.rb +++ b/spec/system/examples/core_workflow_examples.rb @@ -122,6 +122,42 @@ RSpec.shared_examples 'core workflow' do 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 before do create(:core_workflow, @@ -284,6 +320,42 @@ RSpec.shared_examples 'core workflow' do 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 before do create(:core_workflow, @@ -607,6 +679,42 @@ RSpec.shared_examples 'core workflow' do 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 before do create(:core_workflow,