From 4aab5a94b4b12fcbea99bc03e5aef85333b0263f Mon Sep 17 00:00:00 2001 From: emmanmills Date: Fri, 25 Jun 2021 14:31:05 -0700 Subject: [PATCH 1/3] Add accessor renewal radio buttons --- .../AccessRequirementList.tsx | 8 +- .../CancelRequestDataAccess.tsx | 2 +- .../ManagedACTAccessRequirement.tsx | 5 +- .../RequestDataAccessStep1.tsx | 7 +- .../RequestDataAccessStep2.tsx | 92 +++++++++++++++++-- .../components/_access-requirement-list.scss | 17 ++++ 6 files changed, 114 insertions(+), 17 deletions(-) diff --git a/src/lib/containers/access_requirement_list/AccessRequirementList.tsx b/src/lib/containers/access_requirement_list/AccessRequirementList.tsx index 2fe00c93da..f720ad4a7c 100644 --- a/src/lib/containers/access_requirement_list/AccessRequirementList.tsx +++ b/src/lib/containers/access_requirement_list/AccessRequirementList.tsx @@ -44,16 +44,16 @@ type AccessRequirementAndStatus = { export type AccessRequirementListProps = { entityId: string // will show this entity info accessRequirementFromProps?: Array - onHide?: Function + onHide?: () => void renderAsModal?: boolean numberOfFilesAffected?: number // if provided, will show this instead of the entity information } export type requestDataStepCallbackProps = { - managedACTAccessRequirement: ManagedACTAccessRequirement + managedACTAccessRequirement?: ManagedACTAccessRequirement step: number - researchProjectId: string - formSubmitRequestObject: RequestInterface + researchProjectId?: string + formSubmitRequestObject?: RequestInterface } export enum SUPPORTED_ACCESS_REQUIREMENTS { diff --git a/src/lib/containers/access_requirement_list/managedACTAccess/CancelRequestDataAccess.tsx b/src/lib/containers/access_requirement_list/managedACTAccess/CancelRequestDataAccess.tsx index 2be414e65f..7d52adcf38 100644 --- a/src/lib/containers/access_requirement_list/managedACTAccess/CancelRequestDataAccess.tsx +++ b/src/lib/containers/access_requirement_list/managedACTAccess/CancelRequestDataAccess.tsx @@ -9,7 +9,7 @@ import { useSynapseContext } from '../../../utils/SynapseContext' export type CancelRequestDataAccessProps = { formSubmitRequestObject: RequestInterface | undefined - onHide: Function + onHide: () => void } const CancelRequestDataAccess: React.FC = props => { diff --git a/src/lib/containers/access_requirement_list/managedACTAccess/ManagedACTAccessRequirement.tsx b/src/lib/containers/access_requirement_list/managedACTAccess/ManagedACTAccessRequirement.tsx index 9aea1e7a99..6ef231997a 100644 --- a/src/lib/containers/access_requirement_list/managedACTAccess/ManagedACTAccessRequirement.tsx +++ b/src/lib/containers/access_requirement_list/managedACTAccess/ManagedACTAccessRequirement.tsx @@ -8,14 +8,15 @@ import { SynapseClient } from '../../../utils' import RequestDataAccess from './RequestDataAccess' import { ManagedACTAccessRequirementStatus } from '../../../utils/synapseTypes/AccessRequirement/ManagedACTAccessRequirementStatus' import { useSynapseContext } from '../../../utils/SynapseContext' +import { requestDataStepCallbackProps } from '../AccessRequirementList' export type ManagedACTAccessRequirementComponentProps = { entityId: string user: UserProfile | undefined accessRequirement: ManagedACTAccessRequirement accessRequirementStatus: ManagedACTAccessRequirementStatus - onHide?: Function - requestDataStepCallback?: Function + onHide?: () => void + requestDataStepCallback?: (props: requestDataStepCallbackProps) => void } const ManagedACTAccessRequirementComponent: React.FC = props => { diff --git a/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep1.tsx b/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep1.tsx index f769c942b5..deb285a3e3 100644 --- a/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep1.tsx +++ b/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep1.tsx @@ -10,11 +10,12 @@ import { ResearchProject } from '../../../utils/synapseTypes/ResearchProject' import { ManagedACTAccessRequirement } from '../../../utils/synapseTypes' import { AlertProps } from './RequestDataAccessStep2' import { useSynapseContext } from '../../../utils/SynapseContext' +import { requestDataStepCallbackProps } from '../AccessRequirementList' export type RequestDataAccessStep1Props = { - requestDataStepCallback?: Function + requestDataStepCallback?: (props: requestDataStepCallbackProps) => void managedACTAccessRequirement: ManagedACTAccessRequirement - onHide: Function + onHide: () => void } const RequestDataAccessStep1: React.FC = props => { @@ -70,7 +71,7 @@ const RequestDataAccessStep1: React.FC = props => { ) } - const handleSubmit = async (e: React.FormEvent) => { + const handleSubmit = (e: React.FormEvent) => { e.preventDefault() const requestObj: ResearchProject = Object.assign( {}, diff --git a/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx b/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx index 298604430e..7261648fd9 100644 --- a/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx +++ b/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx @@ -30,14 +30,16 @@ import { UserCardSmall } from '../../UserCardSmall' import IconSvg from '../../IconSvg' import { useSynapseContext } from '../../../utils/SynapseContext' import { RenewalInterface } from '../../../utils/synapseTypes/AccessRequirement/RenewalInterface' +import { RadioGroup } from '../../widgets/RadioGroup' +import { requestDataStepCallbackProps } from '../AccessRequirementList' export type RequestDataAccessStep2Props = { managedACTAccessRequirement: ManagedACTAccessRequirement entityId: string - requestDataStepCallback: Function + requestDataStepCallback: (props: requestDataStepCallbackProps) => void user: UserProfile researchProjectId: string - onHide: Function + onHide: () => void } export type DataAccessDoc = { @@ -66,6 +68,10 @@ export type AlertProps = { message: string | JSX.Element } +type requestedFileTypesMap = { + [key: string]: string[] +} + const RequestDataAccessStep2: React.FC = props => { const { requestDataStepCallback, @@ -83,10 +89,12 @@ const RequestDataAccessStep2: React.FC = props => { const [ formSubmitRequestObject, setFormSubmitRequestObject, - ] = useState() + ] = useState() const [alert, setAlert] = useState() const [isRenewal, setIsRenewal] = useState(false) - const requestedFileTypes = {} + const [accessorRadioValue, setAccessorRadioValue] = useState([]) + + const requestedFileTypes:requestedFileTypesMap = {} const batchFileRequest: BatchFileRequest = { requestedFiles: [], includeFileHandles: true, @@ -120,8 +128,28 @@ const RequestDataAccessStep2: React.FC = props => { // get data access required docs data to display file names getFilesData(dataAccessRequestData) + // renewal case if (dataAccessRequestData.concreteType === 'org.sagebionetworks.repo.model.dataaccess.Renewal') { setIsRenewal(true) + if (dataAccessRequestData.accessorChanges?.length) { + const accessorsArr = dataAccessRequestData.accessorChanges.map(item => { + return { + userId: item.userId, + type: AccessType.RENEW_ACCESS, + } + }) + setFormSubmitRequestObject(prevState => { + return Object.assign({}, prevState, { + accessorChanges: accessorsArr, + }) + }) + setAccessorRadioValue(accessorsArr) + } else { + setAccessorRadioValue([{ + userId: user.ownerId, + type: AccessType.RENEW_ACCESS, + }]) + } } } @@ -241,7 +269,7 @@ const RequestDataAccessStep2: React.FC = props => { getFiles(batchFileRequest, accessToken).then(resp => { resp.requestedFiles.forEach(file => { const fileName = file.fileHandle!.fileName - const fileTypes = requestedFileTypes[file.fileHandleId] + const fileTypes:string[] = requestedFileTypes[file.fileHandleId] fileTypes.forEach((type: string) => { switch (type) { @@ -387,7 +415,7 @@ const RequestDataAccessStep2: React.FC = props => { const onClearAccessor = (pid: string) => { // Update the view const filtered: UserProfile[] = accessorProfiles.filter( - item => item.ownerId !== pid, + item => item.ownerId !== pid ) setAccessorProfiles(filtered) @@ -403,12 +431,20 @@ const RequestDataAccessStep2: React.FC = props => { accessorChanges: newAccessorChanges, }) }) + + // Update accessor renewal radio buttons if available + if (isRenewal) { + const filteredRadio:AccessorChange[] = accessorRadioValue.filter( + item => item.userId !== pid + ) + setAccessorRadioValue(filteredRadio) + } } const onClearAttachment = (fid: string) => { // Update the view const filtered: DataAccessDoc[] = attachments.filter( - item => item.fileHandleId !== fid, + item => item.fileHandleId !== fid ) setAttachments(filtered) @@ -509,6 +545,25 @@ const RequestDataAccessStep2: React.FC = props => { } } + // For renewal only + const onAccessorRadioBtnChange = (accessType: AccessType, userId: string) => { + // Make the radio button appears selected when clicked. + const copy = [...accessorRadioValue] + const index = copy.findIndex(item => item.userId === userId) + copy[index].type = accessType + setAccessorRadioValue(copy) + + // Update formSubmitRequestObject + const formCopy = formSubmitRequestObject?.accessorChanges || [] + const index2 = formCopy.findIndex(item => item.userId === userId) + formCopy[index2].type = accessType + setFormSubmitRequestObject(prevState => { + return Object.assign({}, prevState, { + accessorChanges: formCopy + }) + }) + } + return ( <>
= props => { ) } + { + isRenewal && accessorRadioValue[i] && ( + <> + + onAccessorRadioBtnChange(value as AccessType, profile.ownerId) + } + > + + ) + } ) })} diff --git a/src/lib/style/components/_access-requirement-list.scss b/src/lib/style/components/_access-requirement-list.scss index e9ca362574..ddbeb892f2 100644 --- a/src/lib/style/components/_access-requirement-list.scss +++ b/src/lib/style/components/_access-requirement-list.scss @@ -169,6 +169,23 @@ margin-bottom: 0.5rem; display: inline-block; } + .SRC-userCard { + svg { + margin-top: 8px; + } + } + .radiogroup { + flex: auto; + text-align: right; + .radio, .radio+.radio { + display: inline-block; + margin: 0; + margin-left: 1rem; + } + label { + margin-bottom: 0; + } + } } } } From d83a078f88bef620f3a5afea8565f44fbab94327 Mon Sep 17 00:00:00 2001 From: emmanmills Date: Fri, 25 Jun 2021 15:00:05 -0700 Subject: [PATCH 2/3] Remove useless code --- .../managedACTAccess/RequestDataAccessStep2.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx b/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx index 7261648fd9..d30f0a6903 100644 --- a/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx +++ b/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx @@ -144,11 +144,6 @@ const RequestDataAccessStep2: React.FC = props => { }) }) setAccessorRadioValue(accessorsArr) - } else { - setAccessorRadioValue([{ - userId: user.ownerId, - type: AccessType.RENEW_ACCESS, - }]) } } } From 3514dbac8238635554dadde4fe77f956d10a2de0 Mon Sep 17 00:00:00 2001 From: emmanmills Date: Mon, 28 Jun 2021 16:55:51 -0700 Subject: [PATCH 3/3] Code review changes to fix renew and revoke logic --- .../RequestDataAccessStep2.tsx | 177 +++++++++--------- 1 file changed, 86 insertions(+), 91 deletions(-) diff --git a/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx b/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx index d30f0a6903..b650e15265 100644 --- a/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx +++ b/src/lib/containers/access_requirement_list/managedACTAccess/RequestDataAccessStep2.tsx @@ -72,6 +72,11 @@ type requestedFileTypesMap = { [key: string]: string[] } +type Accessor = { + profile: UserProfile + accessType: AccessType +} + const RequestDataAccessStep2: React.FC = props => { const { requestDataStepCallback, @@ -85,14 +90,13 @@ const RequestDataAccessStep2: React.FC = props => { const [DUC, setDUC] = useState() const [IRB, setIRB] = useState() const [attachments, setAttachments] = useState([]) - const [accessorProfiles, setAccessorProfiles] = useState([]) const [ formSubmitRequestObject, setFormSubmitRequestObject, ] = useState() const [alert, setAlert] = useState() const [isRenewal, setIsRenewal] = useState(false) - const [accessorRadioValue, setAccessorRadioValue] = useState([]) + const [accessors, setAccessors] = useState([]) const requestedFileTypes:requestedFileTypesMap = {} const batchFileRequest: BatchFileRequest = { @@ -120,6 +124,11 @@ const RequestDataAccessStep2: React.FC = props => { accessToken!, ) + // renewal case + if (dataAccessRequestData.concreteType === 'org.sagebionetworks.repo.model.dataaccess.Renewal') { + setIsRenewal(true) + } + // initialize form submission request object dataAccessRequestData.researchProjectId = researchProjectId setFormSubmitRequestObject(dataAccessRequestData) @@ -127,25 +136,6 @@ const RequestDataAccessStep2: React.FC = props => { getAccessorsData(dataAccessRequestData) // get data access required docs data to display file names getFilesData(dataAccessRequestData) - - // renewal case - if (dataAccessRequestData.concreteType === 'org.sagebionetworks.repo.model.dataaccess.Renewal') { - setIsRenewal(true) - if (dataAccessRequestData.accessorChanges?.length) { - const accessorsArr = dataAccessRequestData.accessorChanges.map(item => { - return { - userId: item.userId, - type: AccessType.RENEW_ACCESS, - } - }) - setFormSubmitRequestObject(prevState => { - return Object.assign({}, prevState, { - accessorChanges: accessorsArr, - }) - }) - setAccessorRadioValue(accessorsArr) - } - } } const getAccessorsData = (dataAccessRequestData: RequestInterface) => { @@ -175,7 +165,13 @@ const RequestDataAccessStep2: React.FC = props => { return getUserProfileById(accessToken, userId) }) Promise.all(promises).then(profiles => { - setAccessorProfiles(profiles) + const profileAndAccessType:Accessor[] = profiles.map((item, i) => { + return { + profile: item, + accessType: accessorChanges[i].type + } + }) + setAccessors(profileAndAccessType) }) } @@ -409,16 +405,16 @@ const RequestDataAccessStep2: React.FC = props => { const onClearAccessor = (pid: string) => { // Update the view - const filtered: UserProfile[] = accessorProfiles.filter( - item => item.ownerId !== pid + const filtered:Accessor[] = accessors.filter(item => + item.profile.ownerId !== pid ) - setAccessorProfiles(filtered) + setAccessors(filtered) // Update form submission request object const newAccessorChanges: AccessorChange[] = filtered.map(item => { return { - userId: item.ownerId, - type: AccessType.GAIN_ACCESS, + userId: item.profile.ownerId, + type: item.accessType } }) setFormSubmitRequestObject(prevState => { @@ -426,14 +422,6 @@ const RequestDataAccessStep2: React.FC = props => { accessorChanges: newAccessorChanges, }) }) - - // Update accessor renewal radio buttons if available - if (isRenewal) { - const filteredRadio:AccessorChange[] = accessorRadioValue.filter( - item => item.userId !== pid - ) - setAccessorRadioValue(filteredRadio) - } } const onClearAttachment = (fid: string) => { @@ -497,15 +485,19 @@ const RequestDataAccessStep2: React.FC = props => { // User search input event handler const onSelectUserCallback = (selected: UserProfile) => { - setAccessorProfiles(prev => [ + setAccessors(prev => [ ...prev, { - ownerId: selected.ownerId, - firstName: selected.firstName, - lastName: selected.lastName, - userName: selected.userName, - }, + profile: { + ownerId: selected.ownerId, + firstName: selected.firstName, + lastName: selected.lastName, + userName: selected.userName, + }, + accessType: AccessType.GAIN_ACCESS + } ]) + const selectedAccessor: AccessorChange = { userId: selected.ownerId, type: AccessType.GAIN_ACCESS, @@ -543,10 +535,10 @@ const RequestDataAccessStep2: React.FC = props => { // For renewal only const onAccessorRadioBtnChange = (accessType: AccessType, userId: string) => { // Make the radio button appears selected when clicked. - const copy = [...accessorRadioValue] - const index = copy.findIndex(item => item.userId === userId) - copy[index].type = accessType - setAccessorRadioValue(copy) + const copy = [...accessors] + const index = copy.findIndex(item => item.profile.ownerId === userId) + copy[index].accessType = accessType + setAccessors(copy) // Update formSubmitRequestObject const formCopy = formSubmitRequestObject?.accessorChanges || [] @@ -607,52 +599,55 @@ const RequestDataAccessStep2: React.FC = props => { {/* Accessors Checkboxes */} - {accessorProfiles.map((profile, i) => { - return ( -
- - { - // only display delete button if the user profile is other users. - user.ownerId !== profile.ownerId && ( - - ) - } - { - isRenewal && accessorRadioValue[i] && ( - <> - - onAccessorRadioBtnChange(value as AccessType, profile.ownerId) - } - > - - ) - } -
- ) - })} + { + accessors.map((accessor, i ) => { + return ( +
+ + { + // only display delete button if the user profile is other users and has not access before + (user.ownerId !== accessor.profile.ownerId) && (accessor.accessType === AccessType.GAIN_ACCESS) && ( + + ) + } + { + // Renewal/Revoke data access, only display if isRenewal is true + isRenewal && (accessor.accessType !== AccessType.GAIN_ACCESS) && ( + <> + + onAccessorRadioBtnChange(value as AccessType, accessor.profile.ownerId) + } + > + + ) + } +
+ ) + }) + }
{/* DUC */}