`;
diff --git a/webapp/channels/src/components/admin_console/system_user_detail/system_user_detail.test.tsx b/webapp/channels/src/components/admin_console/system_user_detail/system_user_detail.test.tsx
index 9d9b0096c56..a94197937a4 100644
--- a/webapp/channels/src/components/admin_console/system_user_detail/system_user_detail.test.tsx
+++ b/webapp/channels/src/components/admin_console/system_user_detail/system_user_detail.test.tsx
@@ -1,18 +1,29 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import '@testing-library/jest-dom';
+
import React from 'react';
+import type {IntlShape} from 'react-intl';
import type {RouteComponentProps} from 'react-router-dom';
import type {UserProfile} from '@mattermost/types/users';
import SystemUserDetail, {getUserAuthenticationTextField} from 'components/admin_console/system_user_detail/system_user_detail';
-import type {
- Props,
- Params,
-} from 'components/admin_console/system_user_detail/system_user_detail';
+import type {Params, Props} from 'components/admin_console/system_user_detail/system_user_detail';
+
+import type {MockIntl} from 'tests/helpers/intl-test-helper';
+import {renderWithContext, waitFor, within} from 'tests/react_testing_utils';
+import Constants from 'utils/constants';
+import {TestHelper} from 'utils/test_helper';
-import {shallowWithIntl, type MockIntl} from 'tests/helpers/intl-test-helper';
+// Mock user profile data
+const user = Object.assign(TestHelper.getUserMock(), {auth_service: Constants.EMAIL_SERVICE}) as UserProfile;
+const ldapUser = {...user, auth_service: Constants.LDAP_SERVICE} as UserProfile;
+
+// Mock getUser action result
+const getUserMock = jest.fn().mockResolvedValue({data: user, error: null});
+const getLdapUserMock = jest.fn().mockResolvedValue({data: ldapUser, error: null});
describe('SystemUserDetail', () => {
const defaultProps: Props = {
@@ -21,7 +32,7 @@ describe('SystemUserDetail', () => {
mfaEnabled: false,
patchUser: jest.fn(),
updateUserMfa: jest.fn(),
- getUser: jest.fn(),
+ getUser: getUserMock,
updateUserActive: jest.fn(),
setNavigationBlocked: jest.fn(),
addUserToTeam: jest.fn(),
@@ -39,51 +50,93 @@ describe('SystemUserDetail', () => {
} as RouteComponentProps),
};
- test('should match default snapshot', () => {
+ const waitForLoadingToFinish = async (container: HTMLElement) => {
+ const noUserBody = container.querySelector('.noUserBody');
+ const spinner = within(noUserBody as HTMLElement).getByTestId('loadingSpinner');
+ expect(spinner).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(container.querySelector('[data-testid="loadingSpinner"]')).not.toBeInTheDocument();
+ });
+ };
+
+ test('should match default snapshot', async () => {
const props = defaultProps;
- const wrapper = shallowWithIntl();
- expect(wrapper).toMatchSnapshot();
+ const {container} = renderWithContext();
+
+ await waitForLoadingToFinish(container);
+
+ expect(container).toMatchSnapshot();
});
- test('should match snapshot if MFA is enabled', () => {
+ test('should match snapshot if MFA is enabled', async () => {
const props = {
...defaultProps,
mfaEnabled: true,
};
- const wrapper = shallowWithIntl();
- expect(wrapper).toMatchSnapshot();
+ const {container} = renderWithContext();
+
+ await waitForLoadingToFinish(container);
+
+ expect(container).toMatchSnapshot();
});
- test('should show manage user settings button as activated', () => {
+ test('should show manage user settings button as activated', async () => {
const props = {
...defaultProps,
showManageUserSettings: true,
};
- const wrapper = shallowWithIntl();
- expect(wrapper).toMatchSnapshot();
+ const {container} = renderWithContext();
+
+ await waitForLoadingToFinish(container);
+
+ expect(container).toMatchSnapshot();
});
- test('should show manage user settings button as disabled when no license', () => {
+ test('should show manage user settings button as disabled when no license', async () => {
const props = {
...defaultProps,
showLockedManageUserSettings: false,
};
- const wrapper = shallowWithIntl();
- expect(wrapper).toMatchSnapshot();
+ const {container} = renderWithContext();
+
+ await waitForLoadingToFinish(container);
+
+ expect(container).toMatchSnapshot();
+ });
+
+ test('should show the activate user button as disabled when user is LDAP', async () => {
+ const props = {
+ ...defaultProps,
+ getUser: getLdapUserMock,
+ isLoading: false,
+ };
+
+ const {container} = renderWithContext();
+
+ await waitForLoadingToFinish(container);
+
+ const activateButton = container.querySelector('button[disabled]');
+ expect(activateButton).toHaveTextContent('Deactivate (Managed By LDAP)');
+
+ expect(container).toMatchSnapshot();
});
- test('should not show manage user settings button when user doesnt have permission', () => {
+ test('should not show manage user settings button when user doesn\'t have permission', async () => {
const props = {
...defaultProps,
showManageUserSettings: false,
};
- const wrapper = shallowWithIntl();
- expect(wrapper).toMatchSnapshot();
+ const {container} = renderWithContext();
+
+ await waitForLoadingToFinish(container);
+
+ expect(container).toMatchSnapshot();
});
});
describe('getUserAuthenticationTextField', () => {
- const intl = {formatMessage: ({defaultMessage}) => defaultMessage} as MockIntl;
+ const intl = {formatMessage: ({defaultMessage}: {defaultMessage: string}) => defaultMessage} as IntlShape;
it('should return empty string if user is not provided', () => {
const result = getUserAuthenticationTextField(intl, false, undefined);
diff --git a/webapp/channels/src/components/admin_console/system_user_detail/system_user_detail.tsx b/webapp/channels/src/components/admin_console/system_user_detail/system_user_detail.tsx
index fd297e0b110..ddf5fcee65f 100644
--- a/webapp/channels/src/components/admin_console/system_user_detail/system_user_detail.tsx
+++ b/webapp/channels/src/components/admin_console/system_user_detail/system_user_detail.tsx
@@ -133,7 +133,7 @@ export class SystemUserDetail extends PureComponent {
};
handleActivateUser = async () => {
- if (!this.state.user) {
+ if (!this.state.user || this.state.user?.auth_service === Constants.LDAP_SERVICE) {
return;
}
@@ -261,6 +261,9 @@ export class SystemUserDetail extends PureComponent {
*/
toggleOpenModalDeactivateMember = () => {
+ if (this.state.user?.auth_service === Constants.LDAP_SERVICE) {
+ return;
+ }
this.setState({showDeactivateMemberModal: true});
};
@@ -319,6 +322,21 @@ export class SystemUserDetail extends PureComponent {
});
};
+ getManagedByLdapText = () => {
+ if (this.state.user?.auth_service !== Constants.LDAP_SERVICE) {
+ return null;
+ }
+ return (
+ <>
+ {' '}
+
+ >
+ );
+ };
+
render() {
return (
@@ -402,22 +420,26 @@ export class SystemUserDetail extends PureComponent {
+ {this.getManagedByLdapText()}
)}
{this.state.user?.delete_at === 0 && (
+ {this.getManagedByLdapText()}
)}
diff --git a/webapp/channels/src/components/admin_console/system_user_detail/team_list/team_list.tsx b/webapp/channels/src/components/admin_console/system_user_detail/team_list/team_list.tsx
index afb6b873458..791e7c7f870 100644
--- a/webapp/channels/src/components/admin_console/system_user_detail/team_list/team_list.tsx
+++ b/webapp/channels/src/components/admin_console/system_user_detail/team_list/team_list.tsx
@@ -114,8 +114,11 @@ export default class TeamList extends React.PureComponent {
private mergeTeamsWithMemberships = (data: [ActionResult, ActionResult]): TeamWithMembership[] => {
const teams = data[0].data;
const memberships = data[1].data;
- let teamsWithMemberships = teams!.map((object: Team) => {
- const results = memberships!.filter((team: TeamMembership) => team.team_id === object.id);
+ if (!teams || !memberships) {
+ return [];
+ }
+ let teamsWithMemberships = teams.map((object: Team) => {
+ const results = memberships.filter((team: TeamMembership) => team.team_id === object.id);
const team = {...object, ...results[0]};
return team;
});
diff --git a/webapp/channels/src/components/admin_console/system_users/system_users_list_actions/index.test.tsx b/webapp/channels/src/components/admin_console/system_users/system_users_list_actions/index.test.tsx
new file mode 100644
index 00000000000..9e3d7ef778d
--- /dev/null
+++ b/webapp/channels/src/components/admin_console/system_users/system_users_list_actions/index.test.tsx
@@ -0,0 +1,115 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {waitFor, screen, within} from '@testing-library/react';
+import React from 'react';
+import '@testing-library/jest-dom';
+
+import type {UserProfile} from '@mattermost/types/users';
+
+import {haveISystemPermission} from 'mattermost-redux/selectors/entities/roles_helpers';
+
+import {renderWithContext, userEvent} from 'tests/react_testing_utils';
+import Constants from 'utils/constants';
+import {TestHelper} from 'utils/test_helper';
+
+import {SystemUsersListAction} from './index';
+
+jest.mock('mattermost-redux/selectors/entities/roles_helpers', () => ({
+ ...jest.requireActual('mattermost-redux/selectors/entities/roles_helpers'),
+ haveISystemPermission: jest.fn(),
+}));
+
+jest.mock('mattermost-redux/selectors/entities/common', () => {
+ const {TestHelper} = jest.requireActual('utils/test_helper');
+ const currentUser = TestHelper.getUserMock({
+ id: 'other_user_id',
+ roles: 'system_admin',
+ username: 'other-user',
+ });
+
+ return {
+ ...jest.requireActual('mattermost-redux/selectors/entities/common') as typeof import('mattermost-redux/selectors/entities/users'),
+ getCurrentUser: () => currentUser,
+ };
+});
+
+describe('SystemUsersListAction Component', () => {
+ const onError = jest.fn();
+ const updateUser = jest.fn();
+
+ const currentUser = TestHelper.getUserMock({
+ id: 'other_user_id',
+ roles: 'system_admin',
+ username: 'other-user',
+ });
+
+ const user = Object.assign(TestHelper.getUserMock(), {auth_service: 'email'}) as UserProfile;
+ const ldapUser = {...user, auth_service: Constants.LDAP_SERVICE} as UserProfile;
+ const deactivatedLDAPUser = {...user, auth_service: Constants.LDAP_SERVICE, delete_at: 12345} as UserProfile;
+
+ beforeEach(() => {
+ (haveISystemPermission as jest.Mock).mockImplementation(() => true);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const renderComponent = (authServiceUser: UserProfile) => {
+ renderWithContext(
+ ,
+ );
+ };
+
+ const openMenuAndFindItem = async (buttonText: string, itemText: RegExp) => {
+ const menuButton = screen.getByText(buttonText);
+ await userEvent.click(menuButton);
+ await waitFor(() => {
+ expect(screen.getByRole('menuitem', {name: itemText})).toBeInTheDocument();
+ });
+ return screen.findByRole('menuitem', {name: itemText});
+ };
+
+ const verifyDisabledMenuItem = (menuItem: HTMLElement, disabledText: RegExp) => {
+ expect(menuItem).toHaveAttribute('aria-disabled', 'true');
+ expect(menuItem).toHaveClass('Mui-disabled');
+ expect(within(menuItem).getByText(disabledText)).toBeInTheDocument();
+ };
+
+ test('Deactivate button is disabled and contains the Managed by LDAP text when user authmethod is LDAP', async () => {
+ renderComponent(ldapUser);
+
+ const deactivateMenuItem = await openMenuAndFindItem('Member', /deactivate/i);
+
+ // Verify that the item is disabled and contains "Managed by LDAP"
+ verifyDisabledMenuItem(deactivateMenuItem, /Managed by LDAP/i);
+ });
+
+ test('Activate button is disabled and contains the Managed by LDAP text when user authmethod is LDAP', async () => {
+ renderComponent(deactivatedLDAPUser);
+
+ const activateMenuItem = await openMenuAndFindItem('Deactivated', /activate/i);
+
+ // Verify that the item is disabled and contains "Managed by LDAP"
+ verifyDisabledMenuItem(activateMenuItem, /Managed by LDAP/i);
+ });
+
+ test('element is enabled and does NOT contain the Managed by LDAP text when user authmethod is NOT LDAP', async () => {
+ renderComponent(user);
+
+ const deactivateMenuItem = await openMenuAndFindItem('Member', /deactivate/i);
+
+ // Check if the item is enabled and does NOT contain "Managed by LDAP"
+ expect(deactivateMenuItem).not.toHaveAttribute('aria-disabled', 'true');
+ expect(deactivateMenuItem).not.toHaveClass('Mui-disabled');
+ expect(within(deactivateMenuItem).queryByText(/Managed by LDAP/i)).not.toBeInTheDocument();
+ });
+});
diff --git a/webapp/channels/src/components/admin_console/system_users/system_users_list_actions/index.tsx b/webapp/channels/src/components/admin_console/system_users/system_users_list_actions/index.tsx
index 615c0323518..c0b73fbc1a1 100644
--- a/webapp/channels/src/components/admin_console/system_users/system_users_list_actions/index.tsx
+++ b/webapp/channels/src/components/admin_console/system_users/system_users_list_actions/index.tsx
@@ -281,6 +281,9 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
}, [user.id, user.auth_service, updateUser, onError]);
const handleDeactivateMemberClick = useCallback(() => {
+ if (user.auth_service === Constants.LDAP_SERVICE) {
+ return;
+ }
function onDeactivateMemberSuccess() {
updateUser({delete_at: new Date().getMilliseconds()});
}
@@ -298,6 +301,17 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
);
}, [user, updateUser, onError]);
+ const disableActivationToggle = user.auth_service === Constants.LDAP_SERVICE;
+
+ const getManagedByLDAPText = (managedByLDAP: boolean) => {
+ return managedByLDAP ? {
+ trailingElements: formatMessage({
+ id: 'admin.system_users.list.actions.menu.managedByLdap',
+ defaultMessage: 'Managed by LDAP',
+ }),
+ } : {};
+ };
+
return (
}
- disabled={user.auth_service === Constants.LDAP_SERVICE}
+ disabled={disableActivationToggle}
+ {...getManagedByLDAPText(disableActivationToggle)}
onClick={handleActivateUserClick}
/>
)}
@@ -496,6 +511,8 @@ export function SystemUsersListAction({user, currentUser, tableId, rowIndex, onE
/>
}
onClick={handleDeactivateMemberClick}
+ disabled={disableActivationToggle}
+ {...getManagedByLDAPText(disableActivationToggle)}
/>
)}
diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json
index b415ac80c9d..c9eb10ccf95 100644
--- a/webapp/channels/src/i18n/en.json
+++ b/webapp/channels/src/i18n/en.json
@@ -2563,6 +2563,7 @@
"admin.system_users.list.actions.menu.deactivate": "Deactivate",
"admin.system_users.list.actions.menu.demoteToGuest": "Demote to guest",
"admin.system_users.list.actions.menu.dropdownAriaLabel": "User actions menu",
+ "admin.system_users.list.actions.menu.managedByLdap": "(Managed by LDAP)",
"admin.system_users.list.actions.menu.manageRoles": "Manage roles",
"admin.system_users.list.actions.menu.manageSettings": "Manage user settings",
"admin.system_users.list.actions.menu.manageTeams": "Manage teams",
@@ -2773,6 +2774,7 @@
"admin.user_item.makeActive": "Activate",
"admin.user_item.makeMember": "Make Team Member",
"admin.user_item.makeTeamAdmin": "Make Team Admin",
+ "admin.user_item.managedByLdap": "(Managed By LDAP)",
"admin.user_item.manageSettings": "Manage User Settings",
"admin.user_item.manageSettings.confirm_dialog.body": "You are about to access {userDisplayName}'s account settings. Any modifications you make will take effect immediately in their account. {userDisplayName} retains the ability to view and modify these settings at any time.
Are you sure you want to proceed with managing {userDisplayName}'s settings?",
"admin.user_item.manageSettings.disabled_tooltip": "Please upgrade to Enterprise to manage user settings",