diff --git a/app/controllers/chat_controller.rb b/app/controllers/chat_controller.rb index 42879f8..257cafb 100644 --- a/app/controllers/chat_controller.rb +++ b/app/controllers/chat_controller.rb @@ -118,7 +118,7 @@ class DiscourseChat::ChatController < ApplicationController def create_rule begin - hash = params.require(:rule).permit(:channel_id, :filter, :category_id, tags:[]) + hash = params.require(:rule).permit(:channel_id, :type, :filter, :group_id, :category_id, tags:[]) rule = DiscourseChat::Rule.new(hash) @@ -135,7 +135,7 @@ class DiscourseChat::ChatController < ApplicationController def update_rule begin rule = DiscourseChat::Rule.find(params[:id].to_i) - hash = params.require(:rule).permit(:filter, :category_id, tags:[]) + hash = params.require(:rule).permit(:type, :filter, :group_id, :category_id, tags:[]) if not rule.update(hash) raise Discourse::InvalidParameters, 'Rule is not valid' diff --git a/app/helpers/helper.rb b/app/helpers/helper.rb index 7deca25..c50aac6 100644 --- a/app/helpers/helper.rb +++ b/app/helpers/helper.rb @@ -82,14 +82,25 @@ module DiscourseChat i = 1 rules.each do |rule| category_id = rule.category_id - if category_id.nil? - category_name = I18n.t("chat_integration.all_categories") - else - category = Category.find_by(id: category_id) - if category - category_name = category.slug + + case rule.type + when "normal" + if category_id.nil? + category_name = I18n.t("chat_integration.all_categories") else - category_name = I18n.t("chat_integration.deleted_category") + category = Category.find_by(id: category_id) + if category + category_name = category.slug + else + category_name = I18n.t("chat_integration.deleted_category") + end + end + when "group_mention", "group_message" + group = Group.find_by(id: rule.group_id) + if group + category_name = I18n.t("chat_integration.#{rule.type}_template", name: group.name) + else + category_name = I18n.t("chat_integration.deleted_group") end end diff --git a/app/initializers/discourse_chat.rb b/app/initializers/discourse_chat.rb index 0ab00a7..e6754c6 100644 --- a/app/initializers/discourse_chat.rb +++ b/app/initializers/discourse_chat.rb @@ -47,4 +47,6 @@ require_relative "../services/manager" require_relative "../jobs/regular/notify_chats" -require_relative "../../lib/discourse_chat/provider" \ No newline at end of file +require_relative "../../lib/discourse_chat/provider" + +require_relative "../jobs/onceoff/add_type_field" diff --git a/app/jobs/onceoff/add_type_field.rb b/app/jobs/onceoff/add_type_field.rb new file mode 100644 index 0000000..20de608 --- /dev/null +++ b/app/jobs/onceoff/add_type_field.rb @@ -0,0 +1,9 @@ +module Jobs + class DiscourseChatAddTypeField < Jobs::Onceoff + def execute_onceoff(args) + DiscourseChat::Rule.find_each do |rule| + rule.save(validate: false) + end + end + end +end diff --git a/app/models/channel.rb b/app/models/channel.rb index c2730fb..1fe280d 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -59,7 +59,7 @@ class DiscourseChat::Channel < DiscourseChat::PluginModel end def rules - DiscourseChat::Rule.with_channel_id(id) + DiscourseChat::Rule.with_channel_id(id).order_by_precedence end scope :with_provider, ->(provider) { where("value::json->>'provider'=?", provider)} diff --git a/app/models/rule.rb b/app/models/rule.rb index 6ccd4f4..ca1bbfb 100644 --- a/app/models/rule.rb +++ b/app/models/rule.rb @@ -2,33 +2,56 @@ class DiscourseChat::Rule < DiscourseChat::PluginModel KEY_PREFIX = 'rule:' # Setup ActiveRecord::Store to use the JSON field to read/write these values - store :value, accessors: [ :channel_id, :category_id, :tags, :filter ], coder: JSON + store :value, accessors: [ :channel_id, :type, :group_id, :category_id, :tags, :filter ], coder: JSON after_initialize :init_filter def init_filter self.filter ||= 'watch' + self.type ||= 'normal' end validates :filter, :inclusion => { :in => %w(watch follow mute), :message => "%{value} is not a valid filter" } - validate :channel_valid?, :category_valid?, :tags_valid? + validates :type, :inclusion => { :in => %w(normal group_message group_mention), + :message => "%{value} is not a valid filter" } + + validate :channel_valid?, :category_valid?, :group_valid?, :tags_valid? def channel_valid? - # Validate category + # Validate channel if not (DiscourseChat::Channel.where(id: channel_id).exists?) errors.add(:channel_id, "#{channel_id} is not a valid channel id") end end def category_valid? + if type != 'normal' && !category_id.nil? + errors.add(:category_id, "cannot be specified for that type of rule") + end + + return unless type == 'normal' + # Validate category if not (category_id.nil? or Category.where(id: category_id).exists?) errors.add(:category_id, "#{category_id} is not a valid category id") end end + def group_valid? + if type == 'normal' && !group_id.nil? + errors.add(:group_id, "cannot be specified for that type of rule") + end + + return if type == 'normal' + + # Validate group + if not Group.where(id: group_id).exists? + errors.add(:group_id, "#{group_id} is not a valid group id") + end + end + def tags_valid? # Validate tags return if tags.nil? @@ -48,22 +71,16 @@ class DiscourseChat::Rule < DiscourseChat::PluginModel end end - # Don't want this to end up as anything other than an integer - def category_id=(val) - if val.nil? or val.blank? - super(nil) - else - super(val.to_i) + # These are only allowed to be integers + %w(channel_id category_id group_id).each do |name| + define_method "#{name}=" do |val| + if val.nil? or val.blank? + super(nil) + else + super(val.to_i) + end end - end - # Don't want this to end up as anything other than an integer - def channel_id=(val) - if val.nil? or val.blank? - super(nil) - else - super(val.to_i) - end end # Mock foreign key @@ -75,12 +92,20 @@ class DiscourseChat::Rule < DiscourseChat::PluginModel self.channel_id = val.id end + scope :with_type, ->(type) { where("value::json->>'type'=?", type.to_s)} + scope :with_channel, ->(channel) { with_channel_id(channel.id) } scope :with_channel_id, ->(channel_id) { where("value::json->>'channel_id'=?", channel_id.to_s)} scope :with_category_id, ->(category_id) { category_id.nil? ? where("(value::json->'category_id') IS NULL OR json_typeof(value::json->'category_id')='null'") : where("value::json->>'category_id'=?", category_id.to_s)} + scope :with_group_ids, ->(group_id) { where("value::json->>'group_id' IN (?)", group_id.map(&:to_s))} scope :order_by_precedence, ->{ order("CASE + WHEN value::json->>'type' = 'group_mention' THEN 1 + WHEN value::json->>'type' = 'group_message' THEN 2 + ELSE 3 + END", + "CASE WHEN value::json->>'filter' = 'mute' THEN 1 WHEN value::json->>'filter' = 'watch' THEN 2 WHEN value::json->>'filter' = 'follow' THEN 3 diff --git a/app/serializers/rule_serializer.rb b/app/serializers/rule_serializer.rb index 57a0581..efaf02b 100644 --- a/app/serializers/rule_serializer.rb +++ b/app/serializers/rule_serializer.rb @@ -1,3 +1,14 @@ class DiscourseChat::RuleSerializer < ApplicationSerializer - attributes :id, :channel_id, :category_id, :tags, :filter + attributes :id, :channel_id, :type, :group_id, :group_name, :category_id, :tags, :filter + + def group_name + if object.group_id + groups = Group.where(id:object.group_id) + if groups.exists? + return groups.first.name + else + return I18n.t("chat_integration.deleted_group") + end + end + end end \ No newline at end of file diff --git a/app/services/manager.rb b/app/services/manager.rb index 78bee22..8c6ac82 100644 --- a/app/services/manager.rb +++ b/app/services/manager.rb @@ -18,14 +18,28 @@ module DiscourseChat topic = post.topic - # Abort if a private message (possible TODO: Add support for notifying about group PMs) - return if topic.blank? || topic.archetype == Archetype.private_message + # Abort if topic is blank... this should never be the case + return if topic.blank? - # Load all the rules that apply to this topic's category - matching_rules = DiscourseChat::Rule.with_category_id(topic.category_id) + # If it's a private message, filter rules by groups, otherwise filter rules by category + if topic.archetype == Archetype.private_message + group_ids_with_access = topic.topic_allowed_groups.pluck(:group_id) + return if group_ids_with_access.empty? + matching_rules = DiscourseChat::Rule.with_type('group_message').with_group_ids(group_ids_with_access) + else + matching_rules = DiscourseChat::Rule.with_type('normal').with_category_id(topic.category_id) + if topic.category # Also load the rules for the wildcard category + matching_rules += DiscourseChat::Rule.with_type('normal').with_category_id(nil) + end + end - if topic.category # Also load the rules for the wildcard category - matching_rules += DiscourseChat::Rule.with_category_id(nil) + # If groups are mentioned, check for any matching rules and append them + mentions = post.raw_mentions + if mentions && mentions.length > 0 + groups = Group.where('LOWER(name) IN (?)', mentions) + if groups.exists? + matching_rules += DiscourseChat::Rule.with_type('group_mention').with_group_ids(groups.map(&:id)) + end end # If tagging is enabled, thow away rules that don't apply to this topic diff --git a/assets/javascripts/admin/components/channel-data.js.es6 b/assets/javascripts/admin/components/channel-data.js.es6 new file mode 100644 index 0000000..259fb6c --- /dev/null +++ b/assets/javascripts/admin/components/channel-data.js.es6 @@ -0,0 +1,3 @@ +export default Ember.Component.extend({ + classNames: ['channel-info'], +}); \ No newline at end of file diff --git a/assets/javascripts/admin/components/channel-details.js.es6 b/assets/javascripts/admin/components/channel-details.js.es6 index 08fcd1b..3b9195c 100644 --- a/assets/javascripts/admin/components/channel-details.js.es6 +++ b/assets/javascripts/admin/components/channel-details.js.es6 @@ -26,8 +26,11 @@ export default Ember.Component.extend({ }, createRule(channel){ - var newRule = this.get('store').createRecord('rule',{channel_id: channel.id}); - channel.rules.pushObject(newRule) + this.sendAction('createRule', channel) + }, + + editRule(rule){ + this.sendAction('editRule', rule, this.get('channel')) }, showError(error_key){ diff --git a/assets/javascripts/admin/components/rule-row.js.es6 b/assets/javascripts/admin/components/rule-row.js.es6 index 7622fa1..f246e98 100644 --- a/assets/javascripts/admin/components/rule-row.js.es6 +++ b/assets/javascripts/admin/components/rule-row.js.es6 @@ -2,37 +2,33 @@ import { popupAjaxError } from 'discourse/lib/ajax-error'; export default Ember.Component.extend({ tagName: 'tr', - editing: false, - autoEdit: function(){ - if(!this.get('rule').id){ - this.set('editing', true); - } - }.on('init'), + isCategory: function(){ + return this.get('rule.type') == 'normal' + }.property('rule.type'), + + isMessage: function(){ + return this.get('rule.type') == 'group_message' + }.property('rule.type'), + + isMention: function(){ + return this.get('rule.type') == 'group_mention' + }.property('rule.type'), actions: { edit: function(){ - this.set('editing', true); + this.sendAction('edit', this.get('rule')) }, - - cancel: function(){ - this.send('refresh'); - }, - - save: function(){ - this.get('rule').save().then(result => { - this.send('refresh'); - }).catch(popupAjaxError); - }, - delete(rule){ rule.destroyRecord().then(() => { this.send('refresh'); }).catch(popupAjaxError) }, - refresh: function(){ this.sendAction('refresh'); } + + + } }); \ No newline at end of file diff --git a/assets/javascripts/admin/controllers/admin-plugins-chat-provider.js.es6 b/assets/javascripts/admin/controllers/admin-plugins-chat-provider.js.es6 index f52965c..1d15db3 100644 --- a/assets/javascripts/admin/controllers/admin-plugins-chat-provider.js.es6 +++ b/assets/javascripts/admin/controllers/admin-plugins-chat-provider.js.es6 @@ -35,6 +35,17 @@ export default Ember.Controller.extend({ showModal('admin-plugins-chat-test', { model: model, admin: true }); }, + createRule(channel){ + this.set('modalShowing', true); + var model = {rule: this.store.createRecord('rule',{channel_id: channel.id}), channel:channel, provider:this.get('model.provider'), groups:this.get('model.groups')}; + showModal('admin-plugins-chat-edit-rule', { model: model, admin: true }); + }, + editRule(rule, channel){ + this.set('modalShowing', true); + var model = {rule: rule, channel:channel, provider:this.get('model.provider'), groups:this.get('model.groups')}; + showModal('admin-plugins-chat-edit-rule', { model: model, admin: true }); + }, + } }); \ No newline at end of file diff --git a/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-rule.js.es6 b/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-rule.js.es6 new file mode 100644 index 0000000..aa32187 --- /dev/null +++ b/assets/javascripts/admin/controllers/modals/admin-plugins-chat-edit-rule.js.es6 @@ -0,0 +1,46 @@ +import Rule from 'discourse/plugins/discourse-chat-integration/admin/models/rule' +import ModalFunctionality from 'discourse/mixins/modal-functionality'; +import { ajax } from 'discourse/lib/ajax'; +import { extractError } from 'discourse/lib/ajax-error'; +import InputValidation from 'discourse/models/input-validation'; +import computed from "ember-addons/ember-computed-decorators"; + +export default Ember.Controller.extend(ModalFunctionality, { + setupKeydown: function() { + Ember.run.schedule('afterRender', () => { + $('#chat_integration_edit_channel_modal').keydown(e => { + if (e.keyCode === 13) { + this.send('save'); + } + }); + }); + }.on('init'), + + saveDisabled: function(){ + return false; + }.property(), + + @computed('model.rule.type') + showCategory: function(type){ + return (type == "normal") + }, + + actions: { + cancel: function(){ + this.send('closeModal'); + }, + + save: function(){ + if(this.get('saveDisabled')){return}; + + const self = this; + + this.get('model.rule').save().then(function(result) { + self.send('closeModal'); + }).catch(function(error) { + self.flash(extractError(error), 'error'); + }); + + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/admin/models/rule.js.es6 b/assets/javascripts/admin/models/rule.js.es6 index 44fe2c8..70fab17 100644 --- a/assets/javascripts/admin/models/rule.js.es6 +++ b/assets/javascripts/admin/models/rule.js.es6 @@ -9,12 +9,29 @@ export default RestModel.extend({ { id: 'mute', name: I18n.t('chat_integration.filter.mute'), icon: 'times-circle' } ], + available_types: [ + { id: 'normal', name: I18n.t('chat_integration.type.normal')}, + { id: 'group_message', name: I18n.t('chat_integration.type.group_message')}, + { id: 'group_mention', name: I18n.t('chat_integration.type.group_mention')} + ], + category_id: null, tags: null, channel_id: null, filter: 'watch', + type: 'normal', error_key: null, + + removeUnneededInfo: function(){ + const type=this.get('type'); + if(type=='normal'){ + this.set('group_id', null); + }else{ + this.set('category_id', null); + } + }.observes('type'), + @computed('category_id') category(categoryId) { if (categoryId){ @@ -30,12 +47,12 @@ export default RestModel.extend({ }, updateProperties() { - var prop_names = ['category_id','tags','filter']; + var prop_names = ['type','category_id','group_id','tags','filter']; return this.getProperties(prop_names); }, createProperties() { - var prop_names = ['channel_id', 'category_id','tags','filter']; + var prop_names = ['type','channel_id', 'category_id','group_id','tags','filter']; return this.getProperties(prop_names); } diff --git a/assets/javascripts/admin/routes/admin-plugins-chat-provider.js.es6 b/assets/javascripts/admin/routes/admin-plugins-chat-provider.js.es6 index 7889816..216f407 100644 --- a/assets/javascripts/admin/routes/admin-plugins-chat-provider.js.es6 +++ b/assets/javascripts/admin/routes/admin-plugins-chat-provider.js.es6 @@ -1,11 +1,15 @@ import { ajax } from 'discourse/lib/ajax'; +import Group from 'discourse/models/group'; export default Discourse.Route.extend({ model(params, transition) { return Ember.RSVP.hash({ channels: this.store.findAll('channel', {provider: params.provider}), - provider: this.modelFor("admin-plugins-chat").findBy('id',params.provider) + provider: this.modelFor("admin-plugins-chat").findBy('id',params.provider), + groups: Group.findAll().then(groups => { + return groups.filter(g => !g.get('automatic')); + }) }).then(value => { value.channels.forEach(channel => { channel.set('rules', channel.rules.map(rule => { diff --git a/assets/javascripts/admin/templates/modal/admin-plugins-chat-edit-rule.hbs b/assets/javascripts/admin/templates/modal/admin-plugins-chat-edit-rule.hbs new file mode 100644 index 0000000..4b2e83c --- /dev/null +++ b/assets/javascripts/admin/templates/modal/admin-plugins-chat-edit-rule.hbs @@ -0,0 +1,107 @@ +{{#d-modal-body id="chat_integration_edit_rule_modal" title="chat_integration.edit_rule_modal.title"}} +