FEATURE: Filter topic and post web hook events by tags (#6726)

* FEATURE: Filter topic and post web hook events by tags

* Add a spec test with unmatched tags
This commit is contained in:
Vinoth Kannan 2018-12-05 14:44:06 +05:30 committed by GitHub
parent 1a71f98d28
commit d33d031742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 72 additions and 0 deletions

View File

@ -9,6 +9,11 @@ export default Ember.Controller.extend({
defaultEventTypes: Ember.computed.alias("adminWebHooks.defaultEventTypes"), defaultEventTypes: Ember.computed.alias("adminWebHooks.defaultEventTypes"),
contentTypes: Ember.computed.alias("adminWebHooks.contentTypes"), contentTypes: Ember.computed.alias("adminWebHooks.contentTypes"),
@computed
showTagsFilter() {
return this.siteSettings.tagging_enabled;
},
@computed("model.isSaving", "saved", "saveButtonDisabled") @computed("model.isSaving", "saved", "saveButtonDisabled")
savingStatus(isSaving, saved, saveButtonDisabled) { savingStatus(isSaving, saved, saveButtonDisabled) {
if (isSaving) { if (isSaving) {

View File

@ -63,6 +63,7 @@ export default RestModel.extend({
createProperties() { createProperties() {
const types = this.get("web_hook_event_types"); const types = this.get("web_hook_event_types");
const categoryIds = this.get("categories").map(c => c.id); const categoryIds = this.get("categories").map(c => c.id);
const tagNames = this.get("tag_names");
// Hack as {{group-selector}} accepts a comma-separated string as data source, but // Hack as {{group-selector}} accepts a comma-separated string as data source, but
// we use an array to populate the datasource above. // we use an array to populate the datasource above.
@ -81,6 +82,7 @@ export default RestModel.extend({
? [null] ? [null]
: types.map(type => type.id), : types.map(type => type.id),
category_ids: Ember.isEmpty(categoryIds) ? [null] : categoryIds, category_ids: Ember.isEmpty(categoryIds) ? [null] : categoryIds,
tag_names: Ember.isEmpty(tagNames) ? [null] : tagNames,
group_ids: group_ids:
Ember.isEmpty(groupNames) || Ember.isEmpty(groupNames[0]) Ember.isEmpty(groupNames) || Ember.isEmpty(groupNames[0])
? [null] ? [null]

View File

@ -19,6 +19,7 @@ export default Discourse.Route.extend({
} }
model.set("category_ids", model.get("category_ids")); model.set("category_ids", model.get("category_ids"));
model.set("tag_names", model.get("tag_names"));
model.set("group_ids", model.get("group_ids")); model.set("group_ids", model.get("group_ids"));
controller.setProperties({ model, saved: false }); controller.setProperties({ model, saved: false });
}, },

View File

@ -51,6 +51,13 @@
{{category-selector categories=model.categories}} {{category-selector categories=model.categories}}
<div class="instructions">{{i18n 'admin.web_hooks.categories_filter_instructions'}}</div> <div class="instructions">{{i18n 'admin.web_hooks.categories_filter_instructions'}}</div>
</div> </div>
{{#if showTagsFilter}}
<div class="filter">
<label>{{d-icon 'circle' class='tracking'}}{{i18n 'admin.web_hooks.tags_filter'}}</label>
{{tag-chooser tags=model.tag_names everyTag=true}}
<div class="instructions">{{i18n 'admin.web_hooks.tags_filter_instructions'}}</div>
</div>
{{/if}}
<div class="filter"> <div class="filter">
<label>{{d-icon 'circle' class='tracking'}}{{i18n 'admin.web_hooks.groups_filter'}}</label> <label>{{d-icon 'circle' class='tracking'}}{{i18n 'admin.web_hooks.groups_filter'}}</label>
{{group-selector groupNames=model.groupsFilterInName groupFinder=model.groupFinder}} {{group-selector groupNames=model.groupsFilterInName groupFinder=model.groupFinder}}

View File

@ -111,6 +111,7 @@ class Admin::WebHooksController < Admin::AdminController
:wildcard_web_hook, :active, :verify_certificate, :wildcard_web_hook, :active, :verify_certificate,
web_hook_event_type_ids: [], web_hook_event_type_ids: [],
group_ids: [], group_ids: [],
tag_names: [],
category_ids: []) category_ids: [])
end end

View File

@ -28,6 +28,9 @@ module Jobs
return if web_hook.category_ids.present? && (!args[:category_id].present? || return if web_hook.category_ids.present? && (!args[:category_id].present? ||
!web_hook.category_ids.include?(args[:category_id])) !web_hook.category_ids.include?(args[:category_id]))
return if web_hook.tag_ids.present? && (args[:tag_ids].blank? ||
(web_hook.tag_ids & args[:tag_ids]).blank?)
raise Discourse::InvalidParameters.new(:payload) unless args[:payload].present? raise Discourse::InvalidParameters.new(:payload) unless args[:payload].present?
args[:payload] = JSON.parse(args[:payload]) args[:payload] = JSON.parse(args[:payload])
end end

View File

@ -2,6 +2,7 @@ class WebHook < ActiveRecord::Base
has_and_belongs_to_many :web_hook_event_types has_and_belongs_to_many :web_hook_event_types
has_and_belongs_to_many :groups has_and_belongs_to_many :groups
has_and_belongs_to_many :categories has_and_belongs_to_many :categories
has_and_belongs_to_many :tags
has_many :web_hook_events, dependent: :destroy has_many :web_hook_events, dependent: :destroy
@ -15,6 +16,10 @@ class WebHook < ActiveRecord::Base
before_save :strip_url before_save :strip_url
def tag_names=(tag_names_arg)
DiscourseTagging.add_or_create_tags_by_name(self, tag_names_arg, unlimited: true)
end
def self.content_types def self.content_types
@content_types ||= Enum.new('application/json' => 1, @content_types ||= Enum.new('application/json' => 1,
'application/x-www-form-urlencoded' => 2) 'application/x-www-form-urlencoded' => 2)
@ -68,6 +73,7 @@ class WebHook < ActiveRecord::Base
WebHook.enqueue_hooks(:topic, event, WebHook.enqueue_hooks(:topic, event,
id: topic.id, id: topic.id,
category_id: topic&.category_id, category_id: topic&.category_id,
tag_ids: topic&.tags.pluck(:id),
payload: payload payload: payload
) )
end end
@ -80,6 +86,7 @@ class WebHook < ActiveRecord::Base
WebHook.enqueue_hooks(:post, event, WebHook.enqueue_hooks(:post, event,
id: post.id, id: post.id,
category_id: post&.topic&.category_id, category_id: post&.topic&.category_id,
tag_ids: post&.topic&.tags.pluck(:id),
payload: payload payload: payload
) )
end end

View File

@ -10,6 +10,7 @@ class AdminWebHookSerializer < ApplicationSerializer
:web_hook_event_types :web_hook_event_types
has_many :categories, serializer: BasicCategorySerializer, embed: :ids, include: false has_many :categories, serializer: BasicCategorySerializer, embed: :ids, include: false
has_many :tags, key: :tag_names, serializer: TagSerializer, embed: :ids, embed_key: :name, include: false
has_many :groups, serializer: BasicGroupSerializer, embed: :ids, include: false has_many :groups, serializer: BasicGroupSerializer, embed: :ids, include: false
def web_hook_event_types def web_hook_event_types

View File

@ -3082,6 +3082,8 @@ en:
active_notice: "We will deliver event details when it happens." active_notice: "We will deliver event details when it happens."
categories_filter_instructions: "Relevant webhooks will only be triggered if the event is related with specified categories. Leave blank to trigger webhooks for all categories." categories_filter_instructions: "Relevant webhooks will only be triggered if the event is related with specified categories. Leave blank to trigger webhooks for all categories."
categories_filter: "Triggered Categories" categories_filter: "Triggered Categories"
tags_filter_instructions: "Relevant webhooks will only be triggered if the event is related with specified tags. Leave blank to trigger webhooks for all tags."
tags_filter: "Triggered Tags"
groups_filter_instructions: "Relevant webhooks will only be triggered if the event is related with specified groups. Leave blank to trigger webhooks for all groups." groups_filter_instructions: "Relevant webhooks will only be triggered if the event is related with specified groups. Leave blank to trigger webhooks for all groups."
groups_filter: "Triggered Groups" groups_filter: "Triggered Groups"
delete_confirm: "Delete this webhook?" delete_confirm: "Delete this webhook?"

View File

@ -0,0 +1,7 @@
class CreateJoinTableWebHooksTags < ActiveRecord::Migration[5.2]
def change
create_join_table :web_hooks, :tags do |t|
t.index [:web_hook_id, :tag_id], name: 'web_hooks_tags', unique: true
end
end
end

View File

@ -125,6 +125,42 @@ describe Jobs::EmitWebHookEvent do
end end
end end
context 'with tag filters' do
let(:tag) { Fabricate(:tag) }
let(:topic) { Fabricate(:topic, tags: [tag]) }
let(:topic_hook) { Fabricate(:topic_web_hook, tags: [tag]) }
it "doesn't emit when event is not included any tags" do
subject.execute(
web_hook_id: topic_hook.id,
event_type: 'topic',
payload: { test: "some payload" }.to_json
)
end
it "doesn't emit when event is not related with defined tags" do
subject.execute(
web_hook_id: topic_hook.id,
event_type: 'topic',
tag_ids: [Fabricate(:tag).id],
payload: { test: "some payload" }.to_json
)
end
it 'emit when event is related with defined tags' do
stub_request(:post, "https://meta.discourse.org/webhook_listener")
.with(body: "{\"topic\":{\"test\":\"some payload\"}}")
.to_return(body: 'OK', status: 200)
subject.execute(
web_hook_id: topic_hook.id,
event_type: 'topic',
tag_ids: topic.tags.pluck(:id),
payload: { test: "some payload" }.to_json
)
end
end
describe '#web_hook_request' do describe '#web_hook_request' do
it 'creates delivery event record' do it 'creates delivery event record' do
stub_request(:post, "https://meta.discourse.org/webhook_listener") stub_request(:post, "https://meta.discourse.org/webhook_listener")