settings_playground: Add UI to list all playgrounds.

Adds a setting UI to list all configured playgrounds
in a realm. The filter functionality can be used to
search playgrounds by its name or language.

Default sort is provided on the 'pygments_language'
field.

Front tests added to maintain server_event_dispatch
coverage. The `settings_playgrounds.js` file is added
to coverage exclusion list since it is majorly UI
based and will be tested using puppeteer tests (in
following commits).
This commit is contained in:
Sumanth V Rao 2021-04-16 07:34:18 +05:30 committed by Tim Abbott
parent 32390e0c87
commit 0ecf5d0e83
10 changed files with 160 additions and 0 deletions

View File

@ -39,6 +39,7 @@ const notifications = mock_esm("../../static/js/notifications");
const reactions = mock_esm("../../static/js/reactions"); const reactions = mock_esm("../../static/js/reactions");
const realm_icon = mock_esm("../../static/js/realm_icon"); const realm_icon = mock_esm("../../static/js/realm_icon");
const realm_logo = mock_esm("../../static/js/realm_logo"); const realm_logo = mock_esm("../../static/js/realm_logo");
const realm_playground = mock_esm("../../static/js/realm_playground");
const reload = mock_esm("../../static/js/reload"); const reload = mock_esm("../../static/js/reload");
const scroll_bar = mock_esm("../../static/js/scroll_bar"); const scroll_bar = mock_esm("../../static/js/scroll_bar");
const settings_account = mock_esm("../../static/js/settings_account"); const settings_account = mock_esm("../../static/js/settings_account");
@ -48,6 +49,7 @@ const settings_emoji = mock_esm("../../static/js/settings_emoji");
const settings_exports = mock_esm("../../static/js/settings_exports"); const settings_exports = mock_esm("../../static/js/settings_exports");
const settings_invites = mock_esm("../../static/js/settings_invites"); const settings_invites = mock_esm("../../static/js/settings_invites");
const settings_linkifiers = mock_esm("../../static/js/settings_linkifiers"); const settings_linkifiers = mock_esm("../../static/js/settings_linkifiers");
const settings_playgrounds = mock_esm("../../static/js/settings_playgrounds");
const settings_notifications = mock_esm("../../static/js/settings_notifications"); const settings_notifications = mock_esm("../../static/js/settings_notifications");
const settings_org = mock_esm("../../static/js/settings_org"); const settings_org = mock_esm("../../static/js/settings_org");
const settings_profile_fields = mock_esm("../../static/js/settings_profile_fields"); const settings_profile_fields = mock_esm("../../static/js/settings_profile_fields");
@ -525,6 +527,15 @@ run_test("realm_linkifiers", (override) => {
assert_same(page_params.realm_linkifiers, event.realm_linkifiers); assert_same(page_params.realm_linkifiers, event.realm_linkifiers);
}); });
run_test("realm_playgrounds", (override) => {
const event = event_fixtures.realm_playgrounds;
page_params.realm_playgrounds = [];
override(settings_playgrounds, "populate_playgrounds", noop);
override(realm_playground, "update_playgrounds", noop);
dispatch(event);
assert_same(page_params.realm_playgrounds, event.realm_playgrounds);
});
run_test("realm_domains", (override) => { run_test("realm_domains", (override) => {
let event = event_fixtures.realm_domains__add; let event = event_fixtures.realm_domains__add;
page_params.realm_domains = []; page_params.realm_domains = [];

View File

@ -467,6 +467,18 @@ exports.fixtures = {
], ],
}, },
realm_playgrounds: {
type: "realm_playgrounds",
realm_playgrounds: [
{
id: 1,
name: "Lean playground",
pygments_language: "Lean",
url_prefix: "https://leanprover.github.io/live/latest/#code=",
},
],
},
realm_user__add: { realm_user__add: {
type: "realm_user", type: "realm_user",
op: "add", op: "add",

View File

@ -44,6 +44,7 @@ import * as settings_invites from "./settings_invites";
import * as settings_linkifiers from "./settings_linkifiers"; import * as settings_linkifiers from "./settings_linkifiers";
import * as settings_notifications from "./settings_notifications"; import * as settings_notifications from "./settings_notifications";
import * as settings_org from "./settings_org"; import * as settings_org from "./settings_org";
import * as settings_playgrounds from "./settings_playgrounds";
import * as settings_profile_fields from "./settings_profile_fields"; import * as settings_profile_fields from "./settings_profile_fields";
import * as settings_streams from "./settings_streams"; import * as settings_streams from "./settings_streams";
import * as settings_user_groups from "./settings_user_groups"; import * as settings_user_groups from "./settings_user_groups";
@ -353,6 +354,7 @@ export function dispatch_normal_event(event) {
case "realm_playgrounds": case "realm_playgrounds":
page_params.realm_playgrounds = event.realm_playgrounds; page_params.realm_playgrounds = event.realm_playgrounds;
realm_playground.update_playgrounds(page_params.realm_playgrounds); realm_playground.update_playgrounds(page_params.realm_playgrounds);
settings_playgrounds.populate_playgrounds(page_params.realm_playgrounds);
break; break;
case "realm_domains": case "realm_domains":

View File

@ -0,0 +1,87 @@
import $ from "jquery";
import render_admin_playground_list from "../templates/admin_playground_list.hbs";
import * as ListWidget from "./list_widget";
import {page_params} from "./page_params";
import * as ui from "./ui";
const meta = {
loaded: false,
};
export function reset() {
meta.loaded = false;
}
function compare_by_index(a, b, i) {
if (a[i] > b[i]) {
return 1;
} else if (a[i] === b[i]) {
return 0;
}
return -1;
}
function sort_pygments_language(a, b) {
return compare_by_index(a, b, 0);
}
function sort_playground_name(a, b) {
return compare_by_index(a, b, 1);
}
export function maybe_disable_widgets() {
if (page_params.is_admin) {
return;
}
}
export function populate_playgrounds(playgrounds_data) {
if (!meta.loaded) {
return;
}
const playgrounds_table = $("#admin_playgrounds_table").expectOne();
ListWidget.create(playgrounds_table, playgrounds_data, {
name: "playgrounds_list",
modifier(playground) {
return render_admin_playground_list({
playground: {
playground_name: playground.name,
pygments_language: playground.pygments_language,
url_prefix: playground.url_prefix,
},
});
},
filter: {
element: playgrounds_table.closest(".settings-section").find(".search"),
predicate(item, value) {
return (
item.name.toLowerCase().includes(value) ||
item.pygments_language.toLowerCase().includes(value)
);
},
onupdate() {
ui.reset_scrollbar(playgrounds_table);
},
},
parent_container: $("#playground-settings").expectOne(),
init_sort: [sort_pygments_language],
sort_fields: {
pygments_language: sort_pygments_language,
playground_name: sort_playground_name,
},
simplebar_container: $("#playground-settings .progressive-table-wrapper"),
});
}
export function set_up() {
build_page();
maybe_disable_widgets();
}
function build_page() {
meta.loaded = true;
populate_playgrounds(page_params.realm_playgrounds);
}

View File

@ -11,6 +11,7 @@ import * as settings_linkifiers from "./settings_linkifiers";
import * as settings_muted_topics from "./settings_muted_topics"; import * as settings_muted_topics from "./settings_muted_topics";
import * as settings_notifications from "./settings_notifications"; import * as settings_notifications from "./settings_notifications";
import * as settings_org from "./settings_org"; import * as settings_org from "./settings_org";
import * as settings_playgrounds from "./settings_playgrounds";
import * as settings_profile_fields from "./settings_profile_fields"; import * as settings_profile_fields from "./settings_profile_fields";
import * as settings_streams from "./settings_streams"; import * as settings_streams from "./settings_streams";
import * as settings_user_groups from "./settings_user_groups"; import * as settings_user_groups from "./settings_user_groups";
@ -58,6 +59,7 @@ export function initialize() {
load_func_dict.set("emoji-settings", settings_emoji.set_up); load_func_dict.set("emoji-settings", settings_emoji.set_up);
load_func_dict.set("default-streams-list", settings_streams.set_up); load_func_dict.set("default-streams-list", settings_streams.set_up);
load_func_dict.set("linkifier-settings", settings_linkifiers.set_up); load_func_dict.set("linkifier-settings", settings_linkifiers.set_up);
load_func_dict.set("playground-settings", settings_playgrounds.set_up);
load_func_dict.set("invites-list-admin", settings_invites.set_up); load_func_dict.set("invites-list-admin", settings_invites.set_up);
load_func_dict.set("user-groups-admin", settings_user_groups.set_up); load_func_dict.set("user-groups-admin", settings_user_groups.set_up);
load_func_dict.set("profile-field-settings", settings_profile_fields.set_up); load_func_dict.set("profile-field-settings", settings_profile_fields.set_up);
@ -90,6 +92,7 @@ export function reset_sections() {
settings_emoji.reset(); settings_emoji.reset();
settings_exports.reset(); settings_exports.reset();
settings_linkifiers.reset(); settings_linkifiers.reset();
settings_playgrounds.reset();
settings_invites.reset(); settings_invites.reset();
settings_org.reset(); settings_org.reset();
settings_profile_fields.reset(); settings_profile_fields.reset();

View File

@ -0,0 +1,13 @@
{{#with playground}}
<tr>
<td>
<span>{{pygments_language}}</span>
</td>
<td>
<span>{{playground_name}}</span>
</td>
<td>
<span>{{url_prefix}}</span>
</td>
</tr>
{{/with}}

View File

@ -23,6 +23,8 @@
{{> settings/linkifier_settings_admin }} {{> settings/linkifier_settings_admin }}
{{> settings/playground_settings_admin }}
{{> settings/invites_list_admin }} {{> settings/invites_list_admin }}
{{> user_groups_admin }} {{> user_groups_admin }}

View File

@ -0,0 +1,20 @@
<div id="playground-settings" class="settings-section" data-name="playground-settings">
<div class="admin-table-wrapper">
<p>
{{#tr}}
Configure external sites that can run code present in a Zulip code block.
{{/tr}}
</p>
<input type="text" class="search" placeholder="{{t 'Filter playgrounds' }}" aria-label="{{t 'Filter playgrounds' }}"/>
<div class="progressive-table-wrapper" data-simplebar>
<table class="table table-condensed table-striped wrapped-table admin_playgrounds_table">
<thead>
<th class="active" data-sort="pygments_language">{{t "Language" }}</th>
<th data-sort="playground_name">{{t "Name" }}</th>
<th data-sort="url">{{t "URL prefix" }}</th>
</thead>
<tbody id="admin_playgrounds_table" class="required-text" data-empty="{{t 'No playgrounds configured.' }}"></tbody>
</table>
</div>
</div>
</div>

View File

@ -134,6 +134,15 @@
<i class="locked fa fa-lock" title="{{ _('Only organization administrators can edit these settings.') }}"></i> <i class="locked fa fa-lock" title="{{ _('Only organization administrators can edit these settings.') }}"></i>
{% endif %} {% endif %}
</li> </li>
{% if development_environment %}
<li tabindex="0" data-section="playground-settings">
<i class="icon fa fa-external-link" aria-hidden="true"></i>
<div class="text">{{ _('Code playgrounds') }}</div>
{% if not is_admin %}
<i class="locked fa fa-lock" title="{{ _('Only organization administrators can edit these settings.') }}"></i>
{% endif %}
</li>
{% endif %}
{% if is_admin %} {% if is_admin %}
<li tabindex="0" data-section="profile-field-settings"> <li tabindex="0" data-section="profile-field-settings">
<i class="icon fa fa-user" aria-hidden="true"></i> <i class="icon fa fa-user" aria-hidden="true"></i>

View File

@ -124,6 +124,7 @@ EXEMPT_FILES = {
"static/js/settings_invites.js", "static/js/settings_invites.js",
"static/js/settings.js", "static/js/settings.js",
"static/js/settings_linkifiers.js", "static/js/settings_linkifiers.js",
"static/js/settings_playgrounds.js",
"static/js/settings_notifications.js", "static/js/settings_notifications.js",
"static/js/settings_org.js", "static/js/settings_org.js",
"static/js/settings_panel_menu.js", "static/js/settings_panel_menu.js",