Skip to content

Commit

Permalink
Refactor keycloak groups view (#1272)
Browse files Browse the repository at this point in the history
* Remove effective Roles view component from form

* Include inherited roles in group view details

* Make transfer elements bigger to fix content

* fix test regressions
  • Loading branch information
peterMuriuki authored Sep 29, 2023
1 parent fa3578a commit 1561255
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export interface UserGroupFormProps {
allRoles: KeycloakUserRole[];
assignedRoles: KeycloakUserRole[];
availableRoles: KeycloakUserRole[];
effectiveRoles: KeycloakUserRole[];
initialValues: KeycloakUserGroup;
keycloakBaseURL: string;
}
Expand All @@ -56,7 +55,6 @@ export const defaultProps: Partial<UserGroupFormProps> = {
allRoles: [],
assignedRoles: [],
availableRoles: [],
effectiveRoles: [],
initialValues: defaultInitialValues,
keycloakBaseURL: '',
};
Expand Down Expand Up @@ -92,14 +90,7 @@ export const handleTransferChange = async (
* @param {object} props - component props
*/
const UserGroupForm: React.FC<UserGroupFormProps> = (props: UserGroupFormProps) => {
const {
initialValues,
keycloakBaseURL,
assignedRoles,
availableRoles,
effectiveRoles,
allRoles,
} = props;
const { initialValues, keycloakBaseURL, assignedRoles, availableRoles, allRoles } = props;
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [sourceSelectedKeys, setSourceSelectedKeys] = useState<string[]>([]);
const [targetSelectedKeys, setTargetSelectedKeys] = useState<string[]>([]);
Expand Down Expand Up @@ -199,7 +190,10 @@ const UserGroupForm: React.FC<UserGroupFormProps> = (props: UserGroupFormProps)
<Transfer
dataSource={data}
titles={[t('Available Roles'), t('Assigned Roles')]}
listStyle={{ flexGrow: 'inherit' }}
listStyle={{
minWidth: 300,
minHeight: 300,
}}
targetKeys={
targetKeys.length
? targetKeys
Expand All @@ -215,40 +209,6 @@ const UserGroupForm: React.FC<UserGroupFormProps> = (props: UserGroupFormProps)
searchPlaceholder: t('Search'),
}}
/>
{/** custom transfer to list effective roles */}
<div className="ant-transfer">
<div className="ant-transfer-list">
<div className="ant-transfer-list-header">
<span className="ant-transfer-list-header-title">{t('Effective Roles')}</span>
</div>
<div className="ant-transfer-list-body">
{!effectiveRoles.length ? (
<div className="ant-transfer-list-body-not-found">
{t('The list is empty')}
</div>
) : (
<ul className="ant-transfer-list-content">
{effectiveRoles.map((role: KeycloakUserRole) => (
<li
key={role.id}
className="ant-transfer-list-content-item ant-transfer-list-content-item-disabled"
>
<label className="ant-checkbox-wrapper">
<span className="ant-checkbox">
<input type="checkbox" className="ant-checkbox-input" />
<span className="ant-checkbox-inner" />
</span>
</label>
<span className="ant-transfer-list-content-item-text">
<div>{role.name}</div>
</span>
</li>
))}
</ul>
)}
</div>
</div>
</div>
</Form.Item>
) : (
''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { defaultInitialValues, UserGroupFormProps } from './Form';
import {
KEYCLOAK_URL_ASSIGNED_ROLES,
KEYCLOAK_URL_AVAILABLE_ROLES,
KEYCLOAK_URL_EFFECTIVE_ROLES,
ROUTE_PARAM_USER_GROUP_ID,
} from '../../constants';
import { useTranslation } from '../../mls';
Expand Down Expand Up @@ -62,7 +61,6 @@ const CreateEditUserGroup: React.FC<CreateEditGroupPropTypes> = (
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [availableRoles, setAvailableRoles] = React.useState<KeycloakUserRole[]>([]);
const [assignedRoles, setAssignedRoles] = React.useState<KeycloakUserRole[]>([]);
const [effectiveRoles, setEffectiveRoles] = React.useState<KeycloakUserRole[]>([]);
const { t } = useTranslation();
const dispatch = useDispatch();
const userGroupId = props.match.params[ROUTE_PARAM_USER_GROUP_ID];
Expand Down Expand Up @@ -95,20 +93,7 @@ const CreateEditUserGroup: React.FC<CreateEditGroupPropTypes> = (
setAssignedRoles,
t
);
const effectiveRolesPromise = fetchRoleMappings(
userGroupId,
keycloakBaseURL,
KEYCLOAK_URL_EFFECTIVE_ROLES,
setEffectiveRoles,
t
);
Promise.all([
groupPromise,
allRolesPromise,
availableRolesPromise,
assignedRolesPromise,
effectiveRolesPromise,
])
Promise.all([groupPromise, allRolesPromise, availableRolesPromise, assignedRolesPromise])
.catch(() => sendErrorNotification(t('There was a problem fetching user groups')))
.finally(() => setIsLoading(false));
}
Expand All @@ -123,7 +108,6 @@ const CreateEditUserGroup: React.FC<CreateEditGroupPropTypes> = (
allRoles,
assignedRoles,
availableRoles,
effectiveRoles,
initialValues: initialValues as KeycloakUserGroup,
keycloakBaseURL,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`components/CreateEditUserGroup renders correctly: full user group form 1`] = `"Edit User Group | AdminNameRealm Roles2 itemsAvailable RolesEDIT_KEYCLOAK_USERSVIEW_KEYCLOAK_USERS6 itemsAssigned RolesOPENMRSALL_EVENTSPLANS_FOR_USERrealm-adminoffline_accessuma_authorizationEffective RolesOPENMRSALL_EVENTSPLANS_FOR_USERrealm-adminoffline_accessuma_authorizationSaveCancel"`;
exports[`components/CreateEditUserGroup renders correctly: full user group form 1`] = `"Edit User Group | AdminNameRealm Roles2 itemsAvailable RolesEDIT_KEYCLOAK_USERSVIEW_KEYCLOAK_USERS6 itemsAssigned RolesOPENMRSALL_EVENTSPLANS_FOR_USERrealm-adminoffline_accessuma_authorizationSaveCancel"`;
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ describe('components/CreateEditUserGroup', () => {
});

it('handles error if fetch user group fails when page reloads', async () => {
fetch.mockRejectOnce(new Error('API is down'));
fetch.mockReject(new Error('API is down'));
const mockNotificationError = jest.spyOn(notifications, 'sendErrorNotification');

const wrapper = mount(
Expand All @@ -236,7 +236,10 @@ describe('components/CreateEditUserGroup', () => {
wrapper.update();
});

expect(mockNotificationError).toHaveBeenCalledWith('There was a problem fetching User Group');
expect(mockNotificationError.mock.calls).toEqual([
['There was a problem fetching role mappings'],
['There was a problem fetching role mappings'],
]);
wrapper.unmount();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Col, Space, Spin } from 'antd';
import { Col, Space, Spin, Typography } from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import { Resource404 } from '@opensrp/react-utils';
import { Button } from 'antd';
Expand All @@ -8,12 +8,16 @@ import { UserGroupMembers } from '../UserGroupsList';
import { Link } from 'react-router-dom';
import { useTranslation } from '../../mls';
import { KeycloakUserGroup } from '../../ducks/userGroups';
import { KeycloakUserRole } from 'keycloak-user-management/src/ducks/userRoles';

const { Text } = Typography;

/** typings for the view details component */
export interface ViewDetailsProps {
loading: boolean;
error: boolean;
GroupDetails: KeycloakUserGroup | undefined;
effectiveRoles: KeycloakUserRole[] | undefined;
userGroupMembers: UserGroupMembers[] | undefined;
onClose: () => void;
}
Expand All @@ -25,7 +29,7 @@ export interface ViewDetailsProps {
* @param props - detail view component props
*/
const ViewDetails = (props: ViewDetailsProps) => {
const { loading, error, GroupDetails, userGroupMembers, onClose } = props;
const { loading, error, GroupDetails, userGroupMembers, onClose, effectiveRoles } = props;
const { t } = useTranslation();

return (
Expand Down Expand Up @@ -57,22 +61,16 @@ const ViewDetails = (props: ViewDetailsProps) => {
</div>
<div className="mb-2 medium mt-2">
<p className="mb-0 font-weight-bold">{t('Roles')}</p>
{GroupDetails.realmRoles?.length ? (
GroupDetails.realmRoles.map((role, indx) => {
// append word break to wrap underscored strings with css
const wordBreakRoleName = role.split('_').join('_<wbr/>');
return (
<p
key={`${role}-${indx}`}
className="mb-2"
id="realRole"
dangerouslySetInnerHTML={{ __html: wordBreakRoleName }}
/>
);
})
) : (
<p id="noRealRole">{t('No assigned roles')}</p>
)}
<Space direction="vertical" size={1}>
{effectiveRoles?.length ? (
effectiveRoles.map((role) => {
// append word break to wrap underscored strings with css
return <Text key={role.name}>{role.name}</Text>;
})
) : (
<p id="noRealRole">{t('No assigned roles')}</p>
)}
</Space>
</div>
<div className="mb-2 medium mt-2">
<p className="mb-0 font-weight-bold">{t('Members')}</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`View User Group Details works correctly: nominal display 1`] = `"Group uuid123-some-group-uuid-456NameGroup NameRolesROLE_ONEROLE_TWOROLE_THREEMembersname 1 (name-1)undefined undefined (name-2)undefined undefined (name-3)undefined undefined (name-4)"`;
exports[`View User Group Details works correctly: nominal display 1`] = `"Group uuid123-some-group-uuid-456NameGroup NameRolesEDIT_KEYCLOAK_USERSVIEW_KEYCLOAK_USERSMembersname 1 (name-1)undefined undefined (name-2)undefined undefined (name-3)undefined undefined (name-4)"`;

exports[`View User Group Details works correctly: space element 1`] = `
Object {
Expand Down Expand Up @@ -43,33 +43,17 @@ Object {
>
Roles
</p>
<p
className="mb-2"
dangerouslySetInnerHTML={
Object {
"__html": "ROLE_<wbr/>ONE",
}
}
id="realRole"
/>
<p
className="mb-2"
dangerouslySetInnerHTML={
Object {
"__html": "ROLE_<wbr/>TWO",
}
}
id="realRole"
/>
<p
className="mb-2"
dangerouslySetInnerHTML={
Object {
"__html": "ROLE_<wbr/>THREE",
}
}
id="realRole"
/>
<Space
direction="vertical"
size={1}
>
<ForwardRef(Text)>
EDIT_KEYCLOAK_USERS
</ForwardRef(Text)>
<ForwardRef(Text)>
VIEW_KEYCLOAK_USERS
</ForwardRef(Text)>
</Space>
</div>,
<div
className="mb-2 medium mt-2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import flushPromises from 'flush-promises';
import { KeycloakUserGroup } from '../../../ducks/userGroups';
import { UserGroupMembers } from '../../UserGroupsList';
import { Resource404 } from '@opensrp/react-utils';
import { effectiveRoles } from '../../UserGroupsList/tests/fixtures';

const history = createMemoryHistory();
history.push(URL_USER_GROUPS);
Expand Down Expand Up @@ -44,6 +45,7 @@ describe('View User Group Details', () => {
username: 'name-4',
},
] as UserGroupMembers[],
effectiveRoles,
onClose: jest.fn(),
};

Expand All @@ -61,7 +63,7 @@ describe('View User Group Details', () => {

expect(wrapper.text()).toMatchSnapshot('nominal display');
// att test case to capture space element props snapshot
expect(wrapper.find('ViewDetails Space').props()).toMatchSnapshot('space element');
expect(wrapper.find('ViewDetails Space').first().props()).toMatchSnapshot('space element');
wrapper.unmount();
});

Expand Down Expand Up @@ -119,6 +121,7 @@ describe('View User Group Details', () => {
...props.GroupDetails,
realmRoles: [],
},
effectiveRoles: [],
userGroupMembers: [],
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '../../ducks/userGroups';
import { useTranslation } from '../../mls';
import {
KEYCLOAK_URL_EFFECTIVE_ROLES,
KEYCLOAK_URL_USER_GROUPS,
SEARCH_QUERY_PARAM,
URL_USER_GROUP_CREATE,
Expand Down Expand Up @@ -116,6 +117,25 @@ export const UserGroupsList: React.FC<UserGroupListTypes> = (props: UserGroupLis
}
);

const {
isLoading: effectiveRolesLoading,
isError: effectiveRolesError,
data: effectiveRoles,
} = useQuery(
[KEYCLOAK_URL_EFFECTIVE_ROLES, groupId, keycloakBaseURL],
() => {
const keycloakService = new KeycloakService(
`${KEYCLOAK_URL_USER_GROUPS}/${groupId}${KEYCLOAK_URL_EFFECTIVE_ROLES}`,
keycloakBaseURL
);
return keycloakService.list();
},
{
enabled: groupId !== null,
onError: () => sendErrorNotification(t('There was a problem fetching effective roles')),
}
);

const {
isLoading: isUserGroupMembersLoading,
isError: isUserGroupMembersError,
Expand Down Expand Up @@ -217,9 +237,10 @@ export const UserGroupsList: React.FC<UserGroupListTypes> = (props: UserGroupLis
{groupId ? (
<Col className="pl-3" span={5}>
<ViewDetails
loading={isGroupDetailsLoading || isUserGroupMembersLoading}
error={isGroupDetailsError || isUserGroupMembersError}
loading={isGroupDetailsLoading || isUserGroupMembersLoading || effectiveRolesLoading}
error={isGroupDetailsError || isUserGroupMembersError || effectiveRolesError}
GroupDetails={GroupDetails}
effectiveRoles={effectiveRoles}
userGroupMembers={userGroupMembers}
onClose={() => {
setGroupId(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,22 @@ export const userGroup1 = {
subGroups: [],
access: { view: true, manage: true, manageMembership: true },
};

export const effectiveRoles = [
{
id: 'cb4d5fc9-3b05-4514-b007-5971adba6d2f',
name: 'EDIT_KEYCLOAK_USERS',
description: 'Allows the management of keycloak users',
composite: true,
clientRole: false,
containerId: 'FHIR_Android',
},
{
id: '55c89f15-94b4-4395-b343-fb57740ff234',
name: 'VIEW_KEYCLOAK_USERS',
description: 'Allows the user to view the users created in keycloak',
composite: true,
clientRole: false,
containerId: 'FHIR_Android',
},
];

0 comments on commit 1561255

Please sign in to comment.