mirror of https://github.com/zulip/zulip.git
custom_profile_fields: Add "required" parameter to the profile fields.
Fixes #28512.
This commit is contained in:
parent
ac0673e0b5
commit
f758ca596b
|
@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 9.0
|
||||
|
||||
**Feature level 244**
|
||||
|
||||
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
|
||||
[`POST /realm/profile_fields`](/api/create-custom-profile-field),
|
||||
[`GET /realm/profile_fields`](/api/get-custom-profile-fields): Added a new
|
||||
parameter `required`, on custom profile field objects, indicating whether an
|
||||
organization administrator has configured the field as something users should
|
||||
be required to provide.
|
||||
|
||||
**Feature level 243**
|
||||
|
||||
* [`POST /register`](/api/register-queue), [`GET
|
||||
|
|
|
@ -265,6 +265,7 @@ EXEMPT_FILES = make_set(
|
|||
"web/src/url-template.d.ts",
|
||||
"web/src/user_card_popover.js",
|
||||
"web/src/user_deactivation_ui.ts",
|
||||
"web/src/user_events.js",
|
||||
"web/src/user_group_components.js",
|
||||
"web/src/user_group_create.js",
|
||||
"web/src/user_group_create_members.js",
|
||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
|||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
API_FEATURE_LEVEL = 243
|
||||
API_FEATURE_LEVEL = 244
|
||||
|
||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||
# only when going from an old version of the code to a newer version. Bump
|
||||
|
|
|
@ -62,6 +62,7 @@ export function append_custom_profile_fields(element_id, user_id) {
|
|||
is_select_field,
|
||||
field_choices,
|
||||
for_manage_user_modal: element_id === "#edit-user-form .custom-profile-field-form",
|
||||
is_empty_required_field: field.required && !field_value.value,
|
||||
});
|
||||
$(element_id).append(html);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import render_bankruptcy_alert_content from "../templates/navbar_alerts/bankrupt
|
|||
import render_configure_email_alert_content from "../templates/navbar_alerts/configure_outgoing_email.hbs";
|
||||
import render_demo_organization_deadline_content from "../templates/navbar_alerts/demo_organization_deadline.hbs";
|
||||
import render_desktop_notifications_alert_content from "../templates/navbar_alerts/desktop_notifications.hbs";
|
||||
import render_empty_required_profile_fields from "../templates/navbar_alerts/empty_required_profile_fields.hbs";
|
||||
import render_insecure_desktop_app_alert_content from "../templates/navbar_alerts/insecure_desktop_app.hbs";
|
||||
import render_navbar_alert_wrapper from "../templates/navbar_alerts/navbar_alert_wrapper.hbs";
|
||||
import render_profile_incomplete_alert_content from "../templates/navbar_alerts/profile_incomplete.hbs";
|
||||
|
@ -14,6 +15,7 @@ import * as desktop_notifications from "./desktop_notifications";
|
|||
import * as keydown_util from "./keydown_util";
|
||||
import {localstorage} from "./localstorage";
|
||||
import {page_params} from "./page_params";
|
||||
import * as people from "./people";
|
||||
import {current_user, realm} from "./state_data";
|
||||
import {should_display_profile_incomplete_alert} from "./timerender";
|
||||
import * as unread from "./unread";
|
||||
|
@ -70,6 +72,30 @@ export function should_show_server_upgrade_notification(ls) {
|
|||
return Date.now() > upgrade_nag_dismissal_duration;
|
||||
}
|
||||
|
||||
export function maybe_show_empty_required_profile_fields_alert() {
|
||||
const empty_required_profile_fields_exist = realm.custom_profile_fields
|
||||
.map((f) => ({
|
||||
...f,
|
||||
value: people.my_custom_profile_data(f.id)?.value,
|
||||
}))
|
||||
.find((f) => f.required && !f.value);
|
||||
if (!empty_required_profile_fields_exist) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $navbar_alert = $("#navbar_alerts_wrapper").children(".alert").first();
|
||||
if (
|
||||
!$navbar_alert?.length ||
|
||||
$navbar_alert.is("#empty-required-profile-fields-warning") ||
|
||||
$navbar_alert.is(":hidden")
|
||||
) {
|
||||
open({
|
||||
data_process: "profile-missing-required",
|
||||
rendered_alert_content_html: render_empty_required_profile_fields(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function dismiss_upgrade_nag(ls) {
|
||||
$(".alert[data-process='server-needs-upgrade'").hide();
|
||||
if (localstorage.supported()) {
|
||||
|
@ -171,6 +197,8 @@ export function initialize() {
|
|||
data_process: "profile-incomplete",
|
||||
rendered_alert_content_html: render_profile_incomplete_alert_content(),
|
||||
});
|
||||
} else {
|
||||
maybe_show_empty_required_profile_fields_alert();
|
||||
}
|
||||
|
||||
// Configure click handlers.
|
||||
|
@ -208,6 +236,7 @@ export function initialize() {
|
|||
show_step($process, 2);
|
||||
} else {
|
||||
$(this).closest(".alert").hide();
|
||||
maybe_show_empty_required_profile_fields_alert();
|
||||
}
|
||||
$(window).trigger("resize");
|
||||
});
|
||||
|
|
|
@ -104,6 +104,7 @@ export function dispatch_normal_event(event) {
|
|||
realm.custom_profile_fields = event.fields;
|
||||
settings_profile_fields.populate_profile_fields(realm.custom_profile_fields);
|
||||
settings_account.add_custom_profile_fields_to_settings();
|
||||
navbar_alerts.maybe_show_empty_required_profile_fields_alert();
|
||||
break;
|
||||
|
||||
case "default_streams":
|
||||
|
|
|
@ -269,6 +269,7 @@ function open_custom_profile_field_form_modal() {
|
|||
display_in_profile_summary: $("#profile_field_display_in_profile_summary").is(
|
||||
":checked",
|
||||
),
|
||||
required: $("#profile-field-required").is(":checked"),
|
||||
};
|
||||
const url = "/json/realm/profile_fields";
|
||||
const opts = {
|
||||
|
@ -454,6 +455,7 @@ function open_edit_form_modal(e) {
|
|||
hint: field.hint,
|
||||
choices,
|
||||
display_in_profile_summary: field.display_in_profile_summary === true,
|
||||
required: field.required === true,
|
||||
is_select_field: field.type === field_types.SELECT.id,
|
||||
is_external_account_field: field.type === field_types.EXTERNAL_ACCOUNT.id,
|
||||
valid_to_display_in_summary: is_valid_to_display_in_summary(field.type),
|
||||
|
@ -510,6 +512,7 @@ function open_edit_form_modal(e) {
|
|||
data.display_in_profile_summary = $profile_field_form
|
||||
.find("input[name=display_in_profile_summary]")
|
||||
.is(":checked");
|
||||
data.required = $profile_field_form.find("input[name=required]").is(":checked");
|
||||
|
||||
const new_field_data = read_field_data_from_form(
|
||||
Number.parseInt(field.type, 10),
|
||||
|
@ -588,6 +591,7 @@ function toggle_display_in_profile_summary_profile_field(e) {
|
|||
hint: field.hint,
|
||||
field_data,
|
||||
display_in_profile_summary: !field.display_in_profile_summary,
|
||||
required: field.required,
|
||||
};
|
||||
const $profile_field_status = $("#admin-profile-field-status").expectOne();
|
||||
|
||||
|
@ -599,6 +603,31 @@ function toggle_display_in_profile_summary_profile_field(e) {
|
|||
);
|
||||
}
|
||||
|
||||
function toggle_required(e) {
|
||||
const field_id = Number.parseInt($(e.currentTarget).attr("data-profile-field-id"), 10);
|
||||
const field = get_profile_field(field_id);
|
||||
|
||||
let field_data;
|
||||
if (field.field_data) {
|
||||
field_data = field.field_data;
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: field.name,
|
||||
hint: field.hint,
|
||||
field_data,
|
||||
display_in_profile_summary: field.display_in_profile_summary,
|
||||
required: !field.required,
|
||||
};
|
||||
const $profile_field_status = $("#admin-profile-field-status").expectOne();
|
||||
|
||||
settings_ui.do_settings_change(
|
||||
channel.patch,
|
||||
"/json/realm/profile_fields/" + field_id,
|
||||
data,
|
||||
$profile_field_status,
|
||||
);
|
||||
}
|
||||
export function reset() {
|
||||
meta.loaded = false;
|
||||
}
|
||||
|
@ -648,6 +677,7 @@ export function do_populate_profile_fields(profile_fields_data) {
|
|||
}
|
||||
|
||||
const display_in_profile_summary = profile_field.display_in_profile_summary === true;
|
||||
const required = profile_field.required === true;
|
||||
$profile_fields_table.append(
|
||||
render_admin_profile_field_list({
|
||||
profile_field: {
|
||||
|
@ -661,6 +691,7 @@ export function do_populate_profile_fields(profile_fields_data) {
|
|||
profile_field.type === field_types.EXTERNAL_ACCOUNT.id,
|
||||
display_in_profile_summary,
|
||||
valid_to_display_in_summary: is_valid_to_display_in_summary(profile_field.type),
|
||||
required,
|
||||
},
|
||||
can_modify: current_user.is_admin,
|
||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||
|
@ -767,4 +798,5 @@ export function build_page() {
|
|||
".display_in_profile_summary",
|
||||
toggle_display_in_profile_summary_profile_field,
|
||||
);
|
||||
$("#admin_profile_fields_table").on("click", ".required-field-toggle", toggle_required);
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ export const realm_schema = z.object({
|
|||
id: z.number(),
|
||||
name: z.string(),
|
||||
order: z.number(),
|
||||
required: z.boolean(),
|
||||
type: z.number(),
|
||||
}),
|
||||
),
|
||||
|
|
|
@ -592,4 +592,15 @@ export function initialize(): void {
|
|||
);
|
||||
},
|
||||
});
|
||||
|
||||
delegate("body", {
|
||||
target: ".custom-user-field-label-wrapper",
|
||||
content: $t({
|
||||
defaultMessage: "This profile field is required.",
|
||||
}),
|
||||
appendTo: () => document.body,
|
||||
onHidden(instance) {
|
||||
instance.destroy();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {buddy_list} from "./buddy_list";
|
|||
import * as compose_state from "./compose_state";
|
||||
import * as message_live_update from "./message_live_update";
|
||||
import * as narrow_state from "./narrow_state";
|
||||
import * as navbar_alerts from "./navbar_alerts";
|
||||
import * as people from "./people";
|
||||
import * as pm_list from "./pm_list";
|
||||
import * as settings from "./settings";
|
||||
|
@ -21,7 +22,7 @@ import * as settings_profile_fields from "./settings_profile_fields";
|
|||
import * as settings_realm_user_settings_defaults from "./settings_realm_user_settings_defaults";
|
||||
import * as settings_streams from "./settings_streams";
|
||||
import * as settings_users from "./settings_users";
|
||||
import {current_user} from "./state_data";
|
||||
import {current_user, realm} from "./state_data";
|
||||
import * as stream_events from "./stream_events";
|
||||
|
||||
export const update_person = function update(person) {
|
||||
|
@ -128,6 +129,33 @@ export const update_person = function update(person) {
|
|||
|
||||
if (Object.hasOwn(person, "custom_profile_field")) {
|
||||
people.set_custom_profile_field_data(person.user_id, person.custom_profile_field);
|
||||
if (person.user_id === people.my_current_user_id()) {
|
||||
navbar_alerts.maybe_show_empty_required_profile_fields_alert();
|
||||
|
||||
const field_id = person.custom_profile_field.id;
|
||||
const field_value = people.get_custom_profile_data(person.user_id, field_id)?.value;
|
||||
const is_field_required = realm.custom_profile_fields?.find(
|
||||
(f) => field_id === f.id,
|
||||
)?.required;
|
||||
if (is_field_required) {
|
||||
const $custom_user_field = $(
|
||||
`.profile-settings-form .custom_user_field[data-field-id="${CSS.escape(field_id)}"]`,
|
||||
);
|
||||
const $field = $custom_user_field.find(".field");
|
||||
const $required_symbol = $custom_user_field.find(".required-symbol");
|
||||
if (!field_value) {
|
||||
if (!$field.hasClass("empty-required-field")) {
|
||||
$field.addClass("empty-required-field");
|
||||
$required_symbol.removeClass("hidden");
|
||||
}
|
||||
} else {
|
||||
if ($field.hasClass("empty-required-field")) {
|
||||
$field.removeClass("empty-required-field");
|
||||
$required_symbol.addClass("hidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.hasOwn(person, "timezone")) {
|
||||
|
|
|
@ -287,6 +287,7 @@ export function get_custom_profile_field_data(user, field, field_types) {
|
|||
profile_field.is_external_account = field_type === field_types.EXTERNAL_ACCOUNT.id;
|
||||
profile_field.type = field_type;
|
||||
profile_field.display_in_profile_summary = field.display_in_profile_summary;
|
||||
profile_field.required = field.required;
|
||||
|
||||
switch (field_type) {
|
||||
case field_types.DATE.id:
|
||||
|
|
|
@ -482,6 +482,10 @@
|
|||
border-width: 1px;
|
||||
}
|
||||
|
||||
.field {
|
||||
border-color: hsl(3deg 73% 74%);
|
||||
}
|
||||
|
||||
.deactivated-pill {
|
||||
background-color: hsl(0deg 86% 14%) !important;
|
||||
}
|
||||
|
|
|
@ -131,7 +131,9 @@ h3,
|
|||
|
||||
.admin_profile_fields_table {
|
||||
& th.display,
|
||||
td.display_in_profile_summary_cell {
|
||||
& th.required,
|
||||
td.display_in_profile_summary_cell,
|
||||
td.required-cell {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
@ -672,6 +674,15 @@ input[type="checkbox"] {
|
|||
}
|
||||
}
|
||||
|
||||
.required-symbol {
|
||||
color: hsl(0deg 66% 60%);
|
||||
}
|
||||
|
||||
.settings-profile-user-field {
|
||||
margin-top: 5px;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.control-label-disabled {
|
||||
color: hsl(0deg 0% 82%);
|
||||
|
||||
|
@ -1513,7 +1524,6 @@ $option_title_width: 180px;
|
|||
#edit-user-form {
|
||||
.person_picker {
|
||||
min-width: 206px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
& textarea {
|
||||
|
@ -1536,7 +1546,6 @@ $option_title_width: 180px;
|
|||
& select {
|
||||
/* Override undesired Bootstrap default. */
|
||||
margin-bottom: 0;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1906,6 +1915,22 @@ $option_title_width: 180px;
|
|||
}
|
||||
}
|
||||
|
||||
.empty-required-field {
|
||||
border: 1px solid hsl(3deg 57% 33%);
|
||||
border-radius: 5px;
|
||||
|
||||
.settings_url_input,
|
||||
.settings_text_input,
|
||||
.settings_textarea,
|
||||
.pill-container {
|
||||
border-color: hsl(0deg 0% 100%);
|
||||
|
||||
&:focus {
|
||||
border-color: hsl(206deg 80% 62% / 80%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#generate-integration-url-modal {
|
||||
.inline {
|
||||
display: inline;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<div data-step="1">
|
||||
<span>
|
||||
{{t 'Your profile is missing required fields.'}}
|
||||
<a class="alert-link" href="#settings/profile">{{t 'Edit your profile'}}</a>
|
||||
</span>
|
||||
</div>
|
|
@ -43,5 +43,12 @@
|
|||
{{t 'Display on user card' }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label class="checkbox" for="profile-field-required">
|
||||
<input type="checkbox" id="profile-field-required" name="required"/>
|
||||
<span></span>
|
||||
{{t 'Required field' }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -23,6 +23,14 @@
|
|||
</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="required-cell">
|
||||
<span class="profile-field-required">
|
||||
<label class="checkbox" for="profile-field-required-{{id}}">
|
||||
<input class="required-field-toggle required-checkbox-{{required}}" type="checkbox" id="profile-field-required-{{id}}" {{#if required}} checked="checked" {{/if}} data-profile-field-id="{{id}}"/>
|
||||
<span></span>
|
||||
</label>
|
||||
</span>
|
||||
</td>
|
||||
{{#if ../can_modify}}
|
||||
<td class="actions">
|
||||
<button class="button rounded small btn-warning open-edit-form-modal tippy-zulip-delayed-tooltip" data-tippy-content="{{t 'Edit custom profile field' }}" data-profile-field-id="{{id}}">
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<div class="custom_user_field" name="{{ field.name }}" data-field-id="{{ field.id }}">
|
||||
<label class="inline-block" for="{{ field.name }}" class="title">{{ field.name }}</label>
|
||||
<span class="custom-user-field-label-wrapper">
|
||||
<label class="inline-block {{#if field.required}}required-field{{/if}}" for="{{ field.name }}" class="title">{{ field.name }}</label>
|
||||
<span class="required-symbol {{#unless is_empty_required_field}}hidden{{/unless}}"> *</span>
|
||||
</span>
|
||||
<div class="alert-notification custom-field-status"></div>
|
||||
<div class="settings-profile-field-user-hint">{{ field.hint }}</div>
|
||||
<div class="settings-profile-user-field">
|
||||
<div class="settings-profile-user-field {{#if is_empty_required_field}}empty-required-field{{/if}}">
|
||||
{{#if is_long_text_field}}
|
||||
<textarea maxlength="500" class="custom_user_field_value settings_textarea">{{ field_value.value }}</textarea>
|
||||
{{else if is_select_field}}
|
||||
|
|
|
@ -45,5 +45,12 @@
|
|||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="input-group">
|
||||
<label class="checkbox" for="edit-required-{{id}}">
|
||||
<input class="edit-required" type="checkbox" id="edit-required-{{id}}" name="required" {{#if required}} checked="checked" {{/if}}/>
|
||||
<span></span>
|
||||
{{t 'Required field' }}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
{{/with}}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<th>{{t "Type" }}</th>
|
||||
{{#if is_admin}}
|
||||
<th class="display">{{t "Card"}}</th>
|
||||
<th class="required">{{t "Required"}}</th>
|
||||
<th class="actions">{{t "Actions" }}</th>
|
||||
{{/if}}
|
||||
</tr>
|
||||
|
|
|
@ -40,6 +40,7 @@ const message_lists = mock_esm("../src/message_lists");
|
|||
const user_topics_ui = mock_esm("../src/user_topics_ui");
|
||||
const muted_users_ui = mock_esm("../src/muted_users_ui");
|
||||
const narrow_title = mock_esm("../src/narrow_title");
|
||||
const navbar_alerts = mock_esm("../src/navbar_alerts");
|
||||
const pm_list = mock_esm("../src/pm_list");
|
||||
const reactions = mock_esm("../src/reactions");
|
||||
const realm_icon = mock_esm("../src/realm_icon");
|
||||
|
@ -115,7 +116,6 @@ message_lists.home = {
|
|||
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
|
||||
|
||||
// page_params is highly coupled to dispatching now
|
||||
|
||||
page_params.test_suite = false;
|
||||
current_user.is_admin = true;
|
||||
realm.realm_description = "already set description";
|
||||
|
@ -134,8 +134,17 @@ function dispatch(ev) {
|
|||
server_events_dispatch.dispatch_normal_event(ev);
|
||||
}
|
||||
|
||||
const me = {
|
||||
email: "me@example.com",
|
||||
user_id: 20,
|
||||
full_name: "Me Myself",
|
||||
timezone: "America/Los_Angeles",
|
||||
};
|
||||
|
||||
people.init();
|
||||
people.add_active_user(me);
|
||||
people.add_active_user(test_user);
|
||||
people.initialize_current_user(me.user_id);
|
||||
|
||||
message_helper.process_new_message(test_message);
|
||||
|
||||
|
@ -295,6 +304,7 @@ run_test("custom profile fields", ({override}) => {
|
|||
const event = event_fixtures.custom_profile_fields;
|
||||
override(settings_profile_fields, "populate_profile_fields", noop);
|
||||
override(settings_account, "add_custom_profile_fields_to_settings", noop);
|
||||
override(navbar_alerts, "maybe_show_empty_required_profile_fields_alert", noop);
|
||||
dispatch(event);
|
||||
assert_same(realm.custom_profile_fields, event.fields);
|
||||
});
|
||||
|
@ -432,6 +442,8 @@ run_test("realm settings", ({override}) => {
|
|||
override(sidebar_ui, "update_invite_user_option", noop);
|
||||
override(gear_menu, "rerender", noop);
|
||||
override(narrow_title, "redraw_title", noop);
|
||||
override(navbar_alerts, "check_profile_incomplete", noop);
|
||||
override(navbar_alerts, "show_profile_incomplete", noop);
|
||||
|
||||
function test_electron_dispatch(event, fake_send_event) {
|
||||
with_overrides(({override}) => {
|
||||
|
|
|
@ -138,6 +138,7 @@ exports.fixtures = {
|
|||
field_data: "",
|
||||
order: 1,
|
||||
display_in_profile_summary: false,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
@ -147,6 +148,7 @@ exports.fixtures = {
|
|||
field_data: "",
|
||||
order: 2,
|
||||
display_in_profile_summary: false,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -99,6 +99,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
|||
field_data: "",
|
||||
display_in_profile_summary: false,
|
||||
valid_to_display_in_summary: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: SELECT_ID,
|
||||
|
@ -117,6 +118,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
|||
]),
|
||||
display_in_profile_summary: false,
|
||||
valid_to_display_in_summary: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: EXTERNAL_ACCOUNT_ID,
|
||||
|
@ -128,6 +130,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
|||
}),
|
||||
display_in_profile_summary: true,
|
||||
valid_to_display_in_summary: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
type: EXTERNAL_ACCOUNT_ID,
|
||||
|
@ -140,6 +143,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
|||
}),
|
||||
display_in_profile_summary: true,
|
||||
valid_to_display_in_summary: true,
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
const expected_template_data = [
|
||||
|
@ -154,6 +158,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
|||
is_external_account_field: false,
|
||||
display_in_profile_summary: false,
|
||||
valid_to_display_in_summary: true,
|
||||
required: false,
|
||||
},
|
||||
can_modify: true,
|
||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||
|
@ -172,6 +177,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
|||
is_external_account_field: false,
|
||||
display_in_profile_summary: false,
|
||||
valid_to_display_in_summary: true,
|
||||
required: false,
|
||||
},
|
||||
can_modify: true,
|
||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||
|
@ -187,6 +193,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
|||
is_external_account_field: true,
|
||||
display_in_profile_summary: true,
|
||||
valid_to_display_in_summary: true,
|
||||
required: false,
|
||||
},
|
||||
can_modify: true,
|
||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||
|
@ -202,6 +209,7 @@ run_test("populate_profile_fields", ({mock_template}) => {
|
|||
is_external_account_field: true,
|
||||
display_in_profile_summary: true,
|
||||
valid_to_display_in_summary: true,
|
||||
required: false,
|
||||
},
|
||||
can_modify: true,
|
||||
realm_default_external_accounts: realm.realm_default_external_accounts,
|
||||
|
|
|
@ -9,6 +9,7 @@ const $ = require("./lib/zjquery");
|
|||
const {current_user} = require("./lib/zpage_params");
|
||||
|
||||
const message_live_update = mock_esm("../src/message_live_update");
|
||||
const navbar_alerts = mock_esm("../src/navbar_alerts");
|
||||
const settings_account = mock_esm("../src/settings_account", {
|
||||
maybe_update_deactivate_account_button() {},
|
||||
update_email() {},
|
||||
|
@ -68,7 +69,7 @@ function initialize() {
|
|||
|
||||
initialize();
|
||||
|
||||
run_test("updates", () => {
|
||||
run_test("updates", ({override}) => {
|
||||
let person;
|
||||
|
||||
const isaac = {
|
||||
|
@ -79,6 +80,7 @@ run_test("updates", () => {
|
|||
};
|
||||
people.add_active_user(isaac);
|
||||
|
||||
override(navbar_alerts, "maybe_show_empty_required_profile_fields_alert", noop);
|
||||
user_events.update_person({
|
||||
user_id: isaac.user_id,
|
||||
role: settings_config.user_role_values.guest.code,
|
||||
|
|
|
@ -25,6 +25,7 @@ def try_add_realm_default_custom_profile_field(
|
|||
realm: Realm,
|
||||
field_subtype: str,
|
||||
display_in_profile_summary: bool = False,
|
||||
required: bool = False,
|
||||
) -> CustomProfileField:
|
||||
field_data = DEFAULT_EXTERNAL_ACCOUNTS[field_subtype]
|
||||
custom_profile_field = CustomProfileField(
|
||||
|
@ -34,6 +35,7 @@ def try_add_realm_default_custom_profile_field(
|
|||
hint=field_data.hint,
|
||||
field_data=orjson.dumps(dict(subtype=field_subtype)).decode(),
|
||||
display_in_profile_summary=display_in_profile_summary,
|
||||
required=required,
|
||||
)
|
||||
custom_profile_field.save()
|
||||
custom_profile_field.order = custom_profile_field.id
|
||||
|
@ -49,12 +51,14 @@ def try_add_realm_custom_profile_field(
|
|||
hint: str = "",
|
||||
field_data: Optional[ProfileFieldData] = None,
|
||||
display_in_profile_summary: bool = False,
|
||||
required: bool = False,
|
||||
) -> CustomProfileField:
|
||||
custom_profile_field = CustomProfileField(
|
||||
realm=realm,
|
||||
name=name,
|
||||
field_type=field_type,
|
||||
display_in_profile_summary=display_in_profile_summary,
|
||||
required=required,
|
||||
)
|
||||
custom_profile_field.hint = hint
|
||||
if custom_profile_field.field_type in (
|
||||
|
@ -101,10 +105,12 @@ def try_update_realm_custom_profile_field(
|
|||
hint: str = "",
|
||||
field_data: Optional[ProfileFieldData] = None,
|
||||
display_in_profile_summary: bool = False,
|
||||
required: bool = False,
|
||||
) -> None:
|
||||
field.name = name
|
||||
field.hint = hint
|
||||
field.display_in_profile_summary = display_in_profile_summary
|
||||
field.required = required
|
||||
if field.field_type in (CustomProfileField.SELECT, CustomProfileField.EXTERNAL_ACCOUNT):
|
||||
if field.field_type == CustomProfileField.SELECT:
|
||||
assert field_data is not None
|
||||
|
|
|
@ -169,6 +169,7 @@ custom_profile_field_type = DictType(
|
|||
("hint", str),
|
||||
("field_data", str),
|
||||
("order", int),
|
||||
("required", bool),
|
||||
],
|
||||
optional_keys=[
|
||||
("display_in_profile_summary", bool),
|
||||
|
|
|
@ -22,6 +22,7 @@ class ProfileDataElementBase(TypedDict, total=False):
|
|||
type: int
|
||||
hint: str
|
||||
display_in_profile_summary: bool
|
||||
required: bool
|
||||
field_data: str
|
||||
order: int
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# Generated by Django 4.2.10 on 2024-03-01 09:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0503_realm_zulip_update_announcements_level"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="customprofilefield",
|
||||
name="required",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
|
@ -77,6 +77,7 @@ class CustomProfileField(models.Model):
|
|||
# Whether the field should be displayed in smaller summary
|
||||
# sections of a page displaying custom profile fields.
|
||||
display_in_profile_summary = models.BooleanField(default=False)
|
||||
required = models.BooleanField(default=False)
|
||||
|
||||
SHORT_TEXT = 1
|
||||
LONG_TEXT = 2
|
||||
|
@ -165,6 +166,7 @@ class CustomProfileField(models.Model):
|
|||
"hint": self.hint,
|
||||
"field_data": self.field_data,
|
||||
"order": self.order,
|
||||
"required": self.required,
|
||||
}
|
||||
if self.display_in_profile_summary:
|
||||
data_as_dict["display_in_profile_summary"] = True
|
||||
|
|
|
@ -1913,6 +1913,7 @@ paths:
|
|||
"hint": "Enter your GitHub username",
|
||||
"field_data": '{"subtype":"github"}',
|
||||
"order": 8,
|
||||
"required": true,
|
||||
},
|
||||
],
|
||||
"id": 0,
|
||||
|
@ -10298,6 +10299,7 @@ paths:
|
|||
"hint": "Enter your GitHub username",
|
||||
"field_data": '{"subtype":"github"}',
|
||||
"order": 8,
|
||||
"required": true,
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
|
@ -10422,6 +10424,18 @@ paths:
|
|||
**Changes**: New in Zulip 6.0 (feature level 146).
|
||||
type: boolean
|
||||
example: true
|
||||
required:
|
||||
description: |
|
||||
Whether an organization administrator has configured this profile field as
|
||||
required.
|
||||
|
||||
Because the required property is mutable, clients cannot assume that a required
|
||||
custom profile field has a value. The Zulip web application displays a prominent
|
||||
banner to any user who has not set a value for a required field.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 244).
|
||||
type: boolean
|
||||
example: true
|
||||
required:
|
||||
- field_type
|
||||
encoding:
|
||||
|
@ -10431,6 +10445,8 @@ paths:
|
|||
contentType: application/json
|
||||
display_in_profile_summary:
|
||||
contentType: application/json
|
||||
required:
|
||||
contentType: application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Success.
|
||||
|
@ -18946,6 +18962,17 @@ components:
|
|||
[profile field types](/help/custom-profile-fields#profile-field-types).
|
||||
|
||||
**Changes**: New in Zulip 6.0 (feature level 146).
|
||||
required:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether an organization administrator has configured this profile field as
|
||||
required.
|
||||
|
||||
Because the required property is mutable, clients cannot assume that a required
|
||||
custom profile field has a value. The Zulip web application displays a prominent
|
||||
banner to any user who has not set a value for a required field.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 244).
|
||||
OnboardingStep:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
|
|
@ -482,6 +482,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
|||
self.assertEqual(field.name, "New phone number")
|
||||
self.assertIs(field.hint, "")
|
||||
self.assertEqual(field.field_type, CustomProfileField.SHORT_TEXT)
|
||||
self.assertEqual(field.required, False)
|
||||
|
||||
result = self.client_patch(
|
||||
f"/json/realm/profile_fields/{field.id}",
|
||||
|
@ -511,12 +512,24 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
|||
msg = 'Argument "display_in_profile_summary" is not valid JSON.'
|
||||
self.assert_json_error(result, msg)
|
||||
|
||||
result = self.client_patch(
|
||||
f"/json/realm/profile_fields/{field.id}",
|
||||
info={
|
||||
"name": "New phone number",
|
||||
"hint": "New contact number",
|
||||
"required": "invalid value",
|
||||
},
|
||||
)
|
||||
msg = 'Argument "required" is not valid JSON.'
|
||||
self.assert_json_error(result, msg)
|
||||
|
||||
result = self.client_patch(
|
||||
f"/json/realm/profile_fields/{field.id}",
|
||||
info={
|
||||
"name": "New phone number",
|
||||
"hint": "New contact number",
|
||||
"display_in_profile_summary": "true",
|
||||
"required": "true",
|
||||
},
|
||||
)
|
||||
self.assert_json_success(result)
|
||||
|
@ -527,6 +540,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
|||
self.assertEqual(field.hint, "New contact number")
|
||||
self.assertEqual(field.field_type, CustomProfileField.SHORT_TEXT)
|
||||
self.assertEqual(field.display_in_profile_summary, True)
|
||||
self.assertEqual(field.required, True)
|
||||
|
||||
result = self.client_patch(
|
||||
f"/json/realm/profile_fields/{field.id}",
|
||||
|
|
|
@ -155,6 +155,7 @@ def create_realm_custom_profile_field(
|
|||
field_data: ProfileFieldData = REQ(default={}, json_validator=check_profile_field_data),
|
||||
field_type: int = REQ(json_validator=check_int),
|
||||
display_in_profile_summary: bool = REQ(default=False, json_validator=check_bool),
|
||||
required: bool = REQ(default=False, json_validator=check_bool),
|
||||
) -> HttpResponse:
|
||||
if display_in_profile_summary and display_in_profile_summary_limit_reached(user_profile.realm):
|
||||
raise JsonableError(
|
||||
|
@ -170,6 +171,7 @@ def create_realm_custom_profile_field(
|
|||
realm=user_profile.realm,
|
||||
field_subtype=field_subtype,
|
||||
display_in_profile_summary=display_in_profile_summary,
|
||||
required=required,
|
||||
)
|
||||
return json_success(request, data={"id": field.id})
|
||||
else:
|
||||
|
@ -180,6 +182,7 @@ def create_realm_custom_profile_field(
|
|||
field_type=field_type,
|
||||
hint=hint,
|
||||
display_in_profile_summary=display_in_profile_summary,
|
||||
required=required,
|
||||
)
|
||||
return json_success(request, data={"id": field.id})
|
||||
except IntegrityError:
|
||||
|
@ -209,6 +212,7 @@ def update_realm_custom_profile_field(
|
|||
hint: str = REQ(default=""),
|
||||
field_data: ProfileFieldData = REQ(default={}, json_validator=check_profile_field_data),
|
||||
display_in_profile_summary: bool = REQ(default=False, json_validator=check_bool),
|
||||
required: bool = REQ(default=False, json_validator=check_bool),
|
||||
) -> HttpResponse:
|
||||
realm = user_profile.realm
|
||||
try:
|
||||
|
@ -246,6 +250,7 @@ def update_realm_custom_profile_field(
|
|||
hint=hint,
|
||||
field_data=field_data,
|
||||
display_in_profile_summary=display_in_profile_summary,
|
||||
required=required,
|
||||
)
|
||||
except IntegrityError:
|
||||
raise JsonableError(_("A field with that label already exists."))
|
||||
|
|
Loading…
Reference in New Issue