custom_profile_fields: Support non editable profile fields.

This commit allows configuration of "editable_by_user" property from the
organization settings modal. It also adds support for non-editable
fields in profile settings modal.

Fixes #22883.

Co-Authored-By: Ujjawal Modi <umodi2003@gmail.com>
This commit is contained in:
tnmkr 2024-08-15 20:08:27 +05:30 committed by Tim Abbott
parent fc5cdd9e83
commit 23efb5cec7
11 changed files with 86 additions and 11 deletions

View File

@ -98,6 +98,26 @@ it out.
{end_tabs} {end_tabs}
## Configure whether users can edit custom profile fields
{!admin-only.md!}
You can configure whether users in your organization can modify their
own custom profile fields. For example, you may want to restrict editing
if syncing profile fields from an employee directory.
{start_tabs}
{settings_tab|profile-field-settings}
1. In the **Actions** column, click the **pencil** (<i class="fa fa-pencil"></i>)
icon for the profile field you want to configure.
1. Toggle **Users can edit this field**.
4. Click **Save changes**.
{end_tabs}
## Profile field types ## Profile field types

View File

@ -10,7 +10,7 @@ import {$t} from "./i18n";
import * as people from "./people"; import * as people from "./people";
import * as pill_typeahead from "./pill_typeahead"; import * as pill_typeahead from "./pill_typeahead";
import * as settings_components from "./settings_components"; import * as settings_components from "./settings_components";
import {realm} from "./state_data"; import {current_user, realm} from "./state_data";
import * as typeahead_helper from "./typeahead_helper"; import * as typeahead_helper from "./typeahead_helper";
import type {UserPillWidget} from "./user_pill"; import type {UserPillWidget} from "./user_pill";
import * as user_pill from "./user_pill"; import * as user_pill from "./user_pill";
@ -38,6 +38,7 @@ export function append_custom_profile_fields(element_id: string, user_id: number
for (const field of all_custom_fields) { for (const field of all_custom_fields) {
let field_value = people.get_custom_profile_data(user_id, field.id); let field_value = people.get_custom_profile_data(user_id, field.id);
const editable_by_user = current_user.is_admin || field.editable_by_user;
const is_select_field = field.type === all_field_types.SELECT.id; const is_select_field = field.type === all_field_types.SELECT.id;
const field_choices = []; const field_choices = [];
@ -70,6 +71,7 @@ export function append_custom_profile_fields(element_id: string, user_id: number
field_choices, field_choices,
for_manage_user_modal: element_id === "#edit-user-form .custom-profile-field-form", for_manage_user_modal: element_id === "#edit-user-form .custom-profile-field-form",
is_empty_required_field: field.required && !field_value.value, is_empty_required_field: field.required && !field_value.value,
editable_by_user,
}); });
$(element_id).append($(html)); $(element_id).append($(html));
} }

View File

@ -242,6 +242,7 @@ function open_custom_profile_field_form_modal(): void {
":checked", ":checked",
), ),
required: $("#profile-field-required").is(":checked"), required: $("#profile-field-required").is(":checked"),
editable_by_user: $("#profile_field_editable_by_user").is(":checked"),
}; };
const url = "/json/realm/profile_fields"; const url = "/json/realm/profile_fields";
const opts = { const opts = {
@ -461,6 +462,7 @@ function open_edit_form_modal(this: HTMLElement): void {
choices, choices,
display_in_profile_summary: field.display_in_profile_summary === true, display_in_profile_summary: field.display_in_profile_summary === true,
required: field.required, required: field.required,
editable_by_user: field.editable_by_user,
is_select_field: field.type === field_types.SELECT.id, is_select_field: field.type === field_types.SELECT.id,
is_external_account_field: field.type === field_types.EXTERNAL_ACCOUNT.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), valid_to_display_in_summary: is_valid_to_display_in_summary(field.type),

View File

@ -34,6 +34,7 @@ export type NarrowTerm = z.output<typeof narrow_term_schema>;
export const custom_profile_field_schema = z.object({ export const custom_profile_field_schema = z.object({
display_in_profile_summary: z.optional(z.boolean()), display_in_profile_summary: z.optional(z.boolean()),
editable_by_user: z.boolean(),
field_data: z.string(), field_data: z.string(),
hint: z.string(), hint: z.string(),
id: z.number(), id: z.number(),

View File

@ -762,6 +762,18 @@ export function initialize(): void {
}, },
}); });
tippy.delegate("body", {
target: ".settings-profile-user-field.not-editable-by-user-input-wrapper",
content: $t({
defaultMessage:
"You are not allowed to change this field. Contact an administrator to update it.",
}),
appendTo: () => document.body,
onHidden(instance) {
instance.destroy();
},
});
tippy.delegate("body", { tippy.delegate("body", {
target: ".popover-contains-shift-hotkey", target: ".popover-contains-shift-hotkey",
trigger: "mouseenter", trigger: "mouseenter",

View File

@ -409,6 +409,10 @@
.pill-container { .pill-container {
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
&.not-editable-by-user {
opacity: 0.5;
}
} }
.settings-profile-user-field { .settings-profile-user-field {

View File

@ -164,6 +164,19 @@
perspective: 1000px; perspective: 1000px;
} }
} }
&.not-editable-by-user {
cursor: not-allowed;
background-color: hsl(0deg 0% 93%);
.pill {
cursor: not-allowed;
}
.exit {
display: none;
}
}
} }
#compose-direct-recipient .pill-container { #compose-direct-recipient .pill-container {

View File

@ -1545,6 +1545,10 @@ $option_title_width: 180px;
.datepicker { .datepicker {
cursor: default; cursor: default;
} }
& input[disabled].datepicker {
cursor: not-allowed;
}
} }
#show_my_user_profile_modal { #show_my_user_profile_modal {
@ -1988,6 +1992,11 @@ $option_title_width: 180px;
cursor: default; cursor: default;
opacity: 0.7; opacity: 0.7;
} }
/* Needed for settings_checkbox partial. */
.inline {
display: inline;
}
} }
#admin_users_table .deactivated_user, #admin_users_table .deactivated_user,

View File

@ -50,5 +50,11 @@
{{t 'Required field' }} {{t 'Required field' }}
</label> </label>
</div> </div>
{{> settings_checkbox
prefix="profile_field_"
setting_name="editable_by_user"
is_checked=true
label=(t "Users can edit this field")
}}
</div> </div>
</form> </form>

View File

@ -5,30 +5,30 @@
</span> </span>
<div class="alert-notification custom-field-status"></div> <div class="alert-notification custom-field-status"></div>
<div class="settings-profile-user-field-hint">{{ field.hint }}</div> <div class="settings-profile-user-field-hint">{{ field.hint }}</div>
<div class="settings-profile-user-field {{#if is_empty_required_field}}empty-required-field{{/if}}"> <div class="settings-profile-user-field {{#if is_empty_required_field}}empty-required-field{{/if}} {{#unless editable_by_user}}not-editable-by-user-input-wrapper{{/unless}}">
{{#if is_long_text_field}} {{#if is_long_text_field}}
<textarea maxlength="500" class="custom_user_field_value settings_textarea" name="{{ field.id }}">{{ field_value.value }}</textarea> <textarea maxlength="500" class="custom_user_field_value settings_textarea" name="{{ field.id }}" {{#unless editable_by_user}}disabled{{/unless}}>{{ field_value.value }}</textarea>
{{else if is_select_field}} {{else if is_select_field}}
<select class="custom_user_field_value {{#if for_manage_user_modal}}modal_select{{else}}settings_select{{/if}} bootstrap-focus-style" name="{{ field.id }}"> <select class="custom_user_field_value {{#if for_manage_user_modal}}modal_select{{else}}settings_select{{/if}} bootstrap-focus-style" name="{{ field.id }}" {{#unless editable_by_user}}disabled{{/unless}}>
<option value=""></option> <option value=""></option>
{{#each field_choices}} {{#each field_choices}}
<option value="{{ this.value }}" {{#if this.selected}}selected{{/if}}>{{ this.text }}</option> <option value="{{ this.value }}" {{#if this.selected}}selected{{/if}}>{{ this.text }}</option>
{{/each}} {{/each}}
</select> </select>
{{else if is_user_field }} {{else if is_user_field }}
<div class="pill-container person_picker" name="{{ field.id }}"> <div class="pill-container person_picker {{#unless editable_by_user}}not-editable-by-user disabled{{/unless}}" name="{{ field.id }}">
<div class="input" contenteditable="true"></div> <div class="input" {{#if editable_by_user}}contenteditable="true"{{/if}}></div>
</div> </div>
{{else if is_date_field }} {{else if is_date_field }}
<input class="custom_user_field_value datepicker {{#if for_manage_user_modal}}modal_text_input{{else}}settings_text_input{{/if}}" name="{{ field.id }}" data-field-id="{{ field.id }}" type="text" <input class="custom_user_field_value datepicker {{#if for_manage_user_modal}}modal_text_input{{else}}settings_text_input{{/if}}" name="{{ field.id }}" data-field-id="{{ field.id }}" type="text"
value="{{ field_value.value }}" /> value="{{ field_value.value }}" {{#unless editable_by_user}}disabled{{/unless}}/>
<span class="remove_date"><i class="fa fa-close"></i></span> {{#if editable_by_user}}<span class="remove_date"><i class="fa fa-close"></i></span>{{/if}}
{{else if is_url_field }} {{else if is_url_field }}
<input class="custom_user_field_value {{#if for_manage_user_modal}}modal_url_input{{else}}settings_url_input{{/if}}" name="{{ field.id }}" type="{{ field_type }}" value="{{ field_value.value }}" maxlength="2048" /> <input class="custom_user_field_value {{#if for_manage_user_modal}}modal_url_input{{else}}settings_url_input{{/if}}" name="{{ field.id }}" type="{{ field_type }}" value="{{ field_value.value }}" maxlength="2048" {{#unless editable_by_user}}disabled{{/unless}}/>
{{else if is_pronouns_field}} {{else if is_pronouns_field}}
<input class="custom_user_field_value pronouns_type_field {{#if for_manage_user_modal}}modal_text_input{{else}}settings_text_input{{/if}}" name="{{ field.id }}" type="{{ field_type }}" value="{{ field_value.value }}" maxlength="50" /> <input class="custom_user_field_value pronouns_type_field {{#if for_manage_user_modal}}modal_text_input{{else}}settings_text_input{{/if}}" name="{{ field.id }}" type="{{ field_type }}" value="{{ field_value.value }}" maxlength="50" {{#unless editable_by_user}}disabled{{/unless}}/>
{{else}} {{else}}
<input class="custom_user_field_value {{#if for_manage_user_modal}}modal_text_input{{else}}settings_text_input{{/if}}" name="{{ field.id }}" type="{{ field_type }}" value="{{ field_value.value }}" maxlength="50" /> <input class="custom_user_field_value {{#if for_manage_user_modal}}modal_text_input{{else}}settings_text_input{{/if}}" name="{{ field.id }}" type="{{ field_type }}" value="{{ field_value.value }}" maxlength="50" {{#unless editable_by_user}}disabled{{/unless}}/>
{{/if}} {{/if}}
</div> </div>
</div> </div>

View File

@ -52,5 +52,11 @@
{{t 'Required field' }} {{t 'Required field' }}
</label> </label>
</div> </div>
{{> settings_checkbox
prefix="id-custom-profile-field-"
setting_name="editable-by-user"
is_checked= editable_by_user
label=(t "Users can edit this field")
}}
</form> </form>
{{/with}} {{/with}}