From f8b1abf7f60306c96a264006ad7214b200d273c6 Mon Sep 17 00:00:00 2001 From: fboulnois Date: Tue, 31 Oct 2023 11:02:58 -0400 Subject: [PATCH] [DUOS-2714] Add external access to data submission form (#2359) --- .../data_submission/DataAccessGovernance.js | 7 +- .../data_submission/RegistrationValidation.js | 2 +- .../consent_group/ConsentGroupErrors.js | 8 +- .../consent_group/ConsentGroupForm.js | 4 +- .../consent_group/EditConsentGroup.js | 112 ++++++++++++------ 5 files changed, 86 insertions(+), 47 deletions(-) diff --git a/src/pages/data_submission/DataAccessGovernance.js b/src/pages/data_submission/DataAccessGovernance.js index 3052d9525..017ba92f9 100644 --- a/src/pages/data_submission/DataAccessGovernance.js +++ b/src/pages/data_submission/DataAccessGovernance.js @@ -116,9 +116,8 @@ export const DataAccessGovernance = (props) => { const prefillConsentGroups = useCallback(async () => { var consentGroups = await Promise.all(datasets?.map(async (dataset, idx) => { const dataUse = await normalizeDataUse(dataset?.dataUse); - // DAC is required if openAccess is false - const openAccess = extract('Open Access', dataset); - const dac = openAccess ? undefined : await DAC.get(dataset?.dacId); + const accessManagement = extract('Access Management', dataset); + const dac = 'dacId' in dataset ? await DAC.get(dataset.dacId) : undefined; return { consentGroup: { @@ -130,7 +129,7 @@ export const DataAccessGovernance = (props) => { hmb: dataUse.hmbResearch, diseaseSpecificUse: dataUse.diseaseRestrictions, poa: dataUse.populationOriginsAncestry, - openAccess: openAccess, + accessManagement: accessManagement, otherPrimary: dataUse.other, // secondary diff --git a/src/pages/data_submission/RegistrationValidation.js b/src/pages/data_submission/RegistrationValidation.js index 2675e0269..92afc85f5 100644 --- a/src/pages/data_submission/RegistrationValidation.js +++ b/src/pages/data_submission/RegistrationValidation.js @@ -86,7 +86,7 @@ const updateValidation = (existingValidation, validationError) => { }; } // if the field is required and empty, we shouldn't also error on, e.g., that it isn't a date format - if (existingValidation.failed.includes('required') || validationError === 'required') { + if ((existingValidation.failed && existingValidation.failed.includes('required')) || validationError === 'required') { return { valid: false, failed: ['required'] diff --git a/src/pages/data_submission/consent_group/ConsentGroupErrors.js b/src/pages/data_submission/consent_group/ConsentGroupErrors.js index 1dedcdce7..3baa21382 100644 --- a/src/pages/data_submission/consent_group/ConsentGroupErrors.js +++ b/src/pages/data_submission/consent_group/ConsentGroupErrors.js @@ -23,7 +23,11 @@ const invalidFormatError = (format) => { export const computeConsentGroupValidationErrors = (consentGroup, datasetNames = []) => { const validation = {}; - if (isNil(selectedPrimaryGroup(consentGroup))) { + if (isNil(consentGroup.accessManagement)) { + validation.accessManagement = requiredError; + } + + if (isNil(selectedPrimaryGroup(consentGroup)) && consentGroup.accessManagement !== 'open') { validation.primaryConsent = requiredError; } @@ -61,7 +65,7 @@ export const computeConsentGroupValidationErrors = (consentGroup, datasetNames = validation.otherPrimary = requiredError; } - if (isNil(consentGroup.dataAccessCommitteeId) && consentGroup.openAccess !== true) { + if (isNil(consentGroup.dataAccessCommitteeId) && consentGroup.accessManagement === 'controlled') { validation.dataAccessCommitteeId = requiredError; } diff --git a/src/pages/data_submission/consent_group/ConsentGroupForm.js b/src/pages/data_submission/consent_group/ConsentGroupForm.js index b4a3b9659..ffe02d87f 100644 --- a/src/pages/data_submission/consent_group/ConsentGroupForm.js +++ b/src/pages/data_submission/consent_group/ConsentGroupForm.js @@ -22,12 +22,14 @@ export const ConsentGroupForm = (props) => { datasetId: curConsentGroup.datasetId || null, consentGroupName: curConsentGroup.consentGroupName || '', + // access management is one of: "controlled", "open", "external" + accessManagement: curConsentGroup.accessManagement || undefined, // string + // primary: generalResearchUse: curConsentGroup.generalResearchUse || undefined, hmb: curConsentGroup.hmb || undefined, diseaseSpecificUse: curConsentGroup.diseaseSpecificUse || undefined, // string poa: curConsentGroup.poa || undefined, - openAccess: curConsentGroup.openAccess || undefined, otherPrimary: curConsentGroup.otherPrimary || undefined, // string // secondary: diff --git a/src/pages/data_submission/consent_group/EditConsentGroup.js b/src/pages/data_submission/consent_group/EditConsentGroup.js index 9da1f0015..1923d97c4 100644 --- a/src/pages/data_submission/consent_group/EditConsentGroup.js +++ b/src/pages/data_submission/consent_group/EditConsentGroup.js @@ -14,8 +14,6 @@ export const selectedPrimaryGroup = (consentGroup) => { return 'diseaseSpecificUse'; } else if (!isNil(consentGroup.poa) && consentGroup.poa) { return 'poa'; - } else if (!isNil(consentGroup.openAccess) && consentGroup.openAccess) { - return 'openAccess'; } else if (!isNil(consentGroup.otherPrimary) && isString(consentGroup.otherPrimary)) { return 'otherPrimary'; } @@ -75,7 +73,7 @@ export const EditConsentGroup = (props) => { setConsentGroup((cg) => { const consentGroup = cloneDeep(cg); - updates.forEach(({key, value}) => { + updates.forEach(({ key, value }) => { consentGroup[key] = value; }); @@ -91,7 +89,6 @@ export const EditConsentGroup = (props) => { hmb: false, diseaseSpecificUse: undefined, poa: false, - openAccess: false, otherPrimary: undefined, }, ...{ @@ -119,8 +116,62 @@ export const EditConsentGroup = (props) => { onValidationChange, }), - // primary + // controlled, open and external access div({}, [ + h(FormField, { + title: 'Data Access Management', + description: 'Select a data access management strategy', + id: idx + '_accessManagement_controlled', + name: 'accessManagement', + value: 'controlled', + type: FormFieldTypes.RADIOBUTTON, + toggleText: 'Controlled Access (managed by a DAC in DUOS)', + disabled: disableFields, + defaultValue: consentGroup.accessManagement, + onChange, + validation: validation.accessManagement, + onValidationChange: ({ validation }) => { + onValidationChange({ key: 'accessManagement', validation }); + }, + }), + + h(FormField, { + id: idx + '_accessManagement_open', + name: 'accessManagement', + value: 'open', + type: FormFieldTypes.RADIOBUTTON, + toggleText: 'Open Access (does not need DAC approval)', + disabled: disableFields, + defaultValue: consentGroup.accessManagement, + onChange: ({ key, value }) => { + onPrimaryChange({ key, value }); + }, + validation: validation.accessManagement, + onValidationChange: ({ validation }) => { + onValidationChange({ key: 'accessManagement', validation }); + }, + }), + + h(FormField, { + id: idx + '_accessManagement_external', + name: 'accessManagement', + value: 'external', + type: FormFieldTypes.RADIOBUTTON, + toggleText: 'External Access (managed by a DAC external to DUOS)', + disabled: disableFields, + defaultValue: consentGroup.accessManagement, + onChange, + validation: validation.accessManagement, + onValidationChange: ({ validation }) => { + onValidationChange({ key: 'accessManagement', validation }); + }, + }), + ]), + + // primary + div({ + isRendered: consentGroup.accessManagement !== 'open', + }, [ h(FormField, { title: 'Primary Data Use Terms*', description: 'Please select one of the following data use permissions for your dataset', @@ -229,23 +280,6 @@ export const EditConsentGroup = (props) => { }, }), - h(FormField, { - type: FormFieldTypes.RADIOBUTTON, - id: idx + '_primaryConsent_openAccess', - name: 'primaryConsent', - value: 'openAccess', - toggleText: 'No Restrictions (Open Access Data)', - disabled: disableFields, - defaultValue: selectedPrimaryGroup(consentGroup), - onChange: ({ value }) => { - onPrimaryChange({ key: value, value: true }); - }, - validation: validation.primaryConsent, - onValidationChange: ({ validation }) => { - onValidationChange({ key: 'primaryConsent', validation }); - }, - }), - h(FormField, { type: FormFieldTypes.RADIOBUTTON, id: idx + '_primaryConsent_otherPrimary', @@ -284,7 +318,7 @@ export const EditConsentGroup = (props) => { // secondary div({ - isRendered: consentGroup.openAccess !== true, + isRendered: consentGroup.accessManagement !== 'open', }, [ h(FormField, { title: 'Secondary Data Use Terms', @@ -460,10 +494,10 @@ export const EditConsentGroup = (props) => { // data access committee h(FormField, { - isRendered: consentGroup.openAccess !== true, + isRendered: consentGroup.accessManagement === 'controlled', id: idx + 'dataAccessCommitteeId', name: 'dataAccessCommitteeId', - title: 'Data Access Committee', + title: 'Data Access Committee (DAC)', description: 'Please select which DAC should govern requests for this dataset', type: FormFieldTypes.SELECT, selectOptions: dacs.map((dac) => { @@ -472,7 +506,7 @@ export const EditConsentGroup = (props) => { onChange: ({ key, value }) => { onChange({ key, value: value?.dacId }); }, - validators: [FormValidators.REQUIRED], + validators: consentGroup.accessManagement === 'controlled' ? [FormValidators.REQUIRED] : undefined, validation: validation.dataAccessCommitteeId, disabled: disableFields, defaultValue: dacs.map((dac) => { @@ -483,17 +517,17 @@ export const EditConsentGroup = (props) => { ]), // location - div({style:{ display: 'flex', flexDirection:'row', justifyContent: 'space-between' }}, [ + div({ style: { display: 'flex', flexDirection: 'row', justifyContent: 'space-between' } }, [ h(FormFieldTitle, { required: true, title: 'Data Location', description: 'Please provide the location of your data resource for this consent group', }), ]), - div({className: 'flex flex-row'}, [ + div({ className: 'flex flex-row' }, [ h(FormField, { style: { width: '50%' }, - id: idx+'_dataLocation', + id: idx + '_dataLocation', name: 'dataLocation', type: FormFieldTypes.SELECT, selectOptions: [ @@ -504,14 +538,14 @@ export const EditConsentGroup = (props) => { ], placeholder: 'Data Location(s)', defaultValue: consentGroup.dataLocation, - onChange: ({key, value, isValid}) => { + onChange: ({ key, value, isValid }) => { if (value === 'Not Determined') { // if not determined, clear url field as well. // must do in one batch call, otherwise react gets confused. - onBatchChange({ key, value }, {key: 'url', value: undefined}); + onBatchChange({ key, value }, { key: 'url', value: undefined }); } else { - onChange({key, value, isValid}); + onChange({ key, value, isValid }); } }, validation: validation.dataLocation, @@ -519,7 +553,7 @@ export const EditConsentGroup = (props) => { }), h(FormField, { style: { width: '50%', paddingLeft: '1.5%' }, - id: idx+'_url', + id: idx + '_url', name: 'url', validators: [FormValidators.URL], disabled: consentGroup.dataLocation === 'Not Determined', @@ -532,7 +566,7 @@ export const EditConsentGroup = (props) => { }), ]), h(FormTable, { - id: idx+'_fileTypes', + id: idx + '_fileTypes', name: 'fileTypes', formFields: [ { @@ -573,22 +607,22 @@ export const EditConsentGroup = (props) => { }), ]), - div({style:{ display: 'flex', flexDirection:'row', justifyContent: 'flex-start', alignItems: 'flex-end', marginRight: '30px' }}, [ + div({ style: { display: 'flex', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'flex-end', marginRight: '30px' } }, [ h(FormField, { type: FormFieldTypes.FILE, title: 'NIH Institutional Certification', description: 'If an Institutional Certification for this consent group exists, please upload it here', - id: idx+'_nihInstituionalCertificationFile', + id: idx + '_nihInstituionalCertificationFile', name: 'nihInstituionalCertificationFile', hideTextBar: true, hideInput: true, }), h(FormField, { - style: {margin: '11px'}, + style: { margin: '11px' }, type: FormFieldTypes.FILE, - id: idx+'_fileInputSection', + id: idx + '_fileInputSection', defaultValue: nihInstitutionalCertificationFile, - onChange: ({value}) => setNihInstitutionalCertificationFile(value), + onChange: ({ value }) => setNihInstitutionalCertificationFile(value), hideTextBar: true, }), ]),