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

UISAUTHCOM-23 Display toast message for saving and editing authorization roles #37

Merged
merged 7 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
* [UISAUTHCOM-14](https://folio-org.atlassian.net/browse/UISAUTHCOM-14) ECS - Support sharing of authorization roles and policies.
* [UISAUTHCOM-20](https://folio-org.atlassian.net/browse/UISAUTHCOM-20) ECS - Prevent editing of shared Role from outside "Consortium manager".
* [UISAUTHCOM-21](https://folio-org.atlassian.net/browse/UISAUTHCOM-21) ECS - Prevent editing of shared Policies from outside "Consortium manager".
* [UISAUTHCOM-23](https://folio-org.atlassian.net/browse/UISAUTHCOM-23) Display toast message for saving and editing authorization roles.
11 changes: 8 additions & 3 deletions lib/Policy/PolicyFormContainer/PolicyFormContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ import {
import { isShared } from '../../utils';
import { PolicyForm } from '../PolicyForm';

export const PolicyFormContainer = ({ path, ...props }) => {
export const PolicyFormContainer = ({
path,
tenantId,
...props
}) => {
const stripes = useStripes();
const history = useHistory();
const showCallout = useShowCallout();
const { id: policyId } = useParams();
const actionType = policyId ? 'edit' : 'create';

const { policy, isLoading } = useAuthorizationPolicyById(policyId);
const { mutatePolicy, isLoading: isMutationLoading } = useAuthorizationPolicyMutation();
const { policy, isLoading } = useAuthorizationPolicyById(policyId, { tenantId });
const { mutatePolicy, isLoading: isMutationLoading } = useAuthorizationPolicyMutation(null, { tenantId });
const { upsertSharedPolicy, isLoading: isSharing } = usePolicySharing();

const isPolicyShared = Boolean(stripes.hasInterface('consortia') && isShared(policy));
Expand Down Expand Up @@ -71,4 +75,5 @@ export const PolicyFormContainer = ({ path, ...props }) => {

PolicyFormContainer.propTypes = {
path: PropTypes.string.isRequired,
tenantId: PropTypes.string,
};
23 changes: 18 additions & 5 deletions lib/PolicyDetails/PolicyDetails/PolicyDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ import {
useStripes,
} from '@folio/stripes/core';

import { usePolicySharing } from '../../hooks';
import {
usePolicySharing,
useShowCallout,
} from '../../hooks';
import {
isShared,
isTenantConsortiumCentral,
Expand All @@ -47,10 +50,11 @@ const PolicyDetails = ({
}) => {
const stripes = useStripes();
const intl = useIntl();
const showCallout = useShowCallout();

const ConnectedViewMetaData = stripes.connect(ViewMetaData);

const { upsertSharedPolicy } = usePolicySharing();
const { upsertSharedPolicy, isLoading } = usePolicySharing();

const [isConfirmShareModalOpen, setIsConfirmShareModalOpen] = useState(false);

Expand Down Expand Up @@ -101,9 +105,17 @@ const PolicyDetails = ({
}, [displayShareAction, isMutationsPrevented, isPolicyShared, isTargetTenantCentral, onEdit, stripes]);

const sharePolicy = useCallback(async () => {
await upsertSharedPolicy({ policy })
.then(() => setIsConfirmShareModalOpen(false));
}, [policy, upsertSharedPolicy]);
try {
await upsertSharedPolicy({ policy });
setIsConfirmShareModalOpen(false);
showCallout({ messageId: 'stripes-authorization-components.policy.share.success' });
} catch {
showCallout({
messageId: 'stripes-authorization-components.policy.share.error',
type: 'error',
});
}
}, [policy, showCallout, upsertSharedPolicy]);

return (
<Pane
Expand Down Expand Up @@ -174,6 +186,7 @@ const PolicyDetails = ({
)}
onConfirm={sharePolicy}
onCancel={() => setIsConfirmShareModalOpen(false)}
isConfirmButtonDisabled={isLoading}
/>
</Pane>
);
Expand Down
2 changes: 1 addition & 1 deletion lib/PolicyDetails/PolicyDetails/PolicyDetails.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,5 @@ describe('PolicyDetails component', () => {

expect(upsertSharedPolicy).toHaveBeenCalledWith(expect.objectContaining({ policy: defaultProps.policy }));
});
})
});
});
16 changes: 14 additions & 2 deletions lib/Role/RoleCreate/RoleCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useApplicationCapabilitySets,
useCreateRoleMutation,
useErrorCallout,
useShowCallout,
} from '../../hooks';
import { RoleForm } from '../RoleForm';

Expand All @@ -16,6 +17,7 @@ export const RoleCreate = ({
tenantId,
}) => {
const history = useHistory();
const showCallout = useShowCallout();

const [roleName, setRoleName] = useState('');
const [description, setDescription] = useState('');
Expand Down Expand Up @@ -74,8 +76,18 @@ export const RoleCreate = ({

const onSubmit = async (e) => {
e.preventDefault();
await mutateRole({ name: roleName, description });
onClose();

try {
const { id: roleId } = await mutateRole({ name: roleName, description });

history.push(`${path}/${roleId}`);
showCallout({ messageId: 'stripes-authorization-components.role.create.success' });
} catch {
showCallout({
messageId: 'stripes-authorization-components.role.create.error',
type: 'error',
});
}
};

const onSubmitSelectApplications = (appIds, callback) => {
Expand Down
30 changes: 20 additions & 10 deletions lib/Role/RoleEdit/RoleEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useRoleCapabilities,
useRoleCapabilitySets,
useRoleSharing,
useShowCallout,
} from '../../hooks';
import { isShared } from '../../utils';
import { RoleForm } from '../RoleForm';
Expand All @@ -24,6 +25,7 @@ export const RoleEdit = ({ path, tenantId }) => {
const { id: roleId } = useParams();
const queryClient = useQueryClient();
const stripes = useStripes();
const showCallout = useShowCallout();

const { roleDetails, isSuccess: isRoleDetailsLoaded } = useRoleById(roleId, { tenantId });

Expand Down Expand Up @@ -171,18 +173,26 @@ export const RoleEdit = ({ path, tenantId }) => {
const onSubmit = async (event) => {
event.preventDefault();

if (isRoleShared) {
await upsertSharedRole({
role: { ...roleDetails, ...roleData },
...updateIndicators,
capabilityNames: roleCapabilitiesListNames,
capabilitySetNames: roleCapabilitySetsListNames,
try {
if (isRoleShared) {
await upsertSharedRole({
role: { ...roleDetails, ...roleData },
...updateIndicators,
capabilityNames: roleCapabilitiesListNames,
capabilitySetNames: roleCapabilitySetsListNames,
});
} else {
await mutateRole();
}

onClose();
showCallout({ messageId: 'stripes-authorization-components.role.edit.success' });
} catch (error) {
showCallout({
messageId: 'stripes-authorization-components.role.edit.error',
type: 'error',
});
} else {
await mutateRole();
}

onClose();
};

const isInitialDataReady = isInitialRoleCapabilitySetsLoaded && isInitialRoleCapabilitiesLoaded;
Expand Down
55 changes: 48 additions & 7 deletions lib/RoleDetails/RoleDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
useErrorCallout,
useInitialRoleSharing,
useRoleById,
useRoleSharing,
useShowCallout,
} from '../hooks';
import {
isShared,
Expand All @@ -59,6 +61,8 @@ export const RoleDetails = ({
}) => {
const stripes = useStripes();
const intl = useIntl();
const showCallout = useShowCallout();

const ConnectedViewMetaData = stripes.connect(ViewMetaData);

const [isDeleting, setIsDeleting] = useState(false);
Expand All @@ -71,8 +75,18 @@ export const RoleDetails = ({
const onClose = () => history.push(path);

const { roleDetails: role } = useRoleById(roleId, { tenantId });
const { mutateAsync: deleteRole } = useDeleteRoleMutation(onClose, sendErrorCallout, { tenantId });
const { shareRole } = useInitialRoleSharing(role);
const {
mutateAsync: deleteRole,
isLoading: isRoleDeleting,
} = useDeleteRoleMutation(onClose, sendErrorCallout, { tenantId });
const {
shareRole,
isLoading: isRoleInitialSharing,
} = useInitialRoleSharing(role);
const {
deleteSharedRole,
isLoading: isSharedRoleDeleting,
} = useRoleSharing();

const isRoleShared = Boolean(stripes.hasInterface('consortia') && isShared(role));
const targetTenantId = tenantId || stripes.okapi.tenant;
Expand Down Expand Up @@ -150,10 +164,35 @@ export const RoleDetails = ({
);
};

const onRoleShare = useCallback(() => {
return shareRole()
.then(() => setIsConfirmShareModalOpen(false));
}, [shareRole]);
const onRoleShare = useCallback(async () => {
try {
await shareRole();
setIsConfirmShareModalOpen(false);
showCallout({ messageId: 'stripes-authorization-components.role.share.success' });
} catch {
showCallout({
messageId: 'stripes-authorization-components.role.share.error',
type: 'error',
});
}
}, [shareRole, showCallout]);

const onRoleDelete = useCallback(async () => {
try {
if (isRoleShared) {
await deleteSharedRole({ role });
} else {
await deleteRole(roleId);
}

showCallout({ messageId: 'stripes-authorization-components.role.delete.success' });
} catch {
showCallout({
messageId: 'stripes-authorization-components.role.delete.error',
type: 'error',
});
}
}, [deleteRole, deleteSharedRole, isRoleShared, role, roleId, showCallout]);

const paneTitle = isLoading || !role?.name ? <Loading /> : role?.name;

Expand Down Expand Up @@ -231,9 +270,10 @@ export const RoleDetails = ({
values={{ rolename: role?.name }}
/>
)}
onConfirm={() => deleteRole(roleId)}
onConfirm={onRoleDelete}
onCancel={() => setIsDeleting(false)}
confirmLabel={<FormattedMessage id="stripes-authorization-components.crud.delete" />}
isConfirmButtonDisabled={isRoleDeleting || isSharedRoleDeleting}
/>

<ConfirmationModal
Expand All @@ -247,6 +287,7 @@ export const RoleDetails = ({
)}
onConfirm={onRoleShare}
onCancel={() => setIsConfirmShareModalOpen(false)}
isConfirmButtonDisabled={isRoleInitialSharing}
/>
<ConfirmationModal
open={isDuplicating}
Expand Down
33 changes: 33 additions & 0 deletions lib/RoleDetails/RoleDetails.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
useDeleteRoleMutation,
useInitialRoleSharing,
useRoleById,
useRoleSharing,
} from '../hooks';
import { isTenantConsortiumCentral } from '../utils';
import { RoleDetails } from './RoleDetails';
Expand All @@ -22,6 +23,7 @@ jest.mock('../hooks', () => ({
useDeleteRoleMutation: jest.fn(),
useInitialRoleSharing: jest.fn(),
useRoleById: jest.fn(),
useRoleSharing: jest.fn(),
}));
jest.mock('../utils', () => ({
...jest.requireActual('../utils'),
Expand Down Expand Up @@ -77,14 +79,19 @@ jest.mock('./RoleDetailsCapabilitySetsAccordion', () => ({
}));

describe('RoleDetails component', () => {
const deleteSharedRole = jest.fn(() => Promise.resolve());
const shareRole = jest.fn(() => Promise.resolve());

beforeEach(() => {
shareRole.mockClear();
deleteSharedRole.mockClear();

useInitialRoleSharing
.mockClear()
.mockReturnValue({ shareRole });
useRoleSharing
.mockClear()
.mockReturnValue({ deleteSharedRole });
});

describe('renders roles details pane with expanded information', () => {
Expand Down Expand Up @@ -136,6 +143,12 @@ describe('RoleDetails component', () => {
});

describe('ECS mode', () => {
beforeEach(() => {
isTenantConsortiumCentral
.mockClear()
.mockReturnValue(true);
});

it('should handle policy sharing when "Share to all" action is performed', async () => {
isTenantConsortiumCentral.mockReturnValue(true);

Expand All @@ -149,5 +162,25 @@ describe('RoleDetails component', () => {

expect(shareRole).toHaveBeenCalled();
});

it('should handle shared policy delete', async () => {
useRoleById
.mockClear()
.mockReturnValue({
roleDetails: { ...getRoleData(), type: 'CONSORTIUM' },
isRoleDetailsLoaded: true,
});

renderComponent({ displayShareAction: true });

await userEvent.click(screen.getByRole('button', { name: 'stripes-components.paneMenuActionsToggleLabel' }));
await userEvent.click(screen.getByRole('button', { name: 'stripes-authorization-components.crud.delete' }));
await userEvent.click(
within(screen.getByText('stripes-authorization-components.crud.deleteRole'))
.getByRole('button', { name: 'confirm' })
);

expect(deleteSharedRole).toHaveBeenCalled();
});
});
});
Loading
Loading