Files
58b151b65b MM-65126: Allow admins to change CPA values for users from the system console (#33984)
* Update system_user_detail to enable editing of User Attributes

- This adds a `updateUserCustomProfileAttributesValues` method to client4 to allow updating attributes for a given userID, as a match to getUserCustomProfilesAttributesValues
- Fixed issue in saveCustomProfileAttributes where it used the version that did not take the passed in userId

* Fixes for tests

* Update saveCustomProfileAttribute mock in users.test.ts

The `saveCustomProfileAttribute` test was still using the old getCustomProfileAttributeValuesRoute instead of the same route that `getCustomProfileAttributeValues` uses.

* Adding e2e tests for editing User attributes (custom and system) in System Console

* Fix testing issue with selector for 'Authentication Method'

* workflows/server-ci-report.yml: security fixes by validating inputs (#33892)

* [MM-61899] Properly restrict users who previously shared a team from DMs/GMs when they no longer share a team. (#30094)

* [MM-61899] Properly restrict users who previously shared a team from DMs/GMs when they no longer share a team.

* Fix checks

* Fix test

* Fix i18n

* Added E2E tests

* Merge'd

* Add restricted DM check to more places

* Merge'd

* Restrict patching the channel (updating the channel)

* Update verbiage in the admin console

* Fix lint

* More tests

---------

Co-authored-by: Mattermost Build <[email protected]>

* Mirror Postgres 14 docker image (#34009)

* ugprade to go 1.24.6 (#34004)

* Bump Postgres minimum supported version to 14 (#34010)

* Downgrade French language (#33826)

* Downgrade French language

* Update i18n.jsx

* Updates buildFieldAttrs to preseve existing attrs when editing a field (#33991)

* Updates buildFieldAttrs to preseve existing attrs when editing a field

* Fix preserve option issue for select/multiselect type fields

* Fix linter

---------

Co-authored-by: Miguel de la Cruz <[email protected]>

* MM-64423 - Removing the clear func on switch (#31214)

* MM-64423 - Removing the clear func on switch

* Select all teams by default

* Updating test

* Updating test

* Removing showFilterHaveBeenReset

* Removing search string

* Do not clear the search term

* Updating tests

---------

Co-authored-by: Mattermost Build <[email protected]>

* Mm 65123 remove channel abac ff (#33953)

* MM-65123 - remove channel abac feature flag

* enable the channel scope access control to true

* fix linters

* adjust expected error in tests

* remove no longer needed comment

* Remove write_restrictable from core ABAC settings and fix channel access control logic

---------

Co-authored-by: Mattermost Build <[email protected]>

* [MM-65837], [MM-65824] - Update Dependencies (#33972)

* Update github.com/mholt/archives

* Update github.com/spf13/viper

* make batch migration worker tests less flaky

---------

Co-authored-by: Jesse Hallam <[email protected]>

* (a11y-test): team menu (#33998)

* MM-66071: Do not error on empty slice in /groups/names (#34021)

* Do not error on empty slice in /groups/names

If group_names is an empty slice, this should not be an invalid
parameter. We return what we were asked for: an empty array.

* Avoid requests to /groups/names if list is empty

If the list of group names is empty, we do not need to ask the server
for the corresponding groups: we already know it'll be an empty list.

* Fix ABAC not available for entry (#34027)

Automatic Merge

* Explicitly name Postgres container volume (#33954)

* Explicitly name Postgres container volume

* Remove unused server/docker-compose.yaml

This file doesn't seem to actually be used. When we run docker compose locally,
it uses docker-compose.makefile.yml merged with the output of
build/docker-compose-generator/main.go.

* Revert "Remove unused server/docker-compose.yaml"

This reverts commit 5a4596521752585eba7153787abb5cb65d88dc33.

* Update volume name

* Flag post API (#33765)

* Added enable/disable setting and feature flag

* added rest of notifgication settings

* Added backend for content flagging setting and populated notification values from server side defaults

* WIP user selector

* Added common reviewers UI

* Added additonal reviewers section

* WIP

* WIP

* Team table base

* Added search in teams

* Added search in teams

* Added additional settings section

* WIP

* Inbtegrated reviewers settings

* WIP

* WIP

* Added server side validation

* cleanup

* cleanup

* [skip ci]

* Some refactoring

* type fixes

* lint fix

* test: add content flagging settings test file

* test: add comprehensive unit tests for content flagging settings

* enhanced tests

* test: add test file for content flagging additional settings

* test: add comprehensive unit tests for ContentFlaggingAdditionalSettingsSection

* Added additoonal settings test

* test: add empty test file for team reviewers section

* test: add comprehensive unit tests for TeamReviewersSection component

* test: update tests to handle async data fetching in team reviewers section

* test: add empty test file for content reviewers component

* feat: add comprehensive unit tests for ContentFlaggingContentReviewers component

* Added ContentFlaggingContentReviewersContentFlaggingContentReviewers test

* test: add notification settings test file for content flagging

* test: add comprehensive unit tests for content flagging notification settings

* Added ContentFlaggingNotificationSettingsSection tests

* test: add user profile pill test file

* test: add comprehensive unit tests for UserProfilePill component

* refactor: Replace enzyme shallow with renderWithContext in user_profile_pill tests

* Added UserProfilePill tests

* test: add empty test file for content reviewers team option

* test: add comprehensive unit tests for TeamOptionComponent

* Added TeamOptionComponent tests

* test: add empty test file for reason_option component

* test: add comprehensive unit tests for ReasonOption component

* Added ReasonOption tests

* cleanup

* Fixed i18n error

* fixed e2e test lijnt issues

* Updated test cases

* Added snaoshot

* Updated snaoshot

* lint fix

* WIP

* lint fix

* Added post flagging properties setup

* review fixes

* updated snapshot

* CI

* Added base APIs

* Fetched team status data on load and team switch

* WIP

* Review fixes

* wip

* WIP

* Removed an test, updated comment

* CI

* Added tests

* Added tests

* Lint fix

* Added API specs

* Fixed types

* CI fixes

* API tests

* lint fixes

* Set env variable so API routes are regiustered

* Test update

* term renaming and disabling API tests on MySQL

* typo

* Updated store type definition

* Minor tweaks

* Added tests

* Removed error in app startup when content flaghging setup fails

* Updated sync condition:

* Flag message modal basE

* added post preview

* displaying options

* Adde comment input

* Updated tests and docs

* finction rename

* WIP

* Updated tests

* refactor

* lint fix

* MOved to data migration

* lint fix

* CI

* added new migration mocks

* Used setup for tests

* some comment

* Removed unnecesseery nil check

* Form validation

* WIP tests

* WIP tests

* WIP tests

* fix: mock content flagging config selector with correct reasons format

* fix: add mock for getContentFlaggingConfig in flag post modal test

* Updated error code order in API docs

* removed empty files

* Added tests

* lint fixes

* minor tweak

* lint fix

* type fix

* fixed test

* nit

* test enhancements

* API WIP

* API WIP

* creating values

* creating content flagging channel and properties

* Able to save properties

* Added another property field

* WIP

* WIP

* Added validations

* Added data validations and hidden post if confifgured to

* lint fixes

* Added API spec

* Added some tests

* Added tests for getContentReviewBot

* test: add comprehensive tests for getContentReviewChannels function

* Added more app layer tests

* Added TestCanFlagPost

* test: Add comprehensive tests for FlagPost function

* Added all app layer tests

* Removed a file that was reamoved downstream

* test: add content flagging test file

* test: add comprehensive tests for FlagContentRequest.IsValid method

* Added model tests

* test: add comprehensive tests for SqlPropertyValueStore.CreateMany

* test: add comprehensive tests for flagPost() API function

* Added API tests

* linter fix

* WIP

* sent post flagging confirmation message

* fixed i18n nissues

* fixed i18n nissues

* CI

* Updated test

* fix: reset contentFlaggingGroupId for test isolation in content flagging tests

* removed cached group ID

* removed debug log

* review fixes

* Used correct ot name

* CI

* Updated mobile text

* Handled JSON error

* fixerdf i18n

* CI

* Integrate flag post api (#33798)

* WIP

* WIP

* Added API call

* test: add test for Client4.flagPost API call in FlagPostModal

* fix: remove userEvent.setup() from flag post modal test

* test: wrap submit button click in act for proper state updates

* Updated tests

* lint fix

* CI

* Updated to allow special characters in comments

* Handled empty comment

* Used finally

* CI

* Fixed test

* Spillage card integration (#33832)

* Created getContentFlaggingFields API

* created getPostPropertyValues API

* WIP

* Created useContentFlaggingFields hook

* WIP

* WIP

* Added option to retain data for reviewers

* Displayed deleted post's preview

* DIsplayed all properties

* Adding field name i18n

* WIP - managing i18n able texts

* Finished displaying all fields

* Manual cleanup

* lint fixes

* team role filter logic fix

* Fixed tests

* created new API to fetch flagged posts

* lint fix

* Added new client methods

* test: add comprehensive tests for content flagging APIs

* Added new API tests

* fixed openapi spec

* Fixed DataSpillageReport tests

* Fixed PostMarkdown test

* Fixed PostPreviewPropertyRenderer test

* Added metadata to card renderer

* test fixes

* Added no comment placeholder

* Fixed test

* refactor: improve test mocking for data spillage report component

* test mock updates

* Updated reducer

* not resetting mocks

* WIP

* review fixes

* CI

* Fixed

* fixes

* Content flagging actions implementation (#33852)

* Added view detail button

* Created RemoveFlaggedMessageConfirmationModal modal

* Added key and remove flag request modal

* IMplemented delete flagged post

* Handled edge cases of deleting flagged post

* keep message

* UI integration

* Added WS event for post report update and handled deleted files of flagged post

* Added error handling in keep/remove forms

* i18n fixes

* Updated OpenAPI specs

* fixed types

* fixed types

* refactoring

* Fixed tests

* review fixes

* Added new property translations

* Improved test

* fixed test

* CI

* fixes

* CI

* fixed a test

* CI

---------

* GH-34031: Add accessibility tests for Sidebar settings panel (#34041)

* Add accessibility tests for Sidebar settings panel

* Add aria snapshots and fix accessibility test expectations for sidebar settings

* Update system_user_detail to enable editing of User Attributes

- This adds a `updateUserCustomProfileAttributesValues` method to client4 to allow updating attributes for a given userID, as a match to getUserCustomProfilesAttributesValues
- Fixed issue in saveCustomProfileAttributes where it used the version that did not take the passed in userId

* Fixes for tests

* Update saveCustomProfileAttribute mock in users.test.ts

The `saveCustomProfileAttribute` test was still using the old getCustomProfileAttributeValuesRoute instead of the same route that `getCustomProfileAttributeValues` uses.

* Adding e2e tests for editing User attributes (custom and system) in System Console

* Fix testing issue with selector for 'Authentication Method'

* Fixes from PR suggestions

* Fix lint/export issues

* Fix playwright lint issue halting e2e smoke test

* Results from npm run prettier:fix (in e2e-tests/playwright folder)

---------

Co-authored-by: Mattermost Build <[email protected]>
Co-authored-by: Ibrahim Serdar Acikgoz <[email protected]>

Co-authored-by: Alejandro García Montoro <[email protected]>
Co-authored-by: Jesse Hallam <[email protected]>
Co-authored-by: Amy Blais <[email protected]>
Co-authored-by: Miguel de la Cruz <[email protected]>
Co-authored-by: Miguel de la Cruz <[email protected]>
Co-authored-by: Asaad Mahmood <[email protected]>
Co-authored-by: Pablo Vélez <[email protected]>
Co-authored-by: Eva Sarafianou <[email protected]>
Co-authored-by: Jesse Hallam <[email protected]>
Co-authored-by: sabril <[email protected]>
Co-authored-by: Maria A Nunez <[email protected]>
Co-authored-by: Harrison Healey <[email protected]>
Co-authored-by: Harshil Sharma <[email protected]>

Co-authored-by: Mudit Sharma <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2025-10-06 15:41:29 -07:00

486 lines
17 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Page} from '@playwright/test';
import {Client4} from '@mattermost/client';
import {UserPropertyField, UserPropertyFieldPatch, FieldType} from '@mattermost/types/properties';
import {expect, ChannelsPage} from '@mattermost/playwright-lib';
// Common test data constants
export const TEST_PHONE = '555-123-4567';
export const TEST_UPDATED_PHONE = '555-987-6543';
export const TEST_URL = 'https://example.com';
export const TEST_UPDATED_URL = 'https://mattermost.com';
export const TEST_INVALID_URL = 'ftp://invalid-url';
export const TEST_VALID_URL = 'https://example2.com';
export const TEST_DEPARTMENT = 'Engineering';
export const TEST_UPDATED_DEPARTMENT = 'Product';
export const TEST_LOCATION = 'Remote';
export const TEST_UPDATED_LOCATION = 'Office';
export const TEST_TITLE = 'Software Engineer';
export const TEST_CHANGED_VALUE = 'Changed Value';
export const TEST_MESSAGE = 'Hello from the test user';
export const TEST_MESSAGE_OTHER = 'Hello from the other user';
export const TEST_LOCATION_OPTIONS = [
{name: 'Remote', color: '#00FFFF'},
{name: 'Office', color: '#FF00FF'},
{name: 'Hybrid', color: '#FFFF00'},
];
export const TEST_SKILLS_OPTIONS = [
{name: 'JavaScript', color: '#F0DB4F'},
{name: 'React', color: '#61DAFB'},
{name: 'Node.js', color: '#68A063'},
{name: 'Python', color: '#3776AB'},
];
/**
* Represents a custom profile attribute definition
*/
export type CustomProfileAttribute = {
name: string;
value?: string;
type: string;
options?: {name: string; color: string; sort_order?: number}[];
attrs?: {
value_type?: string;
visibility?: string;
options?: {name: string; color: string}[];
};
};
// Custom attribute definitions for user settings tests (with select/multiselect attributes)
export const userSettingsAttributes: CustomProfileAttribute[] = [
{
name: 'Department',
value: TEST_DEPARTMENT,
type: 'text',
},
{
name: 'Location',
type: 'select',
options: TEST_LOCATION_OPTIONS,
},
{
name: 'Skills',
type: 'multiselect',
options: TEST_SKILLS_OPTIONS,
},
{
name: 'Phone',
value: TEST_PHONE,
type: 'text',
attrs: {
value_type: 'phone',
},
},
{
name: 'Website',
value: TEST_URL,
type: 'text',
attrs: {
value_type: 'url',
},
},
];
// Custom attribute definitions for custom attributes tests (with text attributes and Title)
export const customAttributesTestData: CustomProfileAttribute[] = [
{
name: 'Department',
value: TEST_DEPARTMENT,
type: 'text',
},
{
name: 'Location',
value: TEST_LOCATION,
type: 'text',
},
{
name: 'Title',
value: TEST_TITLE,
type: 'text',
},
{
name: 'Phone',
value: TEST_PHONE,
type: 'text',
attrs: {
value_type: 'phone',
},
},
{
name: 'Website',
value: TEST_URL,
type: 'text',
attrs: {
value_type: 'url',
},
},
];
/**
* Helper function to get field ID by name
* @param {Object} fieldsMap - Map of field IDs to field objects
* @param {string} name - The name of the field to find
* @returns {string} - The field ID
*/
export function getFieldIdByName(fieldsMap: Record<string, UserPropertyField>, name: string): string {
for (const [id, field] of Object.entries(fieldsMap)) {
if (field.name === name) {
return id;
}
}
throw new Error(`Could not find field ID for attribute: ${name}`);
}
/**
* Helper function to edit a text attribute
* @param {Page} page - The Playwright page object
* @param {Object} fieldsMap - Map of field IDs to field objects
* @param {string} attributeName - The name of the attribute to edit
* @param {string} newValue - The new value to set
*/
export async function editTextAttribute(
page: Page,
fieldsMap: Record<string, UserPropertyField>,
attributeName: string,
newValue: string,
): Promise<void> {
const fieldId = getFieldIdByName(fieldsMap, attributeName);
await page.locator(`text=${attributeName}`).scrollIntoViewIfNeeded();
await page.locator(`#customAttribute_${fieldId}Edit`).scrollIntoViewIfNeeded();
await page.locator(`#customAttribute_${fieldId}Edit`).click();
await page.locator(`#customAttribute_${fieldId}`).scrollIntoViewIfNeeded();
await page.locator(`#customAttribute_${fieldId}`).clear();
if (newValue) {
await page.locator(`#customAttribute_${fieldId}`).fill(newValue);
}
await page.locator('button:has-text("Save")').click();
}
/**
* Helper function to edit a select attribute
* @param {Page} page - The Playwright page object
* @param {Object} fieldsMap - Map of field IDs to field objects
* @param {string} attributeName - The name of the attribute to edit
* @param {number} optionIndex - The index of the option to select
*/
export async function editSelectAttribute(
page: Page,
fieldsMap: Record<string, UserPropertyField>,
attributeName: string,
optionIndex: number,
): Promise<void> {
const fieldId = getFieldIdByName(fieldsMap, attributeName);
await page.locator(`text=${attributeName}`).scrollIntoViewIfNeeded();
await page.locator(`#customAttribute_${fieldId}Edit`).scrollIntoViewIfNeeded();
await page.locator(`#customAttribute_${fieldId}Edit`).click();
await page.locator(`#customProfileAttribute_${fieldId}`).scrollIntoViewIfNeeded();
await page.locator(`#customProfileAttribute_${fieldId}`).click();
await page.locator(`#react-select-2-option-${optionIndex}`).click();
await page.locator('button:has-text("Save")').click();
}
/**
* Helper function to edit a multiselect attribute
* @param {Page} page - The Playwright page object
* @param {Object} fieldsMap - Map of field IDs to field objects
* @param {string} attributeName - The name of the attribute to edit
* @param {Array<number>} optionIndices - The indices of the options to select
*/
export async function editMultiselectAttribute(
page: Page,
fieldsMap: Record<string, UserPropertyField>,
attributeName: string,
optionIndices: number[],
): Promise<void> {
const fieldId = getFieldIdByName(fieldsMap, attributeName);
await page.locator(`text=${attributeName}`).scrollIntoViewIfNeeded();
await page.locator(`#customAttribute_${fieldId}Edit`).scrollIntoViewIfNeeded();
await page.locator(`#customAttribute_${fieldId}Edit`).click();
for (const index of optionIndices) {
await page.waitForTimeout(500); // Wait for the dropdown to stabilize
await page.locator(`#customProfileAttribute_${fieldId}`).scrollIntoViewIfNeeded();
await page.locator(`#customProfileAttribute_${fieldId}`).click();
await page.locator(`#react-select-3-option-${index}`).click();
}
await page.locator('button:has-text("Save")').click();
await page.waitForTimeout(500); // Wait for save to complete
}
/**
* Helper function to verify an attribute exists in the profile settings
* @param {Page} page - The Playwright page object
* @param {Array} attributes - Array of attribute objects with name
*/
export async function verifyAttributesExistInSettings(page: Page, attributes: CustomProfileAttribute[]): Promise<void> {
for (const attribute of attributes) {
await page.locator(`text=${attribute.name}`).scrollIntoViewIfNeeded();
await expect(page.locator(`.user-settings:has-text("${attribute.name}")`)).toBeVisible();
}
}
/**
* Helper function to verify an attribute is displayed in the profile popover
* @param {ChannelsPage} channelsPage - The Playwright channels page object
* @param {string} attributeName - The name of the attribute to verify
* @param {string} attributeValue - The value of the attribute to verify
*/
export async function verifyAttributeInPopover(
channelsPage: ChannelsPage,
attributeName: string,
attributeValue: string,
): Promise<void> {
const popover = channelsPage.userProfilePopover.container;
// Check for the attribute name
const nameElement = popover.getByText(attributeName, {exact: false});
await expect(nameElement).toBeVisible();
// Check for the attribute value
const valueElement = popover.getByText(attributeValue, {exact: false});
await expect(valueElement).toBeVisible();
}
/**
* Helper function to verify an attribute is not displayed in the profile popover
* @param {ChannelsPage} channelsPage - The Playwright channels page object
* @param {string} attributeName - The name of the attribute to verify
*/
export async function verifyAttributeNotInPopover(channelsPage: ChannelsPage, attributeName: string): Promise<void> {
const popover = channelsPage.userProfilePopover.container;
// Check that the attribute name is not present
const nameElement = popover.getByText(attributeName, {exact: false});
await expect(nameElement).not.toBeVisible();
}
/**
* Updates the visibility property of a custom profile attribute field
* @param {Client4} adminClient - Admin API client
* @param {Object} fieldsMap - Map of field IDs to field objects
* @param {string} attributeName - The name of the attribute to update
* @param {string} visibility - The visibility value to set ('when_set', 'hidden', or 'always')
*/
export async function updateCustomProfileAttributeVisibility(
adminClient: Client4,
fieldsMap: Record<string, UserPropertyField>,
attributeName: string,
visibility: 'when_set' | 'hidden' | 'always',
): Promise<void> {
const fieldID = getFieldIdByName(fieldsMap, attributeName);
try {
// Update the visibility property
const updatedField = await adminClient.patchCustomProfileAttributeField(fieldID, {
// @ts-expect-error The type definition requires more properties than we need to set
attrs: {
visibility,
},
});
// Update the fieldsMap with the updated field
fieldsMap[updatedField.id] = updatedField;
} catch (error) {
// eslint-disable-next-line no-console
console.log(`Failed to update visibility for attribute ${attributeName}:`, error);
}
}
/**
* Sets up custom profile attributes fields
* @param {Client4} adminClient - Admin API client
* @param {Array} attributes - Array of attribute objects with name and value
* @returns {Promise<Object>} - A promise that resolves to a map of field IDs to field objects
*/
export async function setupCustomProfileAttributeFields(
adminClient: Client4,
attributes: CustomProfileAttribute[],
): Promise<Record<string, UserPropertyField>> {
const fieldsMap: Record<string, UserPropertyField> = {};
// Create the attribute fields array
const attributeFields: UserPropertyFieldPatch[] = attributes.map((attr, index) => {
// Start with basic field properties
const field: UserPropertyFieldPatch = {
name: attr.name,
type: (attr.type as FieldType) || 'text',
// @ts-expect-error @mattermost/types needs to be updated
attrs: {
sort_order: index,
},
};
// Add options for select and multiselect fields
if ((attr.type === 'select' || attr.type === 'multiselect') && attr.options) {
// @ts-expect-error @mattermost/types needs to be updated
field.attrs.options = attr.options;
}
// Add any additional attributes if provided
if (attr.attrs) {
// @ts-expect-error @mattermost/types needs to be updated
field.attrs = {
...field.attrs,
...attr.attrs,
};
}
return field;
});
// Get existing fields
try {
const existingFields = await adminClient.getCustomProfileAttributeFields();
// If fields exist, use them
if (existingFields && existingFields.length > 0) {
for (const field of existingFields) {
fieldsMap[field.id] = field;
}
return fieldsMap;
}
} catch (error) {
// If request fails, continue to create new fields
// eslint-disable-next-line no-console
console.log('Error getting existing custom profile fields, will create new ones', error);
}
// Create fields sequentially
for (const field of attributeFields) {
try {
const createdField = await adminClient.createCustomProfileAttributeField(field);
fieldsMap[createdField.id] = createdField;
} catch (error) {
// eslint-disable-next-line no-console
console.log(`Failed to create field ${field.name}:`, error);
}
}
return fieldsMap;
}
/**
* Sets up custom profile attribute values for the current user
* @param {Client4} userClient - User client object
* @param {Array} attributes - Array of attribute objects with name and value
* @param {Object} fields - Map of field IDs to field objects
*/
export async function setupCustomProfileAttributeValues(
userClient: Client4,
attributes: CustomProfileAttribute[],
fields: Record<string, UserPropertyField>,
): Promise<void> {
// Create a map of attribute values by field ID
const valuesByFieldId: Record<string, string | string[]> = {};
for (const attr of attributes) {
let fieldID = '';
// Find the field ID for this attribute name
for (const [id, field] of Object.entries(fields)) {
if (field.name === attr.name) {
fieldID = id;
break;
}
}
// If we found a matching field, add it to our values object
if (fieldID && attr.value) {
valuesByFieldId[fieldID] = attr.value;
}
}
// Only make the API call if we have values to set
if (Object.keys(valuesByFieldId).length > 0) {
try {
await userClient.updateCustomProfileAttributeValues(valuesByFieldId);
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed to set attribute values:', error);
}
}
}
/**
* Sets up custom profile attribute values for a specific user (admin function)
* @param {Client4} adminClient - Admin client object
* @param {Array} attributes - Array of attribute objects with name and value
* @param {Object} fields - Map of field IDs to field objects
* @param {string} targetUserId - ID of the user to set values for
*/
export async function setupCustomProfileAttributeValuesForUser(
adminClient: Client4,
attributes: CustomProfileAttribute[],
fields: Record<string, UserPropertyField>,
targetUserId: string,
): Promise<void> {
// Create a map of attribute values by field ID
const valuesByFieldId: Record<string, string | string[]> = {};
for (const attr of attributes) {
let fieldID = '';
// Find the field ID for this attribute name
for (const [id, field] of Object.entries(fields)) {
if (field.name === attr.name) {
fieldID = id;
break;
}
}
// If we found a matching field, add it to our values object
if (fieldID && attr.value) {
valuesByFieldId[fieldID] = attr.value;
}
}
// Only make the API call if we have values to set
if (Object.keys(valuesByFieldId).length > 0) {
try {
// Use the admin client method for updating other user's values
await adminClient.updateUserCustomProfileAttributesValues(targetUserId, valuesByFieldId);
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed to set attribute values for user:', error);
}
}
}
/**
* Deletes all custom profile attributes
* @param {Client4} adminClient - Admin API client
* @param {Object} attributes - Map of field IDs to field objects
*/
export async function deleteCustomProfileAttributes(
adminClient: Client4,
attributes: Record<string, UserPropertyField>,
): Promise<void> {
// Delete each field
for (const id of Object.keys(attributes)) {
try {
await adminClient.deleteCustomProfileAttributeField(id);
} catch (error) {
// eslint-disable-next-line no-console
console.log(`Failed to delete field ${id}:`, error);
}
}
// Verify deletion was successful
try {
const response = await adminClient.getCustomProfileAttributeFields();
if (response && response.length > 0) {
// eslint-disable-next-line no-console
console.log('Warning: Not all custom profile attributes were deleted');
}
} catch (error) {
// eslint-disable-next-line no-console
console.log('Error checking if all fields were deleted:', error);
}
}