mirror of https://github.com/zulip/zulip.git
settings: Add support for adding/removing custom profile fields.
Now that we have support for displaying custom profile fields, this adds administrator-level support for creating them. Tweaked by tabbott to fix a few small bugs and clean up the commit message. Fixes #1760.
This commit is contained in:
parent
7885dd4408
commit
c14cefc24c
|
@ -61,6 +61,7 @@
|
|||
"settings_filters": false,
|
||||
"settings_invites": false,
|
||||
"settings_user_groups": false,
|
||||
"settings_profile_fields": false,
|
||||
"settings": false,
|
||||
"resize": false,
|
||||
"loading": false,
|
||||
|
|
|
@ -184,6 +184,55 @@ casper.then(function () {
|
|||
});
|
||||
});
|
||||
|
||||
// Test custom profile fields
|
||||
casper.test.info("Testing custom profile fields");
|
||||
casper.thenClick("li[data-section='profile-field-settings']");
|
||||
casper.then(function () {
|
||||
casper.waitUntilVisible('.admin-profile-field-form', function () {
|
||||
casper.fill('form.admin-profile-field-form', {
|
||||
name: 'Teams',
|
||||
field_type: '3',
|
||||
});
|
||||
casper.click('form.admin-profile-field-form button.button');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.waitUntilVisible('div#admin-profile-field-status', function () {
|
||||
casper.test.assertSelectorHasText('div#admin-profile-field-status',
|
||||
'Custom profile field added!');
|
||||
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_name', 'Teams');
|
||||
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_type', 'Short Text');
|
||||
casper.click('.profile-field-row button.open-edit-form');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.waitUntilVisible('tr.profile-field-form form', function () {
|
||||
casper.fill('tr.profile-field-form form.name-setting', {
|
||||
name: 'team',
|
||||
});
|
||||
casper.click('tr.profile-field-form button.submit');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.waitUntilVisible('div#admin-profile-field-status', function () {
|
||||
casper.test.assertSelectorHasText('div#admin-profile-field-status',
|
||||
'Custom profile field updated!');
|
||||
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_name', 'team');
|
||||
casper.test.assertSelectorHasText('.profile-field-row span.profile_field_type', 'Short Text');
|
||||
casper.click('.profile-field-row button.delete');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.waitUntilVisible('div#admin-profile-field-status', function () {
|
||||
casper.test.assertSelectorHasText('div#admin-profile-field-status',
|
||||
'Custom profile field deleted!');
|
||||
});
|
||||
});
|
||||
|
||||
// Test custom realm filters
|
||||
casper.then(function () {
|
||||
casper.click("li[data-section='filter-settings']");
|
||||
|
|
|
@ -476,6 +476,14 @@ var event_fixtures = {
|
|||
type: 'delete_message',
|
||||
message_id: 1337,
|
||||
},
|
||||
|
||||
custom_profile_fields: {
|
||||
type: 'custom_profile_fields',
|
||||
fields: [
|
||||
{id: 1, name: 'teams', type: 1},
|
||||
{id: 2, name: 'hobbies', type: 1},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
function assert_same(actual, expected) {
|
||||
|
@ -496,6 +504,16 @@ with_overrides(function (override) {
|
|||
|
||||
});
|
||||
|
||||
with_overrides(function (override) {
|
||||
// custom profile fields
|
||||
var event = event_fixtures.custom_profile_fields;
|
||||
override('settings_profile_fields.populate_profile_fields', noop);
|
||||
override('settings_profile_fields.report_success', noop);
|
||||
dispatch(event);
|
||||
assert_same(global.page_params.custom_profile_fields, event.fields);
|
||||
|
||||
});
|
||||
|
||||
with_overrides(function (override) {
|
||||
// default_streams
|
||||
var event = event_fixtures.default_streams;
|
||||
|
|
|
@ -195,6 +195,55 @@ function render(template_name, args) {
|
|||
assert.equal(emoji_url.attr('src'), 'http://emojipedia-us.s3.amazonaws.com/cache/46/7f/467fe69069c408e07517621f263ea9b5.png');
|
||||
}());
|
||||
|
||||
(function admin_profile_field_list() {
|
||||
|
||||
// When the logged in user is admin
|
||||
var args = {
|
||||
profile_field: {
|
||||
name: "teams",
|
||||
type: "Long Text",
|
||||
},
|
||||
can_modify: true,
|
||||
};
|
||||
|
||||
var html = '';
|
||||
html += '<tbody id="admin_profile_fields_table">';
|
||||
html += render('admin_profile_field_list', args);
|
||||
html += '</tbody>';
|
||||
|
||||
var field_name = $(html).find('tr.profile-field-row:first span.profile_field_name');
|
||||
var field_type = $(html).find('tr.profile-field-row:first span.profile_field_type');
|
||||
var td = $(html).find('tr.profile-field-row:first td');
|
||||
|
||||
assert.equal(field_name.text(), 'teams');
|
||||
assert.equal(field_type.text(), 'Long Text');
|
||||
assert.equal(td.length, 3);
|
||||
|
||||
// When the logged in user is not admin
|
||||
args = {
|
||||
profile_field: {
|
||||
name: "teams",
|
||||
type: "Long Text",
|
||||
},
|
||||
can_modify: false,
|
||||
};
|
||||
|
||||
html = '';
|
||||
html += '<tbody id="admin_profile_fields_table">';
|
||||
html += render('admin_profile_field_list', args);
|
||||
html += '</tbody>';
|
||||
|
||||
global.write_test_output('admin_profile_field_list', html);
|
||||
|
||||
field_name = $(html).find('tr.profile-field-row:first span.profile_field_name');
|
||||
field_type = $(html).find('tr.profile-field-row:first span.profile_field_type');
|
||||
td = $(html).find('tr.profile-field-row:first td');
|
||||
|
||||
assert.equal(field_name.text(), 'teams');
|
||||
assert.equal(field_type.text(), 'Long Text');
|
||||
assert.equal(td.length, 2);
|
||||
}());
|
||||
|
||||
(function admin_filter_list() {
|
||||
|
||||
// When the logged in user is admin
|
||||
|
|
|
@ -20,12 +20,15 @@ exports.show_or_hide_menu_item = function () {
|
|||
.find("input:not(.search), button, select").attr("disabled", true);
|
||||
$(".organization-box [data-name='filter-settings']")
|
||||
.find("input, button, select").attr("disabled", true);
|
||||
$(".organization-box [data-name='profile-field-settings']")
|
||||
.find("input, button, select").attr("disabled", true);
|
||||
$(".control-label-disabled").css("color", "#333333");
|
||||
}
|
||||
};
|
||||
|
||||
function _setup_page() {
|
||||
var options = {
|
||||
custom_profile_field_types: page_params.custom_profile_field_types,
|
||||
realm_name: page_params.realm_name,
|
||||
realm_description: page_params.realm_description,
|
||||
realm_restricted_to_domain: page_params.realm_restricted_to_domain,
|
||||
|
|
|
@ -35,6 +35,9 @@ exports.load_admin_section = function (name) {
|
|||
case 'user-groups-admin':
|
||||
section = 'user-groups';
|
||||
break;
|
||||
case 'profile-field-settings':
|
||||
section = 'profile-fields';
|
||||
break;
|
||||
default:
|
||||
blueslip.error('Unknown admin id ' + name);
|
||||
return;
|
||||
|
@ -68,6 +71,9 @@ exports.load_admin_section = function (name) {
|
|||
case 'user-groups':
|
||||
settings_user_groups.set_up();
|
||||
break;
|
||||
case 'profile-fields':
|
||||
settings_profile_fields.set_up();
|
||||
break;
|
||||
default:
|
||||
blueslip.error('programming error for section ' + section);
|
||||
return;
|
||||
|
@ -85,6 +91,7 @@ exports.reset_sections = function () {
|
|||
settings_filters.reset();
|
||||
settings_invites.reset();
|
||||
settings_user_groups.reset();
|
||||
settings_profile_fields.reset();
|
||||
};
|
||||
|
||||
return exports;
|
||||
|
|
|
@ -160,6 +160,12 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
|||
settings_filters.populate_filters(page_params.realm_filters);
|
||||
break;
|
||||
|
||||
case 'custom_profile_fields':
|
||||
page_params.custom_profile_fields = event.fields;
|
||||
settings_profile_fields.populate_profile_fields(page_params.custom_profile_fields);
|
||||
settings_profile_fields.report_success(event.op);
|
||||
break;
|
||||
|
||||
case 'realm_domains':
|
||||
var i;
|
||||
if (event.op === 'add') {
|
||||
|
|
|
@ -108,6 +108,7 @@ function _setup_page() {
|
|||
"filter-settings": i18n.t("Filter settings"),
|
||||
"invites-list-admin": i18n.t("Invitations"),
|
||||
"user-groups-admin": i18n.t("User groups"),
|
||||
"profile-field-settings": i18n.t("Profile field settings"),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
var settings_profile_fields = (function () {
|
||||
|
||||
var exports = {};
|
||||
|
||||
var meta = {
|
||||
loaded: false,
|
||||
};
|
||||
|
||||
function field_type_id_to_string(type_id) {
|
||||
var name = _.find(page_params.custom_profile_field_types, function (type) {
|
||||
return type[0] === type_id;
|
||||
})[1];
|
||||
return name;
|
||||
}
|
||||
|
||||
function delete_profile_field(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var btn = $(this);
|
||||
|
||||
channel.del({
|
||||
url: '/json/realm/profile_fields/' + encodeURIComponent(btn.attr('data-profile-field-id')),
|
||||
error: function (xhr) {
|
||||
if (xhr.status.toString().charAt(0) === "4") {
|
||||
btn.closest("td").html(
|
||||
$("<p>").addClass("text-error").text(JSON.parse(xhr.responseText).msg)
|
||||
);
|
||||
} else {
|
||||
btn.text(i18n.t("Failed!"));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function create_profile_field(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var name_status = $('#admin-profile-field-name-status');
|
||||
name_status.hide();
|
||||
|
||||
channel.post({
|
||||
url: "/json/realm/profile_fields",
|
||||
data: $(this).serialize(),
|
||||
error: function (xhr) {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
xhr.responseText = JSON.stringify({msg: response.msg});
|
||||
ui_report.error(i18n.t("Failed"), xhr, name_status);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function get_profile_field_info(id) {
|
||||
var info = {};
|
||||
info.row = $("tr.profile-field-row[data-profile-field-id='" + id + "']");
|
||||
info.form = $("tr.profile-field-form[data-profile-field-id='" + id + "']");
|
||||
return info;
|
||||
}
|
||||
|
||||
function open_edit_form(e) {
|
||||
var field_id = $(e.currentTarget).attr("data-profile-field-id");
|
||||
var profile_field = get_profile_field_info(field_id);
|
||||
|
||||
profile_field.row.hide();
|
||||
profile_field.form.show();
|
||||
|
||||
profile_field.form.find('.reset').on("click", function () {
|
||||
profile_field.form.hide();
|
||||
profile_field.row.show();
|
||||
});
|
||||
|
||||
profile_field.form.find('.submit').on("click", function () {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var profile_field_status = $('#admin-profile-field-status');
|
||||
profile_field_status.hide();
|
||||
|
||||
// For some reason jQuery's serialize() is not working with
|
||||
// channel.patch even though it is supported by $.ajax.
|
||||
var data = {};
|
||||
data.name = profile_field.form.find('input[name=name]').val();
|
||||
|
||||
channel.patch({
|
||||
url: "/json/realm/profile_fields/" + field_id,
|
||||
data: data,
|
||||
error: function (xhr) {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
xhr.responseText = JSON.stringify({msg: response.msg});
|
||||
ui_report.error(i18n.t("Failed"), xhr, profile_field_status);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.reset = function () {
|
||||
meta.loaded = false;
|
||||
};
|
||||
|
||||
exports.populate_profile_fields = function (profile_fields_data) {
|
||||
if (!meta.loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
var profile_fields_table = $("#admin_profile_fields_table").expectOne();
|
||||
profile_fields_table.find("tr.profile-field-row").remove(); // Clear all rows.
|
||||
profile_fields_table.find("tr.profile-field-form").remove(); // Clear all rows.
|
||||
_.each(profile_fields_data, function (profile_field) {
|
||||
profile_fields_table.append(
|
||||
templates.render(
|
||||
"admin_profile_field_list", {
|
||||
profile_field: {
|
||||
id: profile_field.id,
|
||||
name: profile_field.name,
|
||||
type: field_type_id_to_string(profile_field.type),
|
||||
},
|
||||
can_modify: page_params.is_admin,
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
loading.destroy_indicator($('#admin_page_profile_fields_loading_indicator'));
|
||||
};
|
||||
|
||||
exports.set_up = function () {
|
||||
meta.loaded = true;
|
||||
|
||||
// create loading indicators
|
||||
loading.make_indicator($('#admin_page_profile_fields_loading_indicator'));
|
||||
// Populate profile_fields table
|
||||
exports.populate_profile_fields(page_params.custom_profile_fields);
|
||||
|
||||
$('#admin_profile_fields_table').on('click', '.delete', delete_profile_field);
|
||||
$(".organization").on("submit", "form.admin-profile-field-form", create_profile_field);
|
||||
$("#admin_profile_fields_table").on("click", ".open-edit-form", open_edit_form);
|
||||
};
|
||||
|
||||
exports.report_success = function (operation) {
|
||||
var profile_field_status = $('#admin-profile-field-status');
|
||||
profile_field_status.hide();
|
||||
var msg;
|
||||
|
||||
if (operation === 'add') {
|
||||
msg = i18n.t('Custom profile field added!');
|
||||
} else if (operation === 'delete') {
|
||||
msg = i18n.t('Custom profile field deleted!');
|
||||
} else if (operation === 'update') {
|
||||
msg = i18n.t('Custom profile field updated!');
|
||||
}
|
||||
|
||||
ui_report.success(msg, profile_field_status);
|
||||
};
|
||||
|
||||
return exports;
|
||||
}());
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = settings_profile_fields;
|
||||
}
|
|
@ -423,6 +423,7 @@ input[type=checkbox].inline-block {
|
|||
#settings .settings-section .new-bot-form,
|
||||
#settings .settings-section .new-alert-word-form,
|
||||
#filter-settings .new-filter-form,
|
||||
#profile-field-settings .new-profile-field-form,
|
||||
#settings .settings-section .notification-settings-form,
|
||||
#settings .settings-section .display-settings-form,
|
||||
#settings .settings-section .edit-bot-form-box {
|
||||
|
@ -434,6 +435,7 @@ input[type=checkbox].inline-block {
|
|||
#settings .settings-section .new-bot-form .control-label,
|
||||
#settings .settings-section .new-alert-word-form .control-label,
|
||||
#filter-settings .new-filter-form .control-label,
|
||||
#profile-field-settings .new-profile-field-form .control-label,
|
||||
#settings .settings-section .edit-bot-form-box .control-label {
|
||||
display: block;
|
||||
width: 120px;
|
||||
|
@ -449,6 +451,7 @@ input[type=checkbox].inline-block {
|
|||
#settings .settings-section .new-bot-form .controls,
|
||||
#settings .settings-section .new-alert-word-form button,
|
||||
#filter-settings .new-filter-form .controls,
|
||||
#profile-field-settings .new-profile-field-form .controls,
|
||||
#settings .settings-section .edit-bot-form-box .controls {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
|
@ -586,6 +589,7 @@ input[type=checkbox].inline-block {
|
|||
color: hsl(0, 0%, 66%);
|
||||
}
|
||||
|
||||
.add-new-profile-field-box button,
|
||||
.add-new-filter-box button {
|
||||
margin-left: calc(10em - -20px) !important;
|
||||
}
|
||||
|
@ -594,10 +598,12 @@ input[type=checkbox].inline-block {
|
|||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
.admin_profile_fields_table,
|
||||
.admin_filters_table {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#admin-profile-field-name-status,
|
||||
#admin-filter-pattern-status,
|
||||
#admin-filter-format-status {
|
||||
margin: 20px 0 0 0;
|
||||
|
@ -777,6 +783,7 @@ input[type=checkbox].inline-block {
|
|||
#get_api_key_box .control-label,
|
||||
.admin-emoji-form .control-label,
|
||||
.admin-filter-form .control-label,
|
||||
.admin-profile-field-form .control-label,
|
||||
.edit_bot_form .control-label {
|
||||
width: 10em;
|
||||
text-align: right;
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
{{#with profile_field}}
|
||||
<tr class="profile-field-row" data-profile-field-id="{{id}}">
|
||||
<td>
|
||||
<span class="profile_field_name">{{name}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="profile_field_type">{{type}}</span>
|
||||
</td>
|
||||
{{#if ../can_modify}}
|
||||
<td>
|
||||
<button class="button rounded small delete btn-danger" title="{{t 'Delete' }}" data-profile-field-id="{{id}}">
|
||||
<i class="icon-vector-trash"></i>
|
||||
</button>
|
||||
<button class="button rounded small btn-warning open-edit-form" title="{{t 'Edit' }}" data-profile-field-id="{{id}}">
|
||||
<i class="icon-vector-pencil"></i>
|
||||
</button>
|
||||
</td>
|
||||
{{/if}}
|
||||
</tr>
|
||||
<tr class="profile-field-form display-none" data-profile-field-id="{{id}}">
|
||||
<td colspan="3">
|
||||
<form class="form-horizontal name-setting">
|
||||
<div class="input-group name_change_container">
|
||||
<label for="name">{{t "Name" }}</label>
|
||||
<input type="text" name="name" value="{{ name }}" />
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<button type="button" class="button rounded sea-green submit">
|
||||
{{t 'Save changes' }}
|
||||
</button>
|
||||
<button type="button" class="button rounded btn-danger reset">
|
||||
{{t 'Cancel' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{/with}}
|
|
@ -28,3 +28,5 @@
|
|||
{{ partial "invites-list-admin" }}
|
||||
|
||||
{{ partial "user-groups-admin" }}
|
||||
|
||||
{{ partial "profile-field-settings-admin" }}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<div id="profile-field-settings" class="settings-section" data-name="profile-field-settings">
|
||||
<div class="admin-table-wrapper">
|
||||
<div class="alert" id="admin-profile-field-status"></div>
|
||||
<table class="table table-condensed table-striped admin_profile_fields_table">
|
||||
<tbody id="admin_profile_fields_table">
|
||||
<th>{{t "Label" }}</th>
|
||||
<th>{{t "Type" }}</th>
|
||||
{{#if is_admin}}
|
||||
<th class="actions">{{t "Actions" }}</th>
|
||||
{{/if}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{#if is_admin}}
|
||||
<form class="form-horizontal admin-profile-field-form">
|
||||
<div class="add-new-profile-field-box grey-box">
|
||||
<div class="new-profile-field-form wrapper">
|
||||
<div class="settings-section-title new-profile-field-section-title">{{t "Add a new profile field" }}</div>
|
||||
<div class="control-group">
|
||||
<label for="profile_field_name" class="control-label">{{t "Label" }}</label>
|
||||
<input type="text" id="profile_field_name" name="name" />
|
||||
<div class="alert" id="admin-profile-field-name-status"></div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="profile_field_type" class="control-label">{{t "Type" }}</label>
|
||||
<select id="profile_field_type" name="field_type">
|
||||
{{#each custom_profile_field_types}}
|
||||
<option value='{{this.[0]}}'>{{this.[1]}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="button rounded sea-green">
|
||||
{{t 'Add profile field' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -96,6 +96,12 @@
|
|||
<i class="icon icon-vector-font"></i>
|
||||
<div class="text">{{ _('Filter settings') }}</div>
|
||||
</li>
|
||||
{% if development_environment %}
|
||||
<li class="admin" tabindex="0" data-section="profile-field-settings">
|
||||
<i class="icon icon-vector-user"></i>
|
||||
<div class="text">{{ _('Custom profile fields') }}</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_admin %}
|
||||
<li class="admin" tabindex="0" data-section="invites-list-admin">
|
||||
<i class="icon icon-vector-user"></i>
|
||||
|
|
|
@ -1136,6 +1136,7 @@ JS_SPECS = {
|
|||
'js/settings_filters.js',
|
||||
'js/settings_invites.js',
|
||||
'js/settings_user_groups.js',
|
||||
'js/settings_profile_fields.js',
|
||||
'js/settings.js',
|
||||
'js/admin_sections.js',
|
||||
'js/admin.js',
|
||||
|
|
Loading…
Reference in New Issue