mirror of
https://github.com/zulip/zulip.git
synced 2025-11-09 00:18:12 +00:00
@@ -137,6 +137,52 @@ casper.waitWhileSelector('.emoji_row', function () {
|
||||
casper.test.assertDoesntExist('.emoji_row');
|
||||
});
|
||||
|
||||
function get_suggestions(str) {
|
||||
casper.then(function () {
|
||||
casper.evaluate(function (str) {
|
||||
$('.create_default_stream')
|
||||
.focus()
|
||||
.val(str)
|
||||
.trigger($.Event('keyup', { which: 0 }));
|
||||
}, str);
|
||||
});
|
||||
}
|
||||
|
||||
function select_from_suggestions(item) {
|
||||
casper.then(function () {
|
||||
casper.evaluate(function (item) {
|
||||
var tah = $('.create_default_stream').data().typeahead;
|
||||
tah.mouseenter({
|
||||
currentTarget: $('.typeahead:visible li:contains("'+item+'")')[0]
|
||||
});
|
||||
tah.select();
|
||||
}, {item: item});
|
||||
});
|
||||
}
|
||||
|
||||
// Test default stream creation and addition
|
||||
casper.then(function () {
|
||||
casper.click('#settings-dropdown');
|
||||
casper.click('a[href^="#subscriptions"]');
|
||||
casper.click('#settings-dropdown');
|
||||
casper.click('a[href^="#administration"]');
|
||||
var stream_name = "Scotland";
|
||||
// It matches with all the stream names which has 'O' as a substring (Rome, Scotland, Verona etc).
|
||||
// I used 'O' to make sure that it works even if there are multiple suggestions.
|
||||
// Capital 'O' is used instead of small 'o' to make sure that the suggestions are not case sensitive.
|
||||
get_suggestions("O");
|
||||
select_from_suggestions(stream_name);
|
||||
casper.waitForSelector('.default_stream_row[id='+stream_name+']', function () {
|
||||
casper.test.assertSelectorHasText('.default_stream_row[id='+stream_name+'] .default_stream_name', stream_name);
|
||||
});
|
||||
casper.waitForSelector('.default_stream_row[id='+stream_name+']', function () {
|
||||
casper.test.assertSelectorHasText('.default_stream_row[id='+stream_name+'] .default_stream_name', stream_name);
|
||||
casper.click('.default_stream_row[id='+stream_name+'] button.remove-default-stream');
|
||||
});
|
||||
casper.waitWhileSelector('.default_stream_row[id='+stream_name+']', function () {
|
||||
casper.test.assertDoesntExist('.default_stream_row[id='+stream_name+']');
|
||||
});
|
||||
});
|
||||
// TODO: Test stream deletion
|
||||
|
||||
common.then_log_out();
|
||||
|
||||
@@ -86,6 +86,19 @@ function render(template_name, args) {
|
||||
global.write_test_output("admin_tab.handlebars", html);
|
||||
}());
|
||||
|
||||
(function admin_default_streams_list() {
|
||||
var html = '<table>';
|
||||
var streams = ['devel', 'trac', 'zulip'];
|
||||
_.each(streams, function (stream) {
|
||||
var args = {stream: {name: stream, invite_only: false}};
|
||||
html += render('admin_default_streams_list', args);
|
||||
});
|
||||
html += "</table>";
|
||||
var span = $(html).find(".default_stream_name:first");
|
||||
assert.equal(span.text(), "devel");
|
||||
global.write_test_output("admin_default_streams_list.handlebars", html);
|
||||
}());
|
||||
|
||||
(function admin_streams_list() {
|
||||
var html = '<table>';
|
||||
var streams = ['devel', 'trac', 'zulip'];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var admin = (function () {
|
||||
|
||||
var exports = {};
|
||||
var all_streams = [];
|
||||
|
||||
exports.show_or_hide_menu_item = function () {
|
||||
var item = $('.admin-menu-item').expectOne();
|
||||
@@ -67,6 +68,7 @@ function populate_users (realm_people_data) {
|
||||
|
||||
function populate_streams (streams_data) {
|
||||
var streams_table = $("#admin_streams_table").expectOne();
|
||||
all_streams = streams_data;
|
||||
streams_table.find("tr.stream_row").remove();
|
||||
_.each(streams_data.streams, function (stream) {
|
||||
streams_table.append(templates.render("admin_streams_list", {stream: stream}));
|
||||
@@ -74,6 +76,55 @@ function populate_streams (streams_data) {
|
||||
loading.destroy_indicator($('#admin_page_streams_loading_indicator'));
|
||||
}
|
||||
|
||||
function populate_default_streams(streams_data) {
|
||||
var default_streams_table = $("#admin_default_streams_table").expectOne();
|
||||
_.each(streams_data, function (stream) {
|
||||
default_streams_table.append(templates.render("admin_default_streams_list", {stream: stream}));
|
||||
});
|
||||
loading.destroy_indicator($('#admin_page_default_streams_loading_indicator'));
|
||||
}
|
||||
|
||||
function get_non_default_streams_names(streams_data) {
|
||||
var non_default_streams_names = [];
|
||||
var default_streams_names = [];
|
||||
|
||||
_.each(page_params.realm_default_streams, function (default_stream) {
|
||||
default_streams_names.push(default_stream.name);
|
||||
});
|
||||
|
||||
_.each(streams_data.streams, function (stream) {
|
||||
if (default_streams_names.indexOf(stream.name) < 0) {
|
||||
non_default_streams_names.push(stream.name);
|
||||
}
|
||||
});
|
||||
return non_default_streams_names;
|
||||
}
|
||||
|
||||
exports.update_default_streams_table = function () {
|
||||
$("#admin_default_streams_table").expectOne().find("tr.default_stream_row").remove();
|
||||
populate_default_streams(page_params.realm_default_streams);
|
||||
};
|
||||
|
||||
function make_stream_default(stream_name) {
|
||||
var data = {
|
||||
stream_name: stream_name
|
||||
};
|
||||
var default_streams_table = $("#admin_default_streams_table").expectOne();
|
||||
|
||||
channel.put({
|
||||
url: '/json/default_streams',
|
||||
data: data,
|
||||
error: function (xhr, error_type) {
|
||||
if (xhr.status.toString().charAt(0) === "4") {
|
||||
$(".active_stream_row button").closest("td").html(
|
||||
$("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg));
|
||||
} else {
|
||||
$(".active_stream_row button").text("Failed!");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.populate_emoji = function (emoji_data) {
|
||||
var emoji_table = $('#admin_emoji_table').expectOne();
|
||||
emoji_table.find('tr.emoji_row').remove();
|
||||
@@ -123,7 +174,7 @@ exports.setup_page = function () {
|
||||
|
||||
// Populate streams table
|
||||
channel.get({
|
||||
url: '/json/streams?include_public=true&include_subscribed=true',
|
||||
url: '/json/streams?include_public=true&include_subscribed=true&include_default=true',
|
||||
timeout: 10*1000,
|
||||
idempotent: true,
|
||||
success: populate_streams,
|
||||
@@ -132,6 +183,7 @@ exports.setup_page = function () {
|
||||
|
||||
// Populate emoji table
|
||||
exports.populate_emoji(page_params.realm_emoji);
|
||||
exports.update_default_streams_table();
|
||||
|
||||
// Setup click handlers
|
||||
$(".admin_user_table").on("click", ".deactivate", function (e) {
|
||||
@@ -164,6 +216,51 @@ exports.setup_page = function () {
|
||||
$("#deactivation_stream_modal").modal("show");
|
||||
});
|
||||
|
||||
$(".admin_default_stream_table").on("click", ".remove-default-stream", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
$(".active_default_stream_row").removeClass("active_default_stream_row");
|
||||
var row = $(e.target).closest(".default_stream_row");
|
||||
row.addClass("active_default_stream_row");
|
||||
var stream_name = row.find('.default_stream_name').text();
|
||||
|
||||
channel.del({
|
||||
url: '/json/default_streams'+ '?' + $.param({"stream_name": stream_name}),
|
||||
error: function (xhr, error_type) {
|
||||
if (xhr.status.toString().charAt(0) === "4") {
|
||||
$(".active_default_stream_row button").closest("td").html(
|
||||
$("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg));
|
||||
} else {
|
||||
$(".active_default_stream_row button").text("Failed!");
|
||||
}
|
||||
},
|
||||
success: function () {
|
||||
var row = $(".active_default_stream_row");
|
||||
row.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.create_default_stream').keypress(function (e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
$('.create_default_stream').typeahead({
|
||||
items: 5,
|
||||
fixed: true,
|
||||
source: function (query) {
|
||||
return get_non_default_streams_names(all_streams);
|
||||
},
|
||||
highlight: true,
|
||||
updater: function (stream_name) {
|
||||
make_stream_default(stream_name);
|
||||
}
|
||||
});
|
||||
|
||||
$(".admin_bot_table").on("click", ".deactivate", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -119,8 +119,13 @@ function get_events_success(events) {
|
||||
if (event.op === 'update') {
|
||||
// Legacy: Stream properties are still managed by subs.js on the client side.
|
||||
subs.update_subscription_properties(event.name, event.property, event.value);
|
||||
admin.update_default_streams_table();
|
||||
}
|
||||
break;
|
||||
case 'default_streams':
|
||||
page_params.realm_default_streams = event.default_streams;
|
||||
admin.update_default_streams_table();
|
||||
break;
|
||||
case 'subscription':
|
||||
if (event.op === 'add') {
|
||||
_.each(event.subscriptions, function (sub) {
|
||||
|
||||
@@ -3246,7 +3246,8 @@ div.edit_bot {
|
||||
|
||||
.edit_bot_form .control-label,
|
||||
#create_bot_form .control-label,
|
||||
.admin-emoji-form .control-label {
|
||||
.admin-emoji-form .control-label,
|
||||
.default-stream-form .control-label {
|
||||
width: 10em;
|
||||
text-align: right;
|
||||
margin-right: 20px;
|
||||
@@ -3501,7 +3502,8 @@ div.edit_bot {
|
||||
|
||||
#settings .bot-information-box,
|
||||
#settings .add-new-bot-box,
|
||||
#emoji-settings .add-new-emoji-box {
|
||||
#emoji-settings .add-new-emoji-box,
|
||||
#admin-default-streams-list .add-new-default-stream-box{
|
||||
background: #e3e3e3;
|
||||
padding: 10px;
|
||||
margin-left: 38px;
|
||||
@@ -3513,10 +3515,17 @@ div.edit_bot {
|
||||
}
|
||||
|
||||
#settings .add-new-bot-box,
|
||||
#emoji-settings .add-new-emoji-box {
|
||||
#emoji-settings .add-new-emoji-box,
|
||||
#admin-default-streams-list .add-new-default-stream-box {
|
||||
background: #cbe3cb;
|
||||
}
|
||||
|
||||
.new-default-stream-section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#settings #get_api_key_box,
|
||||
#settings #show_api_key_box,
|
||||
#settings #api_key_button_box .control-group {
|
||||
|
||||
13
static/templates/admin_default_streams_list.handlebars
Normal file
13
static/templates/admin_default_streams_list.handlebars
Normal file
@@ -0,0 +1,13 @@
|
||||
{{#with stream}}
|
||||
<tr class="default_stream_row" id="{{name}}">
|
||||
<td>
|
||||
{{#if invite_only}}<i class="icon-vector-lock "></i>{{/if}}
|
||||
<span class="default_stream_name">{{name}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn remove-default-stream btn-danger">
|
||||
{{t "Remove from default" }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{/with}}
|
||||
@@ -19,6 +19,9 @@
|
||||
<li role="presentation">
|
||||
<a href="#streams" aria-controls="streams" role="tab" data-toggle="tab"><i class="icon-vector-exchange settings-section-icon"></i> {{t "Streams Deletion" }}</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#default-streams" aria-controls="default-streams" role="tab" data-toggle="tab"><i class="icon-vector-exchange settings-section-icon"></i> {{t "Default Streams" }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="organization">
|
||||
@@ -163,6 +166,31 @@
|
||||
<div id="admin_page_streams_loading_indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="default-streams">
|
||||
<div id="admin-default-streams-list" class="settings-section">
|
||||
<div class="settings-section-title"><i class="icon-vector-exchange settings-section-icon"></i>
|
||||
{{t "Default Streams"}}</div>
|
||||
<div class="admin-table-wrapper">
|
||||
<p>{{#tr this}}Configure the default streams new users are subscribed to when joining the {{domain}} organization.{{/tr}}</p>
|
||||
<table class="table table-condensed table-striped">
|
||||
<tbody id="admin_default_streams_table" class="admin_default_stream_table">
|
||||
<th>{{t "Name" }}</th>
|
||||
<th class="actions">{{t "Actions" }}</th>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="admin_page_default_streams_loading_indicator"></div>
|
||||
<form class="form-horizontal default-stream-form">
|
||||
<div class="add-new-default-stream-box">
|
||||
<div class="new-default-stream-section-title">{{t "Add New Default Stream" }}</div>
|
||||
<div class="control-group" id="default_stream_inputs">
|
||||
<label for="default_stream_name" class="control-label">{{t "Stream Name" }}</label>
|
||||
<input class="create_default_stream" type="text" placeholder="{{t "Stream Name" }}" name="stream_name" autocomplete="off"></input>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="deactivation_user_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="deactivation_user_modal_label" aria-hidden="true">
|
||||
|
||||
@@ -2025,11 +2025,21 @@ def set_default_streams(realm, stream_names):
|
||||
'domain': realm.domain,
|
||||
'streams': stream_names})
|
||||
|
||||
|
||||
def notify_default_streams(realm):
|
||||
# type: (Realm) -> None
|
||||
event = dict(
|
||||
type="default_streams",
|
||||
default_streams=streams_to_dicts_sorted(get_default_streams_for_realm(realm))
|
||||
)
|
||||
send_event(event, active_user_ids(realm))
|
||||
|
||||
def do_add_default_stream(realm, stream_name):
|
||||
# type: (Realm, text_type) -> None
|
||||
stream, _ = create_stream_if_needed(realm, stream_name)
|
||||
if not DefaultStream.objects.filter(realm=realm, stream=stream).exists():
|
||||
DefaultStream.objects.create(realm=realm, stream=stream)
|
||||
notify_default_streams(realm)
|
||||
|
||||
def do_remove_default_stream(realm, stream_name):
|
||||
# type: (Realm, text_type) -> None
|
||||
@@ -2037,6 +2047,7 @@ def do_remove_default_stream(realm, stream_name):
|
||||
if stream is None:
|
||||
raise JsonableError(_("Stream does not exist"))
|
||||
DefaultStream.objects.filter(realm=realm, stream=stream).delete()
|
||||
notify_default_streams(realm)
|
||||
|
||||
def get_default_streams_for_realm(realm):
|
||||
# type: (Realm) -> List[Stream]
|
||||
@@ -2049,6 +2060,11 @@ def get_default_subs(user_profile):
|
||||
# to some day further customize how we set up default streams for new users.
|
||||
return get_default_streams_for_realm(user_profile.realm)
|
||||
|
||||
# returns default streams in json serializeable format
|
||||
def streams_to_dicts_sorted(streams):
|
||||
# type: (List[Stream]) -> List[Dict[str, Any]]
|
||||
return sorted([stream.to_dict() for stream in streams], key=lambda elt: elt["name"])
|
||||
|
||||
def do_update_user_activity_interval(user_profile, log_time):
|
||||
# type: (UserProfile, datetime.datetime) -> None
|
||||
effective_end = log_time + datetime.timedelta(minutes=15)
|
||||
@@ -2687,6 +2703,8 @@ def fetch_initial_state_data(user_profile, event_types, queue_id):
|
||||
|
||||
if want('stream'):
|
||||
state['streams'] = do_get_streams(user_profile)
|
||||
if want('default_streams'):
|
||||
state['realm_default_streams'] = streams_to_dicts_sorted(get_default_streams_for_realm(user_profile.realm))
|
||||
|
||||
if want('update_display_settings'):
|
||||
state['twenty_four_hour_time'] = user_profile.twenty_four_hour_time
|
||||
@@ -2760,6 +2778,8 @@ def apply_events(state, events, user_profile):
|
||||
elif event['op'] == "vacate":
|
||||
stream_ids = [s["stream_id"] for s in event['streams']]
|
||||
state['streams'] = [s for s in state['streams'] if s["stream_id"] not in stream_ids]
|
||||
elif event['type'] == 'default_streams':
|
||||
state['realm_default_streams'] = event['default_streams']
|
||||
elif event['type'] == 'realm':
|
||||
field = 'realm_' + event['property']
|
||||
state[field] = event['value']
|
||||
@@ -3167,8 +3187,8 @@ def get_occupied_streams(realm):
|
||||
return Stream.objects.filter(id__in=stream_ids, realm=realm, deactivated=False)
|
||||
|
||||
def do_get_streams(user_profile, include_public=True, include_subscribed=True,
|
||||
include_all_active=False):
|
||||
# type: (UserProfile, bool, bool, bool) -> List[Dict[str, Any]]
|
||||
include_all_active=False, include_default=False):
|
||||
# type: (UserProfile, bool, bool, bool, bool) -> List[Dict[str, Any]]
|
||||
if include_all_active and not user_profile.is_api_super_user:
|
||||
raise JsonableError(_("User not authorized for this query"))
|
||||
|
||||
@@ -3199,6 +3219,13 @@ def do_get_streams(user_profile, include_public=True, include_subscribed=True,
|
||||
|
||||
streams = [(row.to_dict()) for row in query]
|
||||
streams.sort(key=lambda elt: elt["name"])
|
||||
if include_default:
|
||||
is_default = {}
|
||||
default_streams = get_default_streams_for_realm(user_profile.realm)
|
||||
for default_stream in default_streams:
|
||||
is_default[default_stream.id] = True
|
||||
for stream in streams:
|
||||
stream['is_default'] = is_default.get(stream["stream_id"], False)
|
||||
|
||||
return streams
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from zerver.lib.actions import (
|
||||
do_remove_realm_filter,
|
||||
do_remove_subscription,
|
||||
do_rename_stream,
|
||||
do_add_default_stream,
|
||||
do_set_muted_topics,
|
||||
do_set_realm_create_stream_by_admins_only,
|
||||
do_set_realm_name,
|
||||
@@ -377,6 +378,21 @@ class EventsRegisterTest(AuthedTestCase):
|
||||
error = alert_words_checker('events[0]', events[0])
|
||||
self.assert_on_error(error)
|
||||
|
||||
def test_default_streams_events(self):
|
||||
default_streams_checker = check_dict([
|
||||
('type', equals('default_streams')),
|
||||
('default_streams', check_list(check_dict([
|
||||
('description', check_string),
|
||||
('invite_only', check_bool),
|
||||
('name', check_string),
|
||||
('stream_id', check_int),
|
||||
]))),
|
||||
])
|
||||
|
||||
events = self.do_test(lambda: do_add_default_stream(self.user_profile.realm, "Scotland"))
|
||||
error = default_streams_checker('events[0]', events[0])
|
||||
self.assert_on_error(error)
|
||||
|
||||
def test_muted_topics_events(self):
|
||||
# type: () -> None
|
||||
muted_topics_checker = check_dict([
|
||||
@@ -387,7 +403,6 @@ class EventsRegisterTest(AuthedTestCase):
|
||||
error = muted_topics_checker('events[0]', events[0])
|
||||
self.assert_on_error(error)
|
||||
|
||||
|
||||
def test_change_full_name(self):
|
||||
# type: () -> None
|
||||
schema_checker = check_dict([
|
||||
|
||||
@@ -862,6 +862,7 @@ def home(request):
|
||||
alert_words = register_ret['alert_words'],
|
||||
muted_topics = register_ret['muted_topics'],
|
||||
realm_filters = register_ret['realm_filters'],
|
||||
realm_default_streams = register_ret['realm_default_streams'],
|
||||
is_admin = user_profile.is_realm_admin,
|
||||
can_create_streams = user_profile.can_create_streams(),
|
||||
name_changes_disabled = name_changes_disabled(user_profile.realm),
|
||||
|
||||
@@ -169,10 +169,16 @@ def json_make_stream_private(request, user_profile, stream_name=REQ()):
|
||||
@require_realm_admin
|
||||
@has_request_variables
|
||||
def update_stream_backend(request, user_profile, stream_name,
|
||||
description=REQ(validator=check_string, default=None)):
|
||||
# type: (HttpRequest, UserProfile, str, Optional[str]) -> HttpResponse
|
||||
description=REQ(validator=check_string, default=None),
|
||||
is_default=REQ(validator=check_bool, default=None)):
|
||||
# type: (HttpRequest, UserProfile, str, Optional[str], Optional[bool]) -> HttpResponse
|
||||
if description is not None:
|
||||
do_change_stream_description(user_profile.realm, stream_name, description)
|
||||
if is_default is not None:
|
||||
if is_default:
|
||||
do_add_default_stream(user_profile.realm, stream_name)
|
||||
else:
|
||||
do_remove_default_stream(user_profile.realm, stream_name)
|
||||
return json_success({})
|
||||
|
||||
def list_subscriptions_backend(request, user_profile):
|
||||
@@ -416,11 +422,15 @@ def json_get_subscribers(request, user_profile):
|
||||
def get_streams_backend(request, user_profile,
|
||||
include_public=REQ(validator=check_bool, default=True),
|
||||
include_subscribed=REQ(validator=check_bool, default=True),
|
||||
include_all_active=REQ(validator=check_bool, default=False)):
|
||||
# type: (HttpRequest, UserProfile, bool, bool, bool) -> HttpResponse
|
||||
include_all_active=REQ(validator=check_bool, default=False),
|
||||
include_default=REQ(validator=check_bool, default=False)):
|
||||
# type: (HttpRequest, UserProfile, bool, bool, bool, bool) -> HttpResponse
|
||||
|
||||
streams = do_get_streams(user_profile, include_public, include_subscribed,
|
||||
include_all_active)
|
||||
|
||||
streams = do_get_streams(user_profile, include_public=include_public,
|
||||
include_subscribed=include_subscribed,
|
||||
include_all_active=include_all_active,
|
||||
include_default=include_default)
|
||||
return json_success({"streams": streams})
|
||||
|
||||
@authenticated_json_post_view
|
||||
|
||||
Reference in New Issue
Block a user