From 8d883fba938528492815b1d015b85bcf5e83b47d Mon Sep 17 00:00:00 2001 From: Blake Erickson Date: Fri, 6 Mar 2020 11:28:29 -0700 Subject: [PATCH] FEATURE: Publish WebHook event when solving/unsolving (#85) * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 * UX: Add "solved" status filter in advanced search page. And rename `in:solved` to `status:solved`. * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 Co-authored-by: Vinoth Kannan --- config/locales/client.en.yml | 6 +++++ plugin.rb | 27 ++++++++++++++++++++++ spec/fabricators/solved_hook_fabricator.rb | 9 ++++++++ spec/integration/solved_spec.rb | 24 +++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 spec/fabricators/solved_hook_fabricator.rb diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index b7bf1d2..4361381 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -32,3 +32,9 @@ en: advanced: statuses: solved: "are solved" + + admin: + web_hooks: + solved_event: + name: "Solved Event" + details: "When a user marks a post as the accepted or unaccepted answer." diff --git a/plugin.rb b/plugin.rb index 19af807..26c2378 100644 --- a/plugin.rb +++ b/plugin.rb @@ -20,6 +20,7 @@ register_asset 'stylesheets/solutions.scss' register_asset 'stylesheets/mobile/solutions.scss', :mobile after_initialize do + SeedFu.fixture_paths << Rails.root.join("plugins", "discourse-solved", "db", "fixtures").to_s [ @@ -138,6 +139,11 @@ SQL topic.save! post.save! + if WebHook.active_web_hooks(:solved).exists? + payload = WebHook.generate_payload(:post, post) + WebHook.enqueue_solved_hooks(:accepted_solution, post, payload) + end + DiscourseEvent.trigger(:accepted_solution, post) end @@ -172,6 +178,12 @@ SQL ) notification.destroy! if notification + + if WebHook.active_web_hooks(:solved).exists? + payload = WebHook.generate_payload(:post, post) + WebHook.enqueue_solved_hooks(:unaccepted_solution, post, payload) + end + DiscourseEvent.trigger(:unaccepted_solution, post) end end @@ -348,6 +360,21 @@ SQL end end + class ::WebHook + def self.enqueue_solved_hooks(event, post, payload = nil) + if active_web_hooks('solved').exists? && post.present? + payload ||= WebHook.generate_payload(:post, post) + + WebHook.enqueue_hooks(:solved, event, + id: post.id, + category_id: post.topic&.category_id, + tag_ids: post.topic&.tags&.pluck(:id), + payload: payload + ) + end + end + end + require_dependency 'topic_view_serializer' class ::TopicViewSerializer attributes :accepted_answer diff --git a/spec/fabricators/solved_hook_fabricator.rb b/spec/fabricators/solved_hook_fabricator.rb new file mode 100644 index 0000000..641fef8 --- /dev/null +++ b/spec/fabricators/solved_hook_fabricator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +Fabricator(:solved_web_hook, from: :web_hook) do + transient solved_hook: WebHookEventType.find_by(name: 'solved') + + after_build do |web_hook, transients| + web_hook.web_hook_event_types = [transients[:solved_hook]] + end +end diff --git a/spec/integration/solved_spec.rb b/spec/integration/solved_spec.rb index fda575a..e39a87e 100644 --- a/spec/integration/solved_spec.rb +++ b/spec/integration/solved_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'rails_helper' +require_relative '../fabricators/solved_hook_fabricator.rb' RSpec.describe "Managing Posts solved status" do let(:topic) { Fabricate(:topic) } @@ -98,6 +99,17 @@ RSpec.describe "Managing Posts solved status" do post "/solution/accept.json", params: { id: whisper.id } expect(response.status).to eq(403) end + + it 'triggers a webhook' do + Fabricate(:solved_web_hook) + post "/solution/accept.json", params: { id: p1.id } + + job_args = Jobs::EmitWebHookEvent.jobs[0]["args"].first + + expect(job_args["event_name"]).to eq("accepted_solution") + payload = JSON.parse(job_args["payload"]) + expect(payload["id"]).to eq(p1.id) + end end describe '#unaccept' do @@ -122,6 +134,18 @@ RSpec.describe "Managing Posts solved status" do expect(p1.custom_fields["is_accepted_answer"]).to eq(nil) expect(p1.topic.custom_fields["accepted_answer_post_id"]).to eq(nil) end + + end + + it 'triggers a webhook' do + Fabricate(:solved_web_hook) + post "/solution/unaccept.json", params: { id: p1.id } + + job_args = Jobs::EmitWebHookEvent.jobs[0]["args"].first + + expect(job_args["event_name"]).to eq("unaccepted_solution") + payload = JSON.parse(job_args["payload"]) + expect(payload["id"]).to eq(p1.id) end end end