From 2067d8ccfc5cc16b67b4e8637d30f89a71148cd2 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Tue, 3 Sep 2024 09:28:50 -0400 Subject: [PATCH 01/13] Participant reset work WIP --- index.html | 2 ++ index.js | 2 ++ src/participantDetails.js | 13 ++++++++++- src/participantDetailsHelpers.js | 39 ++++++++++++++++++++++++++++++++ src/utils.js | 1 + 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 76289d5..95385fb 100644 --- a/index.html +++ b/index.html @@ -98,6 +98,8 @@ + + diff --git a/index.js b/index.js index 8f4d732..dca7f32 100644 --- a/index.js +++ b/index.js @@ -71,6 +71,8 @@ window.onload = async () => { else { !firebase.apps.length ? firebase.initializeApp(devFirebaseConfig) : firebase.app(); !isLocalDev && window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'dev' }); + // TODO: Remove this line + if (location.host.startsWith('localhost')) firebase.functions().useFunctionsEmulator('http://localhost:5001'); } !isLocalDev && window.DD_RUM && window.DD_RUM.startSessionReplayRecording(); diff --git a/src/participantDetails.js b/src/participantDetails.js index be1de91..b150057 100644 --- a/src/participantDetails.js +++ b/src/participantDetails.js @@ -1,5 +1,5 @@ import { dashboardNavBarLinks, removeActiveClass } from './navigationBar.js'; -import { attachUpdateLoginMethodListeners, allStates, closeModal, getFieldValues, getImportantRows, getModalLabel, hideUneditableButtons, renderReturnSearchResults, resetChanges, saveResponses, showSaveNoteInModal, submitClickHandler, suffixList, languageList, viewParticipantSummary, } from './participantDetailsHelpers.js'; +import { attachUpdateLoginMethodListeners, allStates, closeModal, getFieldValues, getImportantRows, getModalLabel, hideUneditableButtons, renderReturnSearchResults, resetChanges, saveResponses, showSaveNoteInModal, submitClickHandler, resetClickHandlers, suffixList, languageList, viewParticipantSummary, } from './participantDetailsHelpers.js'; import fieldMapping from './fieldToConceptIdMapping.js'; import { renderParticipantHeader } from './participantHeader.js'; import { getDataAttributes } from './utils.js'; @@ -43,6 +43,7 @@ export const renderParticipantDetails = (participant, changedOption) => { renderReturnSearchResults(); attachUpdateLoginMethodListeners(participant[fieldMapping.accountEmail], participant[fieldMapping.accountPhone], participant.token, participant.state.uid); submitClickHandler(participant, changedOption); + resetClickHandlers(participant); } export const render = (participant, changedOption) => { @@ -63,6 +64,7 @@ export const render = (participant, changedOption) => { ${renderParticipantHeader(participant)} ${renderBackToSearchDivAndButton()} ${renderCancelChangesAndSaveChangesButtons()} + ${renderResetUserButton()} ${renderDetailsTableHeader()} `; @@ -282,6 +284,15 @@ const renderBackToSearchDivAndButton = () => { `; }; +const renderResetUserButton = () => { + // @TODO: Make this dev environment only + return ` +
+ +
+ ` +} + const renderDetailsTableHeader = () => { return `

Participant Details

diff --git a/src/participantDetailsHelpers.js b/src/participantDetailsHelpers.js index cd64675..3980ba1 100644 --- a/src/participantDetailsHelpers.js +++ b/src/participantDetailsHelpers.js @@ -1206,6 +1206,15 @@ export const submitClickHandler = async (participant, changedOption) => { } }; +export const resetClickHandlers = async (participant) => { + console.log('participant', participant); + const participantUid = participant?.state?.uid; + const resetButton = document.getElementById('resetUserBtn'); + resetButton.addEventListener('click', async (e) => { + + }); +} + /** * Handle the query.frstName and query.lastName fields. * Check changedUserDataForProfile the participant profile for all name types. If a name is in changedUserDataForProfile, Add it to the queryNameArray. @@ -1387,6 +1396,10 @@ const processUserDataUpdate = async (changedUserDataForProfile, changedUserDataF }); return true; }; + +const processUserDataReset = async() => { + +} /** * Update the user's history based on new data entered by the user. This only triggers if the user's profile is verified. @@ -1490,3 +1503,29 @@ export const postUserDataUpdate = async (changedUserData) => { throw error; } } + +export const getResetUserData = async (uid) => { + try { + const idToken = await getIdToken(); + const response = await fetch(`${baseAPI}/dashboard?api=resetUser`, { + method: "GET", + headers:{ + Authorization: "Bearer " + idToken, + "Content-Type": "application/json" + }, + body: JSON.stringify({uid}) + }); + + console.log('response', response); + + if (!response.ok) { + const error = (response.status + ": " + (await response.json()).message); + throw new Error(error); + } + + return await response.json(); + } catch (error) { + console.error('Error in getResetUserData:', error); + throw error; + } +} \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index 16f22d5..bc5064f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -115,6 +115,7 @@ export const urls = { let api = ``; if(location.host === urls.prod) api = 'https://api-myconnect.cancer.gov'; else if(location.host === urls.stage) api = 'https://api-myconnect-stage.cancer.gov'; +else if(location.host.startsWith('localhost')) api = 'http://localhost:5001/nih-nci-dceg-connect-dev/us-central1'; else api = 'https://us-central1-nih-nci-dceg-connect-dev.cloudfunctions.net'; export const baseAPI = api; From 2920984f114fb1acff53eaf7db48587804d240ce Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Tue, 3 Sep 2024 09:51:12 -0400 Subject: [PATCH 02/13] Local Dev key setup done --- .gitignore | 5 ++++- index.js | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index edae712..08b8e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -69,4 +69,7 @@ typings/ scratch scratch.js .eslintrc.json -.hintrc \ No newline at end of file +.hintrc + +# private local dev Firebase key +config/local-dev/ \ No newline at end of file diff --git a/index.js b/index.js index 8f4d732..4285435 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,11 @@ import fieldMapping from './src/fieldToConceptIdMapping.js'; import { nameToKeyObj } from './src/idsToName.js'; import { renderAllCharts } from './src/participantChartsRender.js'; import { firebaseConfig as devFirebaseConfig } from "./config/dev/config.js"; +// When doing local development, uncomment this and comment out the line below it. +// Get the API key file from Box or the DevOps team +// Do not accept PRs with the localDevFirebaseConfig import uncommented +// import { firebaseConfig as localDevFirebaseConfig } from "./config/local-dev/config.js"; +const localDevFirebaseConfig = devFirebaseConfig; import { firebaseConfig as stageFirebaseConfig } from "./config/stage/config.js"; import { firebaseConfig as prodFirebaseConfig } from "./config/prod/config.js"; import { SSOConfig as devSSOConfig} from './config/dev/identityProvider.js'; @@ -68,6 +73,9 @@ window.onload = async () => { !firebase.apps.length ? firebase.initializeApp(stageFirebaseConfig) : firebase.app(); window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'stage' }); } + else if (isLocalDev) { + !firebase.apps.length ? firebase.initializeApp(localDevFirebaseConfig) : firebase.app(); + } else { !firebase.apps.length ? firebase.initializeApp(devFirebaseConfig) : firebase.app(); !isLocalDev && window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'dev' }); From 0f52d84b119ccdf11943611259c19ab84c1ccdd0 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Thu, 5 Sep 2024 14:23:00 -0400 Subject: [PATCH 03/13] WIP --- src/participantDetailsHelpers.js | 8 ++++---- src/participantLookup.js | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/participantDetailsHelpers.js b/src/participantDetailsHelpers.js index 3980ba1..951d7fb 100644 --- a/src/participantDetailsHelpers.js +++ b/src/participantDetailsHelpers.js @@ -1211,7 +1211,7 @@ export const resetClickHandlers = async (participant) => { const participantUid = participant?.state?.uid; const resetButton = document.getElementById('resetUserBtn'); resetButton.addEventListener('click', async (e) => { - + await postResetUserData(participantUid); }); } @@ -1504,11 +1504,11 @@ export const postUserDataUpdate = async (changedUserData) => { } } -export const getResetUserData = async (uid) => { +export const postResetUserData = async (uid) => { try { const idToken = await getIdToken(); const response = await fetch(`${baseAPI}/dashboard?api=resetUser`, { - method: "GET", + method: "POST", headers:{ Authorization: "Bearer " + idToken, "Content-Type": "application/json" @@ -1525,7 +1525,7 @@ export const getResetUserData = async (uid) => { return await response.json(); } catch (error) { - console.error('Error in getResetUserData:', error); + console.error('Error in postResetUserData:', error); throw error; } } \ No newline at end of file diff --git a/src/participantLookup.js b/src/participantLookup.js index e590cd0..10d40da 100644 --- a/src/participantLookup.js +++ b/src/participantLookup.js @@ -204,6 +204,7 @@ export const performSearch = async (query, sitePref, failedElem) => { if(response.code === 200 && response.data.length > 0) { const mainContent = document.getElementById('mainContent') let filterRawData = filterdata(response.data); + console.log('filterRawData', filterRawData); if (sitePref !== undefined && sitePref != null && sitePref !== 'allResults') { const sitePrefId = nameToKeyObj[sitePref]; From 8ef38bad2019d2f67b077ae30ce854b0876bac17 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Mon, 9 Sep 2024 09:10:56 -0400 Subject: [PATCH 04/13] Reset user button limited to dev; commit parameter now passed along Reset also has confirm dialog now. --- src/participantDetails.js | 53 ++++++++++++++++++++++------ src/participantDetailsHelpers.js | 59 +++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/src/participantDetails.js b/src/participantDetails.js index b150057..341c5a1 100644 --- a/src/participantDetails.js +++ b/src/participantDetails.js @@ -2,7 +2,7 @@ import { dashboardNavBarLinks, removeActiveClass } from './navigationBar.js'; import { attachUpdateLoginMethodListeners, allStates, closeModal, getFieldValues, getImportantRows, getModalLabel, hideUneditableButtons, renderReturnSearchResults, resetChanges, saveResponses, showSaveNoteInModal, submitClickHandler, resetClickHandlers, suffixList, languageList, viewParticipantSummary, } from './participantDetailsHelpers.js'; import fieldMapping from './fieldToConceptIdMapping.js'; import { renderParticipantHeader } from './participantHeader.js'; -import { getDataAttributes } from './utils.js'; +import { getDataAttributes, urls } from './utils.js'; import { appState } from './stateManager.js'; appState.setState({unsavedChangesTrack:{saveFlag: false, counter: 0}}); @@ -38,12 +38,12 @@ export const renderParticipantDetails = (participant, changedOption) => { hideUneditableButtons(participant, changedOption); localStorage.setItem("participant", JSON.stringify(participant)); changeParticipantDetail(participant, changedOption, originalHTML); + resetParticipantConfirm(originalHTML); editAltContact(participant); viewParticipantSummary(participant); renderReturnSearchResults(); attachUpdateLoginMethodListeners(participant[fieldMapping.accountEmail], participant[fieldMapping.accountPhone], participant.token, participant.state.uid); submitClickHandler(participant, changedOption); - resetClickHandlers(participant); } export const render = (participant, changedOption) => { @@ -64,7 +64,7 @@ export const render = (participant, changedOption) => { ${renderParticipantHeader(participant)} ${renderBackToSearchDivAndButton()} ${renderCancelChangesAndSaveChangesButtons()} - ${renderResetUserButton()} + ${renderResetUserButton(participant?.state?.uid)} ${renderDetailsTableHeader()} `; @@ -109,6 +109,30 @@ export const render = (participant, changedOption) => { return template; } +const resetParticipantConfirm = (originalHTML) => { + const openResetDialogBtn = document.getElementById('openResetDialog'); + if(openResetDialogBtn) { + let data = getDataAttributes(openResetDialogBtn); + openResetDialogBtn.addEventListener('click', () => { + const header = document.getElementById('modalHeader'); + const body = document.getElementById('modalBody'); + const uid = data.participantuid; + header.innerHTML = ` +
Confirm Participant Reset
+ ` + body.innerHTML = `
+ Are you sure you want to reset this participant to a just-verified state? This cannot be undone. +
+ +   + +
+
` + resetClickHandlers(uid); + }); + } +} + const changeParticipantDetail = (participant, changedOption, originalHTML) => { const detailedRow = Array.from(document.getElementsByClassName('detailedRow')); if (detailedRow) { @@ -284,13 +308,22 @@ const renderBackToSearchDivAndButton = () => { `; }; -const renderResetUserButton = () => { - // @TODO: Make this dev environment only - return ` -
- -
- ` +const renderResetUserButton = (participantUid) => { + if(location.hostname === 'localhost' || location.hostname === '127.0.0.1' || location.host === urls.stage) { + return ` + + + + ` + } else { + return ''; + } } const renderDetailsTableHeader = () => { diff --git a/src/participantDetailsHelpers.js b/src/participantDetailsHelpers.js index 951d7fb..93b519c 100644 --- a/src/participantDetailsHelpers.js +++ b/src/participantDetailsHelpers.js @@ -760,6 +760,37 @@ const showAuthUpdateAPIError = (bodyId, message) => { return false; } +export const refreshParticipantAfterReset = async (participant) => { + showAnimation(); + localStorage.setItem('participant', JSON.stringify(participant)); + renderParticipantDetails(participant, {}); + appState.setState({unsavedChangesTrack:{saveFlag: false, counter: 0}}) + let alertList = document.getElementById('alert_placeholder'); + let template = ''; + template += `` + hideAnimation(); + alertList.innerHTML = template; +} + +export const participantRefreshError = async (errorMsg) => { + showAnimation(); + let alertList = document.getElementById('alert_placeholder'); + let template = ''; + template += `` + hideAnimation(); + alertList.innerHTML = template; +} + export const refreshParticipantAfterUpdate = async (participant) => { showAnimation(); localStorage.setItem('participant', JSON.stringify(participant)); @@ -1206,12 +1237,26 @@ export const submitClickHandler = async (participant, changedOption) => { } }; -export const resetClickHandlers = async (participant) => { - console.log('participant', participant); - const participantUid = participant?.state?.uid; +export const resetClickHandlers = async (participantUid) => { const resetButton = document.getElementById('resetUserBtn'); - resetButton.addEventListener('click', async (e) => { - await postResetUserData(participantUid); + if(!resetButton) { + return; + } + resetButton.addEventListener('click', async () => { + try { + const json = await postResetUserData(participantUid); + closeModal(); + if(json.code === 200) { + refreshParticipantAfterReset(json.data.data); + } else if (json.code === 404) { + participantRefreshError('Unable to find participant.'); + } else { + participantRefreshError(json.data); + } + } catch(error) { + console.error('error', error); + participantRefreshError('Unknown error.'); + } }); } @@ -1513,11 +1558,9 @@ export const postResetUserData = async (uid) => { Authorization: "Bearer " + idToken, "Content-Type": "application/json" }, - body: JSON.stringify({uid}) + body: JSON.stringify({uid, saveToDb: 'true'}) }); - console.log('response', response); - if (!response.ok) { const error = (response.status + ": " + (await response.json()).message); throw new Error(error); From 0a4f40eba27213885ac3d14dc0152621fe44e71c Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Mon, 9 Sep 2024 09:14:49 -0400 Subject: [PATCH 05/13] Removed erroneously included lines for local API usage + console log --- index.html | 2 -- index.js | 2 -- src/participantLookup.js | 1 - src/utils.js | 1 - 4 files changed, 6 deletions(-) diff --git a/index.html b/index.html index 95385fb..76289d5 100644 --- a/index.html +++ b/index.html @@ -98,8 +98,6 @@ - - diff --git a/index.js b/index.js index dca7f32..8f4d732 100644 --- a/index.js +++ b/index.js @@ -71,8 +71,6 @@ window.onload = async () => { else { !firebase.apps.length ? firebase.initializeApp(devFirebaseConfig) : firebase.app(); !isLocalDev && window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'dev' }); - // TODO: Remove this line - if (location.host.startsWith('localhost')) firebase.functions().useFunctionsEmulator('http://localhost:5001'); } !isLocalDev && window.DD_RUM && window.DD_RUM.startSessionReplayRecording(); diff --git a/src/participantLookup.js b/src/participantLookup.js index 10d40da..e590cd0 100644 --- a/src/participantLookup.js +++ b/src/participantLookup.js @@ -204,7 +204,6 @@ export const performSearch = async (query, sitePref, failedElem) => { if(response.code === 200 && response.data.length > 0) { const mainContent = document.getElementById('mainContent') let filterRawData = filterdata(response.data); - console.log('filterRawData', filterRawData); if (sitePref !== undefined && sitePref != null && sitePref !== 'allResults') { const sitePrefId = nameToKeyObj[sitePref]; diff --git a/src/utils.js b/src/utils.js index bc5064f..16f22d5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -115,7 +115,6 @@ export const urls = { let api = ``; if(location.host === urls.prod) api = 'https://api-myconnect.cancer.gov'; else if(location.host === urls.stage) api = 'https://api-myconnect-stage.cancer.gov'; -else if(location.host.startsWith('localhost')) api = 'http://localhost:5001/nih-nci-dceg-connect-dev/us-central1'; else api = 'https://us-central1-nih-nci-dceg-connect-dev.cloudfunctions.net'; export const baseAPI = api; From 842a024d65695fe0fe0b1fdb0450e5530feed1fc Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Mon, 9 Sep 2024 11:23:53 -0400 Subject: [PATCH 06/13] Addressed PR comments --- src/participantDetails.js | 6 +++--- src/participantDetailsHelpers.js | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/participantDetails.js b/src/participantDetails.js index 341c5a1..95a7025 100644 --- a/src/participantDetails.js +++ b/src/participantDetails.js @@ -38,7 +38,7 @@ export const renderParticipantDetails = (participant, changedOption) => { hideUneditableButtons(participant, changedOption); localStorage.setItem("participant", JSON.stringify(participant)); changeParticipantDetail(participant, changedOption, originalHTML); - resetParticipantConfirm(originalHTML); + resetParticipantConfirm(); editAltContact(participant); viewParticipantSummary(participant); renderReturnSearchResults(); @@ -109,7 +109,7 @@ export const render = (participant, changedOption) => { return template; } -const resetParticipantConfirm = (originalHTML) => { +const resetParticipantConfirm = () => { const openResetDialogBtn = document.getElementById('openResetDialog'); if(openResetDialogBtn) { let data = getDataAttributes(openResetDialogBtn); @@ -309,7 +309,7 @@ const renderBackToSearchDivAndButton = () => { }; const renderResetUserButton = (participantUid) => { - if(location.hostname === 'localhost' || location.hostname === '127.0.0.1' || location.host === urls.stage) { + if(location.hostname === 'localhost' || location.hostname === '127.0.0.1' || location.host === urls.dev) { return ` { - -} /** * Update the user's history based on new data entered by the user. This only triggers if the user's profile is verified. From a447bd19ac30034bd46fbb0af936dbfff3fc899a Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Mon, 9 Sep 2024 15:59:39 -0400 Subject: [PATCH 07/13] Updated per feedback --- index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 4285435..14fb0e0 100644 --- a/index.js +++ b/index.js @@ -14,11 +14,10 @@ import fieldMapping from './src/fieldToConceptIdMapping.js'; import { nameToKeyObj } from './src/idsToName.js'; import { renderAllCharts } from './src/participantChartsRender.js'; import { firebaseConfig as devFirebaseConfig } from "./config/dev/config.js"; -// When doing local development, uncomment this and comment out the line below it. +// When doing local development, uncomment this // Get the API key file from Box or the DevOps team // Do not accept PRs with the localDevFirebaseConfig import uncommented // import { firebaseConfig as localDevFirebaseConfig } from "./config/local-dev/config.js"; -const localDevFirebaseConfig = devFirebaseConfig; import { firebaseConfig as stageFirebaseConfig } from "./config/stage/config.js"; import { firebaseConfig as prodFirebaseConfig } from "./config/prod/config.js"; import { SSOConfig as devSSOConfig} from './config/dev/identityProvider.js'; @@ -74,6 +73,10 @@ window.onload = async () => { window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'stage' }); } else if (isLocalDev) { + if (typeof localDevFirebaseConfig === 'undefined') { + console.error('Local development requires a localDevFirebaseConfig function to be defined in src/local-dev/config.js.'); + return; + } !firebase.apps.length ? firebase.initializeApp(localDevFirebaseConfig) : firebase.app(); } else { From 058f068582c1a8339f4f7b7d754a06775e5238b2 Mon Sep 17 00:00:00 2001 From: Warren Lu Date: Thu, 19 Sep 2024 20:45:33 -0400 Subject: [PATCH 08/13] implementation and cleanups --- .gitignore | 73 +----------------- index.js | 20 +++-- src/storeNotifications.js | 155 +++++++++++++++++++++++--------------- 3 files changed, 111 insertions(+), 137 deletions(-) diff --git a/.gitignore b/.gitignore index 08b8e1e..f83ede4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,75 +1,8 @@ -# remove hidden mac files .DS_Store - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file +.hintrc .env - -# next.js build output -.next - .vscode - -# ignore scratch & scratch.js -scratch -scratch.js .eslintrc.json -.hintrc - -# private local dev Firebase key +node_modules/ +temp config/local-dev/ \ No newline at end of file diff --git a/index.js b/index.js index 14fb0e0..56d2eb1 100644 --- a/index.js +++ b/index.js @@ -14,10 +14,6 @@ import fieldMapping from './src/fieldToConceptIdMapping.js'; import { nameToKeyObj } from './src/idsToName.js'; import { renderAllCharts } from './src/participantChartsRender.js'; import { firebaseConfig as devFirebaseConfig } from "./config/dev/config.js"; -// When doing local development, uncomment this -// Get the API key file from Box or the DevOps team -// Do not accept PRs with the localDevFirebaseConfig import uncommented -// import { firebaseConfig as localDevFirebaseConfig } from "./config/local-dev/config.js"; import { firebaseConfig as stageFirebaseConfig } from "./config/stage/config.js"; import { firebaseConfig as prodFirebaseConfig } from "./config/prod/config.js"; import { SSOConfig as devSSOConfig} from './config/dev/identityProvider.js'; @@ -73,9 +69,19 @@ window.onload = async () => { window.DD_RUM && window.DD_RUM.init({ ...datadogConfig, env: 'stage' }); } else if (isLocalDev) { - if (typeof localDevFirebaseConfig === 'undefined') { - console.error('Local development requires a localDevFirebaseConfig function to be defined in src/local-dev/config.js.'); - return; + let localDevFirebaseConfig = null; + let hasError = false; + try { + const localDevConfig = await import("./config/local-dev/config.js"); + localDevFirebaseConfig = localDevConfig.firebaseConfig; + if (!localDevFirebaseConfig) hasError = true; + } catch (error) { + hasError = true; + } + + if (hasError) { + console.error("Local development requires firebaseConfig defined in src/local-dev/config.js."); + return; } !firebase.apps.length ? firebase.initializeApp(localDevFirebaseConfig) : firebase.app(); } diff --git a/src/storeNotifications.js b/src/storeNotifications.js index fd68513..7d74e3e 100644 --- a/src/storeNotifications.js +++ b/src/storeNotifications.js @@ -91,19 +91,18 @@ export const getSchemaHtmlStr = (schemaData = null, isReadOnly = false) => { let index = 0; if (schemaData?.conditions) { - for (const [conditionKey, valObj] of Object.entries(schemaData.conditions)) { - const [conditionOperator, conditionValue] = Object.entries(valObj)[0]; - conditionHtmlStrAll += getConditionHtmlStr({ - index, - isReadOnly, - conditionKey, - conditionOperator, - conditionValue, - }); - index++; + const conditions = JSON.parse(schemaData.conditions); + for (const condition of conditions) { + if (Array.isArray(condition) && condition.length === 3) { + conditionHtmlStrAll += getConditionHtmlStr(index, isReadOnly, condition); + index++; + } else if (typeof condition === "string") { + conditionHtmlStrAll += getSQLConditionHtmlStr(index, isReadOnly, condition); + index++; + } } } else { - conditionHtmlStrAll = getConditionHtmlStr({ index, isReadOnly }); + conditionHtmlStrAll = getConditionHtmlStr(index, isReadOnly); } return ` @@ -123,7 +122,7 @@ export const getSchemaHtmlStr = (schemaData = null, isReadOnly = false) => {
- +     @@ -149,15 +148,16 @@ export const getSchemaHtmlStr = (schemaData = null, isReadOnly = false) => {
${schemaData?.email ? getEmailDivHtml(schemaData.email, isReadOnly, true) : "" }
${schemaData?.sms ? getSmsDivHtml(schemaData.sms, isReadOnly, true) : "" }
${schemaData?.push?.subject ? getPushDivHtml(schemaData) : "" }
-
+
${conditionHtmlStrAll}
- + +
- +
- +
@@ -177,7 +177,7 @@ export const getSchemaHtmlStr = (schemaData = null, isReadOnly = false) => {
- +
@@ -187,7 +187,7 @@ export const getSchemaHtmlStr = (schemaData = null, isReadOnly = false) => {
- +
@@ -197,7 +197,7 @@ export const getSchemaHtmlStr = (schemaData = null, isReadOnly = false) => {
- +
@@ -207,10 +207,16 @@ export const getSchemaHtmlStr = (schemaData = null, isReadOnly = false) => {
- - - - + + + + +
+
+ + + +
`; }; @@ -245,9 +251,16 @@ const handleFormSubmit = () => { schema["primaryField"] = document.getElementById("primaryFieldConceptId").value; schema["time"] = { - day: parseInt(document.getElementById("days").value), - hour: parseInt(document.getElementById("hours").value), - minute: parseInt(document.getElementById("minutes").value), + start: { + day: parseInt(document.getElementById("startDays").value), + hour: parseInt(document.getElementById("startHours").value), + minute: parseInt(document.getElementById("startMinutes").value), + }, + stop: { + day: parseInt(document.getElementById("stopDays").value), + hour: parseInt(document.getElementById("stopHours").value), + minute: parseInt(document.getElementById("stopMinutes").value), + }, }; const emailInputDivList = document.querySelectorAll("#emailDiv div[data-email-lang]"); @@ -285,18 +298,25 @@ const handleFormSubmit = () => { schema["push"] = { subject: pushSubjectEle.value, body: document.getElementById("pushBody").value }; } - const opeartorEleArray = Array.from(document.getElementsByName("condition-operator")); - const valueTypeEleArray = Array.from(document.getElementsByName("value-type")); - const valueEleArray = Array.from(document.getElementsByName("condition-value")); - schema["conditions"] = {}; - Array.from(document.getElementsByName("condition-key")).forEach((element, i) => { - schema["conditions"][element.value] = {}; - if (valueTypeEleArray[i].value === "string") { - schema["conditions"][element.value][opeartorEleArray[i].value] = valueEleArray[i].value; - } else if (valueTypeEleArray[i].value === "number") { - schema["conditions"][element.value][opeartorEleArray[i].value] = parseInt(valueEleArray[i].value); + let conditionArray = []; + const conditionRowArray = Array.from(document.querySelectorAll("#conditionsDiv div[data-condition-idx]")); + for (const conditionRow of conditionRowArray) { + if (conditionRow.dataset.conditionType === "sql") { + conditionArray.push([conditionRow.querySelector("textarea").value.trim()]); + continue; } - }); + + const conditionKey = conditionRow.querySelector("input[name=condition-key]").value.trim().split(/\s+/)[0]; + const conditionOperator = conditionRow.querySelector("select[name=condition-operator]").value.trim().split(/\s+/)[0]; + const conditionValueType = conditionRow.querySelector("select[name=value-type]").value.trim().split(/\s+/)[0]; + const conditionValue = conditionRow.querySelector("input[name=condition-value]").value.trim().split(/\s+/)[0]; + if (conditionValueType === "string") { + conditionArray.push([conditionKey, conditionOperator, conditionValue]); + } else if (conditionValueType === "number") { + conditionArray.push([conditionKey, conditionOperator, parseInt(conditionValue)]); + } + } + schema["conditions"] = JSON.stringify(conditionArray); const currSchemaId = await storeNotificationSchema(schema); if (!hasSchemaId && currSchemaId.length > 0) { @@ -311,39 +331,42 @@ const handleFormSubmit = () => { const handleAddCondition = () => { const conditionDiv = document.getElementById("conditionsDiv"); - const btn = document.getElementById("addConditions"); - - btn && btn.addEventListener("click", () => { - const index = parseInt(btn.dataset.condition); - const wrapperDiv = document.createElement("div"); - wrapperDiv.innerHTML = getConditionHtmlStr({ index }); - conditionDiv.appendChild(wrapperDiv.firstElementChild); - conditionDiv.querySelector(`button[data-btn-idx="${index}"]`).addEventListener("click", deleteConditionListener); - btn.dataset.condition = index + 1; - }); + const idAndCallbacks = [ + ["addOneCondition", getConditionHtmlStr], + ["addSqlCondition", getSQLConditionHtmlStr], + ]; + + for (const [btnId, getHtmlStr] of idAndCallbacks) { + const btn = document.getElementById(btnId); + if (!btn || btn.hasClickListener) continue; + + btn.addEventListener("click", () => { + const index = parseInt(conditionDiv.dataset.currentIndex) + 1; + const wrapperDiv = document.createElement("div"); + wrapperDiv.innerHTML = getHtmlStr(index); + conditionDiv.appendChild(wrapperDiv.firstElementChild); + conditionDiv.querySelector(`button[data-btn-idx="${index}"]`).addEventListener("click", handleDeleteCondition); + conditionDiv.dataset.currentIndex = index; + btn.hasClickListener = true; + }); + } }; const handleDeleteExistingConditions = () => { - const conditionDiv = document.getElementById("conditionsDiv"); - const btns = conditionDiv.querySelectorAll("button[data-btn-idx]"); + const btns = document.querySelectorAll("#conditionsDiv button[data-btn-idx]"); btns.forEach((btn) => { if (!btn.hasClickListener) { - btn.addEventListener("click", deleteConditionListener); + btn.addEventListener("click", handleDeleteCondition); btn.hasClickListener = true; } }); }; -const getConditionHtmlStr = ({ - index = 0, - isReadOnly = false, - conditionKey = null, - conditionOperator = null, - conditionValue = null, -}) => { +const getConditionHtmlStr = (index = 0, isReadOnly = false, condition = []) => { const readonlyCheck = isReadOnly ? "disabled" : ""; + const [conditionKey = null, conditionOperator = null, conditionValue = null] = condition; return ` -
+
@@ -373,7 +396,19 @@ const getConditionHtmlStr = ({
`; }; -const deleteConditionListener = (event) => { +const getSQLConditionHtmlStr = (index = 0, isReadOnly = false, conditionStr = "") => { + const readonlyCheck = isReadOnly ? "disabled" : ""; + return ` +
+ +
+ +
+ +
`; +}; + +const handleDeleteCondition = (event) => { event.target.parentElement.remove(); }; @@ -670,7 +705,7 @@ const handleDryRun = () => { if (resJson.code === 200) { dryRunSummary += "Everything looks good!"; } else { - dryRunSummary += `Error occurred in dry run:\n${resJson.message}`; + dryRunSummary = `Error occurred during dry run:\n${resJson.message}`; } resultEle.textContent = dryRunSummary; From 2b0bb9ba24106511114b76aff3c95f8bed7c7264 Mon Sep 17 00:00:00 2001 From: Warren Lu Date: Fri, 20 Sep 2024 13:39:43 -0400 Subject: [PATCH 09/13] Minor cleanups --- src/storeNotifications.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/storeNotifications.js b/src/storeNotifications.js index 7d74e3e..0ce733f 100644 --- a/src/storeNotifications.js +++ b/src/storeNotifications.js @@ -208,15 +208,15 @@ export const getSchemaHtmlStr = (schemaData = null, isReadOnly = false) => {
- - - + + +
- - - + + +
`; }; @@ -368,7 +368,7 @@ const getConditionHtmlStr = (index = 0, isReadOnly = false, condition = []) => { return `
-
+
${conceptsOptionsStr} @@ -401,7 +401,7 @@ const getSQLConditionHtmlStr = (index = 0, isReadOnly = false, conditionStr = "" return `
-
+
From b979a47b1fb462974e81948910395e165b16452b Mon Sep 17 00:00:00 2001 From: Warren Lu Date: Fri, 20 Sep 2024 17:54:30 -0400 Subject: [PATCH 10/13] handle empty string --- src/storeNotifications.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/storeNotifications.js b/src/storeNotifications.js index 0ce733f..7cd6fcb 100644 --- a/src/storeNotifications.js +++ b/src/storeNotifications.js @@ -240,7 +240,7 @@ const handleFormSubmit = () => { } schema["scheduleAt"] = Array.from(document.getElementsByName("scheduleAt")).filter((dt) => dt.checked)[0].value; - schema["sendType"]= "scheduled"; + schema["sendType"] = "scheduled"; schema["emailField"] = document.getElementById("emailConceptId").value; schema["phoneField"] = document.getElementById("phoneConceptId").value; schema["firstNameField"] = document.getElementById("firstNameConceptId").value; @@ -302,7 +302,8 @@ const handleFormSubmit = () => { const conditionRowArray = Array.from(document.querySelectorAll("#conditionsDiv div[data-condition-idx]")); for (const conditionRow of conditionRowArray) { if (conditionRow.dataset.conditionType === "sql") { - conditionArray.push([conditionRow.querySelector("textarea").value.trim()]); + const sqlCondition = conditionRow.querySelector("textarea").value.trim(); + if (sqlCondition.length > 0) conditionArray.push(sqlCondition); continue; } From 399081d3a0c4adc60d82401884ffa02f87f70884 Mon Sep 17 00:00:00 2001 From: amber-emmes Date: Mon, 23 Sep 2024 10:14:57 -0400 Subject: [PATCH 11/13] Added dev URL to list of URLs --- src/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index 16f22d5..84bc1d4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -109,7 +109,8 @@ export const hideAnimation = () => { export const urls = { 'stage': 'dashboard-myconnect-stage.cancer.gov', - 'prod': 'dashboard-myconnect.cancer.gov' + 'prod': 'dashboard-myconnect.cancer.gov', + 'dev': 'episphere.github.io' } let api = ``; From ca5d00b2bf41166639a0d5f0f69cd95325905c11 Mon Sep 17 00:00:00 2001 From: Brian Bransteitter Date: Thu, 26 Sep 2024 14:05:34 -0400 Subject: [PATCH 12/13] Implemented 2024 Experience Survey and Withdrawl --- src/fieldToConceptIdMapping.js | 8 ++++++++ src/participantCommons.js | 5 +++-- src/participantSummary.js | 6 +++++- src/participantSummaryRow.js | 18 ++++++++++++++++++ src/participantWithdrawal.js | 4 ++++ src/participantWithdrawalForm.js | 18 ++++++++++++++++++ 6 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/fieldToConceptIdMapping.js b/src/fieldToConceptIdMapping.js index 3a509f9..d5b437c 100644 --- a/src/fieldToConceptIdMapping.js +++ b/src/fieldToConceptIdMapping.js @@ -188,6 +188,10 @@ export default 'combinedBoodUrineMouthwashSurveyStartDate': 822499427, 'combinedBoodUrineMouthwashSurveyCompleteDate': 222161762, + 'experienceSurvey': 956490759, + 'experienceSurveyStartDate': 263355177, + 'experienceCompleteDate': 199471989, + "baselineEMR": 209454331, "baselineEMRflag": 101170406, "baselineEMRpushDate": 143523420, @@ -329,6 +333,8 @@ export default 'refusedFutureSamples': 352996056, 'refusedQualityOfLifeSurvey': 936015433, 'refusedAllFutureQualityOfLifeSurveys': 688142378, + 'refusedExperienceSurvey': 101763809, + 'refusedAllFutureExperienceSurveys': 525277409, 'refusedAllFutureActivities': 906417725, 'revokeHIPAA': 773707518, @@ -487,6 +493,8 @@ export default 'refAllFutureActivitesTimeStamp': 614264509, 'refQualityOfLifeSurveyTimeStamp': 667960135, 'refAllFutureQualityOfLifeSurveysTimeStamp': 953225775, + 'refExperienceSurveyTimeStamp': 688172931, + 'refAllFutureExperienceSurveysTimeStamp': 182718292, // remaining withdrawal 'contactSuspended': 726389747, diff --git a/src/participantCommons.js b/src/participantCommons.js index ad6e320..e2dac2b 100644 --- a/src/participantCommons.js +++ b/src/participantCommons.js @@ -25,7 +25,7 @@ export const renderTable = (data, source) => { fieldMapping.email2, fieldMapping.cellPhone, fieldMapping.homePhone, fieldMapping.otherPhone, fieldMapping.previousCancer, fieldMapping.allBaselineSurveysCompleted, fieldMapping.preferredLanguage, fieldMapping.participationStatus, fieldMapping.bohStatusFlag1, fieldMapping.mreStatusFlag1, fieldMapping.sasStatusFlag1, fieldMapping.lawStausFlag1, fieldMapping.ssnFullflag, fieldMapping.ssnPartialFlag , fieldMapping.refusedSurvey, fieldMapping.refusedBlood, fieldMapping.refusedUrine, fieldMapping.refusedMouthwash, fieldMapping.refusedSpecimenSurveys, fieldMapping.refusedFutureSamples, fieldMapping.refusedQualityOfLifeSurvey, fieldMapping.refusedAllFutureQualityOfLifeSurveys, - fieldMapping.refusedFutureSurveys, fieldMapping.refusedAllFutureActivities, fieldMapping.revokeHIPAA, fieldMapping.dateHipaaRevokeRequested, fieldMapping.dateHIPAARevoc, fieldMapping.withdrawConsent, fieldMapping.dateWithdrewConsentRequested, + fieldMapping.refusedExperienceSurvey, fieldMapping.refusedAllFutureExperienceSurveys, fieldMapping.refusedFutureSurveys, fieldMapping.refusedAllFutureActivities, fieldMapping.revokeHIPAA, fieldMapping.dateHipaaRevokeRequested, fieldMapping.dateHIPAARevoc, fieldMapping.withdrawConsent, fieldMapping.dateWithdrewConsentRequested, fieldMapping.participantDeceased, fieldMapping.dateOfDeath, fieldMapping.destroyData, fieldMapping.dateDataDestroyRequested, fieldMapping.dateDataDestroy, fieldMapping.suspendContact ]; localStorage.removeItem("participant"); @@ -579,7 +579,8 @@ const tableTemplate = (data, showButtons) => { ) : (x === (fieldMapping.refusedSurvey).toString() || x === (fieldMapping.refusedBlood).toString() || x === (fieldMapping.refusedUrine).toString() || x === (fieldMapping.refusedMouthwash).toString() || x === (fieldMapping.refusedSpecimenSurveys).toString() || x === (fieldMapping.refusedFutureSamples).toString() || - x === (fieldMapping.refusedFutureSurveys).toString() || x === (fieldMapping.refusedQualityOfLifeSurvey).toString() || x === (fieldMapping.refusedAllFutureQualityOfLifeSurveys).toString()) ? + x === (fieldMapping.refusedFutureSurveys).toString() || x === (fieldMapping.refusedQualityOfLifeSurvey).toString() || x === (fieldMapping.refusedAllFutureQualityOfLifeSurveys).toString() || + x === (fieldMapping.refusedExperienceSurvey).toString() || x === (fieldMapping.refusedAllFutureExperienceSurveys).toString()) ? ( (participant[fieldMapping.refusalOptions]?.[x] === fieldMapping.yes ? ( template += `
` ) diff --git a/src/participantSummary.js b/src/participantSummary.js index 67410b2..643c6de 100644 --- a/src/participantSummary.js +++ b/src/participantSummary.js @@ -3,7 +3,8 @@ import { renderParticipantHeader } from './participantHeader.js'; import fieldMapping from './fieldToConceptIdMapping.js'; import { userProfile, verificationStatus, baselineBOHSurvey, baselineMRESurvey,baselineSASSurvey, baselineLAWSurvey, baselineSSN, baselineCOVIDSurvey, baselineBloodSample, baselineUrineSample, baselineBiospecSurvey, baselineMenstrualSurvey, - baselineMouthwashSample, baselineBloodUrineSurvey, baselineMouthwashSurvey, baselinePromisSurvey, baselineEMR, baselinePayment } from './participantSummaryRow.js'; + baselineMouthwashSample, baselineBloodUrineSurvey, baselineMouthwashSurvey, baselinePromisSurvey, baselineEMR, baselinePayment, + baselineExperienceSurvey} from './participantSummaryRow.js'; import { humanReadableMDY, conceptToSiteMapping, pdfCoordinatesMap } from './utils.js'; const { PDFDocument, StandardFonts } = PDFLib @@ -98,6 +99,9 @@ export const render = (participant) => { ${baselinePromisSurvey(participant)} + + ${baselineExperienceSurvey(participant)} + ${baselineBloodSample(participant)} diff --git a/src/participantSummaryRow.js b/src/participantSummaryRow.js index 70cbaa4..fd232fd 100644 --- a/src/participantSummaryRow.js +++ b/src/participantSummaryRow.js @@ -362,6 +362,24 @@ export const baselinePromisSurvey = (participant) => { return getTemplateRow(icon, color, timeline, category, item, itemStatus, date, setting, refused, extra); }; +export const baselineExperienceSurvey = (participant) => { + + const refusedAllFutureSurveys = participant[fieldMapping.refusalOptions]?.[fieldMapping.refusedFutureSurveys]; + const refusedAllFutureActivities = participant[fieldMapping.refusedAllFutureActivities]; + const refusedExperienceSurvey = participant[fieldMapping.refusalOptions]?.[fieldMapping.refusedExperienceSurvey]; + + const { icon, color, itemStatus, date } = getSurveyStatus(participant, fieldMapping.experienceSurvey, fieldMapping.experienceSurveyStartDate, fieldMapping.experienceCompleteDate); + + const timeline = "Follow-Up 3-mo"; + const category = "Survey"; + const item = "2024 Connect Experience"; + const setting = "N/A"; + const refused = refusedAllFutureSurveys === fieldMapping.yes || refusedAllFutureActivities === fieldMapping.yes || refusedExperienceSurvey === fieldMapping.yes ? "Y" : "N"; + const extra = "N/A"; + + return getTemplateRow(icon, color, timeline, category, item, itemStatus, date, setting, refused, extra); +}; + export const baselineMenstrualSurvey = (participant) => { let template = ``; diff --git a/src/participantWithdrawal.js b/src/participantWithdrawal.js index 1aabf9a..db6bbe1 100644 --- a/src/participantWithdrawal.js +++ b/src/participantWithdrawal.js @@ -85,6 +85,10 @@ const getParticipantSelectedRefusals = (participant) => { strArray.push("Refused QOL survey 3-mo (but willing to do other future surveys)"); if (refusalOptions[fieldMapping.refusedAllFutureQualityOfLifeSurveys] === fieldMapping.yes) strArray.push("Refused all future QOL surveys (but willing to do other future surveys)"); + if (refusalOptions[fieldMapping.refusedExperienceSurvey] === fieldMapping.yes) + strArray.push("Refused 2024 Connect Experience Survey (but willing to do other future surveys)"); + if (refusalOptions[fieldMapping.refusedAllFutureExperienceSurveys] === fieldMapping.yes) + strArray.push("Refused all future Connect Experience surveys (but willing to do other future surveys)"); if (refusalOptions[fieldMapping.refusedFutureSurveys] === fieldMapping.yes) strArray.push("All future surveys (willing to do specimens)"); } diff --git a/src/participantWithdrawalForm.js b/src/participantWithdrawalForm.js index 15c3b67..cea796b 100644 --- a/src/participantWithdrawalForm.js +++ b/src/participantWithdrawalForm.js @@ -76,6 +76,18 @@ export const renderParticipantWithdrawalLandingPage = () => { All future QOL Surveys (but willing to do other future surveys)
+ + +
+ + +
${participant[fieldMapping.refusalOptions]?.[x] ? 'Yes' : ''}