From b45a2c33f30d4b3e5ebd85bc6c4462788c1edf96 Mon Sep 17 00:00:00 2001 From: Nathan Birus Date: Thu, 17 Oct 2024 10:46:32 -0500 Subject: [PATCH 01/13] 1012 - Research Dashboard Collection Phlebotomist Initials are now saved when Save is selected --- src/events.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/events.js b/src/events.js index 86fdc065..2fa519a9 100644 --- a/src/events.js +++ b/src/events.js @@ -1365,7 +1365,10 @@ const collectionSubmission = async (participantData, biospecimenData, continueTo // Make a deep copy. Check for changes at end of function prior to saving. const originalSpecimenData = JSON.parse(JSON.stringify(biospecimenData)); - if (getWorkflow() === 'research' && biospecimenData[conceptIds.collection.collectionTime] === undefined) biospecimenData[conceptIds.collection.collectionTime] = new Date().toISOString(); + + if (getWorkflow() === 'research' && biospecimenData[conceptIds.collection.collectionTime] === undefined) { + biospecimenData[conceptIds.collection.collectionTime] = new Date().toISOString(); + } const inputFields = Array.from(document.getElementsByClassName('input-barcode-id')); const siteTubesList = getSiteTubesLists(biospecimenData); @@ -1495,17 +1498,17 @@ const collectionSubmission = async (participantData, biospecimenData, continueTo if (getWorkflow() === 'clinical') { if (biospecimenData[conceptIds.collection.scannedTime] === undefined) biospecimenData[conceptIds.collection.scannedTime] = new Date().toISOString(); } + } - if (getWorkflow() === 'research') { - let initials = document.getElementById('collectionInitials') - if(initials && initials.value.trim().length == 0) { - errorMessage(initials.id, 'This field is required. Please enter the phlebotomist\'s initials.', focus); - focus = false; - return; - } - else { - biospecimenData[conceptIds.collection.phlebotomistInitials] = initials.value.trim(); - } + if (getWorkflow() === 'research') { + let initials = document.getElementById('collectionInitials') + if(initials && initials.value.trim().length == 0) { + errorMessage(initials.id, 'This field is required. Please enter the phlebotomist\'s initials.', focus); + focus = false; + return; + } + else { + biospecimenData[conceptIds.collection.phlebotomistInitials] = initials.value.trim(); } } @@ -1534,6 +1537,7 @@ const collectionSubmission = async (participantData, biospecimenData, continueTo } } + /** * Handle case where form has been updated but specimen is already finalized. * If specimen has already been finalized, alert user that changes will update the specimen. From 9234a9485fc2b38aca0d5e8ae29e6787f0c40579 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Fri, 18 Oct 2024 17:11:22 -0400 Subject: [PATCH 02/13] Work to use new endpoints + optimize memory usage for home mouthwash workflow --- src/events.js | 11 ++--- src/pages/homeCollection/printLabels.js | 57 +++++++++++++++---------- src/shared.js | 36 +++++++++++++--- 3 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/events.js b/src/events.js index 86fdc065..baabab7a 100644 --- a/src/events.js +++ b/src/events.js @@ -7,7 +7,7 @@ import { checkNonAlphanumericStr, shippingNonAlphaNumericStrMessage, visitType, getParticipantCollections, updateBaselineData, siteSpecificLocationToConceptId, conceptIdToSiteSpecificLocation, locationConceptIDToLocationMap, updateCollectionSettingData, convertToOldBox, translateNumToType, getCollectionsByVisit, getSpecimenAndParticipant, getUserProfile, checkDuplicateTrackingIdFromDb, checkAccessionId, checkSurveyEmailTrigger, checkDerivedVariables, isDeviceMobile, replaceDateInputWithMaskedInput, bagConceptIdList, showModalNotification, showTimedNotifications, showNotificationsCancelOrContinue, validateSpecimenAndParticipantResponse, findReplacementTubeLabels, - showConfirmationModal, dismissBiospecimenModal + showConfirmationModal, dismissBiospecimenModal, getIdToken, finalizeSpecimen } from './shared.js'; import { searchTemplate, searchBiospecimenTemplate } from './pages/dashboard.js'; import { showReportsManifest } from './pages/reportsQuery.js'; @@ -1581,13 +1581,8 @@ const processSpecimenCollectionFormUpdates = async (biospecimenData, participant try { showAnimation(); - await Promise.all([ - updateSpecimen([biospecimenData]), - updateCollectionSettingData(biospecimenData, siteTubesList, participantData), - ]); - - if (baselineVisit && clinicalResearchSetting) await updateBaselineData(siteTubesList, participantData); - await checkDerivedVariables({ "token": participantData["token"] }); + const idToken = await getIdToken(); + await finalizeSpecimen(biospecimenData, participantData, siteTubesList); hideAnimation(); } catch (error) { diff --git a/src/pages/homeCollection/printLabels.js b/src/pages/homeCollection/printLabels.js index 18d70291..70cb5a78 100644 --- a/src/pages/homeCollection/printLabels.js +++ b/src/pages/homeCollection/printLabels.js @@ -56,16 +56,29 @@ const printLabelsTemplate = (name) => { const initializeTotalAddressesToPrint = async () => { showAnimation(); - const totalAddresses = await getTotalAddressesToPrint(); - const processedAddresses = totalAddresses.data.filter(obj => obj.length !== 0) - appState.setState({'totalAddresses': processedAddresses}) - appState.setState({'totalAddressesLength': processedAddresses.length }) + const totalAddressCount = await(getAddressesToPrintCount()); + appState.setState({'totalAddressesLength': totalAddressCount.data }) hideAnimation(); } -export const getTotalAddressesToPrint = async () => { +export const getAddressesToPrintCount = async () => { const idToken = await getIdToken(); - const response = await fetch(`${baseAPI}api=totalAddressesToPrint`, { + const response = await fetch(`${baseAPI}api=totalAddressesToPrintCount`, { + method: "GET", + headers: { + Authorization:"Bearer "+idToken + } + }); + return await response.json(); +} + +export const getTotalAddressesToPrint = async (limit) => { + const idToken = await getIdToken(); + let url = `${baseAPI}api=totalAddressesToPrint`; + if(limit) { + url += `&limit=${limit}` + } + const response = await fetch(url, { method: "GET", headers: { Authorization:"Bearer "+idToken @@ -77,26 +90,26 @@ export const getTotalAddressesToPrint = async () => { const generateParticipantCsvGetter = (name) => { const generateCsvButton = document.getElementById("generateCsv"); if (generateCsvButton) { - generateCsvButton.addEventListener("click", () => { + generateCsvButton.addEventListener("click", async () => { + const totalAddressesLength = appState.getState().totalAddressesLength; const numberToPrint = document.getElementById("numberToPrint").value; - if (numberToPrint) { - const arrayLengthToProcess = appState.getState().totalAddressesLength - if (arrayLengthToProcess >= numberToPrint) { - const arrayToProcess = appState.getState().totalAddresses.filter(obj => obj.length !== 0) - const remainingArrayToProcess = arrayToProcess.slice(numberToPrint, appState.getState().totalAddressesLength) - appState.setState({totalAddresses: remainingArrayToProcess }) - appState.setState({'totalAddressesLength': remainingArrayToProcess.length }) - generateParticipantCsv(arrayToProcess.slice(0, numberToPrint)); + if(!numberToPrint || !totalAddressesLength) { + triggerErrorModal(`No labels to print`); + } else if (numberToPrint > totalAddressesLength) { + triggerErrorModal(`Max labels to print: ${arrayLengthToProcess}`); + } else { + const totalAddressesRes = await getTotalAddressesToPrint(numberToPrint); + if (totalAddressesRes.code === 200) { + const arrayToProcess = totalAddressesRes.data; + appState.setState({'totalAddressesLength': totalAddressesLength - numberToPrint }); // No need for another API call + generateParticipantCsv(arrayToProcess); printLabelsTemplate(name); triggerSuccessModal('Success!'); // Display success message - } - else if (appState.getState().totalAddressesLength === 0) { - triggerErrorModal(`No labels to print`) - } - else { - triggerErrorModal(`Max labels to print: ${arrayLengthToProcess}`); - } + } else { + console.error('response', totalAddressesRes); + triggerErrorModal(`Error getting records; please review console.`); } + } }); } }; diff --git a/src/shared.js b/src/shared.js index a48dee2b..3cc91dc1 100644 --- a/src/shared.js +++ b/src/shared.js @@ -123,8 +123,6 @@ export const getDailyParticipant = async () => { return await response.json(); } - - export const updateParticipant = async (dataObj) => { const idToken = await getIdToken(); const response = await fetch(`${api}api=updateParticipantDataNotSite`, { @@ -804,6 +802,25 @@ export const updateSpecimen = async (array) => { return response.json(); } +export const finalizeSpecimen = async (biospecimenData, participantData, siteTubesList) => { + // Used when finalizing specimen to update both participant and specimen data + logAPICallStartDev('finalizeSpecimen'); + const idToken = await getIdToken(); + let requestObj = { + method: "POST", + headers:{ + Authorization:"Bearer "+idToken, + "Content-Type": "application/json" + }, + body: JSON.stringify({biospecimenData, participantData, siteTubesList}), + } + const response = await fetch(`${api}api=finalizeSpecimen`, requestObj); + console.log('response', response); + logAPICallEndDev('finalizeSpecimen'); + return response.json(); + +} + export const checkDerivedVariables = async (participantObjToken) => { const idToken = await getIdToken(); let requestObj = { @@ -1729,6 +1746,9 @@ export const getLocationsInstitute = async () => { if (siteAcronym === 'BSWH') locations.sort((a, b) => a.localeCompare(b)); logAPICallEndDev('getLocationsInstitute'); + // For the purposes of 1008 we are filtering out some locations. + // This will require more discussion for a long-term implementation + locations = locations.filter(loc => ['River East', 'South Loop', 'Orland Park', 'Henry Ford West Bloomfield Hospital', 'Henry Ford Medical Center-Fairlane'].indexOf(loc) === -1); return locations; } @@ -1811,9 +1831,7 @@ export const getUpdatedParticipantData = async (participantData) => { return responseParticipant.data[0]; } -export const updateCollectionSettingData = async (biospecimenData, tubes, participantData) => { - participantData = await getUpdatedParticipantData(participantData); - +export const generateCollectionSettingData = (biospecimenData, tubes, participantData) => { let settings; let derivedVariables = {}; let visit = biospecimenData[conceptIds.collection.selectedVisit]; @@ -1964,6 +1982,14 @@ export const updateCollectionSettingData = async (biospecimenData, tubes, partic // Update derived variables to 'NO' from 'YES'. After specimens, are unchecked after checking them. settingData = { ...settingData, ...derivedVariables }; + return settingData; +} + +export const updateCollectionSettingData = async (biospecimenData, tubes, participantData) => { + participantData = await getUpdatedParticipantData(participantData); + + const settingData = generateCollectionSettingData(biospecimenData, tubes, participantData); + await updateParticipant(settingData); } From 1095228e8dc1cfec44d661c6950d2e85fa62cc2c Mon Sep 17 00:00:00 2001 From: amber-emmes <160774541+amber-emmes@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:06:22 -0400 Subject: [PATCH 03/13] Update src/pages/homeCollection/printLabels.js Co-authored-by: Joe Armani <93854858+JoeArmani@users.noreply.github.com> --- src/pages/homeCollection/printLabels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/homeCollection/printLabels.js b/src/pages/homeCollection/printLabels.js index 70cb5a78..c69c8d89 100644 --- a/src/pages/homeCollection/printLabels.js +++ b/src/pages/homeCollection/printLabels.js @@ -56,7 +56,7 @@ const printLabelsTemplate = (name) => { const initializeTotalAddressesToPrint = async () => { showAnimation(); - const totalAddressCount = await(getAddressesToPrintCount()); + const totalAddressCount = await getAddressesToPrintCount(); appState.setState({'totalAddressesLength': totalAddressCount.data }) hideAnimation(); } From 6f28d54ccd22c29ca65fa0f9a05edc852126afd9 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Tue, 22 Oct 2024 09:52:29 -0400 Subject: [PATCH 04/13] Removed erroneously included method --- src/shared.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/shared.js b/src/shared.js index 3cc91dc1..4cac87c7 100644 --- a/src/shared.js +++ b/src/shared.js @@ -1985,14 +1985,6 @@ export const generateCollectionSettingData = (biospecimenData, tubes, participan return settingData; } -export const updateCollectionSettingData = async (biospecimenData, tubes, participantData) => { - participantData = await getUpdatedParticipantData(participantData); - - const settingData = generateCollectionSettingData(biospecimenData, tubes, participantData); - - await updateParticipant(settingData); -} - export const updateBaselineData = async (siteTubesList, data) => { data = await getUpdatedParticipantData(data); From 0ecb2517b9e7c080f8f147591ded343131549f50 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Tue, 22 Oct 2024 09:54:36 -0400 Subject: [PATCH 05/13] Logs removed --- src/shared.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/shared.js b/src/shared.js index 4cac87c7..35c1062a 100644 --- a/src/shared.js +++ b/src/shared.js @@ -804,7 +804,6 @@ export const updateSpecimen = async (array) => { export const finalizeSpecimen = async (biospecimenData, participantData, siteTubesList) => { // Used when finalizing specimen to update both participant and specimen data - logAPICallStartDev('finalizeSpecimen'); const idToken = await getIdToken(); let requestObj = { method: "POST", @@ -816,7 +815,6 @@ export const finalizeSpecimen = async (biospecimenData, participantData, siteTub } const response = await fetch(`${api}api=finalizeSpecimen`, requestObj); console.log('response', response); - logAPICallEndDev('finalizeSpecimen'); return response.json(); } From 347900ff107ede74ee21dee3c4f21167eda7d9e9 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Tue, 22 Oct 2024 09:56:12 -0400 Subject: [PATCH 06/13] Added try/catch block --- src/pages/homeCollection/printLabels.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pages/homeCollection/printLabels.js b/src/pages/homeCollection/printLabels.js index c69c8d89..8e686734 100644 --- a/src/pages/homeCollection/printLabels.js +++ b/src/pages/homeCollection/printLabels.js @@ -55,10 +55,15 @@ const printLabelsTemplate = (name) => { }; const initializeTotalAddressesToPrint = async () => { - showAnimation(); - const totalAddressCount = await getAddressesToPrintCount(); - appState.setState({'totalAddressesLength': totalAddressCount.data }) - hideAnimation(); + try { + showAnimation(); + const totalAddressCount = await getAddressesToPrintCount(); + appState.setState({'totalAddressesLength': totalAddressCount.data }) + hideAnimation(); + } catch(err) { + console.error('Error initializing total addresses to print', err); + } + } export const getAddressesToPrintCount = async () => { From 31541fe76fc7ea876c0c5009e8981eeb24f9afb4 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Tue, 22 Oct 2024 10:18:12 -0400 Subject: [PATCH 07/13] finalizeSpecimen renamed to submitSpecimen --- src/events.js | 6 +++--- src/shared.js | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/events.js b/src/events.js index baabab7a..308056f7 100644 --- a/src/events.js +++ b/src/events.js @@ -5,9 +5,9 @@ import { getSiteCouriers, getPage, getNumPages, removeSingleError, displayManifestContactInfo, checkShipForage, checkAlertState, retrieveDateFromIsoString, convertConceptIdToPackageCondition, checkFedexShipDuplicate, shippingDuplicateMessage, checkInParticipant, checkOutParticipant, getCheckedInVisit, participantCanCheckIn, shippingPrintManifestReminder, checkNonAlphanumericStr, shippingNonAlphaNumericStrMessage, visitType, getParticipantCollections, updateBaselineData, - siteSpecificLocationToConceptId, conceptIdToSiteSpecificLocation, locationConceptIDToLocationMap, updateCollectionSettingData, convertToOldBox, translateNumToType, + siteSpecificLocationToConceptId, conceptIdToSiteSpecificLocation, locationConceptIDToLocationMap, convertToOldBox, translateNumToType, getCollectionsByVisit, getSpecimenAndParticipant, getUserProfile, checkDuplicateTrackingIdFromDb, checkAccessionId, checkSurveyEmailTrigger, checkDerivedVariables, isDeviceMobile, replaceDateInputWithMaskedInput, bagConceptIdList, showModalNotification, showTimedNotifications, showNotificationsCancelOrContinue, validateSpecimenAndParticipantResponse, findReplacementTubeLabels, - showConfirmationModal, dismissBiospecimenModal, getIdToken, finalizeSpecimen + showConfirmationModal, dismissBiospecimenModal, getIdToken, submitSpecimen } from './shared.js'; import { searchTemplate, searchBiospecimenTemplate } from './pages/dashboard.js'; import { showReportsManifest } from './pages/reportsQuery.js'; @@ -1582,7 +1582,7 @@ const processSpecimenCollectionFormUpdates = async (biospecimenData, participant showAnimation(); const idToken = await getIdToken(); - await finalizeSpecimen(biospecimenData, participantData, siteTubesList); + await submitSpecimen(biospecimenData, participantData, siteTubesList); hideAnimation(); } catch (error) { diff --git a/src/shared.js b/src/shared.js index 35c1062a..d139053c 100644 --- a/src/shared.js +++ b/src/shared.js @@ -802,7 +802,10 @@ export const updateSpecimen = async (array) => { return response.json(); } -export const finalizeSpecimen = async (biospecimenData, participantData, siteTubesList) => { +// Distinct from updateSpecimen in that this triggers a larger workflow which also +// updates the participant and gets the derived variables +// while updateSpecimen only updates a specimen record +export const submitSpecimen = async (biospecimenData, participantData, siteTubesList) => { // Used when finalizing specimen to update both participant and specimen data const idToken = await getIdToken(); let requestObj = { @@ -813,7 +816,7 @@ export const finalizeSpecimen = async (biospecimenData, participantData, siteTub }, body: JSON.stringify({biospecimenData, participantData, siteTubesList}), } - const response = await fetch(`${api}api=finalizeSpecimen`, requestObj); + const response = await fetch(`${api}api=submitSpecimen`, requestObj); console.log('response', response); return response.json(); From 994e81a897f9d4bba086a989f685f24a708320d2 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Tue, 22 Oct 2024 10:34:10 -0400 Subject: [PATCH 08/13] Extra console.log removed --- src/shared.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shared.js b/src/shared.js index d139053c..eac6149d 100644 --- a/src/shared.js +++ b/src/shared.js @@ -817,7 +817,6 @@ export const submitSpecimen = async (biospecimenData, participantData, siteTubes body: JSON.stringify({biospecimenData, participantData, siteTubesList}), } const response = await fetch(`${api}api=submitSpecimen`, requestObj); - console.log('response', response); return response.json(); } From 00c04e8f3dd900864ad0a8a2bdddc2d065c634d4 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Wed, 23 Oct 2024 14:25:53 -0400 Subject: [PATCH 09/13] Addressed PR comments --- src/events.js | 7 ++- src/shared.js | 156 +------------------------------------------------- 2 files changed, 5 insertions(+), 158 deletions(-) diff --git a/src/events.js b/src/events.js index 308056f7..fa3e629b 100644 --- a/src/events.js +++ b/src/events.js @@ -1581,9 +1581,10 @@ const processSpecimenCollectionFormUpdates = async (biospecimenData, participant try { showAnimation(); - const idToken = await getIdToken(); - await submitSpecimen(biospecimenData, participantData, siteTubesList); - + const {code, message} = await submitSpecimen(biospecimenData, participantData, siteTubesList); + if(code !== 200) { + throw new Error(message); + } hideAnimation(); } catch (error) { hideAnimation(); diff --git a/src/shared.js b/src/shared.js index eac6149d..08627bb9 100644 --- a/src/shared.js +++ b/src/shared.js @@ -806,7 +806,7 @@ export const updateSpecimen = async (array) => { // updates the participant and gets the derived variables // while updateSpecimen only updates a specimen record export const submitSpecimen = async (biospecimenData, participantData, siteTubesList) => { - // Used when finalizing specimen to update both participant and specimen data + // Used when submitting specimen to update both participant and specimen data const idToken = await getIdToken(); let requestObj = { method: "POST", @@ -1831,160 +1831,6 @@ export const getUpdatedParticipantData = async (participantData) => { return responseParticipant.data[0]; } -export const generateCollectionSettingData = (biospecimenData, tubes, participantData) => { - let settings; - let derivedVariables = {}; - let visit = biospecimenData[conceptIds.collection.selectedVisit]; - - const bloodTubes = tubes.filter(tube => tube.tubeType === "Blood tube"); - const urineTubes = tubes.filter(tube => tube.tubeType === "Urine"); - const mouthwashTubes = tubes.filter(tube => tube.tubeType === "Mouthwash"); - - let bloodTubesLength = 0 - let urineTubesLength = 0 - let mouthwashTubesLength = 0 - - const collectionSetting = biospecimenData[conceptIds.collection.collectionSetting]; - const isResearch = collectionSetting === conceptIds.research; - const isClinical = collectionSetting === conceptIds.clinical; - - if (participantData[conceptIds.collectionDetails]) { - settings = participantData[conceptIds.collectionDetails]; - if (!settings[visit]) settings[visit] = {}; - - } else { - settings = { - [visit]: {} - } - } - - if (!settings[visit][conceptIds.bloodCollectionSetting]) { - bloodTubes.forEach(tube => { - const tubeIsCollected = biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes; - if(tubeIsCollected) { - settings[visit][conceptIds.bloodCollectionSetting] = collectionSetting; - if(isResearch) { - settings[visit][conceptIds.baseline.bloodCollectedTime] = biospecimenData[conceptIds.collection.collectionTime]; - } - else if(isClinical) { - settings[visit][conceptIds.clinicalDashboard.bloodCollected] = conceptIds.yes; - settings[visit][conceptIds.clinicalDashboard.bloodCollectedTime] = biospecimenData[conceptIds.collection.scannedTime]; - - settings[visit][conceptIds.anySpecimenCollected] = conceptIds.yes; - - if(!(settings[visit][conceptIds.anySpecimenCollectedTime])) { - settings[visit][conceptIds.anySpecimenCollectedTime] = biospecimenData[conceptIds.collection.scannedTime]; - } - } - bloodTubesLength += 1 - } - }); - } - else if (settings[visit][conceptIds.baseline.bloodCollectedTime] !== '' || settings[visit][conceptIds.clinicalDashboard.bloodCollectedTime] !== ''){ - const participantBloodCollected = participantData[conceptIds.baseline.bloodCollected] === conceptIds.yes; - const totalBloodTubesAvail = bloodTubes.filter((tube) => biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes); - if (totalBloodTubesAvail.length === 0 && participantBloodCollected) { - delete settings[visit][conceptIds.bloodCollectionSetting]; // derived variables & timestamp are updated only if all the blood tubes are unchecked - if (isResearch) { - delete settings[visit][conceptIds.baseline.bloodCollectedTime]; - } - else if (isClinical) { - settings[visit][conceptIds.clinicalDashboard.bloodCollected] = conceptIds.no; - delete settings[visit][conceptIds.clinicalDashboard.bloodCollectedTime]; - - if (urineTubesLength === 0 && mouthwashTubesLength === 0) { // anySpecimenCollected variable will only be updated to NO if mouthwash & urine specimens are not present. - settings[visit][conceptIds.anySpecimenCollected] = conceptIds.no; - if (!(settings[visit][conceptIds.anySpecimenCollectedTime])) { - delete settings[visit][conceptIds.anySpecimenCollectedTime]; - } - } - } - derivedVariables[conceptIds.baseline.bloodCollected] = conceptIds.no; - bloodTubesLength = totalBloodTubesAvail.length; - } - } - - if (!settings[visit][conceptIds.urineCollectionSetting]) { - urineTubes.forEach(tube => { - const tubeIsCollected = biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes; - if (tubeIsCollected) { - settings[visit][conceptIds.urineCollectionSetting] = collectionSetting; - if (isResearch) { - settings[visit][conceptIds.baseline.urineCollectedTime] = biospecimenData[conceptIds.collection.collectionTime]; - } - else if (isClinical) { - settings[visit][conceptIds.clinicalDashboard.urineCollected] = conceptIds.yes; - settings[visit][conceptIds.clinicalDashboard.urineCollectedTime] = biospecimenData[conceptIds.collection.scannedTime]; - - settings[visit][conceptIds.anySpecimenCollected] = conceptIds.yes; - - if (!(settings[visit][conceptIds.anySpecimenCollectedTime])) { - settings[visit][conceptIds.anySpecimenCollectedTime] = biospecimenData[conceptIds.collection.scannedTime]; - } - } - urineTubesLength += 1 - } - }); - } - else if (settings[visit][conceptIds.baseline.urineCollectedTime] !== '' || settings[visit][conceptIds.clinicalDashboard.urineCollectedTime] !== '') { - const participantUrineCollected = participantData[conceptIds.baseline.urineCollected] === conceptIds.yes; - const totalUrineTubesAvail = urineTubes.filter((tube) => biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes); - if (totalUrineTubesAvail.length === 0 && participantUrineCollected) { - delete settings[visit][conceptIds.urineCollectionSetting]; - if(isResearch) { - delete settings[visit][conceptIds.baseline.urineCollectedTime]; - } - else if (isClinical) { - settings[visit][conceptIds.clinicalDashboard.urineCollected] = conceptIds.no; - delete settings[visit][conceptIds.clinicalDashboard.urineCollectedTime]; - - if (bloodTubesLength === 0 && mouthwashTubesLength === 0) { // anySpecimenCollected variable will only be updated to NO if mouthwash & blood specimens are not present. - settings[visit][conceptIds.anySpecimenCollected] = conceptIds.no; - if (!(settings[visit][conceptIds.anySpecimenCollectedTime])) { - delete settings[visit][conceptIds.anySpecimenCollectedTime]; - } - } - } - derivedVariables[conceptIds.baseline.urineCollected] = conceptIds.no; - urineTubesLength = totalUrineTubesAvail.length; - } - } - - if (!settings[visit][conceptIds.mouthwashCollectionSetting]) { - mouthwashTubes.forEach(tube => { - const isTubeCollected = biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes; - if (isTubeCollected) { - settings[visit][conceptIds.mouthwashCollectionSetting] = collectionSetting; - if (isResearch) { - settings[visit][conceptIds.baseline.mouthwashCollectedTime] = biospecimenData[conceptIds.collection.collectionTime]; - } - mouthwashTubesLength += 1 - } - }); - } - else if (settings[visit][conceptIds.baseline.mouthwashCollectedTime] !== '' && participantData[conceptIds.baseline.mouthwashCollected] === conceptIds.yes) { - const isParticipantMouthwashCollected = participantData[conceptIds.baseline.mouthwashCollected] === conceptIds.yes; - const totalMouthwasTubesAvail = mouthwashTubes.filter((tube) => biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes); - if (totalMouthwasTubesAvail.length === 0 && isParticipantMouthwashCollected) { - delete settings[visit][conceptIds.mouthwashCollectionSetting] - if (isResearch) { - delete settings[visit][conceptIds.baseline.mouthwashCollectedTime]; - } - derivedVariables[conceptIds.baseline.mouthwashCollected] = conceptIds.no; - mouthwashTubesLength = totalMouthwasTubesAvail.length; - } - } - - let settingData = { - [conceptIds.collectionDetails]: settings, - uid: participantData?.state?.uid - }; - - // Update derived variables to 'NO' from 'YES'. After specimens, are unchecked after checking them. - settingData = { ...settingData, ...derivedVariables }; - return settingData; -} - export const updateBaselineData = async (siteTubesList, data) => { data = await getUpdatedParticipantData(data); From 762a32adcce6c91f91876c434edf75139c7db8f6 Mon Sep 17 00:00:00 2001 From: amber-emmes <160774541+amber-emmes@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:08:46 -0400 Subject: [PATCH 10/13] Revert "Work to use new endpoints + optimize memory usage for home mouthwash Workflow" --- src/events.js | 16 ++- src/pages/homeCollection/printLabels.js | 66 ++++----- src/shared.js | 180 +++++++++++++++++++++--- 3 files changed, 192 insertions(+), 70 deletions(-) diff --git a/src/events.js b/src/events.js index 0b4f4a01..2fa519a9 100644 --- a/src/events.js +++ b/src/events.js @@ -5,9 +5,9 @@ import { getSiteCouriers, getPage, getNumPages, removeSingleError, displayManifestContactInfo, checkShipForage, checkAlertState, retrieveDateFromIsoString, convertConceptIdToPackageCondition, checkFedexShipDuplicate, shippingDuplicateMessage, checkInParticipant, checkOutParticipant, getCheckedInVisit, participantCanCheckIn, shippingPrintManifestReminder, checkNonAlphanumericStr, shippingNonAlphaNumericStrMessage, visitType, getParticipantCollections, updateBaselineData, - siteSpecificLocationToConceptId, conceptIdToSiteSpecificLocation, locationConceptIDToLocationMap, convertToOldBox, translateNumToType, + siteSpecificLocationToConceptId, conceptIdToSiteSpecificLocation, locationConceptIDToLocationMap, updateCollectionSettingData, convertToOldBox, translateNumToType, getCollectionsByVisit, getSpecimenAndParticipant, getUserProfile, checkDuplicateTrackingIdFromDb, checkAccessionId, checkSurveyEmailTrigger, checkDerivedVariables, isDeviceMobile, replaceDateInputWithMaskedInput, bagConceptIdList, showModalNotification, showTimedNotifications, showNotificationsCancelOrContinue, validateSpecimenAndParticipantResponse, findReplacementTubeLabels, - showConfirmationModal, dismissBiospecimenModal, getIdToken, submitSpecimen + showConfirmationModal, dismissBiospecimenModal } from './shared.js'; import { searchTemplate, searchBiospecimenTemplate } from './pages/dashboard.js'; import { showReportsManifest } from './pages/reportsQuery.js'; @@ -1585,10 +1585,14 @@ const processSpecimenCollectionFormUpdates = async (biospecimenData, participant try { showAnimation(); - const {code, message} = await submitSpecimen(biospecimenData, participantData, siteTubesList); - if(code !== 200) { - throw new Error(message); - } + await Promise.all([ + updateSpecimen([biospecimenData]), + updateCollectionSettingData(biospecimenData, siteTubesList, participantData), + ]); + + if (baselineVisit && clinicalResearchSetting) await updateBaselineData(siteTubesList, participantData); + await checkDerivedVariables({ "token": participantData["token"] }); + hideAnimation(); } catch (error) { hideAnimation(); diff --git a/src/pages/homeCollection/printLabels.js b/src/pages/homeCollection/printLabels.js index 8e686734..18d70291 100644 --- a/src/pages/homeCollection/printLabels.js +++ b/src/pages/homeCollection/printLabels.js @@ -55,35 +55,17 @@ const printLabelsTemplate = (name) => { }; const initializeTotalAddressesToPrint = async () => { - try { - showAnimation(); - const totalAddressCount = await getAddressesToPrintCount(); - appState.setState({'totalAddressesLength': totalAddressCount.data }) - hideAnimation(); - } catch(err) { - console.error('Error initializing total addresses to print', err); - } - -} - -export const getAddressesToPrintCount = async () => { - const idToken = await getIdToken(); - const response = await fetch(`${baseAPI}api=totalAddressesToPrintCount`, { - method: "GET", - headers: { - Authorization:"Bearer "+idToken - } - }); - return await response.json(); + showAnimation(); + const totalAddresses = await getTotalAddressesToPrint(); + const processedAddresses = totalAddresses.data.filter(obj => obj.length !== 0) + appState.setState({'totalAddresses': processedAddresses}) + appState.setState({'totalAddressesLength': processedAddresses.length }) + hideAnimation(); } -export const getTotalAddressesToPrint = async (limit) => { +export const getTotalAddressesToPrint = async () => { const idToken = await getIdToken(); - let url = `${baseAPI}api=totalAddressesToPrint`; - if(limit) { - url += `&limit=${limit}` - } - const response = await fetch(url, { + const response = await fetch(`${baseAPI}api=totalAddressesToPrint`, { method: "GET", headers: { Authorization:"Bearer "+idToken @@ -95,26 +77,26 @@ export const getTotalAddressesToPrint = async (limit) => { const generateParticipantCsvGetter = (name) => { const generateCsvButton = document.getElementById("generateCsv"); if (generateCsvButton) { - generateCsvButton.addEventListener("click", async () => { - const totalAddressesLength = appState.getState().totalAddressesLength; + generateCsvButton.addEventListener("click", () => { const numberToPrint = document.getElementById("numberToPrint").value; - if(!numberToPrint || !totalAddressesLength) { - triggerErrorModal(`No labels to print`); - } else if (numberToPrint > totalAddressesLength) { - triggerErrorModal(`Max labels to print: ${arrayLengthToProcess}`); - } else { - const totalAddressesRes = await getTotalAddressesToPrint(numberToPrint); - if (totalAddressesRes.code === 200) { - const arrayToProcess = totalAddressesRes.data; - appState.setState({'totalAddressesLength': totalAddressesLength - numberToPrint }); // No need for another API call - generateParticipantCsv(arrayToProcess); + if (numberToPrint) { + const arrayLengthToProcess = appState.getState().totalAddressesLength + if (arrayLengthToProcess >= numberToPrint) { + const arrayToProcess = appState.getState().totalAddresses.filter(obj => obj.length !== 0) + const remainingArrayToProcess = arrayToProcess.slice(numberToPrint, appState.getState().totalAddressesLength) + appState.setState({totalAddresses: remainingArrayToProcess }) + appState.setState({'totalAddressesLength': remainingArrayToProcess.length }) + generateParticipantCsv(arrayToProcess.slice(0, numberToPrint)); printLabelsTemplate(name); triggerSuccessModal('Success!'); // Display success message - } else { - console.error('response', totalAddressesRes); - triggerErrorModal(`Error getting records; please review console.`); + } + else if (appState.getState().totalAddressesLength === 0) { + triggerErrorModal(`No labels to print`) + } + else { + triggerErrorModal(`Max labels to print: ${arrayLengthToProcess}`); + } } - } }); } }; diff --git a/src/shared.js b/src/shared.js index 08627bb9..a48dee2b 100644 --- a/src/shared.js +++ b/src/shared.js @@ -123,6 +123,8 @@ export const getDailyParticipant = async () => { return await response.json(); } + + export const updateParticipant = async (dataObj) => { const idToken = await getIdToken(); const response = await fetch(`${api}api=updateParticipantDataNotSite`, { @@ -802,25 +804,6 @@ export const updateSpecimen = async (array) => { return response.json(); } -// Distinct from updateSpecimen in that this triggers a larger workflow which also -// updates the participant and gets the derived variables -// while updateSpecimen only updates a specimen record -export const submitSpecimen = async (biospecimenData, participantData, siteTubesList) => { - // Used when submitting specimen to update both participant and specimen data - const idToken = await getIdToken(); - let requestObj = { - method: "POST", - headers:{ - Authorization:"Bearer "+idToken, - "Content-Type": "application/json" - }, - body: JSON.stringify({biospecimenData, participantData, siteTubesList}), - } - const response = await fetch(`${api}api=submitSpecimen`, requestObj); - return response.json(); - -} - export const checkDerivedVariables = async (participantObjToken) => { const idToken = await getIdToken(); let requestObj = { @@ -1746,9 +1729,6 @@ export const getLocationsInstitute = async () => { if (siteAcronym === 'BSWH') locations.sort((a, b) => a.localeCompare(b)); logAPICallEndDev('getLocationsInstitute'); - // For the purposes of 1008 we are filtering out some locations. - // This will require more discussion for a long-term implementation - locations = locations.filter(loc => ['River East', 'South Loop', 'Orland Park', 'Henry Ford West Bloomfield Hospital', 'Henry Ford Medical Center-Fairlane'].indexOf(loc) === -1); return locations; } @@ -1831,6 +1811,162 @@ export const getUpdatedParticipantData = async (participantData) => { return responseParticipant.data[0]; } +export const updateCollectionSettingData = async (biospecimenData, tubes, participantData) => { + participantData = await getUpdatedParticipantData(participantData); + + let settings; + let derivedVariables = {}; + let visit = biospecimenData[conceptIds.collection.selectedVisit]; + + const bloodTubes = tubes.filter(tube => tube.tubeType === "Blood tube"); + const urineTubes = tubes.filter(tube => tube.tubeType === "Urine"); + const mouthwashTubes = tubes.filter(tube => tube.tubeType === "Mouthwash"); + + let bloodTubesLength = 0 + let urineTubesLength = 0 + let mouthwashTubesLength = 0 + + const collectionSetting = biospecimenData[conceptIds.collection.collectionSetting]; + const isResearch = collectionSetting === conceptIds.research; + const isClinical = collectionSetting === conceptIds.clinical; + + if (participantData[conceptIds.collectionDetails]) { + settings = participantData[conceptIds.collectionDetails]; + if (!settings[visit]) settings[visit] = {}; + + } else { + settings = { + [visit]: {} + } + } + + if (!settings[visit][conceptIds.bloodCollectionSetting]) { + bloodTubes.forEach(tube => { + const tubeIsCollected = biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes; + if(tubeIsCollected) { + settings[visit][conceptIds.bloodCollectionSetting] = collectionSetting; + if(isResearch) { + settings[visit][conceptIds.baseline.bloodCollectedTime] = biospecimenData[conceptIds.collection.collectionTime]; + } + else if(isClinical) { + settings[visit][conceptIds.clinicalDashboard.bloodCollected] = conceptIds.yes; + settings[visit][conceptIds.clinicalDashboard.bloodCollectedTime] = biospecimenData[conceptIds.collection.scannedTime]; + + settings[visit][conceptIds.anySpecimenCollected] = conceptIds.yes; + + if(!(settings[visit][conceptIds.anySpecimenCollectedTime])) { + settings[visit][conceptIds.anySpecimenCollectedTime] = biospecimenData[conceptIds.collection.scannedTime]; + } + } + bloodTubesLength += 1 + } + }); + } + else if (settings[visit][conceptIds.baseline.bloodCollectedTime] !== '' || settings[visit][conceptIds.clinicalDashboard.bloodCollectedTime] !== ''){ + const participantBloodCollected = participantData[conceptIds.baseline.bloodCollected] === conceptIds.yes; + const totalBloodTubesAvail = bloodTubes.filter((tube) => biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes); + if (totalBloodTubesAvail.length === 0 && participantBloodCollected) { + delete settings[visit][conceptIds.bloodCollectionSetting]; // derived variables & timestamp are updated only if all the blood tubes are unchecked + if (isResearch) { + delete settings[visit][conceptIds.baseline.bloodCollectedTime]; + } + else if (isClinical) { + settings[visit][conceptIds.clinicalDashboard.bloodCollected] = conceptIds.no; + delete settings[visit][conceptIds.clinicalDashboard.bloodCollectedTime]; + + if (urineTubesLength === 0 && mouthwashTubesLength === 0) { // anySpecimenCollected variable will only be updated to NO if mouthwash & urine specimens are not present. + settings[visit][conceptIds.anySpecimenCollected] = conceptIds.no; + if (!(settings[visit][conceptIds.anySpecimenCollectedTime])) { + delete settings[visit][conceptIds.anySpecimenCollectedTime]; + } + } + } + derivedVariables[conceptIds.baseline.bloodCollected] = conceptIds.no; + bloodTubesLength = totalBloodTubesAvail.length; + } + } + + if (!settings[visit][conceptIds.urineCollectionSetting]) { + urineTubes.forEach(tube => { + const tubeIsCollected = biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes; + if (tubeIsCollected) { + settings[visit][conceptIds.urineCollectionSetting] = collectionSetting; + if (isResearch) { + settings[visit][conceptIds.baseline.urineCollectedTime] = biospecimenData[conceptIds.collection.collectionTime]; + } + else if (isClinical) { + settings[visit][conceptIds.clinicalDashboard.urineCollected] = conceptIds.yes; + settings[visit][conceptIds.clinicalDashboard.urineCollectedTime] = biospecimenData[conceptIds.collection.scannedTime]; + + settings[visit][conceptIds.anySpecimenCollected] = conceptIds.yes; + + if (!(settings[visit][conceptIds.anySpecimenCollectedTime])) { + settings[visit][conceptIds.anySpecimenCollectedTime] = biospecimenData[conceptIds.collection.scannedTime]; + } + } + urineTubesLength += 1 + } + }); + } + else if (settings[visit][conceptIds.baseline.urineCollectedTime] !== '' || settings[visit][conceptIds.clinicalDashboard.urineCollectedTime] !== '') { + const participantUrineCollected = participantData[conceptIds.baseline.urineCollected] === conceptIds.yes; + const totalUrineTubesAvail = urineTubes.filter((tube) => biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes); + if (totalUrineTubesAvail.length === 0 && participantUrineCollected) { + delete settings[visit][conceptIds.urineCollectionSetting]; + if(isResearch) { + delete settings[visit][conceptIds.baseline.urineCollectedTime]; + } + else if (isClinical) { + settings[visit][conceptIds.clinicalDashboard.urineCollected] = conceptIds.no; + delete settings[visit][conceptIds.clinicalDashboard.urineCollectedTime]; + + if (bloodTubesLength === 0 && mouthwashTubesLength === 0) { // anySpecimenCollected variable will only be updated to NO if mouthwash & blood specimens are not present. + settings[visit][conceptIds.anySpecimenCollected] = conceptIds.no; + if (!(settings[visit][conceptIds.anySpecimenCollectedTime])) { + delete settings[visit][conceptIds.anySpecimenCollectedTime]; + } + } + } + derivedVariables[conceptIds.baseline.urineCollected] = conceptIds.no; + urineTubesLength = totalUrineTubesAvail.length; + } + } + + if (!settings[visit][conceptIds.mouthwashCollectionSetting]) { + mouthwashTubes.forEach(tube => { + const isTubeCollected = biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes; + if (isTubeCollected) { + settings[visit][conceptIds.mouthwashCollectionSetting] = collectionSetting; + if (isResearch) { + settings[visit][conceptIds.baseline.mouthwashCollectedTime] = biospecimenData[conceptIds.collection.collectionTime]; + } + mouthwashTubesLength += 1 + } + }); + } + else if (settings[visit][conceptIds.baseline.mouthwashCollectedTime] !== '' && participantData[conceptIds.baseline.mouthwashCollected] === conceptIds.yes) { + const isParticipantMouthwashCollected = participantData[conceptIds.baseline.mouthwashCollected] === conceptIds.yes; + const totalMouthwasTubesAvail = mouthwashTubes.filter((tube) => biospecimenData[tube.concept][conceptIds.collection.tube.isCollected] === conceptIds.yes); + if (totalMouthwasTubesAvail.length === 0 && isParticipantMouthwashCollected) { + delete settings[visit][conceptIds.mouthwashCollectionSetting] + if (isResearch) { + delete settings[visit][conceptIds.baseline.mouthwashCollectedTime]; + } + derivedVariables[conceptIds.baseline.mouthwashCollected] = conceptIds.no; + mouthwashTubesLength = totalMouthwasTubesAvail.length; + } + } + + let settingData = { + [conceptIds.collectionDetails]: settings, + uid: participantData?.state?.uid + }; + + // Update derived variables to 'NO' from 'YES'. After specimens, are unchecked after checking them. + settingData = { ...settingData, ...derivedVariables }; + await updateParticipant(settingData); +} + export const updateBaselineData = async (siteTubesList, data) => { data = await getUpdatedParticipantData(data); From 4753a607d9d2e271160a0a0c6f5141760b334921 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Mon, 28 Oct 2024 12:39:16 -0400 Subject: [PATCH 11/13] Filter out deprecated locations (1008) --- src/pages/specimen.js | 10 ++++++++-- src/shared.js | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/pages/specimen.js b/src/pages/specimen.js index bd648ad1..a295167e 100644 --- a/src/pages/specimen.js +++ b/src/pages/specimen.js @@ -49,20 +49,26 @@ export const specimenTemplate = async (data, formData) => { const siteAcronym = getSiteAcronym(); if(siteLocations[workflow] && siteLocations[workflow][siteAcronym]) { + + + // For the purposes of 1008 we are filtering out some locations. + // This will require more discussion for a long-term implementation + let siteLocationArray = siteLocations[workflow][siteAcronym]; // Form of [{location, concept}] + siteLocationArray = siteLocationArray.filter(loc => ['River East', 'South Loop', 'Orland Park', 'Henry Ford West Bloomfield Hospital', 'Henry Ford Medical Center-Fairlane'].indexOf(loc.location) === -1); + template += `