diff --git a/app/controllers/api/alert_actions_controller.rb b/app/controllers/api/alert_actions_controller.rb new file mode 100644 index 00000000000..ef844a78793 --- /dev/null +++ b/app/controllers/api/alert_actions_controller.rb @@ -0,0 +1,4 @@ +module Api + class AlertActionsController < BaseController + end +end diff --git a/app/controllers/api/alerts_controller.rb b/app/controllers/api/alerts_controller.rb index 79875a689f6..f329d59587c 100644 --- a/app/controllers/api/alerts_controller.rb +++ b/app/controllers/api/alerts_controller.rb @@ -1,4 +1,5 @@ module Api class AlertsController < BaseController + include Subcollections::AlertActions end end diff --git a/app/controllers/api/subcollections/alert_actions.rb b/app/controllers/api/subcollections/alert_actions.rb new file mode 100644 index 00000000000..ca07c9d901c --- /dev/null +++ b/app/controllers/api/subcollections/alert_actions.rb @@ -0,0 +1,24 @@ +module Api + module Subcollections + module AlertActions + def alert_actions_query_resource(object) + object.miq_alert_status_actions + end + + def alert_actions_create_resource(object, type, _id, data) + attributes = data.dup + attributes['miq_alert_status_id'] = object.id + attributes['user_id'] = User.current_user.id + if data.key?('assignee') + attributes['assignee_id'] = parse_id(attributes.delete('assignee'), :users) + end + alert_action = collection_class(type).create(attributes) + if alert_action.invalid? + raise BadRequestError, + "Failed to add a new alert action resource - #{alert_action.errors.full_messages.join(', ')}" + end + alert_action + end + end + end +end diff --git a/config/api.yml b/config/api.yml index 02c3e665a27..e13d32d5ffd 100644 --- a/config/api.yml +++ b/config/api.yml @@ -654,6 +654,8 @@ - :collection :verbs: *g :klass: MiqAlertStatus + :subcollections: + - :alert_actions :collection_actions: :get: - :name: read @@ -662,6 +664,20 @@ :get: - :name: read :identifier: alert_status_show + :alert_actions: + :description: Alert Actions + :identifier: alert_action + :options: + - :subcollection + :verbs: *gp + :klass: MiqAlertStatusAction + :subcollection_actions: + :get: + - :name: read + :identifier: alert_status_action_show_list + :post: + - :name: create + :identifier: alert_status_action_new :notifications: :description: "User's past notifications" :options: diff --git a/db/fixtures/miq_product_features.yml b/db/fixtures/miq_product_features.yml index 28c714295cf..db766d2deca 100644 --- a/db/fixtures/miq_product_features.yml +++ b/db/fixtures/miq_product_features.yml @@ -1952,6 +1952,21 @@ :description: Display Individual Alert Status :feature_type: view :identifier: alert_status_show + - :name: Alerts Status Actions + :description: Everything under Status Actions + :protected: true + :feature_type: node + :hidden: true + :identifier: alert_action + :children: + - :name: List + :description: Display Lists of Alert Status Actions + :feature_type: view + :identifier: alert_status_action_show_list + - :name: Add + :description: Display Individual Alert Status + :feature_type: admin + :identifier: alert_status_action_new # Policy Simulation - :name: Simulation :description: Policy Simulation diff --git a/spec/requests/api/alerts_spec.rb b/spec/requests/api/alerts_spec.rb index 3281c38e314..d7f61389110 100644 --- a/spec/requests/api/alerts_spec.rb +++ b/spec/requests/api/alerts_spec.rb @@ -44,4 +44,147 @@ "id" => alert_status.id ) end + + context "alert_actions subcollection" do + let(:alert) { FactoryGirl.create(:miq_alert_status) } + let(:actions_subcollection_url) { "#{alerts_url(alert.id)}/alert_actions" } + let(:assignee) { FactoryGirl.create(:user) } + let(:expected_assignee) do + { + 'results' => a_collection_containing_exactly( + a_hash_including("assignee_id" => assignee.id) + ) + } + end + + it "forbids access to alerts actions subcolletion without an appropriate role" do + FactoryGirl.create( + :miq_alert_status_action, + :miq_alert_status => alert, + :user => FactoryGirl.create(:user) + ) + api_basic_authorize + run_get(actions_subcollection_url) + expect(response).to have_http_status(:forbidden) + end + + it "reads an alert action as a sub collection under an alert" do + api_basic_authorize subcollection_action_identifier(:alerts, :alert_actions, :read, :get) + alert_action = FactoryGirl.create( + :miq_alert_status_action, + :miq_alert_status => alert, + :user => FactoryGirl.create(:user) + ) + run_get(actions_subcollection_url) + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include( + "name" => "alert_actions", + "count" => 1, + "subcount" => 1, + "resources" => [ + { + "href" => a_string_matching("#{alerts_url(alert.id)}/alert_actions/#{alert_action.id}") + } + ] + ) + end + + it "forbids creation of an alert action under alerts without an appropriate role" do + api_basic_authorize + run_post( + actions_subcollection_url, + "action_type" => "comment", + "comment" => "comment text", + ) + expect(response).to have_http_status(:forbidden) + end + + it "creates an alert action under an alert" do + attributes = { + "action_type" => "comment", + "comment" => "comment text", + } + api_basic_authorize subcollection_action_identifier(:alerts, :alert_actions, :create, :post) + run_post(actions_subcollection_url, attributes) + expect(response).to have_http_status(:ok) + expected = { + "results" => [ + a_hash_including(attributes) + ] + } + expect(response.parsed_body).to include(expected) + end + + it "creates an alert action on the current user" do + user = FactoryGirl.create(:user) + attributes = { + "action_type" => "comment", + "comment" => "comment text", + "user_id" => user.id # should be ignored + } + api_basic_authorize subcollection_action_identifier(:alerts, :alert_actions, :create, :post) + run_post(actions_subcollection_url, attributes) + expect(response).to have_http_status(:ok) + expected = { + "results" => [ + a_hash_including(attributes.merge("user_id" => User.current_user.id)) + ] + } + expect(response.parsed_body).to include(expected) + expect(user.id).not_to eq(User.current_user.id) + end + + it "create an assignment alert action reference by id" do + attributes = { + "action_type" => "assign", + "assignee" => { "id" => assignee.id } + } + api_basic_authorize subcollection_action_identifier(:alerts, :alert_actions, :create, :post) + run_post(actions_subcollection_url, attributes) + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected_assignee) + end + + it "create an assignment alert action reference by href" do + attributes = { + "action_type" => "assign", + "assignee" => { "href" => users_url(assignee.id) } + } + api_basic_authorize subcollection_action_identifier(:alerts, :alert_actions, :create, :post) + run_post(actions_subcollection_url, attributes) + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected_assignee) + end + + it "returns errors when creating an invalid alert" do + api_basic_authorize subcollection_action_identifier(:alerts, :alert_actions, :create, :post) + run_post( + actions_subcollection_url, + "action_type" => "assign", + ) + expect(response).to have_http_status(:bad_request) + expect(response.parsed_body).to include_error_with_message( + "Failed to add a new alert action resource - Assignee can't be blank" + ) + end + + it "reads an alert action as a resource under an alert" do + api_basic_authorize subcollection_action_identifier(:alerts, :alert_actions, :read, :get) + user = FactoryGirl.create(:user) + alert_action = FactoryGirl.create( + :miq_alert_status_action, + :miq_alert_status => alert, + :user => user + ) + run_get("#{actions_subcollection_url}/#{alert_action.id}") + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include( + "href" => a_string_matching("#{alerts_url(alert.id)}/alert_actions/#{alert_action.id}"), + "id" => alert_action.id, + "action_type" => alert_action.action_type, + "user_id" => user.id, + "comment" => alert_action.comment, + ) + end + end end