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

* sort in selector

* test

* lint/test

* test
Этот коммит содержится в:
Caleb Roseland
2025-03-12 13:27:35 -05:00
коммит произвёл GitHub
родитель 9f1ec59fa0
Коммит 8a21016266
6 изменённых файлов: 33 добавлений и 31 удалений

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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