mirror of https://github.com/zulip/zulip.git
Extract presence.js to track presence info.
Most of this code was simply moved from activity.js with some minor renaming of functions like set_presence_info -> set_info. Some functions were slightly nontrivial extractions: is_not_offline: came from activity.huddle_fraction_present get_status/get_mobile: simple getters set_user_status: partial extraction from activity.set_user_status last_active_date: pulled out of admin.js code We also fixed activity.filter_and_sort to take user_ids.
This commit is contained in:
parent
0605a9fb0f
commit
2718bd0b5d
|
@ -98,6 +98,7 @@
|
|||
"floating_recipient_bar": false,
|
||||
"tab_bar": false,
|
||||
"emoji": false,
|
||||
"presence": false,
|
||||
"activity": false,
|
||||
"invite": false,
|
||||
"colorspace": false,
|
||||
|
|
|
@ -22,9 +22,14 @@ add_dependencies({
|
|||
hash_util: 'js/hash_util.js',
|
||||
hashchange: 'js/hashchange.js',
|
||||
narrow: 'js/narrow.js',
|
||||
presence: 'js/presence.js',
|
||||
activity: 'js/activity.js',
|
||||
});
|
||||
|
||||
var presence = global.presence;
|
||||
|
||||
var OFFLINE_THRESHOLD_SECS = 140;
|
||||
|
||||
set_global('resize', {
|
||||
resize_page_components: function () {},
|
||||
});
|
||||
|
@ -88,7 +93,7 @@ global.compile_template('user_presence_rows');
|
|||
presence_info[fred.user_id] = { status: 'active' };
|
||||
presence_info[jill.user_id] = { status: 'active' };
|
||||
|
||||
activity.presence_info = presence_info;
|
||||
presence.presence_info = presence_info;
|
||||
activity._sort_users(user_ids);
|
||||
|
||||
assert.deepEqual(user_ids, [
|
||||
|
@ -192,7 +197,7 @@ global.compile_template('user_presence_rows');
|
|||
presence_info[fred.user_id] = { status: 'idle' }; // counts as present
|
||||
// jill not in list
|
||||
presence_info[mark.user_id] = { status: 'offline' }; // does not count
|
||||
activity.presence_info = presence_info;
|
||||
presence.presence_info = presence_info;
|
||||
|
||||
assert.equal(
|
||||
activity.huddle_fraction_present(huddle),
|
||||
|
@ -201,54 +206,57 @@ global.compile_template('user_presence_rows');
|
|||
|
||||
|
||||
(function test_on_mobile_property() {
|
||||
// TODO: move this test to a new test module directly testing presence.js
|
||||
var status_from_timestamp = presence._status_from_timestamp;
|
||||
|
||||
var base_time = 500;
|
||||
var presence = {
|
||||
var info = {
|
||||
website: {
|
||||
status: "active",
|
||||
timestamp: base_time,
|
||||
},
|
||||
};
|
||||
var status = activity._status_from_timestamp(
|
||||
base_time + activity._OFFLINE_THRESHOLD_SECS - 1, presence);
|
||||
var status = status_from_timestamp(
|
||||
base_time + OFFLINE_THRESHOLD_SECS - 1, info);
|
||||
assert.equal(status.mobile, false);
|
||||
|
||||
presence.Android = {
|
||||
info.Android = {
|
||||
status: "active",
|
||||
timestamp: base_time + activity._OFFLINE_THRESHOLD_SECS / 2,
|
||||
timestamp: base_time + OFFLINE_THRESHOLD_SECS / 2,
|
||||
pushable: false,
|
||||
};
|
||||
status = activity._status_from_timestamp(
|
||||
base_time + activity._OFFLINE_THRESHOLD_SECS, presence);
|
||||
status = status_from_timestamp(
|
||||
base_time + OFFLINE_THRESHOLD_SECS, info);
|
||||
assert.equal(status.mobile, true);
|
||||
assert.equal(status.status, "active");
|
||||
|
||||
status = activity._status_from_timestamp(
|
||||
base_time + activity._OFFLINE_THRESHOLD_SECS - 1, presence);
|
||||
status = status_from_timestamp(
|
||||
base_time + OFFLINE_THRESHOLD_SECS - 1, info);
|
||||
assert.equal(status.mobile, false);
|
||||
assert.equal(status.status, "active");
|
||||
|
||||
status = activity._status_from_timestamp(
|
||||
base_time + activity._OFFLINE_THRESHOLD_SECS * 2, presence);
|
||||
status = status_from_timestamp(
|
||||
base_time + OFFLINE_THRESHOLD_SECS * 2, info);
|
||||
assert.equal(status.mobile, false);
|
||||
assert.equal(status.status, "offline");
|
||||
|
||||
presence.Android = {
|
||||
info.Android = {
|
||||
status: "idle",
|
||||
timestamp: base_time + activity._OFFLINE_THRESHOLD_SECS / 2,
|
||||
timestamp: base_time + OFFLINE_THRESHOLD_SECS / 2,
|
||||
pushable: true,
|
||||
};
|
||||
status = activity._status_from_timestamp(
|
||||
base_time + activity._OFFLINE_THRESHOLD_SECS, presence);
|
||||
status = status_from_timestamp(
|
||||
base_time + OFFLINE_THRESHOLD_SECS, info);
|
||||
assert.equal(status.mobile, true);
|
||||
assert.equal(status.status, "idle");
|
||||
|
||||
status = activity._status_from_timestamp(
|
||||
base_time + activity._OFFLINE_THRESHOLD_SECS - 1, presence);
|
||||
status = status_from_timestamp(
|
||||
base_time + OFFLINE_THRESHOLD_SECS - 1, info);
|
||||
assert.equal(status.mobile, false);
|
||||
assert.equal(status.status, "active");
|
||||
|
||||
status = activity._status_from_timestamp(
|
||||
base_time + activity._OFFLINE_THRESHOLD_SECS * 2, presence);
|
||||
status = status_from_timestamp(
|
||||
base_time + OFFLINE_THRESHOLD_SECS * 2, info);
|
||||
assert.equal(status.mobile, true);
|
||||
assert.equal(status.status, "offline");
|
||||
|
||||
|
@ -272,23 +280,23 @@ global.compile_template('user_presence_rows');
|
|||
},
|
||||
};
|
||||
|
||||
activity.set_presence_info(presences, base_time);
|
||||
presence.set_info(presences, base_time);
|
||||
|
||||
assert.deepEqual(activity.presence_info[alice.user_id],
|
||||
assert.deepEqual(presence.presence_info[alice.user_id],
|
||||
{ status: 'active', mobile: false, last_active: 500}
|
||||
);
|
||||
|
||||
assert.deepEqual(activity.presence_info[fred.user_id],
|
||||
assert.deepEqual(presence.presence_info[fred.user_id],
|
||||
{ status: 'idle', mobile: false, last_active: 500}
|
||||
);
|
||||
}());
|
||||
|
||||
activity.presence_info = {};
|
||||
activity.presence_info[alice.user_id] = { status: activity.IDLE };
|
||||
activity.presence_info[fred.user_id] = { status: activity.ACTIVE };
|
||||
activity.presence_info[jill.user_id] = { status: activity.ACTIVE };
|
||||
activity.presence_info[mark.user_id] = { status: activity.IDLE };
|
||||
activity.presence_info[norbert.user_id] = { status: activity.ACTIVE };
|
||||
presence.presence_info = {};
|
||||
presence.presence_info[alice.user_id] = { status: activity.IDLE };
|
||||
presence.presence_info[fred.user_id] = { status: activity.ACTIVE };
|
||||
presence.presence_info[jill.user_id] = { status: activity.ACTIVE };
|
||||
presence.presence_info[mark.user_id] = { status: activity.IDLE };
|
||||
presence.presence_info[norbert.user_id] = { status: activity.ACTIVE };
|
||||
|
||||
(function test_presence_list_full_update() {
|
||||
global.$ = function () {
|
||||
|
|
|
@ -10,20 +10,6 @@ var DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|||
/* Time between keep-alive pings */
|
||||
var ACTIVE_PING_INTERVAL_MS = 50 * 1000;
|
||||
|
||||
/* Mark users as offline after 140 seconds since their last checkin,
|
||||
* Keep in sync with zerver/tornado/event_queue.py:receiver_is_idle
|
||||
*/
|
||||
var OFFLINE_THRESHOLD_SECS = 140;
|
||||
|
||||
// Testing
|
||||
exports._OFFLINE_THRESHOLD_SECS = OFFLINE_THRESHOLD_SECS;
|
||||
|
||||
var MOBILE_DEVICES = ["Android", "ZulipiOS", "ios"];
|
||||
|
||||
function is_mobile(device) {
|
||||
return MOBILE_DEVICES.indexOf(device) !== -1;
|
||||
}
|
||||
|
||||
var presence_descriptions = {
|
||||
active: 'is active',
|
||||
idle: 'is not active',
|
||||
|
@ -47,8 +33,6 @@ $("html").on("mousemove", function () {
|
|||
exports.new_user_input = true;
|
||||
});
|
||||
|
||||
exports.presence_info = {};
|
||||
|
||||
var huddle_timestamps = new Dict();
|
||||
|
||||
function update_count_in_dom(count_span, value_span, count) {
|
||||
|
@ -165,17 +149,13 @@ exports.short_huddle_name = function (huddle) {
|
|||
};
|
||||
|
||||
exports.huddle_fraction_present = function (huddle) {
|
||||
var presence_info = exports.presence_info;
|
||||
var user_ids = huddle.split(',');
|
||||
|
||||
var num_present = 0;
|
||||
_.each(user_ids, function (user_id) {
|
||||
if (presence_info[user_id]) {
|
||||
var status = presence_info[user_id].status;
|
||||
if (status && (status !== 'offline')) {
|
||||
if (presence.is_not_offline(user_id)) {
|
||||
num_present += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var ratio = num_present / user_ids.length;
|
||||
|
@ -184,7 +164,6 @@ exports.huddle_fraction_present = function (huddle) {
|
|||
};
|
||||
|
||||
function compare_function(a, b) {
|
||||
var presence_info = exports.presence_info;
|
||||
|
||||
function level(status) {
|
||||
switch (status) {
|
||||
|
@ -197,8 +176,8 @@ function compare_function(a, b) {
|
|||
}
|
||||
}
|
||||
|
||||
var level_a = level(presence_info[a].status);
|
||||
var level_b = level(presence_info[b].status);
|
||||
var level_a = level(presence.get_status(a));
|
||||
var level_b = level(presence.get_status(b));
|
||||
var diff = level_a - level_b;
|
||||
if (diff !== 0) {
|
||||
return diff;
|
||||
|
@ -264,8 +243,7 @@ function matches_filter(user_id) {
|
|||
return (filter_user_ids([user_id]).length === 1);
|
||||
}
|
||||
|
||||
function filter_and_sort(users) {
|
||||
var user_ids = Object.keys(users);
|
||||
function filter_and_sort(user_ids) {
|
||||
user_ids = filter_user_ids(user_ids);
|
||||
user_ids = sort_users(user_ids);
|
||||
return user_ids;
|
||||
|
@ -281,16 +259,16 @@ function get_num_unread(user_id) {
|
|||
}
|
||||
|
||||
function info_for(user_id) {
|
||||
var presence = exports.presence_info[user_id].status;
|
||||
var status = presence.get_status(user_id);
|
||||
var person = people.get_person_from_user_id(user_id);
|
||||
return {
|
||||
href: narrow.pm_with_uri(person.email),
|
||||
name: person.full_name,
|
||||
user_id: user_id,
|
||||
num_unread: get_num_unread(user_id),
|
||||
type: presence,
|
||||
type_desc: presence_descriptions[presence],
|
||||
mobile: exports.presence_info[user_id].mobile,
|
||||
type: status,
|
||||
type_desc: presence_descriptions[status],
|
||||
mobile: presence.get_mobile(user_id),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -335,10 +313,9 @@ exports.build_user_sidebar = function () {
|
|||
return;
|
||||
}
|
||||
|
||||
var users = exports.presence_info;
|
||||
users = filter_and_sort(users);
|
||||
var user_ids = filter_and_sort(presence.get_user_ids());
|
||||
|
||||
var user_info = _.map(users, info_for);
|
||||
var user_info = _.map(user_ids, info_for);
|
||||
var html = templates.render('user_presence_rows', {users: user_info});
|
||||
$('#user_presences').html(html);
|
||||
|
||||
|
@ -398,51 +375,6 @@ exports.update_huddles = function () {
|
|||
show_huddles();
|
||||
};
|
||||
|
||||
function status_from_timestamp(baseline_time, presence) {
|
||||
var status = 'offline';
|
||||
var last_active = 0;
|
||||
var mobileAvailable = false;
|
||||
var nonmobileAvailable = false;
|
||||
_.each(presence, function (device_presence, device) {
|
||||
var age = baseline_time - device_presence.timestamp;
|
||||
if (last_active < device_presence.timestamp) {
|
||||
last_active = device_presence.timestamp;
|
||||
}
|
||||
if (is_mobile(device)) {
|
||||
mobileAvailable = device_presence.pushable || mobileAvailable;
|
||||
}
|
||||
if (age < OFFLINE_THRESHOLD_SECS) {
|
||||
switch (device_presence.status) {
|
||||
case 'active':
|
||||
if (is_mobile(device)) {
|
||||
mobileAvailable = true;
|
||||
} else {
|
||||
nonmobileAvailable = true;
|
||||
}
|
||||
status = device_presence.status;
|
||||
break;
|
||||
case 'idle':
|
||||
if (status !== 'active') {
|
||||
status = device_presence.status;
|
||||
}
|
||||
break;
|
||||
case 'offline':
|
||||
if (status !== 'active' && status !== 'idle') {
|
||||
status = device_presence.status;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
blueslip.error('Unexpected status', {presence_object: device_presence, device: device}, undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
return {status: status,
|
||||
mobile: !nonmobileAvailable && mobileAvailable,
|
||||
last_active: last_active };
|
||||
}
|
||||
|
||||
// For testing
|
||||
exports._status_from_timestamp = status_from_timestamp;
|
||||
|
||||
function focus_ping(want_redraw) {
|
||||
channel.post({
|
||||
|
@ -465,7 +397,7 @@ function focus_ping(want_redraw) {
|
|||
// not send us any presences data. But avoiding the redraw
|
||||
// helps.
|
||||
if (want_redraw) {
|
||||
exports.set_presence_info(data.presences, data.server_timestamp);
|
||||
presence.set_info(data.presences, data.server_timestamp);
|
||||
exports.build_user_sidebar();
|
||||
exports.update_huddles();
|
||||
}
|
||||
|
@ -487,7 +419,7 @@ exports.initialize = function () {
|
|||
onActive: focus_gained,
|
||||
keepTracking: true});
|
||||
|
||||
activity.set_presence_info(page_params.initial_presences,
|
||||
presence.set_info(page_params.initial_presences,
|
||||
page_params.initial_servertime);
|
||||
exports.build_user_sidebar();
|
||||
exports.update_huddles();
|
||||
|
@ -503,36 +435,22 @@ exports.initialize = function () {
|
|||
setInterval(get_full_presence_list_update, ACTIVE_PING_INTERVAL_MS);
|
||||
};
|
||||
|
||||
exports.set_user_status = function (email, presence, server_time) {
|
||||
exports.set_user_status = function (email, info, server_time) {
|
||||
if (people.is_current_user(email)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var user_id = people.get_user_id(email);
|
||||
if (user_id) {
|
||||
var status = status_from_timestamp(server_time, presence);
|
||||
exports.presence_info[user_id] = status;
|
||||
exports.insert_user_into_list(user_id);
|
||||
} else {
|
||||
if (!user_id) {
|
||||
blueslip.warn('unknown email: ' + email);
|
||||
return;
|
||||
}
|
||||
|
||||
presence.set_user_status(user_id, info, server_time);
|
||||
exports.insert_user_into_list(user_id);
|
||||
exports.update_huddles();
|
||||
};
|
||||
|
||||
exports.set_presence_info = function (presences, server_timestamp) {
|
||||
exports.presence_info = {};
|
||||
_.each(presences, function (presence, this_email) {
|
||||
if (!people.is_current_user(this_email)) {
|
||||
var user_id = people.get_user_id(this_email);
|
||||
if (user_id) {
|
||||
var status = status_from_timestamp(server_timestamp,
|
||||
presence);
|
||||
exports.presence_info[user_id] = status;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.redraw = function () {
|
||||
exports.build_user_sidebar();
|
||||
exports.update_huddles();
|
||||
|
|
|
@ -139,13 +139,14 @@ function populate_users(realm_people_data) {
|
|||
var row = $(templates.render("admin_user_list", {user: user}));
|
||||
if (people.is_current_user(user.email)) {
|
||||
activity_rendered = timerender.render_date(new XDate());
|
||||
} else if (activity.presence_info[user.user_id]) {
|
||||
// XDate takes number of milliseconds since UTC epoch.
|
||||
var last_active = activity.presence_info[user.user_id].last_active * 1000;
|
||||
activity_rendered = timerender.render_date(new XDate(last_active));
|
||||
} else {
|
||||
var last_active_date = presence.last_active_date(user.user_id);
|
||||
if (last_active_date) {
|
||||
activity_rendered = timerender.render_date(last_active_date);
|
||||
} else {
|
||||
activity_rendered = $("<span></span>").text(i18n.t("Never"));
|
||||
}
|
||||
}
|
||||
row.find(".last_active").append(activity_rendered);
|
||||
users_table.append(row);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
var presence = (function () {
|
||||
var exports = {};
|
||||
|
||||
// This module just manages data. See activity.js for
|
||||
// the UI of our buddy list.
|
||||
|
||||
exports.presence_info = {};
|
||||
|
||||
|
||||
/* Mark users as offline after 140 seconds since their last checkin,
|
||||
* Keep in sync with zerver/tornado/event_queue.py:receiver_is_idle
|
||||
*/
|
||||
var OFFLINE_THRESHOLD_SECS = 140;
|
||||
|
||||
var MOBILE_DEVICES = ["Android", "ZulipiOS", "ios"];
|
||||
|
||||
function is_mobile(device) {
|
||||
return MOBILE_DEVICES.indexOf(device) !== -1;
|
||||
}
|
||||
|
||||
exports.is_not_offline = function (user_id) {
|
||||
var presence_info = exports.presence_info;
|
||||
|
||||
if (presence_info[user_id]) {
|
||||
var status = presence_info[user_id].status;
|
||||
if (status && (status !== 'offline')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.get_status = function (user_id) {
|
||||
return exports.presence_info[user_id].status;
|
||||
};
|
||||
|
||||
exports.get_mobile = function (user_id) {
|
||||
return exports.presence_info[user_id].mobile;
|
||||
};
|
||||
|
||||
exports.get_user_ids = function () {
|
||||
var user_ids = Object.keys(exports.presence_info);
|
||||
return user_ids;
|
||||
};
|
||||
|
||||
function status_from_timestamp(baseline_time, info) {
|
||||
var status = 'offline';
|
||||
var last_active = 0;
|
||||
var mobileAvailable = false;
|
||||
var nonmobileAvailable = false;
|
||||
_.each(info, function (device_presence, device) {
|
||||
var age = baseline_time - device_presence.timestamp;
|
||||
if (last_active < device_presence.timestamp) {
|
||||
last_active = device_presence.timestamp;
|
||||
}
|
||||
if (is_mobile(device)) {
|
||||
mobileAvailable = device_presence.pushable || mobileAvailable;
|
||||
}
|
||||
if (age < OFFLINE_THRESHOLD_SECS) {
|
||||
switch (device_presence.status) {
|
||||
case 'active':
|
||||
if (is_mobile(device)) {
|
||||
mobileAvailable = true;
|
||||
} else {
|
||||
nonmobileAvailable = true;
|
||||
}
|
||||
status = device_presence.status;
|
||||
break;
|
||||
case 'idle':
|
||||
if (status !== 'active') {
|
||||
status = device_presence.status;
|
||||
}
|
||||
break;
|
||||
case 'offline':
|
||||
if (status !== 'active' && status !== 'idle') {
|
||||
status = device_presence.status;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
blueslip.error('Unexpected status', {presence_object: device_presence, device: device}, undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
return {status: status,
|
||||
mobile: !nonmobileAvailable && mobileAvailable,
|
||||
last_active: last_active };
|
||||
}
|
||||
|
||||
// For testing
|
||||
exports._status_from_timestamp = status_from_timestamp;
|
||||
|
||||
exports.set_user_status = function (user_id, info, server_time) {
|
||||
var status = status_from_timestamp(server_time, info);
|
||||
exports.presence_info[user_id] = status;
|
||||
};
|
||||
|
||||
exports.set_info = function (presences, server_timestamp) {
|
||||
exports.presence_info = {};
|
||||
_.each(presences, function (info, this_email) {
|
||||
if (!people.is_current_user(this_email)) {
|
||||
var user_id = people.get_user_id(this_email);
|
||||
if (user_id) {
|
||||
var status = status_from_timestamp(server_timestamp,
|
||||
info);
|
||||
exports.presence_info[user_id] = status;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.last_active_date = function (user_id) {
|
||||
var info = exports.presence_info[user_id];
|
||||
|
||||
if (!info || !info.last_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
var date = new XDate(info.last_active * 1000);
|
||||
return date;
|
||||
};
|
||||
|
||||
return exports;
|
||||
|
||||
}());
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = presence;
|
||||
}
|
|
@ -677,7 +677,7 @@ def receiver_is_idle(user_profile_id, realm_presences):
|
|||
idle = True
|
||||
else:
|
||||
active_datetime = timestamp_to_datetime(latest_active_timestamp)
|
||||
# 140 seconds is consistent with activity.js:OFFLINE_THRESHOLD_SECS
|
||||
# 140 seconds is consistent with presence.js:OFFLINE_THRESHOLD_SECS
|
||||
idle = timezone.now() - active_datetime > datetime.timedelta(seconds=140)
|
||||
|
||||
return off_zulip or idle
|
||||
|
|
|
@ -893,6 +893,7 @@ JS_SPECS = {
|
|||
'js/message_fetch.js',
|
||||
'js/server_events.js',
|
||||
'js/zulip.js',
|
||||
'js/presence.js',
|
||||
'js/activity.js',
|
||||
'js/user_events.js',
|
||||
'js/colorspace.js',
|
||||
|
|
Loading…
Reference in New Issue