From 3bbc7a1a60ae019f7a0b677c4b7451e8fe4a809d Mon Sep 17 00:00:00 2001 From: Jingyi Gao Date: Mon, 25 Sep 2023 16:35:20 +1000 Subject: [PATCH 1/4] Extract update sharing types endpoint --- src/api/entities/AuditTrail.ts | 12 ++++++- src/api/routers/participantsRouter.ts | 43 +++++++++++++++++++------ src/api/services/auditTrailService.ts | 28 ++++++++++++++++ src/api/services/participantsService.ts | 28 ++++++++++++---- src/web/screens/sharingPermissions.tsx | 37 ++++++++++++--------- src/web/services/participant.ts | 25 ++++++++++---- 6 files changed, 135 insertions(+), 38 deletions(-) diff --git a/src/api/entities/AuditTrail.ts b/src/api/entities/AuditTrail.ts index e1b7cdd8f..2bdaba17a 100644 --- a/src/api/entities/AuditTrail.ts +++ b/src/api/entities/AuditTrail.ts @@ -1,5 +1,6 @@ import { Model } from 'objection'; +import { ClientType } from '../services/adminServiceHelpers'; import { BaseModel } from './BaseModel'; import { ModelObjectOpt } from './ModelObjectOpt'; @@ -10,10 +11,14 @@ export enum SharingAction { export enum AuditTrailEvents { UpdateSharingPermissions = 'UpdateSharingPermissions', + UpdateSharingTypes = 'UpdateSharingTypes', ApproveAccount = 'ApproveAccount', } -export type AuditTrailEventData = UpdateSharingPermissionEventData | ApproveAccountEventData; +export type AuditTrailEventData = + | UpdateSharingPermissionEventData + | ApproveAccountEventData + | UpdateSharingTypesEventData; export type UpdateSharingPermissionEventData = { siteId: number; @@ -21,6 +26,11 @@ export type UpdateSharingPermissionEventData = { sharingPermissions: number[]; }; +export type UpdateSharingTypesEventData = { + siteId: number; + allowedTypes: ClientType[]; +}; + export type ApproveAccountEventData = { siteId: number; oldName?: string; diff --git a/src/api/routers/participantsRouter.ts b/src/api/routers/participantsRouter.ts index 1625557ec..ec077bc9a 100644 --- a/src/api/routers/participantsRouter.ts +++ b/src/api/routers/participantsRouter.ts @@ -17,6 +17,7 @@ import { getSharingList } from '../services/adminServiceClient'; import { insertApproveAccountAuditTrail, insertSharingAuditTrails, + insertSharingTypesAuditTrail, updateAuditTrailToProceed, } from '../services/auditTrailService'; import { assignClientRoleToUser, createNewUser, sendInviteEmail } from '../services/kcUsersService'; @@ -28,6 +29,7 @@ import { ParticipantRequest, sendNewParticipantEmail, sendParticipantApprovedEmail, + UpdateSharingTypes, } from '../services/participantsService'; import { createUserInPortal, @@ -210,9 +212,8 @@ export function createParticipantsRouter() { } ); - const sharingRelationParser = z.object({ + const sharingSitesParser = z.object({ newParticipantSites: z.array(z.number()), - newTypes: z.array(ClientTypeEnum), }); participantsRouter.post( @@ -222,7 +223,7 @@ export function createParticipantsRouter() { if (!participant?.siteId) { return res.status(400).send('Site id is not set'); } - const { newParticipantSites, newTypes } = sharingRelationParser.parse(req.body); + const { newParticipantSites } = sharingSitesParser.parse(req.body); const currentUser = await findUserByEmail(req.auth?.payload?.email as string); const auditTrail = await insertSharingAuditTrails( participant, @@ -234,8 +235,7 @@ export function createParticipantsRouter() { const sharingParticipants = await addSharingParticipants( participant.siteId, - newParticipantSites, - newTypes + newParticipantSites ); await updateAuditTrailToProceed(auditTrail.id); @@ -243,9 +243,8 @@ export function createParticipantsRouter() { } ); - const removeSharingRelationParser = z.object({ + const removeSharingSitesParser = z.object({ sharingSitesToRemove: z.array(z.number()), - types: z.array(ClientTypeEnum), }); participantsRouter.post( @@ -255,7 +254,7 @@ export function createParticipantsRouter() { if (!participant?.siteId) { return res.status(400).send('Site id is not set'); } - const { sharingSitesToRemove, types } = removeSharingRelationParser.parse(req.body); + const { sharingSitesToRemove } = removeSharingSitesParser.parse(req.body); const currentUser = await findUserByEmail(req.auth?.payload?.email as string); const auditTrail = await insertSharingAuditTrails( participant, @@ -267,10 +266,36 @@ export function createParticipantsRouter() { const sharingParticipants = await deleteSharingParticipants( participant.siteId, - sharingSitesToRemove, + sharingSitesToRemove + ); + + await updateAuditTrailToProceed(auditTrail.id); + + return res.status(200).json(sharingParticipants); + } + ); + + const sharingTypesParser = z.object({ + types: z.array(ClientTypeEnum), + }); + participantsRouter.post( + '/:participantId/sharingPermission/shareWithTypes', + async (req: ParticipantRequest, res: Response) => { + const { participant } = req; + if (!participant?.siteId) { + return res.status(400).send('Site id is not set'); + } + const { types } = sharingTypesParser.parse(req.body); + const currentUser = await findUserByEmail(req.auth?.payload?.email as string); + const auditTrail = await insertSharingTypesAuditTrail( + participant, + currentUser!.id, + currentUser!.email, types ); + const sharingParticipants = await UpdateSharingTypes(participant.siteId, types); + await updateAuditTrailToProceed(auditTrail.id); return res.status(200).json(sharingParticipants); diff --git a/src/api/services/auditTrailService.ts b/src/api/services/auditTrailService.ts index 54a6a8568..91e0225e3 100644 --- a/src/api/services/auditTrailService.ts +++ b/src/api/services/auditTrailService.ts @@ -9,6 +9,7 @@ import { } from '../entities/AuditTrail'; import { Participant, ParticipantApprovalPartial } from '../entities/Participant'; import { getLoggers } from '../helpers/loggingHelpers'; +import { ClientType } from './adminServiceHelpers'; import { findUserByEmail } from './usersService'; const arraysHaveSameElements = (a: unknown[], b: unknown[]): boolean => { @@ -52,6 +53,33 @@ export const insertSharingAuditTrails = async ( } }; +export const insertSharingTypesAuditTrail = async ( + participant: Participant, + userId: number, + userEmail: string, + types: ClientType[] +) => { + try { + const sharingAuditTrail: Omit = { + participantId: participant.id, + userId, + userEmail, + event: AuditTrailEvents.UpdateSharingTypes, + eventData: { + siteId: participant.siteId!, + allowedTypes: types, + }, + succeeded: false, + }; + + return await AuditTrail.query().insert(sharingAuditTrail); + } catch (error) { + const [logger] = getLoggers(); + logger.error(`Audit trails inserted failed: ${error}`); + throw error; + } +}; + export const insertApproveAccountAuditTrail = async ( participant: Participant, userEmail: string, diff --git a/src/api/services/participantsService.ts b/src/api/services/participantsService.ts index 81fba8c22..5d44ac5e9 100644 --- a/src/api/services/participantsService.ts +++ b/src/api/services/participantsService.ts @@ -75,8 +75,7 @@ export const getParticipantsBySiteIds = async (siteIds: number[]) => { export const addSharingParticipants = async ( participantSiteId: number, - siteIds: number[], - types: ClientType[] + siteIds: number[] ): Promise => { const sharingListResponse = await getSharingList(participantSiteId); const newSharingSet = new Set([...sharingListResponse.allowed_sites, ...siteIds]); @@ -84,21 +83,38 @@ export const addSharingParticipants = async ( participantSiteId, sharingListResponse.hash, [...newSharingSet], - types + sharingListResponse.allowed_types ); return response; }; export const deleteSharingParticipants = async ( participantSiteId: number, - siteIds: number[], - types: ClientType[] + siteIds: number[] ): Promise => { const sharingListResponse = await getSharingList(participantSiteId); const newSharingList = sharingListResponse.allowed_sites.filter( (siteId) => !siteIds.includes(siteId) ); - return updateSharingList(participantSiteId, sharingListResponse.hash, newSharingList, types); + return updateSharingList( + participantSiteId, + sharingListResponse.hash, + newSharingList, + sharingListResponse.allowed_types + ); +}; + +export const UpdateSharingTypes = async ( + participantSiteId: number, + types: ClientType[] +): Promise => { + const sharingListResponse = await getSharingList(participantSiteId); + return updateSharingList( + participantSiteId, + sharingListResponse.hash, + sharingListResponse.allowed_sites ?? [], + types + ); }; export const sendParticipantApprovedEmail = async (users: User[]) => { diff --git a/src/web/screens/sharingPermissions.tsx b/src/web/screens/sharingPermissions.tsx index 8d2d2e26a..42bd98577 100644 --- a/src/web/screens/sharingPermissions.tsx +++ b/src/web/screens/sharingPermissions.tsx @@ -12,6 +12,7 @@ import { CompleteRecommendations, DeleteSharingParticipants, GetSharingList, + UpdateSharingTypes, } from '../services/participant'; import { preloadAllSitesList, preloadAvailableSiteList } from '../services/site'; import { PortalRoute } from './routeUtils'; @@ -21,13 +22,13 @@ import './sharingPermissions.scss'; function SharingPermissions() { const [showStatusPopup, setShowStatusPopup] = useState(false); const { participant, setParticipant } = useContext(ParticipantContext); - const [sharedSiteIds, setSharedSiteIds] = useState([]); + const [sharedSiteIds, setSharedSiteIds] = useState([]); const [sharedTypes, setSharedTypes] = useState([]); const [statusPopup, setStatusPopup] = useState(); const handleSaveSharingType = async (selectedTypes: ClientType[]) => { try { - const response = await AddSharingParticipants(participant!.id, sharedSiteIds, selectedTypes); + const response = await UpdateSharingTypes(participant!.id, selectedTypes); setStatusPopup({ type: 'Success', message: `${ @@ -37,6 +38,7 @@ function SharingPermissions() { } saved to Your Sharing Permissions`, }); setSharedTypes(response.allowed_types ?? []); + if (!sharedSiteIds) setSharedSiteIds(response.allowed_sites); if (!participant?.completedRecommendations) { const updatedParticipant = await CompleteRecommendations(participant!.id); setParticipant(updatedParticipant); @@ -53,7 +55,7 @@ function SharingPermissions() { const handleAddSharingSite = async (selectedSiteIds: number[]) => { try { - const response = await AddSharingParticipants(participant!.id, selectedSiteIds, sharedTypes); + const response = await AddSharingParticipants(participant!.id, selectedSiteIds); setStatusPopup({ type: 'Success', message: `${ @@ -73,11 +75,7 @@ function SharingPermissions() { const handleDeleteSharingSite = async (siteIdsToDelete: number[]) => { try { - const response = await DeleteSharingParticipants( - participant!.id, - siteIdsToDelete, - sharedTypes - ); + const response = await DeleteSharingParticipants(participant!.id, siteIdsToDelete); setStatusPopup({ type: 'Success', message: `${siteIdsToDelete.length} sharing ${ @@ -105,6 +103,19 @@ function SharingPermissions() { loadSharingList(); }, [loadSharingList]); + const renderSharingPermissionTable = () => { + if (!participant?.completedRecommendations) return; + const tableSharedSiteIds = sharedSiteIds ?? []; + const tableSharedTypes = sharedSiteIds === null ? ['DSP' as ClientType] : sharedTypes; + return ( + + ); + }; + return (

Sharing Permissions

@@ -124,16 +135,10 @@ function SharingPermissions() { - {(participant?.completedRecommendations || sharedSiteIds.length > 0) && ( - - )} + {renderSharingPermissionTable()}
{statusPopup && ( diff --git a/src/web/services/participant.ts b/src/web/services/participant.ts index 5f689ce03..6eed2ca3e 100644 --- a/src/web/services/participant.ts +++ b/src/web/services/participant.ts @@ -134,15 +134,13 @@ export async function GetSharingList(participantId?: number): Promise { try { const result = await axios.post( `/participants/${participantId}/sharingPermission/add`, { newParticipantSites, - newTypes, } ); return result.data; @@ -153,15 +151,13 @@ export async function AddSharingParticipants( export async function DeleteSharingParticipants( participantId: number, - sharingSitesToRemove: number[], - types: ClientType[] + sharingSitesToRemove: number[] ): Promise { try { const result = await axios.post( `/participants/${participantId}/sharingPermission/delete`, { sharingSitesToRemove, - types, } ); return result.data; @@ -170,6 +166,23 @@ export async function DeleteSharingParticipants( } } +export async function UpdateSharingTypes( + participantId: number, + types: ClientType[] +): Promise { + try { + const result = await axios.post( + `/participants/${participantId}/sharingPermission/shareWithTypes`, + { + types, + } + ); + return result.data; + } catch (e: unknown) { + throw backendError(e, 'Could not update sharing types'); + } +} + export type BusinessContactResponse = z.infer; export type BusinessContactForm = { From 6feda9ae722fc05076d4cc0d26d3c5171229f665 Mon Sep 17 00:00:00 2001 From: Jingyi Gao Date: Mon, 25 Sep 2023 17:32:36 +1000 Subject: [PATCH 2/4] Update sharing response type --- src/api/services/adminServiceHelpers.ts | 2 +- src/api/services/participantsService.ts | 6 ++++-- src/web/screens/home.tsx | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/api/services/adminServiceHelpers.ts b/src/api/services/adminServiceHelpers.ts index 81a8943c9..89e9d0782 100644 --- a/src/api/services/adminServiceHelpers.ts +++ b/src/api/services/adminServiceHelpers.ts @@ -27,7 +27,7 @@ export type SiteDTO = { }; export type SharingListResponse = { - allowed_sites: number[]; + allowed_sites: number[] | null; allowed_types: ClientType[]; hash: number; }; diff --git a/src/api/services/participantsService.ts b/src/api/services/participantsService.ts index 5d44ac5e9..37bfdaf95 100644 --- a/src/api/services/participantsService.ts +++ b/src/api/services/participantsService.ts @@ -78,11 +78,12 @@ export const addSharingParticipants = async ( siteIds: number[] ): Promise => { const sharingListResponse = await getSharingList(participantSiteId); - const newSharingSet = new Set([...sharingListResponse.allowed_sites, ...siteIds]); + const existingSharedSites = sharingListResponse.allowed_sites ?? []; + const newSharingSet = new Set([...existingSharedSites, ...siteIds]); const response = await updateSharingList( participantSiteId, sharingListResponse.hash, - [...newSharingSet], + Array.from(newSharingSet), sharingListResponse.allowed_types ); return response; @@ -93,6 +94,7 @@ export const deleteSharingParticipants = async ( siteIds: number[] ): Promise => { const sharingListResponse = await getSharingList(participantSiteId); + if (!sharingListResponse.allowed_sites) throw Error('The site is not shared with any sites'); const newSharingList = sharingListResponse.allowed_sites.filter( (siteId) => !siteIds.includes(siteId) ); diff --git a/src/web/screens/home.tsx b/src/web/screens/home.tsx index b2d1edffe..01b95de3a 100644 --- a/src/web/screens/home.tsx +++ b/src/web/screens/home.tsx @@ -22,7 +22,7 @@ function Home() { try { const sharingList = await GetSharingList(); // TODO include the sites from allowed_types in the count - setSharingPermissionsCount(sharingList.allowed_sites.length); + setSharingPermissionsCount((sharingList.allowed_sites ?? []).length); } catch (e: unknown) { log.error(e); setHasError(true); From 95ba4b4435b7fa70e896f8f6b2cda4e7c58858f5 Mon Sep 17 00:00:00 2001 From: Jingyi Gao Date: Fri, 29 Sep 2023 16:59:14 +1000 Subject: [PATCH 3/4] Address feedback --- src/api/services/participantsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/services/participantsService.ts b/src/api/services/participantsService.ts index 37bfdaf95..8a2b771ee 100644 --- a/src/api/services/participantsService.ts +++ b/src/api/services/participantsService.ts @@ -94,7 +94,7 @@ export const deleteSharingParticipants = async ( siteIds: number[] ): Promise => { const sharingListResponse = await getSharingList(participantSiteId); - if (!sharingListResponse.allowed_sites) throw Error('The site is not shared with any sites'); + if (!sharingListResponse.allowed_sites) throw Error('There are no sharing sites to delete'); const newSharingList = sharingListResponse.allowed_sites.filter( (siteId) => !siteIds.includes(siteId) ); From 5114e23db1b770ef2ba00fbb15a88c3e16b8056a Mon Sep 17 00:00:00 2001 From: Jingyi Gao Date: Thu, 5 Oct 2023 15:31:49 +1100 Subject: [PATCH 4/4] Remove type assertion --- src/web/screens/sharingPermissions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/screens/sharingPermissions.tsx b/src/web/screens/sharingPermissions.tsx index 42bd98577..8cf3c8b0b 100644 --- a/src/web/screens/sharingPermissions.tsx +++ b/src/web/screens/sharingPermissions.tsx @@ -106,7 +106,7 @@ function SharingPermissions() { const renderSharingPermissionTable = () => { if (!participant?.completedRecommendations) return; const tableSharedSiteIds = sharedSiteIds ?? []; - const tableSharedTypes = sharedSiteIds === null ? ['DSP' as ClientType] : sharedTypes; + const tableSharedTypes: ClientType[] = sharedSiteIds === null ? ['DSP'] : sharedTypes; return (