mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 09:42:02 +08:00
Cleaned up TopicUserSpec, introduces clearing of pinned topics
This commit is contained in:
parent
3af2ab9022
commit
f8d8272406
2
Gemfile
2
Gemfile
|
@ -1,4 +1,4 @@
|
|||
source 'https://rubygems.org'
|
||||
source 'http://rubygems.org'
|
||||
|
||||
gem 'active_model_serializers', git: 'git://github.com/rails-api/active_model_serializers.git'
|
||||
gem 'ember-rails', git: 'git://github.com/emberjs/ember-rails.git' # so we get the pre version
|
||||
|
|
|
@ -71,7 +71,7 @@ PATH
|
|||
rails (~> 3.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
actionmailer (3.2.12)
|
||||
actionpack (= 3.2.12)
|
||||
|
|
|
@ -255,6 +255,15 @@ Discourse.TopicController = Discourse.ObjectController.extend({
|
|||
this.get('content').toggleStar();
|
||||
},
|
||||
|
||||
/**
|
||||
Clears the pin from a topic for the currentUser
|
||||
|
||||
@method clearPin
|
||||
**/
|
||||
clearPin: function() {
|
||||
this.get('content').clearPin();
|
||||
},
|
||||
|
||||
// Receive notifications for this topic
|
||||
subscribe: function() {
|
||||
var bus,
|
||||
|
|
|
@ -329,6 +329,27 @@ Discourse.Topic = Discourse.Model.extend({
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Clears the pin from a topic for the currentUser
|
||||
|
||||
@method clearPin
|
||||
**/
|
||||
clearPin: function() {
|
||||
|
||||
var topic = this;
|
||||
|
||||
// Clear the pin optimistically from the object
|
||||
topic.set('pinned', false);
|
||||
|
||||
$.ajax("/t/" + this.get('id') + "/clear-pin", {
|
||||
type: 'PUT',
|
||||
error: function() {
|
||||
// On error, put the pin back
|
||||
topic.set('pinned', true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Is the reply to a post directly below it?
|
||||
isReplyDirectlyBelow: function(post) {
|
||||
var postBelow, posts;
|
||||
|
|
|
@ -68,6 +68,30 @@ Discourse.TopicFooterButtonsView = Ember.ContainerView.extend({
|
|||
buffer.push("<i class='icon icon-share'></i>");
|
||||
}
|
||||
}));
|
||||
|
||||
// Add our clear pin button
|
||||
this.addObject(Discourse.ButtonView.createWithMixins({
|
||||
textKey: 'topic.clear_pin.title',
|
||||
helpKey: 'topic.clear_pin.help',
|
||||
classNameBindings: ['unpinned'],
|
||||
|
||||
// Hide the button if it becomes unpinned
|
||||
unpinned: function() {
|
||||
// When not logged in don't show the button
|
||||
if (!Discourse.get('currentUser')) return 'hidden'
|
||||
|
||||
return this.get('controller.pinned') ? null : 'hidden';
|
||||
}.property('controller.pinned'),
|
||||
|
||||
click: function(buffer) {
|
||||
this.get('controller').clearPin();
|
||||
},
|
||||
|
||||
renderIcon: function(buffer) {
|
||||
buffer.push("<i class='icon icon-pushpin'></i>");
|
||||
}
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
this.addObject(Discourse.ButtonView.createWithMixins({
|
||||
|
|
|
@ -14,7 +14,9 @@ class TopicsController < ApplicationController
|
|||
:mute,
|
||||
:unmute,
|
||||
:set_notifications,
|
||||
:move_posts]
|
||||
:move_posts,
|
||||
:clear_pin]
|
||||
|
||||
before_filter :consider_user_for_promotion, only: :show
|
||||
|
||||
skip_before_filter :check_xhr, only: [:avatar, :show, :feed]
|
||||
|
@ -127,16 +129,21 @@ class TopicsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def clear_pin
|
||||
topic = Topic.where(id: params[:topic_id].to_i).first
|
||||
guardian.ensure_can_see!(topic)
|
||||
topic.clear_pin_for(current_user)
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def timings
|
||||
|
||||
PostTiming.process_timings(
|
||||
current_user,
|
||||
params[:topic_id].to_i,
|
||||
params[:highest_seen].to_i,
|
||||
params[:topic_time].to_i,
|
||||
(params[:timings] || []).map{|post_number, t| [post_number.to_i, t.to_i]}
|
||||
current_user,
|
||||
params[:topic_id].to_i,
|
||||
params[:highest_seen].to_i,
|
||||
params[:topic_time].to_i,
|
||||
(params[:timings] || []).map{|post_number, t| [post_number.to_i, t.to_i]}
|
||||
)
|
||||
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class Post < ActiveRecord::Base
|
|||
after_commit :store_unique_post_key, on: :create
|
||||
|
||||
after_create do
|
||||
TopicUser.auto_track(user_id, topic_id, TopicUser::NotificationReasons::CREATED_POST)
|
||||
TopicUser.auto_track(user_id, topic_id, TopicUser.notification_reasons[:created_post])
|
||||
end
|
||||
|
||||
scope :by_newest, order('created_at desc, id desc')
|
||||
|
|
|
@ -80,7 +80,7 @@ class PostAlertObserver < ActiveRecord::Observer
|
|||
return unless Guardian.new(user).can_see?(post)
|
||||
|
||||
# skip if muted on the topic
|
||||
return if TopicUser.get(post.topic, user).try(:notification_level) == TopicUser::NotificationLevel::MUTED
|
||||
return if TopicUser.get(post.topic, user).try(:notification_level) == TopicUser.notification_levels[:muted]
|
||||
|
||||
# Don't notify the same user about the same notification on the same post
|
||||
return if user.notifications.exists?(notification_type: type, topic_id: post.topic_id, post_number: post.post_number)
|
||||
|
@ -132,7 +132,7 @@ class PostAlertObserver < ActiveRecord::Observer
|
|||
exclude_user_ids << extract_mentioned_users(post).map(&:id)
|
||||
exclude_user_ids << extract_quoted_users(post).map(&:id)
|
||||
exclude_user_ids.flatten!
|
||||
TopicUser.where(topic_id: post.topic_id, notification_level: TopicUser::NotificationLevel::WATCHING).includes(:user).each do |tu|
|
||||
TopicUser.where(topic_id: post.topic_id, notification_level: TopicUser.notification_levels[:watching]).includes(:user).each do |tu|
|
||||
create_notification(tu.user, Notification.types[:posted], post) unless exclude_user_ids.include?(tu.user_id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -83,11 +83,9 @@ class Topic < ActiveRecord::Base
|
|||
|
||||
after_create do
|
||||
changed_to_category(category)
|
||||
TopicUser.change(
|
||||
user_id, id,
|
||||
notification_level: TopicUser::NotificationLevel::WATCHING,
|
||||
notifications_reason_id: TopicUser::NotificationReasons::CREATED_TOPIC
|
||||
)
|
||||
TopicUser.change(user_id, id,
|
||||
notification_level: TopicUser.notification_levels[:watching],
|
||||
notifications_reason_id: TopicUser.notification_reasons[:created_topic])
|
||||
if archetype == Archetype.private_message
|
||||
DraftSequence.next!(user, Draft::NEW_PRIVATE_MESSAGE)
|
||||
else
|
||||
|
@ -206,8 +204,17 @@ class Topic < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def update_status(property, status, user)
|
||||
|
||||
Topic.transaction do
|
||||
update_column(property, status)
|
||||
|
||||
# Special case: if it's pinned, update that
|
||||
if property.to_sym == :pinned
|
||||
update_pinned(status)
|
||||
else
|
||||
# otherwise update the column
|
||||
update_column(property, status)
|
||||
end
|
||||
|
||||
key = "topic_statuses.#{property}_"
|
||||
key << (status ? 'enabled' : 'disabled')
|
||||
|
||||
|
@ -506,7 +513,7 @@ class Topic < ActiveRecord::Base
|
|||
|
||||
# Enable/disable the mute on the topic
|
||||
def toggle_mute(user, muted)
|
||||
TopicUser.change(user, self.id, notification_level: muted?(user) ? TopicUser::NotificationLevel::REGULAR : TopicUser::NotificationLevel::MUTED )
|
||||
TopicUser.change(user, self.id, notification_level: muted?(user) ? TopicUser.notification_levels[:regular] : TopicUser.notification_levels[:muted] )
|
||||
end
|
||||
|
||||
def slug
|
||||
|
@ -526,7 +533,17 @@ class Topic < ActiveRecord::Base
|
|||
def muted?(user)
|
||||
return false unless user && user.id
|
||||
tu = topic_users.where(user_id: user.id).first
|
||||
tu && tu.notification_level == TopicUser::NotificationLevel::MUTED
|
||||
tu && tu.notification_level == TopicUser.notification_levels[:muted]
|
||||
end
|
||||
|
||||
def clear_pin_for(user)
|
||||
return unless user.present?
|
||||
|
||||
TopicUser.change(user.id, id, cleared_pinned_at: Time.now)
|
||||
end
|
||||
|
||||
def update_pinned(status)
|
||||
update_column(:pinned_at, status ? Time.now : nil)
|
||||
end
|
||||
|
||||
def draft_key
|
||||
|
@ -535,18 +552,18 @@ class Topic < ActiveRecord::Base
|
|||
|
||||
# notification stuff
|
||||
def notify_watch!(user)
|
||||
TopicUser.change(user, id, notification_level: TopicUser::NotificationLevel::WATCHING)
|
||||
TopicUser.change(user, id, notification_level: TopicUser.notification_levels[:watching])
|
||||
end
|
||||
|
||||
def notify_tracking!(user)
|
||||
TopicUser.change(user, id, notification_level: TopicUser::NotificationLevel::TRACKING)
|
||||
TopicUser.change(user, id, notification_level: TopicUser.notification_levels[:tracking])
|
||||
end
|
||||
|
||||
def notify_regular!(user)
|
||||
TopicUser.change(user, id, notification_level: TopicUser::NotificationLevel::REGULAR)
|
||||
TopicUser.change(user, id, notification_level: TopicUser.notification_levels[:regular])
|
||||
end
|
||||
|
||||
def notify_muted!(user)
|
||||
TopicUser.change(user, id, notification_level: TopicUser::NotificationLevel::MUTED)
|
||||
TopicUser.change(user, id, notification_level: TopicUser.notification_levels[:muted])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,185 +2,173 @@ class TopicUser < ActiveRecord::Base
|
|||
belongs_to :user
|
||||
belongs_to :topic
|
||||
|
||||
module NotificationLevel
|
||||
WATCHING = 3
|
||||
TRACKING = 2
|
||||
REGULAR = 1
|
||||
MUTED = 0
|
||||
end
|
||||
# Class methods
|
||||
class << self
|
||||
|
||||
module NotificationReasons
|
||||
CREATED_TOPIC = 1
|
||||
USER_CHANGED = 2
|
||||
USER_INTERACTED = 3
|
||||
CREATED_POST = 4
|
||||
end
|
||||
# Enums
|
||||
def notification_levels
|
||||
@notification_levels ||= Enum.new(:muted, :regular, :tracking, :watching, start: 0)
|
||||
end
|
||||
|
||||
def self.auto_track(user_id, topic_id, reason)
|
||||
if TopicUser.where(user_id: user_id, topic_id: topic_id, notifications_reason_id: nil).exists?
|
||||
change(user_id, topic_id,
|
||||
notification_level: NotificationLevel::TRACKING,
|
||||
def notification_reasons
|
||||
@notification_reasons ||= Enum.new(:created_topic, :user_changed, :user_interacted, :created_post)
|
||||
end
|
||||
|
||||
def auto_track(user_id, topic_id, reason)
|
||||
if TopicUser.where(user_id: user_id, topic_id: topic_id, notifications_reason_id: nil).exists?
|
||||
change(user_id, topic_id,
|
||||
notification_level: notification_levels[:tracking],
|
||||
notifications_reason_id: reason
|
||||
)
|
||||
)
|
||||
|
||||
MessageBus.publish("/topic/#{topic_id}", {
|
||||
notification_level_change: NotificationLevel::TRACKING,
|
||||
notifications_reason_id: reason
|
||||
}, user_ids: [user_id])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Find the information specific to a user in a forum topic
|
||||
def self.lookup_for(user, topics)
|
||||
# If the user isn't logged in, there's no last read posts
|
||||
return {} if user.blank? || topics.blank?
|
||||
|
||||
topic_ids = topics.map(&:id)
|
||||
create_lookup(TopicUser.where(topic_id: topic_ids, user_id: user.id))
|
||||
end
|
||||
|
||||
def self.create_lookup(topic_users)
|
||||
topic_users = topic_users.to_a
|
||||
|
||||
result = {}
|
||||
return result if topic_users.blank?
|
||||
|
||||
topic_users.each do |ftu|
|
||||
result[ftu.topic_id] = ftu
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def self.get(topic,user)
|
||||
if Topic === topic
|
||||
topic = topic.id
|
||||
end
|
||||
if User === user
|
||||
user = user.id
|
||||
end
|
||||
|
||||
TopicUser.where('topic_id = ? and user_id = ?', topic, user).first
|
||||
end
|
||||
|
||||
# Change attributes for a user (creates a record when none is present). First it tries an update
|
||||
# since there's more likely to be an existing record than not. If the update returns 0 rows affected
|
||||
# it then creates the row instead.
|
||||
def self.change(user_id, topic_id, attrs)
|
||||
# Sometimes people pass objs instead of the ids. We can handle that.
|
||||
topic_id = topic_id.id if topic_id.is_a?(Topic)
|
||||
user_id = user_id.id if user_id.is_a?(User)
|
||||
|
||||
TopicUser.transaction do
|
||||
attrs = attrs.dup
|
||||
attrs[:starred_at] = DateTime.now if attrs[:starred_at].nil? && attrs[:starred]
|
||||
|
||||
if attrs[:notification_level]
|
||||
attrs[:notifications_changed_at] ||= DateTime.now
|
||||
attrs[:notifications_reason_id] ||= TopicUser::NotificationReasons::USER_CHANGED
|
||||
MessageBus.publish("/topic/#{topic_id}", {
|
||||
notification_level_change: notification_levels[:tracking],
|
||||
notifications_reason_id: reason
|
||||
}, user_ids: [user_id])
|
||||
end
|
||||
attrs_array = attrs.to_a
|
||||
end
|
||||
|
||||
attrs_sql = attrs_array.map { |t| "#{t[0]} = ?" }.join(", ")
|
||||
vals = attrs_array.map { |t| t[1] }
|
||||
rows = TopicUser.update_all([attrs_sql, *vals], topic_id: topic_id.to_i, user_id: user_id)
|
||||
# Find the information specific to a user in a forum topic
|
||||
def lookup_for(user, topics)
|
||||
# If the user isn't logged in, there's no last read posts
|
||||
return {} if user.blank? || topics.blank?
|
||||
|
||||
if rows == 0
|
||||
now = DateTime.now
|
||||
auto_track_after = self.exec_sql("select auto_track_topics_after_msecs from users where id = ?", user_id).values[0][0]
|
||||
auto_track_after ||= SiteSetting.auto_track_topics_after
|
||||
auto_track_after = auto_track_after.to_i
|
||||
topic_ids = topics.map(&:id)
|
||||
create_lookup(TopicUser.where(topic_id: topic_ids, user_id: user.id))
|
||||
end
|
||||
|
||||
if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed] || 0)
|
||||
attrs[:notification_level] ||= TopicUser::NotificationLevel::TRACKING
|
||||
def create_lookup(topic_users)
|
||||
topic_users = topic_users.to_a
|
||||
|
||||
result = {}
|
||||
return result if topic_users.blank?
|
||||
|
||||
topic_users.each do |ftu|
|
||||
result[ftu.topic_id] = ftu
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def get(topic,user)
|
||||
topic = topic.id if Topic === topic
|
||||
user = user.id if User === user
|
||||
TopicUser.where('topic_id = ? and user_id = ?', topic, user).first
|
||||
end
|
||||
|
||||
# Change attributes for a user (creates a record when none is present). First it tries an update
|
||||
# since there's more likely to be an existing record than not. If the update returns 0 rows affected
|
||||
# it then creates the row instead.
|
||||
def change(user_id, topic_id, attrs)
|
||||
# Sometimes people pass objs instead of the ids. We can handle that.
|
||||
topic_id = topic_id.id if topic_id.is_a?(Topic)
|
||||
user_id = user_id.id if user_id.is_a?(User)
|
||||
|
||||
TopicUser.transaction do
|
||||
attrs = attrs.dup
|
||||
attrs[:starred_at] = DateTime.now if attrs[:starred_at].nil? && attrs[:starred]
|
||||
|
||||
if attrs[:notification_level]
|
||||
attrs[:notifications_changed_at] ||= DateTime.now
|
||||
attrs[:notifications_reason_id] ||= TopicUser.notification_reasons[:user_changed]
|
||||
end
|
||||
attrs_array = attrs.to_a
|
||||
|
||||
TopicUser.create(attrs.merge!(user_id: user_id, topic_id: topic_id.to_i, first_visited_at: now ,last_visited_at: now))
|
||||
attrs_sql = attrs_array.map { |t| "#{t[0]} = ?" }.join(", ")
|
||||
vals = attrs_array.map { |t| t[1] }
|
||||
rows = TopicUser.update_all([attrs_sql, *vals], topic_id: topic_id.to_i, user_id: user_id)
|
||||
|
||||
if rows == 0
|
||||
now = DateTime.now
|
||||
auto_track_after = User.select(:auto_track_topics_after_msecs).where(id: user_id).first.auto_track_topics_after_msecs
|
||||
auto_track_after ||= SiteSetting.auto_track_topics_after
|
||||
|
||||
if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed] || 0)
|
||||
attrs[:notification_level] ||= notification_levels[:tracking]
|
||||
end
|
||||
|
||||
TopicUser.create(attrs.merge!(user_id: user_id, topic_id: topic_id.to_i, first_visited_at: now ,last_visited_at: now))
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# In case of a race condition to insert, do nothing
|
||||
end
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# In case of a race condition to insert, do nothing
|
||||
end
|
||||
|
||||
def self.track_visit!(topic,user)
|
||||
now = DateTime.now
|
||||
rows = exec_sql_row_count(
|
||||
"update topic_users set last_visited_at=? where topic_id=? and user_id=?",
|
||||
now, topic.id, user.id
|
||||
)
|
||||
|
||||
if rows == 0
|
||||
exec_sql('insert into topic_users(topic_id, user_id, last_visited_at, first_visited_at)
|
||||
values(?,?,?,?)',
|
||||
topic.id, user.id, now, now)
|
||||
end
|
||||
end
|
||||
|
||||
# Update the last read and the last seen post count, but only if it doesn't exist.
|
||||
# This would be a lot easier if psql supported some kind of upsert
|
||||
def self.update_last_read(user, topic_id, post_number, msecs)
|
||||
return if post_number.blank?
|
||||
msecs = 0 if msecs.to_i < 0
|
||||
|
||||
args = {
|
||||
user_id: user.id,
|
||||
topic_id: topic_id,
|
||||
post_number: post_number,
|
||||
now: DateTime.now,
|
||||
msecs: msecs,
|
||||
tracking: TopicUser::NotificationLevel::TRACKING,
|
||||
threshold: SiteSetting.auto_track_topics_after
|
||||
}
|
||||
|
||||
rows = exec_sql("UPDATE topic_users
|
||||
SET
|
||||
last_read_post_number = greatest(:post_number, tu.last_read_post_number),
|
||||
seen_post_count = t.highest_post_number,
|
||||
total_msecs_viewed = tu.total_msecs_viewed + :msecs,
|
||||
notification_level =
|
||||
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
|
||||
coalesce(u.auto_track_topics_after_msecs,:threshold) and
|
||||
coalesce(u.auto_track_topics_after_msecs, :threshold) >= 0 then
|
||||
:tracking
|
||||
else
|
||||
tu.notification_level
|
||||
end
|
||||
FROM topic_users tu
|
||||
join topics t on t.id = tu.topic_id
|
||||
join users u on u.id = :user_id
|
||||
WHERE
|
||||
tu.topic_id = topic_users.topic_id AND
|
||||
tu.user_id = topic_users.user_id AND
|
||||
tu.topic_id = :topic_id AND
|
||||
tu.user_id = :user_id
|
||||
RETURNING
|
||||
topic_users.notification_level, tu.notification_level old_level
|
||||
",
|
||||
args).values
|
||||
|
||||
if rows.length == 1
|
||||
before = rows[0][1].to_i
|
||||
after = rows[0][0].to_i
|
||||
|
||||
if before != after
|
||||
MessageBus.publish("/topic/#{topic_id}", {notification_level_change: after}, user_ids: [user.id])
|
||||
def track_visit!(topic,user)
|
||||
now = DateTime.now
|
||||
rows = TopicUser.update_all({last_visited_at: now}, {topic_id: topic.id, user_id: user.id})
|
||||
if rows == 0
|
||||
TopicUser.create(topic_id: topic.id, user_id: user.id, last_visited_at: now, first_visited_at: now)
|
||||
end
|
||||
end
|
||||
|
||||
if rows.length == 0
|
||||
args[:tracking] = TopicUser::NotificationLevel::TRACKING
|
||||
args[:regular] = TopicUser::NotificationLevel::REGULAR
|
||||
args[:site_setting] = SiteSetting.auto_track_topics_after
|
||||
exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, seen_post_count, last_visited_at, first_visited_at, notification_level)
|
||||
SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now,
|
||||
case when coalesce(u.auto_track_topics_after_msecs, :site_setting) = 0 then :tracking else :regular end
|
||||
FROM topics AS ft
|
||||
JOIN users u on u.id = :user_id
|
||||
WHERE ft.id = :topic_id
|
||||
AND NOT EXISTS(SELECT 1
|
||||
FROM topic_users AS ftu
|
||||
WHERE ftu.user_id = :user_id and ftu.topic_id = :topic_id)",
|
||||
args)
|
||||
# Update the last read and the last seen post count, but only if it doesn't exist.
|
||||
# This would be a lot easier if psql supported some kind of upsert
|
||||
def update_last_read(user, topic_id, post_number, msecs)
|
||||
return if post_number.blank?
|
||||
msecs = 0 if msecs.to_i < 0
|
||||
|
||||
args = {
|
||||
user_id: user.id,
|
||||
topic_id: topic_id,
|
||||
post_number: post_number,
|
||||
now: DateTime.now,
|
||||
msecs: msecs,
|
||||
tracking: notification_levels[:tracking],
|
||||
threshold: SiteSetting.auto_track_topics_after
|
||||
}
|
||||
|
||||
rows = exec_sql("UPDATE topic_users
|
||||
SET
|
||||
last_read_post_number = greatest(:post_number, tu.last_read_post_number),
|
||||
seen_post_count = t.highest_post_number,
|
||||
total_msecs_viewed = tu.total_msecs_viewed + :msecs,
|
||||
notification_level =
|
||||
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
|
||||
coalesce(u.auto_track_topics_after_msecs,:threshold) and
|
||||
coalesce(u.auto_track_topics_after_msecs, :threshold) >= 0 then
|
||||
:tracking
|
||||
else
|
||||
tu.notification_level
|
||||
end
|
||||
FROM topic_users tu
|
||||
join topics t on t.id = tu.topic_id
|
||||
join users u on u.id = :user_id
|
||||
WHERE
|
||||
tu.topic_id = topic_users.topic_id AND
|
||||
tu.user_id = topic_users.user_id AND
|
||||
tu.topic_id = :topic_id AND
|
||||
tu.user_id = :user_id
|
||||
RETURNING
|
||||
topic_users.notification_level, tu.notification_level old_level
|
||||
",
|
||||
args).values
|
||||
|
||||
if rows.length == 1
|
||||
before = rows[0][1].to_i
|
||||
after = rows[0][0].to_i
|
||||
|
||||
if before != after
|
||||
MessageBus.publish("/topic/#{topic_id}", {notification_level_change: after}, user_ids: [user.id])
|
||||
end
|
||||
end
|
||||
|
||||
if rows.length == 0
|
||||
args[:tracking] = notification_levels[:tracking]
|
||||
args[:regular] = notification_levels[:regular]
|
||||
args[:site_setting] = SiteSetting.auto_track_topics_after
|
||||
exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, seen_post_count, last_visited_at, first_visited_at, notification_level)
|
||||
SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now,
|
||||
case when coalesce(u.auto_track_topics_after_msecs, :site_setting) = 0 then :tracking else :regular end
|
||||
FROM topics AS ft
|
||||
JOIN users u on u.id = :user_id
|
||||
WHERE ft.id = :topic_id
|
||||
AND NOT EXISTS(SELECT 1
|
||||
FROM topic_users AS ftu
|
||||
WHERE ftu.user_id = :user_id and ftu.topic_id = :topic_id)",
|
||||
args)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -443,11 +443,8 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def readable_name
|
||||
if name.present? && name != username
|
||||
"#{name} (#{username})"
|
||||
else
|
||||
username
|
||||
end
|
||||
return "#{name} (#{username})" if name.present? && name != username
|
||||
username
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -461,25 +458,14 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def update_tracked_topics
|
||||
if auto_track_topics_after_msecs_changed?
|
||||
return unless auto_track_topics_after_msecs_changed?
|
||||
|
||||
if auto_track_topics_after_msecs < 0
|
||||
|
||||
User.exec_sql('update topic_users set notification_level = ?
|
||||
where notifications_reason_id is null and
|
||||
user_id = ?' , TopicUser::NotificationLevel::REGULAR , id)
|
||||
else
|
||||
|
||||
User.exec_sql('update topic_users set notification_level = ?
|
||||
where notifications_reason_id is null and
|
||||
user_id = ? and
|
||||
total_msecs_viewed < ?' , TopicUser::NotificationLevel::REGULAR , id, auto_track_topics_after_msecs)
|
||||
|
||||
User.exec_sql('update topic_users set notification_level = ?
|
||||
where notifications_reason_id is null and
|
||||
user_id = ? and
|
||||
total_msecs_viewed >= ?' , TopicUser::NotificationLevel::TRACKING , id, auto_track_topics_after_msecs)
|
||||
end
|
||||
where_conditions = {notifications_reason_id: nil, user_id: id}
|
||||
if auto_track_topics_after_msecs < 0
|
||||
TopicUser.update_all({notification_level: TopicUser.notification_levels[:regular]}, where_conditions)
|
||||
else
|
||||
TopicUser.update_all(["notification_level = CASE WHEN total_msecs_viewed < ? THEN ? ELSE ? END",
|
||||
auto_track_topics_after_msecs, TopicUser.notification_levels[:regular], TopicUser.notification_levels[:tracking]], where_conditions)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ class CategoryTopicSerializer < BasicTopicSerializer
|
|||
|
||||
attributes :slug,
|
||||
:visible,
|
||||
:pinned,
|
||||
:closed,
|
||||
:archived
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require_dependency 'pinned_check'
|
||||
|
||||
class TopicListItemSerializer < BasicTopicSerializer
|
||||
|
||||
attributes :views,
|
||||
|
@ -29,4 +31,8 @@ class TopicListItemSerializer < BasicTopicSerializer
|
|||
object.posters || []
|
||||
end
|
||||
|
||||
def pinned
|
||||
PinnedCheck.new(object, object.user_data).pinned?
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require_dependency 'pinned_check'
|
||||
|
||||
class TopicViewSerializer < ApplicationSerializer
|
||||
|
||||
# These attributes will be delegated to the topic
|
||||
|
@ -12,7 +14,6 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
:last_posted_at,
|
||||
:visible,
|
||||
:closed,
|
||||
:pinned,
|
||||
:archived,
|
||||
:moderator_posts_count,
|
||||
:has_best_of,
|
||||
|
@ -42,7 +43,8 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
:notifications_reason_id,
|
||||
:posts,
|
||||
:at_bottom,
|
||||
:highest_post_number
|
||||
:highest_post_number,
|
||||
:pinned
|
||||
|
||||
has_one :created_by, serializer: BasicUserSerializer, embed: :objects
|
||||
has_one :last_poster, serializer: BasicUserSerializer, embed: :objects
|
||||
|
@ -193,6 +195,10 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
object.highest_post_number
|
||||
end
|
||||
|
||||
def pinned
|
||||
PinnedCheck.new(object.topic, object.topic_user).pinned?
|
||||
end
|
||||
|
||||
def posts
|
||||
return @posts if @posts.present?
|
||||
@posts = []
|
||||
|
|
|
@ -452,6 +452,10 @@ en:
|
|||
title: 'Reply'
|
||||
help: 'begin composing a reply to this topic'
|
||||
|
||||
clear_pin:
|
||||
title: "Clear pin"
|
||||
help: "Clear the pinned status of this topic so it no longer appears at the top of your topic list"
|
||||
|
||||
share:
|
||||
title: 'Share'
|
||||
help: 'share a link to this topic'
|
||||
|
|
|
@ -175,6 +175,7 @@ Discourse::Application.routes.draw do
|
|||
put 't/:topic_id/star' => 'topics#star', :constraints => {:topic_id => /\d+/}
|
||||
put 't/:slug/:topic_id/status' => 'topics#status', :constraints => {:topic_id => /\d+/}
|
||||
put 't/:topic_id/status' => 'topics#status', :constraints => {:topic_id => /\d+/}
|
||||
put 't/:topic_id/clear-pin' => 'topics#clear_pin', :constraints => {:topic_id => /\d+/}
|
||||
put 't/:topic_id/mute' => 'topics#mute', :constraints => {:topic_id => /\d+/}
|
||||
put 't/:topic_id/unmute' => 'topics#unmute', :constraints => {:topic_id => /\d+/}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
class AddClearedPinnedToTopicUsers < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :topic_users, :cleared_pinned_at, :datetime, null: true
|
||||
|
||||
add_column :topics, :pinned_at, :datetime, null: true
|
||||
execute "UPDATE topics SET pinned_at = created_at WHERE pinned"
|
||||
remove_column :topics, :pinned
|
||||
end
|
||||
end
|
2049
db/structure.sql
2049
db/structure.sql
File diff suppressed because it is too large
Load Diff
24
lib/pinned_check.rb
Normal file
24
lib/pinned_check.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Helps us determine whether a topic should be displayed as pinned or not,
|
||||
# taking into account anonymous users and users who have dismissed it
|
||||
class PinnedCheck
|
||||
|
||||
def initialize(topic, topic_user=nil)
|
||||
@topic, @topic_user = topic, topic_user
|
||||
end
|
||||
|
||||
def pinned?
|
||||
|
||||
# If the topic isn't pinned the answer is false
|
||||
return false if @topic.pinned_at.blank?
|
||||
|
||||
# If the user is anonymous or hasn't entered the topic, the value is always true
|
||||
return true if @topic_user.blank?
|
||||
|
||||
# If the user hasn't cleared the pin, it's true
|
||||
return true if @topic_user.cleared_pinned_at.blank?
|
||||
|
||||
# The final check is to see whether the cleared the pin before or after it was last pinned
|
||||
@topic_user.cleared_pinned_at < @topic.pinned_at
|
||||
end
|
||||
|
||||
end
|
|
@ -6,6 +6,47 @@ require_dependency 'topic_list'
|
|||
|
||||
class TopicQuery
|
||||
|
||||
class << self
|
||||
# use the constants in conjuction with COALESCE to determine the order with regard to pinned
|
||||
# topics that have been cleared by the user. There
|
||||
# might be a cleaner way to do this.
|
||||
def lowest_date
|
||||
"2010-01-01"
|
||||
end
|
||||
|
||||
def highest_date
|
||||
"3000-01-01"
|
||||
end
|
||||
|
||||
# If you've clearned the pin, use bumped_at, otherwise put it at the top
|
||||
def order_with_pinned_sql
|
||||
"CASE
|
||||
WHEN (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}'))
|
||||
THEN '#{highest_date}'
|
||||
ELSE topics.bumped_at
|
||||
END DESC"
|
||||
end
|
||||
|
||||
# If you've clearned the pin, use bumped_at, otherwise put it at the top
|
||||
def order_nocategory_with_pinned_sql
|
||||
"CASE
|
||||
WHEN topics.category_id IS NULL and (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}'))
|
||||
THEN '#{highest_date}'
|
||||
ELSE topics.bumped_at
|
||||
END DESC"
|
||||
end
|
||||
|
||||
# For anonymous users
|
||||
def order_nocategory_basic_bumped
|
||||
"CASE WHEN topics.category_id IS NULL and (topics.pinned_at IS NOT NULL) THEN 0 ELSE 1 END, topics.bumped_at DESC"
|
||||
end
|
||||
|
||||
def order_basic_bumped
|
||||
"CASE WHEN (topics.pinned_at IS NOT NULL) THEN 0 ELSE 1 END, topics.bumped_at DESC"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def initialize(user=nil, opts={})
|
||||
@user = user
|
||||
|
||||
|
@ -64,23 +105,19 @@ class TopicQuery
|
|||
|
||||
# The popular view of topics
|
||||
def list_popular
|
||||
return_list(unordered: true) do |list|
|
||||
list.order('CASE WHEN topics.category_id IS NULL and topics.pinned THEN 0 ELSE 1 END, topics.bumped_at DESC')
|
||||
end
|
||||
TopicList.new(@user, default_list)
|
||||
end
|
||||
|
||||
# The favorited topics
|
||||
def list_favorited
|
||||
return_list do |list|
|
||||
list.joins("INNER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.starred AND tu.user_id = #{@user_id})")
|
||||
list.where('tu.starred')
|
||||
end
|
||||
end
|
||||
|
||||
def list_read
|
||||
return_list(unordered: true) do |list|
|
||||
list
|
||||
.joins("INNER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id})")
|
||||
.order('COALESCE(tu.last_visited_at, topics.bumped_at) DESC')
|
||||
list.order('COALESCE(tu.last_visited_at, topics.bumped_at) DESC')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -93,17 +130,30 @@ class TopicQuery
|
|||
end
|
||||
|
||||
def list_posted
|
||||
return_list do |list|
|
||||
list.joins("INNER JOIN topic_users AS tu ON (tu.topic_id = topics.id AND tu.posted AND tu.user_id = #{@user_id})")
|
||||
end
|
||||
return_list {|l| l.where('tu.user_id IS NOT NULL') }
|
||||
end
|
||||
|
||||
def list_uncategorized
|
||||
return_list {|l| l.where(category_id: nil).order('topics.pinned desc')}
|
||||
return_list(unordered: true) do |list|
|
||||
list = list.where(category_id: nil)
|
||||
|
||||
if @user_id.present?
|
||||
list.order(TopicQuery.order_with_pinned_sql)
|
||||
else
|
||||
list.order(TopicQuery.order_nocategory_basic_bumped)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def list_category(category)
|
||||
return_list {|l| l.where(category_id: category.id).order('topics.pinned desc')}
|
||||
return_list(unordered: true) do |list|
|
||||
list = list.where(category_id: category.id)
|
||||
if @user_id.present?
|
||||
list.order(TopicQuery.order_with_pinned_sql)
|
||||
else
|
||||
list.order(TopicQuery.order_basic_bumped)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unread_count
|
||||
|
@ -130,8 +180,22 @@ class TopicQuery
|
|||
query_opts = @opts.merge(list_opts)
|
||||
page_size = query_opts[:per_page] || SiteSetting.topics_per_page
|
||||
|
||||
# Start with a list of all topics
|
||||
result = Topic
|
||||
result = result.topic_list_order unless query_opts[:unordered]
|
||||
|
||||
if @user_id.present?
|
||||
result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id})")
|
||||
end
|
||||
|
||||
unless query_opts[:unordered]
|
||||
# If we're logged in, we have to pay attention to our pinned settings
|
||||
if @user_id.present?
|
||||
result = result.order(TopicQuery.order_nocategory_with_pinned_sql)
|
||||
else
|
||||
result = result.order(TopicQuery.order_basic_bumped)
|
||||
end
|
||||
end
|
||||
|
||||
result = result.listable_topics.includes(:category)
|
||||
result = result.where('categories.name is null or categories.name <> ?', query_opts[:exclude_category]) if query_opts[:exclude_category]
|
||||
result = result.where('categories.name = ?', query_opts[:only_category]) if query_opts[:only_category]
|
||||
|
@ -145,16 +209,15 @@ class TopicQuery
|
|||
def new_results(list_opts={})
|
||||
|
||||
default_list(list_opts)
|
||||
.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id})")
|
||||
.where("topics.created_at >= :created_at", created_at: @user.treat_as_new_topic_start_date)
|
||||
.where("tu.last_read_post_number IS NULL")
|
||||
.where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser::NotificationLevel::TRACKING)
|
||||
.where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser.notification_levels[:tracking])
|
||||
end
|
||||
|
||||
def unread_results(list_opts={})
|
||||
default_list(list_opts)
|
||||
.joins("INNER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id} AND tu.last_read_post_number < topics.highest_post_number)")
|
||||
.where("COALESCE(tu.notification_level, :regular) >= :tracking", regular: TopicUser::NotificationLevel::REGULAR, tracking: TopicUser::NotificationLevel::TRACKING)
|
||||
.where("tu.last_read_post_number < topics.highest_post_number")
|
||||
.where("COALESCE(tu.notification_level, :regular) >= :tracking", regular: TopicUser.notification_levels[:regular], tracking: TopicUser.notification_levels[:tracking])
|
||||
end
|
||||
|
||||
def random_suggested_results_for(topic, count, exclude_topic_ids)
|
||||
|
|
|
@ -27,7 +27,7 @@ class Unread
|
|||
protected
|
||||
|
||||
def do_not_notify?(notification_level)
|
||||
[TopicUser::NotificationLevel::MUTED, TopicUser::NotificationLevel::REGULAR].include?(notification_level)
|
||||
[TopicUser.notification_levels[:muted], TopicUser.notification_levels[:regular]].include?(notification_level)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
58
spec/components/pinned_check_spec.rb
Normal file
58
spec/components/pinned_check_spec.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
require 'pinned_check'
|
||||
|
||||
describe PinnedCheck do
|
||||
|
||||
#let(:topic) { Fabricate.build(:topic) }
|
||||
|
||||
let(:pinned_at) { 12.hours.ago }
|
||||
let(:unpinned_topic) { Fabricate.build(:topic) }
|
||||
let(:pinned_topic) { Fabricate.build(:topic, pinned_at: pinned_at) }
|
||||
|
||||
context "without a topic_user record (either anonymous or never been in the topic)" do
|
||||
|
||||
it "returns false if the topic is not pinned" do
|
||||
PinnedCheck.new(unpinned_topic).should_not be_pinned
|
||||
end
|
||||
|
||||
it "returns true if the topic is pinned" do
|
||||
PinnedCheck.new(unpinned_topic).should_not be_pinned
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "with a topic_user record" do
|
||||
let(:user) { Fabricate.build(:user) }
|
||||
let(:unpinned_topic_user) { Fabricate.build(:topic_user, user: user, topic: unpinned_topic) }
|
||||
|
||||
describe "unpinned topic" do
|
||||
let(:topic_user) { TopicUser.new(topic: unpinned_topic, user: user) }
|
||||
|
||||
it "returns false" do
|
||||
PinnedCheck.new(unpinned_topic, topic_user).should_not be_pinned
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "pinned topic" do
|
||||
let(:topic_user) { TopicUser.new(topic: pinned_topic, user: user) }
|
||||
|
||||
it "is pinned if the topic_user's cleared_pinned_at is blank" do
|
||||
PinnedCheck.new(pinned_topic, topic_user).should be_pinned
|
||||
end
|
||||
|
||||
it "is not pinned if the topic_user's cleared_pinned_at is later than when it was pinned_at" do
|
||||
topic_user.cleared_pinned_at = (pinned_at + 1.hour)
|
||||
PinnedCheck.new(pinned_topic, topic_user).should_not be_pinned
|
||||
end
|
||||
|
||||
it "is pinned if the topic_user's cleared_pinned_at is earlier than when it was pinned_at" do
|
||||
topic_user.cleared_pinned_at = (pinned_at - 3.hours)
|
||||
PinnedCheck.new(pinned_topic, topic_user).should be_pinned
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -12,14 +12,13 @@ describe TopicQuery do
|
|||
|
||||
context 'a bunch of topics' do
|
||||
let!(:regular_topic) { Fabricate(:topic, title: 'this is a regular topic', user: creator, bumped_at: 15.minutes.ago) }
|
||||
let!(:pinned_topic) { Fabricate(:topic, title: 'this is a pinned topic', user: creator, pinned: true, bumped_at: 10.minutes.ago) }
|
||||
let!(:pinned_topic) { Fabricate(:topic, title: 'this is a pinned topic', user: creator, pinned_at: 10.minutes.ago, bumped_at: 10.minutes.ago) }
|
||||
let!(:archived_topic) { Fabricate(:topic, title: 'this is an archived topic', user: creator, archived: true, bumped_at: 6.minutes.ago) }
|
||||
let!(:invisible_topic) { Fabricate(:topic, title: 'this is an invisible topic', user: creator, visible: false, bumped_at: 5.minutes.ago) }
|
||||
let!(:closed_topic) { Fabricate(:topic, title: 'this is a closed topic', user: creator, closed: true, bumped_at: 1.minute.ago) }
|
||||
let(:topics) { topic_query.list_popular.topics }
|
||||
|
||||
context 'list_popular' do
|
||||
let(:topics) { topic_query.list_popular.topics }
|
||||
|
||||
it "returns the topics in the correct order" do
|
||||
topics.should == [pinned_topic, closed_topic, archived_topic, regular_topic]
|
||||
end
|
||||
|
@ -33,6 +32,17 @@ describe TopicQuery do
|
|||
end
|
||||
end
|
||||
|
||||
context 'after clearring a pinned topic' do
|
||||
before do
|
||||
pinned_topic.clear_pin_for(user)
|
||||
end
|
||||
|
||||
it "no longer shows the pinned topic at the top" do
|
||||
topics.should == [closed_topic, archived_topic, pinned_topic, regular_topic]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'categorized' do
|
||||
|
|
|
@ -7,8 +7,8 @@ describe Unread do
|
|||
before do
|
||||
@topic = Fabricate(:topic, posts_count: 13, highest_post_number: 13)
|
||||
@topic_user = TopicUser.get(@topic, @topic.user)
|
||||
@topic_user.stubs(:notification_level).returns(TopicUser::NotificationLevel::TRACKING)
|
||||
@topic_user.notification_level = TopicUser::NotificationLevel::TRACKING
|
||||
@topic_user.stubs(:notification_level).returns(TopicUser.notification_levels[:tracking])
|
||||
@topic_user.notification_level = TopicUser.notification_levels[:tracking]
|
||||
@unread = Unread.new(@topic, @topic_user)
|
||||
end
|
||||
|
||||
|
@ -51,7 +51,7 @@ describe Unread do
|
|||
|
||||
it 'has 0 new posts if the user has read 10 posts but is not tracking' do
|
||||
@topic_user.stubs(:seen_post_count).returns(10)
|
||||
@topic_user.stubs(:notification_level).returns(TopicUser::NotificationLevel::REGULAR)
|
||||
@topic_user.stubs(:notification_level).returns(TopicUser.notification_levels[:regular])
|
||||
@unread.new_posts.should == 0
|
||||
end
|
||||
|
||||
|
|
|
@ -74,6 +74,37 @@ describe TopicsController do
|
|||
|
||||
end
|
||||
|
||||
context 'clear_pin' do
|
||||
it 'needs you to be logged in' do
|
||||
lambda { xhr :put, :clear_pin, topic_id: 1 }.should raise_error(Discourse::NotLoggedIn)
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
let!(:user) { log_in }
|
||||
|
||||
it "fails when the user can't see the topic" do
|
||||
Guardian.any_instance.expects(:can_see?).with(topic).returns(false)
|
||||
xhr :put, :clear_pin, topic_id: topic.id
|
||||
response.should_not be_success
|
||||
end
|
||||
|
||||
describe 'when the user can see the topic' do
|
||||
it "calls clear_pin_for if the user can see the topic" do
|
||||
Topic.any_instance.expects(:clear_pin_for).with(user).once
|
||||
xhr :put, :clear_pin, topic_id: topic.id
|
||||
end
|
||||
|
||||
it "succeeds" do
|
||||
xhr :put, :clear_pin, topic_id: topic.id
|
||||
response.should be_success
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'status' do
|
||||
it 'needs you to be logged in' do
|
||||
lambda { xhr :put, :status, topic_id: 1, status: 'visible', enabled: true }.should raise_error(Discourse::NotLoggedIn)
|
||||
|
|
|
@ -547,8 +547,12 @@ describe Topic do
|
|||
@topic.reload
|
||||
end
|
||||
|
||||
it "doesn't have a pinned_at" do
|
||||
@topic.pinned_at.should be_blank
|
||||
end
|
||||
|
||||
it 'should not be pinned' do
|
||||
@topic.should_not be_pinned
|
||||
@topic.pinned_at.should be_blank
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
|
@ -562,13 +566,13 @@ describe Topic do
|
|||
|
||||
context 'enable' do
|
||||
before do
|
||||
@topic.update_attribute :pinned, false
|
||||
@topic.update_attribute :pinned_at, nil
|
||||
@topic.update_status('pinned', true, @user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should be pinned' do
|
||||
@topic.should be_pinned
|
||||
@topic.pinned_at.should be_present
|
||||
end
|
||||
|
||||
it 'adds a moderator post' do
|
||||
|
@ -588,7 +592,7 @@ describe Topic do
|
|||
@topic.reload
|
||||
end
|
||||
|
||||
it 'should not be pinned' do
|
||||
it 'should not be archived' do
|
||||
@topic.should_not be_archived
|
||||
end
|
||||
|
||||
|
@ -866,8 +870,12 @@ describe Topic do
|
|||
topic.should be_visible
|
||||
end
|
||||
|
||||
it "has an empty pinned_at" do
|
||||
topic.pinned_at.should be_blank
|
||||
end
|
||||
|
||||
it 'is not pinned' do
|
||||
topic.should_not be_pinned
|
||||
topic.pinned_at.should be_blank
|
||||
end
|
||||
|
||||
it 'is not closed' do
|
||||
|
|
|
@ -5,155 +5,165 @@ describe TopicUser do
|
|||
it { should belong_to :user }
|
||||
it { should belong_to :topic }
|
||||
|
||||
let!(:yesterday) { DateTime.now.yesterday }
|
||||
|
||||
before do
|
||||
#mock time so we can test dates
|
||||
@now = DateTime.now.yesterday
|
||||
DateTime.expects(:now).at_least_once.returns(@now)
|
||||
@topic = Fabricate(:topic)
|
||||
@user = Fabricate(:coding_horror)
|
||||
DateTime.expects(:now).at_least_once.returns(yesterday)
|
||||
end
|
||||
|
||||
let!(:topic) { Fabricate(:topic) }
|
||||
let!(:user) { Fabricate(:coding_horror) }
|
||||
let(:topic_user) { TopicUser.get(topic,user) }
|
||||
let(:topic_creator_user) { TopicUser.get(topic, topic.user) }
|
||||
|
||||
let(:post) { Fabricate(:post, topic: topic, user: user) }
|
||||
let(:new_user) { Fabricate(:user, auto_track_topics_after_msecs: 1000) }
|
||||
let(:topic_new_user) { TopicUser.get(topic, new_user)}
|
||||
|
||||
|
||||
describe "unpinned" do
|
||||
|
||||
before do
|
||||
TopicUser.change(user, topic, {:starred_at => yesterday})
|
||||
end
|
||||
|
||||
it "defaults to blank" do
|
||||
topic_user.cleared_pinned_at.should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'notifications' do
|
||||
|
||||
it 'should be set to tracking if auto_track_topics is enabled' do
|
||||
@user.auto_track_topics_after_msecs = 0
|
||||
@user.save
|
||||
TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
|
||||
TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::TRACKING
|
||||
user.update_column(:auto_track_topics_after_msecs, 0)
|
||||
TopicUser.change(user, topic, {:starred_at => yesterday})
|
||||
TopicUser.get(topic, user).notification_level.should == TopicUser.notification_levels[:tracking]
|
||||
end
|
||||
|
||||
it 'should reset regular topics to tracking topics if auto track is changed' do
|
||||
TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
|
||||
@user.auto_track_topics_after_msecs = 0
|
||||
@user.save
|
||||
TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::TRACKING
|
||||
TopicUser.change(user, topic, {:starred_at => yesterday})
|
||||
user.auto_track_topics_after_msecs = 0
|
||||
user.save
|
||||
topic_user.notification_level.should == TopicUser.notification_levels[:tracking]
|
||||
end
|
||||
|
||||
it 'should be set to "regular" notifications, by default on non creators' do
|
||||
TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
|
||||
TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
TopicUser.change(user, topic, {:starred_at => yesterday})
|
||||
TopicUser.get(topic,user).notification_level.should == TopicUser.notification_levels[:regular]
|
||||
end
|
||||
|
||||
it 'reason should reset when changed' do
|
||||
@topic.notify_muted!(@topic.user)
|
||||
TopicUser.get(@topic,@topic.user).notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
topic.notify_muted!(topic.user)
|
||||
TopicUser.get(topic,topic.user).notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
|
||||
end
|
||||
|
||||
it 'should have the correct reason for a user change when watched' do
|
||||
@topic.notify_watch!(@user)
|
||||
tu = TopicUser.get(@topic,@user)
|
||||
tu.notification_level.should == TopicUser::NotificationLevel::WATCHING
|
||||
tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
tu.notifications_changed_at.should_not be_nil
|
||||
topic.notify_watch!(user)
|
||||
topic_user.notification_level.should == TopicUser.notification_levels[:watching]
|
||||
topic_user.notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
|
||||
topic_user.notifications_changed_at.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should have the correct reason for a user change when set to regular' do
|
||||
@topic.notify_regular!(@user)
|
||||
tu = TopicUser.get(@topic,@user)
|
||||
tu.notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
tu.notifications_changed_at.should_not be_nil
|
||||
topic.notify_regular!(user)
|
||||
topic_user.notification_level.should == TopicUser.notification_levels[:regular]
|
||||
topic_user.notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
|
||||
topic_user.notifications_changed_at.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should have the correct reason for a user change when set to regular' do
|
||||
@topic.notify_muted!(@user)
|
||||
tu = TopicUser.get(@topic,@user)
|
||||
tu.notification_level.should == TopicUser::NotificationLevel::MUTED
|
||||
tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
tu.notifications_changed_at.should_not be_nil
|
||||
topic.notify_muted!(user)
|
||||
topic_user.notification_level.should == TopicUser.notification_levels[:muted]
|
||||
topic_user.notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
|
||||
topic_user.notifications_changed_at.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should watch topics a user created' do
|
||||
tu = TopicUser.get(@topic,@topic.user)
|
||||
tu.notification_level.should == TopicUser::NotificationLevel::WATCHING
|
||||
tu.notifications_reason_id.should == TopicUser::NotificationReasons::CREATED_TOPIC
|
||||
topic_creator_user.notification_level.should == TopicUser.notification_levels[:watching]
|
||||
topic_creator_user.notifications_reason_id.should == TopicUser.notification_reasons[:created_topic]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'visited at' do
|
||||
before do
|
||||
TopicUser.track_visit!(@topic, @user)
|
||||
@topic_user = TopicUser.get(@topic,@user)
|
||||
|
||||
before do
|
||||
TopicUser.track_visit!(topic, user)
|
||||
end
|
||||
|
||||
it 'set upon initial visit' do
|
||||
@topic_user.first_visited_at.to_i.should == @now.to_i
|
||||
@topic_user.last_visited_at.to_i.should == @now.to_i
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
end
|
||||
|
||||
it 'updates upon repeat visit' do
|
||||
tomorrow = @now.tomorrow
|
||||
DateTime.expects(:now).returns(tomorrow)
|
||||
today = yesterday.tomorrow
|
||||
DateTime.expects(:now).returns(today)
|
||||
|
||||
TopicUser.track_visit!(@topic,@user)
|
||||
TopicUser.track_visit!(topic,user)
|
||||
# reload is a no go
|
||||
@topic_user = TopicUser.get(@topic,@user)
|
||||
@topic_user.first_visited_at.to_i.should == @now.to_i
|
||||
@topic_user.last_visited_at.to_i.should == tomorrow.to_i
|
||||
topic_user = TopicUser.get(topic,user)
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.last_visited_at.to_i.should == today.to_i
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'read tracking' do
|
||||
before do
|
||||
@post = Fabricate(:post, topic: @topic, user: @topic.user)
|
||||
TopicUser.update_last_read(@user, @topic.id, 1, 0)
|
||||
@topic_user = TopicUser.get(@topic,@user)
|
||||
end
|
||||
|
||||
it 'should create a new record for a visit' do
|
||||
@topic_user.last_read_post_number.should == 1
|
||||
@topic_user.last_visited_at.to_i.should == @now.to_i
|
||||
@topic_user.first_visited_at.to_i.should == @now.to_i
|
||||
end
|
||||
context "without auto tracking" do
|
||||
|
||||
it 'should update the record for repeat visit' do
|
||||
Fabricate(:post, topic: @topic, user: @user)
|
||||
TopicUser.update_last_read(@user, @topic.id, 2, 0)
|
||||
@topic_user = TopicUser.get(@topic,@user)
|
||||
@topic_user.last_read_post_number.should == 2
|
||||
@topic_user.last_visited_at.to_i.should == @now.to_i
|
||||
@topic_user.first_visited_at.to_i.should == @now.to_i
|
||||
before do
|
||||
TopicUser.update_last_read(user, topic.id, 1, 0)
|
||||
end
|
||||
|
||||
let(:topic_user) { TopicUser.get(topic,user) }
|
||||
|
||||
it 'should create a new record for a visit' do
|
||||
topic_user.last_read_post_number.should == 1
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
end
|
||||
|
||||
it 'should update the record for repeat visit' do
|
||||
Fabricate(:post, topic: topic, user: user)
|
||||
TopicUser.update_last_read(user, topic.id, 2, 0)
|
||||
topic_user = TopicUser.get(topic,user)
|
||||
topic_user.last_read_post_number.should == 2
|
||||
topic_user.last_visited_at.to_i.should == yesterday.to_i
|
||||
topic_user.first_visited_at.to_i.should == yesterday.to_i
|
||||
end
|
||||
end
|
||||
|
||||
context 'auto tracking' do
|
||||
|
||||
before do
|
||||
Fabricate(:post, topic: @topic, user: @user)
|
||||
@new_user = Fabricate(:user, auto_track_topics_after_msecs: 1000)
|
||||
TopicUser.update_last_read(@new_user, @topic.id, 2, 0)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
TopicUser.update_last_read(new_user, topic.id, 2, 0)
|
||||
end
|
||||
|
||||
it 'should automatically track topics you reply to' do
|
||||
post = Fabricate(:post, topic: @topic, user: @new_user)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::TRACKING
|
||||
@topic_user.notifications_reason_id.should == TopicUser::NotificationReasons::CREATED_POST
|
||||
post = Fabricate(:post, topic: topic, user: new_user)
|
||||
topic_new_user.notification_level.should == TopicUser.notification_levels[:tracking]
|
||||
topic_new_user.notifications_reason_id.should == TopicUser.notification_reasons[:created_post]
|
||||
end
|
||||
|
||||
it 'should not automatically track topics you reply to and have set state manually' do
|
||||
Fabricate(:post, topic: @topic, user: @new_user)
|
||||
TopicUser.change(@new_user, @topic, notification_level: TopicUser::NotificationLevel::REGULAR)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
@topic_user.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
|
||||
Fabricate(:post, topic: topic, user: new_user)
|
||||
TopicUser.change(new_user, topic, notification_level: TopicUser.notification_levels[:regular])
|
||||
topic_new_user.notification_level.should == TopicUser.notification_levels[:regular]
|
||||
topic_new_user.notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
|
||||
end
|
||||
|
||||
it 'should automatically track topics after they are read for long enough' do
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
TopicUser.update_last_read(@new_user, @topic.id, 2, 1001)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::TRACKING
|
||||
topic_new_user.notification_level.should ==TopicUser.notification_levels[:regular]
|
||||
TopicUser.update_last_read(new_user, topic.id, 2, 1001)
|
||||
TopicUser.get(topic, new_user).notification_level.should == TopicUser.notification_levels[:tracking]
|
||||
end
|
||||
|
||||
it 'should not automatically track topics after they are read for long enough if changed manually' do
|
||||
TopicUser.change(@new_user, @topic, notification_level: TopicUser::NotificationLevel::REGULAR)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
|
||||
TopicUser.update_last_read(@new_user, @topic, 2, 1001)
|
||||
@topic_user = TopicUser.get(@topic,@new_user)
|
||||
@topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
|
||||
TopicUser.change(new_user, topic, notification_level: TopicUser.notification_levels[:regular])
|
||||
TopicUser.update_last_read(new_user, topic, 2, 1001)
|
||||
topic_new_user.notification_level.should == TopicUser.notification_levels[:regular]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -162,34 +172,33 @@ describe TopicUser do
|
|||
|
||||
it 'creates a forum topic user record' do
|
||||
lambda {
|
||||
TopicUser.change(@user, @topic.id, starred: true)
|
||||
TopicUser.change(user, topic.id, starred: true)
|
||||
}.should change(TopicUser, :count).by(1)
|
||||
end
|
||||
|
||||
it "only inserts a row once, even on repeated calls" do
|
||||
lambda {
|
||||
TopicUser.change(@user, @topic.id, starred: true)
|
||||
TopicUser.change(@user, @topic.id, starred: false)
|
||||
TopicUser.change(@user, @topic.id, starred: true)
|
||||
TopicUser.change(user, topic.id, starred: true)
|
||||
TopicUser.change(user, topic.id, starred: false)
|
||||
TopicUser.change(user, topic.id, starred: true)
|
||||
}.should change(TopicUser, :count).by(1)
|
||||
end
|
||||
|
||||
describe 'after creating a row' do
|
||||
before do
|
||||
TopicUser.change(@user, @topic.id, starred: true)
|
||||
@topic_user = TopicUser.where(user_id: @user.id, topic_id: @topic.id).first
|
||||
TopicUser.change(user, topic.id, starred: true)
|
||||
end
|
||||
|
||||
it 'has the correct starred value' do
|
||||
@topic_user.should be_starred
|
||||
TopicUser.get(topic, user).should be_starred
|
||||
end
|
||||
|
||||
it 'has a lookup' do
|
||||
TopicUser.lookup_for(@user, [@topic]).should be_present
|
||||
TopicUser.lookup_for(user, [topic]).should be_present
|
||||
end
|
||||
|
||||
it 'has a key in the lookup for this forum topic' do
|
||||
TopicUser.lookup_for(@user, [@topic]).has_key?(@topic.id).should be_true
|
||||
TopicUser.lookup_for(user, [topic]).has_key?(topic.id).should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user