From 904c0267775b171478de9daee5cd216bee7f3d51 Mon Sep 17 00:00:00 2001 From: Greg Rushton Date: Fri, 29 Sep 2023 16:28:01 -0400 Subject: [PATCH 1/5] first pass --- src/components/forms/formValidation.js | 8 +++++- src/libs/ajax.js | 10 +++++++ src/pages/DataSubmissionForm.js | 15 +++++++--- src/utils/JsonSchemaUtils.js | 38 ++++++++++++++++---------- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/components/forms/formValidation.js b/src/components/forms/formValidation.js index c4e689a8d..9b5e0e441 100644 --- a/src/components/forms/formValidation.js +++ b/src/components/forms/formValidation.js @@ -30,7 +30,13 @@ export const dateValidator = { msg: 'Please enter a date (YYYY-MM-DD), e.g. 2018-11-13', }; -const validators = [requiredValidator, urlValidator, emailValidator, dateValidator]; +export const uniqueValidator = { + id: 'unique', + isValid: (val, list) => !list.includes(val), + msg: 'Please enter a unique value that doesn\'t exist in the system' +}; + +const validators = [requiredValidator, urlValidator, emailValidator, dateValidator, uniqueValidator]; /** * Validates the form value diff --git a/src/libs/ajax.js b/src/libs/ajax.js index 0d1d4a92e..7ecf91f0e 100644 --- a/src/libs/ajax.js +++ b/src/libs/ajax.js @@ -384,6 +384,16 @@ export const DataSet = { }, }; +export const Study = { + + getStudyNames: async () => { + const url = `${await getApiUrl()}/api/dataset/studyNames`; + const res = await fetchOk(url, Config.authOpts()); + return await res.json(); + } + +}; + export const DatasetAssociation = { createDatasetAssociations: async (objectId, usersIdList) => { diff --git a/src/pages/DataSubmissionForm.js b/src/pages/DataSubmissionForm.js index 7f6b895a3..b2a3720ab 100644 --- a/src/pages/DataSubmissionForm.js +++ b/src/pages/DataSubmissionForm.js @@ -3,7 +3,7 @@ import { validateForm } from '../utils/JsonSchemaUtils'; import { cloneDeep, isNil } from 'lodash/fp'; import { useState, useEffect } from 'react'; -import { Institution, DataSet } from '../libs/ajax'; +import { Institution, DataSet, Study } from '../libs/ajax'; import { Notifications } from '../libs/utils'; import lockIcon from '../images/lock-icon.png'; @@ -15,7 +15,7 @@ import NIHAdministrativeInformation from '../components/data_submission/NIHAdmin import NIHDataManagement from '../components/data_submission/NIHDataManagement'; import NihAnvilUse from '../components/data_submission/NihAnvilUse'; // schema validation is auto-generated from pre-compiled code - if the backend -// schama changes, then run `npm run genschemas` to regenerate this code +// schema changes, then run `npm run genschemas` to regenerate this code import validateSchema from '../assets/schemas/DataRegistrationV1Validation'; import { set } from 'lodash'; import UsgOmbText from '../components/UsgOmbText'; @@ -26,6 +26,7 @@ export const DataSubmissionForm = (props) => { } = props; const [institutions, setInstitutions] = useState([]); + const [studyNames, setStudyNames] = useState([]); const [failedInit, setFailedInit] = useState(false); const [allConsentGroupsSaved, setAllConsentGroupsSaved] = useState(false); @@ -37,9 +38,15 @@ export const DataSubmissionForm = (props) => { setInstitutions(institutions); }; + const getAllStudies = async() => { + const studyNames = await Study.getStudyNames(); + setStudyNames(studyNames); + }; + const init = async () => { try { - getAllInstitutions(); + await getAllInstitutions(); + await getAllStudies(); } catch (error) { setFailedInit(true); Notifications.showError({ @@ -99,7 +106,7 @@ export const DataSubmissionForm = (props) => { formatForRegistration(registration); // check against json schema to see if there are uncaught validation issues - let [valid, validation] = validateForm(validateSchema, registration); + let [valid, validation] = validateForm(validateSchema, registration, studyNames); if (formData.alternativeDataSharingPlan === true) { if (isNil(formFiles.alternativeDataSharingPlanFile)) { validation.alternativeDataSharingPlanFile = { diff --git a/src/utils/JsonSchemaUtils.js b/src/utils/JsonSchemaUtils.js index 993901d20..261da8300 100644 --- a/src/utils/JsonSchemaUtils.js +++ b/src/utils/JsonSchemaUtils.js @@ -1,29 +1,27 @@ import {default as Ajv} from 'ajv/dist/2019'; -import { - urlValidator, - dateValidator, - emailValidator -} from '../components/forms/formValidation'; -import { - get, set, isNil -} from 'lodash'; +import {dateValidator, emailValidator, uniqueValidator, urlValidator} from '../components/forms/formValidation'; +import {get, isNil, set} from 'lodash'; const formats = { date: dateValidator.isValid, uri: urlValidator.isValid, - email: emailValidator.isValid + email: emailValidator.isValid, + unique: uniqueValidator.isValid }; /** * Validates given object according to the schema in a format that * our internal form components can understand. * - * @param {*} compiledSchema Compiled schema + * @param {*} validate Compiled schema * @param {*} obj Form data + * @param {*} studyNames List of existing study names * @returns Form component compatible validation object */ -export const validateForm = (validate, obj) => { - const valid = validate(obj); +export const validateForm = (validate, obj, studyNames = []) => { + + const studyNameUnique = !studyNames.includes(obj.studyName); + const valid = validate(obj) && studyNameUnique; if (valid) { return [true, {}]; @@ -34,10 +32,13 @@ export const validateForm = (validate, obj) => { let path; let errorType; - if (error.keyword === 'required') { - path = error.instancePath +'/'+ error.params.missingProperty; + if (error.keyword === 'unique') { + path = error.instancePath + '/' + error.params.missingProperty; + errorType = 'unique'; + } else if (error.keyword === 'required') { + path = error.instancePath + '/' + error.params.missingProperty; errorType = 'required'; - } else if (error.keyword === 'format'){ + } else if (error.keyword === 'format') { // format errors are, e.g., date/email/uri errors path = error.instancePath; errorType = error.params.format; // e.g., 'date' @@ -58,6 +59,13 @@ export const validateForm = (validate, obj) => { set(validationObject, splitPath, newValidation); }); + if (!studyNameUnique) { + validationObject.studyName = { + failed: ['unique'], + valid: false + }; + } + return [false, validationObject]; }; From 96a48e01c853963102cc3fc46ff7fb01158c1b6c Mon Sep 17 00:00:00 2001 From: Greg Rushton Date: Wed, 4 Oct 2023 06:18:00 -0400 Subject: [PATCH 2/5] doc --- src/pages/DataSubmissionForm.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/DataSubmissionForm.js b/src/pages/DataSubmissionForm.js index b2a3720ab..c0b8607c5 100644 --- a/src/pages/DataSubmissionForm.js +++ b/src/pages/DataSubmissionForm.js @@ -14,8 +14,9 @@ import DataSubmissionStudyInformation from '../components/data_submission/ds_stu import NIHAdministrativeInformation from '../components/data_submission/NIHAdministrativeInformation'; import NIHDataManagement from '../components/data_submission/NIHDataManagement'; import NihAnvilUse from '../components/data_submission/NihAnvilUse'; -// schema validation is auto-generated from pre-compiled code - if the backend -// schema changes, then run `npm run genschemas` to regenerate this code +// Schema validation was previously auto-generated from pre-compiled code +// If any validation changes, it needs to be manually updated in both DataRegistrationV1Validation +// and JsonSchemaUtils import validateSchema from '../assets/schemas/DataRegistrationV1Validation'; import { set } from 'lodash'; import UsgOmbText from '../components/UsgOmbText'; From acaa86f7868ca10e7e11859e9d601503972201c1 Mon Sep 17 00:00:00 2001 From: Greg Rushton Date: Wed, 4 Oct 2023 06:28:59 -0400 Subject: [PATCH 3/5] move back to submission form --- src/pages/DataSubmissionForm.js | 9 +++++++- src/utils/JsonSchemaUtils.js | 38 +++++++++++++-------------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/pages/DataSubmissionForm.js b/src/pages/DataSubmissionForm.js index c0b8607c5..7df743cb0 100644 --- a/src/pages/DataSubmissionForm.js +++ b/src/pages/DataSubmissionForm.js @@ -107,7 +107,14 @@ export const DataSubmissionForm = (props) => { formatForRegistration(registration); // check against json schema to see if there are uncaught validation issues - let [valid, validation] = validateForm(validateSchema, registration, studyNames); + let [valid, validation] = validateForm(validateSchema, registration); + if (studyNames.includes(registration.studyName)) { + validation.studyName = { + failed: ['unique'], + valid: false + }; + } + if (formData.alternativeDataSharingPlan === true) { if (isNil(formFiles.alternativeDataSharingPlanFile)) { validation.alternativeDataSharingPlanFile = { diff --git a/src/utils/JsonSchemaUtils.js b/src/utils/JsonSchemaUtils.js index 261da8300..993901d20 100644 --- a/src/utils/JsonSchemaUtils.js +++ b/src/utils/JsonSchemaUtils.js @@ -1,27 +1,29 @@ import {default as Ajv} from 'ajv/dist/2019'; -import {dateValidator, emailValidator, uniqueValidator, urlValidator} from '../components/forms/formValidation'; -import {get, isNil, set} from 'lodash'; +import { + urlValidator, + dateValidator, + emailValidator +} from '../components/forms/formValidation'; +import { + get, set, isNil +} from 'lodash'; const formats = { date: dateValidator.isValid, uri: urlValidator.isValid, - email: emailValidator.isValid, - unique: uniqueValidator.isValid + email: emailValidator.isValid }; /** * Validates given object according to the schema in a format that * our internal form components can understand. * - * @param {*} validate Compiled schema + * @param {*} compiledSchema Compiled schema * @param {*} obj Form data - * @param {*} studyNames List of existing study names * @returns Form component compatible validation object */ -export const validateForm = (validate, obj, studyNames = []) => { - - const studyNameUnique = !studyNames.includes(obj.studyName); - const valid = validate(obj) && studyNameUnique; +export const validateForm = (validate, obj) => { + const valid = validate(obj); if (valid) { return [true, {}]; @@ -32,13 +34,10 @@ export const validateForm = (validate, obj, studyNames = []) => { let path; let errorType; - if (error.keyword === 'unique') { - path = error.instancePath + '/' + error.params.missingProperty; - errorType = 'unique'; - } else if (error.keyword === 'required') { - path = error.instancePath + '/' + error.params.missingProperty; + if (error.keyword === 'required') { + path = error.instancePath +'/'+ error.params.missingProperty; errorType = 'required'; - } else if (error.keyword === 'format') { + } else if (error.keyword === 'format'){ // format errors are, e.g., date/email/uri errors path = error.instancePath; errorType = error.params.format; // e.g., 'date' @@ -59,13 +58,6 @@ export const validateForm = (validate, obj, studyNames = []) => { set(validationObject, splitPath, newValidation); }); - if (!studyNameUnique) { - validationObject.studyName = { - failed: ['unique'], - valid: false - }; - } - return [false, validationObject]; }; From 69e57225595ac59555bd5500b690f4c7b26d9e0c Mon Sep 17 00:00:00 2001 From: Greg Rushton Date: Wed, 4 Oct 2023 06:33:15 -0400 Subject: [PATCH 4/5] use form validator --- src/pages/DataSubmissionForm.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/DataSubmissionForm.js b/src/pages/DataSubmissionForm.js index 7df743cb0..38c2c7f12 100644 --- a/src/pages/DataSubmissionForm.js +++ b/src/pages/DataSubmissionForm.js @@ -18,6 +18,7 @@ import NihAnvilUse from '../components/data_submission/NihAnvilUse'; // If any validation changes, it needs to be manually updated in both DataRegistrationV1Validation // and JsonSchemaUtils import validateSchema from '../assets/schemas/DataRegistrationV1Validation'; +import {uniqueValidator} from '../components/forms/formValidation'; import { set } from 'lodash'; import UsgOmbText from '../components/UsgOmbText'; @@ -108,7 +109,7 @@ export const DataSubmissionForm = (props) => { // check against json schema to see if there are uncaught validation issues let [valid, validation] = validateForm(validateSchema, registration); - if (studyNames.includes(registration.studyName)) { + if (!uniqueValidator.isValid(registration.studyName, studyNames)) { validation.studyName = { failed: ['unique'], valid: false From 30ed4a2893d4b7cf2938b232b446359a7f016260 Mon Sep 17 00:00:00 2001 From: Greg Rushton Date: Wed, 4 Oct 2023 06:42:12 -0400 Subject: [PATCH 5/5] add missing stub --- .../component/DataSubmission/data_access_governance.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/component/DataSubmission/data_access_governance.spec.js b/cypress/component/DataSubmission/data_access_governance.spec.js index 8b68348be..771ecdc20 100644 --- a/cypress/component/DataSubmission/data_access_governance.spec.js +++ b/cypress/component/DataSubmission/data_access_governance.spec.js @@ -1,6 +1,6 @@ /* eslint-disable no-undef */ import React from 'react'; -import { DAC, User, Institution, Schema } from '../../../src/libs/ajax'; +import { DAC, User, Institution, Schema, Study } from '../../../src/libs/ajax'; import DataSubmissionForm from '../../../src/pages/DataSubmissionForm'; import { mount } from 'cypress/react'; @@ -22,6 +22,7 @@ beforeEach(() => { cy.stub(User, 'getMe').returns(user); cy.stub(Institution, 'list').returns([{name: 'Test Institution'}]); cy.stub(Schema, 'datasetRegistrationV1').returns({}); + cy.stub(Study, 'getStudyNames').returns([]); }); describe('Data Access Governance', function () {