Skip to content

Commit

Permalink
more progress towards live unread and new counts, unread message impl…
Browse files Browse the repository at this point in the history
…emented, still to implement delete messages
  • Loading branch information
SamSaffron committed May 30, 2013
1 parent f2da06a commit e93b7a3
Show file tree
Hide file tree
Showing 19 changed files with 325 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Discourse.ListController = Discourse.Controller.extend({
var listController = this;
this.set('loading', true);

var trackingState = Discourse.get('currentUser.userTrackingState');
var trackingState = Discourse.TopicTrackingState.current();

if (filterMode === 'categories') {
return Discourse.CategoryList.list(filterMode).then(function(items) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Discourse.ListTopicsController = Discourse.ObjectController.extend({

// Show newly inserted topics
showInserted: function(e) {
var tracker = Discourse.get('currentUser.userTrackingState');
var tracker = Discourse.TopicTrackingState.current();

// Move inserted into topics
this.get('content').loadBefore(tracker.get('newIncoming'));
Expand Down
9 changes: 6 additions & 3 deletions app/assets/javascripts/discourse/models/nav_item.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ var validNavNames = ['latest', 'hot', 'categories', 'category', 'favorited', 'un
var validAnon = ['latest', 'hot', 'categories', 'category'];

Discourse.NavItem = Discourse.Model.extend({
userTrackingStateBinding: Ember.Binding.oneWay('Discourse.currentUser.userTrackingState.messageCount'),
topicTrackingState: function(){
return Discourse.TopicTrackingState.current();
}.property(),

categoryName: function() {
var split = this.get('name').split('/');
return split[0] === 'category' ? split[1] : null;
Expand All @@ -25,11 +28,11 @@ Discourse.NavItem = Discourse.Model.extend({
}.property('name'),

count: function() {
var state = Discourse.get('currentUser.userTrackingState');
var state = this.get('topicTrackingState');
if (state) {
return state.lookupCount(this.get('name'));
}
}.property('userTrackingState')
}.property('topicTrackingState.messageCount')
});

Discourse.NavItem.reopenClass({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Discourse.UserTrackingState = Discourse.Model.extend({
Discourse.TopicTrackingState = Discourse.Model.extend({
messageCount: 0,

init: function(){
Expand All @@ -17,7 +17,7 @@ Discourse.UserTrackingState = Discourse.Model.extend({
tracker.removeTopic(data.topic_id);
}

if (data.message_type === "new_topic") {
if (data.message_type === "new_topic" || data.message_type === "unread") {
tracker.states["t" + data.topic_id] = data.payload;
tracker.notify(data);
}
Expand All @@ -26,7 +26,10 @@ Discourse.UserTrackingState = Discourse.Model.extend({
};

Discourse.MessageBus.subscribe("/new", process);
Discourse.MessageBus.subscribe("/unread/" + Discourse.currentUser.id, process);
var currentUser = Discourse.User.current();
if(currentUser) {
Discourse.MessageBus.subscribe("/unread/" + currentUser.id, process);
}
},

notify: function(data){
Expand Down Expand Up @@ -62,6 +65,8 @@ Discourse.UserTrackingState = Discourse.Model.extend({
sync: function(list, filter){
var tracker = this;

if(!list || !list.topics || !list.topics.length) { return; }

if(filter === "new" && !list.more_topics_url){
// scrub all new rows and reload from list
$.each(this.states, function(){
Expand All @@ -88,15 +93,19 @@ Discourse.UserTrackingState = Discourse.Model.extend({
if(topic.unseen) {
row.last_read_post_number = null;
} else {
row.last_read_post_number = topic.last_read_post_number;
// subtle issue here
row.last_read_post_number = topic.last_read_post_number || topic.highest_post_number;
}

row.highest_post_number = topic.highest_post_number;
if (topic.category) {
row.category_name = topic.category.name;
}

if (row.last_read_post_number === null || row.highest_post_number > row.last_read_post_number) {
tracker.states["t" + topic.id] = row;
} else {
delete tracker.states["t" + topic.id];
}
});

Expand Down Expand Up @@ -151,18 +160,28 @@ Discourse.UserTrackingState = Discourse.Model.extend({
// not exposed
var states = this.states;

data.each(function(row){
states["t" + row.topic_id] = row;
});
if(data) {
data.each(function(row){
states["t" + row.topic_id] = row;
});
}
}
});


Discourse.UserTrackingState.reopenClass({
Discourse.TopicTrackingState.reopenClass({
createFromStates: function(data){
var instance = Discourse.UserTrackingState.create();
var instance = Discourse.TopicTrackingState.create();
instance.loadStates(data);
instance.establishChannels();
return instance;
},
current: function(){
if (!this.tracker) {
var data = PreloadStore.get('topicTrackingStates');
this.tracker = this.createFromStates(data);
PreloadStore.remove('topicTrackingStates');
}
return this.tracker;
}
});
24 changes: 0 additions & 24 deletions app/assets/javascripts/discourse/routes/application_route.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
</tr>
</thead>

{{#if Discourse.currentUser.userTrackingState.hasIncoming}}
{{#if view.topicTrackingState.hasIncoming}}
<tbody>
<tr>
<td colspan="9">
<div class='alert alert-info'>
{{countI18n new_topics_inserted countBinding="Discourse.currentUser.userTrackingState.incomingCount"}}
{{countI18n new_topics_inserted countBinding="view.topicTrackingState.incomingCount"}}
<a href='#' {{action showInserted}}>{{i18n show_new_topics}}</a>
</div>
</td>
Expand Down
10 changes: 8 additions & 2 deletions app/assets/javascripts/discourse/views/list/list_topics_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, {
listBinding: 'controller.model',
loadedMore: false,
currentTopicId: null,
topicTrackingState: function() {
return Discourse.TopicTrackingState.current();
}.property(),

willDestroyElement: function() {
this.unbindScrolling();
Expand Down Expand Up @@ -42,8 +45,11 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, {
},

showTable: function() {
return this.get('list.topics').length > 0 || Discourse.get('currentUser.userTrackingState.hasIncoming');
}.property('list.topics','Discourse.currentUser.userTrackingState.hasIncoming'),
var topics = this.get('list.topics');
if(topics) {
return this.get('list.topics').length > 0 || this.get('topicTrackingState.hasIncoming');
}
}.property('list.topics','topicTrackingState.hasIncoming'),

loadMore: function() {
var listTopicsView = this;
Expand Down
3 changes: 3 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ def preload_json

if current_user.present?
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, root: false)))

serializer = ActiveModel::ArraySerializer.new(TopicTrackingState.report([current_user.id]), each_serializer: TopicTrackingStateSerializer)
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
end
store_preloaded("siteSettings", SiteSetting.client_settings_json)
end
Expand Down
6 changes: 6 additions & 0 deletions app/models/category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ def allow(group)
end
end

def secure_group_ids
if self.secure
groups.pluck("groups.id")
end
end

end

# == Schema Information
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,61 @@
# the allows end users to always know which topics have unread posts in them
# and which topics are new

class UserTrackingState
class TopicTrackingState

include ActiveModel::SerializerSupport

CHANNEL = "/user-tracking"

attr_accessor :user_id, :topic_id, :highest_post_number, :last_read_post_number, :created_at, :category_name

MessageBus.client_filter(CHANNEL) do |user_id, message|
if user_id
UserTrackingState.new(User.find(user_id)).filter(message)
else
nil
def self.publish_new(topic)

message = {
topic_id: topic.id,
message_type: "new_topic",
payload: {
last_read_post_number: nil,
highest_post_number: 1,
created_at: topic.created_at,
topic_id: topic.id
}
}

group_ids = topic.category && topic.category.secure_group_ids

MessageBus.publish("/new", message.as_json, group_ids: group_ids)
publish_read(topic.id, 1, topic.user_id)
end

def self.publish_unread(post)
# TODO at high scale we are going to have to defer this,
# perhaps cut down to users that are around in the last 7 days as well
#
group_ids = post.topic.category && post.topic.category.secure_group_ids

TopicUser
.tracking(post.topic_id)
.select([:user_id,:last_read_post_number])
.each do |tu|

message = {
topic_id: post.topic_id,
message_type: "unread",
payload: {
last_read_post_number: tu.last_read_post_number,
highest_post_number: post.post_number,
created_at: post.created_at,
topic_id: post.topic_id
}
}

MessageBus.publish("/unread/#{tu.user_id}", message.as_json, group_ids: group_ids)

end
end

def self.trigger_change(topic_id, post_number, user_id=nil)
MessageBus.publish(CHANNEL, "CHANGE", user_ids: [user_id].compact)
def self.publish_read(topic_id, highest_post_number, user_id)
end

def self.treat_as_new_topic_clause
Expand Down Expand Up @@ -76,7 +113,7 @@ def self.report(user_ids, topic_id = nil)
end

SqlBuilder.new(sql)
.map_exec(UserTrackingState, user_ids: user_ids, topic_id: topic_id)
.map_exec(TopicTrackingState, user_ids: user_ids, topic_id: topic_id)

end

Expand Down
6 changes: 6 additions & 0 deletions app/models/topic_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ class TopicUser < ActiveRecord::Base
scope :starred_since, lambda { |sinceDaysAgo| where('starred_at > ?', sinceDaysAgo.days.ago) }
scope :by_date_starred, group('date(starred_at)').order('date(starred_at)')

scope :tracking, lambda { |topic_id|
where(topic_id: topic_id)
.where("COALESCE(topic_users.notification_level, :regular) >= :tracking",
regular: TopicUser.notification_levels[:regular], tracking: TopicUser.notification_levels[:tracking])
}

# Class methods
class << self

Expand Down
3 changes: 0 additions & 3 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -576,9 +576,6 @@ def flag_linked_posts_as_spam
end
end

def user_tracking_states
UserTrackingState.report([self.id])
end

protected

Expand Down
4 changes: 0 additions & 4 deletions app/serializers/current_user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ class CurrentUserSerializer < BasicUserSerializer
:external_links_in_new_tab,
:trust_level

has_many :user_tracking_states, serializer: UserTrackingStateSerializer, embed: :objects

# we probably want to move this into site, but that json is cached so hanging it off current user seems okish

def include_site_flagged_posts_count?
object.staff?
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
class UserTrackingStateSerializer < ApplicationSerializer
class TopicTrackingStateSerializer < ApplicationSerializer
attributes :topic_id, :highest_post_number, :last_read_post_number, :created_at, :category_name
end
Loading

0 comments on commit e93b7a3

Please sign in to comment.