MM-63271: CPA Sorting in Profile Popover & Profile Settings (#30369)

* sort in selector

* test

* lint/test

* test
This commit is contained in:
Caleb Roseland 2025-03-12 13:27:35 -05:00 committed by GitHub
parent 9f1ec59fa0
commit 8a21016266
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 33 additions and 31 deletions

View file

@ -1390,9 +1390,9 @@ describe('handleCustomAttributeCRUD', () => {
let cpaFields = getCustomProfileAttributes(testStore.getState());
expect(cpaFields).toBeTruthy();
expect(Object.keys(cpaFields).length).toEqual(1);
expect(cpaFields.field1.type).toEqual(field1.type);
expect(cpaFields.field1.name).toEqual(field1.name);
expect(cpaFields.length).toEqual(1);
expect(cpaFields.filter(({id}) => id === field1.id)[0].type).toEqual(field1.type);
expect(cpaFields.filter(({id}) => id === field1.id)[0].name).toEqual(field1.name);
// create second field
testStore.dispatch(handleCustomAttributesCreated({
@ -1404,9 +1404,9 @@ describe('handleCustomAttributeCRUD', () => {
cpaFields = getCustomProfileAttributes(testStore.getState());
expect(cpaFields).toBeTruthy();
expect(Object.keys(cpaFields).length).toEqual(2);
expect(cpaFields.field2.type).toEqual(field2.type);
expect(cpaFields.field2.name).toEqual(field2.name);
expect(cpaFields.length).toEqual(2);
expect(cpaFields.filter(({id}) => id === field2.id)[0].type).toEqual(field2.type);
expect(cpaFields.filter(({id}) => id === field2.id)[0].name).toEqual(field2.name);
// update field
testStore.dispatch(handleCustomAttributesUpdated({
@ -1418,9 +1418,9 @@ describe('handleCustomAttributeCRUD', () => {
cpaFields = getCustomProfileAttributes(testStore.getState());
expect(cpaFields).toBeTruthy();
expect(Object.keys(cpaFields).length).toEqual(2);
expect(cpaFields.field1.name).toEqual('Updated Name');
expect(cpaFields.field2.name).toEqual(field2.name);
expect(cpaFields.length).toEqual(2);
expect(cpaFields.filter(({id}) => id === field1.id)[0].name).toEqual('Updated Name');
expect(cpaFields.filter(({id}) => id === field2.id)[0].name).toEqual(field2.name);
// delete field
testStore.dispatch(handleCustomAttributesDeleted({
@ -1432,7 +1432,7 @@ describe('handleCustomAttributeCRUD', () => {
cpaFields = getCustomProfileAttributes(testStore.getState());
expect(cpaFields).toBeTruthy();
expect(Object.keys(cpaFields).length).toEqual(1);
expect(cpaFields.field2).toBeTruthy();
expect(cpaFields.length).toEqual(1);
expect(cpaFields.filter(({id}) => id === field2.id)[0]).toBeTruthy();
});
});

View file

@ -25,7 +25,7 @@ const ProfilePopoverCustomAttributes = ({
dispatch(getCustomProfileAttributeValues(userID));
}
});
const attributeSections = Object.values(customProfileAttributeFields).map((attribute) => {
const attributeSections = customProfileAttributeFields.map((attribute) => {
if (userProfile.custom_profile_attributes) {
const value = userProfile.custom_profile_attributes[attribute.id];
if (!value) {

View file

@ -45,7 +45,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
closeModal: jest.fn(),
collapseModal: jest.fn(),
isMobileView: false,
customProfileAttributeFields: {},
customProfileAttributeFields: [],
actions: {
logError: jest.fn(),
clearErrors: jest.fn(),
@ -199,7 +199,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
const props = {
...requiredProps,
enableCustomProfileAttributes: true,
customProfileAttributeFields: {field1: customProfileAttribute},
customProfileAttributeFields: [customProfileAttribute],
user: testUser,
};
@ -216,7 +216,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
const props = {
...requiredProps,
enableCustomProfileAttributes: true,
customProfileAttributeFields: {field1: customProfileAttribute},
customProfileAttributeFields: [customProfileAttribute],
user: testUser,
};
@ -233,7 +233,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
const props = {
...requiredProps,
enableCustomProfileAttributes: true,
customProfileAttributeFields: {field1: customProfileAttribute},
customProfileAttributeFields: [customProfileAttribute],
user: testUser,
};
@ -248,7 +248,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
const props = {
...requiredProps,
enableCustomProfileAttributes: true,
customProfileAttributeFields: {field1: customProfileAttribute},
customProfileAttributeFields: [customProfileAttribute],
actions: {...requiredProps.actions},
user: testUser,
};
@ -268,7 +268,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
const props = {
...requiredProps,
enableCustomProfileAttributes: true,
customProfileAttributeFields: {field1: customProfileAttribute},
customProfileAttributeFields: [customProfileAttribute],
user,
activeSection: 'customAttribute_field1',
};
@ -283,7 +283,7 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
...requiredProps,
enableCustomProfileAttributes: true,
actions: {...requiredProps.actions, saveCustomProfileAttribute},
customProfileAttributeFields: {field1: customProfileAttribute},
customProfileAttributeFields: [customProfileAttribute],
user: {...user},
activeSection: 'customAttribute_field1',
};

View file

@ -9,7 +9,6 @@ import type {IntlShape} from 'react-intl';
import type {UserPropertyField} from '@mattermost/types/properties';
import type {UserProfile} from '@mattermost/types/users';
import type {IDMappedObjects} from '@mattermost/types/utilities';
import type {LogErrorOptions} from 'mattermost-redux/actions/errors';
import {LogErrorBarMode} from 'mattermost-redux/actions/errors';
@ -110,7 +109,7 @@ export type Props = {
collapseModal: () => void;
isMobileView: boolean;
maxFileSize: number;
customProfileAttributeFields: IDMappedObjects<UserPropertyField>;
customProfileAttributeFields: UserPropertyField[];
actions: {
logError: ({message, type}: {message: any; type: string}, options?: LogErrorOptions) => void;
clearErrors: () => void;
@ -1325,7 +1324,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
return <></>;
}
const attributeSections = Object.values(this.props.customProfileAttributeFields).map((attribute) => {
const attributeSections = this.props.customProfileAttributeFields.map((attribute) => {
const sectionName = 'customAttribute_' + attribute.id;
const active = this.props.activeSection === sectionName;
let max = null;

View file

@ -412,21 +412,21 @@ describe('Selectors.General', () => {
},
} as unknown as GlobalState;
expect(Selectors.getCustomProfileAttributes(state)).toEqual({});
expect(Selectors.getCustomProfileAttributes(state)).toEqual([]);
});
test('should return the value of the attributes', () => {
const state = {
let state = {
entities: {
general: {
customProfileAttributes: [{id: '123', name: 'test attribute', dataType: 'text'}],
customProfileAttributes: {123: {id: '123', name: 'test attribute', dataType: 'text'}},
},
},
} as unknown as GlobalState;
expect(Selectors.getCustomProfileAttributes(state)[0].id).toEqual('123');
state.entities.general.customProfileAttributes = {};
expect(Selectors.getCustomProfileAttributes(state)).toEqual({});
state = {...state, entities: {...state.entities, general: {...state.entities.general, customProfileAttributes: {}}}};
expect(Selectors.getCustomProfileAttributes(state)).toEqual([]);
});
});
});

View file

@ -4,7 +4,6 @@
import type {ClientConfig, FeatureFlags, ClientLicense} from '@mattermost/types/config';
import type {UserPropertyField} from '@mattermost/types/properties';
import type {GlobalState} from '@mattermost/types/store';
import type {IDMappedObjects} from '@mattermost/types/utilities';
import {General} from 'mattermost-redux/constants';
import {createSelector} from 'mattermost-redux/selectors/create_selector';
@ -153,6 +152,10 @@ export function testingEnabled(state: GlobalState): boolean {
return state.entities.general.config.EnableTesting === 'true';
}
export function getCustomProfileAttributes(state: GlobalState): IDMappedObjects<UserPropertyField> {
return state.entities.general.customProfileAttributes;
}
export const getCustomProfileAttributes: (state: GlobalState) => UserPropertyField[] = createSelector(
'getCustomProfileAttributes',
(state) => state.entities.general.customProfileAttributes,
(fields) => {
return Object.values(fields).sort((a, b) => (a.attrs?.sort_order ?? 0) - (b.attrs?.sort_order ?? 0));
},
);