diff --git a/app/controllers/api/providers_controller.rb b/app/controllers/api/providers_controller.rb index e04fcafdc00..d0f76f802eb 100644 --- a/app/controllers/api/providers_controller.rb +++ b/app/controllers/api/providers_controller.rb @@ -13,6 +13,7 @@ class ProvidersController < BaseController include Subcollections::PolicyProfiles include Subcollections::Tags include Subcollections::CloudNetworks + include Subcollections::CustomAttributes def create_resource(type, _id, data = {}) assert_id_not_specified(data, type) @@ -50,8 +51,28 @@ def delete_resource(type, id = nil, _data = nil) end end + def custom_attributes_edit_resource(object, type, id, data = nil) + formatted_data = format_provider_custom_attributes(data) + super(object, type, id, formatted_data) + end + + def custom_attributes_add_resource(object, type, id, data = nil) + formatted_data = format_provider_custom_attributes(data) + super(object, type, id, formatted_data) + end + private + def format_provider_custom_attributes(attribute) + if CustomAttribute::ALLOWED_API_VALUE_TYPES.include? attribute["field_type"] + attribute["value"] = attribute.delete("field_type").safe_constantize.parse(attribute["value"]) + end + attribute["section"] = "metadata" unless @req.action == "edit" + attribute + rescue => err + raise BadRequestError, "Invalid provider custom attributes specified - #{err}" + end + def provider_ident(provider) "Provider id:#{provider.id} name:'#{provider.name}'" end diff --git a/app/helpers/ems_container_helper/textual_summary.rb b/app/helpers/ems_container_helper/textual_summary.rb index b9a93a4a653..c253ba83383 100644 --- a/app/helpers/ems_container_helper/textual_summary.rb +++ b/app/helpers/ems_container_helper/textual_summary.rb @@ -106,4 +106,10 @@ def textual_endpoints {:label => _('Hawkular API Port'), :value => @ems.connection_configurations.hawkular.endpoint.port}] end + + def textual_miq_custom_attributes + attrs = @record.custom_attributes + return nil if attrs.blank? + attrs.collect { |a| {:label => a.name.tr("_", " "), :value => a.value} } + end end diff --git a/app/models/custom_attribute.rb b/app/models/custom_attribute.rb index 927694cde15..321bf883cff 100644 --- a/app/models/custom_attribute.rb +++ b/app/models/custom_attribute.rb @@ -1,4 +1,5 @@ class CustomAttribute < ApplicationRecord + ALLOWED_API_VALUE_TYPES = %w(DateTime Time Date).freeze belongs_to :resource, :polymorphic => true serialize :serialized_value diff --git a/app/views/ems_container/_main.html.haml b/app/views/ems_container/_main.html.haml index 77509d7f6b1..992d0dbf9e2 100644 --- a/app/views/ems_container/_main.html.haml +++ b/app/views/ems_container/_main.html.haml @@ -9,6 +9,8 @@ -# commented out to hide Component Statuses table since the information is currently broken (see Issue #8572) -# = render :partial => "shared/summary/textual_multilabel", :locals => {:title => _("Component Statuses"), -# :items => textual_group_component_statuses} + = render :partial => "shared/summary/textual", :locals => {:title => _("Custom Attributes"), + :items => textual_miq_custom_attributes} .col-sm-12.col-md-12.col-lg-6 = render :partial => "shared/summary/textual", :locals => {:title => _("Relationships"), :items => textual_group_relationships} diff --git a/config/api.yml b/config/api.yml index 29b95fde7c6..673e8d52444 100644 --- a/config/api.yml +++ b/config/api.yml @@ -746,6 +746,7 @@ - :policies - :policy_profiles - :cloud_networks + - :custom_attributes :collection_actions: :get: - :name: read @@ -781,6 +782,14 @@ :identifier: ems_infra_tag - :name: unassign :identifier: ems_infra_tag + :custom_attributes_subcollection_actions: + :post: + - :name: add + :identifier: ems_infra_edit + - :name: edit + :identifier: ems_infra_edit + - :name: delete + :identifier: ems_infra_edit :policies_subcollection_actions: :post: - :name: assign diff --git a/spec/helpers/ems_cotainer_helper/textual_summary_spec.rb b/spec/helpers/ems_cotainer_helper/textual_summary_spec.rb new file mode 100644 index 00000000000..24da179f4be --- /dev/null +++ b/spec/helpers/ems_cotainer_helper/textual_summary_spec.rb @@ -0,0 +1,24 @@ +describe EmsContainerHelper::TextualSummary do + context "providers custom attributes" do + before do + @record = FactoryGirl.build(:ems_openshift) + allow_any_instance_of(described_class).to receive(:role_allows?).and_return(true) + allow(controller).to receive(:restful?).and_return(true) + allow(controller).to receive(:controller_name).and_return("ems_container") + end + + it "should parse custom attributes to labels and values" do + @record.custom_attributes << FactoryGirl.build(:custom_attribute, + :name => "Example_custom_attribute", + :value => 4) + + expect(textual_miq_custom_attributes.first[:label]).to eq("Example custom attribute") + + expect(textual_miq_custom_attributes.first[:value]).to eq("4") + end + + it "should return nil if no custom attributes" do + expect(textual_miq_custom_attributes).to eq(nil) + end + end +end diff --git a/spec/requests/api/providers_spec.rb b/spec/requests/api/providers_spec.rb index cc15b2a693c..bb95faba464 100644 --- a/spec/requests/api/providers_spec.rb +++ b/spec/requests/api/providers_spec.rb @@ -100,6 +100,132 @@ } end + context "Provider custom_attributes" do + let(:provider) { FactoryGirl.create(:ext_management_system, sample_rhevm) } + let(:provider_url) { providers_url(provider.id) } + let(:ca1) { FactoryGirl.create(:custom_attribute, :name => "name1", :value => "value1") } + let(:ca2) { FactoryGirl.create(:custom_attribute, :name => "name2", :value => "value2") } + let(:provider_ca_url) { "#{provider_url}/custom_attributes" } + let(:ca1_url) { "#{provider_ca_url}/#{ca1.id}" } + let(:ca2_url) { "#{provider_ca_url}/#{ca2.id}" } + let(:provider_ca_url_list) { [ca1_url, ca2_url] } + + it "getting custom_attributes from a provider with no custom_attributes" do + api_basic_authorize + + run_get(provider_ca_url) + + expect_empty_query_result(:custom_attributes) + end + + it "getting custom_attributes from a provider" do + api_basic_authorize + provider.custom_attributes = [ca1, ca2] + + run_get provider_ca_url + + expect_query_result(:custom_attributes, 2) + + expect_result_resources_to_include_hrefs("resources", :provider_ca_url_list) + end + + it "getting custom_attributes from a provider in expanded form" do + api_basic_authorize + provider.custom_attributes = [ca1, ca2] + + run_get provider_ca_url, :expand => "resources" + + expect_query_result(:custom_attributes, 2) + + expect_result_resources_to_include_data("resources", "name" => %w(name1 name2)) + end + + it "getting custom_attributes from a provider using expand" do + api_basic_authorize action_identifier(:providers, :read, :resource_actions, :get) + provider.custom_attributes = [ca1, ca2] + + run_get provider_url, :expand => "custom_attributes" + + expect_single_resource_query("guid" => provider.guid) + + expect_result_resources_to_include_data("custom_attributes", "name" => %w(name1 name2)) + end + + it "delete a custom_attribute without appropriate role" do + api_basic_authorize + provider.custom_attributes = [ca1] + + run_post(provider_ca_url, gen_request(:delete, nil, provider_url)) + + expect(response).to have_http_status(:forbidden) + end + + it "delete a custom_attribute from a provider via the delete action" do + api_basic_authorize action_identifier(:providers, :edit) + provider.custom_attributes = [ca1] + + run_post(provider_ca_url, gen_request(:delete, nil, ca1_url)) + + expect(response).to have_http_status(:ok) + + expect(provider.reload.custom_attributes).to be_empty + end + + it "add custom attribute to a provider without a name" do + api_basic_authorize action_identifier(:providers, :edit) + + run_post(provider_ca_url, gen_request(:add, "value" => "value1")) + + expect_bad_request("Must specify a name") + end + + it "add custom attributes to a provider" do + api_basic_authorize action_identifier(:providers, :edit) + + run_post(provider_ca_url, gen_request(:add, [{"name" => "name1", "value" => "value1"}, + {"name" => "name2", "value" => "value2"}])) + expected = { + "results" => a_collection_containing_exactly( + a_hash_including("name" => "name1", "value" => "value1", "section" => "metadata"), + a_hash_including("name" => "name2", "value" => "value2", "section" => "metadata") + ) + } + expect(response).to have_http_status(:ok) + + expect(response.parsed_body).to include(expected) + + expect(provider.custom_attributes.size).to eq(2) + end + + it "formats custom attribute of type date" do + api_basic_authorize action_identifier(:providers, :edit) + date_field = DateTime.new.in_time_zone + + run_post(provider_ca_url, gen_request(:add, [{"name" => "name1", + "value" => date_field, + "field_type" => "DateTime"}])) + + expect(response).to have_http_status(:ok) + + expect(provider.custom_attributes.first.serialized_value).to eq(date_field) + + expect(provider.custom_attributes.first.section).to eq("metadata") + end + + it "edit a custom attribute by name" do + api_basic_authorize action_identifier(:providers, :edit) + provider.custom_attributes = [ca1] + + run_post(provider_ca_url, gen_request(:edit, "name" => "name1", "value" => "value one")) + + expect(response).to have_http_status(:ok) + + expect_result_resources_to_include_data("results", "value" => ["value one"]) + + expect(provider.reload.custom_attributes.first.value).to eq("value one") + end + end + describe "Providers actions on Provider class" do let(:foreman_type) { ManageIQ::Providers::Foreman::Provider } let(:sample_foreman) do