From 6e636c2228431d7bd272763c1b514344068c1b10 Mon Sep 17 00:00:00 2001 From: borisko123 Date: Thu, 7 May 2020 13:00:45 +0300 Subject: [PATCH] add dialog info when import/export custom buttons Isuue##20124 --- lib/task_helpers/exports/custom_buttons.rb | 24 +- lib/task_helpers/imports/custom_buttons.rb | 75 +++-- .../imports/custom_buttons_spec.rb | 105 +++++-- .../data/custom_buttons/CustomButtons.yaml | 260 +++++++++--------- .../custom_buttons/CustomButtonsForUpdate.yml | 136 +++++++++ ...CustomButtonsForUpdateOneButtonChanged.yml | 60 ++++ 6 files changed, 490 insertions(+), 170 deletions(-) create mode 100644 spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsForUpdate.yml create mode 100644 spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsForUpdateOneButtonChanged.yml diff --git a/lib/task_helpers/exports/custom_buttons.rb b/lib/task_helpers/exports/custom_buttons.rb index 6e5a98c853ca..3d38d06fb094 100644 --- a/lib/task_helpers/exports/custom_buttons.rb +++ b/lib/task_helpers/exports/custom_buttons.rb @@ -3,11 +3,22 @@ class Exports class CustomButtons class ExportArInstances EXCLUDE_ATTRS = %w(id created_on updated_on created_at updated_at dialog_id resource_id).freeze - def self.export_object(obj, hash) + + # extract dialog data (name), for this resource action + def self.handle_dialog(obj, a) + extra_attributes = {} # will contain dialog data + if a.to_sym == :resource_action + dialog_label = obj.try(:resource_action).try(:dialog).try(:name) + extra_attributes.merge!({"dialog_label" => dialog_label}) if dialog_label + end + extra_attributes + end + + def self.export_object(obj, hash, extra_attributes = {}) class_name = obj.class.name.underscore $log.info("Exporting #{obj.class.name}: #{obj.try('name')} (ID: #{obj&.id})") - (hash[class_name] ||= []) << item = { 'attributes' => build_attr_list(obj.try(:attributes)) } + (hash[class_name] ||= []) << item = {'attributes' => build_attr_list(obj.try(:attributes)).merge(extra_attributes)} create_association_list(obj, item) descendant_list(obj, item) end @@ -18,12 +29,15 @@ def self.build_attr_list(attrs) def self.create_association_list(obj, item) associations = obj.class.try(:reflections) + if associations - associations = associations.collect { |model, assoc| { model => assoc.class.to_s.demodulize } }.select { |as| as.values.first != "BelongsToReflection" && as.keys.first != "all_relationships" } + associations = associations.collect { |model, assoc| {model => assoc.class.to_s.demodulize} }.select { |as| as.values.first != "BelongsToReflection" && as.keys.first != "all_relationships" } associations.each do |assoc| assoc.each do |a| next if obj.try(a.first.to_sym).blank? - export_object(obj.try(a.first.to_sym), (item['associations'] ||= {})) + assoc_name = obj.try(a.first.to_sym) + extra_attributes = handle_dialog(obj, a.first) # stores dialog data + export_object(obj.try(a.first.to_sym), (item['associations'] ||= {}), extra_attributes) end end end @@ -53,4 +67,4 @@ def export(options = {}) end end end -end +end \ No newline at end of file diff --git a/lib/task_helpers/imports/custom_buttons.rb b/lib/task_helpers/imports/custom_buttons.rb index 5724d51b9bee..884f96f63152 100644 --- a/lib/task_helpers/imports/custom_buttons.rb +++ b/lib/task_helpers/imports/custom_buttons.rb @@ -1,12 +1,15 @@ +# frozen_string_literal: true + module TaskHelpers class Imports class CustomButtons + def import(options) return unless options[:source] - glob = File.file?(options[:source]) ? options[:source] : "#{options[:source]}/*.yaml" + + glob = File.file?(options[:source]) ? options[:source] : "#{options[:source]}/*.yaml*" Dir.glob(glob) do |filename| $log.info("Importing Custom Buttons from: #{filename}") - begin import_custom_buttons(filename) rescue StandardError @@ -26,36 +29,50 @@ def import(obj_hash) ActiveRecord::Base.transaction { obj_hash.each { |obj_def| create_object(*obj_def) } } end + def find_or_create_object(class_name, obj) + klass = class_name.camelize.constantize + attributes = obj['attributes'] + name = attributes.fetch('name', nil) + instance = klass.find_by(name: name) if name + # create new object + unless instance + item = klass.create!(attributes&.except('guid')) + populate_item(item, obj, class_name) + else # update existed object + instance.update_attributes!(attributes&.except('guid')) + populate_item(instance, obj, class_name) + end + end + def create_object(class_name, obj_array) klass = class_name.camelize.constantize obj_array.collect do |obj| - if klass.name == "CustomButtonSet" + if klass.name == 'CustomButtonSet' order = obj.fetch_path('attributes', 'set_data', :button_order) obj['attributes']['set_data'][:button_order] = nil if order.present? end - - klass.create!(obj['attributes']&.except('guid')).tap do |new_obj| - add_children(obj, new_obj) - add_associations(obj, new_obj) - try("#{class_name}_post", new_obj) - end + find_or_create_object(class_name, obj) end end def add_children(obj, new_obj) if obj['children'].present? obj['children'].each do |child| - new_obj.add_members(create_object(*child)) + childes = create_object(*child) + new_obj.add_members(childes) end end end def add_associations(obj, new_obj) - if obj['associations'].present? - obj['associations'].each do |assoc| - new_obj.send("#{assoc.first}=", create_object(*assoc).first) - end + return unless obj['associations'].present? + + # try to find existing resource_actions + resource_action = ResourceAction.find_by(resource_id: new_obj.id, resource_type: 'CustomButton') + obj['associations'].each do |assoc| + populate_action(new_obj, assoc) { create_object(*assoc).first } unless resource_action + populate_action(new_obj, assoc) { |args| resource_action.tap { |ra| ra.update_attributes!(args) } } if resource_action end end @@ -69,8 +86,32 @@ def custom_button_post(new_obj) end def check_user(new_obj) - existing_user = User.find_by(:name => new_obj[:userid]) - new_obj.update(:userid => existing_user.nil? ? "admin" : existing_user) + existing_user = User.find_by(name: new_obj[:userid]) + new_obj.update(userid: existing_user.nil? ? 'admin' : existing_user) + end + + private + + def populate_item(item, obj, class_name) + item.tap do |i| + add_children(obj, i) + add_associations(obj, i) + try("#{class_name}_post", i) + end + end + + def populate_action(item, assoc) + # check if imported data has dialog data + args = assoc.second.first['attributes'] + dialog_label = args.delete('dialog_label') + + unless dialog_label.nil? + dialog = Dialog.find_by_label(dialog_label) + $log.info("Unable to locate dialog: [#{dialog_label}]") unless dialog + end + resource_action = yield (args) + resource_action['dialog_id'] = dialog.id if dialog + item.send("#{assoc.first}=", resource_action) end end @@ -80,4 +121,4 @@ def import_custom_buttons(filename) end end end -end +end \ No newline at end of file diff --git a/spec/lib/task_helpers/imports/custom_buttons_spec.rb b/spec/lib/task_helpers/imports/custom_buttons_spec.rb index 61dc256c95e3..2118b2678a9a 100644 --- a/spec/lib/task_helpers/imports/custom_buttons_spec.rb +++ b/spec/lib/task_helpers/imports/custom_buttons_spec.rb @@ -1,11 +1,15 @@ RSpec.describe TaskHelpers::Imports::CustomButtons do let(:data_dir) { File.join(File.expand_path(__dir__), 'data', 'custom_buttons') } - let(:custom_button_file) { 'CustomButtons.yaml' } - let(:bad_custom_button_file) { 'CustomButtonsBad.yaml' } - let(:custom_button_set_name) { 'group1|Vm|' } + let(:custom_button_file) { 'CustomButtons.yaml' } + let(:bad_custom_button_file) { 'CustomButtonsBad.yaml' } + let(:custom_button_set_name) { 'group1|Vm|' } let(:custom_button_set_description) { 'group1' } - let(:resource_action_ae_namespace) { 'SYSTEM' } - let(:options) { {:source => source} } + let(:resource_action_ae_namespace) { 'SYSTEM' } + let(:options) { {:source => source} } + let(:custom_button_1_name) { 'button 1' } + let!(:test_dialog) { FactoryBot.create(:dialog, :label => 'dialog') } + let!(:test_dialog_2) { FactoryBot.create(:dialog, :label => 'dialog 2') } + let(:for_update_custom_button_file) { 'CustomButtonsForUpdate.yml' } describe "#import" do describe "when the source is a directory" do @@ -19,19 +23,23 @@ end context "with existing identical buttons" do - it 'should raise' do + it 'should NOT raise' do TaskHelpers::Imports::CustomButtons.new.import(options) - assert_raises_import_error + assert_imports_only_custom_button_set_one end end context "yaml import failure" do it 'should raise' do file = Tempfile.new('foo.yaml', data_dir) - file.write("bad yaml here") - TaskHelpers::Imports::CustomButtons.new.import(options) - assert_raises_import_error - assert_imports_only_custom_button_set_one + begin + file.write("bad yaml here") + assert_raises_import_error + assert_imports_only_custom_button_set_one + ensure + file.close + file.unlink # deletes the temp file + end end end end @@ -45,28 +53,66 @@ TaskHelpers::Imports::CustomButtons.new.import(options) assert_test_custom_button_set_present assert_imports_only_custom_button_set_one + assert_dialog_is_set end end + end - context "doesn't import bad yaml" do - let(:source) { "#{data_dir}/#{bad_custom_button_file}" } - it 'does not imports a specified file' do - TaskHelpers::Imports::CustomButtons.new.import(options) - assert_imports_no_custom_buttons - end + context "doesn't import bad yaml" do + let(:source) { "#{data_dir}/#{bad_custom_button_file}" } + it 'does not imports a specified file' do + TaskHelpers::Imports::CustomButtons.new.import(options) + assert_imports_no_custom_buttons end end context "with existing identical buttons" do it 'should not import anything' do TaskHelpers::Imports::CustomButtons.new.import(options) - assert_raises_import_error + assert_imports_only_custom_button_set_one assert_imports_only_custom_button_set_one end end + + + context "updating" do + let(:source) { "#{data_dir}/#{custom_button_file}" } + context "updated with the same data" do + it "should remain the same" do + TaskHelpers::Imports::CustomButtons.new.import(options) + assert_imports_custom_buttons(3) + assert_button_data('#ff0000') + # call second time, in order to test update logic + TaskHelpers::Imports::CustomButtons.new.import(options) + assert_imports_custom_buttons(3) + assert_button_data('#ff0000') + end + end + context "updated with the group and button's color" do + let(:source) { "#{data_dir}/#{custom_button_file}" } + it "color should be changed" do + TaskHelpers::Imports::CustomButtons.new.import(options) + assert_imports_custom_buttons(3) + options[:source] = "#{data_dir}/#{for_update_custom_button_file}" + # call second time, in order to test update logic + TaskHelpers::Imports::CustomButtons.new.import(options) + assert_imports_custom_buttons(3) + assert_button_data('#aabbcc') + assert_button_set_data('#112233') + assert_resource_action_data('#112233') + end + end + end end end + + def assert_dialog_is_set + btn1 = CustomButton.find_by(name: custom_button_1_name) + expect(btn1).to be + expect(btn1.resource_action.dialog.id).to eq(test_dialog_2.id) + end + def assert_test_custom_button_set_present cbs = CustomButtonSet.find_by(:name => custom_button_set_name) expect(cbs.custom_buttons.count).to eq(3) @@ -86,4 +132,25 @@ def assert_imports_no_custom_buttons def assert_raises_import_error expect { TaskHelpers::Imports::CustomButtons.new.import(options) }.to raise_error(StandardError, /Error importing/) end -end + + def assert_imports_custom_buttons(count = 0) + expect(CustomButton.count).to eq(count) + end + + def assert_button_data(color) + b1 = CustomButton.find_by(name: 'button 1') + expect(b1).to be + expect(b1.options[:button_color]).to eq(color) + end + + def assert_button_set_data(color) + cbs1 = CustomButtonSet.find_by(name: 'group1|Vm|') + expect(cbs1).to be + expect(cbs1.set_data[:button_color]).to eq(color) + end + + def assert_resource_action_data(action) + b1 = CustomButton.find_by(name: 'button 1') + expect(b1.resource_action.action).to eq('my_action') + end +end \ No newline at end of file diff --git a/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtons.yaml b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtons.yaml index c8732ffa14c3..a315d9159d09 100644 --- a/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtons.yaml +++ b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtons.yaml @@ -1,134 +1,136 @@ --- custom_button_set: -- attributes: - name: group1|Vm| - description: group1 - set_type: CustomButtonSet - guid: ba4ff235-75eb-4fa4-a9f9-b854d4186c3a - read_only: - set_data: - :button_order: - - 2 - - 3 - - 10 - :button_icon: ff ff-class - :button_color: "#a341ab" - :display: true - :applies_to_class: Vm - :group_index: 1 - mode: - owner_type: - owner_id: - userid: - group_id: - children: - custom_button: - - attributes: - guid: f059931f-8703-4bcf-b876-e482d38ce8ea - description: button 1 - applies_to_class: Vm - visibility_expression: - options: - :button_icon: pficon pficon-cpu - :button_type: default - :display: true - :open_url: false - :display_for: single - :submit_how: one - userid: admin - wait_for_complete: - name: button 1 - visibility: - :roles: - - _ALL_ - applies_to_id: - enablement_expression: - disabled_text: - associations: - resource_action: + - attributes: + name: group1|Vm| + description: group1 + set_type: CustomButtonSet + guid: ba4ff235-75eb-4fa4-a9f9-b854d4186c3a + read_only: + set_data: + :button_order: + - 2 + - 3 + - 10 + :button_icon: ff ff-class + :button_color: "#a341ab" + :display: true + :applies_to_class: Vm + :group_index: 1 + mode: + owner_type: + owner_id: + userid: + group_id: + children: + custom_button: - attributes: - action: - resource_type: CustomButton - ae_namespace: SYSTEM - ae_class: PROCESS - ae_instance: Request - ae_message: - ae_attributes: - request: test1 - configuration_template_id: - configuration_template_type: - - attributes: - guid: 3f50d617-851e-451f-95ae-a17fc548cb11 - description: button 2 - applies_to_class: Vm - visibility_expression: - options: - :button_icon: pficon pficon-home - :button_color: "#c03638" - :button_type: default - :display: true - :open_url: false - :display_for: single - :submit_how: one - userid: - wait_for_complete: - name: button 2 - visibility: - :roles: - - _ALL_ - applies_to_id: - enablement_expression: - disabled_text: - associations: - resource_action: + guid: f059931f-8703-4bcf-b876-e482d38ce8ea + description: button 1 + applies_to_class: Vm + visibility_expression: + options: + :button_icon: pficon pficon-cpu + :button_color: "#ff0000" + :button_type: default + :display: true + :open_url: false + :display_for: single + :submit_how: one + userid: admin + wait_for_complete: + name: button 1 + visibility: + :roles: + - _ALL_ + applies_to_id: + enablement_expression: + disabled_text: + associations: + resource_action: + - attributes: + action: + resource_type: CustomButton + ae_namespace: SYSTEM + ae_class: PROCESS + ae_instance: Request + ae_message: + ae_attributes: + request: test1 + configuration_template_id: + configuration_template_type: + dialog_label: dialog 2 - attributes: - action: - resource_type: CustomButton - ae_namespace: SYSTEM - ae_class: PROCESS - ae_instance: Request - ae_message: - ae_attributes: - request: test2 - configuration_template_id: - configuration_template_type: - - attributes: - guid: d3cd608a-f476-48b7-aa25-a930ec046e00 - description: multiselect - applies_to_class: Vm - visibility_expression: !ruby/object:MiqExpression - exp: - "=": - field: Vm-power_state - value: 'on' - context_type: - options: - :button_icon: fa fa-users - :button_color: "#996633" - :button_type: default - :display: true - :open_url: false - :display_for: both - :submit_how: all - userid: admin - wait_for_complete: - name: multiselect - visibility: - :roles: - - _ALL_ - applies_to_id: - enablement_expression: - disabled_text: - associations: - resource_action: + guid: 3f50d617-851e-451f-95ae-a17fc548cb11 + description: button 2 + applies_to_class: Vm + visibility_expression: + options: + :button_icon: pficon pficon-home + :button_color: "#c03638" + :button_type: default + :display: true + :open_url: false + :display_for: single + :submit_how: one + userid: + wait_for_complete: + name: button 2 + visibility: + :roles: + - _ALL_ + applies_to_id: + enablement_expression: + disabled_text: + associations: + resource_action: + - attributes: + action: + resource_type: CustomButton + ae_namespace: SYSTEM + ae_class: PROCESS + ae_instance: Request + ae_message: + ae_attributes: + request: test2 + configuration_template_id: + configuration_template_type: - attributes: - action: - resource_type: CustomButton - ae_namespace: SYSTEM - ae_class: PROCESS - ae_instance: Request - ae_message: - ae_attributes: - request: multiselect - configuration_template_id: - configuration_template_type: + guid: d3cd608a-f476-48b7-aa25-a930ec046e00 + description: multiselect + applies_to_class: Vm + visibility_expression: !ruby/object:MiqExpression + exp: + "=": + field: Vm-power_state + value: 'on' + context_type: + options: + :button_icon: fa fa-users + :button_color: "#996633" + :button_type: default + :display: true + :open_url: false + :display_for: both + :submit_how: all + userid: admin + wait_for_complete: + name: multiselect + visibility: + :roles: + - _ALL_ + applies_to_id: + enablement_expression: + disabled_text: + associations: + resource_action: + - attributes: + action: + resource_type: CustomButton + ae_namespace: SYSTEM + ae_class: PROCESS + ae_instance: Request + ae_message: + ae_attributes: + request: multiselect + configuration_template_id: + configuration_template_type: \ No newline at end of file diff --git a/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsForUpdate.yml b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsForUpdate.yml new file mode 100644 index 000000000000..579349921454 --- /dev/null +++ b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsForUpdate.yml @@ -0,0 +1,136 @@ +--- +custom_button_set: +- attributes: + name: group1|Vm| + description: group1 + set_type: CustomButtonSet + guid: ba4ff235-75eb-4fa4-a9f9-b854d4186c3a + read_only: + set_data: + :button_order: + - 2 + - 3 + - 10 + :button_icon: ff ff-class + :button_color: "#112233" + :display: true + :applies_to_class: Vm + :group_index: 1 + mode: + owner_type: + owner_id: + userid: + group_id: + children: + custom_button: + - attributes: + guid: f059931f-8703-4bcf-b876-e482d38ce8ea + description: button 1 + applies_to_class: Vm + visibility_expression: + options: + :button_icon: pficon pficon-cpu + :button_color: "#aabbcc" + :button_type: default + :display: true + :open_url: false + :display_for: single + :submit_how: one + userid: admin + wait_for_complete: + name: button 1 + visibility: + :roles: + - _ALL_ + applies_to_id: + enablement_expression: + disabled_text: + associations: + resource_action: + - attributes: + action: my_action + resource_type: CustomButton + ae_namespace: SYSTEM + ae_class: PROCESS + ae_instance: Request + ae_message: + ae_attributes: + request: test1 + configuration_template_id: + configuration_template_type: + dialog_label: dialog + - attributes: + guid: 3f50d617-851e-451f-95ae-a17fc548cb11 + description: button 2 + applies_to_class: Vm + visibility_expression: + options: + :button_icon: pficon pficon-home + :button_color: "#c03638" + :button_type: default + :display: true + :open_url: false + :display_for: single + :submit_how: one + userid: + wait_for_complete: + name: button 2 + visibility: + :roles: + - _ALL_ + applies_to_id: + enablement_expression: + disabled_text: + associations: + resource_action: + - attributes: + action: + resource_type: CustomButton + ae_namespace: SYSTEM + ae_class: PROCESS + ae_instance: Request + ae_message: + ae_attributes: + request: test2 + configuration_template_id: + configuration_template_type: + - attributes: + guid: d3cd608a-f476-48b7-aa25-a930ec046e00 + description: multiselect + applies_to_class: Vm + visibility_expression: !ruby/object:MiqExpression + exp: + "=": + field: Vm-power_state + value: 'on' + context_type: + options: + :button_icon: fa fa-users + :button_color: "#996633" + :button_type: default + :display: true + :open_url: false + :display_for: both + :submit_how: all + userid: admin + wait_for_complete: + name: multiselect + visibility: + :roles: + - _ALL_ + applies_to_id: + enablement_expression: + disabled_text: + associations: + resource_action: + - attributes: + action: + resource_type: CustomButton + ae_namespace: SYSTEM + ae_class: PROCESS + ae_instance: Request + ae_message: + ae_attributes: + request: multiselect + configuration_template_id: + configuration_template_type: \ No newline at end of file diff --git a/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsForUpdateOneButtonChanged.yml b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsForUpdateOneButtonChanged.yml new file mode 100644 index 000000000000..8f021b53f187 --- /dev/null +++ b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsForUpdateOneButtonChanged.yml @@ -0,0 +1,60 @@ +--- +custom_button_set: + - attributes: + name: group1|Vm| + description: group1 + set_type: CustomButtonSet + guid: ba4ff235-75eb-4fa4-a9f9-b854d4186c3a + read_only: + set_data: + :button_order: + - 2 + - 3 + - 10 + :button_icon: ff ff-class + :button_color: "#a341ab" + :display: true + :applies_to_class: Vm + :group_index: 1 + mode: + owner_type: + owner_id: + userid: + group_id: + children: + custom_button: + - attributes: + guid: f059931f-8703-4bcf-b876-e482d38ce8ea + description: button 1 + applies_to_class: Vm + visibility_expression: + options: + :button_icon: pficon pficon-cpu + :button_type: default + :display: true + :open_url: false + :display_for: single + :submit_how: one + userid: admin + wait_for_complete: + name: button 1 + visibility: + :roles: + - _ALL_ + applies_to_id: + enablement_expression: + disabled_text: + associations: + resource_action: + - attributes: + action: + resource_type: CustomButton + ae_namespace: SYSTEM + ae_class: PROCESS + ae_instance: Request + ae_message: + ae_attributes: + request: test1 + configuration_template_id: + configuration_template_type: + dialog_label: dialog