Skip to content

Commit

Permalink
topics: Add topics locking.
Browse files Browse the repository at this point in the history
Closes zulip#8041.
  • Loading branch information
graszka22 committed Mar 20, 2018
1 parent 679941b commit 5d0cd09
Show file tree
Hide file tree
Showing 32 changed files with 723 additions and 15 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@
"hotspots": false,
"compose_ui": false,
"common": false,
"panels": false
"panels": false,
"desktop_notifications_panel": false,
"locking": false,
"locking_ui": false
},
"plugins": [
"eslint-plugin-empty-returns"
Expand Down
23 changes: 23 additions & 0 deletions frontend_tests/node_tests/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ set_global('notifications', {
clear_compose_notifications: noop,
});

set_global('locking', {
is_topic_locked: function () { return false; },
});

// Setting these up so that we can test that links to uploads within messages are
// automatically converted to server relative links.
global.document.location.protocol = 'https:';
Expand Down Expand Up @@ -224,6 +228,25 @@ people.add(bob);
$("#subject").select(noop);
assert(!compose.validate());
assert.equal($('#compose-error-msg').html(), i18n.t('Please specify a topic'));

global.with_overrides(function (override) {
global.with_stub(function (stub) {
override('locking.is_topic_locked', stub.f);
compose_state.subject('Denmark3');
$("#subject").select(noop);
assert(!compose.validate());
assert.equal($('#compose-error-msg').html(), i18n.t('The topic is locked.'));
var args = stub.get_args('stream', 'topic');
var valid_args = {
stream: 'Denmark',
topic: 'Denmark3',
};
assert.deepEqual(args, valid_args);
});
});
set_global('locking', {
is_topic_locked: function () { return false; },
});
}());

(function test_get_invalid_recipient_emails() {
Expand Down
17 changes: 17 additions & 0 deletions frontend_tests/node_tests/dispatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ var event_fixtures = {
hotspots: ['nice', 'chicken'],
},

locked_topics: {
type: 'locked_topics',
locked_topics: [['devel', 'js'], ['lunch', 'burritos']],
},

muted_topics: {
type: 'muted_topics',
muted_topics: [['devel', 'js'], ['lunch', 'burritos']],
Expand Down Expand Up @@ -515,6 +520,18 @@ with_overrides(function (override) {
assert_same(page_params.hotspots, event.hotspots);
});

with_overrides(function (override) {
// locked_topics
var event = event_fixtures.locked_topics;

global.with_stub(function (stub) {
override('locking_ui.handle_updates', stub.f);
dispatch(event);
var args = stub.get_args('locked_topics');
assert_same(args.locked_topics, event.locked_topics);
});
});

with_overrides(function (override) {
// muted_topics
var event = event_fixtures.muted_topics;
Expand Down
87 changes: 87 additions & 0 deletions frontend_tests/node_tests/locking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
zrequire('locking');

(function test_edge_cases() {
assert(!locking.is_topic_locked(undefined, undefined));
assert(!locking.is_topic_locked('nonexistent', undefined));
}());

(function test_basics() {
assert(!locking.is_topic_locked('devel', 'java'));
locking.add_locked_topic('devel', 'java');
assert(locking.is_topic_locked('devel', 'java'));

// test idempotentcy
locking.add_locked_topic('devel', 'java');
assert(locking.is_topic_locked('devel', 'java'));

locking.remove_locked_topic('devel', 'java');
assert(!locking.is_topic_locked('devel', 'java'));

// test idempotentcy
locking.remove_locked_topic('devel', 'java');
assert(!locking.is_topic_locked('devel', 'java'));

// test unknown stream is harmless too
locking.remove_locked_topic('unknown', 'java');
assert(!locking.is_topic_locked('unknown', 'java'));
}());

(function test_set_locked_topics() {
locking.set_locked_topics([
['social', 'breakfast'],
['design', 'typography'],
]);
assert(locking.is_topic_locked('social', 'breakfast'));
assert(locking.is_topic_locked('design', 'typography'));
locking.set_locked_topics([
['devel', 'java'],
]);
assert(!locking.is_topic_locked('social', 'breakfast'));
assert(!locking.is_topic_locked('design', 'typography'));
assert(locking.is_topic_locked('devel', 'java'));
}());

(function test_case_insensitivity() {
locking.set_locked_topics([]);
assert(!locking.is_topic_locked('SOCial', 'breakfast'));
locking.set_locked_topics([
['SOCial', 'breakfast'],
]);
assert(locking.is_topic_locked('SOCial', 'breakfast'));
assert(locking.is_topic_locked('social', 'breakfast'));
assert(locking.is_topic_locked('social', 'breakFAST'));
}());

set_global('page_params', {
is_admin: false,
});

(function test_can_lock_topic_no_admin() {
locking.set_locked_topics([]);
assert(!locking.can_lock_topic('social', 'breakfast'));
assert(!locking.can_unlock_topic('social', 'breakfast'));
assert(!locking.can_lock_topic(undefined, 'breakfast'));
assert(!locking.can_lock_topic('social', undefined));
assert(!locking.can_unlock_topic(undefined, 'breakfast'));
assert(!locking.can_unlock_topic('social', undefined));
locking.add_locked_topic('social', 'breakfast');
assert(!locking.can_lock_topic('social', 'breakfast'));
assert(!locking.can_unlock_topic('social', 'breakfast'));
}());

set_global('page_params', {
is_admin: true,
});

(function test_can_lock_topic_admin() {
locking.set_locked_topics([]);
assert(locking.can_lock_topic('social', 'breakfast'));
assert(!locking.can_unlock_topic('social', 'breakfast'));
assert(!locking.can_lock_topic(undefined, 'breakfast'));
assert(!locking.can_lock_topic('social', undefined));
assert(!locking.can_unlock_topic(undefined, 'breakfast'));
assert(!locking.can_unlock_topic('social', undefined));
locking.add_locked_topic('social', 'breakfast');
assert(!locking.can_lock_topic('social', 'breakfast'));
assert(locking.can_unlock_topic('social', 'breakfast'));
}());
54 changes: 54 additions & 0 deletions frontend_tests/node_tests/message_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ var MessageList = zrequire('message_list').MessageList;

set_global('i18n', global.stub_i18n);
set_global('feature_flags', {});
set_global('locking', {
is_topic_locked: function () { return false; },
});

var with_overrides = global.with_overrides; // make lint happy

Expand Down Expand Up @@ -349,6 +352,57 @@ var with_overrides = global.with_overrides; // make lint happy
});
}());

(function test_locked_topic_bookend() {
var table;
var filter = {};

var list = new MessageList(table, filter);

with_overrides(function (override) {
var expected = "translated: The topic is locked";
list.view.clear_locked_bookend = noop;
list.narrowed = true;

override("narrow_state.stream", function () {
return "social";
});

override("narrow_state.topic", function () {
return "breakfast";
});

global.with_stub(function (render_stub) {
global.with_stub(function (is_locked_stub) {
list.view.render_locked_bookend = render_stub.f;
override('locking.is_topic_locked', is_locked_stub.f);
list.update_locked_bookend();
var bookend = render_stub.get_args('content');
assert.equal(bookend.content, expected);
var is_locked_args = is_locked_stub.get_args('stream', 'topic');
var valid_args = {
stream: 'social',
topic: 'breakfast',
};
assert.deepEqual(is_locked_args, valid_args);
});
});

set_global('locking', {
is_topic_locked: function () { return false; },
});

global.with_stub(function (render_stub) {
global.with_stub(function (clear_stub) {
list.view.render_trailing_bookend = render_stub.f;
list.view.clear_locked_bookend = clear_stub.f;
list.update_locked_bookend();
assert(render_stub.num_calls === 0);
render_stub.num_calls = 1; // it's checked later in with_sub
});
});
});
}());

(function test_unmuted_messages() {
var table;
var filter = {};
Expand Down
11 changes: 11 additions & 0 deletions frontend_tests/node_tests/topic_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set_global('narrow_state', {});
set_global('stream_data', {});
set_global('unread', {});
set_global('muting', {});
set_global('locking', {});
set_global('stream_popover', {});
set_global('templates', {});

Expand Down Expand Up @@ -42,6 +43,7 @@ zrequire('topic_list');
};

var checked_mutes;
var checked_locked;
var rendered;

templates.render = function (name, info) {
Expand All @@ -51,6 +53,7 @@ zrequire('topic_list');
unread: 3,
is_zero: false,
is_muted: false,
is_locked: false,
url: '#narrow/stream/devel/subject/coding',
};
assert.deepEqual(info, expected);
Expand All @@ -65,6 +68,13 @@ zrequire('topic_list');
return false;
};

locking.is_topic_locked = function (stream_name, topic_name) {
assert.equal(stream_name, 'devel');
assert.equal(topic_name, 'coding');
checked_locked = true;
return false;
};

var ul = $('<ul class="topic-list">');

var list_items = [];
Expand Down Expand Up @@ -95,6 +105,7 @@ zrequire('topic_list');
assert.equal(widget.get_parent(), parent_elem);

assert(checked_mutes);
assert(checked_locked);
assert(rendered);
assert.equal(list_items[0].html(), '<topic list item>');
assert.equal(list_items[1].html(), '<more topics>');
Expand Down
16 changes: 10 additions & 6 deletions static/js/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,12 +536,16 @@ function validate_stream_message() {
return false;
}

if (page_params.realm_mandatory_topics) {
var topic = compose_state.subject();
if (topic === "") {
compose_error(i18n.t("Please specify a topic"), $("#subject"));
return false;
}
var topic = compose_state.subject();

if (page_params.realm_mandatory_topics && topic === "") {
compose_error(i18n.t("Please specify a topic"), $("#subject"));
return false;
}

if (locking.is_topic_locked(stream_name, topic)) {
compose_error(i18n.t("The topic is locked."));
return false;
}

// If both `@all` is mentioned and it's in `#announce`, just validate
Expand Down
64 changes: 64 additions & 0 deletions static/js/locking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
var locking = (function () {

var exports = {};

var locked_topics = new Dict({fold_case: true});

exports.add_locked_topic = function (stream, topic) {
var sub_dict = locked_topics.get(stream);
if (!sub_dict) {
sub_dict = new Dict({fold_case: true});
locked_topics.set(stream, sub_dict);
}
sub_dict.set(topic, true);
};

exports.remove_locked_topic = function (stream, topic) {
var sub_dict = locked_topics.get(stream);
if (sub_dict) {
sub_dict.del(topic);
}
};

exports.set_locked_topics = function (tuples) {
locked_topics = new Dict({fold_case: true});

_.each(tuples, function (tuple) {
var stream = tuple[0];
var topic = tuple[1];

exports.add_locked_topic(stream, topic);
});
};

exports.can_lock_topic = function (stream, topic) {
if (stream === undefined || topic === undefined) {
return false;
}
if (!page_params.is_admin) {
return false;
}
var sub_dict = locked_topics.get(stream);
return !sub_dict || !sub_dict.get(topic);
};

exports.can_unlock_topic = function (stream, topic) {
if (!page_params.is_admin || stream === undefined || topic === undefined) {
return false;
}
return !exports.can_lock_topic(stream, topic);
};

exports.is_topic_locked = function (stream, topic) {
if (stream === undefined || topic === undefined) {
return false;
}
var sub_dict = locked_topics.get(stream);
return sub_dict && sub_dict.get(topic);
};

return exports;
}());
if (typeof module !== 'undefined') {
module.exports = locking;
}

0 comments on commit 5d0cd09

Please sign in to comment.