Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor keycloak groups view #1272

Merged
merged 4 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
},
];
Loading