FEATURE: improved tag and category watching and tracking

- present tags watched on the user prefs page
- automatically watch or unwatch old topics based on watch status

New watching and tracking logic takes care of handling old topics
(either with or without read state)

When you watch a topic you now watch historically

Also removes confusing warnings from user.
This commit is contained in:
Sam 2016-07-08 12:58:18 +10:00
parent 58c2389a7b
commit 4161ee210a
19 changed files with 583 additions and 224 deletions

View file

@ -36,8 +36,13 @@ export default Ember.TextField.extend({
const site = this.site, const site = this.site,
self = this, self = this,
filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"); filterRegexp = new RegExp(this.site.tags_filter_regexp, "g");
var limit = this.siteSettings.max_tags_per_topic; var limit = this.siteSettings.max_tags_per_topic;
if (this.get('allowCreate') !== false) {
this.set('allowCreate', site.get('can_create_tag'));
}
if (this.get('unlimitedTagCount')) { if (this.get('unlimitedTagCount')) {
limit = null; limit = null;
} else if (this.get('limit')) { } else if (this.get('limit')) {
@ -46,7 +51,7 @@ export default Ember.TextField.extend({
this.$().select2({ this.$().select2({
tags: true, tags: true,
placeholder: I18n.t(this.get('placeholderKey') || 'tagging.choose_for_topic'), placeholder: this.get('placeholder') === "" ? "" : I18n.t(this.get('placeholderKey') || 'tagging.choose_for_topic'),
maximumInputLength: this.siteSettings.max_tag_length, maximumInputLength: this.siteSettings.max_tag_length,
maximumSelectionSize: limit, maximumSelectionSize: limit,
initSelection(element, callback) { initSelection(element, callback) {
@ -73,7 +78,7 @@ export default Ember.TextField.extend({
term = term.replace(filterRegexp, '').trim(); term = term.replace(filterRegexp, '').trim();
// No empty terms, make sure the user has permission to create the tag // No empty terms, make sure the user has permission to create the tag
if (!term.length || !site.get('can_create_tag')) { return; } if (!term.lenght || !this.get('allowCreate')) return;
if ($(data).filter(function() { if ($(data).filter(function() {
return this.text.localeCompare(term) === 0; return this.text.localeCompare(term) === 0;

View file

@ -2,7 +2,6 @@ import { setting } from 'discourse/lib/computed';
import CanCheckEmails from 'discourse/mixins/can-check-emails'; import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import computed from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
export default Ember.Controller.extend(CanCheckEmails, { export default Ember.Controller.extend(CanCheckEmails, {
@ -136,24 +135,6 @@ export default Ember.Controller.extend(CanCheckEmails, {
const model = this.get('model'); const model = this.get('model');
// watched status changes warn user
const changedWatch = model.changedCategoryNotifications("watched");
if (changedWatch.remove.length > 0 && !this.get("warnedRemoveWatch")) {
var categories = Discourse.Category.findByIds(changedWatch.remove).map((cat) => {
return categoryBadgeHTML(cat);
}).join(" ");
bootbox.confirm(I18n.t('user.warn_unwatch.message', {categories: categories}),
I18n.t('user.warn_unwatch.no_value', {count: changedWatch.remove.length}), I18n.t('user.warn_unwatch.yes_value'),
(yes)=>{
this.set('unwatchCategoryTopics', yes ? changedWatch.remove : false);
this.send('save');
});
this.set("warnedRemoveWatch", true);
return;
}
const userFields = this.get('userFields'); const userFields = this.get('userFields');
// Update the user fields // Update the user fields
@ -169,9 +150,6 @@ export default Ember.Controller.extend(CanCheckEmails, {
// Cook the bio for preview // Cook the bio for preview
model.set('name', this.get('newNameInput')); model.set('name', this.get('newNameInput'));
var options = {}; var options = {};
if (this.get('warnedRemoveWatch') && this.get('unwatchCategoryTopics')) {
options["unwatchCategoryTopics"] = this.get("unwatchCategoryTopics");
}
return model.save(options).then(() => { return model.save(options).then(() => {
if (Discourse.User.currentProp('id') === model.get('id')) { if (Discourse.User.currentProp('id') === model.get('id')) {
@ -179,8 +157,6 @@ export default Ember.Controller.extend(CanCheckEmails, {
} }
model.set('bio_cooked', Discourse.Markdown.cook(Discourse.Markdown.sanitize(model.get('bio_raw')))); model.set('bio_cooked', Discourse.Markdown.cook(Discourse.Markdown.sanitize(model.get('bio_raw'))));
this.set('saved', true); this.set('saved', true);
this.set("unwatchTopics", false);
this.set('warnedRemoveWatch', false);
}).catch(popupAjaxError); }).catch(popupAjaxError);
}, },

View file

@ -141,7 +141,7 @@ const User = RestModel.extend({
return Discourse.User.create(this.getProperties(Object.keys(this))); return Discourse.User.create(this.getProperties(Object.keys(this)));
}, },
save(options) { save() {
const data = this.getProperties( const data = this.getProperties(
'bio_raw', 'bio_raw',
'website', 'website',
@ -152,7 +152,10 @@ const User = RestModel.extend({
'user_fields', 'user_fields',
'muted_usernames', 'muted_usernames',
'profile_background', 'profile_background',
'card_background' 'card_background',
'muted_tags',
'tracked_tags',
'watched_tags'
); );
[ 'email_always', [ 'email_always',
@ -192,10 +195,6 @@ const User = RestModel.extend({
data['edit_history_public'] = this.get('user_option.edit_history_public'); data['edit_history_public'] = this.get('user_option.edit_history_public');
} }
if (options && options.unwatchCategoryTopics) {
data.unwatch_category_topics = options.unwatchCategoryTopics;
}
// TODO: We can remove this when migrated fully to rest model. // TODO: We can remove this when migrated fully to rest model.
this.set('isSaving', true); this.set('isSaving', true);
return Discourse.ajax(`/users/${this.get('username_lower')}`, { return Discourse.ajax(`/users/${this.get('username_lower')}`, {

View file

@ -274,6 +274,27 @@
</div> </div>
</div> </div>
{{#if siteSettings.tagging_enabled}}
<div class="control-group tags">
<label class="control-label">{{i18n 'user.tag_settings'}}</label>
<div class="controls tag-controls">
<label><span class="icon fa fa-exclamation-circle watching"></span> {{i18n 'user.watched_tags'}}</label>
{{tag-chooser tags=model.watched_tags blacklist=selectedTags allowCreate=false placeholder=""}}
</div>
<div class="instructions">{{i18n 'user.watched_tags_instructions'}}</div>
<div class="controls tag-controls">
<label><span class="icon fa fa-circle tracking"></span> {{i18n 'user.tracked_tags'}}</label>
{{tag-chooser tags=model.tracked_tags blacklist=selectedTags allowCreate=false placeholder=""}}
</div>
<div class="instructions">{{i18n 'user.tracked_tags_instructions'}}</div>
<div class="controls tag-controls">
<label><span class="icon fa fa-times-circle muted"></span> {{i18n 'user.muted_tags'}}</label>
{{tag-chooser tags=model.muted_tags blacklist=selectedTags allowCreate=false placeholder=""}}
</div>
<div class="instructions">{{i18n 'user.muted_tags_instructions'}}</div>
</div>
{{/if}}
<div class="control-group muting"> <div class="control-group muting">
<label class="control-label">{{i18n 'user.users'}}</label> <label class="control-label">{{i18n 'user.users'}}</label>
<div class="controls category-controls"> <div class="controls category-controls">

View file

@ -279,3 +279,12 @@ and (max-width : 600px) {
width: 100%; width: 100%;
} }
} }
.user-preferences .tags .select2-container-multi {
border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
width: 540px;
border-radius: 0;
.select2-choices {
border: none;
}
}

View file

@ -89,10 +89,6 @@ class UsersController < ApplicationController
user = fetch_user_from_params user = fetch_user_from_params
guardian.ensure_can_edit!(user) guardian.ensure_can_edit!(user)
if params[:unwatch_category_topics]
TopicUser.unwatch_categories!(user, params[:unwatch_category_topics])
end
if params[:user_fields].present? if params[:user_fields].present?
params[:custom_fields] = {} unless params[:custom_fields].present? params[:custom_fields] = {} unless params[:custom_fields].present?

View file

@ -20,85 +20,141 @@ class CategoryUser < ActiveRecord::Base
[notification_levels[:watching], notification_levels[:watching_first_post]] [notification_levels[:watching], notification_levels[:watching_first_post]]
end end
%w{watch track}.each do |s|
define_singleton_method("auto_#{s}_new_topic") do |topic, new_category=nil|
category_id = topic.category_id
if new_category && topic.created_at > 5.days.ago
# we want to apply default of the new category
category_id = new_category.id
# remove defaults from previous category
remove_default_from_topic(topic.id, category_id, TopicUser.notification_levels[:"#{s}ing"], TopicUser.notification_reasons[:"auto_#{s}_category"])
end
apply_default_to_topic(topic.id, category_id, TopicUser.notification_levels[:"#{s}ing"], TopicUser.notification_reasons[:"auto_#{s}_category"])
end
end
def self.batch_set(user, level, category_ids) def self.batch_set(user, level, category_ids)
records = CategoryUser.where(user: user, notification_level: notification_levels[level]) records = CategoryUser.where(user: user, notification_level: notification_levels[level])
old_ids = records.pluck(:category_id) old_ids = records.pluck(:category_id)
changed = false
category_ids = Category.where('id in (?)', category_ids).pluck(:id) category_ids = Category.where('id in (?)', category_ids).pluck(:id)
remove = (old_ids - category_ids) remove = (old_ids - category_ids)
if remove.present? if remove.present?
records.where('category_id in (?)', remove).destroy_all records.where('category_id in (?)', remove).destroy_all
changed = true
end end
(category_ids - old_ids).each do |id| (category_ids - old_ids).each do |id|
CategoryUser.create!(user: user, category_id: id, notification_level: notification_levels[level]) CategoryUser.create!(user: user, category_id: id, notification_level: notification_levels[level])
changed = true
end end
if changed
auto_watch(user_id: user.id)
auto_track(user_id: user.id)
end
changed
end end
def self.set_notification_level_for_category(user, level, category_id) def self.set_notification_level_for_category(user, level, category_id)
record = CategoryUser.where(user: user, category_id: category_id).first record = CategoryUser.where(user: user, category_id: category_id).first
return if record && record.notification_level = level
if record.present? if record.present?
record.notification_level = level record.notification_level = level
record.save! record.save!
else else
CategoryUser.create!(user: user, category_id: category_id, notification_level: level) CategoryUser.create!(user: user, category_id: category_id, notification_level: level)
end end
auto_watch(user_id: user.id)
auto_track(user_id: user.id)
end end
def self.apply_default_to_topic(topic_id, category_id, level, reason) def self.auto_track(opts={})
# Can not afford to slow down creation of topics when a pile of users are watching new topics, reverting to SQL for max perf here
sql = <<-SQL
INSERT INTO topic_users(user_id, topic_id, notification_level, notifications_reason_id)
SELECT user_id, :topic_id, :level, :reason
FROM category_users
WHERE notification_level = :level
AND category_id = :category_id
AND NOT EXISTS(SELECT 1 FROM topic_users WHERE topic_id = :topic_id AND user_id = category_users.user_id)
SQL
exec_sql(sql, builder = SqlBuilder.new <<SQL
topic_id: topic_id, UPDATE topic_users tu
category_id: category_id, SET notification_level = :tracking,
level: level, notifications_reason_id = :auto_track_category
reason: reason FROM topics t, category_users cu
) /*where*/
SQL
builder.where("tu.topic_id = t.id AND
cu.category_id = t.category_id AND
cu.user_id = tu.user_id AND
cu.notification_level = :tracking AND
tu.notification_level = :regular")
if category_id = opts[:category_id]
builder.where("t.category_id = :category_id", category_id: category_id)
end
if topic_id = opts[:topic_id]
builder.where("tu.topic_id = :topic_id", topic_id: topic_id)
end
if user_id = opts[:user_id]
builder.where("tu.user_id = :user_id", user_id: user_id)
end
builder.exec(tracking: notification_levels[:tracking],
regular: notification_levels[:regular],
auto_track_category: TopicUser.notification_reasons[:auto_track_category])
end end
def self.remove_default_from_topic(topic_id, category_id, level, reason) def self.auto_watch(opts={})
sql = <<-SQL
DELETE FROM topic_users builder = SqlBuilder.new <<SQL
WHERE topic_id = :topic_id UPDATE topic_users tu
AND notifications_changed_at IS NULL SET notification_level =
AND notification_level = :level CASE WHEN should_track THEN :tracking
AND notifications_reason_id = :reason WHEN should_watch THEN :watching
AND NOT EXISTS(SELECT 1 FROM category_users WHERE category_users.category_id = :category_id AND category_users.notification_level = :level AND category_users.user_id = topic_users.user_id) ELSE notification_level
SQL END,
notifications_reason_id =
CASE WHEN should_track THEN null
WHEN should_watch THEN :auto_watch_category
ELSE notifications_reason_id
END
FROM (
SELECT tu1.topic_id,
tu1.user_id,
CASE WHEN
cu.user_id IS NULL AND tu1.notification_level = :watching AND tu1.notifications_reason_id = :auto_watch_category THEN true
ELSE false
END should_track,
CASE WHEN
cu.user_id IS NOT NULL AND tu1.notification_level in (:regular, :tracking) THEN true
ELSE false
END should_watch
FROM topic_users tu1
JOIN topics t ON t.id = tu1.topic_id
LEFT JOIN category_users cu ON cu.category_id = t.category_id AND cu.user_id = tu1.user_id AND cu.notification_level = :watching
/*where2*/
) as X
/*where*/
SQL
builder.where("X.topic_id = tu.topic_id AND X.user_id = tu.user_id")
builder.where("should_watch OR should_track")
if category_id = opts[:category_id]
builder.where2("t.category_id = :category_id", category_id: category_id)
end
if topic_id = opts[:topic_id]
builder.where("tu.topic_id = :topic_id", topic_id: topic_id)
builder.where2("tu1.topic_id = :topic_id", topic_id: topic_id)
end
if user_id = opts[:user_id]
builder.where("tu.user_id = :user_id", user_id: user_id)
builder.where2("tu1.user_id = :user_id", user_id: user_id)
end
builder.exec(watching: notification_levels[:watching],
tracking: notification_levels[:tracking],
regular: notification_levels[:regular],
auto_watch_category: TopicUser.notification_reasons[:auto_watch_category])
exec_sql(sql,
topic_id: topic_id,
category_id: category_id,
level: level,
reason: reason
)
end end
def self.ensure_consistency! def self.ensure_consistency!
exec_sql <<SQL exec_sql <<SQL
DELETE FROM category_users DELETE FROM category_users
@ -110,7 +166,6 @@ class CategoryUser < ActiveRecord::Base
SQL SQL
end end
private_class_method :apply_default_to_topic, :remove_default_from_topic
end end
# == Schema Information # == Schema Information

View file

@ -8,6 +8,38 @@ class TagUser < ActiveRecord::Base
NotificationLevels.all NotificationLevels.all
end end
def self.lookup(user, level)
where(user: user, notification_level: notification_levels[level])
end
def self.batch_set(user, level, tags)
tags ||= []
changed = false
records = TagUser.where(user: user, notification_level: notification_levels[level])
old_ids = records.pluck(:tag_id)
tag_ids = tags.empty? ? [] : Tag.where('name in (?)', tags).pluck(:id)
remove = (old_ids - tag_ids)
if remove.present?
records.where('tag_id in (?)', remove).destroy_all
changed = true
end
(tag_ids - old_ids).each do |id|
TagUser.create!(user: user, tag_id: id, notification_level: notification_levels[level])
changed = true
end
if changed
auto_watch(user_id: user.id)
auto_track(user_id: user.id)
end
changed
end
def self.change(user_id, tag_id, level) def self.change(user_id, tag_id, level)
tag_id = tag_id.id if tag_id.is_a?(::Tag) tag_id = tag_id.id if tag_id.is_a?(::Tag)
user_id = user_id.id if user_id.is_a?(::User) user_id = user_id.id if user_id.is_a?(::User)
@ -25,75 +57,100 @@ class TagUser < ActiveRecord::Base
tag_user = TagUser.create(user_id: user_id, tag_id: tag_id, notification_level: level) tag_user = TagUser.create(user_id: user_id, tag_id: tag_id, notification_level: level)
end end
auto_watch(user_id: user_id)
auto_track(user_id: user_id)
tag_user tag_user
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
# In case of a race condition to insert, do nothing # In case of a race condition to insert, do nothing
end end
%w{watch track}.each do |s| def self.auto_watch(opts)
define_singleton_method("auto_#{s}_new_topic") do |topic, new_tags=nil| builder = SqlBuilder.new <<SQL
tag_ids = topic.tags.pluck(:id)
if !new_tags.nil? && topic.created_at && topic.created_at > 5.days.ago
tag_ids = new_tags.map(&:id)
remove_default_from_topic( topic.id, tag_ids,
TopicUser.notification_levels[:"#{s}ing"],
TopicUser.notification_reasons[:"auto_#{s}_tag"] )
end
apply_default_to_topic( topic.id, tag_ids, UPDATE topic_users
TopicUser.notification_levels[:"#{s}ing"], SET notification_level = CASE WHEN should_watch THEN :watching ELSE :tracking END,
TopicUser.notification_reasons[:"auto_#{s}_tag"]) notifications_reason_id = CASE WHEN should_watch THEN :auto_watch_tag ELSE NULL END
end FROM
end (
SELECT tu.topic_id, tu.user_id, CASE
WHEN MAX(tag_users.notification_level) = :watching THEN true
ELSE false
END
should_watch,
def self.apply_default_to_topic(topic_id, tag_ids, level, reason) CASE WHEN MAX(tag_users.notification_level) IS NULL AND
sql = <<-SQL tu.notification_level = :watching AND
INSERT INTO topic_users(user_id, topic_id, notification_level, notifications_reason_id) tu.notifications_reason_id = :auto_watch_tag
SELECT user_id, :topic_id, :level, :reason THEN true
FROM tag_users ELSE false
WHERE notification_level = :level END
AND tag_id in (:tag_ids) should_track
AND NOT EXISTS(SELECT 1 FROM topic_users WHERE topic_id = :topic_id AND user_id = tag_users.user_id)
LIMIT 1
SQL
exec_sql(sql, FROM topic_users tu
topic_id: topic_id, LEFT JOIN topic_tags ON tu.topic_id = topic_tags.topic_id
tag_ids: tag_ids, LEFT JOIN tag_users ON tag_users.user_id = tu.user_id
level: level, AND topic_tags.tag_id = tag_users.tag_id
reason: reason AND tag_users.notification_level = :watching
) /*where*/
end GROUP BY tu.topic_id, tu.user_id, tu.notification_level, tu.notifications_reason_id
) AS X
WHERE X.topic_id = topic_users.topic_id AND
X.user_id = topic_users.user_id AND
(should_track OR should_watch)
def self.remove_default_from_topic(topic_id, tag_ids, level, reason) SQL
sql = <<-SQL
DELETE FROM topic_users
WHERE topic_id = :topic_id
AND notifications_changed_at IS NULL
AND notification_level = :level
AND notifications_reason_id = :reason
SQL
if !tag_ids.empty? builder.where("tu.notification_level in (:tracking, :regular, :watching)")
sql << <<-SQL
AND NOT EXISTS( if topic_id = opts[:topic_id]
SELECT 1 builder.where("tu.topic_id = :topic_id", topic_id: topic_id)
FROM tag_users
WHERE tag_users.tag_id in (:tag_ids)
AND tag_users.notification_level = :level
AND tag_users.user_id = topic_users.user_id)
SQL
end end
exec_sql(sql, if user_id = opts[:user_id]
topic_id: topic_id, builder.where("tu.user_id = :user_id", user_id: user_id)
level: level, end
reason: reason,
tag_ids: tag_ids builder.exec(watching: notification_levels[:watching],
) tracking: notification_levels[:tracking],
regular: notification_levels[:regular],
auto_watch_tag: TopicUser.notification_reasons[:auto_watch_tag])
end
def self.auto_track(opts)
builder = SqlBuilder.new <<SQL
UPDATE topic_users
SET notification_level = :tracking, notifications_reason_id = :auto_track_tag
FROM (
SELECT DISTINCT tu.topic_id, tu.user_id
FROM topic_users tu
JOIN topic_tags ON tu.topic_id = topic_tags.topic_id
JOIN tag_users ON tag_users.user_id = tu.user_id
AND topic_tags.tag_id = tag_users.tag_id
AND tag_users.notification_level = :tracking
/*where*/
) as X
WHERE
topic_users.notification_level = :regular AND
topic_users.topic_id = X.topic_id AND
topic_users.user_id = X.user_id
SQL
if topic_id = opts[:topic_id]
builder.where("tu.topic_id = :topic_id", topic_id: topic_id)
end
if user_id = opts[:user_id]
builder.where("tu.user_id = :user_id", user_id: user_id)
end
builder.exec(tracking: notification_levels[:tracking],
regular: notification_levels[:regular],
auto_track_tag: TopicUser.notification_reasons[:auto_track_tag])
end end
private_class_method :apply_default_to_topic, :remove_default_from_topic
end end
# == Schema Information # == Schema Information

View file

@ -25,7 +25,7 @@ class Topic < ActiveRecord::Base
def_delegator :notifier, :mute!, :notify_muted! def_delegator :notifier, :mute!, :notify_muted!
def_delegator :notifier, :toggle_mute, :toggle_mute def_delegator :notifier, :toggle_mute, :toggle_mute
attr_accessor :allowed_user_ids attr_accessor :allowed_user_ids, :tags_changed
def self.max_sort_order def self.max_sort_order
@max_sort_order ||= (2 ** 31) - 1 @max_sort_order ||= (2 ** 31) - 1
@ -187,6 +187,12 @@ class Topic < ActiveRecord::Base
if archetype_was == banner || archetype == banner if archetype_was == banner || archetype == banner
ApplicationController.banner_json_cache.clear ApplicationController.banner_json_cache.clear
end end
if tags_changed
TagUser.auto_watch(topic_id: id)
TagUser.auto_track(topic_id: id)
self.tags_changed = false
end
end end
def initialize_default_values def initialize_default_values
@ -518,13 +524,16 @@ class Topic < ActiveRecord::Base
self.category_id = new_category.id self.category_id = new_category.id
self.update_column(:category_id, new_category.id) self.update_column(:category_id, new_category.id)
Category.where(id: old_category.id).update_all("topic_count = topic_count - 1") if old_category Category.where(id: old_category.id).update_all("topic_count = topic_count - 1") if old_category
# when a topic changes category we may have to start watching it
# if we happen to have read state for it
CategoryUser.auto_watch(category_id: new_category.id, topic_id: self.id)
CategoryUser.auto_track(category_id: new_category.id, topic_id: self.id)
end end
Category.where(id: new_category.id).update_all("topic_count = topic_count + 1") Category.where(id: new_category.id).update_all("topic_count = topic_count + 1")
CategoryFeaturedTopic.feature_topics_for(old_category) unless @import_mode CategoryFeaturedTopic.feature_topics_for(old_category) unless @import_mode
CategoryFeaturedTopic.feature_topics_for(new_category) unless @import_mode || old_category.id == new_category.id CategoryFeaturedTopic.feature_topics_for(new_category) unless @import_mode || old_category.id == new_category.id
CategoryUser.auto_watch_new_topic(self, new_category)
CategoryUser.auto_track_new_topic(self, new_category)
end end
true true

View file

@ -131,15 +131,7 @@ SQL
rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all([attrs_sql, *vals]) rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all([attrs_sql, *vals])
if rows == 0 if rows == 0
now = DateTime.now create_missing_record(user_id, topic_id, attrs)
auto_track_after = UserOption.where(user_id: user_id).pluck(:auto_track_topics_after_msecs).first
auto_track_after ||= SiteSetting.default_other_auto_track_topics_after_msecs
if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed].to_i || 0)
attrs[:notification_level] ||= notification_levels[:tracking]
end
TopicUser.create(attrs.merge!(user_id: user_id, topic_id: topic_id, first_visited_at: now ,last_visited_at: now))
else else
observe_after_save_callbacks_for topic_id, user_id observe_after_save_callbacks_for topic_id, user_id
end end
@ -153,12 +145,64 @@ SQL
# In case of a race condition to insert, do nothing # In case of a race condition to insert, do nothing
end end
def create_missing_record(user_id, topic_id, attrs)
now = DateTime.now
unless attrs[:notification_level]
category_notification_level = CategoryUser.where(user_id: user_id)
.where("category_id IN (SELECT category_id FROM topics WHERE id = :id)", id: topic_id)
.where("notification_level IN (:levels)", levels: [CategoryUser.notification_levels[:watching],
CategoryUser.notification_levels[:tracking]])
.order("notification_level DESC")
.limit(1)
.pluck(:notification_level)
.first
tag_notification_level = TagUser.where(user_id: user_id)
.where("tag_id IN (SELECT tag_id FROM topic_tags WHERE topic_id = :id)", id: topic_id)
.where("notification_level IN (:levels)", levels: [CategoryUser.notification_levels[:watching],
CategoryUser.notification_levels[:tracking]])
.order("notification_level DESC")
.limit(1)
.pluck(:notification_level)
.first
if category_notification_level && !(tag_notification_level && (tag_notification_level > category_notification_level))
attrs[:notification_level] = category_notification_level
attrs[:notifications_changed_at] = DateTime.now
attrs[:notifications_reason_id] = category_notification_level == CategoryUser.notification_levels[:watching] ?
TopicUser.notification_reasons[:auto_watch_category] :
TopicUser.notification_reasons[:auto_track_category]
elsif tag_notification_level
attrs[:notification_level] = tag_notification_level
attrs[:notifications_changed_at] = DateTime.now
attrs[:notifications_reason_id] = tag_notification_level == TagUser.notification_levels[:watching] ?
TopicUser.notification_reasons[:auto_watch_tag] :
TopicUser.notification_reasons[:auto_track_tag]
end
end
unless attrs[:notification_level]
auto_track_after = UserOption.where(user_id: user_id).pluck(:auto_track_topics_after_msecs).first
auto_track_after ||= SiteSetting.default_other_auto_track_topics_after_msecs
if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed].to_i || 0)
attrs[:notification_level] ||= notification_levels[:tracking]
end
end
TopicUser.create(attrs.merge!(user_id: user_id, topic_id: topic_id, first_visited_at: now ,last_visited_at: now))
end
def track_visit!(topic_id, user_id) def track_visit!(topic_id, user_id)
now = DateTime.now now = DateTime.now
rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all(last_visited_at: now) rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all(last_visited_at: now)
if rows == 0 if rows == 0
TopicUser.create(topic_id: topic_id, user_id: user_id, last_visited_at: now, first_visited_at: now) change(user_id, topic_id, last_visited_at: now, first_visited_at: now)
else else
observe_after_save_callbacks_for(topic_id, user_id) observe_after_save_callbacks_for(topic_id, user_id)
end end

View file

@ -83,6 +83,9 @@ class UserSerializer < BasicUserSerializer
private_attributes :locale, private_attributes :locale,
:muted_category_ids, :muted_category_ids,
:watched_tags,
:tracked_tags,
:muted_tags,
:tracked_category_ids, :tracked_category_ids,
:watched_category_ids, :watched_category_ids,
:private_messages_stats, :private_messages_stats,
@ -246,6 +249,17 @@ class UserSerializer < BasicUserSerializer
### ###
### PRIVATE ATTRIBUTES ### PRIVATE ATTRIBUTES
### ###
def muted_tags
TagUser.lookup(object, :muted).joins(:tag).pluck('tags.name')
end
def tracked_tags
TagUser.lookup(object, :tracking).joins(:tag).pluck('tags.name')
end
def watched_tags
TagUser.lookup(object, :watching).joins(:tag).pluck('tags.name')
end
def muted_category_ids def muted_category_ids
CategoryUser.lookup(object, :muted).pluck(:category_id) CategoryUser.lookup(object, :muted).pluck(:category_id)

View file

@ -452,14 +452,47 @@ class PostAlerter
end end
def notify_post_users(post, notified) def notify_post_users(post, notified)
notify = TopicUser.where(topic_id: post.topic_id) return unless post.topic
.where(notification_level: TopicUser.notification_levels[:watching])
condition = <<SQL
id IN (
SELECT user_id FROM topic_users
WHERE notification_level = :watching AND topic_id = :topic_id
UNION ALL
SELECT cu.user_id FROM category_users cu
LEFT JOIN topic_users tu ON tu.user_id = cu.user_id AND tu.topic_id = :topic_id
WHERE cu.notification_level = :watching AND cu.category_id = :category_id AND tu.user_id IS NULL
/*tags*/
)
SQL
tag_ids = post.topic.topic_tags.pluck('topic_tags.tag_id')
if tag_ids.present?
condition.sub! "/*tags*/", <<SQL
UNION ALL
SELECT tag_users.user_id FROM tag_users
LEFT JOIN topic_users tu ON tu.user_id = tag_users.user_id AND tu.topic_id = :topic_id
WHERE tag_users.notification_level = :watching AND tag_users.tag_id IN (:tag_ids) AND tu.user_id IS NULL
SQL
end
notify = User.where(condition,
watching: TopicUser.notification_levels[:watching],
topic_id: post.topic_id,
category_id: post.topic.category_id,
tag_ids: tag_ids
)
exclude_user_ids = notified.map(&:id) exclude_user_ids = notified.map(&:id)
notify = notify.where("user_id NOT IN (?)", exclude_user_ids) if exclude_user_ids.present? notify = notify.where("id NOT IN (?)", exclude_user_ids) if exclude_user_ids.present?
notify.includes(:user).each do |tu| notify.each do |user|
create_notification(tu.user, Notification.types[:posted], post) create_notification(user, Notification.types[:posted], post)
end end
end end

View file

@ -6,6 +6,12 @@ class UserUpdater
muted_category_ids: :muted muted_category_ids: :muted
} }
TAG_NAMES = {
watched_tags: :watching,
tracked_tags: :tracking,
muted_tags: :muted
}
OPTION_ATTR = [ OPTION_ATTR = [
:email_always, :email_always,
:mailing_list_mode, :mailing_list_mode,
@ -55,6 +61,10 @@ class UserUpdater
end end
end end
TAG_NAMES.each do |attribute, level|
TagUser.batch_set(user, level, attributes[attribute])
end
save_options = false save_options = false

View file

@ -539,8 +539,15 @@ en:
individual: "Send an email for every new post" individual: "Send an email for every new post"
many_per_day: "Send me an email for every new post (about {{dailyEmailEstimate}} per day)" many_per_day: "Send me an email for every new post (about {{dailyEmailEstimate}} per day)"
few_per_day: "Send me an email for every new post (about 2 per day)" few_per_day: "Send me an email for every new post (about 2 per day)"
tag_settings: "Tags"
watched_tags: "Watched"
watched_tags_instructions: "You will automatically watch all topics with these tags. You will be notified of all new posts and topics, and a count of new posts will also appear next to the topic."
tracked_tags: "Tracked"
tracked_tags_instructions: "You will automatically track all new topics with these tags. A count of new posts will appear next to the topic."
muted_tags: "Muted"
muted_tags_instructions: "You will not be notified of anything about new topics with these tags, and they will not appear in latest."
watched_categories: "Watched" watched_categories: "Watched"
watched_categories_instructions: "You will automatically watch all new topics in these categories. You will be notified of all new posts and topics, and a count of new posts will also appear next to the topic." watched_categories_instructions: "You will automatically watch all topics in these categories. You will be notified of all new posts and topics, and a count of new posts will also appear next to the topic."
tracked_categories: "Tracked" tracked_categories: "Tracked"
tracked_categories_instructions: "You will automatically track all new topics in these categories. A count of new posts will appear next to the topic." tracked_categories_instructions: "You will automatically track all new topics in these categories. A count of new posts will appear next to the topic."
muted_categories: "Muted" muted_categories: "Muted"
@ -577,13 +584,6 @@ en:
failed_to_move: "Failed to move selected messages (perhaps your network is down)" failed_to_move: "Failed to move selected messages (perhaps your network is down)"
select_all: "Select All" select_all: "Select All"
warn_unwatch:
message: "Also stop watching previously watched topics in {{categories}}?"
yes_value: "Yes, stop watching topics"
no_value:
one: "No, only stop watching category"
other: "No, only stop watching categories"
change_password: change_password:
success: "(email sent)" success: "(email sent)"
in_progress: "(sending email)" in_progress: "(sending email)"
@ -1349,6 +1349,7 @@ en:
title: change how often you get notified about this topic title: change how often you get notified about this topic
reasons: reasons:
mailing_list_mode: "You have mailing list mode enabled, so you will be notified of replies to this topic via email." mailing_list_mode: "You have mailing list mode enabled, so you will be notified of replies to this topic via email."
"3_10": 'You will receive notifications because you are watching this a tag on this topic.'
"3_6": 'You will receive notifications because you are watching this category.' "3_6": 'You will receive notifications because you are watching this category.'
"3_5": 'You will receive notifications because you started watching this topic automatically.' "3_5": 'You will receive notifications because you started watching this topic automatically.'
"3_2": 'You will receive notifications because you are watching this topic.' "3_2": 'You will receive notifications because you are watching this topic.'

View file

@ -3,10 +3,6 @@ module DiscourseTagging
TAGS_FIELD_NAME = "tags" TAGS_FIELD_NAME = "tags"
TAGS_FILTER_REGEXP = /[<\\\/\>\#\?\&\s]/ TAGS_FILTER_REGEXP = /[<\\\/\>\#\?\&\s]/
# class Engine < ::Rails::Engine
# engine_name "discourse_tagging"
# isolate_namespace DiscourseTagging
# end
def self.tag_topic_by_names(topic, guardian, tag_names_arg) def self.tag_topic_by_names(topic, guardian, tag_names_arg)
if SiteSetting.tagging_enabled if SiteSetting.tagging_enabled
@ -43,13 +39,11 @@ module DiscourseTagging
end end
end end
auto_notify_for(tags, topic)
topic.tags = tags topic.tags = tags
else else
auto_notify_for([], topic)
topic.tags = [] topic.tags = []
end end
topic.tags_changed=true
end end
true true
end end
@ -146,11 +140,6 @@ module DiscourseTagging
query query
end end
def self.auto_notify_for(tags, topic)
TagUser.auto_watch_new_topic(topic, tags)
TagUser.auto_track_new_topic(topic, tags)
end
def self.clean_tag(tag) def self.clean_tag(tag)
tag.downcase.strip[0...SiteSetting.max_tag_length].gsub(TAGS_FILTER_REGEXP, '') tag.downcase.strip[0...SiteSetting.max_tag_length].gsub(TAGS_FILTER_REGEXP, '')
end end

View file

@ -90,14 +90,6 @@ class TopicCreator
topic.notifier.send(action, gu.user_id) topic.notifier.send(action, gu.user_id)
end end
end end
unless topic.private_message?
# In order of importance:
CategoryUser.auto_watch_new_topic(topic)
CategoryUser.auto_track_new_topic(topic)
TagUser.auto_watch_new_topic(topic)
TagUser.auto_track_new_topic(topic)
end
end end
def setup_topic_params def setup_topic_params

View file

@ -5,6 +5,14 @@ require_dependency 'post_creator'
describe CategoryUser do describe CategoryUser do
def tracking
CategoryUser.notification_levels[:tracking]
end
def regular
CategoryUser.notification_levels[:regular]
end
it 'allows batch set' do it 'allows batch set' do
user = Fabricate(:user) user = Fabricate(:user)
category1 = Fabricate(:category) category1 = Fabricate(:category)
@ -22,6 +30,21 @@ describe CategoryUser do
expect(watching.count).to eq 1 expect(watching.count).to eq 1
end end
it 'should correctly auto_track' do
tracking_user = Fabricate(:user)
user = Fabricate(:user)
topic = Fabricate(:post).topic
TopicUser.change(user.id, topic.id, total_msecs_viewed: 10)
TopicUser.change(tracking_user.id, topic.id, total_msecs_viewed: 10)
CategoryUser.create!(user: tracking_user, category: topic.category, notification_level: tracking)
CategoryUser.auto_track(user_id: tracking_user.id)
expect(TopicUser.get(topic, tracking_user).notification_level).to eq(tracking)
expect(TopicUser.get(topic, user).notification_level).to eq(regular)
end
context 'integration' do context 'integration' do
before do before do
@ -35,6 +58,8 @@ describe CategoryUser do
user = Fabricate(:user) user = Fabricate(:user)
early_watched_post = create_post(category: watched_category)
CategoryUser.create!(user: user, category: watched_category, notification_level: CategoryUser.notification_levels[:watching]) CategoryUser.create!(user: user, category: watched_category, notification_level: CategoryUser.notification_levels[:watching])
CategoryUser.create!(user: user, category: muted_category, notification_level: CategoryUser.notification_levels[:muted]) CategoryUser.create!(user: user, category: muted_category, notification_level: CategoryUser.notification_levels[:muted])
CategoryUser.create!(user: user, category: tracked_category, notification_level: CategoryUser.notification_levels[:tracking]) CategoryUser.create!(user: user, category: tracked_category, notification_level: CategoryUser.notification_levels[:tracking])
@ -43,28 +68,36 @@ describe CategoryUser do
_muted_post = create_post(category: muted_category) _muted_post = create_post(category: muted_category)
tracked_post = create_post(category: tracked_category) tracked_post = create_post(category: tracked_category)
create_post(topic_id: early_watched_post.topic_id)
expect(Notification.where(user_id: user.id, topic_id: watched_post.topic_id).count).to eq 1 expect(Notification.where(user_id: user.id, topic_id: watched_post.topic_id).count).to eq 1
expect(Notification.where(user_id: user.id, topic_id: early_watched_post.topic_id).count).to eq 1
expect(Notification.where(user_id: user.id, topic_id: tracked_post.topic_id).count).to eq 0 expect(Notification.where(user_id: user.id, topic_id: tracked_post.topic_id).count).to eq 0
# we must create a record so tracked flicks over
TopicUser.change(user.id, tracked_post.topic_id, total_msecs_viewed: 10)
tu = TopicUser.get(tracked_post.topic, user) tu = TopicUser.get(tracked_post.topic, user)
expect(tu.notification_level).to eq TopicUser.notification_levels[:tracking] expect(tu.notification_level).to eq TopicUser.notification_levels[:tracking]
expect(tu.notifications_reason_id).to eq TopicUser.notification_reasons[:auto_track_category] expect(tu.notifications_reason_id).to eq TopicUser.notification_reasons[:auto_track_category]
end end
it "watches categories that have been changed" do it "topics that move to a tracked category should auto track" do
user = Fabricate(:user) user = Fabricate(:user)
watched_category = Fabricate(:category)
CategoryUser.create!(user: user, category: watched_category, notification_level: CategoryUser.notification_levels[:watching])
post = create_post first_post = create_post
expect(TopicUser.get(post.topic, user)).to be_blank tracked_category = first_post.topic.category
# Now, change the topic's category TopicUser.change(user.id, first_post.topic_id, total_msecs_viewed: 10)
post.topic.change_category_to_id(watched_category.id) tu = TopicUser.get(first_post.topic, user)
tu = TopicUser.get(post.topic, user) expect(tu.notification_level).to eq TopicUser.notification_levels[:regular]
expect(tu.notification_level).to eq TopicUser.notification_levels[:watching]
CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[:tracking], tracked_category.id)
tu = TopicUser.get(first_post.topic, user)
expect(tu.notification_level).to eq TopicUser.notification_levels[:tracking]
end end
it "unwatches categories that have been changed" do it "unwatches categories that have been changed" do
user = Fabricate(:user) user = Fabricate(:user)
watched_category = Fabricate(:category) watched_category = Fabricate(:category)
@ -72,12 +105,15 @@ describe CategoryUser do
post = create_post(category: watched_category) post = create_post(category: watched_category)
tu = TopicUser.get(post.topic, user) tu = TopicUser.get(post.topic, user)
# we start watching cause a notification is sent to the watching user
# this position sent is tracking in topic users
expect(tu.notification_level).to eq TopicUser.notification_levels[:watching] expect(tu.notification_level).to eq TopicUser.notification_levels[:watching]
# Now, change the topic's category # Now, change the topic's category
unwatched_category = Fabricate(:category) unwatched_category = Fabricate(:category)
post.topic.change_category_to_id(unwatched_category.id) post.topic.change_category_to_id(unwatched_category.id)
expect(TopicUser.get(post.topic, user)).to be_blank expect(TopicUser.get(post.topic, user).notification_level).to eq TopicUser.notification_levels[:tracking]
end end
it "does not delete TopicUser record when topic category is changed, and new category has same notification level" do it "does not delete TopicUser record when topic category is changed, and new category has same notification level" do
@ -87,16 +123,26 @@ describe CategoryUser do
user = Fabricate(:user) user = Fabricate(:user)
watched_category_1 = Fabricate(:category) watched_category_1 = Fabricate(:category)
watched_category_2 = Fabricate(:category) watched_category_2 = Fabricate(:category)
category_3 = Fabricate(:category)
post = create_post(category: watched_category_1)
CategoryUser.create!(user: user, category: watched_category_1, notification_level: CategoryUser.notification_levels[:watching]) CategoryUser.create!(user: user, category: watched_category_1, notification_level: CategoryUser.notification_levels[:watching])
CategoryUser.create!(user: user, category: watched_category_2, notification_level: CategoryUser.notification_levels[:watching]) CategoryUser.create!(user: user, category: watched_category_2, notification_level: CategoryUser.notification_levels[:watching])
post = create_post(category: watched_category_1) # we must have a topic user record otherwise it will be watched implicitly
tu = TopicUser.get(post.topic, user) TopicUser.change(user.id, post.topic_id, total_msecs_viewed: 10)
expect(tu.notification_level).to eq TopicUser.notification_levels[:watching]
expect(TopicUser.get(post.topic, user).notification_level).to eq TopicUser.notification_levels[:watching]
post.topic.change_category_to_id(category_3.id)
expect(TopicUser.get(post.topic, user).notification_level).to eq TopicUser.notification_levels[:tracking]
# Now, change the topic's category
post.topic.change_category_to_id(watched_category_2.id) post.topic.change_category_to_id(watched_category_2.id)
expect(TopicUser.get(post.topic, user)).to eq tu expect(TopicUser.get(post.topic, user).notification_level).to eq TopicUser.notification_levels[:watching]
post.topic.change_category_to_id(watched_category_1.id)
expect(TopicUser.get(post.topic, user).notification_level).to eq TopicUser.notification_levels[:watching]
end end
it "deletes TopicUser record when topic category is changed, and new category has different notification level" do it "deletes TopicUser record when topic category is changed, and new category has different notification level" do

View file

@ -4,13 +4,72 @@ require 'rails_helper'
require_dependency 'post_creator' require_dependency 'post_creator'
describe TagUser do describe TagUser do
before do
SiteSetting.tagging_enabled = true
SiteSetting.min_trust_to_create_tag = 0
SiteSetting.min_trust_level_to_tag_topics = 0
end
def regular
TagUser.notification_levels[:regular]
end
def tracking
TagUser.notification_levels[:tracking]
end
def watching
TagUser.notification_levels[:watching]
end
context "change" do
it "watches or tracks on change" do
user = Fabricate(:user)
tag = Fabricate(:tag)
post = create_post(tags: [tag.name])
topic = post.topic
TopicUser.change(user.id, topic.id, total_msecs_viewed: 1)
TagUser.change(user.id, tag.id, tracking)
expect(TopicUser.get(topic, user).notification_level).to eq tracking
TagUser.change(user.id, tag.id, watching)
expect(TopicUser.get(topic, user).notification_level).to eq watching
TagUser.change(user.id, tag.id, regular)
expect(TopicUser.get(topic, user).notification_level).to eq tracking
end
end
context "batch_set" do
it "watches and unwatches tags correctly" do
user = Fabricate(:user)
tag = Fabricate(:tag)
post = create_post(tags: [tag.name])
topic = post.topic
# we need topic user record to ensure watch picks up other wise it is implicit
TopicUser.change(user.id, topic.id, total_msecs_viewed: 1)
TagUser.batch_set(user, :tracking, [tag.name])
expect(TopicUser.get(topic, user).notification_level).to eq tracking
TagUser.batch_set(user, :watching, [tag.name])
expect(TopicUser.get(topic, user).notification_level).to eq watching
TagUser.batch_set(user, :watching, [])
expect(TopicUser.get(topic, user).notification_level).to eq tracking
end
end
context "integration" do context "integration" do
before do before do
ActiveRecord::Base.observers.enable :all ActiveRecord::Base.observers.enable :all
SiteSetting.tagging_enabled = true
SiteSetting.min_trust_to_create_tag = 0
SiteSetting.min_trust_level_to_tag_topics = 0
end end
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
@ -20,28 +79,43 @@ describe TagUser do
let(:tracked_tag) { Fabricate(:tag) } let(:tracked_tag) { Fabricate(:tag) }
context "with some tag notification settings" do context "with some tag notification settings" do
before do
let :watched_post do
TagUser.create!(user: user, tag: watched_tag, notification_level: TagUser.notification_levels[:watching]) TagUser.create!(user: user, tag: watched_tag, notification_level: TagUser.notification_levels[:watching])
create_post(tags: [watched_tag.name])
end
let :muted_post do
TagUser.create!(user: user, tag: muted_tag, notification_level: TagUser.notification_levels[:muted]) TagUser.create!(user: user, tag: muted_tag, notification_level: TagUser.notification_levels[:muted])
create_post(tags: [muted_tag.name])
end
let :tracked_post do
TagUser.create!(user: user, tag: tracked_tag, notification_level: TagUser.notification_levels[:tracking]) TagUser.create!(user: user, tag: tracked_tag, notification_level: TagUser.notification_levels[:tracking])
create_post(tags: [tracked_tag.name])
end end
it "sets notification levels correctly" do it "sets notification levels correctly" do
watched_post = create_post(tags: [watched_tag.name])
muted_post = create_post(tags: [muted_tag.name])
tracked_post = create_post(tags: [tracked_tag.name])
expect(Notification.where(user_id: user.id, topic_id: watched_post.topic_id).count).to eq 1 expect(Notification.where(user_id: user.id, topic_id: watched_post.topic_id).count).to eq 1
expect(Notification.where(user_id: user.id, topic_id: tracked_post.topic_id).count).to eq 0 expect(Notification.where(user_id: user.id, topic_id: tracked_post.topic_id).count).to eq 0
TopicUser.change(user.id, tracked_post.topic.id, total_msecs_viewed: 1)
tu = TopicUser.get(tracked_post.topic, user) tu = TopicUser.get(tracked_post.topic, user)
expect(tu.notification_level).to eq TopicUser.notification_levels[:tracking] expect(tu.notification_level).to eq TopicUser.notification_levels[:tracking]
expect(tu.notifications_reason_id).to eq TopicUser.notification_reasons[:auto_track_tag] expect(tu.notifications_reason_id).to eq TopicUser.notification_reasons[:auto_track_tag]
end end
it "sets notification level to the highest one if there are multiple tags" do it "sets notification level to the highest one if there are multiple tags" do
TagUser.create!(user: user, tag: tracked_tag, notification_level: TagUser.notification_levels[:tracking])
TagUser.create!(user: user, tag: muted_tag, notification_level: TagUser.notification_levels[:muted])
TagUser.create!(user: user, tag: watched_tag, notification_level: TagUser.notification_levels[:watching])
post = create_post(tags: [muted_tag.name, tracked_tag.name, watched_tag.name]) post = create_post(tags: [muted_tag.name, tracked_tag.name, watched_tag.name])
expect(Notification.where(user_id: user.id, topic_id: post.topic_id).count).to eq 1 expect(Notification.where(user_id: user.id, topic_id: post.topic_id).count).to eq 1
TopicUser.change(user.id, post.topic.id, total_msecs_viewed: 1)
tu = TopicUser.get(post.topic, user) tu = TopicUser.get(post.topic, user)
expect(tu.notification_level).to eq TopicUser.notification_levels[:watching] expect(tu.notification_level).to eq TopicUser.notification_levels[:watching]
expect(tu.notifications_reason_id).to eq TopicUser.notification_reasons[:auto_watch_tag] expect(tu.notifications_reason_id).to eq TopicUser.notification_reasons[:auto_watch_tag]
@ -49,36 +123,43 @@ describe TagUser do
it "can start watching after tag has been added" do it "can start watching after tag has been added" do
post = create_post post = create_post
expect(TopicUser.get(post.topic, user)).to be_blank
DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(user), [watched_tag.name])
tu = TopicUser.get(post.topic, user)
expect(tu.notification_level).to eq(TopicUser.notification_levels[:watching])
end
it "can start watching after tag has changed" do # this is assuming post was already visited in the past, but now cause tag
post = create_post(tags: [Fabricate(:tag).name]) # was added we should start watching it
expect(TopicUser.get(post.topic, user)).to be_blank TopicUser.change(user.id, post.topic.id, total_msecs_viewed: 1)
TagUser.create!(user: user, tag: watched_tag, notification_level: TagUser.notification_levels[:watching])
DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(user), [watched_tag.name]) DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(user), [watched_tag.name])
post.topic.save!
tu = TopicUser.get(post.topic, user) tu = TopicUser.get(post.topic, user)
expect(tu.notification_level).to eq(TopicUser.notification_levels[:watching]) expect(tu.notification_level).to eq(TopicUser.notification_levels[:watching])
end end
it "can stop watching after tag has changed" do it "can stop watching after tag has changed" do
post = create_post(tags: [watched_tag.name]) watched_tag2 = Fabricate(:tag)
expect(TopicUser.get(post.topic, user)).to be_present TagUser.create!(user: user, tag: watched_tag, notification_level: TagUser.notification_levels[:watching])
DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(user), [Fabricate(:tag).name]) TagUser.create!(user: user, tag: watched_tag2, notification_level: TagUser.notification_levels[:watching])
expect(TopicUser.get(post.topic, user)).to be_blank
end post = create_post(tags: [watched_tag.name, watched_tag2.name])
TopicUser.change(user.id, post.topic_id, total_msecs_viewed: 1)
expect(TopicUser.get(post.topic, user).notification_level).to eq TopicUser.notification_levels[:watching]
DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(user), [watched_tag.name])
post.topic.save!
expect(TopicUser.get(post.topic, user).notification_level).to eq TopicUser.notification_levels[:watching]
it "can stop watching after tags have been removed" do
post = create_post(tags: [muted_tag.name, tracked_tag.name, watched_tag.name])
expect(TopicUser.get(post.topic, user)).to be_present
DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(user), []) DiscourseTagging.tag_topic_by_names(post.topic, Guardian.new(user), [])
expect(TopicUser.get(post.topic, user)).to be_blank post.topic.save!
expect(TopicUser.get(post.topic, user).notification_level).to eq TopicUser.notification_levels[:tracking]
end end
it "is destroyed when a user is deleted" do it "is destroyed when a user is deleted" do
expect(TagUser.where(user_id: user.id).count).to eq(3) TagUser.create!(user: user, tag: tracked_tag, notification_level: TagUser.notification_levels[:tracking])
user.destroy! user.destroy!
expect(TagUser.where(user_id: user.id).count).to eq(0) expect(TagUser.where(user_id: user.id).count).to eq(0)
end end

View file

@ -39,6 +39,28 @@ describe UserUpdater do
expect(user.reload.name).to eq 'Jim Tom' expect(user.reload.name).to eq 'Jim Tom'
end end
it 'can update categories and tags' do
category = Fabricate(:category)
tag = Fabricate(:tag)
user = Fabricate(:user)
updater = UserUpdater.new(acting_user, user)
updater.update(watched_tags: [tag.name], muted_category_ids: [category.id])
expect(TagUser.where(
user_id: user.id,
tag_id: tag.id,
notification_level: TagUser.notification_levels[:watching]
).count).to eq(1)
expect(CategoryUser.where(
user_id: user.id,
category_id: category.id,
notification_level: CategoryUser.notification_levels[:muted]
).count).to eq(1)
end
it 'updates various fields' do it 'updates various fields' do
user = Fabricate(:user) user = Fabricate(:user)
updater = UserUpdater.new(acting_user, user) updater = UserUpdater.new(acting_user, user)