diff --git a/e2e-tests/playwright/specs/functional/channels/custom_profile_attributes/custom_attributes.spec.ts b/e2e-tests/playwright/specs/functional/channels/custom_profile_attributes/custom_attributes.spec.ts
index b57116b3435..1754589383c 100644
--- a/e2e-tests/playwright/specs/functional/channels/custom_profile_attributes/custom_attributes.spec.ts
+++ b/e2e-tests/playwright/specs/functional/channels/custom_profile_attributes/custom_attributes.spec.ts
@@ -264,7 +264,7 @@ test('MM-T5776 Hide custom profile attributes when visibility is set to hidden @
/**
* Verify that custom profile attributes with visibility set to always
- * are displayed in the profile popover even if they have no value.
+ * are not displayed in the profile popover if they have no value.
*
* Precondition:
* 1. A test server with valid license to support 'Custom Profile Attributes'
@@ -272,7 +272,7 @@ test('MM-T5776 Hide custom profile attributes when visibility is set to hidden @
* 3. Other user has values set for custom profile attributes
* 4. Two user accounts exist and are members of the same channel
*/
-test('MM-T5777 Always display custom profile attributes with visibility set to always @custom_profile_attributes', async ({
+test('MM-T5777 Do not display valueless custom profile attributes with visibility set to always @custom_profile_attributes', async ({
pw,
}) => {
// 1. Update the visibility of the Title attribute to always
@@ -289,16 +289,9 @@ test('MM-T5777 Always display custom profile attributes with visibility set to a
const lastPost = await channelsPage.getLastPost();
await channelsPage.openProfilePopover(lastPost);
- // * Verify custom attributes are displayed correctly
+ // * Verify custom attributes without values are not displayed
for (const attribute of customAttributes) {
- if (attribute.name === 'Title') {
- // * Verify the Title attribute is displayed even though it has no value
- const popover = channelsPage.userProfilePopover.container;
- const nameElement = popover.getByText('Title', {exact: false});
- await expect(nameElement).toBeVisible();
- } else {
- await verifyAttributeNotInPopover(channelsPage, attribute.name);
- }
+ await verifyAttributeNotInPopover(channelsPage, attribute.name);
}
});
diff --git a/webapp/channels/src/components/profile_popover/profile_popover_custom_attributes.test.tsx b/webapp/channels/src/components/profile_popover/profile_popover_custom_attributes.test.tsx
index 589795dabf1..50956c48508 100644
--- a/webapp/channels/src/components/profile_popover/profile_popover_custom_attributes.test.tsx
+++ b/webapp/channels/src/components/profile_popover/profile_popover_custom_attributes.test.tsx
@@ -279,6 +279,156 @@ describe('components/ProfilePopoverCustomAttributes', () => {
expect(screen.queryByText('Text Attribute')).not.toBeInTheDocument();
});
+ test('should not render shared-only always-visible attribute labels when the value is missing', () => {
+ const state = {
+ ...baseState,
+ entities: {
+ ...baseState.entities,
+ users: {
+ profiles: {
+ user_id: TestHelper.getUserMock({
+ id: 'user_id',
+ custom_profile_attributes: {
+ phone_attribute_id: '+1 (555) 123-4567',
+ url_attribute_id: 'https://example.com',
+ select_attribute_id: 'option1',
+ },
+ }),
+ },
+ },
+ general: {
+ ...baseState.entities.general,
+ customProfileAttributes: {
+ ...baseState.entities.general.customProfileAttributes,
+ text_attribute_id: {
+ ...textAttribute,
+ attrs: {
+ ...textAttribute.attrs,
+ visibility: 'always',
+ access_mode: 'shared_only',
+ },
+ },
+ },
+ },
+ },
+ };
+
+ const store = mockStore(state);
+
+ renderWithContext(
+
+
+ ,
+ );
+
+ expect(screen.queryByText('Text Attribute')).not.toBeInTheDocument();
+ expect(screen.getByText('Phone Number')).toBeInTheDocument();
+ });
+
+ test('should not render always-visible select labels when the value is not displayable', () => {
+ const state = {
+ ...baseState,
+ entities: {
+ ...baseState.entities,
+ users: {
+ profiles: {
+ user_id: TestHelper.getUserMock({
+ id: 'user_id',
+ custom_profile_attributes: {
+ ...userProfile.custom_profile_attributes,
+ select_attribute_id: 'filtered-option',
+ },
+ }),
+ },
+ },
+ general: {
+ ...baseState.entities.general,
+ customProfileAttributes: {
+ ...baseState.entities.general.customProfileAttributes,
+ select_attribute_id: {
+ ...selectAttribute,
+ attrs: {
+ ...selectAttribute.attrs,
+ visibility: 'always',
+ },
+ },
+ },
+ },
+ },
+ };
+
+ const store = mockStore(state);
+
+ renderWithContext(
+
+
+ ,
+ );
+
+ expect(screen.queryByText('Select Attribute')).not.toBeInTheDocument();
+ expect(screen.queryByText('filtered-option')).not.toBeInTheDocument();
+ });
+
+ test('should render multiselect labels only when at least one value is displayable', () => {
+ const hiddenMultiselectAttribute: UserPropertyField = {
+ ...selectAttribute,
+ id: 'hidden_multiselect_attribute_id',
+ name: 'Hidden Multiselect Attribute',
+ type: 'multiselect',
+ attrs: {
+ ...selectAttribute.attrs,
+ visibility: 'always',
+ },
+ };
+ const visibleMultiselectAttribute: UserPropertyField = {
+ ...selectAttribute,
+ id: 'visible_multiselect_attribute_id',
+ name: 'Visible Multiselect Attribute',
+ type: 'multiselect',
+ attrs: {
+ ...selectAttribute.attrs,
+ visibility: 'always',
+ },
+ };
+ const state = {
+ ...baseState,
+ entities: {
+ ...baseState.entities,
+ users: {
+ profiles: {
+ user_id: TestHelper.getUserMock({
+ id: 'user_id',
+ custom_profile_attributes: {
+ hidden_multiselect_attribute_id: ['filtered-option'],
+ visible_multiselect_attribute_id: ['filtered-option', 'option1'],
+ },
+ }),
+ },
+ },
+ general: {
+ ...baseState.entities.general,
+ customProfileAttributes: {
+ hidden_multiselect_attribute_id: hiddenMultiselectAttribute,
+ visible_multiselect_attribute_id: visibleMultiselectAttribute,
+ },
+ },
+ },
+ };
+
+ const store = mockStore(state);
+
+ renderWithContext(
+
+
+ ,
+ );
+
+ expect(screen.queryByText('Hidden Multiselect Attribute')).not.toBeInTheDocument();
+ expect(screen.getByText('Visible Multiselect Attribute')).toBeInTheDocument();
+ expect(screen.getByText('Option 1')).toBeInTheDocument();
+ expect(screen.queryByText('filtered-option')).not.toBeInTheDocument();
+ });
+
test('should render display_name as the visible label when set', () => {
const state = {
...baseState,
diff --git a/webapp/channels/src/components/profile_popover/profile_popover_custom_attributes.tsx b/webapp/channels/src/components/profile_popover/profile_popover_custom_attributes.tsx
index d29a5ff9672..f72009c33ca 100644
--- a/webapp/channels/src/components/profile_popover/profile_popover_custom_attributes.tsx
+++ b/webapp/channels/src/components/profile_popover/profile_popover_custom_attributes.tsx
@@ -4,7 +4,7 @@
import React, {useEffect} from 'react';
import {useDispatch, useSelector} from 'react-redux';
-import type {UserPropertyValueType} from '@mattermost/types/properties';
+import type {UserPropertyField, UserPropertyValueType} from '@mattermost/types/properties';
import {getCustomProfileAttributeValues} from 'mattermost-redux/actions/users';
import {getCustomProfileAttributes} from 'mattermost-redux/selectors/entities/general';
@@ -23,6 +23,33 @@ type Props = {
userID: string;
hideStatus?: boolean;
}
+
+const hasDisplayableAttributeValue = (attribute: UserPropertyField, customProfileAttributes: Record): boolean => {
+ const attributeValue = customProfileAttributes[attribute.id];
+ if (Array.isArray(attributeValue)) {
+ if (attributeValue.length === 0) {
+ return false;
+ }
+ } else if (!attributeValue) {
+ return false;
+ }
+
+ if (attribute.type === 'multiselect' || attribute.type === 'select') {
+ const options = attribute.attrs?.options;
+ if (!options?.length) {
+ return false;
+ }
+
+ if (Array.isArray(attributeValue)) {
+ return attributeValue.some((value) => options.some((option) => option.id === value));
+ }
+
+ return options.some((option) => option.id === attributeValue);
+ }
+
+ return true;
+};
+
const ProfilePopoverCustomAttributes = ({
userID,
hideStatus = false,
@@ -49,30 +76,8 @@ const ProfilePopoverCustomAttributes = ({
return null;
}
- // Check if the attribute has a value
- const hasValue = userProfile.custom_profile_attributes[attribute.id]?.length > 0;
-
- if (!hasValue && visibility === 'when_set') {
+ if (!hasDisplayableAttributeValue(attribute, userProfile.custom_profile_attributes)) {
return null;
- } else if (visibility === 'when_set' && (attribute.type === 'multiselect' || attribute.type === 'select')) {
- const attributeValue = userProfile.custom_profile_attributes[attribute.id];
-
- // make sure attribute contains legitimate values
- if (Array.isArray(attributeValue)) {
- // Handle multiselect
- const options = attributeValue.map((value) => {
- return attribute.attrs.options?.find((o) => o.id === value);
- }).filter((o) => o != null);
- if (options.length === 0) {
- return null;
- }
- } else {
- // Handle single select
- const option = attribute.attrs.options?.find((o) => o.id === attributeValue);
- if (option === undefined) {
- return null;
- }
- }
}
const valueType = (attribute.attrs?.value_type as UserPropertyValueType) || '';