From f8ca9bf4ae587a5966255b50be0cae8b35cf4399 Mon Sep 17 00:00:00 2001 From: d-m-u Date: Wed, 18 Jul 2018 13:21:27 -0400 Subject: [PATCH 1/2] Start of rake task to import custom buttons --- lib/task_helpers/imports/custom_buttons.rb | 80 ++++++++++++++++++++++ lib/tasks/evm_export_import.rake | 8 +++ 2 files changed, 88 insertions(+) create mode 100644 lib/task_helpers/imports/custom_buttons.rb diff --git a/lib/task_helpers/imports/custom_buttons.rb b/lib/task_helpers/imports/custom_buttons.rb new file mode 100644 index 00000000000..7cd73cbddae --- /dev/null +++ b/lib/task_helpers/imports/custom_buttons.rb @@ -0,0 +1,80 @@ +require "English" + +module TaskHelpers + class Imports + class CustomButtons + class ImportArInstances + DEBUG_MODE = false + + def self.import(obj_hash) + new.import(obj_hash) + end + + def import(obj_hash) + ActiveRecord::Base.transaction { obj_hash.each { |obj_def| create_object(*obj_def) } } + end + + def create_object(class_name, obj_array) + klass = class_name.camelize.constantize + + obj_array.collect do |obj| + create_unique_values(obj) if DEBUG_MODE + begin + klass.create!(obj['attributes'].except('guid')).tap do |new_obj| + if obj['children'].present? + obj['children'].each do |child| + new_obj.add_members(create_object(*child)) + end + end + + if obj['associations'].present? + obj['associations'].each do |hoo| + new_obj.send("#{hoo.first}=", create_object(*hoo).first) + end + end + try("#{class_name}_post", new_obj) + end + rescue StandardError + $log.send(:info, "Failed to create new instance [#{class_name}] with attributes #{obj['attributes'].inspect}") + $log.send(:info, "#{$ERROR_INFO} at #{$ERROR_POSITION}") + raise + end + end + end + + def custom_button_set_post(new_obj) + new_obj.set_data[:button_order] = new_obj.custom_buttons.collect(&:id) + new_obj.save! + end + + def create_unique_values(obj) + %w(name description).each do |attr_name| + attr_value = obj.dig('attributes', attr_name) + obj.store_path('attributes', attr_name, "#{attr_value} #{Time.zone.now}") if attr_value.present? + end + end + end + + def import(options) + return unless options[:source] + + glob = File.file?(options[:source]) ? options[:source] : "#{options[:source]}/CustomButtons.yaml" + Dir.glob(glob) do |filename| + begin + import_custom_buttons(filename) + rescue + p "#{$ERROR_INFO} at #{$ERROR_POSITION}" + warn("Error importing #{options[:source]}") + end + end + end + + private + + def import_custom_buttons(filename) + custom_buttons = YAML.load_file(filename) + ImportArInstances.import(custom_buttons) + end + end + end +end diff --git a/lib/tasks/evm_export_import.rake b/lib/tasks/evm_export_import.rake index 902614eeeca..b002987a7de 100644 --- a/lib/tasks/evm_export_import.rake +++ b/lib/tasks/evm_export_import.rake @@ -157,5 +157,13 @@ namespace :evm do exit # exit so that parameters to the first rake task are not run as rake tasks end + + desc 'Imports all custom buttons from YAML file' + task :custom_buttons => :environment do + options = TaskHelpers::Imports.parse_options + TaskHelpers::Imports::CustomButtons.new.import(options) + + exit # exit so that parameters to the first rake task are not run as rake tasks + end end end From e2d1697c18959bc784cd021c7d2d2deeae17ed67 Mon Sep 17 00:00:00 2001 From: d-m-u Date: Fri, 20 Jul 2018 10:05:04 -0400 Subject: [PATCH 2/2] Add tests for import --- lib/task_helpers/imports/custom_buttons.rb | 76 +++++----- .../imports/custom_buttons_spec.rb | 89 ++++++++++++ .../data/custom_buttons/CustomButtons.yaml | 134 ++++++++++++++++++ .../data/custom_buttons/CustomButtonsBad.yml | 35 +++++ 4 files changed, 296 insertions(+), 38 deletions(-) create mode 100644 spec/lib/task_helpers/imports/custom_buttons_spec.rb create mode 100644 spec/lib/task_helpers/imports/data/custom_buttons/CustomButtons.yaml create mode 100644 spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsBad.yml diff --git a/lib/task_helpers/imports/custom_buttons.rb b/lib/task_helpers/imports/custom_buttons.rb index 7cd73cbddae..7d7db94d64f 100644 --- a/lib/task_helpers/imports/custom_buttons.rb +++ b/lib/task_helpers/imports/custom_buttons.rb @@ -1,11 +1,21 @@ -require "English" - module TaskHelpers class Imports class CustomButtons - class ImportArInstances - DEBUG_MODE = false + def import(options) + return unless options[:source] + glob = File.file?(options[:source]) ? options[:source] : "#{options[:source]}/*.yaml" + Dir.glob(glob) do |filename| + begin + import_custom_buttons(filename) + rescue StandardError + raise StandardError, "Error importing #{filename} at #{$@}" + end + end + end + + private + class ImportArInstances def self.import(obj_hash) new.import(obj_hash) end @@ -18,59 +28,49 @@ def create_object(class_name, obj_array) klass = class_name.camelize.constantize obj_array.collect do |obj| - create_unique_values(obj) if DEBUG_MODE begin - klass.create!(obj['attributes'].except('guid')).tap do |new_obj| - if obj['children'].present? - obj['children'].each do |child| - new_obj.add_members(create_object(*child)) - end - end - - if obj['associations'].present? - obj['associations'].each do |hoo| - new_obj.send("#{hoo.first}=", create_object(*hoo).first) - end - 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 rescue StandardError - $log.send(:info, "Failed to create new instance [#{class_name}] with attributes #{obj['attributes'].inspect}") - $log.send(:info, "#{$ERROR_INFO} at #{$ERROR_POSITION}") raise end end end + def add_children(obj, new_obj) + if obj['children'].present? + obj['children'].each do |child| + new_obj.add_members(create_object(*child)) + 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 + end + end + def custom_button_set_post(new_obj) new_obj.set_data[:button_order] = new_obj.custom_buttons.collect(&:id) new_obj.save! end - def create_unique_values(obj) - %w(name description).each do |attr_name| - attr_value = obj.dig('attributes', attr_name) - obj.store_path('attributes', attr_name, "#{attr_value} #{Time.zone.now}") if attr_value.present? - end + def custom_button_post(new_obj) + check_user(new_obj) end - end - - def import(options) - return unless options[:source] - glob = File.file?(options[:source]) ? options[:source] : "#{options[:source]}/CustomButtons.yaml" - Dir.glob(glob) do |filename| - begin - import_custom_buttons(filename) - rescue - p "#{$ERROR_INFO} at #{$ERROR_POSITION}" - warn("Error importing #{options[:source]}") - end + def check_user(new_obj) + existing_user = User.find_by(:name => new_obj[:userid]) + new_obj.update_attributes(:userid => existing_user.nil? ? "admin" : existing_user) end end - private - def import_custom_buttons(filename) custom_buttons = YAML.load_file(filename) ImportArInstances.import(custom_buttons) diff --git a/spec/lib/task_helpers/imports/custom_buttons_spec.rb b/spec/lib/task_helpers/imports/custom_buttons_spec.rb new file mode 100644 index 00000000000..d897cd18ee6 --- /dev/null +++ b/spec/lib/task_helpers/imports/custom_buttons_spec.rb @@ -0,0 +1,89 @@ +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_set_description) { 'group1' } + let(:resource_action_ae_namespace) { 'SYSTEM' } + let(:options) { {:source => source} } + + describe "#import" do + describe "when the source is a directory" do + let(:source) { data_dir } + + context "without existing buttons" do + it 'imports all formatted .yaml files in a specified directory' do + TaskHelpers::Imports::CustomButtons.new.import(options) + assert_test_custom_button_set_present + end + end + + context "with existing identical buttons" do + it 'should raise' do + TaskHelpers::Imports::CustomButtons.new.import(options) + assert_raises_import_error + 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 + end + end + end + + describe "when the source is a file" do + let(:source) { "#{data_dir}/#{custom_button_file}" } + + context "without existing buttons" do + context "only imports good yaml" do + it 'imports a specified file' do + TaskHelpers::Imports::CustomButtons.new.import(options) + assert_test_custom_button_set_present + assert_imports_only_custom_button_set_one + 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 + 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 + end + end + end + 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) + expect(cbs.description).to eq(custom_button_set_description) + expect(cbs.custom_buttons.first.resource_action.ae_namespace).to eq(resource_action_ae_namespace) + expect(cbs.custom_buttons.pluck(:userid)).to eq(%w(admin admin admin)) + end + + def assert_imports_only_custom_button_set_one + expect(CustomButton.count).to eq(3) + end + + def assert_imports_no_custom_buttons + expect(CustomButton.count).to eq(0) + end + + def assert_raises_import_error + expect { TaskHelpers::Imports::CustomButtons.new.import(options) }.to raise_error(StandardError, /Error importing/) + end +end diff --git a/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtons.yaml b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtons.yaml new file mode 100644 index 00000000000..c8732ffa14c --- /dev/null +++ b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtons.yaml @@ -0,0 +1,134 @@ +--- +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: + - 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: diff --git a/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsBad.yml b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsBad.yml new file mode 100644 index 00000000000..e0315ff1160 --- /dev/null +++ b/spec/lib/task_helpers/imports/data/custom_buttons/CustomButtonsBad.yml @@ -0,0 +1,35 @@ +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: