From 4adce0d8441f85f3ad892af7a1614901250b70b8 Mon Sep 17 00:00:00 2001 From: Mark VanLandingham Date: Tue, 2 Mar 2021 10:28:27 -0600 Subject: [PATCH] DEV: APIs for plugin to add custom reviewable confirm modal (#12246) --- .../app/components/reviewable-item.js | 34 +++++++++++++-- .../discourse/app/lib/plugin-api.js | 4 ++ app/controllers/reviewables_controller.rb | 5 +++ .../reviewable_action_serializer.rb | 6 ++- lib/discourse_plugin_registry.rb | 1 + lib/plugin/instance.rb | 7 ++++ lib/reviewable/actions.rb | 2 +- spec/requests/reviewables_controller_spec.rb | 42 +++++++++++++++++++ 8 files changed, 95 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/reviewable-item.js b/app/assets/javascripts/discourse/app/components/reviewable-item.js index bd77ff16f2f..746ae8da750 100644 --- a/app/assets/javascripts/discourse/app/components/reviewable-item.js +++ b/app/assets/javascripts/discourse/app/components/reviewable-item.js @@ -12,6 +12,14 @@ import showModal from "discourse/lib/show-modal"; let _components = {}; +const pluginReviewableParams = {}; + +export function addPluginReviewableParam(reviewableType, param) { + pluginReviewableParams[reviewableType] + ? pluginReviewableParams[reviewableType].push(param) + : (pluginReviewableParams[reviewableType] = [param]); +} + export default Component.extend({ adminTools: optionalService(), tagName: "", @@ -106,14 +114,23 @@ export default Component.extend({ let performAction = () => { let version = reviewable.get("version"); this.set("updating", true); + + const data = { + send_email: reviewable.sendEmail, + reject_reason: reviewable.rejectReason, + }; + + (pluginReviewableParams[reviewable.type] || []).forEach((param) => { + if (reviewable[param]) { + data[param] = reviewable[param]; + } + }); + return ajax( `/review/${reviewable.id}/perform/${action.id}?version=${version}`, { type: "PUT", - data: { - send_email: reviewable.sendEmail, - reject_reason: reviewable.rejectReason, - }, + data, } ) .then((result) => { @@ -227,6 +244,7 @@ export default Component.extend({ let msg = action.get("confirm_message"); let requireRejectReason = action.get("require_reject_reason"); + let customModal = action.get("custom_modal"); if (msg) { bootbox.confirm(msg, (answer) => { if (answer) { @@ -241,6 +259,14 @@ export default Component.extend({ performConfirmed: this._performConfirmed.bind(this), action, }); + } else if (customModal) { + showModal(customModal, { + title: `review.${customModal}.title`, + model: this.reviewable, + }).setProperties({ + performConfirmed: this._performConfirmed.bind(this), + action, + }); } else { return this._performConfirmed(action); } diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index 6865976520b..4d00f3a9e2f 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -44,6 +44,7 @@ import { addGTMPageChangedCallback } from "discourse/lib/page-tracker"; import { addGlobalNotice } from "discourse/components/global-notice"; import { addNavItem } from "discourse/models/nav-item"; import { addPluginOutletDecorator } from "discourse/components/plugin-connector"; +import { addPluginReviewableParam } from "discourse/components/reviewable-item"; import { addPopupMenuOptionsCallback } from "discourse/controllers/composer"; import { addPostClassesCallback } from "discourse/widgets/post"; import { addPostSmallActionIcon } from "discourse/widgets/post-small-action"; @@ -1222,6 +1223,9 @@ class PluginApi { addSaveableUserOptionField(fieldName) { addSaveableUserOptionField(fieldName); } + addPluginReviewableParam(reviewableType, param) { + addPluginReviewableParam(reviewableType, param); + } /** * Change the default category background and text colors in the diff --git a/app/controllers/reviewables_controller.rb b/app/controllers/reviewables_controller.rb index c2b2d43ed3a..9afb716ae6d 100644 --- a/app/controllers/reviewables_controller.rb +++ b/app/controllers/reviewables_controller.rb @@ -192,6 +192,11 @@ class ReviewablesController < ApplicationController args.merge!(reject_reason: params[:reject_reason], send_email: params[:send_email] != "false") if reviewable.type == 'ReviewableUser' + plugin_params = DiscoursePluginRegistry.reviewable_params.select do |reviewable_param| + reviewable.type == reviewable_param[:type].to_s.classify + end + args.merge!(params.slice(*plugin_params.map { |pp| pp[:param] }).permit!) + result = reviewable.perform(current_user, params[:action_id].to_sym, args) rescue Reviewable::InvalidAction => e # Consider InvalidAction an InvalidAccess diff --git a/app/serializers/reviewable_action_serializer.rb b/app/serializers/reviewable_action_serializer.rb index cb062b1fb9f..fbba925f27d 100644 --- a/app/serializers/reviewable_action_serializer.rb +++ b/app/serializers/reviewable_action_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ReviewableActionSerializer < ApplicationSerializer - attributes :id, :icon, :button_class, :label, :confirm_message, :description, :client_action, :require_reject_reason + attributes :id, :icon, :button_class, :label, :confirm_message, :description, :client_action, :require_reject_reason, :custom_modal def label I18n.t(object.label) @@ -30,4 +30,8 @@ class ReviewableActionSerializer < ApplicationSerializer def include_require_reject_reason? object.require_reject_reason.present? end + + def include_custom_modal? + object.custom_modal.present? + end end diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb index b3ecd3151a2..840b038977f 100644 --- a/lib/discourse_plugin_registry.rb +++ b/lib/discourse_plugin_registry.rb @@ -84,6 +84,7 @@ class DiscoursePluginRegistry define_filtered_register :user_api_key_scope_mappings define_filtered_register :permitted_bulk_action_parameters + define_filtered_register :reviewable_params def self.register_auth_provider(auth_provider) self.auth_providers << auth_provider diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index a8fd07dadf5..cf27db77f18 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -877,6 +877,13 @@ class Plugin::Instance DiscoursePluginRegistry.demon_processes << demon_class end + def add_permitted_reviewable_param(type, param) + DiscoursePluginRegistry.register_reviewable_param({ + type: type, + param: param + }, self) + end + protected def self.js_path diff --git a/lib/reviewable/actions.rb b/lib/reviewable/actions.rb index bf265969e9a..ba7858d87d2 100644 --- a/lib/reviewable/actions.rb +++ b/lib/reviewable/actions.rb @@ -33,7 +33,7 @@ class Reviewable < ActiveRecord::Base end class Action < Item - attr_accessor :icon, :button_class, :label, :description, :confirm_message, :client_action, :require_reject_reason + attr_accessor :icon, :button_class, :label, :description, :confirm_message, :client_action, :require_reject_reason, :custom_modal def initialize(id, icon = nil, button_class = nil, label = nil) super(id) diff --git a/spec/requests/reviewables_controller_spec.rb b/spec/requests/reviewables_controller_spec.rb index 2504a7c2a54..6dc100a4cad 100644 --- a/spec/requests/reviewables_controller_spec.rb +++ b/spec/requests/reviewables_controller_spec.rb @@ -432,6 +432,48 @@ describe ReviewablesController do end end + describe "with reviewable params added via plugin API" do + class ::ReviewablePhony < Reviewable + def build_actions(actions, guardian, _args) + return [] unless pending? + + actions.add(:approve_phony) do |action| + action.label = "js.phony.review.approve" + end + end + + def perform_approve_phony(performed_by, args) + puts args.inspect + MessageBus.publish("/phony-reviewable-test", { args: args }, user_ids: [1]) + create_result(:success, :approved) + end + end + + before do + plugin = Plugin::Instance.new + plugin.add_permitted_reviewable_param(:reviewable_phony, :fake_id) + end + + after do + DiscoursePluginRegistry.reset! + end + + fab!(:reviewable_phony) { Fabricate(:reviewable, type: "ReviewablePhony") } + + it "passes the added param into the reviewable class' perform method" do + MessageBus.expects(:publish) + .with("/phony-reviewable-test", { args: { + version: reviewable_phony.version, + "fake_id" => "2" } + }, + { user_ids: [1] }) + .once + + put "/review/#{reviewable_phony.id}/perform/approve_phony.json?version=#{reviewable_phony.version}", params: { fake_id: 2 } + expect(response.status).to eq(200) + end + end + context "#topics" do fab!(:post0) { Fabricate(:post) } fab!(:post1) { Fabricate(:post, topic: post0.topic) }