From 8b0ea6489e83969b9a82eab3cb33f26785cc068d Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 30 Aug 2022 15:40:48 -0400 Subject: [PATCH 001/128] Update environment variables to support formio payment request form --- app/server/.env.example | 3 ++- app/server/app/config/formio.js | 6 ++++-- app/server/app/index.js | 9 ++++----- app/server/app/routes/api.js | 14 +++++++------- app/server/app/routes/help.js | 6 +++--- app/server/app/routes/status.js | 4 ++-- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/server/.env.example b/app/server/.env.example index c7532d18..3676def1 100644 --- a/app/server/.env.example +++ b/app/server/.env.example @@ -13,7 +13,8 @@ JWT_PUBLIC_KEY=secret CSB_ENROLLMENT_PERIOD=closed FORMIO_BASE_URL= FORMIO_PROJECT_NAME= -FORMIO_FORM_NAME= +FORMIO_REBATE_FORM_PATH= +FORMIO_PAYMENT_REQUEST_FORM_PATH= FORMIO_API_KEY= BAP_CLIENT_ID= BAP_CLIENT_SECRET= diff --git a/app/server/app/config/formio.js b/app/server/app/config/formio.js index 28e024af..99b8e496 100644 --- a/app/server/app/config/formio.js +++ b/app/server/app/config/formio.js @@ -5,7 +5,8 @@ const log = require("../utilities/logger"); const formioBaseUrl = process.env.FORMIO_BASE_URL; const formioProjectName = process.env.FORMIO_PROJECT_NAME; const formioProjectUrl = `${formioBaseUrl}/${formioProjectName}`; -const formioFormName = process.env.FORMIO_FORM_NAME; +const formioRebateFormPath = process.env.FORMIO_REBATE_FORM_PATH; +const formioPaymentRequestFormPath = process.env.FORMIO_PAYMENT_REQUEST_FORM_PATH; /* prettier-ignore */ const formioApiKey = process.env.FORMIO_API_KEY; function axiosFormio(req) { @@ -75,6 +76,7 @@ const formioCsbMetadata = { module.exports = { axiosFormio, formioProjectUrl, - formioFormName, + formioRebateFormPath, + formioPaymentRequestFormPath, formioCsbMetadata, }; diff --git a/app/server/app/index.js b/app/server/app/index.js index 220d6092..807723fa 100644 --- a/app/server/app/index.js +++ b/app/server/app/index.js @@ -30,7 +30,8 @@ const requiredEnvVars = [ "CSB_ENROLLMENT_PERIOD", "FORMIO_BASE_URL", "FORMIO_PROJECT_NAME", - "FORMIO_FORM_NAME", + "FORMIO_REBATE_FORM_PATH", + "FORMIO_PAYMENT_REQUEST_FORM_PATH", "FORMIO_API_KEY", "S3_PUBLIC_BUCKET", "S3_PUBLIC_REGION", @@ -38,10 +39,8 @@ const requiredEnvVars = [ requiredEnvVars.forEach((envVar) => { if (!process.env[envVar]) { - log({ - level: "error", - message: `Required environment variable ${envVar} not found.`, - }); + const message = `Required environment variable ${envVar} not found.`; + log({ level: "error", message }); process.exitCode = 1; } }); diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index e6d7f73f..b001dd88 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -6,7 +6,7 @@ const axios = require("axios").default; const { axiosFormio, formioProjectUrl, - formioFormName, + formioRebateFormPath, formioCsbMetadata, } = require("../config/formio"); const { @@ -171,7 +171,7 @@ router.get( async (req, res) => { const { id } = req.params; - const existingSubmissionUrl = `${formioProjectUrl}/${formioFormName}/submission/${id}`; + const existingSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission/${id}`; axiosFormio(req) .get(existingSubmissionUrl) @@ -236,7 +236,7 @@ router.post( ...formioCsbMetadata, }; - const existingSubmissionUrl = `${formioProjectUrl}/${formioFormName}/submission/${id}`; + const existingSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission/${id}`; axiosFormio(req) .put(existingSubmissionUrl, req.body) @@ -276,7 +276,7 @@ router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { ...formioCsbMetadata, }; - const newSubmissionUrl = `${formioProjectUrl}/${formioFormName}/submission`; + const newSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission`; axiosFormio(req) .post(newSubmissionUrl, req.body) @@ -300,7 +300,7 @@ router.post("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { return res.status(401).json({ message: "Unauthorized" }); } - const storageUrl = `${formioProjectUrl}/${formioFormName}/storage/s3`; + const storageUrl = `${formioProjectUrl}/${formioRebateFormPath}/storage/s3`; axiosFormio(req) .post(storageUrl, req.body) @@ -327,7 +327,7 @@ router.get("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { return res.status(401).json({ message: "Unauthorized" }); } - const storageUrl = `${formioProjectUrl}/${formioFormName}/storage/s3`; + const storageUrl = `${formioProjectUrl}/${formioRebateFormPath}/storage/s3`; axiosFormio(req) .get(storageUrl, { params: req.query }) @@ -351,7 +351,7 @@ router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { if (req.bapComboKeys.length === 0) return res.json([]); const userSubmissionsUrl = - `${formioProjectUrl}/${formioFormName}/submission` + + `${formioProjectUrl}/${formioRebateFormPath}/submission` + `?sort=-modified` + `&limit=1000000` + `&data.bap_hidden_entity_combo_key=${req.bapComboKeys.join( diff --git a/app/server/app/routes/help.js b/app/server/app/routes/help.js index 309ecbff..b67c59fe 100644 --- a/app/server/app/routes/help.js +++ b/app/server/app/routes/help.js @@ -3,7 +3,7 @@ const express = require("express"); const { axiosFormio, formioProjectUrl, - formioFormName, + formioRebateFormPath, formioCsbMetadata, } = require("../config/formio"); const { @@ -26,7 +26,7 @@ router.get("/rebate-form-submission/:id", verifyMongoObjectId, (req, res) => { const { id } = req.params; axiosFormio(req) - .get(`${formioProjectUrl}/${formioFormName}/submission/${id}`) + .get(`${formioProjectUrl}/${formioRebateFormPath}/submission/${id}`) .then((axiosRes) => axiosRes.data) .then((submission) => { axiosFormio(req) @@ -58,7 +58,7 @@ router.post("/rebate-form-submission/:id", verifyMongoObjectId, (req, res) => { return res.status(400).json({ message }); } - const existingSubmissionUrl = `${formioProjectUrl}/${formioFormName}/submission/${id}`; + const existingSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission/${id}`; axiosFormio(req) .get(existingSubmissionUrl) diff --git a/app/server/app/routes/status.js b/app/server/app/routes/status.js index 451aeb2b..d12c4f77 100644 --- a/app/server/app/routes/status.js +++ b/app/server/app/routes/status.js @@ -3,7 +3,7 @@ const express = require("express"); const { axiosFormio, formioProjectUrl, - formioFormName, + formioRebateFormPath, } = require("../config/formio"); const { getSamData } = require("../utilities/bap"); @@ -25,7 +25,7 @@ router.get("/bap", (req, res) => { router.get("/form", (req, res) => { axiosFormio(req) - .get(`${formioProjectUrl}/${formioFormName}`) + .get(`${formioProjectUrl}/${formioRebateFormPath}`) .then((axiosRes) => axiosRes.data) .then((schema) => // Verify that schema has type of form and a title exists (confirming formio returned a valid schema) From d161cc346c949eae9f92b62c8758f544c66e2e53 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 30 Aug 2022 15:41:45 -0400 Subject: [PATCH 002/128] Update GitHub Actions workflows to include updated formio form path env variables --- .github/workflows/dev.yml | 6 ++++-- .github/workflows/proto.yml | 6 ++++-- .github/workflows/staging.yml | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 3db511ff..f3fa9843 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -45,7 +45,8 @@ jobs: FORMIO_PKG_AUTH_TOKEN: ${{ secrets.FORMIO_PKG_AUTH_TOKEN }} FORMIO_BASE_URL: ${{ secrets.FORMIO_BASE_URL }} FORMIO_PROJECT_NAME: ${{ secrets.FORMIO_PROJECT_NAME }} - FORMIO_FORM_NAME: ${{ secrets.FORMIO_FORM_NAME }} + FORMIO_REBATE_FORM_PATH: ${{ secrets.FORMIO_REBATE_FORM_PATH }} + FORMIO_PAYMENT_REQUEST_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_REQUEST_FORM_PATH }} FORMIO_API_KEY: ${{ secrets.FORMIO_API_KEY }} BAP_CLIENT_ID: ${{ secrets.BAP_CLIENT_ID }} BAP_CLIENT_SECRET: ${{ secrets.BAP_CLIENT_SECRET }} @@ -122,7 +123,8 @@ jobs: cf set-env $APP_NAME "CSB_ENROLLMENT_PERIOD" "$CSB_ENROLLMENT_PERIOD" > /dev/null cf set-env $APP_NAME "FORMIO_BASE_URL" "$FORMIO_BASE_URL" > /dev/null cf set-env $APP_NAME "FORMIO_PROJECT_NAME" "$FORMIO_PROJECT_NAME" > /dev/null - cf set-env $APP_NAME "FORMIO_FORM_NAME" "$FORMIO_FORM_NAME" > /dev/null + cf set-env $APP_NAME "FORMIO_REBATE_FORM_PATH" "$FORMIO_REBATE_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_PAYMENT_REQUEST_FORM_PATH" "$FORMIO_PAYMENT_REQUEST_FORM_PATH" > /dev/null cf set-env $APP_NAME "FORMIO_API_KEY" "$FORMIO_API_KEY" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_ID" "$BAP_CLIENT_ID" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_SECRET" "$BAP_CLIENT_SECRET" > /dev/null diff --git a/.github/workflows/proto.yml b/.github/workflows/proto.yml index 152272af..3c14a4f4 100644 --- a/.github/workflows/proto.yml +++ b/.github/workflows/proto.yml @@ -44,7 +44,8 @@ jobs: FORMIO_PKG_AUTH_TOKEN: ${{ secrets.FORMIO_PKG_AUTH_TOKEN }} FORMIO_BASE_URL: ${{ secrets.FORMIO_BASE_URL }} FORMIO_PROJECT_NAME: ${{ secrets.FORMIO_PROJECT_NAME }} - FORMIO_FORM_NAME: ${{ secrets.FORMIO_FORM_NAME }} + FORMIO_REBATE_FORM_PATH: ${{ secrets.FORMIO_REBATE_FORM_PATH }} + FORMIO_PAYMENT_REQUEST_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_REQUEST_FORM_PATH }} FORMIO_API_KEY: ${{ secrets.FORMIO_API_KEY }} BAP_CLIENT_ID: ${{ secrets.BAP_CLIENT_ID }} BAP_CLIENT_SECRET: ${{ secrets.BAP_CLIENT_SECRET }} @@ -120,7 +121,8 @@ jobs: cf set-env $APP_NAME "CSB_ENROLLMENT_PERIOD" "$CSB_ENROLLMENT_PERIOD" > /dev/null cf set-env $APP_NAME "FORMIO_BASE_URL" "$FORMIO_BASE_URL" > /dev/null cf set-env $APP_NAME "FORMIO_PROJECT_NAME" "$FORMIO_PROJECT_NAME" > /dev/null - cf set-env $APP_NAME "FORMIO_FORM_NAME" "$FORMIO_FORM_NAME" > /dev/null + cf set-env $APP_NAME "FORMIO_REBATE_FORM_PATH" "$FORMIO_REBATE_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_PAYMENT_REQUEST_FORM_PATH" "$FORMIO_PAYMENT_REQUEST_FORM_PATH" > /dev/null cf set-env $APP_NAME "FORMIO_API_KEY" "$FORMIO_API_KEY" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_ID" "$BAP_CLIENT_ID" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_SECRET" "$BAP_CLIENT_SECRET" > /dev/null diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index dc486955..1c162e42 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -45,7 +45,8 @@ jobs: FORMIO_PKG_AUTH_TOKEN: ${{ secrets.FORMIO_PKG_AUTH_TOKEN }} FORMIO_BASE_URL: ${{ secrets.FORMIO_BASE_URL }} FORMIO_PROJECT_NAME: ${{ secrets.FORMIO_PROJECT_NAME }} - FORMIO_FORM_NAME: ${{ secrets.FORMIO_FORM_NAME }} + FORMIO_REBATE_FORM_PATH: ${{ secrets.FORMIO_REBATE_FORM_PATH }} + FORMIO_PAYMENT_REQUEST_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_REQUEST_FORM_PATH }} FORMIO_API_KEY: ${{ secrets.FORMIO_API_KEY }} BAP_CLIENT_ID: ${{ secrets.BAP_CLIENT_ID }} BAP_CLIENT_SECRET: ${{ secrets.BAP_CLIENT_SECRET }} @@ -122,7 +123,8 @@ jobs: cf set-env $APP_NAME "CSB_ENROLLMENT_PERIOD" "$CSB_ENROLLMENT_PERIOD" > /dev/null cf set-env $APP_NAME "FORMIO_BASE_URL" "$FORMIO_BASE_URL" > /dev/null cf set-env $APP_NAME "FORMIO_PROJECT_NAME" "$FORMIO_PROJECT_NAME" > /dev/null - cf set-env $APP_NAME "FORMIO_FORM_NAME" "$FORMIO_FORM_NAME" > /dev/null + cf set-env $APP_NAME "FORMIO_REBATE_FORM_PATH" "$FORMIO_REBATE_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_PAYMENT_REQUEST_FORM_PATH" "$FORMIO_PAYMENT_REQUEST_FORM_PATH" > /dev/null cf set-env $APP_NAME "FORMIO_API_KEY" "$FORMIO_API_KEY" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_ID" "$BAP_CLIENT_ID" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_SECRET" "$BAP_CLIENT_SECRET" > /dev/null From e274c32fe4bb8d19de9ed536ef18947ba892f6fb Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 30 Aug 2022 15:45:07 -0400 Subject: [PATCH 003/128] Reorder formio routes in server app --- app/server/app/routes/api.js | 126 +++++++++++++++++------------------ 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index b001dd88..5eb82ee3 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -163,6 +163,69 @@ router.get("/bap-data", (req, res) => { }); }); +// --- get all rebate form submissions from Forms.gov +router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { + // NOTE: Helpdesk users might not have any SAM.gov records associated with + // their email address so we should not return any submissions to those users. + // The only reason we explicitly need to do this is because there could be + // some submissions without `bap_hidden_entity_combo_key` field values in the + // forms.gov database – that will never be the case for submissions created + // from this app, but there could be submissions created externally if someone + // is testing posting data (e.g. from a REST client, or the Formio Viewer) + if (req.bapComboKeys.length === 0) return res.json([]); + + const userSubmissionsUrl = + `${formioProjectUrl}/${formioRebateFormPath}/submission` + + `?sort=-modified` + + `&limit=1000000` + + `&data.bap_hidden_entity_combo_key=${req.bapComboKeys.join( + "&data.bap_hidden_entity_combo_key=" + )}`; + + axiosFormio(req) + .get(userSubmissionsUrl) + .then((axiosRes) => axiosRes.data) + .then((submissions) => res.json(submissions)) + .catch((error) => { + const message = "Error getting Forms.gov rebate form submissions"; + return res.status(error?.response?.status || 500).json({ message }); + }); +}); + +// --- post a new rebate form submission to Forms.gov +router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { + const comboKey = req.body.data?.bap_hidden_entity_combo_key; + + if (enrollmentClosed) { + const message = "CSB enrollment period is closed"; + return res.status(400).json({ message }); + } + + // verify post data includes one of user's BAP combo keys + if (!req.bapComboKeys.includes(comboKey)) { + const message = `User with email ${req.user.mail} attempted to post new form without a matching BAP combo key`; + log({ level: "error", message, req }); + return res.status(401).json({ message: "Unauthorized" }); + } + + // add custom metadata to track formio submissions from wrapper + req.body.metadata = { + ...req.body.metadata, + ...formioCsbMetadata, + }; + + const newSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission`; + + axiosFormio(req) + .post(newSubmissionUrl, req.body) + .then((axiosRes) => axiosRes.data) + .then((submission) => res.json(submission)) + .catch((error) => { + const message = "Error posting Forms.gov rebate form submission"; + return res.status(error?.response?.status || 500).json({ message }); + }); +}); + // --- get an existing rebate form's schema and submission data from Forms.gov router.get( "/rebate-form-submission/:id", @@ -254,40 +317,6 @@ router.post( } ); -// --- post a new rebate form submission to Forms.gov -router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { - const comboKey = req.body.data?.bap_hidden_entity_combo_key; - - if (enrollmentClosed) { - const message = "CSB enrollment period is closed"; - return res.status(400).json({ message }); - } - - // verify post data includes one of user's BAP combo keys - if (!req.bapComboKeys.includes(comboKey)) { - const message = `User with email ${req.user.mail} attempted to post new form without a matching BAP combo key`; - log({ level: "error", message, req }); - return res.status(401).json({ message: "Unauthorized" }); - } - - // add custom metadata to track formio submissions from wrapper - req.body.metadata = { - ...req.body.metadata, - ...formioCsbMetadata, - }; - - const newSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission`; - - axiosFormio(req) - .post(newSubmissionUrl, req.body) - .then((axiosRes) => axiosRes.data) - .then((submission) => res.json(submission)) - .catch((error) => { - const message = "Error posting Forms.gov rebate form submission"; - return res.status(error?.response?.status || 500).json({ message }); - }); -}); - // --- upload s3 file metadata to Forms.gov router.post("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { const { id, comboKey } = req.params; @@ -339,33 +368,4 @@ router.get("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { }); }); -// --- get all rebate form submissions from Forms.gov -router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { - // NOTE: Helpdesk users might not have any SAM.gov records associated with - // their email address so we should not return any submissions to those users. - // The only reason we explicitly need to do this is because there could be - // some submissions without `bap_hidden_entity_combo_key` field values in the - // forms.gov database – that will never be the case for submissions created - // from this app, but there could be submissions created externally if someone - // is testing posting data (e.g. from a REST client, or the Formio Viewer) - if (req.bapComboKeys.length === 0) return res.json([]); - - const userSubmissionsUrl = - `${formioProjectUrl}/${formioRebateFormPath}/submission` + - `?sort=-modified` + - `&limit=1000000` + - `&data.bap_hidden_entity_combo_key=${req.bapComboKeys.join( - "&data.bap_hidden_entity_combo_key=" - )}`; - - axiosFormio(req) - .get(userSubmissionsUrl) - .then((axiosRes) => axiosRes.data) - .then((submissions) => res.json(submissions)) - .catch((error) => { - const message = "Error getting Forms.gov rebate form submissions"; - return res.status(error?.response?.status || 500).json({ message }); - }); -}); - module.exports = router; From 90ff5fc4394ae3b1d14e219a53af1da7ab5f3e62 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 30 Aug 2022 16:13:42 -0400 Subject: [PATCH 004/128] Use common rebateFormApiPath variable in formio requests and consistently return json or error status code in all requests --- app/server/app/routes/api.js | 44 ++++++++++++++---------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index 5eb82ee3..93de1e0c 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -80,7 +80,7 @@ router.get("/content", (req, res) => { : stringsOrResponses.map((axiosRes) => axiosRes.data); }) .then((data) => { - res.json({ + return res.json({ siteAlert: data[0], helpdeskIntro: data[1], allRebatesIntro: data[2], @@ -101,7 +101,7 @@ router.get("/content", (req, res) => { const message = `S3 Error: ${errorStatus} ${errorMethod} ${errorUrl}`; log({ level: "error", message, req }); - res + return res .status(error?.response?.status || 500) .json({ message: "Error getting static content from S3 bucket" }); }); @@ -116,13 +116,13 @@ router.get("/helpdesk-access", ensureHelpdesk, (req, res) => { // --- get CSB app specific data (open enrollment status, etc.) router.get("/csb-data", (req, res) => { - res.json({ enrollmentClosed }); + return res.json({ enrollmentClosed }); }); // --- get user data from EPA Gateway/Login.gov router.get("/epa-data", (req, res) => { const { mail, memberof, exp } = req.user; - res.json({ mail, memberof, exp }); + return res.json({ mail, memberof, exp }); }); // --- get data from EPA's Business Automation Platform (BAP) @@ -148,7 +148,7 @@ router.get("/bap-data", (req, res) => { getRebateSubmissionsData(comboKeys, req) .then((submissions) => { - res.json({ + return res.json({ samResults: true, samEntities, rebateSubmissions: submissions, @@ -163,6 +163,8 @@ router.get("/bap-data", (req, res) => { }); }); +const rebateFormApiPath = `${formioProjectUrl}/${formioRebateFormPath}`; + // --- get all rebate form submissions from Forms.gov router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { // NOTE: Helpdesk users might not have any SAM.gov records associated with @@ -175,7 +177,7 @@ router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { if (req.bapComboKeys.length === 0) return res.json([]); const userSubmissionsUrl = - `${formioProjectUrl}/${formioRebateFormPath}/submission` + + `${rebateFormApiPath}/submission` + `?sort=-modified` + `&limit=1000000` + `&data.bap_hidden_entity_combo_key=${req.bapComboKeys.join( @@ -214,10 +216,8 @@ router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { ...formioCsbMetadata, }; - const newSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission`; - axiosFormio(req) - .post(newSubmissionUrl, req.body) + .post(`${rebateFormApiPath}/submission`, req.body) .then((axiosRes) => axiosRes.data) .then((submission) => res.json(submission)) .catch((error) => { @@ -234,16 +234,12 @@ router.get( async (req, res) => { const { id } = req.params; - const existingSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission/${id}`; - axiosFormio(req) - .get(existingSubmissionUrl) + .get(`${rebateFormApiPath}/submission/${id}`) .then((axiosRes) => axiosRes.data) .then((submission) => { - const formUrl = `${formioProjectUrl}/form/${submission.form}`; - axiosFormio(req) - .get(formUrl) + .get(`${formioProjectUrl}/form/${submission.form}`) .then((axiosRes) => axiosRes.data) .then((schema) => { const comboKey = submission.data.bap_hidden_entity_combo_key; @@ -251,13 +247,13 @@ router.get( if (!req.bapComboKeys.includes(comboKey)) { const message = `User with email ${req.user.mail} attempted to access submission ${id} that they do not have access to.`; log({ level: "warn", message, req }); - res.json({ + return res.json({ userAccess: false, formSchema: null, submissionData: null, }); } else { - res.json({ + return res.json({ userAccess: true, formSchema: { url: `${formioProjectUrl}/form/${submission.form}`, @@ -299,15 +295,13 @@ router.post( ...formioCsbMetadata, }; - const existingSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission/${id}`; - axiosFormio(req) - .put(existingSubmissionUrl, req.body) + .put(`${rebateFormApiPath}/submission/${id}`, req.body) .then((axiosRes) => axiosRes.data) .then((submission) => res.json(submission)) .catch((error) => { const message = "Error updating Forms.gov rebate form submission"; - res.status(error?.response?.status || 500).json({ message }); + return res.status(error?.response?.status || 500).json({ message }); }); }) .catch((error) => { @@ -329,10 +323,8 @@ router.post("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { return res.status(401).json({ message: "Unauthorized" }); } - const storageUrl = `${formioProjectUrl}/${formioRebateFormPath}/storage/s3`; - axiosFormio(req) - .post(storageUrl, req.body) + .post(`${rebateFormApiPath}/storage/s3`, req.body) .then((axiosRes) => axiosRes.data) .then((fileMetadata) => res.json(fileMetadata)) .catch((error) => { @@ -356,10 +348,8 @@ router.get("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { return res.status(401).json({ message: "Unauthorized" }); } - const storageUrl = `${formioProjectUrl}/${formioRebateFormPath}/storage/s3`; - axiosFormio(req) - .get(storageUrl, { params: req.query }) + .get(`${rebateFormApiPath}/storage/s3`, { params: req.query }) .then((axiosRes) => axiosRes.data) .then((fileMetadata) => res.json(fileMetadata)) .catch((error) => { From 34129dfb3d04e54c0af3e926573b6416d1570bca Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 30 Aug 2022 16:26:44 -0400 Subject: [PATCH 005/128] Rename formio forms env variables --- .github/workflows/dev.yml | 8 ++++---- .github/workflows/proto.yml | 8 ++++---- .github/workflows/staging.yml | 8 ++++---- app/server/.env.example | 4 ++-- app/server/app/config/formio.js | 8 ++++---- app/server/app/index.js | 4 ++-- app/server/app/routes/api.js | 16 ++++++++-------- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index f3fa9843..3f0e8d37 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -45,8 +45,8 @@ jobs: FORMIO_PKG_AUTH_TOKEN: ${{ secrets.FORMIO_PKG_AUTH_TOKEN }} FORMIO_BASE_URL: ${{ secrets.FORMIO_BASE_URL }} FORMIO_PROJECT_NAME: ${{ secrets.FORMIO_PROJECT_NAME }} - FORMIO_REBATE_FORM_PATH: ${{ secrets.FORMIO_REBATE_FORM_PATH }} - FORMIO_PAYMENT_REQUEST_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_REQUEST_FORM_PATH }} + FORMIO_APPLICATION_FORM_PATH: ${{ secrets.FORMIO_APPLICATION_FORM_PATH }} + FORMIO_PAYMENT_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_FORM_PATH }} FORMIO_API_KEY: ${{ secrets.FORMIO_API_KEY }} BAP_CLIENT_ID: ${{ secrets.BAP_CLIENT_ID }} BAP_CLIENT_SECRET: ${{ secrets.BAP_CLIENT_SECRET }} @@ -123,8 +123,8 @@ jobs: cf set-env $APP_NAME "CSB_ENROLLMENT_PERIOD" "$CSB_ENROLLMENT_PERIOD" > /dev/null cf set-env $APP_NAME "FORMIO_BASE_URL" "$FORMIO_BASE_URL" > /dev/null cf set-env $APP_NAME "FORMIO_PROJECT_NAME" "$FORMIO_PROJECT_NAME" > /dev/null - cf set-env $APP_NAME "FORMIO_REBATE_FORM_PATH" "$FORMIO_REBATE_FORM_PATH" > /dev/null - cf set-env $APP_NAME "FORMIO_PAYMENT_REQUEST_FORM_PATH" "$FORMIO_PAYMENT_REQUEST_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_APPLICATION_FORM_PATH" "$FORMIO_APPLICATION_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_PAYMENT_FORM_PATH" "$FORMIO_PAYMENT_FORM_PATH" > /dev/null cf set-env $APP_NAME "FORMIO_API_KEY" "$FORMIO_API_KEY" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_ID" "$BAP_CLIENT_ID" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_SECRET" "$BAP_CLIENT_SECRET" > /dev/null diff --git a/.github/workflows/proto.yml b/.github/workflows/proto.yml index 3c14a4f4..c1724ef8 100644 --- a/.github/workflows/proto.yml +++ b/.github/workflows/proto.yml @@ -44,8 +44,8 @@ jobs: FORMIO_PKG_AUTH_TOKEN: ${{ secrets.FORMIO_PKG_AUTH_TOKEN }} FORMIO_BASE_URL: ${{ secrets.FORMIO_BASE_URL }} FORMIO_PROJECT_NAME: ${{ secrets.FORMIO_PROJECT_NAME }} - FORMIO_REBATE_FORM_PATH: ${{ secrets.FORMIO_REBATE_FORM_PATH }} - FORMIO_PAYMENT_REQUEST_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_REQUEST_FORM_PATH }} + FORMIO_APPLICATION_FORM_PATH: ${{ secrets.FORMIO_APPLICATION_FORM_PATH }} + FORMIO_PAYMENT_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_FORM_PATH }} FORMIO_API_KEY: ${{ secrets.FORMIO_API_KEY }} BAP_CLIENT_ID: ${{ secrets.BAP_CLIENT_ID }} BAP_CLIENT_SECRET: ${{ secrets.BAP_CLIENT_SECRET }} @@ -121,8 +121,8 @@ jobs: cf set-env $APP_NAME "CSB_ENROLLMENT_PERIOD" "$CSB_ENROLLMENT_PERIOD" > /dev/null cf set-env $APP_NAME "FORMIO_BASE_URL" "$FORMIO_BASE_URL" > /dev/null cf set-env $APP_NAME "FORMIO_PROJECT_NAME" "$FORMIO_PROJECT_NAME" > /dev/null - cf set-env $APP_NAME "FORMIO_REBATE_FORM_PATH" "$FORMIO_REBATE_FORM_PATH" > /dev/null - cf set-env $APP_NAME "FORMIO_PAYMENT_REQUEST_FORM_PATH" "$FORMIO_PAYMENT_REQUEST_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_APPLICATION_FORM_PATH" "$FORMIO_APPLICATION_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_PAYMENT_FORM_PATH" "$FORMIO_PAYMENT_FORM_PATH" > /dev/null cf set-env $APP_NAME "FORMIO_API_KEY" "$FORMIO_API_KEY" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_ID" "$BAP_CLIENT_ID" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_SECRET" "$BAP_CLIENT_SECRET" > /dev/null diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 1c162e42..346f9fec 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -45,8 +45,8 @@ jobs: FORMIO_PKG_AUTH_TOKEN: ${{ secrets.FORMIO_PKG_AUTH_TOKEN }} FORMIO_BASE_URL: ${{ secrets.FORMIO_BASE_URL }} FORMIO_PROJECT_NAME: ${{ secrets.FORMIO_PROJECT_NAME }} - FORMIO_REBATE_FORM_PATH: ${{ secrets.FORMIO_REBATE_FORM_PATH }} - FORMIO_PAYMENT_REQUEST_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_REQUEST_FORM_PATH }} + FORMIO_APPLICATION_FORM_PATH: ${{ secrets.FORMIO_APPLICATION_FORM_PATH }} + FORMIO_PAYMENT_FORM_PATH: ${{ secrets.FORMIO_PAYMENT_FORM_PATH }} FORMIO_API_KEY: ${{ secrets.FORMIO_API_KEY }} BAP_CLIENT_ID: ${{ secrets.BAP_CLIENT_ID }} BAP_CLIENT_SECRET: ${{ secrets.BAP_CLIENT_SECRET }} @@ -123,8 +123,8 @@ jobs: cf set-env $APP_NAME "CSB_ENROLLMENT_PERIOD" "$CSB_ENROLLMENT_PERIOD" > /dev/null cf set-env $APP_NAME "FORMIO_BASE_URL" "$FORMIO_BASE_URL" > /dev/null cf set-env $APP_NAME "FORMIO_PROJECT_NAME" "$FORMIO_PROJECT_NAME" > /dev/null - cf set-env $APP_NAME "FORMIO_REBATE_FORM_PATH" "$FORMIO_REBATE_FORM_PATH" > /dev/null - cf set-env $APP_NAME "FORMIO_PAYMENT_REQUEST_FORM_PATH" "$FORMIO_PAYMENT_REQUEST_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_APPLICATION_FORM_PATH" "$FORMIO_APPLICATION_FORM_PATH" > /dev/null + cf set-env $APP_NAME "FORMIO_PAYMENT_FORM_PATH" "$FORMIO_PAYMENT_FORM_PATH" > /dev/null cf set-env $APP_NAME "FORMIO_API_KEY" "$FORMIO_API_KEY" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_ID" "$BAP_CLIENT_ID" > /dev/null cf set-env $APP_NAME "BAP_CLIENT_SECRET" "$BAP_CLIENT_SECRET" > /dev/null diff --git a/app/server/.env.example b/app/server/.env.example index 3676def1..12c77102 100644 --- a/app/server/.env.example +++ b/app/server/.env.example @@ -13,8 +13,8 @@ JWT_PUBLIC_KEY=secret CSB_ENROLLMENT_PERIOD=closed FORMIO_BASE_URL= FORMIO_PROJECT_NAME= -FORMIO_REBATE_FORM_PATH= -FORMIO_PAYMENT_REQUEST_FORM_PATH= +FORMIO_APPLICATION_FORM_PATH= +FORMIO_PAYMENT_FORM_PATH= FORMIO_API_KEY= BAP_CLIENT_ID= BAP_CLIENT_SECRET= diff --git a/app/server/app/config/formio.js b/app/server/app/config/formio.js index 99b8e496..cf52d96e 100644 --- a/app/server/app/config/formio.js +++ b/app/server/app/config/formio.js @@ -5,8 +5,8 @@ const log = require("../utilities/logger"); const formioBaseUrl = process.env.FORMIO_BASE_URL; const formioProjectName = process.env.FORMIO_PROJECT_NAME; const formioProjectUrl = `${formioBaseUrl}/${formioProjectName}`; -const formioRebateFormPath = process.env.FORMIO_REBATE_FORM_PATH; -const formioPaymentRequestFormPath = process.env.FORMIO_PAYMENT_REQUEST_FORM_PATH; /* prettier-ignore */ +const formioApplicationFormPath = process.env.FORMIO_APPLICATION_FORM_PATH; +const formioPaymentFormPath = process.env.FORMIO_PAYMENT_FORM_PATH; const formioApiKey = process.env.FORMIO_API_KEY; function axiosFormio(req) { @@ -76,7 +76,7 @@ const formioCsbMetadata = { module.exports = { axiosFormio, formioProjectUrl, - formioRebateFormPath, - formioPaymentRequestFormPath, + formioApplicationFormPath, + formioPaymentFormPath, formioCsbMetadata, }; diff --git a/app/server/app/index.js b/app/server/app/index.js index 807723fa..d2ce1018 100644 --- a/app/server/app/index.js +++ b/app/server/app/index.js @@ -30,8 +30,8 @@ const requiredEnvVars = [ "CSB_ENROLLMENT_PERIOD", "FORMIO_BASE_URL", "FORMIO_PROJECT_NAME", - "FORMIO_REBATE_FORM_PATH", - "FORMIO_PAYMENT_REQUEST_FORM_PATH", + "FORMIO_APPLICATION_FORM_PATH", + "FORMIO_PAYMENT_FORM_PATH", "FORMIO_API_KEY", "S3_PUBLIC_BUCKET", "S3_PUBLIC_REGION", diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index 93de1e0c..38a38f20 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -6,7 +6,7 @@ const axios = require("axios").default; const { axiosFormio, formioProjectUrl, - formioRebateFormPath, + formioApplicationFormPath, formioCsbMetadata, } = require("../config/formio"); const { @@ -163,7 +163,7 @@ router.get("/bap-data", (req, res) => { }); }); -const rebateFormApiPath = `${formioProjectUrl}/${formioRebateFormPath}`; +const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; // --- get all rebate form submissions from Forms.gov router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { @@ -177,7 +177,7 @@ router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { if (req.bapComboKeys.length === 0) return res.json([]); const userSubmissionsUrl = - `${rebateFormApiPath}/submission` + + `${applicationFormApiPath}/submission` + `?sort=-modified` + `&limit=1000000` + `&data.bap_hidden_entity_combo_key=${req.bapComboKeys.join( @@ -217,7 +217,7 @@ router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { }; axiosFormio(req) - .post(`${rebateFormApiPath}/submission`, req.body) + .post(`${applicationFormApiPath}/submission`, req.body) .then((axiosRes) => axiosRes.data) .then((submission) => res.json(submission)) .catch((error) => { @@ -235,7 +235,7 @@ router.get( const { id } = req.params; axiosFormio(req) - .get(`${rebateFormApiPath}/submission/${id}`) + .get(`${applicationFormApiPath}/submission/${id}`) .then((axiosRes) => axiosRes.data) .then((submission) => { axiosFormio(req) @@ -296,7 +296,7 @@ router.post( }; axiosFormio(req) - .put(`${rebateFormApiPath}/submission/${id}`, req.body) + .put(`${applicationFormApiPath}/submission/${id}`, req.body) .then((axiosRes) => axiosRes.data) .then((submission) => res.json(submission)) .catch((error) => { @@ -324,7 +324,7 @@ router.post("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { } axiosFormio(req) - .post(`${rebateFormApiPath}/storage/s3`, req.body) + .post(`${applicationFormApiPath}/storage/s3`, req.body) .then((axiosRes) => axiosRes.data) .then((fileMetadata) => res.json(fileMetadata)) .catch((error) => { @@ -349,7 +349,7 @@ router.get("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { } axiosFormio(req) - .get(`${rebateFormApiPath}/storage/s3`, { params: req.query }) + .get(`${applicationFormApiPath}/storage/s3`, { params: req.query }) .then((axiosRes) => axiosRes.data) .then((fileMetadata) => res.json(fileMetadata)) .catch((error) => { From 16563629f0599b652a8c9c8d2651ed339fb4046d Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 30 Aug 2022 16:33:04 -0400 Subject: [PATCH 006/128] Add initial payment form submissions api endpoint --- app/server/app/routes/api.js | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index 38a38f20..e4a34662 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -7,6 +7,7 @@ const { axiosFormio, formioProjectUrl, formioApplicationFormPath, + formioPaymentFormPath, formioCsbMetadata, } = require("../config/formio"); const { @@ -189,7 +190,7 @@ router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { .then((axiosRes) => axiosRes.data) .then((submissions) => res.json(submissions)) .catch((error) => { - const message = "Error getting Forms.gov rebate form submissions"; + const message = `Error getting Forms.gov rebate form submissions`; return res.status(error?.response?.status || 500).json({ message }); }); }); @@ -199,7 +200,7 @@ router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { const comboKey = req.body.data?.bap_hidden_entity_combo_key; if (enrollmentClosed) { - const message = "CSB enrollment period is closed"; + const message = `CSB enrollment period is closed`; return res.status(400).json({ message }); } @@ -221,7 +222,7 @@ router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { .then((axiosRes) => axiosRes.data) .then((submission) => res.json(submission)) .catch((error) => { - const message = "Error posting Forms.gov rebate form submission"; + const message = `Error posting Forms.gov rebate form submission`; return res.status(error?.response?.status || 500).json({ message }); }); }); @@ -300,12 +301,12 @@ router.post( .then((axiosRes) => axiosRes.data) .then((submission) => res.json(submission)) .catch((error) => { - const message = "Error updating Forms.gov rebate form submission"; + const message = `Error updating Forms.gov rebate form submission`; return res.status(error?.response?.status || 500).json({ message }); }); }) .catch((error) => { - const message = "CSB enrollment period is closed"; + const message = `CSB enrollment period is closed`; return res.status(400).json({ message }); }); } @@ -328,12 +329,12 @@ router.post("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { .then((axiosRes) => axiosRes.data) .then((fileMetadata) => res.json(fileMetadata)) .catch((error) => { - const message = "Error uploading Forms.gov file"; + const message = `Error uploading Forms.gov file`; return res.status(error?.response?.status || 500).json({ message }); }); }) .catch((error) => { - const message = "CSB enrollment period is closed"; + const message = `CSB enrollment period is closed`; return res.status(400).json({ message }); }); }); @@ -353,7 +354,21 @@ router.get("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { .then((axiosRes) => axiosRes.data) .then((fileMetadata) => res.json(fileMetadata)) .catch((error) => { - const message = "Error downloading Forms.gov file"; + const message = `Error downloading Forms.gov file`; + return res.status(error?.response?.status || 500).json({ message }); + }); +}); + +const paymentFormApiPath = `${formioProjectUrl}/${formioPaymentFormPath}`; + +// --- get all payment request form submissions from Forms.gov +router.get("/payment-form-submissions", storeBapComboKeys, (req, res) => { + axiosFormio(req) + .get(`${paymentFormApiPath}/submission?sort=-modified&limit=1000000`) + .then((axiosRes) => axiosRes.data) + .then((submissions) => res.json(submissions)) + .catch((error) => { + const message = `Error getting Forms.gov payment request form submissions`; return res.status(error?.response?.status || 500).json({ message }); }); }); From d1166f8cbadbacd3fd4ed8c414600220cad3991f Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 11:54:25 -0400 Subject: [PATCH 007/128] Rename all use of 'rebate' in server endpoints to 'application' and rename 'form' status api endpoint to 'application-form' --- app/client/src/routes/allRebates.tsx | 4 +- app/client/src/routes/existingRebate.tsx | 6 +- app/client/src/routes/helpdesk.tsx | 6 +- app/client/src/routes/newRebate.tsx | 2 +- app/server/app/routes/api.js | 24 ++-- app/server/app/routes/help.js | 142 ++++++++++++----------- app/server/app/routes/status.js | 8 +- docs/csb-openapi.json | 24 ++-- 8 files changed, 114 insertions(+), 102 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 465fccec..c107ccf1 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -35,7 +35,7 @@ export default function AllRebates() { dispatch({ type: "FETCH_REBATE_FORM_SUBMISSIONS_REQUEST" }); - fetchData(`${serverUrl}/api/rebate-form-submissions`) + fetchData(`${serverUrl}/api/application-form-submissions`) .then((res) => { dispatch({ type: "FETCH_REBATE_FORM_SUBMISSIONS_SUCCESS", @@ -242,7 +242,7 @@ form for the fields to be displayed. */ // change the submission's state to draft, then // redirect to the form to allow user to edit fetchData( - `${serverUrl}/api/rebate-form-submission/${_id}`, + `${serverUrl}/api/application-form-submission/${_id}`, { data, state: "draft" } ) .then((res) => { diff --git a/app/client/src/routes/existingRebate.tsx b/app/client/src/routes/existingRebate.tsx index 13f104e4..097ff5cb 100644 --- a/app/client/src/routes/existingRebate.tsx +++ b/app/client/src/routes/existingRebate.tsx @@ -273,7 +273,7 @@ function ExistingRebateContent() { }, }); - fetchData(`${serverUrl}/api/rebate-form-submission/${id}`) + fetchData(`${serverUrl}/api/application-form-submission/${id}`) .then((res) => { // Set up s3 re-route to wrapper app const s3Provider = Formio.Providers.providers.storage.s3; @@ -505,7 +505,7 @@ function ExistingRebateContent() { setPendingSubmissionData(data); fetchData( - `${serverUrl}/api/rebate-form-submission/${submissionData._id}`, + `${serverUrl}/api/application-form-submission/${submissionData._id}`, { ...submission, data } ) .then((res) => { @@ -590,7 +590,7 @@ function ExistingRebateContent() { setPendingSubmissionData(data); fetchData( - `${serverUrl}/api/rebate-form-submission/${submissionData._id}`, + `${serverUrl}/api/application-form-submission/${submissionData._id}`, { ...submission, data, state: "draft" } ) .then((res) => { diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 1a26cf15..38ffb289 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -119,7 +119,9 @@ export default function Helpdesk() { }, }); - fetchData(`${serverUrl}/help/rebate-form-submission/${searchText}`) + fetchData( + `${serverUrl}/help/application-form-submission/${searchText}` + ) .then((res) => { setFormId(res.submissionData._id); setRebateFormSubmission({ @@ -281,7 +283,7 @@ export default function Helpdesk() { }); fetchData( - `${serverUrl}/help/rebate-form-submission/${formId}`, + `${serverUrl}/help/application-form-submission/${formId}`, {} ) .then((res) => { diff --git a/app/client/src/routes/newRebate.tsx b/app/client/src/routes/newRebate.tsx index a9ee3bcd..dae36087 100644 --- a/app/client/src/routes/newRebate.tsx +++ b/app/client/src/routes/newRebate.tsx @@ -15,7 +15,7 @@ import { SamEntityData, useUserState } from "contexts/user"; function createNewRebate(email: string, entity: SamEntityData) { const { title, name } = getUserInfo(email, entity); - return fetchData(`${serverUrl}/api/rebate-form-submission/`, { + return fetchData(`${serverUrl}/api/application-form-submission/`, { data: { last_updated_by: email, hidden_current_user_email: email, diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index e4a34662..daa18aa8 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -166,8 +166,8 @@ router.get("/bap-data", (req, res) => { const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; -// --- get all rebate form submissions from Forms.gov -router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { +// --- get all application form submissions from Forms.gov +router.get("/application-form-submissions", storeBapComboKeys, (req, res) => { // NOTE: Helpdesk users might not have any SAM.gov records associated with // their email address so we should not return any submissions to those users. // The only reason we explicitly need to do this is because there could be @@ -190,13 +190,13 @@ router.get("/rebate-form-submissions", storeBapComboKeys, (req, res) => { .then((axiosRes) => axiosRes.data) .then((submissions) => res.json(submissions)) .catch((error) => { - const message = `Error getting Forms.gov rebate form submissions`; + const message = `Error getting Forms.gov application form submissions`; return res.status(error?.response?.status || 500).json({ message }); }); }); -// --- post a new rebate form submission to Forms.gov -router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { +// --- post a new application form submission to Forms.gov +router.post("/application-form-submission", storeBapComboKeys, (req, res) => { const comboKey = req.body.data?.bap_hidden_entity_combo_key; if (enrollmentClosed) { @@ -222,14 +222,14 @@ router.post("/rebate-form-submission", storeBapComboKeys, (req, res) => { .then((axiosRes) => axiosRes.data) .then((submission) => res.json(submission)) .catch((error) => { - const message = `Error posting Forms.gov rebate form submission`; + const message = `Error posting Forms.gov application form submission`; return res.status(error?.response?.status || 500).json({ message }); }); }); -// --- get an existing rebate form's schema and submission data from Forms.gov +// --- get an existing application form's schema and submission data from Forms.gov router.get( - "/rebate-form-submission/:id", + "/application-form-submission/:id", verifyMongoObjectId, storeBapComboKeys, async (req, res) => { @@ -266,15 +266,15 @@ router.get( }); }) .catch((error) => { - const message = `Error getting Forms.gov rebate form submission ${id}`; + const message = `Error getting Forms.gov application form submission ${id}`; res.status(error?.response?.status || 500).json({ message }); }); } ); -// --- post an update to an existing draft rebate form submission to Forms.gov +// --- post an update to an existing draft application form submission to Forms.gov router.post( - "/rebate-form-submission/:id", + "/application-form-submission/:id", verifyMongoObjectId, storeBapComboKeys, (req, res) => { @@ -301,7 +301,7 @@ router.post( .then((axiosRes) => axiosRes.data) .then((submission) => res.json(submission)) .catch((error) => { - const message = `Error updating Forms.gov rebate form submission`; + const message = `Error updating Forms.gov application form submission`; return res.status(error?.response?.status || 500).json({ message }); }); }) diff --git a/app/server/app/routes/help.js b/app/server/app/routes/help.js index b67c59fe..bf9b0209 100644 --- a/app/server/app/routes/help.js +++ b/app/server/app/routes/help.js @@ -3,7 +3,7 @@ const express = require("express"); const { axiosFormio, formioProjectUrl, - formioRebateFormPath, + formioApplicationFormPath, formioCsbMetadata, } = require("../config/formio"); const { @@ -21,78 +21,86 @@ const router = express.Router(); router.use(ensureAuthenticated); router.use(ensureHelpdesk); -// --- get an existing rebate form's submission data from Forms.gov -router.get("/rebate-form-submission/:id", verifyMongoObjectId, (req, res) => { - const { id } = req.params; +const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; - axiosFormio(req) - .get(`${formioProjectUrl}/${formioRebateFormPath}/submission/${id}`) - .then((axiosRes) => axiosRes.data) - .then((submission) => { - axiosFormio(req) - .get(`${formioProjectUrl}/form/${submission.form}`) - .then((axiosRes) => axiosRes.data) - .then((schema) => { - res.json({ - formSchema: { - url: `${formioProjectUrl}/form/${submission.form}`, - json: schema, - }, - submissionData: submission, - }); - }); - }) - .catch((error) => { - const message = `Error getting Forms.gov rebate form submission ${id}`; - return res.status(error?.response?.status || 500).json({ message }); - }); -}); - -// --- change a submitted Forms.gov rebate form's submission back to 'draft' -router.post("/rebate-form-submission/:id", verifyMongoObjectId, (req, res) => { - const { id } = req.params; - const { mail } = req.user; +// --- get an existing application form's submission data from Forms.gov +router.get( + "/application-form-submission/:id", + verifyMongoObjectId, + (req, res) => { + const { id } = req.params; - if (enrollmentClosed) { - const message = "CSB enrollment period is closed"; - return res.status(400).json({ message }); + axiosFormio(req) + .get(`${applicationFormApiPath}/submission/${id}`) + .then((axiosRes) => axiosRes.data) + .then((submission) => { + axiosFormio(req) + .get(`${formioProjectUrl}/form/${submission.form}`) + .then((axiosRes) => axiosRes.data) + .then((schema) => { + return res.json({ + formSchema: { + url: `${formioProjectUrl}/form/${submission.form}`, + json: schema, + }, + submissionData: submission, + }); + }); + }) + .catch((error) => { + const message = `Error getting Forms.gov application form submission ${id}`; + return res.status(error?.response?.status || 500).json({ message }); + }); } +); - const existingSubmissionUrl = `${formioProjectUrl}/${formioRebateFormPath}/submission/${id}`; +// --- change a submitted Forms.gov application form's submission state back to draft +router.post( + "/application-form-submission/:id", + verifyMongoObjectId, + (req, res) => { + const { id } = req.params; + const { mail } = req.user; + + if (enrollmentClosed) { + const message = `CSB enrollment period is closed`; + return res.status(400).json({ message }); + } - axiosFormio(req) - .get(existingSubmissionUrl) - .then((axiosRes) => axiosRes.data) - .then((existingSubmission) => { - axiosFormio(req) - .put(existingSubmissionUrl, { - state: "draft", - data: { ...existingSubmission.data, last_updated_by: mail }, - metadata: { ...existingSubmission.metadata, ...formioCsbMetadata }, - }) - .then((axiosRes) => axiosRes.data) - .then((updatedSubmission) => { - const message = `User with email ${mail} updated rebate form submission ${id} from submitted to draft.`; - log({ level: "info", message, req }); + axiosFormio(req) + .get(`${applicationFormApiPath}/submission/${id}`) + .then((axiosRes) => axiosRes.data) + .then((existingSubmission) => { + axiosFormio(req) + .put(`${applicationFormApiPath}/submission/${id}`, { + state: "draft", + data: { ...existingSubmission.data, last_updated_by: mail }, + metadata: { ...existingSubmission.metadata, ...formioCsbMetadata }, + }) + .then((axiosRes) => axiosRes.data) + .then((updatedSubmission) => { + const message = `User with email ${mail} updated application form submission ${id} from submitted to draft.`; + log({ level: "info", message, req }); - axiosFormio(req) - .get(`${formioProjectUrl}/form/${updatedSubmission.form}`) - .then((axiosRes) => axiosRes.data) - .then((schema) => { - res.json({ - formSchema: { - url: `${formioProjectUrl}/form/${updatedSubmission.form}`, - json: schema, - }, - submissionData: updatedSubmission, + axiosFormio(req) + .get(`${formioProjectUrl}/form/${updatedSubmission.form}`) + .then((axiosRes) => axiosRes.data) + .then((schema) => { + return res.json({ + formSchema: { + url: `${formioProjectUrl}/form/${updatedSubmission.form}`, + json: schema, + }, + submissionData: updatedSubmission, + }); }); - }); - }); - }) - .catch((error) => { - const message = `Error updating Forms.gov rebate form submission ${id}`; - return res.status(error?.response?.status || 500).json({ message }); - }); -}); + }); + }) + .catch((error) => { + const message = `Error updating Forms.gov application form submission ${id}`; + return res.status(error?.response?.status || 500).json({ message }); + }); + } +); module.exports = router; diff --git a/app/server/app/routes/status.js b/app/server/app/routes/status.js index d12c4f77..41871ee3 100644 --- a/app/server/app/routes/status.js +++ b/app/server/app/routes/status.js @@ -3,7 +3,7 @@ const express = require("express"); const { axiosFormio, formioProjectUrl, - formioRebateFormPath, + formioApplicationFormPath, } = require("../config/formio"); const { getSamData } = require("../utilities/bap"); @@ -23,9 +23,11 @@ router.get("/bap", (req, res) => { }); }); -router.get("/form", (req, res) => { +const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; + +router.get("/application-form", (req, res) => { axiosFormio(req) - .get(`${formioProjectUrl}/${formioRebateFormPath}`) + .get(applicationFormApiPath) .then((axiosRes) => axiosRes.data) .then((schema) => // Verify that schema has type of form and a title exists (confirming formio returned a valid schema) diff --git a/docs/csb-openapi.json b/docs/csb-openapi.json index d8737cc8..769bb5c9 100644 --- a/docs/csb-openapi.json +++ b/docs/csb-openapi.json @@ -33,9 +33,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/status/form": { + "/status/application-form": { "get": { - "summary": "/status/form", + "summary": "/status/application-form", "responses": { "200": { "description": "OK" @@ -142,9 +142,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/help/rebate-form-submission/{id}": { + "/help/application-form-submission/{id}": { "get": { - "summary": "/help/rebate-form-submission/{id}", + "summary": "/help/application-form-submission/{id}", "parameters": [ { "name": "id", @@ -192,7 +192,7 @@ "tags": [] }, "post": { - "summary": "/help/rebate-form-submission/{id}", + "summary": "/help/application-form-submission/{id}", "parameters": [ { "name": "id", @@ -479,9 +479,9 @@ ] } }, - "/api/rebate-form-submission": { + "/api/application-form-submission": { "post": { - "summary": "/api/rebate-form-submission", + "summary": "/api/application-form-submission", "responses": { "200": { "description": "OK", @@ -501,9 +501,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/api/rebate-form-submissions": { + "/api/application-form-submissions": { "get": { - "summary": "/api/rebate-form-submissions", + "summary": "/api/application-form-submissions", "responses": { "200": { "description": "OK", @@ -523,9 +523,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/api/rebate-form-submission/{id}": { + "/api/application-form-submission/{id}": { "get": { - "summary": "/api/rebate-form-submission/{id}", + "summary": "/api/application-form-submission/{id}", "parameters": [ { "name": "id", @@ -573,7 +573,7 @@ "tags": [] }, "post": { - "summary": "/api/rebate-form-submission/{id}", + "summary": "/api/application-form-submission/{id}", "parameters": [ { "name": "id", From 1a0e1717a41cedaa407365451d1665c5342575ef Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 12:18:00 -0400 Subject: [PATCH 008/128] Rename 'rebate' in markdown files to 'application' --- app/client/src/contexts/content.tsx | 18 +++++++++--------- app/client/src/routes/existingRebate.tsx | 4 ++-- app/client/src/routes/newRebate.tsx | 2 +- ...ate-intro.md => draft-application-intro.md} | 0 ...ate-dialog.md => new-application-dialog.md} | 0 ...intro.md => submitted-application-intro.md} | 0 app/server/app/routes/api.js | 12 ++++++------ 7 files changed, 18 insertions(+), 18 deletions(-) rename app/server/app/content/{draft-rebate-intro.md => draft-application-intro.md} (100%) rename app/server/app/content/{new-rebate-dialog.md => new-application-dialog.md} (100%) rename app/server/app/content/{submitted-rebate-intro.md => submitted-application-intro.md} (100%) diff --git a/app/client/src/contexts/content.tsx b/app/client/src/contexts/content.tsx index 19c64df5..59af2d31 100644 --- a/app/client/src/contexts/content.tsx +++ b/app/client/src/contexts/content.tsx @@ -15,9 +15,9 @@ type Content = { helpdeskIntro: string; allRebatesIntro: string; allRebatesOutro: string; - newRebateDialog: string; - draftRebateIntro: string; - submittedRebateIntro: string; + newApplicationDialog: string; + draftApplicationIntro: string; + submittedApplicationIntro: string; }; type State = { @@ -57,9 +57,9 @@ function reducer(state: State, action: Action): State { helpdeskIntro, allRebatesIntro, allRebatesOutro, - newRebateDialog, - draftRebateIntro, - submittedRebateIntro, + newApplicationDialog, + draftApplicationIntro, + submittedApplicationIntro, } = action.payload; return { @@ -71,9 +71,9 @@ function reducer(state: State, action: Action): State { helpdeskIntro, allRebatesIntro, allRebatesOutro, - newRebateDialog, - draftRebateIntro, - submittedRebateIntro, + newApplicationDialog, + draftApplicationIntro, + submittedApplicationIntro, }, }, }; diff --git a/app/client/src/routes/existingRebate.tsx b/app/client/src/routes/existingRebate.tsx index 097ff5cb..382ddeb7 100644 --- a/app/client/src/routes/existingRebate.tsx +++ b/app/client/src/routes/existingRebate.tsx @@ -382,9 +382,9 @@ function ExistingRebateContent() { className="margin-top-4" children={ submissionData.state === "draft" - ? content.data?.draftRebateIntro || "" + ? content.data?.draftApplicationIntro || "" : submissionData.state === "submitted" - ? content.data?.submittedRebateIntro || "" + ? content.data?.submittedApplicationIntro || "" : "" } /> diff --git a/app/client/src/routes/newRebate.tsx b/app/client/src/routes/newRebate.tsx index dae36087..05d4cc4c 100644 --- a/app/client/src/routes/newRebate.tsx +++ b/app/client/src/routes/newRebate.tsx @@ -86,7 +86,7 @@ export default function NewRebate() { {content.status === "success" && ( (

{ "helpdesk-intro.md", "all-rebates-intro.md", "all-rebates-outro.md", - "new-rebate-dialog.md", - "draft-rebate-intro.md", - "submitted-rebate-intro.md", + "new-application-dialog.md", + "draft-application-intro.md", + "submitted-application-intro.md", ]; const s3BucketUrl = `https://${s3Bucket}.s3-${s3Region}.amazonaws.com`; @@ -86,9 +86,9 @@ router.get("/content", (req, res) => { helpdeskIntro: data[1], allRebatesIntro: data[2], allRebatesOutro: data[3], - newRebateDialog: data[4], - draftRebateIntro: data[5], - submittedRebateIntro: data[6], + newApplicationDialog: data[4], + draftApplicationIntro: data[5], + submittedApplicationIntro: data[6], }); }) .catch((error) => { From f8a5a64780b7dc7f228ad7d89d34b7fac120219e Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 12:46:38 -0400 Subject: [PATCH 009/128] Update all applicable remaining server app 'rebate' references to 'application' --- app/client/src/contexts/user.tsx | 8 ++++---- app/client/src/routes/allRebates.tsx | 2 +- app/client/src/routes/existingRebate.tsx | 2 +- app/server/app/middleware.js | 1 - app/server/app/routes/api.js | 13 ++++++++----- app/server/app/utilities/bap.js | 18 +++++++++--------- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/app/client/src/contexts/user.tsx b/app/client/src/contexts/user.tsx index 19214ac8..aa26e6fe 100644 --- a/app/client/src/contexts/user.tsx +++ b/app/client/src/contexts/user.tsx @@ -91,12 +91,12 @@ type State = { | { samResults: false; samEntities: []; - rebateSubmissions: []; + applicationSubmissions: []; } | { samResults: true; samEntities: SamEntityData[]; - rebateSubmissions: BapSubmissionData[]; + applicationSubmissions: BapSubmissionData[]; }; } | { status: "failure"; data: {} }; @@ -125,12 +125,12 @@ type Action = | { samResults: false; samEntities: []; - rebateSubmissions: []; + applicationSubmissions: []; } | { samResults: true; samEntities: SamEntityData[]; - rebateSubmissions: BapSubmissionData[]; + applicationSubmissions: BapSubmissionData[]; }; }; } diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index c107ccf1..d6d1b170 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -73,7 +73,7 @@ export default function AllRebates() { * datetime. */ const submissions = rebateFormSubmissions.data.map((formioSubmission) => { - const matchedBapSubmission = bapUserData.data.rebateSubmissions.find( + const matchedBapSubmission = bapUserData.data.applicationSubmissions.find( (bapSubmission) => bapSubmission.CSB_Form_ID__c === formioSubmission._id ); diff --git a/app/client/src/routes/existingRebate.tsx b/app/client/src/routes/existingRebate.tsx index 382ddeb7..655783f2 100644 --- a/app/client/src/routes/existingRebate.tsx +++ b/app/client/src/routes/existingRebate.tsx @@ -349,7 +349,7 @@ function ExistingRebateContent() { const { enrollmentClosed } = csbData.data; - const matchedBapSubmission = bapUserData.data.rebateSubmissions.find( + const matchedBapSubmission = bapUserData.data.applicationSubmissions.find( (bapSubmission) => bapSubmission.CSB_Form_ID__c === id ); diff --git a/app/server/app/middleware.js b/app/server/app/middleware.js index 08cad6ca..f2a116f6 100644 --- a/app/server/app/middleware.js +++ b/app/server/app/middleware.js @@ -3,7 +3,6 @@ const express = require("express"); const jwt = require("jsonwebtoken"); const ObjectId = require("mongodb").ObjectId; // --- -const { getRebateSubmissionsData } = require("./utilities/bap"); const { createJwt, jwtAlgorithm } = require("./utilities/createJwt"); const log = require("./utilities/logger"); const { getComboKeys } = require("./utilities/bap"); diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index b65e1ef6..35975a67 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -16,7 +16,10 @@ const { storeBapComboKeys, verifyMongoObjectId, } = require("../middleware"); -const { getSamData, getRebateSubmissionsData } = require("../utilities/bap"); +const { + getSamData, + getApplicationSubmissionsData, +} = require("../utilities/bap"); const log = require("../utilities/logger"); const enrollmentClosed = process.env.CSB_ENROLLMENT_PERIOD !== "open"; @@ -37,7 +40,7 @@ function checkEnrollmentPeriodAndBapStatus({ id, comboKey, req }) { return Promise.resolve(); } // else, enrollment is closed, so only continue if edits are requested - return getRebateSubmissionsData([comboKey], req).then((submissions) => { + return getApplicationSubmissionsData([comboKey], req).then((submissions) => { const submission = submissions.find((s) => s.CSB_Form_ID__c === id); const status = submission?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c; return status === "Edits Requested" ? Promise.resolve() : Promise.reject(); @@ -141,18 +144,18 @@ router.get("/bap-data", (req, res) => { return res.json({ samResults: false, samEntities: [], - rebateSubmissions: [], + applicationSubmissions: [], }); } const comboKeys = samEntities.map((e) => e.ENTITY_COMBO_KEY__c); - getRebateSubmissionsData(comboKeys, req) + getApplicationSubmissionsData(comboKeys, req) .then((submissions) => { return res.json({ samResults: true, samEntities, - rebateSubmissions: submissions, + applicationSubmissions: submissions, }); }) .catch((error) => { diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 12c8e075..7e73ef7a 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -182,12 +182,12 @@ function getComboKeys(email, req) { } /** - * Uses cached JSforce connection to query the BAP for rebate form submissions. + * Uses cached JSforce connection to query the BAP for application form submissions. * @param {string[]} comboKeys * @param {express.Request} req - * @returns {Promise} collection of rebate form submissions + * @returns {Promise} collection of application form submissions */ -function queryForRebateFormSubmissions(comboKeys, req) { +function queryForApplicationFormSubmissions(comboKeys, req) { /** @type {jsforce.Connection} */ const bapConnection = req.app.locals.bapConnection; return bapConnection @@ -218,18 +218,18 @@ function queryForRebateFormSubmissions(comboKeys, req) { } /** - * Fetches rebate form submissions associated with a provided set of combo keys. + * Fetches application form submissions associated with a provided set of combo keys. * @param {string[]} comboKeys * @param {express.Request} req */ -function getRebateSubmissionsData(comboKeys, req) { +function getApplicationSubmissionsData(comboKeys, req) { // Make sure BAP connection has been initialized if (!req.app.locals.bapConnection) { const message = `BAP Connection has not yet been initialized.`; log({ level: "info", message }); return setupConnection(req.app) - .then(() => queryForRebateFormSubmissions(comboKeys, req)) + .then(() => queryForApplicationFormSubmissions(comboKeys, req)) .catch((err) => { const message = `BAP Error: ${err}`; log({ level: "error", message, req }); @@ -237,7 +237,7 @@ function getRebateSubmissionsData(comboKeys, req) { }); } - return queryForRebateFormSubmissions(comboKeys, req).catch((err) => { + return queryForApplicationFormSubmissions(comboKeys, req).catch((err) => { if (err?.toString() === "invalid_grant: expired access/refresh token") { const message = `BAP access token expired`; log({ level: "info", message, req }); @@ -247,7 +247,7 @@ function getRebateSubmissionsData(comboKeys, req) { } return setupConnection(req.app) - .then(() => queryForRebateFormSubmissions(comboKeys, req)) + .then(() => queryForApplicationFormSubmissions(comboKeys, req)) .catch((retryErr) => { const message = `BAP Error: ${retryErr}`; log({ level: "error", message, req }); @@ -256,4 +256,4 @@ function getRebateSubmissionsData(comboKeys, req) { }); } -module.exports = { getSamData, getComboKeys, getRebateSubmissionsData }; +module.exports = { getSamData, getComboKeys, getApplicationSubmissionsData }; From c5ce375a53d0aa40d0036cafdd9e56296e2eb477 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 12:53:09 -0400 Subject: [PATCH 010/128] Update naming (and some text) of error messages to use 'application' instead of 'rebate' --- app/client/src/config.tsx | 8 ++++---- app/client/src/routes/allRebates.tsx | 4 ++-- app/client/src/routes/helpdesk.tsx | 10 ++++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/client/src/config.tsx b/app/client/src/config.tsx index f1838461..3fc949fe 100644 --- a/app/client/src/config.tsx +++ b/app/client/src/config.tsx @@ -58,11 +58,11 @@ export const messages = { "Error loading SAM.gov or rebate submission data. Please contact support.", noSamResults: "No SAM.gov records match your email. Only Government and Electronic Business SAM.gov Points of Contacts (and alternates) may edit and submit Clean School Bus Rebate Forms.", - rebateSubmissionsError: "Error loading rebate form submissions.", - newRebateApplication: + applicationSubmissionsError: "Error loading application form submissions.", + newApplication: "Please select the “New Application” button above to create your first rebate application.", - helpdeskRebateFormError: - "Error loading rebate form submission. Please confirm the form ID is correct and search again.", + helpdeskApplicationSubmissionError: + "Error loading application form submission. Please confirm the Application ID is correct and search again.", timeout: "For security reasons, you have been logged out due to 15 minutes of inactivity.", logout: "You have successfully logged out.", diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index d6d1b170..6e5bedf2 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -62,7 +62,7 @@ export default function AllRebates() { } if (rebateFormSubmissions.status === "failure") { - return ; + return ; } const { enrollmentClosed } = csbData.data; @@ -93,7 +93,7 @@ export default function AllRebates() { <> {submissions.length === 0 ? (
- +
) : ( <> diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 38ffb289..449f2d4c 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -162,7 +162,10 @@ export default function Helpdesk() { {rebateFormSubmission.status === "pending" && } {rebateFormSubmission.status === "failure" && ( - + )} {/* @@ -172,7 +175,10 @@ export default function Helpdesk() { */} {rebateFormSubmission.status === "success" && !rebateFormSubmission.data && ( - + )} {rebateFormSubmission.status === "success" && From c6c9c8ac3fe223445f6251298de7714d28a937d8 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 12:58:39 -0400 Subject: [PATCH 011/128] Rename existingRebate (and all its internal references) to existingApplication --- app/client/src/components/app.tsx | 4 +-- ...tingRebate.tsx => existingApplication.tsx} | 34 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) rename app/client/src/routes/{existingRebate.tsx => existingApplication.tsx} (95%) diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index ca0200f7..aac4e54e 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -27,7 +27,7 @@ import ConfirmationDialog from "components/confirmationDialog"; import Helpdesk from "routes/helpdesk"; import AllRebates from "routes/allRebates"; import NewRebate from "routes/newRebate"; -import ExistingRebate from "routes/existingRebate"; +import { ExistingApplication } from "routes/existingApplication"; import { useContentState, useContentDispatch } from "contexts/content"; import { useUserState, useUserDispatch } from "contexts/user"; import { useDialogDispatch, useDialogState } from "contexts/dialog"; @@ -289,7 +289,7 @@ export default function App() { */} } /> } /> - } /> + } /> } /> diff --git a/app/client/src/routes/existingRebate.tsx b/app/client/src/routes/existingApplication.tsx similarity index 95% rename from app/client/src/routes/existingRebate.tsx rename to app/client/src/routes/existingApplication.tsx index 655783f2..d641a55e 100644 --- a/app/client/src/routes/existingRebate.tsx +++ b/app/client/src/routes/existingApplication.tsx @@ -112,7 +112,7 @@ function reducer(state: State, action: Action): State { } } -function ExistingRebateProvider({ children }: Props) { +function ExistingApplicationProvider({ children }: Props) { const initialState: State = { displayed: false, type: "info", @@ -131,39 +131,37 @@ function ExistingRebateProvider({ children }: Props) { } /** - * Returns state stored in `ExistingRebateProvider` context component. + * Returns state stored in `ExistingApplicationProvider` context component. */ -function useExistingRebateState() { +function useApplicationRebateState() { const context = useContext(StateContext); if (context === undefined) { - throw new Error( - "useExistingRebateState must be called within a ExistingRebateProvider" - ); + const message = `useApplicationRebateState must be called within a ExistingApplicationProvider`; + throw new Error(message); } return context; } /** * Returns `dispatch` method for dispatching actions to update state stored in - * `ExistingRebateProvider` context component. + * `ExistingApplicationProvider` context component. */ -function useExistingRebateDispatch() { +function useExistingApplicationDispatch() { const context = useContext(DispatchContext); if (context === undefined) { - throw new Error( - "useExistingRebateDispatch must be used within a ExistingRebateProvider" - ); + const message = `useExistingApplicationDispatch must be used within a ExistingApplicationProvider`; + throw new Error(message); } return context; } // ----------------------------------------------------------------------------- -export default function ExistingRebate() { +export function ExistingApplication() { return ( - - - + + + ); } @@ -225,17 +223,17 @@ type SubmissionState = }; function FormMessage() { - const { displayed, type, text } = useExistingRebateState(); + const { displayed, type, text } = useApplicationRebateState(); if (!displayed) return null; return ; } -function ExistingRebateContent() { +function ExistingApplicationContent() { const navigate = useNavigate(); const { id } = useParams<"id">(); const { content } = useContentState(); const { csbData, epaUserData, bapUserData } = useUserState(); - const dispatch = useExistingRebateDispatch(); + const dispatch = useExistingApplicationDispatch(); const [rebateFormSubmission, setRebateFormSubmission] = useState({ From 5d5fcffd2df7879a0524a8faa4ae7bd8b6f7edad Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 13:02:32 -0400 Subject: [PATCH 012/128] Rename newRebate (and all its internal references) to newApplication --- app/client/src/components/app.tsx | 4 ++-- app/client/src/routes/{newRebate.tsx => newApplication.tsx} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename app/client/src/routes/{newRebate.tsx => newApplication.tsx} (98%) diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index aac4e54e..ba554378 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -26,7 +26,7 @@ import Dashboard from "components/dashboard"; import ConfirmationDialog from "components/confirmationDialog"; import Helpdesk from "routes/helpdesk"; import AllRebates from "routes/allRebates"; -import NewRebate from "routes/newRebate"; +import { NewApplication } from "routes/newApplication"; import { ExistingApplication } from "routes/existingApplication"; import { useContentState, useContentDispatch } from "contexts/content"; import { useUserState, useUserDispatch } from "contexts/user"; @@ -288,7 +288,7 @@ export default function App() { displayed. */} } /> - } /> + } /> } /> } /> diff --git a/app/client/src/routes/newRebate.tsx b/app/client/src/routes/newApplication.tsx similarity index 98% rename from app/client/src/routes/newRebate.tsx rename to app/client/src/routes/newApplication.tsx index 05d4cc4c..718aa1ed 100644 --- a/app/client/src/routes/newRebate.tsx +++ b/app/client/src/routes/newApplication.tsx @@ -12,7 +12,7 @@ import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { SamEntityData, useUserState } from "contexts/user"; -function createNewRebate(email: string, entity: SamEntityData) { +function createNewApplication(email: string, entity: SamEntityData) { const { title, name } = getUserInfo(email, entity); return fetchData(`${serverUrl}/api/application-form-submission/`, { @@ -38,7 +38,7 @@ function createNewRebate(email: string, entity: SamEntityData) { }); } -export default function NewRebate() { +export function NewApplication() { const navigate = useNavigate(); const { content } = useContentState(); const { csbData, epaUserData, bapUserData } = useUserState(); @@ -155,7 +155,7 @@ export default function NewRebate() { text: "Creating new rebate form application...", }); - createNewRebate(email, entity) + createNewApplication(email, entity) .then((res) => { navigate(`/rebate/${res._id}`); }) From 35acd68d14bb587e6ca732d639600c4c2f625de0 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 13:09:24 -0400 Subject: [PATCH 013/128] Update all default imports/exports to be named imports/exports, and move Welcome component into 'routes' directory --- app/client/src/components/app.tsx | 16 ++++++++-------- app/client/src/components/confirmationDialog.tsx | 2 +- app/client/src/components/dashboard.tsx | 4 ++-- app/client/src/components/errorBoundary.tsx | 4 ++-- app/client/src/components/loading.tsx | 2 +- app/client/src/components/markdownContent.tsx | 6 +----- app/client/src/components/message.tsx | 2 +- app/client/src/index.tsx | 4 ++-- app/client/src/routes/allRebates.tsx | 8 ++++---- app/client/src/routes/existingApplication.tsx | 6 +++--- app/client/src/routes/helpdesk.tsx | 8 ++++---- app/client/src/routes/newApplication.tsx | 6 +++--- .../src/{components => routes}/welcome.tsx | 4 ++-- 13 files changed, 34 insertions(+), 38 deletions(-) rename app/client/src/{components => routes}/welcome.tsx (97%) diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index ba554378..7f442ab0 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -19,13 +19,13 @@ import "@formio/premium/dist/premium.css"; import "formiojs/dist/formio.full.min.css"; // --- import { serverBasePath, serverUrl, cloudSpace, fetchData } from "../config"; -import Loading from "components/loading"; -import MarkdownContent from "components/markdownContent"; -import Welcome from "components/welcome"; -import Dashboard from "components/dashboard"; -import ConfirmationDialog from "components/confirmationDialog"; -import Helpdesk from "routes/helpdesk"; -import AllRebates from "routes/allRebates"; +import { Loading } from "components/loading"; +import { MarkdownContent } from "components/markdownContent"; +import { Welcome } from "routes/welcome"; +import { Dashboard } from "components/dashboard"; +import { ConfirmationDialog } from "components/confirmationDialog"; +import { Helpdesk } from "routes/helpdesk"; +import { AllRebates } from "routes/allRebates"; import { NewApplication } from "routes/newApplication"; import { ExistingApplication } from "routes/existingApplication"; import { useContentState, useContentDispatch } from "contexts/content"; @@ -257,7 +257,7 @@ function ProtectedRoute({ children }: { children: JSX.Element }) { ); } -export default function App() { +export function App() { useFetchedContent(); useSiteAlertBanner(); useDisclaimerBanner(); diff --git a/app/client/src/components/confirmationDialog.tsx b/app/client/src/components/confirmationDialog.tsx index 79929f12..76c4a49f 100644 --- a/app/client/src/components/confirmationDialog.tsx +++ b/app/client/src/components/confirmationDialog.tsx @@ -9,7 +9,7 @@ import icons from "uswds/img/sprite.svg"; // --- import { useDialogState, useDialogDispatch } from "contexts/dialog"; -export default function ConfirmationDialog() { +export function ConfirmationDialog() { const { dialogShown, dismissable, diff --git a/app/client/src/components/dashboard.tsx b/app/client/src/components/dashboard.tsx index 2799a438..bfed2057 100644 --- a/app/client/src/components/dashboard.tsx +++ b/app/client/src/components/dashboard.tsx @@ -13,7 +13,7 @@ import { fetchData, } from "../config"; import { useHelpdeskAccess } from "components/app"; -import Loading from "components/loading"; +import { Loading } from "components/loading"; import { useUserState, useUserDispatch } from "contexts/user"; import { Action, useDialogDispatch } from "contexts/dialog"; @@ -100,7 +100,7 @@ function IconText({ order, icon, text }: IconTextProps) { ); } -export default function Dashboard() { +export function Dashboard() { const { pathname } = useLocation(); const navigate = useNavigate(); diff --git a/app/client/src/components/errorBoundary.tsx b/app/client/src/components/errorBoundary.tsx index d0c91506..19bcb4ea 100644 --- a/app/client/src/components/errorBoundary.tsx +++ b/app/client/src/components/errorBoundary.tsx @@ -1,7 +1,7 @@ import { Component, ErrorInfo, ReactNode } from "react"; // --- import { messages } from "../config"; -import Message from "components/message"; +import { Message } from "components/message"; type Props = { children: ReactNode; @@ -11,7 +11,7 @@ type State = { hasError: boolean; }; -export default class ErrorBoundary extends Component { +export class ErrorBoundary extends Component { public state: State = { hasError: false, }; diff --git a/app/client/src/components/loading.tsx b/app/client/src/components/loading.tsx index 86901bba..50fb2443 100644 --- a/app/client/src/components/loading.tsx +++ b/app/client/src/components/loading.tsx @@ -2,7 +2,7 @@ // into app's `images/loader.svg` with namespace tags removed import loader from "images/loader.svg"; -export default function Loading() { +export function Loading() { return (
Loading... diff --git a/app/client/src/components/markdownContent.tsx b/app/client/src/components/markdownContent.tsx index bdcfa510..270a720a 100644 --- a/app/client/src/components/markdownContent.tsx +++ b/app/client/src/components/markdownContent.tsx @@ -14,11 +14,7 @@ type Props = { // to determine if an anchor link should open in a new tab – see note below). // Any additional elements beyond anchor links can be passed when to explicitly // set how those components should be rendered. -export default function MarkdownContent({ - className, - children, - components, -}: Props) { +export function MarkdownContent({ className, children, components }: Props) { return (
diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index 7ea8ae39..fc60312b 100644 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -6,8 +6,8 @@ import { ContentProvider } from "contexts/content"; import { UserProvider } from "contexts/user"; import { FormsProvider } from "contexts/forms"; import { DialogProvider } from "contexts/dialog"; -import ErrorBoundary from "components/errorBoundary"; -import App from "components/app"; +import { ErrorBoundary } from "components/errorBoundary"; +import { App } from "components/app"; import "./styles.css"; const container = document.getElementById("root") as HTMLElement; diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 6e5bedf2..9287190a 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -3,15 +3,15 @@ import { Link, useNavigate } from "react-router-dom"; import icons from "uswds/img/sprite.svg"; // --- import { serverUrl, fetchData, messages } from "../config"; -import Loading from "components/loading"; -import Message from "components/message"; -import MarkdownContent from "components/markdownContent"; +import { Loading } from "components/loading"; +import { Message } from "components/message"; +import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; import { useFormsState, useFormsDispatch } from "contexts/forms"; -export default function AllRebates() { +export function AllRebates() { const navigate = useNavigate(); const { content } = useContentState(); const { csbData, bapUserData } = useUserState(); diff --git a/app/client/src/routes/existingApplication.tsx b/app/client/src/routes/existingApplication.tsx index d641a55e..fb41e1dc 100644 --- a/app/client/src/routes/existingApplication.tsx +++ b/app/client/src/routes/existingApplication.tsx @@ -15,9 +15,9 @@ import icons from "uswds/img/sprite.svg"; // --- import { serverUrl, fetchData } from "../config"; import { getUserInfo } from "../utilities"; -import Loading from "components/loading"; -import Message from "components/message"; -import MarkdownContent from "components/markdownContent"; +import { Loading } from "components/loading"; +import { Message } from "components/message"; +import { MarkdownContent } from "components/markdownContent"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 449f2d4c..33e11fa0 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -6,9 +6,9 @@ import icons from "uswds/img/sprite.svg"; // --- import { serverUrl, messages, fetchData } from "../config"; import { useHelpdeskAccess } from "components/app"; -import Loading from "components/loading"; -import Message from "components/message"; -import MarkdownContent from "components/markdownContent"; +import { Loading } from "components/loading"; +import { Message } from "components/message"; +import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; @@ -54,7 +54,7 @@ type SubmissionState = }; }; -export default function Helpdesk() { +export function Helpdesk() { const navigate = useNavigate(); const [searchText, setSearchText] = useState(""); diff --git a/app/client/src/routes/newApplication.tsx b/app/client/src/routes/newApplication.tsx index 718aa1ed..bb25d771 100644 --- a/app/client/src/routes/newApplication.tsx +++ b/app/client/src/routes/newApplication.tsx @@ -5,9 +5,9 @@ import icons from "uswds/img/sprite.svg"; // --- import { serverUrl, messages, fetchData } from "../config"; import { getUserInfo } from "../utilities"; -import Loading from "components/loading"; -import Message from "components/message"; -import MarkdownContent from "components/markdownContent"; +import { Loading } from "components/loading"; +import { Message } from "components/message"; +import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { SamEntityData, useUserState } from "contexts/user"; diff --git a/app/client/src/components/welcome.tsx b/app/client/src/routes/welcome.tsx similarity index 97% rename from app/client/src/components/welcome.tsx rename to app/client/src/routes/welcome.tsx index 8392d7aa..864bd2fd 100644 --- a/app/client/src/components/welcome.tsx +++ b/app/client/src/routes/welcome.tsx @@ -3,9 +3,9 @@ import { useSearchParams } from "react-router-dom"; import icons from "uswds/img/sprite.svg"; // --- import { serverUrlForHrefs, messages } from "../config"; -import Message from "components/message"; +import { Message } from "components/message"; -export default function Welcome() { +export function Welcome() { const [searchParams, setSearchParams] = useSearchParams(); const [message, setMessage] = useState<{ From e9a24e9e30f4b434d0b449403361ebdfeec73ee6 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 14:13:07 -0400 Subject: [PATCH 014/128] Update all remaining references to 'rebate form' to 'application form' in client app --- app/client/src/contexts/forms.tsx | 32 +++++++------- app/client/src/contexts/user.tsx | 12 +++--- app/client/src/routes/allRebates.tsx | 38 ++++++++--------- app/client/src/routes/existingApplication.tsx | 42 ++++++++++--------- app/client/src/routes/helpdesk.tsx | 41 +++++++++--------- app/client/src/routes/newApplication.tsx | 12 +++--- app/client/src/utilities.tsx | 8 ++-- 7 files changed, 94 insertions(+), 91 deletions(-) diff --git a/app/client/src/contexts/forms.tsx b/app/client/src/contexts/forms.tsx index 3b919209..b90afe41 100644 --- a/app/client/src/contexts/forms.tsx +++ b/app/client/src/contexts/forms.tsx @@ -10,7 +10,7 @@ type Props = { children: ReactNode; }; -type RebateFormSubmission = { +type ApplicationFormSubmission = { [field: string]: unknown; _id: string; // MongoDB ObjectId string state: "submitted" | "draft"; @@ -27,53 +27,53 @@ type RebateFormSubmission = { }; type State = { - rebateFormSubmissions: + applicationFormSubmissions: | { status: "idle"; data: [] } | { status: "pending"; data: [] } - | { status: "success"; data: RebateFormSubmission[] } + | { status: "success"; data: ApplicationFormSubmission[] } | { status: "failure"; data: [] }; }; type Action = - | { type: "FETCH_REBATE_FORM_SUBMISSIONS_REQUEST" } + | { type: "FETCH_APPLICATION_FORM_SUBMISSIONS_REQUEST" } | { - type: "FETCH_REBATE_FORM_SUBMISSIONS_SUCCESS"; + type: "FETCH_APPLICATION_FORM_SUBMISSIONS_SUCCESS"; payload: { - rebateFormSubmissions: RebateFormSubmission[]; + applicationFormSubmissions: ApplicationFormSubmission[]; }; } - | { type: "FETCH_REBATE_FORM_SUBMISSIONS_FAILURE" }; + | { type: "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE" }; const StateContext = createContext(undefined); const DispatchContext = createContext | undefined>(undefined); function reducer(state: State, action: Action): State { switch (action.type) { - case "FETCH_REBATE_FORM_SUBMISSIONS_REQUEST": { + case "FETCH_APPLICATION_FORM_SUBMISSIONS_REQUEST": { return { ...state, - rebateFormSubmissions: { + applicationFormSubmissions: { status: "pending", data: [], }, }; } - case "FETCH_REBATE_FORM_SUBMISSIONS_SUCCESS": { - const { rebateFormSubmissions } = action.payload; + case "FETCH_APPLICATION_FORM_SUBMISSIONS_SUCCESS": { + const { applicationFormSubmissions } = action.payload; return { ...state, - rebateFormSubmissions: { + applicationFormSubmissions: { status: "success", - data: rebateFormSubmissions, + data: applicationFormSubmissions, }, }; } - case "FETCH_REBATE_FORM_SUBMISSIONS_FAILURE": { + case "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE": { return { ...state, - rebateFormSubmissions: { + applicationFormSubmissions: { status: "failure", data: [], }, @@ -88,7 +88,7 @@ function reducer(state: State, action: Action): State { export function FormsProvider({ children }: Props) { const initialState: State = { - rebateFormSubmissions: { + applicationFormSubmissions: { status: "idle", data: [], }, diff --git a/app/client/src/contexts/user.tsx b/app/client/src/contexts/user.tsx index aa26e6fe..8fac0a2e 100644 --- a/app/client/src/contexts/user.tsx +++ b/app/client/src/contexts/user.tsx @@ -20,7 +20,7 @@ type EpaUserData = { exp: number; }; -export type SamEntityData = { +export type SamEntity = { ENTITY_COMBO_KEY__c: string; UNIQUE_ENTITY_ID__c: string; ENTITY_EFT_INDICATOR__c: string; @@ -53,7 +53,7 @@ export type SamEntityData = { attributes: { type: string; url: string }; }; -type BapSubmissionData = { +type ApplicationFormSubmissionMetadata = { CSB_Form_ID__c: string; // MongoDB ObjectId string CSB_Modified_Full_String__c: string; // ISO 8601 date string UEI_EFTI_Combo_Key__c: string; @@ -95,8 +95,8 @@ type State = { } | { samResults: true; - samEntities: SamEntityData[]; - applicationSubmissions: BapSubmissionData[]; + samEntities: SamEntity[]; + applicationSubmissions: ApplicationFormSubmissionMetadata[]; }; } | { status: "failure"; data: {} }; @@ -129,8 +129,8 @@ type Action = } | { samResults: true; - samEntities: SamEntityData[]; - applicationSubmissions: BapSubmissionData[]; + samEntities: SamEntity[]; + applicationSubmissions: ApplicationFormSubmissionMetadata[]; }; }; } diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 9287190a..1f5e34d9 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -15,7 +15,7 @@ export function AllRebates() { const navigate = useNavigate(); const { content } = useContentState(); const { csbData, bapUserData } = useUserState(); - const { rebateFormSubmissions } = useFormsState(); + const { applicationFormSubmissions } = useFormsState(); const dispatch = useFormsDispatch(); const [message, setMessage] = useState<{ @@ -33,17 +33,17 @@ export function AllRebates() { return; } - dispatch({ type: "FETCH_REBATE_FORM_SUBMISSIONS_REQUEST" }); + dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_REQUEST" }); fetchData(`${serverUrl}/api/application-form-submissions`) .then((res) => { dispatch({ - type: "FETCH_REBATE_FORM_SUBMISSIONS_SUCCESS", - payload: { rebateFormSubmissions: res }, + type: "FETCH_APPLICATION_FORM_SUBMISSIONS_SUCCESS", + payload: { applicationFormSubmissions: res }, }); }) .catch((err) => { - dispatch({ type: "FETCH_REBATE_FORM_SUBMISSIONS_FAILURE" }); + dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE" }); }); }, [bapUserData, dispatch]); @@ -51,8 +51,8 @@ export function AllRebates() { csbData.status !== "success" || bapUserData.status === "idle" || bapUserData.status === "pending" || - rebateFormSubmissions.status === "idle" || - rebateFormSubmissions.status === "pending" + applicationFormSubmissions.status === "idle" || + applicationFormSubmissions.status === "pending" ) { return ; } @@ -61,29 +61,29 @@ export function AllRebates() { return ; } - if (rebateFormSubmissions.status === "failure") { + if (applicationFormSubmissions.status === "failure") { return ; } const { enrollmentClosed } = csbData.data; /** - * Formio submissions, merged with rebate submissions returned from the BAP, - * so we can include CSB rebate status, CSB review item ID, and last updated - * datetime. + * Formio application submissions, merged with submissions returned from the + * BAP, so we can include CSB rebate status, CSB review item ID, and last + * updated datetime. */ - const submissions = rebateFormSubmissions.data.map((formioSubmission) => { - const matchedBapSubmission = bapUserData.data.applicationSubmissions.find( - (bapSubmission) => bapSubmission.CSB_Form_ID__c === formioSubmission._id + const submissions = applicationFormSubmissions.data.map((submission) => { + const matchedBapMetadata = bapUserData.data.applicationSubmissions.find( + (bapSubmission) => bapSubmission.CSB_Form_ID__c === submission._id ); return { - ...formioSubmission, + ...submission, bap: { - lastModified: matchedBapSubmission?.CSB_Modified_Full_String__c || null, - rebateId: matchedBapSubmission?.Parent_Rebate_ID__c || null, + lastModified: matchedBapMetadata?.CSB_Modified_Full_String__c || null, + rebateId: matchedBapMetadata?.Parent_Rebate_ID__c || null, rebateStatus: - matchedBapSubmission?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || + matchedBapMetadata?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null, }, }; @@ -371,7 +371,7 @@ form for the fields to be displayed. */
{ /* NOTE: -The initial version of the rebate form definition included the `applicantEfti` +Initial version of the application form definition included the `applicantEfti` field, which is configured via the form definition (in formio/forms.gov) to set its value based on the value of the `sam_hidden_applicant_efti` field, which we inject on initial form submission. That value comes from the BAP (SAM.gov data), diff --git a/app/client/src/routes/existingApplication.tsx b/app/client/src/routes/existingApplication.tsx index fb41e1dc..c49c3111 100644 --- a/app/client/src/routes/existingApplication.tsx +++ b/app/client/src/routes/existingApplication.tsx @@ -133,10 +133,10 @@ function ExistingApplicationProvider({ children }: Props) { /** * Returns state stored in `ExistingApplicationProvider` context component. */ -function useApplicationRebateState() { +function useExistingApplicationState() { const context = useContext(StateContext); if (context === undefined) { - const message = `useApplicationRebateState must be called within a ExistingApplicationProvider`; + const message = `useExistingApplicationState must be called within a ExistingApplicationProvider`; throw new Error(message); } return context; @@ -223,7 +223,7 @@ type SubmissionState = }; function FormMessage() { - const { displayed, type, text } = useApplicationRebateState(); + const { displayed, type, text } = useExistingApplicationState(); if (!displayed) return null; return ; } @@ -235,7 +235,7 @@ function ExistingApplicationContent() { const { csbData, epaUserData, bapUserData } = useUserState(); const dispatch = useExistingApplicationDispatch(); - const [rebateFormSubmission, setRebateFormSubmission] = + const [applicationFormSubmission, setApplicationFormSubmission] = useState({ status: "idle", data: { @@ -245,8 +245,9 @@ function ExistingApplicationContent() { }, }); - // set when rebate form submission data is initially fetched, and then re-set - // each time a successful update of the submission data is posted to forms.gov + // set when application form submission data is initially fetched, and then + // re-set each time a successful update of the submission data is posted to + // forms.gov const [storedSubmissionData, setStoredSubmissionData] = useState({}); @@ -262,7 +263,7 @@ function ExistingApplicationContent() { useState({}); useEffect(() => { - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "pending", data: { userAccess: false, @@ -273,7 +274,7 @@ function ExistingApplicationContent() { fetchData(`${serverUrl}/api/application-form-submission/${id}`) .then((res) => { - // Set up s3 re-route to wrapper app + // set up s3 re-route to wrapper app const s3Provider = Formio.Providers.providers.storage.s3; Formio.Providers.providers.storage.s3 = function (formio: any) { const s3Formio = cloneDeep(formio); @@ -296,13 +297,13 @@ function ExistingApplicationContent() { return data; }); - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "success", data: res, }); }) .catch((err) => { - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "failure", data: { userAccess: false, @@ -313,18 +314,19 @@ function ExistingApplicationContent() { }); }, [id]); - if (rebateFormSubmission.status === "idle") { + if (applicationFormSubmission.status === "idle") { return null; } - if (rebateFormSubmission.status === "pending") { + if (applicationFormSubmission.status === "pending") { return ; } - const { userAccess, formSchema, submissionData } = rebateFormSubmission.data; + const { userAccess, formSchema, submissionData } = + applicationFormSubmission.data; if ( - rebateFormSubmission.status === "failure" || + applicationFormSubmission.status === "failure" || !userAccess || !formSchema || !submissionData @@ -347,15 +349,15 @@ function ExistingApplicationContent() { const { enrollmentClosed } = csbData.data; - const matchedBapSubmission = bapUserData.data.applicationSubmissions.find( + const matchedBapMetadata = bapUserData.data.applicationSubmissions.find( (bapSubmission) => bapSubmission.CSB_Form_ID__c === id ); const bap = { - lastModified: matchedBapSubmission?.CSB_Modified_Full_String__c || null, - rebateId: matchedBapSubmission?.Parent_Rebate_ID__c || null, + lastModified: matchedBapMetadata?.CSB_Modified_Full_String__c || null, + rebateId: matchedBapMetadata?.Parent_Rebate_ID__c || null, rebateStatus: - matchedBapSubmission?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null, + matchedBapMetadata?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null, }; const submissionNeedsEdits = bap.rebateStatus === "Edits Requested"; @@ -541,7 +543,7 @@ function ExistingApplicationContent() { .catch((err) => { dispatch({ type: "DISPLAY_ERROR_MESSAGE", - payload: { text: "Error submitting rebate form." }, + payload: { text: "Error submitting application form." }, }); }); }} @@ -611,7 +613,7 @@ function ExistingApplicationContent() { .catch((err) => { dispatch({ type: "DISPLAY_ERROR_MESSAGE", - payload: { text: "Error saving draft rebate form." }, + payload: { text: "Error saving draft application form." }, }); }); }} diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 33e11fa0..298ce266 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -66,7 +66,7 @@ export function Helpdesk() { const dispatch = useDialogDispatch(); const helpdeskAccess = useHelpdeskAccess(); - const [rebateFormSubmission, setRebateFormSubmission] = + const [applicationFormSubmission, setApplicationFormSubmission] = useState({ status: "idle", data: { @@ -90,7 +90,7 @@ export function Helpdesk() { const { enrollmentClosed } = csbData.data; - const { formSchema, submissionData } = rebateFormSubmission.data; + const { formSchema, submissionData } = applicationFormSubmission.data; return ( <> @@ -111,7 +111,7 @@ export function Helpdesk() { setFormId(""); setFormDisplayed(false); - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "pending", data: { formSchema: null, @@ -124,14 +124,14 @@ export function Helpdesk() { ) .then((res) => { setFormId(res.submissionData._id); - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "success", data: res, }); }) .catch((err) => { setFormId(""); - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "failure", data: { formSchema: null, @@ -141,11 +141,11 @@ export function Helpdesk() { }); }} > -
- {rebateFormSubmission.status === "pending" && } + {applicationFormSubmission.status === "pending" && } - {rebateFormSubmission.status === "failure" && ( + {applicationFormSubmission.status === "failure" && ( )} - {rebateFormSubmission.status === "success" && + {applicationFormSubmission.status === "success" && formSchema && submissionData && ( <>
@@ -280,7 +281,7 @@ export function Helpdesk() { confirmedAction: () => { setFormDisplayed(false); - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "pending", data: { formSchema: null, @@ -293,13 +294,13 @@ export function Helpdesk() { {} ) .then((res) => { - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "success", data: res, }); }) .catch((err) => { - setRebateFormSubmission({ + setApplicationFormSubmission({ status: "failure", data: { formSchema: null, diff --git a/app/client/src/routes/newApplication.tsx b/app/client/src/routes/newApplication.tsx index bb25d771..fc46d137 100644 --- a/app/client/src/routes/newApplication.tsx +++ b/app/client/src/routes/newApplication.tsx @@ -10,9 +10,9 @@ import { Message } from "components/message"; import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; -import { SamEntityData, useUserState } from "contexts/user"; +import { SamEntity, useUserState } from "contexts/user"; -function createNewApplication(email: string, entity: SamEntityData) { +function createNewApplication(email: string, entity: SamEntity) { const { title, name } = getUserInfo(email, entity); return fetchData(`${serverUrl}/api/application-form-submission/`, { @@ -72,8 +72,8 @@ export function NewApplication() { navigate("/")}>
@@ -90,7 +90,7 @@ export function NewApplication() { components={{ h2: (props) => (

{props.children} @@ -98,7 +98,7 @@ export function NewApplication() { ), p: (props) => (

{props.children} diff --git a/app/client/src/utilities.tsx b/app/client/src/utilities.tsx index e0e4ad82..a489c217 100644 --- a/app/client/src/utilities.tsx +++ b/app/client/src/utilities.tsx @@ -1,10 +1,10 @@ -import { SamEntityData } from "contexts/user"; +import { SamEntity } from "contexts/user"; /** * Returns a user’s title and name when provided an email address and a SAM.gov * entity/record. */ -export function getUserInfo(email: string, entity: SamEntityData) { +export function getUserInfo(email: string, entity: SamEntity) { const samEmailFields = [ "ELEC_BUS_POC_EMAIL__c", "ALT_ELEC_BUS_POC_EMAIL__c", @@ -30,7 +30,7 @@ export function getUserInfo(email: string, entity: SamEntityData) { const fieldPrefix = matchedEmailField?.split("_EMAIL__c").shift(); return { - title: entity[`${fieldPrefix}_TITLE__c` as keyof SamEntityData] as string, - name: entity[`${fieldPrefix}_NAME__c` as keyof SamEntityData] as string, + title: entity[`${fieldPrefix}_TITLE__c` as keyof SamEntity] as string, + name: entity[`${fieldPrefix}_NAME__c` as keyof SamEntity] as string, }; } From 09473067d32a062ccd36280b1dbda7a85813ca14 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 14:19:12 -0400 Subject: [PATCH 015/128] Update formatting of message passed to Error constructor throughout app to be consistent --- app/client/src/config.tsx | 10 ++++------ app/client/src/contexts/content.tsx | 9 ++++++--- app/client/src/contexts/dialog.tsx | 9 ++++++--- app/client/src/contexts/forms.tsx | 9 ++++++--- app/client/src/contexts/user.tsx | 9 ++++++--- app/client/src/routes/existingApplication.tsx | 3 ++- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/app/client/src/config.tsx b/app/client/src/config.tsx index 3fc949fe..5de84cf5 100644 --- a/app/client/src/config.tsx +++ b/app/client/src/config.tsx @@ -7,15 +7,13 @@ const { } = process.env; if (!REACT_APP_FORMIO_BASE_URL) { - throw new Error( - "Required REACT_APP_FORMIO_BASE_URL environment variable not found." - ); + const message = `Required REACT_APP_FORMIO_BASE_URL environment variable not found.`; + throw new Error(message); } if (!REACT_APP_FORMIO_PROJECT_NAME) { - throw new Error( - "Required REACT_APP_FORMIO_PROJECT_NAME environment variable not found." - ); + const message = `Required REACT_APP_FORMIO_PROJECT_NAME environment variable not found.`; + throw new Error(message); } // allows the app to be accessed from a sub directory of a server (e.g. /csb) diff --git a/app/client/src/contexts/content.tsx b/app/client/src/contexts/content.tsx index 59af2d31..82a7cd8f 100644 --- a/app/client/src/contexts/content.tsx +++ b/app/client/src/contexts/content.tsx @@ -90,7 +90,8 @@ function reducer(state: State, action: Action): State { } default: { - throw new Error(`Unhandled action type: ${action}`); + const message = `Unhandled action type: ${action}`; + throw new Error(message); } } } @@ -120,7 +121,8 @@ export function ContentProvider({ children }: Props) { export function useContentState() { const context = useContext(StateContext); if (context === undefined) { - throw new Error("useContentState must be called within a ContentProvider"); + const message = `useContentState must be called within a ContentProvider`; + throw new Error(message); } return context; } @@ -132,7 +134,8 @@ export function useContentState() { export function useContentDispatch() { const context = useContext(DispatchContext); if (context === undefined) { - throw new Error("useContentDispatch must be used within a ContentProvider"); + const message = `useContentDispatch must be used within a ContentProvider`; + throw new Error(message); } return context; } diff --git a/app/client/src/contexts/dialog.tsx b/app/client/src/contexts/dialog.tsx index 94992fb3..33045895 100644 --- a/app/client/src/contexts/dialog.tsx +++ b/app/client/src/contexts/dialog.tsx @@ -87,7 +87,8 @@ function reducer(state: State, action: Action): State { } default: { - throw new Error(`Unhandled action type: ${action}`); + const message = `Unhandled action type: ${action}`; + throw new Error(message); } } } @@ -120,7 +121,8 @@ export function DialogProvider({ children }: Props) { export function useDialogState() { const context = useContext(StateContext); if (context === undefined) { - throw new Error("useDialogState must be called within a DialogProvider"); + const message = `useDialogState must be called within a DialogProvider`; + throw new Error(message); } return context; } @@ -132,7 +134,8 @@ export function useDialogState() { export function useDialogDispatch() { const context = useContext(DispatchContext); if (context === undefined) { - throw new Error("useDialogDispatch must be used within a DialogProvider"); + const message = `useDialogDispatch must be used within a DialogProvider`; + throw new Error(message); } return context; } diff --git a/app/client/src/contexts/forms.tsx b/app/client/src/contexts/forms.tsx index b90afe41..2e5e445e 100644 --- a/app/client/src/contexts/forms.tsx +++ b/app/client/src/contexts/forms.tsx @@ -81,7 +81,8 @@ function reducer(state: State, action: Action): State { } default: { - throw new Error(`Unhandled action type: ${action}`); + const message = `Unhandled action type: ${action}`; + throw new Error(message); } } } @@ -111,7 +112,8 @@ export function FormsProvider({ children }: Props) { export function useFormsState() { const context = useContext(StateContext); if (context === undefined) { - throw new Error("useFormsState must be called within a FormsProvider"); + const message = `useFormsState must be called within a FormsProvider`; + throw new Error(message); } return context; } @@ -123,7 +125,8 @@ export function useFormsState() { export function useFormsDispatch() { const context = useContext(DispatchContext); if (context === undefined) { - throw new Error("useFormsDispatch must be used within a FormsProvider"); + const message = `useFormsDispatch must be used within a FormsProvider`; + throw new Error(message); } return context; } diff --git a/app/client/src/contexts/user.tsx b/app/client/src/contexts/user.tsx index 8fac0a2e..f33585d7 100644 --- a/app/client/src/contexts/user.tsx +++ b/app/client/src/contexts/user.tsx @@ -263,7 +263,8 @@ function reducer(state: State, action: Action): State { } default: { - throw new Error(`Unhandled action type: ${action}`); + const message = `Unhandled action type: ${action}`; + throw new Error(message); } } } @@ -303,7 +304,8 @@ export function UserProvider({ children }: Props) { export function useUserState() { const context = useContext(StateContext); if (context === undefined) { - throw new Error("useUserState must be called within a UserProvider"); + const message = `useUserState must be called within a UserProvider`; + throw new Error(message); } return context; } @@ -315,7 +317,8 @@ export function useUserState() { export function useUserDispatch() { const context = useContext(DispatchContext); if (context === undefined) { - throw new Error("useUserDispatch must be used within a UserProvider"); + const message = `useUserDispatch must be used within a UserProvider`; + throw new Error(message); } return context; } diff --git a/app/client/src/routes/existingApplication.tsx b/app/client/src/routes/existingApplication.tsx index c49c3111..91a677c7 100644 --- a/app/client/src/routes/existingApplication.tsx +++ b/app/client/src/routes/existingApplication.tsx @@ -107,7 +107,8 @@ function reducer(state: State, action: Action): State { } default: { - throw new Error(`Unhandled action type: ${action}`); + const message = `Unhandled action type: ${action}`; + throw new Error(message); } } } From aa2f2fac5bf217b79349028fd482db81f127a2f5 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 16:07:06 -0400 Subject: [PATCH 016/128] Add new payment request button to submitted rebate applications in all rebates table, and update table striping to apply to each group of rebate forms submissions (and not each row) --- app/client/src/routes/allRebates.tsx | 72 +++++++++++++++++++++------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 1f5e34d9..9e47a8fc 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -111,7 +111,7 @@ export function AllRebates() {

@@ -177,7 +177,7 @@ export function AllRebates() { - {submissions.map((submission) => { + {submissions.map((submission, index) => { const { bap, _id, state, modified, data } = submission; const { applicantUEI, @@ -193,7 +193,7 @@ export function AllRebates() { /** * The submission has been updated since the last time the - * BAP's submissions ETL process has succesfully run. + * BAP's submissions ETL process has last succesfully run. */ const submissionHasBeenUpdated = bap.lastModified ? new Date(modified) > new Date(bap.lastModified) @@ -207,14 +207,29 @@ export function AllRebates() { const submissionHasBeenWithdrawn = bap.rebateStatus === "Withdrawn"; - const statusClassNames = submissionNeedsEdits + const statusStyles = submissionNeedsEdits ? "csb-needs-edits" : enrollmentClosed || state === "submitted" - ? "text-italic text-base-dark" + ? "text-italic" : ""; + /** + * Apply USWDS `usa-table--striped` styles to each rebate, + * which can include up to three rows – one for each of the + * forms: Application, Purchase Order, and Close-Out. + */ + const rebateStyles = index % 2 ? "bg-white" : "bg-gray-5"; + + /** + * Use the BAP Rebate ID if it exists, but fall back to the + * Formio submission ID for cases where the BAP Rebate ID has + * not yet been created (e.g. the form is brand new and the + * BAP's ETL process has not yet run to pick up the new form). + */ + const uniqueId = bap.rebateId || _id; + /* NOTE: when a form is first initially created, and the user -has not yet clicked the "Next" or "Save" buttons, any fields that the formio +has not yet clicked the "Next" or "Save" buttons, any fields that the Formio form definition sets automatically (based on hidden fields we inject on form creation) will not yet be part of the form submission data. As soon as the user clicks the "Next" or "Save" buttons the first time, those fields will be set and @@ -225,9 +240,9 @@ believe is a bit of an edge case, as most users will likely do that after starting a new application), indicate to the user they need to first save the form for the fields to be displayed. */ return ( - - - + - - - - + + {state === "submitted" && ( + + + + )} ); })} From 4ed70b8101b949089482ea4c48fd4ca4d58a7164 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 17:24:25 -0400 Subject: [PATCH 017/128] Update allRebates code to include submissionHasBeenAccepted variable, for easy setting once BAP rebate status returns that value --- app/client/src/routes/allRebates.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 9e47a8fc..77bde619 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -207,6 +207,10 @@ export function AllRebates() { const submissionHasBeenWithdrawn = bap.rebateStatus === "Withdrawn"; + // TODO: update to use the accepted BAP rebate status, + // once it's being returned from the BAP + const submissionHasBeenAccepted = state === "submitted"; + const statusStyles = submissionNeedsEdits ? "csb-needs-edits" : enrollmentClosed || state === "submitted" @@ -465,12 +469,14 @@ save the form for the EFT indicator to be displayed. */ - {state === "submitted" && ( + {submissionHasBeenAccepted && ( )} From 50c3824ec9d05124894c4b848140eb33059ea862 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 17:43:11 -0400 Subject: [PATCH 018/128] Move fetching of application form submissions into a custom hook (called in AllRebates component) and create new custom hook for fetching Payment Request form submissions, and store results in forms context component --- app/client/src/config.tsx | 3 +- app/client/src/contexts/forms.tsx | 61 ++++++++++++++++++++++- app/client/src/routes/allRebates.tsx | 72 +++++++++++++++++++++------- 3 files changed, 118 insertions(+), 18 deletions(-) diff --git a/app/client/src/config.tsx b/app/client/src/config.tsx index 5de84cf5..fc894b9d 100644 --- a/app/client/src/config.tsx +++ b/app/client/src/config.tsx @@ -56,7 +56,8 @@ export const messages = { "Error loading SAM.gov or rebate submission data. Please contact support.", noSamResults: "No SAM.gov records match your email. Only Government and Electronic Business SAM.gov Points of Contacts (and alternates) may edit and submit Clean School Bus Rebate Forms.", - applicationSubmissionsError: "Error loading application form submissions.", + applicationSubmissionsError: "Error loading Application form submissions.", + paymentSubmissionsError: "Error loading Payment Request form submissions.", newApplication: "Please select the “New Application” button above to create your first rebate application.", helpdeskApplicationSubmissionError: diff --git a/app/client/src/contexts/forms.tsx b/app/client/src/contexts/forms.tsx index 2e5e445e..0bc13100 100644 --- a/app/client/src/contexts/forms.tsx +++ b/app/client/src/contexts/forms.tsx @@ -26,12 +26,27 @@ type ApplicationFormSubmission = { }; }; +type PaymentFormSubmission = { + [field: string]: unknown; + _id: string; // MongoDB ObjectId string + state: "submitted" | "draft"; + modified: string; // ISO 8601 date string + data: { + [field: string]: unknown; + }; +}; + type State = { applicationFormSubmissions: | { status: "idle"; data: [] } | { status: "pending"; data: [] } | { status: "success"; data: ApplicationFormSubmission[] } | { status: "failure"; data: [] }; + paymentFormSubmissions: + | { status: "idle"; data: [] } + | { status: "pending"; data: [] } + | { status: "success"; data: PaymentFormSubmission[] } + | { status: "failure"; data: [] }; }; type Action = @@ -42,7 +57,16 @@ type Action = applicationFormSubmissions: ApplicationFormSubmission[]; }; } - | { type: "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE" }; + | { type: "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE" } + | { type: "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST" } + | { type: "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST" } + | { + type: "FETCH_PAYMENT_FORM_SUBMISSIONS_SUCCESS"; + payload: { + paymentFormSubmissions: PaymentFormSubmission[]; + }; + } + | { type: "FETCH_PAYMENT_FORM_SUBMISSIONS_FAILURE" }; const StateContext = createContext(undefined); const DispatchContext = createContext | undefined>(undefined); @@ -80,6 +104,37 @@ function reducer(state: State, action: Action): State { }; } + case "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST": { + return { + ...state, + paymentFormSubmissions: { + status: "pending", + data: [], + }, + }; + } + + case "FETCH_PAYMENT_FORM_SUBMISSIONS_SUCCESS": { + const { paymentFormSubmissions } = action.payload; + return { + ...state, + paymentFormSubmissions: { + status: "success", + data: paymentFormSubmissions, + }, + }; + } + + case "FETCH_PAYMENT_FORM_SUBMISSIONS_FAILURE": { + return { + ...state, + paymentFormSubmissions: { + status: "failure", + data: [], + }, + }; + } + default: { const message = `Unhandled action type: ${action}`; throw new Error(message); @@ -93,6 +148,10 @@ export function FormsProvider({ children }: Props) { status: "idle", data: [], }, + paymentFormSubmissions: { + status: "idle", + data: [], + }, }; const [state, dispatch] = useReducer(reducer, initialState); diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 77bde619..32826495 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -11,23 +11,11 @@ import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; import { useFormsState, useFormsDispatch } from "contexts/forms"; -export function AllRebates() { - const navigate = useNavigate(); - const { content } = useContentState(); - const { csbData, bapUserData } = useUserState(); - const { applicationFormSubmissions } = useFormsState(); +/** Custom hook to fetch Application form submissions */ +function useFetchedApplicationFormSubmissions() { + const { bapUserData } = useUserState(); const dispatch = useFormsDispatch(); - const [message, setMessage] = useState<{ - displayed: boolean; - type: "info" | "success" | "warning" | "error"; - text: string; - }>({ - displayed: false, - type: "info", - text: "", - }); - useEffect(() => { if (bapUserData.status !== "success" || !bapUserData.data.samResults) { return; @@ -46,13 +34,61 @@ export function AllRebates() { dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE" }); }); }, [bapUserData, dispatch]); +} + +/** Custom hook to fetch Payment Request form submissions */ +function useFetchedPaymentFormSubmissions() { + const { bapUserData } = useUserState(); + const dispatch = useFormsDispatch(); + + useEffect(() => { + if (bapUserData.status !== "success" || !bapUserData.data.samResults) { + return; + } + + dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST" }); + + fetchData(`${serverUrl}/api/payment-form-submissions`) + .then((res) => { + dispatch({ + type: "FETCH_PAYMENT_FORM_SUBMISSIONS_SUCCESS", + payload: { paymentFormSubmissions: res }, + }); + }) + .catch((err) => { + dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_FAILURE" }); + }); + }, [bapUserData, dispatch]); +} + +export function AllRebates() { + const navigate = useNavigate(); + const { content } = useContentState(); + const { csbData, bapUserData } = useUserState(); + const { applicationFormSubmissions, paymentFormSubmissions } = + useFormsState(); + + const [message, setMessage] = useState<{ + displayed: boolean; + type: "info" | "success" | "warning" | "error"; + text: string; + }>({ + displayed: false, + type: "info", + text: "", + }); + + useFetchedApplicationFormSubmissions(); + useFetchedPaymentFormSubmissions(); if ( csbData.status !== "success" || bapUserData.status === "idle" || bapUserData.status === "pending" || applicationFormSubmissions.status === "idle" || - applicationFormSubmissions.status === "pending" + applicationFormSubmissions.status === "pending" || + paymentFormSubmissions.status === "idle" || + paymentFormSubmissions.status === "pending" ) { return ; } @@ -65,6 +101,10 @@ export function AllRebates() { return ; } + if (paymentFormSubmissions.status === "failure") { + return ; + } + const { enrollmentClosed } = csbData.data; /** From d0d35e1a081971a90b291d2f8e558339d43307cd Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 17:44:03 -0400 Subject: [PATCH 019/128] Update comment syntax above custom hooks so they're shown in editor intellisense --- app/client/src/components/app.tsx | 10 +++++----- app/client/src/components/dashboard.tsx | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index 7f442ab0..71745045 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -32,7 +32,7 @@ import { useContentState, useContentDispatch } from "contexts/content"; import { useUserState, useUserDispatch } from "contexts/user"; import { useDialogDispatch, useDialogState } from "contexts/dialog"; -// Custom hook to fetch static content +/** Custom hook to fetch static content */ function useFetchedContent() { const dispatch = useContentDispatch(); @@ -51,7 +51,7 @@ function useFetchedContent() { }, [dispatch]); } -// Custom hook to display a site-wide alert banner +/** Custom hook to display a site-wide alert banner */ function useSiteAlertBanner() { const { content } = useContentState(); @@ -89,7 +89,7 @@ function useSiteAlertBanner() { }, [content]); } -// Custom hook to display the CSB disclaimer banner for development/staging +/** Custom hook to display the CSB disclaimer banner for development/staging */ function useDisclaimerBanner() { useEffect(() => { if (!(cloudSpace === "dev" || cloudSpace === "staging")) return; @@ -112,7 +112,7 @@ function useDisclaimerBanner() { }, []); } -// Custom hook to set up inactivity timer to auto-logout user if they're inactive for >15 minutes +/** Custom hook to set up inactivity timer to auto-logout user if they're inactive for >15 minutes */ function useInactivityDialog(callback: () => void) { const { epaUserData } = useUserState(); const { dialogShown, heading } = useDialogState(); @@ -196,7 +196,7 @@ function useInactivityDialog(callback: () => void) { }, [dialogShown, heading, logoutTimer, dispatch]); } -// Custom hook to check if user should have access to helpdesk pages +/** Custom hook to check if user should have access to helpdesk pages */ export function useHelpdeskAccess() { const [helpdeskAccess, setHelpdeskAccess] = useState< "idle" | "pending" | "success" | "failure" diff --git a/app/client/src/components/dashboard.tsx b/app/client/src/components/dashboard.tsx index bfed2057..6f4db289 100644 --- a/app/client/src/components/dashboard.tsx +++ b/app/client/src/components/dashboard.tsx @@ -22,7 +22,7 @@ Formio.setProjectUrl(formioProjectUrl); Formio.use(premium); Formio.use(uswds); -// Custom hook to fetch CSP app specific data +/** Custom hook to fetch CSP app specific data */ function useFetchedCsbData() { const dispatch = useUserDispatch(); @@ -41,7 +41,7 @@ function useFetchedCsbData() { }, [dispatch]); } -// Custom hook to fetch BAP data +/** Custom hook to fetch BAP data */ function useFetchedBapData() { const dispatch = useUserDispatch(); From 37a75f2b6466c4d6be5fa32ef1c38cb2e6769c3e Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 31 Aug 2022 17:46:22 -0400 Subject: [PATCH 020/128] Temporarily set submissionHasBeenAccepted variable to explicitly be false until BAP returns the accepted status for submissions --- app/client/src/routes/allRebates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 32826495..9a34fa6a 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -249,7 +249,7 @@ export function AllRebates() { // TODO: update to use the accepted BAP rebate status, // once it's being returned from the BAP - const submissionHasBeenAccepted = state === "submitted"; + const submissionHasBeenAccepted = false; const statusStyles = submissionNeedsEdits ? "csb-needs-edits" From 0bdf8fe44cde1270d42613e7c2db3daf19210769 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 1 Sep 2022 11:39:45 -0400 Subject: [PATCH 021/128] Rename existingApplication and newApplication components --- app/client/src/components/app.tsx | 8 +++--- ...ingApplication.tsx => applicationForm.tsx} | 28 +++++++++---------- ...Application.tsx => newApplicationForm.tsx} | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) rename app/client/src/routes/{existingApplication.tsx => applicationForm.tsx} (95%) rename app/client/src/routes/{newApplication.tsx => newApplicationForm.tsx} (99%) diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index 71745045..bfd7af4e 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -26,8 +26,8 @@ import { Dashboard } from "components/dashboard"; import { ConfirmationDialog } from "components/confirmationDialog"; import { Helpdesk } from "routes/helpdesk"; import { AllRebates } from "routes/allRebates"; -import { NewApplication } from "routes/newApplication"; -import { ExistingApplication } from "routes/existingApplication"; +import { NewApplicationForm } from "routes/newApplicationForm"; +import { ApplicationForm } from "routes/applicationForm"; import { useContentState, useContentDispatch } from "contexts/content"; import { useUserState, useUserDispatch } from "contexts/user"; import { useDialogDispatch, useDialogState } from "contexts/dialog"; @@ -288,8 +288,8 @@ export function App() { displayed. */} } /> - } /> - } /> + } /> + } /> } /> diff --git a/app/client/src/routes/existingApplication.tsx b/app/client/src/routes/applicationForm.tsx similarity index 95% rename from app/client/src/routes/existingApplication.tsx rename to app/client/src/routes/applicationForm.tsx index 91a677c7..df4af3e8 100644 --- a/app/client/src/routes/existingApplication.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -113,7 +113,7 @@ function reducer(state: State, action: Action): State { } } -function ExistingApplicationProvider({ children }: Props) { +function ApplicationFormProvider({ children }: Props) { const initialState: State = { displayed: false, type: "info", @@ -132,12 +132,12 @@ function ExistingApplicationProvider({ children }: Props) { } /** - * Returns state stored in `ExistingApplicationProvider` context component. + * Returns state stored in `ApplicationFormProvider` context component. */ -function useExistingApplicationState() { +function useApplicationFormState() { const context = useContext(StateContext); if (context === undefined) { - const message = `useExistingApplicationState must be called within a ExistingApplicationProvider`; + const message = `useApplicationFormState must be called within a ApplicationFormProvider`; throw new Error(message); } return context; @@ -145,12 +145,12 @@ function useExistingApplicationState() { /** * Returns `dispatch` method for dispatching actions to update state stored in - * `ExistingApplicationProvider` context component. + * `ApplicationFormProvider` context component. */ -function useExistingApplicationDispatch() { +function useApplicationFormDispatch() { const context = useContext(DispatchContext); if (context === undefined) { - const message = `useExistingApplicationDispatch must be used within a ExistingApplicationProvider`; + const message = `useApplicationFormDispatch must be used within a ApplicationFormProvider`; throw new Error(message); } return context; @@ -158,11 +158,11 @@ function useExistingApplicationDispatch() { // ----------------------------------------------------------------------------- -export function ExistingApplication() { +export function ApplicationForm() { return ( - - - + + + ); } @@ -224,17 +224,17 @@ type SubmissionState = }; function FormMessage() { - const { displayed, type, text } = useExistingApplicationState(); + const { displayed, type, text } = useApplicationFormState(); if (!displayed) return null; return ; } -function ExistingApplicationContent() { +function ApplicationFormContent() { const navigate = useNavigate(); const { id } = useParams<"id">(); const { content } = useContentState(); const { csbData, epaUserData, bapUserData } = useUserState(); - const dispatch = useExistingApplicationDispatch(); + const dispatch = useApplicationFormDispatch(); const [applicationFormSubmission, setApplicationFormSubmission] = useState({ diff --git a/app/client/src/routes/newApplication.tsx b/app/client/src/routes/newApplicationForm.tsx similarity index 99% rename from app/client/src/routes/newApplication.tsx rename to app/client/src/routes/newApplicationForm.tsx index fc46d137..72fd49f2 100644 --- a/app/client/src/routes/newApplication.tsx +++ b/app/client/src/routes/newApplicationForm.tsx @@ -38,7 +38,7 @@ function createNewApplication(email: string, entity: SamEntity) { }); } -export function NewApplication() { +export function NewApplicationForm() { const navigate = useNavigate(); const { content } = useContentState(); const { csbData, epaUserData, bapUserData } = useUserState(); From 3694d2d3fc6c1542fad55ff27379293dcb1babab Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 1 Sep 2022 11:40:28 -0400 Subject: [PATCH 022/128] Update capitalization to be consistent in messages --- app/client/src/config.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/config.tsx b/app/client/src/config.tsx index fc894b9d..79515225 100644 --- a/app/client/src/config.tsx +++ b/app/client/src/config.tsx @@ -61,7 +61,7 @@ export const messages = { newApplication: "Please select the “New Application” button above to create your first rebate application.", helpdeskApplicationSubmissionError: - "Error loading application form submission. Please confirm the Application ID is correct and search again.", + "Error loading Application form submission. Please confirm the Application ID is correct and search again.", timeout: "For security reasons, you have been logged out due to 15 minutes of inactivity.", logout: "You have successfully logged out.", From 321bc554aa8c2b263dd03fc1bde04e840d7a75cf Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 1 Sep 2022 12:18:50 -0400 Subject: [PATCH 023/128] Add new route component for payment form --- app/client/src/components/app.tsx | 2 ++ app/client/src/routes/paymentForm.tsx | 11 +++++++++++ 2 files changed, 13 insertions(+) create mode 100644 app/client/src/routes/paymentForm.tsx diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index bfd7af4e..0f37541f 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -28,6 +28,7 @@ import { Helpdesk } from "routes/helpdesk"; import { AllRebates } from "routes/allRebates"; import { NewApplicationForm } from "routes/newApplicationForm"; import { ApplicationForm } from "routes/applicationForm"; +import { PaymentForm } from "routes/paymentForm"; import { useContentState, useContentDispatch } from "contexts/content"; import { useUserState, useUserDispatch } from "contexts/user"; import { useDialogDispatch, useDialogState } from "contexts/dialog"; @@ -290,6 +291,7 @@ export function App() { } /> } /> } /> + } /> } /> diff --git a/app/client/src/routes/paymentForm.tsx b/app/client/src/routes/paymentForm.tsx new file mode 100644 index 00000000..d7d60220 --- /dev/null +++ b/app/client/src/routes/paymentForm.tsx @@ -0,0 +1,11 @@ +import { useParams } from "react-router-dom"; + +export function PaymentForm() { + const { id } = useParams<"id">(); + + return ( + <> +

Payment Form {id}

+ + ); +} From 958efc8c67dabfded44911ca0324c731a2908654 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 1 Sep 2022 13:30:58 -0400 Subject: [PATCH 024/128] Split fetchData() into getData() and postData() for better clarity --- app/client/src/components/app.tsx | 8 ++-- app/client/src/components/dashboard.tsx | 6 +-- app/client/src/config.tsx | 50 ++++++++++++-------- app/client/src/routes/allRebates.tsx | 8 ++-- app/client/src/routes/applicationForm.tsx | 8 ++-- app/client/src/routes/helpdesk.tsx | 6 +-- app/client/src/routes/newApplicationForm.tsx | 4 +- 7 files changed, 49 insertions(+), 41 deletions(-) diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index 0f37541f..b343fbcc 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -18,7 +18,7 @@ import "@formio/choices.js/public/assets/styles/choices.min.css"; import "@formio/premium/dist/premium.css"; import "formiojs/dist/formio.full.min.css"; // --- -import { serverBasePath, serverUrl, cloudSpace, fetchData } from "../config"; +import { serverBasePath, serverUrl, cloudSpace, getData } from "../config"; import { Loading } from "components/loading"; import { MarkdownContent } from "components/markdownContent"; import { Welcome } from "routes/welcome"; @@ -39,7 +39,7 @@ function useFetchedContent() { useEffect(() => { dispatch({ type: "FETCH_CONTENT_REQUEST" }); - fetchData(`${serverUrl}/api/content`) + getData(`${serverUrl}/api/content`) .then((res) => { dispatch({ type: "FETCH_CONTENT_SUCCESS", @@ -205,7 +205,7 @@ export function useHelpdeskAccess() { useEffect(() => { setHelpdeskAccess("pending"); - fetchData(`${serverUrl}/api/helpdesk-access`) + getData(`${serverUrl}/api/helpdesk-access`) .then((res) => setHelpdeskAccess("success")) .catch((err) => setHelpdeskAccess("failure")); }, []); @@ -221,7 +221,7 @@ function ProtectedRoute({ children }: { children: JSX.Element }) { // Check if user is already logged in or needs to be redirected to /welcome route const verifyUser = useCallback(() => { - fetchData(`${serverUrl}/api/epa-data`) + getData(`${serverUrl}/api/epa-data`) .then((res) => { dispatch({ type: "FETCH_EPA_USER_DATA_SUCCESS", diff --git a/app/client/src/components/dashboard.tsx b/app/client/src/components/dashboard.tsx index 6f4db289..18d05447 100644 --- a/app/client/src/components/dashboard.tsx +++ b/app/client/src/components/dashboard.tsx @@ -10,7 +10,7 @@ import { serverUrlForHrefs, formioBaseUrl, formioProjectUrl, - fetchData, + getData, } from "../config"; import { useHelpdeskAccess } from "components/app"; import { Loading } from "components/loading"; @@ -28,7 +28,7 @@ function useFetchedCsbData() { useEffect(() => { dispatch({ type: "FETCH_CSB_DATA_REQUEST" }); - fetchData(`${serverUrl}/api/csb-data`) + getData(`${serverUrl}/api/csb-data`) .then((res) => { dispatch({ type: "FETCH_CSB_DATA_SUCCESS", @@ -47,7 +47,7 @@ function useFetchedBapData() { useEffect(() => { dispatch({ type: "FETCH_BAP_USER_DATA_REQUEST" }); - fetchData(`${serverUrl}/api/bap-data`) + getData(`${serverUrl}/api/bap-data`) .then((res) => { if (res.samResults) { dispatch({ diff --git a/app/client/src/config.tsx b/app/client/src/config.tsx index 79515225..32aa6772 100644 --- a/app/client/src/config.tsx +++ b/app/client/src/config.tsx @@ -68,31 +68,39 @@ export const messages = { enrollmentClosed: "The CSB enrollment period is closed.", }; -/** - * Returns a promise containing JSON fetched from a provided web service URL - * or handles any other OK response returned from the server - */ -export async function fetchData(url: string, data?: object) { - const options = !data - ? { - method: "GET", - credentials: "include" as const, - } - : { - method: "POST", - credentials: "include" as const, - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data), - }; - +async function fetchData(url: string, options: RequestInit) { try { - const res = await fetch(url, options); - if (!res.ok) throw new Error(res.statusText); - const contentType = res.headers.get("content-type"); + const response = await fetch(url, options); + if (!response.ok) throw new Error(response.statusText); + const contentType = response.headers.get("content-type"); return contentType?.includes("application/json") - ? await res.json() + ? await response.json() : Promise.resolve(); } catch (error) { return await Promise.reject(error); } } + +/** + * Fetches data and returns a promise containing JSON fetched from a provided + * web service URL or handles any other OK response returned from the server + */ +export function getData(url: string) { + return fetchData(url, { + method: "GET", + credentials: "include" as const, + }); +} + +/** + * Posts JSON data and returns a promise containing JSON fetched from a provided + * web service URL or handles any other OK response returned from the server + */ +export function postData(url: string, data: object) { + return fetchData(url, { + method: "POST", + credentials: "include" as const, + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }); +} diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 9a34fa6a..047a56e7 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -2,7 +2,7 @@ import { Fragment, useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; import icons from "uswds/img/sprite.svg"; // --- -import { serverUrl, fetchData, messages } from "../config"; +import { serverUrl, messages, getData, postData } from "../config"; import { Loading } from "components/loading"; import { Message } from "components/message"; import { MarkdownContent } from "components/markdownContent"; @@ -23,7 +23,7 @@ function useFetchedApplicationFormSubmissions() { dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_REQUEST" }); - fetchData(`${serverUrl}/api/application-form-submissions`) + getData(`${serverUrl}/api/application-form-submissions`) .then((res) => { dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_SUCCESS", @@ -48,7 +48,7 @@ function useFetchedPaymentFormSubmissions() { dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST" }); - fetchData(`${serverUrl}/api/payment-form-submissions`) + getData(`${serverUrl}/api/payment-form-submissions`) .then((res) => { dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_SUCCESS", @@ -300,7 +300,7 @@ form for the fields to be displayed. */ // change the submission's state to draft, then // redirect to the form to allow user to edit - fetchData( + postData( `${serverUrl}/api/application-form-submission/${_id}`, { data, state: "draft" } ) diff --git a/app/client/src/routes/applicationForm.tsx b/app/client/src/routes/applicationForm.tsx index df4af3e8..eca726d8 100644 --- a/app/client/src/routes/applicationForm.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -13,7 +13,7 @@ import { Formio, Form } from "@formio/react"; import { cloneDeep, isEqual } from "lodash"; import icons from "uswds/img/sprite.svg"; // --- -import { serverUrl, fetchData } from "../config"; +import { serverUrl, getData, postData } from "../config"; import { getUserInfo } from "../utilities"; import { Loading } from "components/loading"; import { Message } from "components/message"; @@ -273,7 +273,7 @@ function ApplicationFormContent() { }, }); - fetchData(`${serverUrl}/api/application-form-submission/${id}`) + getData(`${serverUrl}/api/application-form-submission/${id}`) .then((res) => { // set up s3 re-route to wrapper app const s3Provider = Formio.Providers.providers.storage.s3; @@ -505,7 +505,7 @@ function ApplicationFormContent() { setPendingSubmissionData(data); - fetchData( + postData( `${serverUrl}/api/application-form-submission/${submissionData._id}`, { ...submission, data } ) @@ -590,7 +590,7 @@ function ApplicationFormContent() { setPendingSubmissionData(data); - fetchData( + postData( `${serverUrl}/api/application-form-submission/${submissionData._id}`, { ...submission, data, state: "draft" } ) diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 298ce266..97d305b3 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -4,7 +4,7 @@ import { Form } from "@formio/react"; import icon from "uswds/img/usa-icons-bg/search--white.svg"; import icons from "uswds/img/sprite.svg"; // --- -import { serverUrl, messages, fetchData } from "../config"; +import { serverUrl, messages, getData, postData } from "../config"; import { useHelpdeskAccess } from "components/app"; import { Loading } from "components/loading"; import { Message } from "components/message"; @@ -119,7 +119,7 @@ export function Helpdesk() { }, }); - fetchData( + getData( `${serverUrl}/help/application-form-submission/${searchText}` ) .then((res) => { @@ -289,7 +289,7 @@ export function Helpdesk() { }, }); - fetchData( + postData( `${serverUrl}/help/application-form-submission/${formId}`, {} ) diff --git a/app/client/src/routes/newApplicationForm.tsx b/app/client/src/routes/newApplicationForm.tsx index 72fd49f2..8c0aa3d5 100644 --- a/app/client/src/routes/newApplicationForm.tsx +++ b/app/client/src/routes/newApplicationForm.tsx @@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom"; import { DialogOverlay, DialogContent } from "@reach/dialog"; import icons from "uswds/img/sprite.svg"; // --- -import { serverUrl, messages, fetchData } from "../config"; +import { serverUrl, messages, postData } from "../config"; import { getUserInfo } from "../utilities"; import { Loading } from "components/loading"; import { Message } from "components/message"; @@ -15,7 +15,7 @@ import { SamEntity, useUserState } from "contexts/user"; function createNewApplication(email: string, entity: SamEntity) { const { title, name } = getUserInfo(email, entity); - return fetchData(`${serverUrl}/api/application-form-submission/`, { + return postData(`${serverUrl}/api/application-form-submission/`, { data: { last_updated_by: email, hidden_current_user_email: email, From c0d3955691c95a2a7991d945babc4548229ac6cb Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 1 Sep 2022 15:09:44 -0400 Subject: [PATCH 025/128] Update PaymentForm component to fetch form schema (from temporary server api endpoint) and render the form --- app/client/src/routes/paymentForm.tsx | 56 +++++++++++++++++++++++++-- app/server/app/routes/api.js | 31 ++++++++++----- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/app/client/src/routes/paymentForm.tsx b/app/client/src/routes/paymentForm.tsx index d7d60220..9c226d78 100644 --- a/app/client/src/routes/paymentForm.tsx +++ b/app/client/src/routes/paymentForm.tsx @@ -1,11 +1,61 @@ +import { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; +import { Form } from "@formio/react"; +import icons from "uswds/img/sprite.svg"; +// --- +import { serverUrl, getData } from "../config"; +import { Loading } from "components/loading"; +import { Message } from "components/message"; + +type FormSchema = + | { status: "idle"; data: null } + | { status: "pending"; data: null } + | { status: "success"; data: object } + | { status: "failure"; data: null }; export function PaymentForm() { const { id } = useParams<"id">(); + const [paymentFormSchema, setPaymentFormSchema] = useState({ + status: "idle", + data: null, + }); + + useEffect(() => { + getData(`${serverUrl}/api/payment-form-schema`) + .then((res) => setPaymentFormSchema({ status: "success", data: res })) + .catch((err) => setPaymentFormSchema({ status: "failure", data: null })); + }, []); + + if ( + paymentFormSchema.status === "idle" || + paymentFormSchema.status === "pending" + ) { + return ; + } + + if (paymentFormSchema.status === "failure") { + return ( + + ); + } + return ( - <> -

Payment Form {id}

- +
+
    +
  • +
    + +
    +
    + Rebate ID: {id} +
    +
  • +
+ +
+
); } diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index 35975a67..622d91f0 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -169,13 +169,13 @@ router.get("/bap-data", (req, res) => { const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; -// --- get all application form submissions from Forms.gov +// --- get all Application form submissions user has access to from Forms.gov router.get("/application-form-submissions", storeBapComboKeys, (req, res) => { // NOTE: Helpdesk users might not have any SAM.gov records associated with // their email address so we should not return any submissions to those users. // The only reason we explicitly need to do this is because there could be // some submissions without `bap_hidden_entity_combo_key` field values in the - // forms.gov database – that will never be the case for submissions created + // Forms.gov database – that will never be the case for submissions created // from this app, but there could be submissions created externally if someone // is testing posting data (e.g. from a REST client, or the Formio Viewer) if (req.bapComboKeys.length === 0) return res.json([]); @@ -193,12 +193,12 @@ router.get("/application-form-submissions", storeBapComboKeys, (req, res) => { .then((axiosRes) => axiosRes.data) .then((submissions) => res.json(submissions)) .catch((error) => { - const message = `Error getting Forms.gov application form submissions`; + const message = `Error getting Forms.gov Application form submissions`; return res.status(error?.response?.status || 500).json({ message }); }); }); -// --- post a new application form submission to Forms.gov +// --- post a new Application form submission to Forms.gov router.post("/application-form-submission", storeBapComboKeys, (req, res) => { const comboKey = req.body.data?.bap_hidden_entity_combo_key; @@ -230,7 +230,7 @@ router.post("/application-form-submission", storeBapComboKeys, (req, res) => { }); }); -// --- get an existing application form's schema and submission data from Forms.gov +// --- get an existing Application form's schema and submission data from Forms.gov router.get( "/application-form-submission/:id", verifyMongoObjectId, @@ -269,13 +269,13 @@ router.get( }); }) .catch((error) => { - const message = `Error getting Forms.gov application form submission ${id}`; + const message = `Error getting Forms.gov Application form submission ${id}`; res.status(error?.response?.status || 500).json({ message }); }); } ); -// --- post an update to an existing draft application form submission to Forms.gov +// --- post an update to an existing draft Application form submission to Forms.gov router.post( "/application-form-submission/:id", verifyMongoObjectId, @@ -364,14 +364,27 @@ router.get("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { const paymentFormApiPath = `${formioProjectUrl}/${formioPaymentFormPath}`; -// --- get all payment request form submissions from Forms.gov +// --- get all Payment Request form submissions from Forms.gov router.get("/payment-form-submissions", storeBapComboKeys, (req, res) => { + // TODO: update URL, passing in data fields as needed to scope submissions to user axiosFormio(req) .get(`${paymentFormApiPath}/submission?sort=-modified&limit=1000000`) .then((axiosRes) => axiosRes.data) .then((submissions) => res.json(submissions)) .catch((error) => { - const message = `Error getting Forms.gov payment request form submissions`; + const message = `Error getting Forms.gov Payment Request form submissions`; + return res.status(error?.response?.status || 500).json({ message }); + }); +}); + +// --- TODO: WIP, as we'll eventually mirror `router.get("/application-form-submission/:id")` +router.get("/payment-form-schema", storeBapComboKeys, (req, res) => { + axiosFormio(req) + .get(paymentFormApiPath) + .then((axiosRes) => axiosRes.data) + .then((schema) => res.json(schema)) + .catch((error) => { + const message = `Error...`; return res.status(error?.response?.status || 500).json({ message }); }); }); From 438b8f5d5abb6fed404154bd1f73a80b44219e67 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 2 Sep 2022 13:44:03 -0400 Subject: [PATCH 026/128] Add code for posting a new draft payment request form submission with data from BAP and Application form --- app/client/src/contexts/forms.tsx | 21 +++++- app/client/src/routes/allRebates.tsx | 103 ++++++++++++++++++++++++--- app/server/app/routes/api.js | 46 ++++++++++-- 3 files changed, 152 insertions(+), 18 deletions(-) diff --git a/app/client/src/contexts/forms.tsx b/app/client/src/contexts/forms.tsx index 0bc13100..58849fc7 100644 --- a/app/client/src/contexts/forms.tsx +++ b/app/client/src/contexts/forms.tsx @@ -10,19 +10,36 @@ type Props = { children: ReactNode; }; -type ApplicationFormSubmission = { +export type ApplicationFormSubmission = { [field: string]: unknown; _id: string; // MongoDB ObjectId string state: "submitted" | "draft"; modified: string; // ISO 8601 date string data: { [field: string]: unknown; + // fields injected by wrapper upon new application creation: + last_updated_by: string; + hidden_current_user_email: string; + hidden_current_user_title: string; + hidden_current_user_name: string; + bap_hidden_entity_combo_key: string; + sam_hidden_applicant_email: string; + sam_hidden_applicant_title: string; + sam_hidden_applicant_name: string; + sam_hidden_applicant_efti: string; + sam_hidden_applicant_uei: string; + sam_hidden_applicant_organization_name: string; + sam_hidden_applicant_street_address_1: string; + sam_hidden_applicant_street_address_2: string; + sam_hidden_applicant_city: string; + sam_hidden_applicant_state: string; + sam_hidden_applicant_zip_code: string; + // fields set by form definition (among others): applicantUEI: string; applicantEfti: string; applicantEfti_display: string; applicantOrganizationName: string; schoolDistrictName: string; - last_updated_by: string; }; }; diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 047a56e7..7fb38a91 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -3,13 +3,18 @@ import { Link, useNavigate } from "react-router-dom"; import icons from "uswds/img/sprite.svg"; // --- import { serverUrl, messages, getData, postData } from "../config"; +import { getUserInfo } from "../utilities"; import { Loading } from "components/loading"; import { Message } from "components/message"; import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; -import { useUserState } from "contexts/user"; -import { useFormsState, useFormsDispatch } from "contexts/forms"; +import { SamEntity, useUserState } from "contexts/user"; +import { + ApplicationFormSubmission, + useFormsState, + useFormsDispatch, +} from "contexts/forms"; /** Custom hook to fetch Application form submissions */ function useFetchedApplicationFormSubmissions() { @@ -61,10 +66,62 @@ function useFetchedPaymentFormSubmissions() { }, [bapUserData, dispatch]); } +function createNewPaymentRequest( + email: string, + entity: SamEntity, + rebateId: string, + applicationData: ApplicationFormSubmission["data"] +) { + const { title, name } = getUserInfo(email, entity); + const { + bap_hidden_entity_combo_key, + ncesDistrictId, + totalRebateFundsRequested, + primaryContactName, + primaryContactTitle, + primaryContactPhoneNumber, + primaryContactEmail, + alternateContactName, + alternateContactTitle, + alternateContactPhoneNumber, + alternateContactEmail, + applicantOrganizationName, + schoolDistrictName, + } = applicationData; + + return postData(`${serverUrl}/api/payment-form-submission/`, { + data: { + last_updated_by: email, + hidden_current_user_email: email, + hidden_current_user_title: title, + hidden_current_user_name: name, + bap_hidden_entity_combo_key, + hidden_bap_review_item_id: rebateId, + hidden_bap_prioritized: null, // TODO: get from BAP + hidden_bap_bus_data: null, // TODO: get from BAP (to include bus numbers) + hidden_bap_district_id: ncesDistrictId, + hidden_bap_requested_funds: totalRebateFundsRequested, + hidden_bap_primary_name: primaryContactName, + hidden_bap_primary_title: primaryContactTitle, + hidden_bap_primary_phone_number: primaryContactPhoneNumber, + hidden_bap_primary_email: primaryContactEmail, + hidden_bap_alternate_name: alternateContactName, + hidden_bap_alternate_title: alternateContactTitle, + hidden_bap_alternate_phone_number: alternateContactPhoneNumber, + hidden_bap_alternate_email: alternateContactEmail, + hidden_bap_org_name: applicantOrganizationName, + hidden_bap_fleet_name: "", // TODO: investigate where this come from + hidden_bap_district_name: schoolDistrictName, + hidden_bap_infra_max_rebate: null, // TODO: get from BAP + }, + state: "draft", + }); +} + export function AllRebates() { const navigate = useNavigate(); const { content } = useContentState(); - const { csbData, bapUserData } = useUserState(); + const { csbData, epaUserData, bapUserData } = useUserState(); const { applicationFormSubmissions, paymentFormSubmissions } = useFormsState(); @@ -83,6 +140,7 @@ export function AllRebates() { if ( csbData.status !== "success" || + epaUserData.status !== "success" || bapUserData.status === "idle" || bapUserData.status === "pending" || applicationFormSubmissions.status === "idle" || @@ -106,6 +164,7 @@ export function AllRebates() { } const { enrollmentClosed } = csbData.data; + const email = epaUserData.data.mail; /** * Formio application submissions, merged with submissions returned from the @@ -220,6 +279,7 @@ export function AllRebates() { {submissions.map((submission, index) => { const { bap, _id, state, modified, data } = submission; const { + bap_hidden_entity_combo_key, applicantUEI, applicantEfti, applicantEfti_display, @@ -265,12 +325,15 @@ export function AllRebates() { const rebateStyles = index % 2 ? "bg-white" : "bg-gray-5"; /** - * Use the BAP Rebate ID if it exists, but fall back to the - * Formio submission ID for cases where the BAP Rebate ID has - * not yet been created (e.g. the form is brand new and the - * BAP's ETL process has not yet run to pick up the new form). + * matched SAM.gov entity for each submission (used to set the + * user's name and title in a new payment request form) */ - const uniqueId = bap.rebateId || _id; + const entity = bapUserData.data.samEntities.find((entity) => { + return ( + entity.ENTITY_STATUS__c === "Active" && + entity.ENTITY_COMBO_KEY__c === bap_hidden_entity_combo_key + ); + }); /* NOTE: when a form is first initially created, and the user has not yet clicked the "Next" or "Save" buttons, any fields that the Formio @@ -284,7 +347,7 @@ believe is a bit of an edge case, as most users will likely do that after starting a new application), indicate to the user they need to first save the form for the fields to be displayed. */ return ( - +
- {submissionHasBeenAccepted && ( + {submissionHasBeenSelected && ( @@ -307,8 +318,9 @@ export function AllRebates() { const submissionHasBeenWithdrawn = bap.rebateStatus === "Withdrawn"; - const submissionHasBeenSelected = - bap.rebateStatus === "Selected"; + const submissionHasBeenSelected = false; + // const submissionHasBeenSelected = + // bap.rebateStatus === "Selected"; const statusStyles = submissionNeedsEdits ? "csb-needs-edits" From f60503a65bcaa12b5e2cca5aa8d9b121a4a48c18 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 15:50:08 -0400 Subject: [PATCH 037/128] Split server API endpoints that returned BAP data, rename epa-user-data server endpoint, move fetched BAP data into its own client context component, and update client app to reflect re-organized fetching of BAP data --- app/client/src/components/app.tsx | 4 +- app/client/src/components/dashboard.tsx | 30 +-- app/client/src/config.tsx | 5 +- app/client/src/contexts/bap.tsx | 218 +++++++++++++++++++ app/client/src/contexts/user.tsx | 126 +---------- app/client/src/index.tsx | 13 +- app/client/src/routes/allRebates.tsx | 79 +++++-- app/client/src/routes/applicationForm.tsx | 23 +- app/client/src/routes/newApplicationForm.tsx | 12 +- app/client/src/routes/welcome.tsx | 8 +- app/client/src/utilities.tsx | 2 +- app/server/app/routes/api.js | 42 ++-- app/server/app/utilities/bap.js | 4 +- docs/csb-openapi.json | 40 +++- 14 files changed, 376 insertions(+), 230 deletions(-) create mode 100644 app/client/src/contexts/bap.tsx diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index acf3d489..5b255ed0 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -158,7 +158,7 @@ function useInactivityDialog(callback: () => void) { /** * If user makes action and the JWT is set to expire within 3 minutes, - * call the callback (hit the /epa-data endpoint) to refresh the JWT + * call the callback (hit the /epa-user-data endpoint) to refresh the JWT */ if (epaUserData.status !== "success") return; @@ -221,7 +221,7 @@ function ProtectedRoute({ children }: { children: JSX.Element }) { // Check if user is already logged in or needs to be redirected to /welcome route const verifyUser = useCallback(() => { - getData(`${serverUrl}/api/epa-data`) + getData(`${serverUrl}/api/epa-user-data`) .then((res) => { dispatch({ type: "FETCH_EPA_USER_DATA_SUCCESS", diff --git a/app/client/src/components/dashboard.tsx b/app/client/src/components/dashboard.tsx index 18d05447..354a26c6 100644 --- a/app/client/src/components/dashboard.tsx +++ b/app/client/src/components/dashboard.tsx @@ -15,6 +15,7 @@ import { import { useHelpdeskAccess } from "components/app"; import { Loading } from "components/loading"; import { useUserState, useUserDispatch } from "contexts/user"; +import { useBapState, useBapDispatch } from "contexts/bap"; import { Action, useDialogDispatch } from "contexts/dialog"; Formio.setBaseUrl(formioBaseUrl); @@ -41,26 +42,26 @@ function useFetchedCsbData() { }, [dispatch]); } -/** Custom hook to fetch BAP data */ -function useFetchedBapData() { - const dispatch = useUserDispatch(); +/** Custom hook to fetch SAM.gov data */ +function useFetchedSamData() { + const dispatch = useBapDispatch(); useEffect(() => { - dispatch({ type: "FETCH_BAP_USER_DATA_REQUEST" }); - getData(`${serverUrl}/api/bap-data`) + dispatch({ type: "FETCH_BAP_SAM_DATA_REQUEST" }); + getData(`${serverUrl}/api/bap-sam-data`) .then((res) => { - if (res.samResults) { + if (res.results) { dispatch({ - type: "FETCH_BAP_USER_DATA_SUCCESS", - payload: { bapUserData: res }, + type: "FETCH_BAP_SAM_DATA_SUCCESS", + payload: { samEntities: res }, }); } else { - window.location.href = `${serverUrlForHrefs}/logout?RelayState=/welcome?info=sam-results`; + window.location.href = `${serverUrlForHrefs}/logout?RelayState=/welcome?info=bap-sam-results`; } }) .catch((err) => { - dispatch({ type: "FETCH_BAP_USER_DATA_FAILURE" }); - window.location.href = `${serverUrlForHrefs}/logout?RelayState=/welcome?error=bap-fetch`; + dispatch({ type: "FETCH_BAP_SAM_DATA_FAILURE" }); + window.location.href = `${serverUrlForHrefs}/logout?RelayState=/welcome?error=bap-sam-fetch`; }); }, [dispatch]); } @@ -104,12 +105,13 @@ export function Dashboard() { const { pathname } = useLocation(); const navigate = useNavigate(); - const { csbData, epaUserData, bapUserData } = useUserState(); + const { csbData, epaUserData } = useUserState(); + const { samEntities } = useBapState(); const dispatch = useDialogDispatch(); const helpdeskAccess = useHelpdeskAccess(); useFetchedCsbData(); - useFetchedBapData(); + useFetchedSamData(); /** * When provided a destination location to navigate to, creates an action @@ -132,7 +134,7 @@ export function Dashboard() { }; } - if (bapUserData.status !== "success") { + if (samEntities.status !== "success") { return ; } diff --git a/app/client/src/config.tsx b/app/client/src/config.tsx index 32aa6772..f1b96de1 100644 --- a/app/client/src/config.tsx +++ b/app/client/src/config.tsx @@ -52,9 +52,8 @@ export const messages = { genericError: "Something went wrong.", authError: "Authentication error. Please log in again or contact support.", samlError: "Error logging in. Please try again or contact support.", - bapFetchError: - "Error loading SAM.gov or rebate submission data. Please contact support.", - noSamResults: + bapSamFetchError: "Error loading SAM.gov data. Please contact support.", + bapNoSamResults: "No SAM.gov records match your email. Only Government and Electronic Business SAM.gov Points of Contacts (and alternates) may edit and submit Clean School Bus Rebate Forms.", applicationSubmissionsError: "Error loading Application form submissions.", paymentSubmissionsError: "Error loading Payment Request form submissions.", diff --git a/app/client/src/contexts/bap.tsx b/app/client/src/contexts/bap.tsx new file mode 100644 index 00000000..b480dcaf --- /dev/null +++ b/app/client/src/contexts/bap.tsx @@ -0,0 +1,218 @@ +import { + Dispatch, + ReactNode, + createContext, + useContext, + useReducer, +} from "react"; + +type Props = { + children: ReactNode; +}; + +export type SamEntity = { + ENTITY_COMBO_KEY__c: string; + UNIQUE_ENTITY_ID__c: string; + ENTITY_EFT_INDICATOR__c: string; + ENTITY_STATUS__c: "Active" | string; + LEGAL_BUSINESS_NAME__c: string; + PHYSICAL_ADDRESS_LINE_1__c: string; + PHYSICAL_ADDRESS_LINE_2__c: string | null; + PHYSICAL_ADDRESS_CITY__c: string; + PHYSICAL_ADDRESS_PROVINCE_OR_STATE__c: string; + PHYSICAL_ADDRESS_ZIPPOSTAL_CODE__c: string; + PHYSICAL_ADDRESS_ZIP_CODE_4__c: string; + // contacts + ELEC_BUS_POC_EMAIL__c: string | null; + ELEC_BUS_POC_NAME__c: string | null; + ELEC_BUS_POC_TITLE__c: string | null; + // + ALT_ELEC_BUS_POC_EMAIL__c: string | null; + ALT_ELEC_BUS_POC_NAME__c: string | null; + ALT_ELEC_BUS_POC_TITLE__c: string | null; + // + GOVT_BUS_POC_EMAIL__c: string | null; + GOVT_BUS_POC_NAME__c: string | null; + GOVT_BUS_POC_TITLE__c: string | null; + // + ALT_GOVT_BUS_POC_EMAIL__c: string | null; + ALT_GOVT_BUS_POC_NAME__c: string | null; + ALT_GOVT_BUS_POC_TITLE__c: string | null; + // + attributes: { type: string; url: string }; +}; + +type ApplicationFormSubmission = { + CSB_Form_ID__c: string; // MongoDB ObjectId string + CSB_Modified_Full_String__c: string; // ISO 8601 date string + UEI_EFTI_Combo_Key__c: string; + Parent_Rebate_ID__c: string; // CSB Rebate ID + Parent_CSB_Rebate__r: { + CSB_Rebate_Status__c: + | "Draft" + | "Submitted" + | "Edits Requested" + | "Withdrawn" + | "Selected"; + attributes: { type: string; url: string }; + }; + attributes: { type: string; url: string }; +}; + +type State = { + samEntities: + | { status: "idle"; data: {} } + | { status: "pending"; data: {} } + | { + status: "success"; + data: + | { results: false; entities: [] } + | { results: true; entities: SamEntity[] }; + } + | { status: "failure"; data: {} }; + applicationSubmissions: + | { status: "idle"; data: {} } + | { status: "pending"; data: {} } + | { status: "success"; data: ApplicationFormSubmission[] } + | { status: "failure"; data: {} }; +}; + +type Action = + | { type: "FETCH_BAP_SAM_DATA_REQUEST" } + | { + type: "FETCH_BAP_SAM_DATA_SUCCESS"; + payload: { + samEntities: + | { results: false; entities: [] } + | { results: true; entities: SamEntity[] }; + }; + } + | { type: "FETCH_BAP_SAM_DATA_FAILURE" } + | { type: "FETCH_BAP_APPLICATION_SUBMISSIONS_REQUEST" } + | { + type: "FETCH_BAP_APPLICATION_SUBMISSIONS_SUCCESS"; + payload: { applicationSubmissions: ApplicationFormSubmission[] }; + } + | { type: "FETCH_BAP_APPLICATION_SUBMISSIONS_FAILURE" }; + +const StateContext = createContext(undefined); +const DispatchContext = createContext | undefined>(undefined); + +function reducer(state: State, action: Action): State { + switch (action.type) { + case "FETCH_BAP_SAM_DATA_REQUEST": { + return { + ...state, + samEntities: { + status: "pending", + data: {}, + }, + }; + } + + case "FETCH_BAP_SAM_DATA_SUCCESS": { + const { samEntities } = action.payload; + return { + ...state, + samEntities: { + status: "success", + data: samEntities, + }, + }; + } + + case "FETCH_BAP_SAM_DATA_FAILURE": { + return { + ...state, + samEntities: { + status: "failure", + data: {}, + }, + }; + } + + case "FETCH_BAP_APPLICATION_SUBMISSIONS_REQUEST": { + return { + ...state, + applicationSubmissions: { + status: "pending", + data: {}, + }, + }; + } + + case "FETCH_BAP_APPLICATION_SUBMISSIONS_SUCCESS": { + const { applicationSubmissions } = action.payload; + return { + ...state, + applicationSubmissions: { + status: "success", + data: applicationSubmissions, + }, + }; + } + + case "FETCH_BAP_APPLICATION_SUBMISSIONS_FAILURE": { + return { + ...state, + applicationSubmissions: { + status: "failure", + data: {}, + }, + }; + } + + default: { + const message = `Unhandled action type: ${action}`; + throw new Error(message); + } + } +} + +export function BapProvider({ children }: Props) { + const initialState: State = { + samEntities: { + status: "idle", + data: {}, + }, + applicationSubmissions: { + status: "idle", + data: {}, + }, + }; + + const [state, dispatch] = useReducer(reducer, initialState); + + return ( + + + {children} + + + ); +} + +/** + * Returns state stored in `BapProvider` context component. + */ +export function useBapState() { + const context = useContext(StateContext); + if (context === undefined) { + const message = `useBapState must be called within a BapProvider`; + throw new Error(message); + } + return context; +} + +/** + * Returns `dispatch` method for dispatching actions to update state stored in + * `BapProvider` context component. + */ +export function useBapDispatch() { + const context = useContext(DispatchContext); + if (context === undefined) { + const message = `useBapDispatch must be used within a BapProvider`; + throw new Error(message); + } + return context; +} diff --git a/app/client/src/contexts/user.tsx b/app/client/src/contexts/user.tsx index 58cef6eb..81897944 100644 --- a/app/client/src/contexts/user.tsx +++ b/app/client/src/contexts/user.tsx @@ -20,55 +20,6 @@ type EpaUserData = { exp: number; }; -export type SamEntity = { - ENTITY_COMBO_KEY__c: string; - UNIQUE_ENTITY_ID__c: string; - ENTITY_EFT_INDICATOR__c: string; - ENTITY_STATUS__c: "Active" | string; - LEGAL_BUSINESS_NAME__c: string; - PHYSICAL_ADDRESS_LINE_1__c: string; - PHYSICAL_ADDRESS_LINE_2__c: string | null; - PHYSICAL_ADDRESS_CITY__c: string; - PHYSICAL_ADDRESS_PROVINCE_OR_STATE__c: string; - PHYSICAL_ADDRESS_ZIPPOSTAL_CODE__c: string; - PHYSICAL_ADDRESS_ZIP_CODE_4__c: string; - // contacts - ELEC_BUS_POC_EMAIL__c: string | null; - ELEC_BUS_POC_NAME__c: string | null; - ELEC_BUS_POC_TITLE__c: string | null; - // - ALT_ELEC_BUS_POC_EMAIL__c: string | null; - ALT_ELEC_BUS_POC_NAME__c: string | null; - ALT_ELEC_BUS_POC_TITLE__c: string | null; - // - GOVT_BUS_POC_EMAIL__c: string | null; - GOVT_BUS_POC_NAME__c: string | null; - GOVT_BUS_POC_TITLE__c: string | null; - // - ALT_GOVT_BUS_POC_EMAIL__c: string | null; - ALT_GOVT_BUS_POC_NAME__c: string | null; - ALT_GOVT_BUS_POC_TITLE__c: string | null; - // - attributes: { type: string; url: string }; -}; - -type ApplicationFormSubmissionMetadata = { - CSB_Form_ID__c: string; // MongoDB ObjectId string - CSB_Modified_Full_String__c: string; // ISO 8601 date string - UEI_EFTI_Combo_Key__c: string; - Parent_Rebate_ID__c: string; // CSB Rebate ID - Parent_CSB_Rebate__r: { - CSB_Rebate_Status__c: - | "Draft" - | "Submitted" - | "Edits Requested" - | "Withdrawn" - | "Selected"; - attributes: { type: string; url: string }; - }; - attributes: { type: string; url: string }; -}; - type State = { isAuthenticating: boolean; isAuthenticated: boolean; @@ -82,24 +33,6 @@ type State = { | { status: "pending"; data: {} } | { status: "success"; data: EpaUserData } | { status: "failure"; data: {} }; - bapUserData: - | { status: "idle"; data: {} } - | { status: "pending"; data: {} } - | { - status: "success"; - data: - | { - samResults: false; - samEntities: []; - applicationSubmissions: []; - } - | { - samResults: true; - samEntities: SamEntity[]; - applicationSubmissions: ApplicationFormSubmissionMetadata[]; - }; - } - | { status: "failure"; data: {} }; }; type Action = @@ -116,25 +49,7 @@ type Action = type: "FETCH_EPA_USER_DATA_SUCCESS"; payload: { epaUserData: EpaUserData }; } - | { type: "FETCH_EPA_USER_DATA_FAILURE" } - | { type: "FETCH_BAP_USER_DATA_REQUEST" } - | { - type: "FETCH_BAP_USER_DATA_SUCCESS"; - payload: { - bapUserData: - | { - samResults: false; - samEntities: []; - applicationSubmissions: []; - } - | { - samResults: true; - samEntities: SamEntity[]; - applicationSubmissions: ApplicationFormSubmissionMetadata[]; - }; - }; - } - | { type: "FETCH_BAP_USER_DATA_FAILURE" }; + | { type: "FETCH_EPA_USER_DATA_FAILURE" }; const StateContext = createContext(undefined); const DispatchContext = createContext | undefined>(undefined); @@ -162,10 +77,6 @@ function reducer(state: State, action: Action): State { status: "idle", data: {}, }, - bapUserData: { - status: "idle", - data: {}, - }, }; } @@ -231,37 +142,6 @@ function reducer(state: State, action: Action): State { }; } - case "FETCH_BAP_USER_DATA_REQUEST": { - return { - ...state, - bapUserData: { - status: "pending", - data: {}, - }, - }; - } - - case "FETCH_BAP_USER_DATA_SUCCESS": { - const { bapUserData } = action.payload; - return { - ...state, - bapUserData: { - status: "success", - data: bapUserData, - }, - }; - } - - case "FETCH_BAP_USER_DATA_FAILURE": { - return { - ...state, - bapUserData: { - status: "failure", - data: {}, - }, - }; - } - default: { const message = `Unhandled action type: ${action}`; throw new Error(message); @@ -281,10 +161,6 @@ export function UserProvider({ children }: Props) { status: "idle", data: {}, }, - bapUserData: { - status: "idle", - data: {}, - }, }; const [state, dispatch] = useReducer(reducer, initialState); diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index fc60312b..e1e92aa3 100644 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -4,6 +4,7 @@ import reportWebVitals from "./reportWebVitals"; // --- import { ContentProvider } from "contexts/content"; import { UserProvider } from "contexts/user"; +import { BapProvider } from "contexts/bap"; import { FormsProvider } from "contexts/forms"; import { DialogProvider } from "contexts/dialog"; import { ErrorBoundary } from "components/errorBoundary"; @@ -17,11 +18,13 @@ render( - - - - - + + + + + + + diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 33fb42b2..387e1029 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -9,20 +9,21 @@ import { Message } from "components/message"; import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; -import { SamEntity, useUserState } from "contexts/user"; +import { useUserState } from "contexts/user"; +import { SamEntity, useBapState, useBapDispatch } from "contexts/bap"; import { ApplicationFormSubmission, useFormsState, useFormsDispatch, } from "contexts/forms"; -/** Custom hook to fetch Application form submissions */ -function useFetchedApplicationFormSubmissions() { - const { bapUserData } = useUserState(); +/** Custom hook to fetch Application form submissions from Forms.gov */ +function useFetchedFormioApplicationSubmissions() { + const { samEntities } = useBapState(); const dispatch = useFormsDispatch(); useEffect(() => { - if (bapUserData.status !== "success" || !bapUserData.data.samResults) { + if (samEntities.status !== "success" || !samEntities.data.results) { return; } @@ -38,16 +39,41 @@ function useFetchedApplicationFormSubmissions() { .catch((err) => { dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE" }); }); - }, [bapUserData, dispatch]); + }, [samEntities, dispatch]); } -/** Custom hook to fetch Payment Request form submissions */ -function useFetchedPaymentFormSubmissions() { - const { bapUserData } = useUserState(); +/** Custom hook to fetch Application form submissions from the BAP */ +function useFetchedBapApplicationSubmissions() { + const { samEntities } = useBapState(); + const dispatch = useBapDispatch(); + + useEffect(() => { + if (samEntities.status !== "success" || !samEntities.data.results) { + return; + } + + dispatch({ type: "FETCH_BAP_APPLICATION_SUBMISSIONS_REQUEST" }); + + getData(`${serverUrl}/api/bap-application-submissions`) + .then((res) => { + dispatch({ + type: "FETCH_BAP_APPLICATION_SUBMISSIONS_SUCCESS", + payload: { applicationSubmissions: res }, + }); + }) + .catch((err) => { + dispatch({ type: "FETCH_BAP_APPLICATION_SUBMISSIONS_FAILURE" }); + }); + }, [samEntities, dispatch]); +} + +/** Custom hook to fetch Payment Request form submissions from Forms.gov */ +function useFetchedFormioPaymentRequestSubmissions() { + const { samEntities } = useBapState(); const dispatch = useFormsDispatch(); useEffect(() => { - if (bapUserData.status !== "success" || !bapUserData.data.samResults) { + if (samEntities.status !== "success" || !samEntities.data.results) { return; } @@ -63,7 +89,7 @@ function useFetchedPaymentFormSubmissions() { .catch((err) => { dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_FAILURE" }); }); - }, [bapUserData, dispatch]); + }, [samEntities, dispatch]); } function createNewPaymentRequest( @@ -134,7 +160,9 @@ function createNewPaymentRequest( export function AllRebates() { const navigate = useNavigate(); const { content } = useContentState(); - const { csbData, epaUserData, bapUserData } = useUserState(); + const { csbData, epaUserData } = useUserState(); + const { samEntities, applicationSubmissions: bapApplicationSubmissions } = + useBapState(); const { applicationFormSubmissions, paymentFormSubmissions } = useFormsState(); @@ -148,14 +176,18 @@ export function AllRebates() { text: "", }); - useFetchedApplicationFormSubmissions(); - useFetchedPaymentFormSubmissions(); + useFetchedFormioApplicationSubmissions(); + useFetchedBapApplicationSubmissions(); + + useFetchedFormioPaymentRequestSubmissions(); if ( csbData.status !== "success" || epaUserData.status !== "success" || - bapUserData.status === "idle" || - bapUserData.status === "pending" || + samEntities.status === "idle" || + samEntities.status === "pending" || + bapApplicationSubmissions.status === "idle" || + bapApplicationSubmissions.status === "pending" || applicationFormSubmissions.status === "idle" || applicationFormSubmissions.status === "pending" || paymentFormSubmissions.status === "idle" || @@ -164,11 +196,14 @@ export function AllRebates() { return ; } - if (bapUserData.status === "failure") { - return ; + if (samEntities.status === "failure") { + return ; } - if (applicationFormSubmissions.status === "failure") { + if ( + bapApplicationSubmissions.status === "failure" || + applicationFormSubmissions.status === "failure" + ) { return ; } @@ -185,8 +220,8 @@ export function AllRebates() { * updated datetime. */ const submissions = applicationFormSubmissions.data.map((formioSub) => { - const match = bapUserData.data.applicationSubmissions.find((bapSub) => { - return bapSub.CSB_Form_ID__c === formioSub._id; + const match = bapApplicationSubmissions.data.find((bapSubmission) => { + return bapSubmission.CSB_Form_ID__c === formioSub._id; }); return { @@ -339,7 +374,7 @@ export function AllRebates() { * matched SAM.gov entity for each submission (used to set the * user's name and title in a new payment request form) */ - const entity = bapUserData.data.samEntities.find((entity) => { + const entity = samEntities.data.entities.find((entity) => { return ( entity.ENTITY_STATUS__c === "Active" && entity.ENTITY_COMBO_KEY__c === bap_hidden_entity_combo_key diff --git a/app/client/src/routes/applicationForm.tsx b/app/client/src/routes/applicationForm.tsx index eca726d8..e6a92da7 100644 --- a/app/client/src/routes/applicationForm.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -20,6 +20,7 @@ import { Message } from "components/message"; import { MarkdownContent } from "components/markdownContent"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; +import { useBapState } from "contexts/bap"; // ----------------------------------------------------------------------------- @@ -233,7 +234,9 @@ function ApplicationFormContent() { const navigate = useNavigate(); const { id } = useParams<"id">(); const { content } = useContentState(); - const { csbData, epaUserData, bapUserData } = useUserState(); + const { csbData, epaUserData } = useUserState(); + const { samEntities, applicationSubmissions: bapApplicationSubmissions } = + useBapState(); const dispatch = useApplicationFormDispatch(); const [applicationFormSubmission, setApplicationFormSubmission] = @@ -343,28 +346,28 @@ function ApplicationFormContent() { if ( csbData.status !== "success" || epaUserData.status !== "success" || - bapUserData.status !== "success" + samEntities.status !== "success" || + bapApplicationSubmissions.status !== "success" ) { return ; } const { enrollmentClosed } = csbData.data; - const matchedBapMetadata = bapUserData.data.applicationSubmissions.find( - (bapSubmission) => bapSubmission.CSB_Form_ID__c === id - ); + const match = bapApplicationSubmissions.data.find((bapSubmission) => { + return bapSubmission.CSB_Form_ID__c === id; + }); const bap = { - lastModified: matchedBapMetadata?.CSB_Modified_Full_String__c || null, - rebateId: matchedBapMetadata?.Parent_Rebate_ID__c || null, - rebateStatus: - matchedBapMetadata?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null, + lastModified: match?.CSB_Modified_Full_String__c || null, + rebateId: match?.Parent_Rebate_ID__c || null, + rebateStatus: match?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null, }; const submissionNeedsEdits = bap.rebateStatus === "Edits Requested"; const entityComboKey = storedSubmissionData.bap_hidden_entity_combo_key; - const entity = bapUserData.data.samEntities.find((entity) => { + const entity = samEntities.data.entities.find((entity) => { return ( entity.ENTITY_STATUS__c === "Active" && entity.ENTITY_COMBO_KEY__c === entityComboKey diff --git a/app/client/src/routes/newApplicationForm.tsx b/app/client/src/routes/newApplicationForm.tsx index 8c0aa3d5..355ca1c9 100644 --- a/app/client/src/routes/newApplicationForm.tsx +++ b/app/client/src/routes/newApplicationForm.tsx @@ -10,7 +10,8 @@ import { Message } from "components/message"; import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; -import { SamEntity, useUserState } from "contexts/user"; +import { useUserState } from "contexts/user"; +import { SamEntity, useBapState } from "contexts/bap"; function createNewApplication(email: string, entity: SamEntity) { const { title, name } = getUserInfo(email, entity); @@ -41,7 +42,8 @@ function createNewApplication(email: string, entity: SamEntity) { export function NewApplicationForm() { const navigate = useNavigate(); const { content } = useContentState(); - const { csbData, epaUserData, bapUserData } = useUserState(); + const { csbData, epaUserData } = useUserState(); + const { samEntities } = useBapState(); const [message, setMessage] = useState<{ displayed: boolean; @@ -56,14 +58,14 @@ export function NewApplicationForm() { if ( csbData.status !== "success" || epaUserData.status !== "success" || - bapUserData.status !== "success" + samEntities.status !== "success" ) { return ; } const email = epaUserData.data.mail; - const activeSamEntities = bapUserData.data.samEntities.filter((entity) => { + const activeSamEntities = samEntities.data.entities.filter((entity) => { return entity.ENTITY_STATUS__c === "Active"; }); @@ -80,7 +82,7 @@ export function NewApplicationForm() { {csbData.data.enrollmentClosed ? ( ) : activeSamEntities.length <= 0 ? ( - + ) : ( <> {content.status === "success" && ( diff --git a/app/client/src/routes/welcome.tsx b/app/client/src/routes/welcome.tsx index 864bd2fd..67369e20 100644 --- a/app/client/src/routes/welcome.tsx +++ b/app/client/src/routes/welcome.tsx @@ -35,19 +35,19 @@ export function Welcome() { }); } - if (searchParams.get("error") === "bap-fetch") { + if (searchParams.get("error") === "bap-sam-fetch") { setMessage({ displayed: true, type: "error", - text: messages.bapFetchError, + text: messages.bapSamFetchError, }); } - if (searchParams.get("info") === "sam-results") { + if (searchParams.get("info") === "bap-sam-results") { setMessage({ displayed: true, type: "info", - text: messages.noSamResults, + text: messages.bapNoSamResults, }); } diff --git a/app/client/src/utilities.tsx b/app/client/src/utilities.tsx index a489c217..d28f403b 100644 --- a/app/client/src/utilities.tsx +++ b/app/client/src/utilities.tsx @@ -1,4 +1,4 @@ -import { SamEntity } from "contexts/user"; +import { SamEntity } from "contexts/bap"; /** * Returns a user’s title and name when provided an email address and a SAM.gov diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index ef85f302..ed2ab111 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -124,52 +124,46 @@ router.get("/csb-data", (req, res) => { }); // --- get user data from EPA Gateway/Login.gov -router.get("/epa-data", (req, res) => { +router.get("/epa-user-data", (req, res) => { const { mail, memberof, exp } = req.user; return res.json({ mail, memberof, exp }); }); -// --- get data from EPA's Business Automation Platform (BAP) -router.get("/bap-data", (req, res) => { +// --- get user's SAM.gov data from EPA's Business Automation Platform (BAP) +router.get("/bap-sam-data", (req, res) => { getSamData(req.user.mail, req) - .then((samEntities) => { + .then((entities) => { // NOTE: allow admin or helpdesk users access to the app, even without SAM.gov data const userRoles = req.user.memberof.split(","); const helpdeskUser = userRoles.includes("csb_admin") || userRoles.includes("csb_helpdesk"); - if (!helpdeskUser && samEntities?.length === 0) { + if (!helpdeskUser && entities?.length === 0) { const message = `User with email ${req.user.mail} tried to use app without any associated SAM records.`; log({ level: "error", message, req }); - return res.json({ - samResults: false, - samEntities: [], - applicationSubmissions: [], - }); + return res.json({ results: false, entities: [] }); } - - const comboKeys = samEntities.map((e) => e.ENTITY_COMBO_KEY__c); - - getApplicationSubmissionsData(comboKeys, req) - .then((submissions) => { - return res.json({ - samResults: true, - samEntities, - applicationSubmissions: submissions, - }); + return res.json({ results: true, entities }); }) .catch((error) => { - throw error; + const message = `Error getting SAM.gov data from BAP`; + return res.status(401).json({ message }); }); - }) +}); + +// --- get user's Application form submissions from EPA's BAP +router.get("/bap-application-submissions", storeBapComboKeys, (req, res) => { + getApplicationSubmissionsData(req.bapComboKeys, req) + .then((submissions) => res.json(submissions)) .catch((error) => { - return res.status(401).json({ message: "Error getting data from BAP" }); + const message = `Error getting application form submissions from BAP`; + return res.status(401).json({ message }); }); }); const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; -// --- get all Application form submissions user has access to from Forms.gov +// --- get user's Application form submissions from Forms.gov router.get("/application-form-submissions", storeBapComboKeys, (req, res) => { // NOTE: Helpdesk users might not have any SAM.gov records associated with // their email address so we should not return any submissions to those users. diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 693cf74e..31c1d375 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -183,9 +183,7 @@ function getSamData(email, req) { */ function getComboKeys(email, req) { return getSamData(email, req) - .then((samEntities) => { - return samEntities.map((samEntity) => samEntity.ENTITY_COMBO_KEY__c); - }) + .then((entities) => entities.map((entity) => entity.ENTITY_COMBO_KEY__c)) .catch((err) => { throw err; }); diff --git a/docs/csb-openapi.json b/docs/csb-openapi.json index 769bb5c9..9c33e102 100644 --- a/docs/csb-openapi.json +++ b/docs/csb-openapi.json @@ -316,9 +316,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/api/epa-data": { + "/api/epa-user-data": { "get": { - "summary": "/api/epa-data", + "summary": "/api/epa-user-data", "responses": { "200": { "description": "OK", @@ -365,9 +365,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/api/bap-data": { + "/api/bap-sam-data": { "get": { - "summary": "/api/bap-data", + "summary": "/api/bap-sam-data", "responses": { "200": { "description": "OK", @@ -376,17 +376,11 @@ "schema": { "type": "object", "properties": { - "samResults": { + "results": { "type": "boolean", "example": true }, - "samEntities": { - "type": "array", - "items": { - "type": "object" - } - }, - "rebateSubmissions": { + "entities": { "type": "array", "items": { "type": "object" @@ -402,6 +396,28 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, + "/api/bap-application-submissions": { + "get": { + "summary": "/api/bap-application-submissions", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + }, + "tags": [], + "parameters": [{ "$ref": "#/components/parameters/scan" }] + } + }, "/api/content": { "get": { "summary": "/api/content", From 18853a765c2579b679e62804a14687b97d5a8b08 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 16:07:46 -0400 Subject: [PATCH 038/128] Rename formio-application-submissions API route to follow bap-application-submissions API route --- app/client/src/routes/allRebates.tsx | 2 +- app/server/app/routes/api.js | 8 ++++---- docs/csb-openapi.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 387e1029..438285c9 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -29,7 +29,7 @@ function useFetchedFormioApplicationSubmissions() { dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_REQUEST" }); - getData(`${serverUrl}/api/application-form-submissions`) + getData(`${serverUrl}/api/formio-application-submissions`) .then((res) => { dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_SUCCESS", diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index ed2ab111..f1f28db6 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -144,11 +144,11 @@ router.get("/bap-sam-data", (req, res) => { return res.json({ results: false, entities: [] }); } return res.json({ results: true, entities }); - }) - .catch((error) => { + }) + .catch((error) => { const message = `Error getting SAM.gov data from BAP`; return res.status(401).json({ message }); - }); + }); }); // --- get user's Application form submissions from EPA's BAP @@ -164,7 +164,7 @@ router.get("/bap-application-submissions", storeBapComboKeys, (req, res) => { const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; // --- get user's Application form submissions from Forms.gov -router.get("/application-form-submissions", storeBapComboKeys, (req, res) => { +router.get("/formio-application-submissions", storeBapComboKeys, (req, res) => { // NOTE: Helpdesk users might not have any SAM.gov records associated with // their email address so we should not return any submissions to those users. // The only reason we explicitly need to do this is because there could be diff --git a/docs/csb-openapi.json b/docs/csb-openapi.json index 9c33e102..8f29a37f 100644 --- a/docs/csb-openapi.json +++ b/docs/csb-openapi.json @@ -517,9 +517,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/api/application-form-submissions": { + "/api/formio-application-submissions": { "get": { - "summary": "/api/application-form-submissions", + "summary": "/api/formio-application-submissions", "responses": { "200": { "description": "OK", From ac37d7110799accd47777ac03f0216505c14b147 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 16:22:13 -0400 Subject: [PATCH 039/128] Update all application-form-submission API endpoints to formio-application-submission, and update status endpoints --- app/client/src/routes/allRebates.tsx | 2 +- app/client/src/routes/applicationForm.tsx | 6 ++--- app/client/src/routes/helpdesk.tsx | 4 ++-- app/client/src/routes/newApplicationForm.tsx | 2 +- app/server/app/routes/api.js | 8 +++---- app/server/app/routes/help.js | 8 +++---- app/server/app/routes/status.js | 4 ++-- docs/csb-openapi.json | 24 ++++++++++---------- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 438285c9..6282ebdb 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -410,7 +410,7 @@ form for the fields to be displayed. */ // change the submission's state to draft, then // redirect to the form to allow user to edit postData( - `${serverUrl}/api/application-form-submission/${_id}`, + `${serverUrl}/api/formio-application-submission/${_id}`, { data, state: "draft" } ) .then((res) => { diff --git a/app/client/src/routes/applicationForm.tsx b/app/client/src/routes/applicationForm.tsx index e6a92da7..e83ec01e 100644 --- a/app/client/src/routes/applicationForm.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -276,7 +276,7 @@ function ApplicationFormContent() { }, }); - getData(`${serverUrl}/api/application-form-submission/${id}`) + getData(`${serverUrl}/api/formio-application-submission/${id}`) .then((res) => { // set up s3 re-route to wrapper app const s3Provider = Formio.Providers.providers.storage.s3; @@ -509,7 +509,7 @@ function ApplicationFormContent() { setPendingSubmissionData(data); postData( - `${serverUrl}/api/application-form-submission/${submissionData._id}`, + `${serverUrl}/api/formio-application-submission/${submissionData._id}`, { ...submission, data } ) .then((res) => { @@ -594,7 +594,7 @@ function ApplicationFormContent() { setPendingSubmissionData(data); postData( - `${serverUrl}/api/application-form-submission/${submissionData._id}`, + `${serverUrl}/api/formio-application-submission/${submissionData._id}`, { ...submission, data, state: "draft" } ) .then((res) => { diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 97d305b3..25cb7297 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -120,7 +120,7 @@ export function Helpdesk() { }); getData( - `${serverUrl}/help/application-form-submission/${searchText}` + `${serverUrl}/help/formio-application-submission/${searchText}` ) .then((res) => { setFormId(res.submissionData._id); @@ -290,7 +290,7 @@ export function Helpdesk() { }); postData( - `${serverUrl}/help/application-form-submission/${formId}`, + `${serverUrl}/help/formio-application-submission/${formId}`, {} ) .then((res) => { diff --git a/app/client/src/routes/newApplicationForm.tsx b/app/client/src/routes/newApplicationForm.tsx index 355ca1c9..1b46a73a 100644 --- a/app/client/src/routes/newApplicationForm.tsx +++ b/app/client/src/routes/newApplicationForm.tsx @@ -16,7 +16,7 @@ import { SamEntity, useBapState } from "contexts/bap"; function createNewApplication(email: string, entity: SamEntity) { const { title, name } = getUserInfo(email, entity); - return postData(`${serverUrl}/api/application-form-submission/`, { + return postData(`${serverUrl}/api/formio-application-submission/`, { data: { last_updated_by: email, hidden_current_user_email: email, diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index f1f28db6..d83c4a89 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -193,7 +193,7 @@ router.get("/formio-application-submissions", storeBapComboKeys, (req, res) => { }); // --- post a new Application form submission to Forms.gov -router.post("/application-form-submission", storeBapComboKeys, (req, res) => { +router.post("/formio-application-submission", storeBapComboKeys, (req, res) => { const comboKey = req.body.data?.bap_hidden_entity_combo_key; if (enrollmentClosed) { @@ -226,7 +226,7 @@ router.post("/application-form-submission", storeBapComboKeys, (req, res) => { // --- get an existing Application form's schema and submission data from Forms.gov router.get( - "/application-form-submission/:id", + "/formio-application-submission/:id", verifyMongoObjectId, storeBapComboKeys, async (req, res) => { @@ -268,7 +268,7 @@ router.get( // --- post an update to an existing draft Application form submission to Forms.gov router.post( - "/application-form-submission/:id", + "/formio-application-submission/:id", verifyMongoObjectId, storeBapComboKeys, (req, res) => { @@ -402,7 +402,7 @@ router.post("/payment-form-submission", storeBapComboKeys, (req, res) => { }); }); -// --- TODO: WIP, as we'll eventually mirror `router.get("/application-form-submission/:id")` +// --- TODO: WIP, as we'll eventually mirror `router.get("/formio-application-submission/:id")` router.get("/payment-form-schema", storeBapComboKeys, (req, res) => { axiosFormio(req) .get(paymentFormApiPath) diff --git a/app/server/app/routes/help.js b/app/server/app/routes/help.js index bf9b0209..78fbd8ce 100644 --- a/app/server/app/routes/help.js +++ b/app/server/app/routes/help.js @@ -23,9 +23,9 @@ router.use(ensureHelpdesk); const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; -// --- get an existing application form's submission data from Forms.gov +// --- get an existing Application form's submission data from Forms.gov router.get( - "/application-form-submission/:id", + "/formio-application-submission/:id", verifyMongoObjectId, (req, res) => { const { id } = req.params; @@ -54,9 +54,9 @@ router.get( } ); -// --- change a submitted Forms.gov application form's submission state back to draft +// --- change a submitted Forms.gov Application form's submission state back to draft router.post( - "/application-form-submission/:id", + "/formio-application-submission/:id", verifyMongoObjectId, (req, res) => { const { id } = req.params; diff --git a/app/server/app/routes/status.js b/app/server/app/routes/status.js index 41871ee3..9f70ab40 100644 --- a/app/server/app/routes/status.js +++ b/app/server/app/routes/status.js @@ -13,7 +13,7 @@ router.get("/app", (req, res) => { res.json({ status: true }); }); -router.get("/bap", (req, res) => { +router.get("/bap-sam-data", (req, res) => { getSamData("CleanSchoolBus@erg.com", req) .then(() => { res.json({ status: true }); @@ -25,7 +25,7 @@ router.get("/bap", (req, res) => { const applicationFormApiPath = `${formioProjectUrl}/${formioApplicationFormPath}`; -router.get("/application-form", (req, res) => { +router.get("/formio-application-schema", (req, res) => { axiosFormio(req) .get(applicationFormApiPath) .then((axiosRes) => axiosRes.data) diff --git a/docs/csb-openapi.json b/docs/csb-openapi.json index 8f29a37f..65780fb2 100644 --- a/docs/csb-openapi.json +++ b/docs/csb-openapi.json @@ -21,9 +21,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/status/bap": { + "/status/bap-sam-data": { "get": { - "summary": "/status/bap", + "summary": "/status/bap-sam-data", "responses": { "200": { "description": "OK" @@ -33,9 +33,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/status/application-form": { + "/status/formio-application-schema": { "get": { - "summary": "/status/application-form", + "summary": "/status/formio-application-schema", "responses": { "200": { "description": "OK" @@ -142,9 +142,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/help/application-form-submission/{id}": { + "/help/formio-application-submission/{id}": { "get": { - "summary": "/help/application-form-submission/{id}", + "summary": "/help/formio-application-submission/{id}", "parameters": [ { "name": "id", @@ -192,7 +192,7 @@ "tags": [] }, "post": { - "summary": "/help/application-form-submission/{id}", + "summary": "/help/formio-application-submission/{id}", "parameters": [ { "name": "id", @@ -495,9 +495,9 @@ ] } }, - "/api/application-form-submission": { + "/api/formio-application-submission": { "post": { - "summary": "/api/application-form-submission", + "summary": "/api/formio-application-submission", "responses": { "200": { "description": "OK", @@ -539,9 +539,9 @@ "parameters": [{ "$ref": "#/components/parameters/scan" }] } }, - "/api/application-form-submission/{id}": { + "/api/formio-application-submission/{id}": { "get": { - "summary": "/api/application-form-submission/{id}", + "summary": "/api/formio-application-submission/{id}", "parameters": [ { "name": "id", @@ -589,7 +589,7 @@ "tags": [] }, "post": { - "summary": "/api/application-form-submission/{id}", + "summary": "/api/formio-application-submission/{id}", "parameters": [ { "name": "id", From 844ac287758daafd3165e429d15c5abb3314de0d Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 16:28:31 -0400 Subject: [PATCH 040/128] Prefix all payment request API endpoints with formio --- app/client/src/routes/allRebates.tsx | 4 +- app/client/src/routes/paymentForm.tsx | 2 +- app/server/app/routes/api.js | 90 +++++++++++++++------------ 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 6282ebdb..db86a618 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -79,7 +79,7 @@ function useFetchedFormioPaymentRequestSubmissions() { dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST" }); - getData(`${serverUrl}/api/payment-form-submissions`) + getData(`${serverUrl}/api/formio-payment-request-submissions`) .then((res) => { dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_SUCCESS", @@ -117,7 +117,7 @@ function createNewPaymentRequest( schoolDistricPrioritized, } = applicationData; - return postData(`${serverUrl}/api/payment-form-submission/`, { + return postData(`${serverUrl}/api/formio-payment-request-submission/`, { data: { last_updated_by: email, hidden_current_user_email: email, diff --git a/app/client/src/routes/paymentForm.tsx b/app/client/src/routes/paymentForm.tsx index 9c226d78..8455a817 100644 --- a/app/client/src/routes/paymentForm.tsx +++ b/app/client/src/routes/paymentForm.tsx @@ -22,7 +22,7 @@ export function PaymentForm() { }); useEffect(() => { - getData(`${serverUrl}/api/payment-form-schema`) + getData(`${serverUrl}/api/formio-payment-request-schema`) .then((res) => setPaymentFormSchema({ status: "success", data: res })) .catch((err) => setPaymentFormSchema({ status: "failure", data: null })); }, []); diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index d83c4a89..3c8c19b9 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -355,55 +355,63 @@ router.get("/:id/:comboKey/storage/s3", storeBapComboKeys, (req, res) => { const paymentFormApiPath = `${formioProjectUrl}/${formioPaymentFormPath}`; -// --- get all Payment Request form submissions from Forms.gov -router.get("/payment-form-submissions", storeBapComboKeys, (req, res) => { - const userSubmissionsUrl = - `${paymentFormApiPath}/submission` + - `?sort=-modified` + - `&limit=1000000` + - `&data.bap_hidden_entity_combo_key=${req.bapComboKeys.join( - "&data.bap_hidden_entity_combo_key=" - )}`; +// --- get user's Payment Request form submissions from Forms.gov +router.get( + "/formio-payment-request-submissions", + storeBapComboKeys, + (req, res) => { + const userSubmissionsUrl = + `${paymentFormApiPath}/submission` + + `?sort=-modified` + + `&limit=1000000` + + `&data.bap_hidden_entity_combo_key=${req.bapComboKeys.join( + "&data.bap_hidden_entity_combo_key=" + )}`; - axiosFormio(req) - .get(userSubmissionsUrl) - .then((axiosRes) => axiosRes.data) - .then((submissions) => res.json(submissions)) - .catch((error) => { - const message = `Error getting Forms.gov Payment Request form submissions`; - return res.status(error?.response?.status || 500).json({ message }); - }); -}); + axiosFormio(req) + .get(userSubmissionsUrl) + .then((axiosRes) => axiosRes.data) + .then((submissions) => res.json(submissions)) + .catch((error) => { + const message = `Error getting Forms.gov Payment Request form submissions`; + return res.status(error?.response?.status || 500).json({ message }); + }); + } +); // --- post a new Payment Request form submission to Forms.gov -router.post("/payment-form-submission", storeBapComboKeys, (req, res) => { - const comboKey = req.body.data?.bap_hidden_entity_combo_key; +router.post( + "/formio-payment-request-submission", + storeBapComboKeys, + (req, res) => { + const comboKey = req.body.data?.bap_hidden_entity_combo_key; - // verify post data includes one of user's BAP combo keys - if (!req.bapComboKeys.includes(comboKey)) { - const message = `User with email ${req.user.mail} attempted to post a new Payment Request form without a matching BAP combo key`; - log({ level: "error", message, req }); - return res.status(401).json({ message: "Unauthorized" }); - } + // verify post data includes one of user's BAP combo keys + if (!req.bapComboKeys.includes(comboKey)) { + const message = `User with email ${req.user.mail} attempted to post a new Payment Request form without a matching BAP combo key`; + log({ level: "error", message, req }); + return res.status(401).json({ message: "Unauthorized" }); + } - // add custom metadata to track formio submissions from wrapper - req.body.metadata = { - ...req.body.metadata, - ...formioCsbMetadata, - }; + // add custom metadata to track formio submissions from wrapper + req.body.metadata = { + ...req.body.metadata, + ...formioCsbMetadata, + }; - axiosFormio(req) - .post(`${paymentFormApiPath}/submission`, req.body) - .then((axiosRes) => axiosRes.data) - .then((submission) => res.json(submission)) - .catch((error) => { - const message = `Error posting Forms.gov Payment Request form submission`; - return res.status(error?.response?.status || 500).json({ message }); - }); -}); + axiosFormio(req) + .post(`${paymentFormApiPath}/submission`, req.body) + .then((axiosRes) => axiosRes.data) + .then((submission) => res.json(submission)) + .catch((error) => { + const message = `Error posting Forms.gov Payment Request form submission`; + return res.status(error?.response?.status || 500).json({ message }); + }); + } +); // --- TODO: WIP, as we'll eventually mirror `router.get("/formio-application-submission/:id")` -router.get("/payment-form-schema", storeBapComboKeys, (req, res) => { +router.get("/formio-payment-request-schema", storeBapComboKeys, (req, res) => { axiosFormio(req) .get(paymentFormApiPath) .then((axiosRes) => axiosRes.data) From fd2d8736c3247353551635a7a6832520ad3f1e71 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 16:31:40 -0400 Subject: [PATCH 041/128] Rename PaymentForm to PaymentRequestForm --- app/client/src/components/app.tsx | 4 ++-- ...paymentForm.tsx => paymentRequestForm.tsx} | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) rename app/client/src/routes/{paymentForm.tsx => paymentRequestForm.tsx} (71%) diff --git a/app/client/src/components/app.tsx b/app/client/src/components/app.tsx index 5b255ed0..6e7d1cb8 100644 --- a/app/client/src/components/app.tsx +++ b/app/client/src/components/app.tsx @@ -28,7 +28,7 @@ import { Helpdesk } from "routes/helpdesk"; import { AllRebates } from "routes/allRebates"; import { NewApplicationForm } from "routes/newApplicationForm"; import { ApplicationForm } from "routes/applicationForm"; -import { PaymentForm } from "routes/paymentForm"; +import { PaymentRequestForm } from "routes/paymentRequestForm"; import { useContentState, useContentDispatch } from "contexts/content"; import { useUserState, useUserDispatch } from "contexts/user"; import { useDialogDispatch, useDialogState } from "contexts/dialog"; @@ -291,7 +291,7 @@ export function App() { } /> } /> } /> - } /> + } /> } /> diff --git a/app/client/src/routes/paymentForm.tsx b/app/client/src/routes/paymentRequestForm.tsx similarity index 71% rename from app/client/src/routes/paymentForm.tsx rename to app/client/src/routes/paymentRequestForm.tsx index 8455a817..f5a9eb48 100644 --- a/app/client/src/routes/paymentForm.tsx +++ b/app/client/src/routes/paymentRequestForm.tsx @@ -13,28 +13,32 @@ type FormSchema = | { status: "success"; data: object } | { status: "failure"; data: null }; -export function PaymentForm() { +export function PaymentRequestForm() { const { id } = useParams<"id">(); - const [paymentFormSchema, setPaymentFormSchema] = useState({ + const [paymentRequestSchema, setPaymentRequestSchema] = useState({ status: "idle", data: null, }); useEffect(() => { getData(`${serverUrl}/api/formio-payment-request-schema`) - .then((res) => setPaymentFormSchema({ status: "success", data: res })) - .catch((err) => setPaymentFormSchema({ status: "failure", data: null })); + .then((res) => { + setPaymentRequestSchema({ status: "success", data: res }); + }) + .catch((err) => { + setPaymentRequestSchema({ status: "failure", data: null }); + }); }, []); if ( - paymentFormSchema.status === "idle" || - paymentFormSchema.status === "pending" + paymentRequestSchema.status === "idle" || + paymentRequestSchema.status === "pending" ) { return ; } - if (paymentFormSchema.status === "failure") { + if (paymentRequestSchema.status === "failure") { return ( ); @@ -55,7 +59,7 @@ export function PaymentForm() { - + ); } From 2be39d2d60a352cba898fc9bc09832acbe7e4cd7 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 16:55:23 -0400 Subject: [PATCH 042/128] Update most data across both apps to be prefixed with Bap or Formio depending on its source --- app/client/src/config.tsx | 3 +- app/client/src/contexts/bap.tsx | 12 ++-- app/client/src/contexts/forms.tsx | 66 ++++++++++---------- app/client/src/routes/allRebates.tsx | 48 +++++++------- app/client/src/routes/applicationForm.tsx | 16 ++--- app/client/src/routes/helpdesk.tsx | 26 ++++---- app/client/src/routes/newApplicationForm.tsx | 4 +- app/client/src/utilities.tsx | 8 +-- app/server/app/utilities/bap.js | 16 ++--- 9 files changed, 102 insertions(+), 97 deletions(-) diff --git a/app/client/src/config.tsx b/app/client/src/config.tsx index f1b96de1..692089c8 100644 --- a/app/client/src/config.tsx +++ b/app/client/src/config.tsx @@ -56,7 +56,8 @@ export const messages = { bapNoSamResults: "No SAM.gov records match your email. Only Government and Electronic Business SAM.gov Points of Contacts (and alternates) may edit and submit Clean School Bus Rebate Forms.", applicationSubmissionsError: "Error loading Application form submissions.", - paymentSubmissionsError: "Error loading Payment Request form submissions.", + paymentRequestSubmissionsError: + "Error loading Payment Request form submissions.", newApplication: "Please select the “New Application” button above to create your first rebate application.", helpdeskApplicationSubmissionError: diff --git a/app/client/src/contexts/bap.tsx b/app/client/src/contexts/bap.tsx index b480dcaf..5e336d0d 100644 --- a/app/client/src/contexts/bap.tsx +++ b/app/client/src/contexts/bap.tsx @@ -10,7 +10,7 @@ type Props = { children: ReactNode; }; -export type SamEntity = { +export type BapSamEntity = { ENTITY_COMBO_KEY__c: string; UNIQUE_ENTITY_ID__c: string; ENTITY_EFT_INDICATOR__c: string; @@ -42,7 +42,7 @@ export type SamEntity = { attributes: { type: string; url: string }; }; -type ApplicationFormSubmission = { +type BapApplicationSubmission = { CSB_Form_ID__c: string; // MongoDB ObjectId string CSB_Modified_Full_String__c: string; // ISO 8601 date string UEI_EFTI_Combo_Key__c: string; @@ -67,13 +67,13 @@ type State = { status: "success"; data: | { results: false; entities: [] } - | { results: true; entities: SamEntity[] }; + | { results: true; entities: BapSamEntity[] }; } | { status: "failure"; data: {} }; applicationSubmissions: | { status: "idle"; data: {} } | { status: "pending"; data: {} } - | { status: "success"; data: ApplicationFormSubmission[] } + | { status: "success"; data: BapApplicationSubmission[] } | { status: "failure"; data: {} }; }; @@ -84,14 +84,14 @@ type Action = payload: { samEntities: | { results: false; entities: [] } - | { results: true; entities: SamEntity[] }; + | { results: true; entities: BapSamEntity[] }; }; } | { type: "FETCH_BAP_SAM_DATA_FAILURE" } | { type: "FETCH_BAP_APPLICATION_SUBMISSIONS_REQUEST" } | { type: "FETCH_BAP_APPLICATION_SUBMISSIONS_SUCCESS"; - payload: { applicationSubmissions: ApplicationFormSubmission[] }; + payload: { applicationSubmissions: BapApplicationSubmission[] }; } | { type: "FETCH_BAP_APPLICATION_SUBMISSIONS_FAILURE" }; diff --git a/app/client/src/contexts/forms.tsx b/app/client/src/contexts/forms.tsx index 58849fc7..83f2f18d 100644 --- a/app/client/src/contexts/forms.tsx +++ b/app/client/src/contexts/forms.tsx @@ -10,7 +10,7 @@ type Props = { children: ReactNode; }; -export type ApplicationFormSubmission = { +export type FormioApplicationSubmission = { [field: string]: unknown; _id: string; // MongoDB ObjectId string state: "submitted" | "draft"; @@ -43,7 +43,7 @@ export type ApplicationFormSubmission = { }; }; -type PaymentFormSubmission = { +type FormioPaymentRequestSubmission = { [field: string]: unknown; _id: string; // MongoDB ObjectId string state: "submitted" | "draft"; @@ -54,98 +54,98 @@ type PaymentFormSubmission = { }; type State = { - applicationFormSubmissions: + applicationSubmissions: | { status: "idle"; data: [] } | { status: "pending"; data: [] } - | { status: "success"; data: ApplicationFormSubmission[] } + | { status: "success"; data: FormioApplicationSubmission[] } | { status: "failure"; data: [] }; - paymentFormSubmissions: + paymentRequestSubmissions: | { status: "idle"; data: [] } | { status: "pending"; data: [] } - | { status: "success"; data: PaymentFormSubmission[] } + | { status: "success"; data: FormioPaymentRequestSubmission[] } | { status: "failure"; data: [] }; }; type Action = - | { type: "FETCH_APPLICATION_FORM_SUBMISSIONS_REQUEST" } + | { type: "FETCH_FORMIO_APPLICATION_SUBMISSIONS_REQUEST" } | { - type: "FETCH_APPLICATION_FORM_SUBMISSIONS_SUCCESS"; + type: "FETCH_FORMIO_APPLICATION_SUBMISSIONS_SUCCESS"; payload: { - applicationFormSubmissions: ApplicationFormSubmission[]; + applicationSubmissions: FormioApplicationSubmission[]; }; } - | { type: "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE" } - | { type: "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST" } - | { type: "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST" } + | { type: "FETCH_FORMIO_APPLICATION_SUBMISSIONS_FAILURE" } + | { type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_REQUEST" } + | { type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_REQUEST" } | { - type: "FETCH_PAYMENT_FORM_SUBMISSIONS_SUCCESS"; + type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_SUCCESS"; payload: { - paymentFormSubmissions: PaymentFormSubmission[]; + paymentRequestSubmissions: FormioPaymentRequestSubmission[]; }; } - | { type: "FETCH_PAYMENT_FORM_SUBMISSIONS_FAILURE" }; + | { type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_FAILURE" }; const StateContext = createContext(undefined); const DispatchContext = createContext | undefined>(undefined); function reducer(state: State, action: Action): State { switch (action.type) { - case "FETCH_APPLICATION_FORM_SUBMISSIONS_REQUEST": { + case "FETCH_FORMIO_APPLICATION_SUBMISSIONS_REQUEST": { return { ...state, - applicationFormSubmissions: { + applicationSubmissions: { status: "pending", data: [], }, }; } - case "FETCH_APPLICATION_FORM_SUBMISSIONS_SUCCESS": { - const { applicationFormSubmissions } = action.payload; + case "FETCH_FORMIO_APPLICATION_SUBMISSIONS_SUCCESS": { + const { applicationSubmissions } = action.payload; return { ...state, - applicationFormSubmissions: { + applicationSubmissions: { status: "success", - data: applicationFormSubmissions, + data: applicationSubmissions, }, }; } - case "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE": { + case "FETCH_FORMIO_APPLICATION_SUBMISSIONS_FAILURE": { return { ...state, - applicationFormSubmissions: { + applicationSubmissions: { status: "failure", data: [], }, }; } - case "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST": { + case "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_REQUEST": { return { ...state, - paymentFormSubmissions: { + paymentRequestSubmissions: { status: "pending", data: [], }, }; } - case "FETCH_PAYMENT_FORM_SUBMISSIONS_SUCCESS": { - const { paymentFormSubmissions } = action.payload; + case "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_SUCCESS": { + const { paymentRequestSubmissions } = action.payload; return { ...state, - paymentFormSubmissions: { + paymentRequestSubmissions: { status: "success", - data: paymentFormSubmissions, + data: paymentRequestSubmissions, }, }; } - case "FETCH_PAYMENT_FORM_SUBMISSIONS_FAILURE": { + case "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_FAILURE": { return { ...state, - paymentFormSubmissions: { + paymentRequestSubmissions: { status: "failure", data: [], }, @@ -161,11 +161,11 @@ function reducer(state: State, action: Action): State { export function FormsProvider({ children }: Props) { const initialState: State = { - applicationFormSubmissions: { + applicationSubmissions: { status: "idle", data: [], }, - paymentFormSubmissions: { + paymentRequestSubmissions: { status: "idle", data: [], }, diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index db86a618..83ddabd9 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -10,9 +10,9 @@ import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; -import { SamEntity, useBapState, useBapDispatch } from "contexts/bap"; +import { BapSamEntity, useBapState, useBapDispatch } from "contexts/bap"; import { - ApplicationFormSubmission, + FormioApplicationSubmission, useFormsState, useFormsDispatch, } from "contexts/forms"; @@ -27,17 +27,17 @@ function useFetchedFormioApplicationSubmissions() { return; } - dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_REQUEST" }); + dispatch({ type: "FETCH_FORMIO_APPLICATION_SUBMISSIONS_REQUEST" }); getData(`${serverUrl}/api/formio-application-submissions`) .then((res) => { dispatch({ - type: "FETCH_APPLICATION_FORM_SUBMISSIONS_SUCCESS", - payload: { applicationFormSubmissions: res }, + type: "FETCH_FORMIO_APPLICATION_SUBMISSIONS_SUCCESS", + payload: { applicationSubmissions: res }, }); }) .catch((err) => { - dispatch({ type: "FETCH_APPLICATION_FORM_SUBMISSIONS_FAILURE" }); + dispatch({ type: "FETCH_FORMIO_APPLICATION_SUBMISSIONS_FAILURE" }); }); }, [samEntities, dispatch]); } @@ -77,26 +77,26 @@ function useFetchedFormioPaymentRequestSubmissions() { return; } - dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_REQUEST" }); + dispatch({ type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_REQUEST" }); getData(`${serverUrl}/api/formio-payment-request-submissions`) .then((res) => { dispatch({ - type: "FETCH_PAYMENT_FORM_SUBMISSIONS_SUCCESS", - payload: { paymentFormSubmissions: res }, + type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_SUCCESS", + payload: { paymentRequestSubmissions: res }, }); }) .catch((err) => { - dispatch({ type: "FETCH_PAYMENT_FORM_SUBMISSIONS_FAILURE" }); + dispatch({ type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_FAILURE" }); }); }, [samEntities, dispatch]); } function createNewPaymentRequest( email: string, - entity: SamEntity, + entity: BapSamEntity, rebateId: string, - applicationData: ApplicationFormSubmission["data"] + applicationData: FormioApplicationSubmission["data"] ) { const { title, name } = getUserInfo(email, entity); const { @@ -163,8 +163,10 @@ export function AllRebates() { const { csbData, epaUserData } = useUserState(); const { samEntities, applicationSubmissions: bapApplicationSubmissions } = useBapState(); - const { applicationFormSubmissions, paymentFormSubmissions } = - useFormsState(); + const { + applicationSubmissions: formioApplicationSubmissions, + paymentRequestSubmissions: formioPaymentRequestSubmissions, + } = useFormsState(); const [message, setMessage] = useState<{ displayed: boolean; @@ -188,10 +190,10 @@ export function AllRebates() { samEntities.status === "pending" || bapApplicationSubmissions.status === "idle" || bapApplicationSubmissions.status === "pending" || - applicationFormSubmissions.status === "idle" || - applicationFormSubmissions.status === "pending" || - paymentFormSubmissions.status === "idle" || - paymentFormSubmissions.status === "pending" + formioApplicationSubmissions.status === "idle" || + formioApplicationSubmissions.status === "pending" || + formioPaymentRequestSubmissions.status === "idle" || + formioPaymentRequestSubmissions.status === "pending" ) { return ; } @@ -202,13 +204,15 @@ export function AllRebates() { if ( bapApplicationSubmissions.status === "failure" || - applicationFormSubmissions.status === "failure" + formioApplicationSubmissions.status === "failure" ) { return ; } - if (paymentFormSubmissions.status === "failure") { - return ; + if (formioPaymentRequestSubmissions.status === "failure") { + return ( + + ); } const { enrollmentClosed } = csbData.data; @@ -219,7 +223,7 @@ export function AllRebates() { * BAP, so we can include CSB rebate status, CSB review item ID, and last * updated datetime. */ - const submissions = applicationFormSubmissions.data.map((formioSub) => { + const submissions = formioApplicationSubmissions.data.map((formioSub) => { const match = bapApplicationSubmissions.data.find((bapSubmission) => { return bapSubmission.CSB_Form_ID__c === formioSub._id; }); diff --git a/app/client/src/routes/applicationForm.tsx b/app/client/src/routes/applicationForm.tsx index e83ec01e..66aefe6b 100644 --- a/app/client/src/routes/applicationForm.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -239,7 +239,7 @@ function ApplicationFormContent() { useBapState(); const dispatch = useApplicationFormDispatch(); - const [applicationFormSubmission, setApplicationFormSubmission] = + const [formioApplicationSubmission, setFormioApplicationSubmission] = useState({ status: "idle", data: { @@ -267,7 +267,7 @@ function ApplicationFormContent() { useState({}); useEffect(() => { - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "pending", data: { userAccess: false, @@ -301,13 +301,13 @@ function ApplicationFormContent() { return data; }); - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "success", data: res, }); }) .catch((err) => { - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "failure", data: { userAccess: false, @@ -318,19 +318,19 @@ function ApplicationFormContent() { }); }, [id]); - if (applicationFormSubmission.status === "idle") { + if (formioApplicationSubmission.status === "idle") { return null; } - if (applicationFormSubmission.status === "pending") { + if (formioApplicationSubmission.status === "pending") { return ; } const { userAccess, formSchema, submissionData } = - applicationFormSubmission.data; + formioApplicationSubmission.data; if ( - applicationFormSubmission.status === "failure" || + formioApplicationSubmission.status === "failure" || !userAccess || !formSchema || !submissionData diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 25cb7297..5028abfe 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -66,7 +66,7 @@ export function Helpdesk() { const dispatch = useDialogDispatch(); const helpdeskAccess = useHelpdeskAccess(); - const [applicationFormSubmission, setApplicationFormSubmission] = + const [formioApplicationSubmission, setFormioApplicationSubmission] = useState({ status: "idle", data: { @@ -90,7 +90,7 @@ export function Helpdesk() { const { enrollmentClosed } = csbData.data; - const { formSchema, submissionData } = applicationFormSubmission.data; + const { formSchema, submissionData } = formioApplicationSubmission.data; return ( <> @@ -111,7 +111,7 @@ export function Helpdesk() { setFormId(""); setFormDisplayed(false); - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "pending", data: { formSchema: null, @@ -124,14 +124,14 @@ export function Helpdesk() { ) .then((res) => { setFormId(res.submissionData._id); - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "success", data: res, }); }) .catch((err) => { setFormId(""); - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "failure", data: { formSchema: null, @@ -159,9 +159,9 @@ export function Helpdesk() { - {applicationFormSubmission.status === "pending" && } + {formioApplicationSubmission.status === "pending" && } - {applicationFormSubmission.status === "failure" && ( + {formioApplicationSubmission.status === "failure" && ( )} - {applicationFormSubmission.status === "success" && + {formioApplicationSubmission.status === "success" && formSchema && submissionData && ( <> @@ -281,7 +281,7 @@ export function Helpdesk() { confirmedAction: () => { setFormDisplayed(false); - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "pending", data: { formSchema: null, @@ -294,13 +294,13 @@ export function Helpdesk() { {} ) .then((res) => { - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "success", data: res, }); }) .catch((err) => { - setApplicationFormSubmission({ + setFormioApplicationSubmission({ status: "failure", data: { formSchema: null, diff --git a/app/client/src/routes/newApplicationForm.tsx b/app/client/src/routes/newApplicationForm.tsx index 1b46a73a..198c8cc4 100644 --- a/app/client/src/routes/newApplicationForm.tsx +++ b/app/client/src/routes/newApplicationForm.tsx @@ -11,9 +11,9 @@ import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; -import { SamEntity, useBapState } from "contexts/bap"; +import { BapSamEntity, useBapState } from "contexts/bap"; -function createNewApplication(email: string, entity: SamEntity) { +function createNewApplication(email: string, entity: BapSamEntity) { const { title, name } = getUserInfo(email, entity); return postData(`${serverUrl}/api/formio-application-submission/`, { diff --git a/app/client/src/utilities.tsx b/app/client/src/utilities.tsx index d28f403b..9d4fe607 100644 --- a/app/client/src/utilities.tsx +++ b/app/client/src/utilities.tsx @@ -1,10 +1,10 @@ -import { SamEntity } from "contexts/bap"; +import { BapSamEntity } from "contexts/bap"; /** * Returns a user’s title and name when provided an email address and a SAM.gov * entity/record. */ -export function getUserInfo(email: string, entity: SamEntity) { +export function getUserInfo(email: string, entity: BapSamEntity) { const samEmailFields = [ "ELEC_BUS_POC_EMAIL__c", "ALT_ELEC_BUS_POC_EMAIL__c", @@ -30,7 +30,7 @@ export function getUserInfo(email: string, entity: SamEntity) { const fieldPrefix = matchedEmailField?.split("_EMAIL__c").shift(); return { - title: entity[`${fieldPrefix}_TITLE__c` as keyof SamEntity] as string, - name: entity[`${fieldPrefix}_NAME__c` as keyof SamEntity] as string, + title: entity[`${fieldPrefix}_TITLE__c` as keyof BapSamEntity] as string, + name: entity[`${fieldPrefix}_NAME__c` as keyof BapSamEntity] as string, }; } diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 31c1d375..4d98e62b 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -7,7 +7,7 @@ const express = require("express"); const log = require("../utilities/logger"); /** - * @typedef {Object} SamEntity + * @typedef {Object} BapSamEntity * @property {string} ENTITY_COMBO_KEY__c * @property {string} ENTITY_STATUS__c * @property {string} UNIQUE_ENTITY_ID__c @@ -37,7 +37,7 @@ const log = require("../utilities/logger"); */ /** - * @typedef {Object} ApplicationFormSubmission + * @typedef {Object} BapApplicationSubmission * @property {string} CSB_Form_ID__c * @property {string} CSB_Modified_Full_String__c * @property {string} UEI_EFTI_Combo_Key__c @@ -91,7 +91,7 @@ function setupConnection(app) { * Uses cached JSforce connection to query the BAP for SAM.gov entities. * @param {string} email * @param {express.Request} req - * @returns {Promise} collection of SAM.gov entities + * @returns {Promise} collection of SAM.gov entities */ async function queryForSamEntities(email, req) { /** @type {jsforce.Connection} */ @@ -193,9 +193,9 @@ function getComboKeys(email, req) { * Uses cached JSforce connection to query the BAP for application form submissions. * @param {string[]} comboKeys * @param {express.Request} req - * @returns {Promise} collection of application form submissions + * @returns {Promise} collection of application form submissions */ -async function queryForApplicationFormSubmissions(comboKeys, req) { +async function queryForApplicationSubmissions(comboKeys, req) { /** @type {jsforce.Connection} */ const bapConnection = req.app.locals.bapConnection; return await bapConnection @@ -227,7 +227,7 @@ function getApplicationSubmissionsData(comboKeys, req) { log({ level: "info", message }); return setupConnection(req.app) - .then(() => queryForApplicationFormSubmissions(comboKeys, req)) + .then(() => queryForApplicationSubmissions(comboKeys, req)) .catch((err) => { const message = `BAP Error: ${err}`; log({ level: "error", message, req }); @@ -235,7 +235,7 @@ function getApplicationSubmissionsData(comboKeys, req) { }); } - return queryForApplicationFormSubmissions(comboKeys, req).catch((err) => { + return queryForApplicationSubmissions(comboKeys, req).catch((err) => { if (err?.toString() === "invalid_grant: expired access/refresh token") { const message = `BAP access token expired`; log({ level: "info", message, req }); @@ -245,7 +245,7 @@ function getApplicationSubmissionsData(comboKeys, req) { } return setupConnection(req.app) - .then(() => queryForApplicationFormSubmissions(comboKeys, req)) + .then(() => queryForApplicationSubmissions(comboKeys, req)) .catch((retryErr) => { const message = `BAP Error: ${retryErr}`; log({ level: "error", message, req }); From 7de5d7a07e030630d8ca58b0bcaf877e4ac8f06e Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 17:03:14 -0400 Subject: [PATCH 043/128] Rename Forms contest to Formio --- app/client/src/contexts/{forms.tsx => formio.tsx} | 14 +++++++------- app/client/src/index.tsx | 6 +++--- app/client/src/routes/allRebates.tsx | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) rename app/client/src/contexts/{forms.tsx => formio.tsx} (93%) diff --git a/app/client/src/contexts/forms.tsx b/app/client/src/contexts/formio.tsx similarity index 93% rename from app/client/src/contexts/forms.tsx rename to app/client/src/contexts/formio.tsx index 83f2f18d..586610dd 100644 --- a/app/client/src/contexts/forms.tsx +++ b/app/client/src/contexts/formio.tsx @@ -159,7 +159,7 @@ function reducer(state: State, action: Action): State { } } -export function FormsProvider({ children }: Props) { +export function FormioProvider({ children }: Props) { const initialState: State = { applicationSubmissions: { status: "idle", @@ -183,12 +183,12 @@ export function FormsProvider({ children }: Props) { } /** - * Returns state stored in `FormsProvider` context component. + * Returns state stored in `FormioProvider` context component. */ -export function useFormsState() { +export function useFormioState() { const context = useContext(StateContext); if (context === undefined) { - const message = `useFormsState must be called within a FormsProvider`; + const message = `useFormioState must be called within a FormioProvider`; throw new Error(message); } return context; @@ -196,12 +196,12 @@ export function useFormsState() { /** * Returns `dispatch` method for dispatching actions to update state stored in - * `FormsProvider` context component. + * `FormioProvider` context component. */ -export function useFormsDispatch() { +export function useFormioDispatch() { const context = useContext(DispatchContext); if (context === undefined) { - const message = `useFormsDispatch must be used within a FormsProvider`; + const message = `useFormioDispatch must be used within a FormioProvider`; throw new Error(message); } return context; diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index e1e92aa3..993e7ea9 100644 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -5,7 +5,7 @@ import reportWebVitals from "./reportWebVitals"; import { ContentProvider } from "contexts/content"; import { UserProvider } from "contexts/user"; import { BapProvider } from "contexts/bap"; -import { FormsProvider } from "contexts/forms"; +import { FormioProvider } from "contexts/formio"; import { DialogProvider } from "contexts/dialog"; import { ErrorBoundary } from "components/errorBoundary"; import { App } from "components/app"; @@ -19,11 +19,11 @@ render( - + - + diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 83ddabd9..cdfc58ea 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -13,14 +13,14 @@ import { useUserState } from "contexts/user"; import { BapSamEntity, useBapState, useBapDispatch } from "contexts/bap"; import { FormioApplicationSubmission, - useFormsState, - useFormsDispatch, -} from "contexts/forms"; + useFormioState, + useFormioDispatch, +} from "contexts/formio"; /** Custom hook to fetch Application form submissions from Forms.gov */ function useFetchedFormioApplicationSubmissions() { const { samEntities } = useBapState(); - const dispatch = useFormsDispatch(); + const dispatch = useFormioDispatch(); useEffect(() => { if (samEntities.status !== "success" || !samEntities.data.results) { @@ -70,7 +70,7 @@ function useFetchedBapApplicationSubmissions() { /** Custom hook to fetch Payment Request form submissions from Forms.gov */ function useFetchedFormioPaymentRequestSubmissions() { const { samEntities } = useBapState(); - const dispatch = useFormsDispatch(); + const dispatch = useFormioDispatch(); useEffect(() => { if (samEntities.status !== "success" || !samEntities.data.results) { @@ -166,7 +166,7 @@ export function AllRebates() { const { applicationSubmissions: formioApplicationSubmissions, paymentRequestSubmissions: formioPaymentRequestSubmissions, - } = useFormsState(); + } = useFormioState(); const [message, setMessage] = useState<{ displayed: boolean; From 49708649a2146e5f4a62eb77701f9fe0105b7cbd Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 17:15:59 -0400 Subject: [PATCH 044/128] Move CSB data out of user context into its own context component --- app/client/src/components/dashboard.tsx | 8 +- app/client/src/contexts/csb.tsx | 118 +++++++++++++++++++ app/client/src/contexts/user.tsx | 54 --------- app/client/src/index.tsx | 17 +-- app/client/src/routes/allRebates.tsx | 4 +- app/client/src/routes/applicationForm.tsx | 4 +- app/client/src/routes/helpdesk.tsx | 4 +- app/client/src/routes/newApplicationForm.tsx | 4 +- 8 files changed, 145 insertions(+), 68 deletions(-) create mode 100644 app/client/src/contexts/csb.tsx diff --git a/app/client/src/components/dashboard.tsx b/app/client/src/components/dashboard.tsx index 354a26c6..23384a07 100644 --- a/app/client/src/components/dashboard.tsx +++ b/app/client/src/components/dashboard.tsx @@ -14,7 +14,8 @@ import { } from "../config"; import { useHelpdeskAccess } from "components/app"; import { Loading } from "components/loading"; -import { useUserState, useUserDispatch } from "contexts/user"; +import { useUserState } from "contexts/user"; +import { useCsbState, useCsbDispatch } from "contexts/csb"; import { useBapState, useBapDispatch } from "contexts/bap"; import { Action, useDialogDispatch } from "contexts/dialog"; @@ -25,7 +26,7 @@ Formio.use(uswds); /** Custom hook to fetch CSP app specific data */ function useFetchedCsbData() { - const dispatch = useUserDispatch(); + const dispatch = useCsbDispatch(); useEffect(() => { dispatch({ type: "FETCH_CSB_DATA_REQUEST" }); @@ -105,7 +106,8 @@ export function Dashboard() { const { pathname } = useLocation(); const navigate = useNavigate(); - const { csbData, epaUserData } = useUserState(); + const { epaUserData } = useUserState(); + const { csbData } = useCsbState(); const { samEntities } = useBapState(); const dispatch = useDialogDispatch(); const helpdeskAccess = useHelpdeskAccess(); diff --git a/app/client/src/contexts/csb.tsx b/app/client/src/contexts/csb.tsx new file mode 100644 index 00000000..378bdfca --- /dev/null +++ b/app/client/src/contexts/csb.tsx @@ -0,0 +1,118 @@ +import { + Dispatch, + ReactNode, + createContext, + useContext, + useReducer, +} from "react"; + +type Props = { + children: ReactNode; +}; + +type CsbData = { + enrollmentClosed: boolean; +}; + +type State = { + csbData: + | { status: "idle"; data: {} } + | { status: "pending"; data: {} } + | { status: "success"; data: CsbData } + | { status: "failure"; data: {} }; +}; + +type Action = + | { type: "FETCH_CSB_DATA_REQUEST" } + | { + type: "FETCH_CSB_DATA_SUCCESS"; + payload: { csbData: CsbData }; + } + | { type: "FETCH_CSB_DATA_FAILURE" }; + +const StateContext = createContext(undefined); +const DispatchContext = createContext | undefined>(undefined); + +function reducer(state: State, action: Action): State { + switch (action.type) { + case "FETCH_CSB_DATA_REQUEST": { + return { + ...state, + csbData: { + status: "pending", + data: {}, + }, + }; + } + + case "FETCH_CSB_DATA_SUCCESS": { + const { csbData } = action.payload; + return { + ...state, + csbData: { + status: "success", + data: csbData, + }, + }; + } + + case "FETCH_CSB_DATA_FAILURE": { + return { + ...state, + csbData: { + status: "failure", + data: {}, + }, + }; + } + + default: { + const message = `Unhandled action type: ${action}`; + throw new Error(message); + } + } +} + +export function CsbProvider({ children }: Props) { + const initialState: State = { + csbData: { + status: "idle", + data: {}, + }, + }; + + const [state, dispatch] = useReducer(reducer, initialState); + + return ( + + + {children} + + + ); +} + +/** + * Returns state stored in `CsbProvider` context component. + */ +export function useCsbState() { + const context = useContext(StateContext); + if (context === undefined) { + const message = `useCsbState must be called within a CsbProvider`; + throw new Error(message); + } + return context; +} + +/** + * Returns `dispatch` method for dispatching actions to update state stored in + * `CsbProvider` context component. + */ +export function useCsbDispatch() { + const context = useContext(DispatchContext); + if (context === undefined) { + const message = `useCsbDispatch must be used within a CsbProvider`; + throw new Error(message); + } + return context; +} diff --git a/app/client/src/contexts/user.tsx b/app/client/src/contexts/user.tsx index 81897944..b15b533e 100644 --- a/app/client/src/contexts/user.tsx +++ b/app/client/src/contexts/user.tsx @@ -10,10 +10,6 @@ type Props = { children: ReactNode; }; -type CsbData = { - enrollmentClosed: boolean; -}; - type EpaUserData = { mail: string; memberof: string; @@ -23,11 +19,6 @@ type EpaUserData = { type State = { isAuthenticating: boolean; isAuthenticated: boolean; - csbData: - | { status: "idle"; data: {} } - | { status: "pending"; data: {} } - | { status: "success"; data: CsbData } - | { status: "failure"; data: {} }; epaUserData: | { status: "idle"; data: {} } | { status: "pending"; data: {} } @@ -38,12 +29,6 @@ type State = { type Action = | { type: "USER_SIGN_IN" } | { type: "USER_SIGN_OUT" } - | { type: "FETCH_CSB_DATA_REQUEST" } - | { - type: "FETCH_CSB_DATA_SUCCESS"; - payload: { csbData: CsbData }; - } - | { type: "FETCH_CSB_DATA_FAILURE" } | { type: "FETCH_EPA_USER_DATA_REQUEST" } | { type: "FETCH_EPA_USER_DATA_SUCCESS"; @@ -69,10 +54,6 @@ function reducer(state: State, action: Action): State { ...state, isAuthenticating: false, isAuthenticated: false, - csbData: { - status: "idle", - data: {}, - }, epaUserData: { status: "idle", data: {}, @@ -80,37 +61,6 @@ function reducer(state: State, action: Action): State { }; } - case "FETCH_CSB_DATA_REQUEST": { - return { - ...state, - csbData: { - status: "pending", - data: {}, - }, - }; - } - - case "FETCH_CSB_DATA_SUCCESS": { - const { csbData } = action.payload; - return { - ...state, - csbData: { - status: "success", - data: csbData, - }, - }; - } - - case "FETCH_CSB_DATA_FAILURE": { - return { - ...state, - csbData: { - status: "failure", - data: {}, - }, - }; - } - case "FETCH_EPA_USER_DATA_REQUEST": { return { ...state, @@ -153,10 +103,6 @@ export function UserProvider({ children }: Props) { const initialState: State = { isAuthenticating: true, isAuthenticated: false, - csbData: { - status: "idle", - data: {}, - }, epaUserData: { status: "idle", data: {}, diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index 993e7ea9..27d3648d 100644 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -4,6 +4,7 @@ import reportWebVitals from "./reportWebVitals"; // --- import { ContentProvider } from "contexts/content"; import { UserProvider } from "contexts/user"; +import { CsbProvider } from "contexts/csb"; import { BapProvider } from "contexts/bap"; import { FormioProvider } from "contexts/formio"; import { DialogProvider } from "contexts/dialog"; @@ -18,13 +19,15 @@ render( - - - - - - - + + + + + + + + + diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index cdfc58ea..c7edc31d 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -10,6 +10,7 @@ import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; +import { useCsbState } from "contexts/csb"; import { BapSamEntity, useBapState, useBapDispatch } from "contexts/bap"; import { FormioApplicationSubmission, @@ -160,7 +161,8 @@ function createNewPaymentRequest( export function AllRebates() { const navigate = useNavigate(); const { content } = useContentState(); - const { csbData, epaUserData } = useUserState(); + const { epaUserData } = useUserState(); + const { csbData } = useCsbState(); const { samEntities, applicationSubmissions: bapApplicationSubmissions } = useBapState(); const { diff --git a/app/client/src/routes/applicationForm.tsx b/app/client/src/routes/applicationForm.tsx index 66aefe6b..e76cbae1 100644 --- a/app/client/src/routes/applicationForm.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -20,6 +20,7 @@ import { Message } from "components/message"; import { MarkdownContent } from "components/markdownContent"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; +import { useCsbState } from "contexts/csb"; import { useBapState } from "contexts/bap"; // ----------------------------------------------------------------------------- @@ -234,7 +235,8 @@ function ApplicationFormContent() { const navigate = useNavigate(); const { id } = useParams<"id">(); const { content } = useContentState(); - const { csbData, epaUserData } = useUserState(); + const { epaUserData } = useUserState(); + const { csbData } = useCsbState(); const { samEntities, applicationSubmissions: bapApplicationSubmissions } = useBapState(); const dispatch = useApplicationFormDispatch(); diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 5028abfe..d7f47101 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -12,6 +12,7 @@ import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; +import { useCsbState } from "contexts/csb"; import { useDialogDispatch } from "contexts/dialog"; type SubmissionState = @@ -62,7 +63,8 @@ export function Helpdesk() { const [formDisplayed, setFormDisplayed] = useState(false); const { content } = useContentState(); - const { csbData, epaUserData } = useUserState(); + const { epaUserData } = useUserState(); + const { csbData } = useCsbState(); const dispatch = useDialogDispatch(); const helpdeskAccess = useHelpdeskAccess(); diff --git a/app/client/src/routes/newApplicationForm.tsx b/app/client/src/routes/newApplicationForm.tsx index 198c8cc4..083a3be7 100644 --- a/app/client/src/routes/newApplicationForm.tsx +++ b/app/client/src/routes/newApplicationForm.tsx @@ -11,6 +11,7 @@ import { MarkdownContent } from "components/markdownContent"; import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; +import { useCsbState } from "contexts/csb"; import { BapSamEntity, useBapState } from "contexts/bap"; function createNewApplication(email: string, entity: BapSamEntity) { @@ -42,7 +43,8 @@ function createNewApplication(email: string, entity: BapSamEntity) { export function NewApplicationForm() { const navigate = useNavigate(); const { content } = useContentState(); - const { csbData, epaUserData } = useUserState(); + const { epaUserData } = useUserState(); + const { csbData } = useCsbState(); const { samEntities } = useBapState(); const [message, setMessage] = useState<{ From 99faa0a95bf4b32819e015ca088505ad474230a8 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 12 Sep 2022 17:24:37 -0400 Subject: [PATCH 045/128] Re-open application enrollment via GitHub Actions workflows for proto and dev apps --- .github/workflows/dev.yml | 2 +- .github/workflows/proto.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 3f0e8d37..d37b9158 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -41,7 +41,7 @@ jobs: # SAML_PRIVATE_KEY: ${{ secrets.SAML_PRIVATE_KEY }} JWT_PUBLIC_KEY: ${{ secrets.JWT_PUBLIC_KEY }} JWT_PRIVATE_KEY: ${{ secrets.JWT_PRIVATE_KEY }} - CSB_ENROLLMENT_PERIOD: closed + CSB_ENROLLMENT_PERIOD: open FORMIO_PKG_AUTH_TOKEN: ${{ secrets.FORMIO_PKG_AUTH_TOKEN }} FORMIO_BASE_URL: ${{ secrets.FORMIO_BASE_URL }} FORMIO_PROJECT_NAME: ${{ secrets.FORMIO_PROJECT_NAME }} diff --git a/.github/workflows/proto.yml b/.github/workflows/proto.yml index c1724ef8..ae839a9f 100644 --- a/.github/workflows/proto.yml +++ b/.github/workflows/proto.yml @@ -40,7 +40,7 @@ jobs: SAML_PUBLIC_KEY: ${{ secrets.SAML_PUBLIC_KEY }} JWT_PUBLIC_KEY: ${{ secrets.JWT_PUBLIC_KEY }} JWT_PRIVATE_KEY: ${{ secrets.JWT_PRIVATE_KEY }} - CSB_ENROLLMENT_PERIOD: closed + CSB_ENROLLMENT_PERIOD: open FORMIO_PKG_AUTH_TOKEN: ${{ secrets.FORMIO_PKG_AUTH_TOKEN }} FORMIO_BASE_URL: ${{ secrets.FORMIO_BASE_URL }} FORMIO_PROJECT_NAME: ${{ secrets.FORMIO_PROJECT_NAME }} From 3829223825eaaa60b01fd3038ca5e0d48378435e Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 13 Sep 2022 15:53:19 -0400 Subject: [PATCH 046/128] Add SOQL queries (comment out for documentation) to server's bap.js file and add CSB_Review_Item_ID__c field (9 digit CSB id) to query for future use in new rebate applications query for data --- app/server/app/utilities/bap.js | 82 +++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 4d98e62b..b877ff6b 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -96,6 +96,52 @@ function setupConnection(app) { async function queryForSamEntities(email, req) { /** @type {jsforce.Connection} */ const bapConnection = req.app.locals.bapConnection; + + /* SOQL: */ + // return bapConnection + // .query( + // ` + // SELECT + // ENTITY_COMBO_KEY__c, + // ENTITY_STATUS__c, + // UNIQUE_ENTITY_ID__c, + // ENTITY_EFT_INDICATOR__c, + // CAGE_CODE__c, + // LEGAL_BUSINESS_NAME__c, + // GOVT_BUS_POC_NAME__c, + // GOVT_BUS_POC_EMAIL__c, + // GOVT_BUS_POC_TITLE__c, + // ALT_GOVT_BUS_POC_NAME__c, + // ALT_GOVT_BUS_POC_EMAIL__c, + // ALT_GOVT_BUS_POC_TITLE__c, + // ELEC_BUS_POC_NAME__c, + // ELEC_BUS_POC_EMAIL__c, + // ELEC_BUS_POC_TITLE__c, + // ALT_ELEC_BUS_POC_NAME__c, + // ALT_ELEC_BUS_POC_EMAIL__c, + // ALT_ELEC_BUS_POC_TITLE__c, + // PHYSICAL_ADDRESS_LINE_1__c, + // PHYSICAL_ADDRESS_LINE_2__c, + // PHYSICAL_ADDRESS_CITY__c, + // PHYSICAL_ADDRESS_PROVINCE_OR_STATE__c, + // PHYSICAL_ADDRESS_ZIPPOSTAL_CODE__c, + // PHYSICAL_ADDRESS_ZIP_CODE_4__c + // FROM + // ${BAP_SAM_TABLE} + // WHERE + // ALT_ELEC_BUS_POC_EMAIL__c = '${email}' OR + // GOVT_BUS_POC_EMAIL__c = '${email}' OR + // ALT_GOVT_BUS_POC_EMAIL__c = '${email}' OR + // ELEC_BUS_POC_EMAIL__c = '${email}' + // ` + // ) + // .then((res) => { + // return res.records; + // }) + // .catch((err) => { + // throw err; + // }); + return await bapConnection .sobject(BAP_SAM_TABLE) .find( @@ -198,16 +244,44 @@ function getComboKeys(email, req) { async function queryForApplicationSubmissions(comboKeys, req) { /** @type {jsforce.Connection} */ const bapConnection = req.app.locals.bapConnection; + + /* SOQL: */ + // return bapConnection + // .query( + // ` + // SELECT + // CSB_Form_ID__c, + // CSB_Modified_Full_String__c, + // CSB_Review_Item_ID__c, + // Parent_Rebate_ID__c, + // Parent_CSB_Rebate__r.CSB_Rebate_Status__c + // FROM + // ${BAP_FORMS_TABLE} + // WHERE + // ${comboKeys + // .map((key) => `UEI_EFTI_Combo_Key__c = '${key}'`) + // .join(" OR ")} + // ORDER BY + // CreatedDate DESC + // ` + // ) + // .then((res) => { + // return res.records; + // }) + // .catch((err) => { + // throw err; + // }); + return await bapConnection .sobject(BAP_FORMS_TABLE) .find( { UEI_EFTI_Combo_Key__c: { $in: comboKeys } }, { // "*": 1, - CSB_Form_ID__c: 1, - CSB_Modified_Full_String__c: 1, - UEI_EFTI_Combo_Key__c: 1, - Parent_Rebate_ID__c: 1, + CSB_Form_ID__c: 1, // MongoDB ObjectId string + CSB_Modified_Full_String__c: 1, // ISO 8601 date string + CSB_Review_Item_ID__c: 1, // CSB Rebate ID w/ form/version ID (9 digits) + Parent_Rebate_ID__c: 1, // CSB Rebate ID (6 digits) "Parent_CSB_Rebate__r.CSB_Rebate_Status__c": 1, } ) From cf0ffb75229051d1c9f0043bca0a991b0df7d23c Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 13 Sep 2022 15:54:41 -0400 Subject: [PATCH 047/128] Update client app storage of BapApplicationSubmission and statuses in allRebates tooltip to reflect latest data being fetched --- app/client/src/contexts/bap.tsx | 7 ++++--- app/client/src/routes/allRebates.tsx | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/client/src/contexts/bap.tsx b/app/client/src/contexts/bap.tsx index 5e336d0d..69660d19 100644 --- a/app/client/src/contexts/bap.tsx +++ b/app/client/src/contexts/bap.tsx @@ -45,15 +45,16 @@ export type BapSamEntity = { type BapApplicationSubmission = { CSB_Form_ID__c: string; // MongoDB ObjectId string CSB_Modified_Full_String__c: string; // ISO 8601 date string - UEI_EFTI_Combo_Key__c: string; - Parent_Rebate_ID__c: string; // CSB Rebate ID + CSB_Review_Item_ID__c: string; // CSB Rebate ID w/ form/version ID (9 digits) + Parent_Rebate_ID__c: string; // CSB Rebate ID (6 digits) Parent_CSB_Rebate__r: { CSB_Rebate_Status__c: | "Draft" | "Submitted" | "Edits Requested" | "Withdrawn" - | "Selected"; + | "Selected" + | "Not Selected"; attributes: { type: string; url: string }; }; attributes: { type: string; url: string }; diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index c7edc31d..393c9caf 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -285,7 +285,7 @@ export function AllRebates() {
From fcad956cb45666a5e6a2a34231c557cd1c65695a Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 13 Sep 2022 16:09:21 -0400 Subject: [PATCH 048/128] Organize and rename bap functions --- app/server/app/middleware.js | 4 +- app/server/app/routes/api.js | 10 +-- app/server/app/routes/status.js | 18 ++--- app/server/app/utilities/bap.js | 112 +++++++++++++++++--------------- 4 files changed, 74 insertions(+), 70 deletions(-) diff --git a/app/server/app/middleware.js b/app/server/app/middleware.js index f2a116f6..26a28008 100644 --- a/app/server/app/middleware.js +++ b/app/server/app/middleware.js @@ -5,7 +5,7 @@ const ObjectId = require("mongodb").ObjectId; // --- const { createJwt, jwtAlgorithm } = require("./utilities/createJwt"); const log = require("./utilities/logger"); -const { getComboKeys } = require("./utilities/bap"); +const { getBapComboKeys } = require("./utilities/bap"); const cookieName = "csb-token"; @@ -173,7 +173,7 @@ function appScan(req, res, next) { * @param {express.NextFunction} next */ function storeBapComboKeys(req, res, next) { - getComboKeys(req.user.mail, req) + getBapComboKeys(req.user.mail, req) .then((bapComboKeys) => { req.bapComboKeys = bapComboKeys; next(); diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index 3c8c19b9..b8f2f985 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -17,8 +17,8 @@ const { verifyMongoObjectId, } = require("../middleware"); const { - getSamData, - getApplicationSubmissionsData, + getSamEntities, + getApplicationSubmissions, } = require("../utilities/bap"); const log = require("../utilities/logger"); @@ -40,7 +40,7 @@ function checkEnrollmentPeriodAndBapStatus({ id, comboKey, req }) { return Promise.resolve(); } // else, enrollment is closed, so only continue if edits are requested - return getApplicationSubmissionsData([comboKey], req).then((submissions) => { + return getApplicationSubmissions([comboKey], req).then((submissions) => { const submission = submissions.find((s) => s.CSB_Form_ID__c === id); const status = submission?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c; return status === "Edits Requested" ? Promise.resolve() : Promise.reject(); @@ -131,7 +131,7 @@ router.get("/epa-user-data", (req, res) => { // --- get user's SAM.gov data from EPA's Business Automation Platform (BAP) router.get("/bap-sam-data", (req, res) => { - getSamData(req.user.mail, req) + getSamEntities(req.user.mail, req) .then((entities) => { // NOTE: allow admin or helpdesk users access to the app, even without SAM.gov data const userRoles = req.user.memberof.split(","); @@ -153,7 +153,7 @@ router.get("/bap-sam-data", (req, res) => { // --- get user's Application form submissions from EPA's BAP router.get("/bap-application-submissions", storeBapComboKeys, (req, res) => { - getApplicationSubmissionsData(req.bapComboKeys, req) + getApplicationSubmissions(req.bapComboKeys, req) .then((submissions) => res.json(submissions)) .catch((error) => { const message = `Error getting application form submissions from BAP`; diff --git a/app/server/app/routes/status.js b/app/server/app/routes/status.js index 9f70ab40..51cd4b79 100644 --- a/app/server/app/routes/status.js +++ b/app/server/app/routes/status.js @@ -5,21 +5,21 @@ const { formioProjectUrl, formioApplicationFormPath, } = require("../config/formio"); -const { getSamData } = require("../utilities/bap"); +const { getSamEntities } = require("../utilities/bap"); const router = express.Router(); router.get("/app", (req, res) => { - res.json({ status: true }); + return res.json({ status: true }); }); router.get("/bap-sam-data", (req, res) => { - getSamData("CleanSchoolBus@erg.com", req) + getSamEntities("CleanSchoolBus@erg.com", req) .then(() => { - res.json({ status: true }); + return res.json({ status: true }); }) .catch(() => { - res.json({ status: false }); + return res.json({ status: false }); }); }); @@ -29,12 +29,12 @@ router.get("/formio-application-schema", (req, res) => { axiosFormio(req) .get(applicationFormApiPath) .then((axiosRes) => axiosRes.data) - .then((schema) => + .then((schema) => { // Verify that schema has type of form and a title exists (confirming formio returned a valid schema) - res.json({ status: schema.type === "form" && !!schema.title }) - ) + return res.json({ status: schema.type === "form" && !!schema.title }); + }) .catch(() => { - res.json({ status: false }); + return res.json({ status: false }); }); }); diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index b877ff6b..d5167215 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -183,58 +183,6 @@ async function queryForSamEntities(email, req) { .execute(async (err, records) => ((await err) ? err : records)); } -/** - * Fetches SAM.gov data associated with a provided user. - * @param {string} email - * @param {express.Request} req - */ -function getSamData(email, req) { - // Make sure BAP connection has been initialized - if (!req.app.locals.bapConnection) { - const message = `BAP Connection has not yet been initialized.`; - log({ level: "info", message }); - - return setupConnection(req.app) - .then(() => queryForSamEntities(email, req)) - .catch((err) => { - const message = `BAP Error: ${err}`; - log({ level: "error", message, req }); - throw err; - }); - } - - return queryForSamEntities(email, req).catch((err) => { - if (err?.toString() === "invalid_grant: expired access/refresh token") { - const message = `BAP access token expired`; - log({ level: "info", message, req }); - } else { - const message = `BAP Error: ${err}`; - log({ level: "error", message, req }); - } - - return setupConnection(req.app) - .then(() => queryForSamEntities(email, req)) - .catch((retryErr) => { - const message = `BAP Error: ${retryErr}`; - log({ level: "error", message, req }); - throw retryErr; - }); - }); -} - -/** - * Fetches SAM.gov entity combo keys data associated with a provided user. - * @param {string} email - * @param {express.Request} req - */ -function getComboKeys(email, req) { - return getSamData(email, req) - .then((entities) => entities.map((entity) => entity.ENTITY_COMBO_KEY__c)) - .catch((err) => { - throw err; - }); -} - /** * Uses cached JSforce connection to query the BAP for application form submissions. * @param {string[]} comboKeys @@ -289,12 +237,64 @@ async function queryForApplicationSubmissions(comboKeys, req) { .execute(async (err, records) => ((await err) ? err : records)); } +/** + * Fetches SAM.gov entities associated with a provided user. + * @param {string} email + * @param {express.Request} req + */ +function getSamEntities(email, req) { + // Make sure BAP connection has been initialized + if (!req.app.locals.bapConnection) { + const message = `BAP Connection has not yet been initialized.`; + log({ level: "info", message }); + + return setupConnection(req.app) + .then(() => queryForSamEntities(email, req)) + .catch((err) => { + const message = `BAP Error: ${err}`; + log({ level: "error", message, req }); + throw err; + }); + } + + return queryForSamEntities(email, req).catch((err) => { + if (err?.toString() === "invalid_grant: expired access/refresh token") { + const message = `BAP access token expired`; + log({ level: "info", message, req }); + } else { + const message = `BAP Error: ${err}`; + log({ level: "error", message, req }); + } + + return setupConnection(req.app) + .then(() => queryForSamEntities(email, req)) + .catch((retryErr) => { + const message = `BAP Error: ${retryErr}`; + log({ level: "error", message, req }); + throw retryErr; + }); + }); +} + +/** + * Fetches SAM.gov entity combo keys data associated with a provided user. + * @param {string} email + * @param {express.Request} req + */ +function getBapComboKeys(email, req) { + return getSamEntities(email, req) + .then((entities) => entities.map((entity) => entity.ENTITY_COMBO_KEY__c)) + .catch((err) => { + throw err; + }); +} + /** * Fetches application form submissions associated with a provided set of combo keys. * @param {string[]} comboKeys * @param {express.Request} req */ -function getApplicationSubmissionsData(comboKeys, req) { +function getApplicationSubmissions(comboKeys, req) { // Make sure BAP connection has been initialized if (!req.app.locals.bapConnection) { const message = `BAP Connection has not yet been initialized.`; @@ -328,4 +328,8 @@ function getApplicationSubmissionsData(comboKeys, req) { }); } -module.exports = { getSamData, getComboKeys, getApplicationSubmissionsData }; +module.exports = { + getSamEntities, + getBapComboKeys, + getApplicationSubmissions, +}; From a129cef42e491e91ad2f3a7759e17c6a4bac5ae1 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 13 Sep 2022 21:17:50 -0400 Subject: [PATCH 049/128] Log BAP queries, simplify SOQL comments, and refactor code used to verify bap connection before querying BAP --- app/server/app/utilities/bap.js | 193 ++++++++++++++------------------ 1 file changed, 84 insertions(+), 109 deletions(-) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index d5167215..c98ec8ce 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -76,8 +76,8 @@ function setupConnection(app) { return bapConnection .loginByOAuth2(BAP_USER, BAP_PASSWORD) - .then(() => { - const message = `Initializing BAP Connection.`; + .then((userInfo) => { + const message = `Initializing BAP Connection: ${userInfo.url}.`; log({ level: "info", message }); // Store bapConnection in global express object using app.locals app.locals.bapConnection = bapConnection; @@ -91,56 +91,48 @@ function setupConnection(app) { * Uses cached JSforce connection to query the BAP for SAM.gov entities. * @param {string} email * @param {express.Request} req - * @returns {Promise} collection of SAM.gov entities + * @returns {Promise} collection of SAM.gov entity data */ async function queryForSamEntities(email, req) { + const message = `Querying BAP for SAM.gov entities for user with email: ${email}.`; + log({ level: "info", message }); + /** @type {jsforce.Connection} */ const bapConnection = req.app.locals.bapConnection; /* SOQL: */ - // return bapConnection - // .query( - // ` - // SELECT - // ENTITY_COMBO_KEY__c, - // ENTITY_STATUS__c, - // UNIQUE_ENTITY_ID__c, - // ENTITY_EFT_INDICATOR__c, - // CAGE_CODE__c, - // LEGAL_BUSINESS_NAME__c, - // GOVT_BUS_POC_NAME__c, - // GOVT_BUS_POC_EMAIL__c, - // GOVT_BUS_POC_TITLE__c, - // ALT_GOVT_BUS_POC_NAME__c, - // ALT_GOVT_BUS_POC_EMAIL__c, - // ALT_GOVT_BUS_POC_TITLE__c, - // ELEC_BUS_POC_NAME__c, - // ELEC_BUS_POC_EMAIL__c, - // ELEC_BUS_POC_TITLE__c, - // ALT_ELEC_BUS_POC_NAME__c, - // ALT_ELEC_BUS_POC_EMAIL__c, - // ALT_ELEC_BUS_POC_TITLE__c, - // PHYSICAL_ADDRESS_LINE_1__c, - // PHYSICAL_ADDRESS_LINE_2__c, - // PHYSICAL_ADDRESS_CITY__c, - // PHYSICAL_ADDRESS_PROVINCE_OR_STATE__c, - // PHYSICAL_ADDRESS_ZIPPOSTAL_CODE__c, - // PHYSICAL_ADDRESS_ZIP_CODE_4__c - // FROM - // ${BAP_SAM_TABLE} - // WHERE - // ALT_ELEC_BUS_POC_EMAIL__c = '${email}' OR - // GOVT_BUS_POC_EMAIL__c = '${email}' OR - // ALT_GOVT_BUS_POC_EMAIL__c = '${email}' OR - // ELEC_BUS_POC_EMAIL__c = '${email}' - // ` - // ) - // .then((res) => { - // return res.records; - // }) - // .catch((err) => { - // throw err; - // }); + // `SELECT + // ENTITY_COMBO_KEY__c, + // ENTITY_STATUS__c, + // UNIQUE_ENTITY_ID__c, + // ENTITY_EFT_INDICATOR__c, + // CAGE_CODE__c, + // LEGAL_BUSINESS_NAME__c, + // GOVT_BUS_POC_NAME__c, + // GOVT_BUS_POC_EMAIL__c, + // GOVT_BUS_POC_TITLE__c, + // ALT_GOVT_BUS_POC_NAME__c, + // ALT_GOVT_BUS_POC_EMAIL__c, + // ALT_GOVT_BUS_POC_TITLE__c, + // ELEC_BUS_POC_NAME__c, + // ELEC_BUS_POC_EMAIL__c, + // ELEC_BUS_POC_TITLE__c, + // ALT_ELEC_BUS_POC_NAME__c, + // ALT_ELEC_BUS_POC_EMAIL__c, + // ALT_ELEC_BUS_POC_TITLE__c, + // PHYSICAL_ADDRESS_LINE_1__c, + // PHYSICAL_ADDRESS_LINE_2__c, + // PHYSICAL_ADDRESS_CITY__c, + // PHYSICAL_ADDRESS_PROVINCE_OR_STATE__c, + // PHYSICAL_ADDRESS_ZIPPOSTAL_CODE__c, + // PHYSICAL_ADDRESS_ZIP_CODE_4__c + // FROM + // ${BAP_SAM_TABLE} + // WHERE + // ALT_ELEC_BUS_POC_EMAIL__c = '${email}' OR + // GOVT_BUS_POC_EMAIL__c = '${email}' OR + // ALT_GOVT_BUS_POC_EMAIL__c = '${email}' OR + // ELEC_BUS_POC_EMAIL__c = '${email}'` return await bapConnection .sobject(BAP_SAM_TABLE) @@ -187,38 +179,30 @@ async function queryForSamEntities(email, req) { * Uses cached JSforce connection to query the BAP for application form submissions. * @param {string[]} comboKeys * @param {express.Request} req - * @returns {Promise} collection of application form submissions + * @returns {Promise} collection of fields associated with each application form submission */ async function queryForApplicationSubmissions(comboKeys, req) { + const message = `Querying BAP for Application form submissions associated with combokeys: ${comboKeys}.`; + log({ level: "info", message }); + /** @type {jsforce.Connection} */ const bapConnection = req.app.locals.bapConnection; /* SOQL: */ - // return bapConnection - // .query( - // ` - // SELECT - // CSB_Form_ID__c, - // CSB_Modified_Full_String__c, - // CSB_Review_Item_ID__c, - // Parent_Rebate_ID__c, - // Parent_CSB_Rebate__r.CSB_Rebate_Status__c - // FROM - // ${BAP_FORMS_TABLE} - // WHERE - // ${comboKeys - // .map((key) => `UEI_EFTI_Combo_Key__c = '${key}'`) - // .join(" OR ")} - // ORDER BY - // CreatedDate DESC - // ` - // ) - // .then((res) => { - // return res.records; - // }) - // .catch((err) => { - // throw err; - // }); + // `SELECT + // CSB_Form_ID__c, + // CSB_Modified_Full_String__c, + // CSB_Review_Item_ID__c, + // Parent_Rebate_ID__c, + // Parent_CSB_Rebate__r.CSB_Rebate_Status__c + // FROM + // ${BAP_FORMS_TABLE} + // WHERE + // ${comboKeys + // .map((key) => `UEI_EFTI_Combo_Key__c = '${key}'`) + // .join(" OR ")} + // ORDER BY + // CreatedDate DESC` return await bapConnection .sobject(BAP_FORMS_TABLE) @@ -238,18 +222,21 @@ async function queryForApplicationSubmissions(comboKeys, req) { } /** - * Fetches SAM.gov entities associated with a provided user. - * @param {string} email + * Verifies the BAP connection has been setup, then calls the provided callback function with the provided arguments. * @param {express.Request} req + * @param {Object} callback callback function name and arguments to call after BAP connection has been verified + * @param {function} callback.name name of the callback function + * @param {any[]} callback.args arguments to pass to the callback function */ -function getSamEntities(email, req) { - // Make sure BAP connection has been initialized +function verifyBapConnection(req, { name, args }) { + // BAP connection has not yet been initialized, so set set it up and call + // callback function if (!req.app.locals.bapConnection) { const message = `BAP Connection has not yet been initialized.`; log({ level: "info", message }); return setupConnection(req.app) - .then(() => queryForSamEntities(email, req)) + .then(() => name(...args)) .catch((err) => { const message = `BAP Error: ${err}`; log({ level: "error", message, req }); @@ -257,7 +244,10 @@ function getSamEntities(email, req) { }); } - return queryForSamEntities(email, req).catch((err) => { + // BAP connection has already been initialized, so call callback function + return name(...args).catch((err) => { + // in case of errors, log appropriate error, attempt to setup connection + // once more, and then call callback function if (err?.toString() === "invalid_grant: expired access/refresh token") { const message = `BAP access token expired`; log({ level: "info", message, req }); @@ -267,7 +257,7 @@ function getSamEntities(email, req) { } return setupConnection(req.app) - .then(() => queryForSamEntities(email, req)) + .then(() => name(...args)) .catch((retryErr) => { const message = `BAP Error: ${retryErr}`; log({ level: "error", message, req }); @@ -276,6 +266,18 @@ function getSamEntities(email, req) { }); } +/** + * Fetches SAM.gov entities associated with a provided user. + * @param {string} email + * @param {express.Request} req + */ +function getSamEntities(email, req) { + return verifyBapConnection(req, { + name: queryForSamEntities, + args: [email, req], + }); +} + /** * Fetches SAM.gov entity combo keys data associated with a provided user. * @param {string} email @@ -295,36 +297,9 @@ function getBapComboKeys(email, req) { * @param {express.Request} req */ function getApplicationSubmissions(comboKeys, req) { - // Make sure BAP connection has been initialized - if (!req.app.locals.bapConnection) { - const message = `BAP Connection has not yet been initialized.`; - log({ level: "info", message }); - - return setupConnection(req.app) - .then(() => queryForApplicationSubmissions(comboKeys, req)) - .catch((err) => { - const message = `BAP Error: ${err}`; - log({ level: "error", message, req }); - throw err; - }); - } - - return queryForApplicationSubmissions(comboKeys, req).catch((err) => { - if (err?.toString() === "invalid_grant: expired access/refresh token") { - const message = `BAP access token expired`; - log({ level: "info", message, req }); - } else { - const message = `BAP Error: ${err}`; - log({ level: "error", message, req }); - } - - return setupConnection(req.app) - .then(() => queryForApplicationSubmissions(comboKeys, req)) - .catch((retryErr) => { - const message = `BAP Error: ${retryErr}`; - log({ level: "error", message, req }); - throw retryErr; - }); + return verifyBapConnection(req, { + name: queryForApplicationSubmissions, + args: [comboKeys, req], }); } From eb06ec2989838ed3b367ec131377c6c0c647b9f3 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 13 Sep 2022 21:27:49 -0400 Subject: [PATCH 050/128] Swap order of arguments in bap functions to pass express request object first --- app/server/app/middleware.js | 2 +- app/server/app/routes/api.js | 6 +++--- app/server/app/routes/status.js | 2 +- app/server/app/utilities/bap.js | 26 +++++++++++++------------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/server/app/middleware.js b/app/server/app/middleware.js index 26a28008..bf7a7eed 100644 --- a/app/server/app/middleware.js +++ b/app/server/app/middleware.js @@ -173,7 +173,7 @@ function appScan(req, res, next) { * @param {express.NextFunction} next */ function storeBapComboKeys(req, res, next) { - getBapComboKeys(req.user.mail, req) + getBapComboKeys(req, req.user.mail) .then((bapComboKeys) => { req.bapComboKeys = bapComboKeys; next(); diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index b8f2f985..9ea0cb6b 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -40,7 +40,7 @@ function checkEnrollmentPeriodAndBapStatus({ id, comboKey, req }) { return Promise.resolve(); } // else, enrollment is closed, so only continue if edits are requested - return getApplicationSubmissions([comboKey], req).then((submissions) => { + return getApplicationSubmissions(req, [comboKey]).then((submissions) => { const submission = submissions.find((s) => s.CSB_Form_ID__c === id); const status = submission?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c; return status === "Edits Requested" ? Promise.resolve() : Promise.reject(); @@ -131,7 +131,7 @@ router.get("/epa-user-data", (req, res) => { // --- get user's SAM.gov data from EPA's Business Automation Platform (BAP) router.get("/bap-sam-data", (req, res) => { - getSamEntities(req.user.mail, req) + getSamEntities(req, req.user.mail) .then((entities) => { // NOTE: allow admin or helpdesk users access to the app, even without SAM.gov data const userRoles = req.user.memberof.split(","); @@ -153,7 +153,7 @@ router.get("/bap-sam-data", (req, res) => { // --- get user's Application form submissions from EPA's BAP router.get("/bap-application-submissions", storeBapComboKeys, (req, res) => { - getApplicationSubmissions(req.bapComboKeys, req) + getApplicationSubmissions(req, req.bapComboKeys) .then((submissions) => res.json(submissions)) .catch((error) => { const message = `Error getting application form submissions from BAP`; diff --git a/app/server/app/routes/status.js b/app/server/app/routes/status.js index 51cd4b79..fadb142d 100644 --- a/app/server/app/routes/status.js +++ b/app/server/app/routes/status.js @@ -14,7 +14,7 @@ router.get("/app", (req, res) => { }); router.get("/bap-sam-data", (req, res) => { - getSamEntities("CleanSchoolBus@erg.com", req) + getSamEntities(req, "CleanSchoolBus@erg.com") .then(() => { return res.json({ status: true }); }) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index c98ec8ce..4515b5a3 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -89,11 +89,11 @@ function setupConnection(app) { /** * Uses cached JSforce connection to query the BAP for SAM.gov entities. - * @param {string} email * @param {express.Request} req + * @param {string} email * @returns {Promise} collection of SAM.gov entity data */ -async function queryForSamEntities(email, req) { +async function queryForSamEntities(req, email) { const message = `Querying BAP for SAM.gov entities for user with email: ${email}.`; log({ level: "info", message }); @@ -177,11 +177,11 @@ async function queryForSamEntities(email, req) { /** * Uses cached JSforce connection to query the BAP for application form submissions. - * @param {string[]} comboKeys * @param {express.Request} req + * @param {string[]} comboKeys * @returns {Promise} collection of fields associated with each application form submission */ -async function queryForApplicationSubmissions(comboKeys, req) { +async function queryForApplicationSubmissions(req, comboKeys) { const message = `Querying BAP for Application form submissions associated with combokeys: ${comboKeys}.`; log({ level: "info", message }); @@ -268,23 +268,23 @@ function verifyBapConnection(req, { name, args }) { /** * Fetches SAM.gov entities associated with a provided user. - * @param {string} email * @param {express.Request} req + * @param {string} email */ -function getSamEntities(email, req) { +function getSamEntities(req, email) { return verifyBapConnection(req, { name: queryForSamEntities, - args: [email, req], + args: [req, email], }); } /** * Fetches SAM.gov entity combo keys data associated with a provided user. - * @param {string} email * @param {express.Request} req + * @param {string} email */ -function getBapComboKeys(email, req) { - return getSamEntities(email, req) +function getBapComboKeys(req, email) { + return getSamEntities(req, email) .then((entities) => entities.map((entity) => entity.ENTITY_COMBO_KEY__c)) .catch((err) => { throw err; @@ -293,13 +293,13 @@ function getBapComboKeys(email, req) { /** * Fetches application form submissions associated with a provided set of combo keys. - * @param {string[]} comboKeys * @param {express.Request} req + * @param {string[]} comboKeys */ -function getApplicationSubmissions(comboKeys, req) { +function getApplicationSubmissions(req, comboKeys) { return verifyBapConnection(req, { name: queryForApplicationSubmissions, - args: [comboKeys, req], + args: [req, comboKeys], }); } From fb4ea968e6f2e304151aaee6a25b56f289f35836 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 14 Sep 2022 13:05:20 -0400 Subject: [PATCH 051/128] Add initial bap methods for fetching application form submission fields, and add 'Statuses' prefix to getApplicationSubmissions and queryForApplicationSubmissions bap functions --- app/server/app/routes/api.js | 51 +++++++++---- app/server/app/utilities/bap.js | 126 ++++++++++++++++++++++++++++++-- 2 files changed, 156 insertions(+), 21 deletions(-) diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index 9ea0cb6b..f095addb 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -18,7 +18,8 @@ const { } = require("../middleware"); const { getSamEntities, - getApplicationSubmissions, + getApplicationSubmissionsStatuses, + getApplicationSubmission, } = require("../utilities/bap"); const log = require("../utilities/logger"); @@ -40,11 +41,15 @@ function checkEnrollmentPeriodAndBapStatus({ id, comboKey, req }) { return Promise.resolve(); } // else, enrollment is closed, so only continue if edits are requested - return getApplicationSubmissions(req, [comboKey]).then((submissions) => { - const submission = submissions.find((s) => s.CSB_Form_ID__c === id); - const status = submission?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c; - return status === "Edits Requested" ? Promise.resolve() : Promise.reject(); - }); + return getApplicationSubmissionsStatuses(req, [comboKey]).then( + (submissions) => { + const submission = submissions.find((s) => s.CSB_Form_ID__c === id); + const status = submission?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c; + return status === "Edits Requested" + ? Promise.resolve() + : Promise.reject(); + } + ); } const router = express.Router(); @@ -143,6 +148,7 @@ router.get("/bap-sam-data", (req, res) => { log({ level: "error", message, req }); return res.json({ results: false, entities: [] }); } + return res.json({ results: true, entities }); }) .catch((error) => { @@ -151,12 +157,12 @@ router.get("/bap-sam-data", (req, res) => { }); }); -// --- get user's Application form submissions from EPA's BAP +// --- get user's Application form submissions statuses from EPA's BAP router.get("/bap-application-submissions", storeBapComboKeys, (req, res) => { - getApplicationSubmissions(req, req.bapComboKeys) + return getApplicationSubmissionsStatuses(req, req.bapComboKeys) .then((submissions) => res.json(submissions)) .catch((error) => { - const message = `Error getting application form submissions from BAP`; + const message = `Error getting Application form submissions statuses from BAP`; return res.status(401).json({ message }); }); }); @@ -385,6 +391,7 @@ router.post( storeBapComboKeys, (req, res) => { const comboKey = req.body.data?.bap_hidden_entity_combo_key; + const reviewItemId = req.body.data?.csb_review_item_id; // verify post data includes one of user's BAP combo keys if (!req.bapComboKeys.includes(comboKey)) { @@ -399,13 +406,27 @@ router.post( ...formioCsbMetadata, }; - axiosFormio(req) - .post(`${paymentFormApiPath}/submission`, req.body) - .then((axiosRes) => axiosRes.data) - .then((submission) => res.json(submission)) + return getApplicationSubmission(req, reviewItemId) + .then((applicationSubmission) => { + const data = { + ...applicationSubmission, + }; + + // TODO: temporarily return data for now...will eventually post to formio + return res.json(data); + + // axiosFormio(req) + // .post(`${paymentFormApiPath}/submission`, data) + // .then((axiosRes) => axiosRes.data) + // .then((submission) => res.json(submission)) + // .catch((error) => { + // const message = `Error posting Forms.gov Payment Request form submission`; + // return res.status(error?.response?.status || 500).json({ message }); + // }); + }) .catch((error) => { - const message = `Error posting Forms.gov Payment Request form submission`; - return res.status(error?.response?.status || 500).json({ message }); + const message = `Error getting Application form submission from BAP`; + return res.status(401).json({ message }); }); } ); diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 4515b5a3..f8f01f42 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -176,13 +176,13 @@ async function queryForSamEntities(req, email) { } /** - * Uses cached JSforce connection to query the BAP for application form submissions. + * Uses cached JSforce connection to query the BAP for application form submissions statuses, and related metadata. * @param {express.Request} req * @param {string[]} comboKeys * @returns {Promise} collection of fields associated with each application form submission */ -async function queryForApplicationSubmissions(req, comboKeys) { - const message = `Querying BAP for Application form submissions associated with combokeys: ${comboKeys}.`; +async function queryForApplicationSubmissionsStatuses(req, comboKeys) { + const message = `Querying BAP for Application form submissions statuses associated with combokeys: ${comboKeys}.`; log({ level: "info", message }); /** @type {jsforce.Connection} */ @@ -221,6 +221,107 @@ async function queryForApplicationSubmissions(req, comboKeys) { .execute(async (err, records) => ((await err) ? err : records)); } +/** + * Uses cached JSforce connection to query the BAP for a single application form submission. + * @param {express.Request} req + * @param {string} reviewItemId CSB Rebate ID with the form/version ID (9 digits) + * @returns {Promise} application form submission fields + */ +async function queryForApplicationSubmission(req, reviewItemId) { + const message = `Querying BAP for Application form submission associated with CSB Review Item ID: ${reviewItemId}.`; + log({ level: "info", message }); + + /** @type {jsforce.Connection} */ + const bapConnection = req.app.locals.bapConnection; + + /* SOQL: */ + // `SELECT + // Id + // FROM + // recordtype + // WHERE + // developername = 'CSB_Funding_Request' AND + // sobjecttype = '${BAP_FORMS_TABLE}' + // LIMIT 1` + + const tableIdQuery = await bapConnection + .sobject("recordtype") + .find( + { + developername: "CSB_Funding_Request", + sobjecttype: BAP_FORMS_TABLE, + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + } + ) + .execute(async (err, records) => ((await err) ? err : records)); + + const tableId = await tableIdQuery[0].Id; + + /* SOQL */ + // `SELECT + // id, + // UEI_EFTI_Combo_Key__c, + // CSB_NCES_ID__c, + // Primary_Applicant__r.Name, + // Primary_Applicant__r.Title, + // Primary_Applicant__r.Phone, + // Primary_Applicant__r.Email, + // Alternate_Applicant__r.Name, + // Alternate_Applicant__r.Title, + // Alternate_Applicant__r.Phone, + // Alternate_Applicant__r.Email, + // Applicant_Organization__r.Name, + // CSB_School_District__r.Name, + // Fleet_Name__c, + // School_District_Prioritized__c, + // Total_Rebate_Funds_Requested__c, + // Total_Infrastructure_Funds__c + // FROM + // ${BAP_FORMS_TABLE} + // WHERE + // recordtypeid = '${tableId}' AND + // CSB_Review_Item_ID__c = '${reviewItemId}' AND + // Latest_Version__c = TRUE` + + const tableFieldsQuery = await bapConnection + .sobject(BAP_FORMS_TABLE) + .find( + { + recordtypeid: tableId, + CSB_Review_Item_ID__c: reviewItemId, + Latest_Version__c: true, + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + UEI_EFTI_Combo_Key__c: 1, + CSB_NCES_ID__c: 1, + "Primary_Applicant__r.Name": 1, + "Primary_Applicant__r.Title": 1, + "Primary_Applicant__r.Phone": 1, + "Primary_Applicant__r.Email": 1, + "Alternate_Applicant__r.Name": 1, + "Alternate_Applicant__r.Title": 1, + "Alternate_Applicant__r.Phone": 1, + "Alternate_Applicant__r.Email": 1, + "Applicant_Organization__r.Name": 1, + "CSB_School_District__r.Name": 1, + Fleet_Name__c: 1, + School_District_Prioritized__c: 1, + Total_Rebate_Funds_Requested__c: 1, + Total_Infrastructure_Funds__c: 1, + } + ) + .execute(async (err, records) => ((await err) ? err : records)); + + return tableFieldsQuery; + + // TODO: query for line items too... +} + /** * Verifies the BAP connection has been setup, then calls the provided callback function with the provided arguments. * @param {express.Request} req @@ -296,15 +397,28 @@ function getBapComboKeys(req, email) { * @param {express.Request} req * @param {string[]} comboKeys */ -function getApplicationSubmissions(req, comboKeys) { +function getApplicationSubmissionsStatuses(req, comboKeys) { return verifyBapConnection(req, { - name: queryForApplicationSubmissions, + name: queryForApplicationSubmissionsStatuses, args: [req, comboKeys], }); } +/** + * Fetches an application form submission associated with a CSB Review Item ID + * @param {express.Request} req + * @param {string[]} comboKeys + */ +function getApplicationSubmission(req, reviewItemId) { + return verifyBapConnection(req, { + name: queryForApplicationSubmission, + args: [req, reviewItemId], + }); +} + module.exports = { getSamEntities, getBapComboKeys, - getApplicationSubmissions, + getApplicationSubmissionsStatuses, + getApplicationSubmission, }; From 722af0f17062fa0c2ae2960ac7587efade17f7d7 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 14 Sep 2022 13:35:17 -0400 Subject: [PATCH 052/128] Update /status/bap-sam-data status check to error if returned value isn't an array, and pass in dummy status check email address, as its now being logged --- app/server/app/routes/status.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/server/app/routes/status.js b/app/server/app/routes/status.js index fadb142d..0e681dae 100644 --- a/app/server/app/routes/status.js +++ b/app/server/app/routes/status.js @@ -14,8 +14,12 @@ router.get("/app", (req, res) => { }); router.get("/bap-sam-data", (req, res) => { - getSamEntities(req, "CleanSchoolBus@erg.com") - .then(() => { + getSamEntities(req, "bap.sam.data.status@erg.com") + .then((bapRes) => { + if (!Array.isArray(bapRes)) { + throw Error(); + } + return res.json({ status: true }); }) .catch(() => { From d87c3b293d4fe5c739d96df96079bc14fdf12535 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 14 Sep 2022 15:42:06 -0400 Subject: [PATCH 053/128] Add BAP queries for fetching bus info --- app/server/.env.example | 1 + app/server/app/utilities/bap.js | 79 ++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/app/server/.env.example b/app/server/.env.example index 12c77102..6e209989 100644 --- a/app/server/.env.example +++ b/app/server/.env.example @@ -23,5 +23,6 @@ BAP_USER= BAP_PASSWORD= BAP_SAM_TABLE= BAP_FORMS_TABLE= +BAP_BUS_TABLE= S3_PUBLIC_BUCKET= S3_PUBLIC_REGION= diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index f8f01f42..0101b667 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -58,6 +58,7 @@ const { BAP_PASSWORD, BAP_SAM_TABLE, BAP_FORMS_TABLE, + BAP_BUS_TABLE, } = process.env; /** @@ -244,7 +245,7 @@ async function queryForApplicationSubmission(req, reviewItemId) { // sobjecttype = '${BAP_FORMS_TABLE}' // LIMIT 1` - const tableIdQuery = await bapConnection + const formsTableIdQuery = await bapConnection .sobject("recordtype") .find( { @@ -258,7 +259,7 @@ async function queryForApplicationSubmission(req, reviewItemId) { ) .execute(async (err, records) => ((await err) ? err : records)); - const tableId = await tableIdQuery[0].Id; + const formsTableId = formsTableIdQuery["0"].Id; /* SOQL */ // `SELECT @@ -282,15 +283,15 @@ async function queryForApplicationSubmission(req, reviewItemId) { // FROM // ${BAP_FORMS_TABLE} // WHERE - // recordtypeid = '${tableId}' AND + // recordtypeid = '${formsTableId}' AND // CSB_Review_Item_ID__c = '${reviewItemId}' AND // Latest_Version__c = TRUE` - const tableFieldsQuery = await bapConnection + const formsTableRecordQuery = await bapConnection .sobject(BAP_FORMS_TABLE) .find( { - recordtypeid: tableId, + recordtypeid: formsTableId, CSB_Review_Item_ID__c: reviewItemId, Latest_Version__c: true, }, @@ -317,9 +318,73 @@ async function queryForApplicationSubmission(req, reviewItemId) { ) .execute(async (err, records) => ((await err) ? err : records)); - return tableFieldsQuery; + const formsTableRecordId = formsTableRecordQuery["0"].Id; - // TODO: query for line items too... + /* SOQL */ + // `SELECT + // Id + // FROM + // recordtype + // WHERE + // developername = 'CSB_Rebate_Item' AND + // sobjecttype = ${BAP_BUS_TABLE} + // LIMIT 1` + + const busTableIdQuery = await bapConnection + .sobject("recordtype") + .find( + { + developername: "CSB_Rebate_Item", + sobjecttype: BAP_BUS_TABLE, + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + } + ) + .limit(1) + .execute(async (err, records) => ((await err) ? err : records)); + + const busTableId = busTableIdQuery["0"].Id; + + /* SOQL: */ + // `SELECT + // Id, + // Rebate_Item_num__c, + // CSB_VIN__c, + // CSB_Model_Year__c, + // CSB_Fuel_Type__c, + // CSB_Replacement_Fuel_Type__c, + // CSB_Funds_Requested__c + // FROM + // ${BAP_BUS_TABLE} + // WHERE + // recordtypeid = '${busTableId}' AND + // Related_Order_Request__c = '${formsTableRecordId}' AND + // CSB_Rebate_Item_Type__c != 'Infrastructure'` + + const busTableRecordsQuery = await bapConnection + .sobject(BAP_BUS_TABLE) + .find( + { + recordtypeid: busTableId, + Related_Order_Request__c: formsTableRecordId, + CSB_Rebate_Item_Type__c: { $neq: "Infrastructure" }, + }, + { + // "*": 1, + Id: 1, // Salesforce record ID + Rebate_Item_num__c: 1, + CSB_VIN__c: 1, + CSB_Model_Year__c: 1, + CSB_Fuel_Type__c: 1, + CSB_Replacement_Fuel_Type__c: 1, + CSB_Funds_Requested__c: 1, + } + ) + .execute(async (err, records) => ((await err) ? err : records)); + + return { formsTableRecordQuery, busTableRecordsQuery }; } /** From d79c050c134ce7acd6f88d4a3568b4788b7ab696 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 14 Sep 2022 15:44:26 -0400 Subject: [PATCH 054/128] Add BAP_BUS_TABLE env variable to GitHub actions workflows --- .github/workflows/dev.yml | 2 ++ .github/workflows/proto.yml | 2 ++ .github/workflows/staging.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index d37b9158..8665eafd 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -55,6 +55,7 @@ jobs: BAP_PASSWORD: ${{ secrets.BAP_PASSWORD }} BAP_SAM_TABLE: ${{ secrets.BAP_SAM_TABLE }} BAP_FORMS_TABLE: ${{ secrets.BAP_FORMS_TABLE }} + BAP_BUS_TABLE: ${{ secrets.BAP_BUS_TABLE }} AWS_ACCESS_KEY_ID: ${{ secrets.S3_PUBLIC_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_PUBLIC_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.S3_PUBLIC_REGION }} @@ -133,6 +134,7 @@ jobs: cf set-env $APP_NAME "BAP_PASSWORD" "$BAP_PASSWORD" > /dev/null cf set-env $APP_NAME "BAP_SAM_TABLE" "$BAP_SAM_TABLE" > /dev/null cf set-env $APP_NAME "BAP_FORMS_TABLE" "$BAP_FORMS_TABLE" > /dev/null + cf set-env $APP_NAME "BAP_BUS_TABLE" "$BAP_BUS_TABLE" > /dev/null cf set-env $APP_NAME "S3_PUBLIC_BUCKET" "$S3_PUBLIC_BUCKET" > /dev/null cf set-env $APP_NAME "S3_PUBLIC_REGION" "$AWS_DEFAULT_REGION" > /dev/null diff --git a/.github/workflows/proto.yml b/.github/workflows/proto.yml index ae839a9f..8c2c3806 100644 --- a/.github/workflows/proto.yml +++ b/.github/workflows/proto.yml @@ -54,6 +54,7 @@ jobs: BAP_PASSWORD: ${{ secrets.BAP_PASSWORD }} BAP_SAM_TABLE: ${{ secrets.BAP_SAM_TABLE }} BAP_FORMS_TABLE: ${{ secrets.BAP_FORMS_TABLE }} + BAP_BUS_TABLE: ${{ secrets.BAP_BUS_TABLE }} AWS_ACCESS_KEY_ID: ${{ secrets.S3_PUBLIC_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_PUBLIC_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.S3_PUBLIC_REGION }} @@ -131,6 +132,7 @@ jobs: cf set-env $APP_NAME "BAP_PASSWORD" "$BAP_PASSWORD" > /dev/null cf set-env $APP_NAME "BAP_SAM_TABLE" "$BAP_SAM_TABLE" > /dev/null cf set-env $APP_NAME "BAP_FORMS_TABLE" "$BAP_FORMS_TABLE" > /dev/null + cf set-env $APP_NAME "BAP_BUS_TABLE" "$BAP_BUS_TABLE" > /dev/null cf set-env $APP_NAME "S3_PUBLIC_BUCKET" "$S3_PUBLIC_BUCKET" > /dev/null cf set-env $APP_NAME "S3_PUBLIC_REGION" "$AWS_DEFAULT_REGION" > /dev/null diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 346f9fec..8c838375 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -55,6 +55,7 @@ jobs: BAP_PASSWORD: ${{ secrets.BAP_PASSWORD }} BAP_SAM_TABLE: ${{ secrets.BAP_SAM_TABLE }} BAP_FORMS_TABLE: ${{ secrets.BAP_FORMS_TABLE }} + BAP_BUS_TABLE: ${{ secrets.BAP_BUS_TABLE }} AWS_ACCESS_KEY_ID: ${{ secrets.S3_PUBLIC_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_PUBLIC_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.S3_PUBLIC_REGION }} @@ -133,6 +134,7 @@ jobs: cf set-env $APP_NAME "BAP_PASSWORD" "$BAP_PASSWORD" > /dev/null cf set-env $APP_NAME "BAP_SAM_TABLE" "$BAP_SAM_TABLE" > /dev/null cf set-env $APP_NAME "BAP_FORMS_TABLE" "$BAP_FORMS_TABLE" > /dev/null + cf set-env $APP_NAME "BAP_BUS_TABLE" "$BAP_BUS_TABLE" > /dev/null cf set-env $APP_NAME "S3_PUBLIC_BUCKET" "$S3_PUBLIC_BUCKET" > /dev/null cf set-env $APP_NAME "S3_PUBLIC_REGION" "$AWS_DEFAULT_REGION" > /dev/null From 2f93a62d00e3e9c625482b010bd405d40335cfeb Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 14 Sep 2022 15:52:30 -0400 Subject: [PATCH 055/128] Correct jsdoc for getApplicationSubmission() --- app/server/app/utilities/bap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 0101b667..05184521 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -472,7 +472,7 @@ function getApplicationSubmissionsStatuses(req, comboKeys) { /** * Fetches an application form submission associated with a CSB Review Item ID * @param {express.Request} req - * @param {string[]} comboKeys + * @param {string} reviewItemId */ function getApplicationSubmission(req, reviewItemId) { return verifyBapConnection(req, { From 10eadf58b7b797d90fe2a8891954c2baa5297e84 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 14 Sep 2022 15:54:31 -0400 Subject: [PATCH 056/128] Update JSDoc comments --- app/server/app/utilities/bap.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 05184521..780cb13b 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -445,7 +445,7 @@ function getSamEntities(req, email) { } /** - * Fetches SAM.gov entity combo keys data associated with a provided user. + * Fetches SAM.gov entity combo keys associated with a provided user. * @param {express.Request} req * @param {string} email */ @@ -458,7 +458,7 @@ function getBapComboKeys(req, email) { } /** - * Fetches application form submissions associated with a provided set of combo keys. + * Fetches application form submissions statuses associated with a provided set of combo keys. * @param {express.Request} req * @param {string[]} comboKeys */ @@ -470,7 +470,7 @@ function getApplicationSubmissionsStatuses(req, comboKeys) { } /** - * Fetches an application form submission associated with a CSB Review Item ID + * Fetches an application form submission associated with a CSB Review Item ID. * @param {express.Request} req * @param {string} reviewItemId */ From 13c84e4d286ae7f12d0044112eda8b66ec369af2 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 14 Sep 2022 20:57:39 -0400 Subject: [PATCH 057/128] Include UEI-EFTI combo key field in bap application submission query --- app/client/src/contexts/bap.tsx | 1 + app/server/app/utilities/bap.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/client/src/contexts/bap.tsx b/app/client/src/contexts/bap.tsx index 69660d19..4c857c08 100644 --- a/app/client/src/contexts/bap.tsx +++ b/app/client/src/contexts/bap.tsx @@ -43,6 +43,7 @@ export type BapSamEntity = { }; type BapApplicationSubmission = { + UEI_EFTI_Combo_Key__c: string; CSB_Form_ID__c: string; // MongoDB ObjectId string CSB_Modified_Full_String__c: string; // ISO 8601 date string CSB_Review_Item_ID__c: string; // CSB Rebate ID w/ form/version ID (9 digits) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index 780cb13b..d2862966 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -191,6 +191,7 @@ async function queryForApplicationSubmissionsStatuses(req, comboKeys) { /* SOQL: */ // `SELECT + // UEI_EFTI_Combo_Key__c, // CSB_Form_ID__c, // CSB_Modified_Full_String__c, // CSB_Review_Item_ID__c, @@ -211,6 +212,7 @@ async function queryForApplicationSubmissionsStatuses(req, comboKeys) { { UEI_EFTI_Combo_Key__c: { $in: comboKeys } }, { // "*": 1, + UEI_EFTI_Combo_Key__c: 1, CSB_Form_ID__c: 1, // MongoDB ObjectId string CSB_Modified_Full_String__c: 1, // ISO 8601 date string CSB_Review_Item_ID__c: 1, // CSB Rebate ID w/ form/version ID (9 digits) From edfc056c1cee34a65c9a04bd1487411756ebc1a3 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 14 Sep 2022 21:07:14 -0400 Subject: [PATCH 058/128] Update client app to post minimal payment request data to server app, which performs BAP query with it and posts a new draft payment request submission to formio --- app/client/src/routes/allRebates.tsx | 100 ++++++--------------------- app/server/app/routes/api.js | 75 +++++++++++++++----- 2 files changed, 82 insertions(+), 93 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 393c9caf..e85097a3 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -11,12 +11,8 @@ import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; import { useCsbState } from "contexts/csb"; -import { BapSamEntity, useBapState, useBapDispatch } from "contexts/bap"; -import { - FormioApplicationSubmission, - useFormioState, - useFormioDispatch, -} from "contexts/formio"; +import { useBapState, useBapDispatch } from "contexts/bap"; +import { useFormioState, useFormioDispatch } from "contexts/formio"; /** Custom hook to fetch Application form submissions from Forms.gov */ function useFetchedFormioApplicationSubmissions() { @@ -93,71 +89,6 @@ function useFetchedFormioPaymentRequestSubmissions() { }, [samEntities, dispatch]); } -function createNewPaymentRequest( - email: string, - entity: BapSamEntity, - rebateId: string, - applicationData: FormioApplicationSubmission["data"] -) { - const { title, name } = getUserInfo(email, entity); - const { - bap_hidden_entity_combo_key, - ncesDistrictId, - totalRebateFundsRequested, - primaryContactName, - primaryContactTitle, - primaryContactPhoneNumber, - primaryContactEmail, - alternateContactName, - alternateContactTitle, - alternateContactPhoneNumber, - alternateContactEmail, - applicantOrganizationName, - privateFleetName, - schoolDistrictName, - schoolDistricPrioritized, - } = applicationData; - - return postData(`${serverUrl}/api/formio-payment-request-submission/`, { - data: { - last_updated_by: email, - hidden_current_user_email: email, - hidden_current_user_title: title, - hidden_current_user_name: name, - bap_hidden_entity_combo_key, - hidden_bap_review_item_id: rebateId, - hidden_bap_prioritized: schoolDistricPrioritized, - hidden_bap_bus_data: null, // TODO: get from BAP (to include bus numbers) - hidden_bap_district_id: ncesDistrictId, - hidden_bap_requested_funds: totalRebateFundsRequested, - hidden_bap_primary_name: primaryContactName, - hidden_bap_primary_title: primaryContactTitle, - hidden_bap_primary_phone_number: primaryContactPhoneNumber, - hidden_bap_primary_email: primaryContactEmail, - hidden_bap_alternate_name: alternateContactName, - hidden_bap_alternate_title: alternateContactTitle, - hidden_bap_alternate_phone_number: alternateContactPhoneNumber, - hidden_bap_alternate_email: alternateContactEmail, - hidden_bap_org_name: applicantOrganizationName, - hidden_bap_fleet_name: privateFleetName, - hidden_bap_district_name: schoolDistrictName, - hidden_bap_infra_max_rebate: null, // TODO: get from BAP - busInfo: [ - { - busNum: 1, // from BAP - maxRebate: 250000, // from Formio - newBusFuelType: "Electric", // from Formio - oldBusFuelType: "Diesel", // from Formio - oldBusModelYear: 2007, // from Formio - oldBusVin: "ETBBT123710161315", // from Formio - oldBusNcesDistrictId: "3407500", // from Formio - }, - ], - }, - state: "draft", - }); -} - export function AllRebates() { const navigate = useNavigate(); const { content } = useContentState(); @@ -233,9 +164,11 @@ export function AllRebates() { return { ...formioSub, bap: { - lastModified: match?.CSB_Modified_Full_String__c || null, + comboKey: match?.UEI_EFTI_Combo_Key__c || null, rebateId: match?.Parent_Rebate_ID__c || null, + reviewItemId: match?.CSB_Review_Item_ID__c || null, rebateStatus: match?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null, + lastModified: match?.CSB_Modified_Full_String__c || null, }, }; }); @@ -630,8 +563,9 @@ save the form for the EFT indicator to be displayed. */
- {submissions.map((submission, index) => { - const { bap, _id, state, modified, data } = submission; + {submissions.map(({ type, formio, bap }, index) => { + const { _id, state, modified, data } = formio; const { bap_hidden_entity_combo_key, applicantUEI, @@ -302,13 +345,6 @@ export function AllRebates() { ? "text-italic" : ""; - /** - * Apply USWDS `usa-table--striped` styles to each rebate, - * which can include up to three rows – one for each of the - * forms: Application, Purchase Order, and Close-Out. - */ - const rebateStyles = index % 2 ? "bg-white" : "bg-gray-5"; - /** * matched SAM.gov entity for each submission (used to set the * user's name and title in a new payment request form) @@ -333,7 +369,7 @@ starting a new application), indicate to the user they need to first save the form for the fields to be displayed. */ return ( - + + )} + + { + /* empty row after each submission but the last one */ + index !== submissions.length - 1 && ( + + + + ) + } ); })} From 4cb9ff4b69d694b6fc4fe0cd33d53059ee31e97e Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 15 Sep 2022 21:40:02 -0400 Subject: [PATCH 064/128] Update allRebates to restructure submission data into a format that will work with all three forms from both data sources (Formio and BAP) in one unified table --- app/client/src/contexts/formio.tsx | 2 +- app/client/src/routes/allRebates.tsx | 946 +++++++++++++++------------ 2 files changed, 519 insertions(+), 429 deletions(-) diff --git a/app/client/src/contexts/formio.tsx b/app/client/src/contexts/formio.tsx index 8e6fefc9..61899d44 100644 --- a/app/client/src/contexts/formio.tsx +++ b/app/client/src/contexts/formio.tsx @@ -43,7 +43,7 @@ export type FormioApplicationSubmission = { }; }; -type FormioPaymentRequestSubmission = { +export type FormioPaymentRequestSubmission = { [field: string]: unknown; _id: string; // MongoDB ObjectId string state: "submitted" | "draft"; diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index d0e9ef3f..e55a0c63 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -11,8 +11,40 @@ import { TextWithTooltip } from "components/infoTooltip"; import { useContentState } from "contexts/content"; import { useUserState } from "contexts/user"; import { useCsbState } from "contexts/csb"; +import { + FormioApplicationSubmission, + FormioPaymentRequestSubmission, + useFormioState, + useFormioDispatch, +} from "contexts/formio"; import { useBapState, useBapDispatch } from "contexts/bap"; -import { useFormioState, useFormioDispatch } from "contexts/formio"; + +type Rebate = { + application: { + formio: FormioApplicationSubmission; + bap: { + comboKey: string | null; + rebateId: string | null; + reviewItemId: string | null; + rebateStatus: string | null; + lastModified: string | null; + } | null; + }; + paymentRequest: { + formio: FormioPaymentRequestSubmission | null; + bap: null; + }; + closeOut: { + formio: null; + bap: null; + }; +}; + +type MessageState = { + displayed: boolean; + type: "info" | "success" | "warning" | "error"; + text: string; +}; /** Custom hook to fetch Application form submissions from Forms.gov */ function useFetchedFormioApplicationSubmissions() { @@ -94,8 +126,463 @@ function useFetchedBapPaymentRequestSubmissions() { // TODO } -export function AllRebates() { +/** + * Custom hook to combine Application form submissions data, Payment Request + * form submissions data, and Close-Out form submissions data from both the BAP + * and Formio into a single `submissions` object, with the BAP assigned + * `rebateId` as the keys. + **/ +function useCombinedSubmissions() { + const { + applicationSubmissions: formioApplicationSubmissions, + paymentRequestSubmissions: formioPaymentRequestSubmissions, + } = useFormioState(); + + const { + applicationSubmissions: bapApplicationSubmissions, + // paymentRequestSubmissions: bapPaymentRequestSubmissions, + } = useBapState(); + + // ensure form submissions data has been fetched from both Formio and the BAP + if ( + formioApplicationSubmissions.status !== "success" || + formioPaymentRequestSubmissions.status !== "success" || + bapApplicationSubmissions.status !== "success" + // bapPaymentRequestSubmissions.status !== "success" + ) { + return {}; + } + + const submissions: { [rebateId: string]: Rebate } = {}; + + /** + * Iterate over Formio Application form submissions, matching them with + * submissions returned from the BAP, so we can build up each rebate object + * with the Application form submission data, initial Payment Request form + * and Close-out Form submission data structure (both to be updated/replaced) + */ + for (const formioSubmission of formioApplicationSubmissions.data) { + const bapMatch = bapApplicationSubmissions.data.find((bapSubmission) => { + return bapSubmission.CSB_Form_ID__c === formioSubmission._id; + }); + + /* NOTE: If new Application form submissions have been reciently created in + Formio and the BAP's ETL process has not yet run to pickup those new Formio + submissions, all of the fields below will be null, so instead of assigning + the submission's key as `rebateId` (which will be null), we'll assign it to + be an underscore concatenated with the Formio submission's mongoDB ObjectID. + */ + const comboKey = bapMatch?.UEI_EFTI_Combo_Key__c || null; + const rebateId = bapMatch?.Parent_Rebate_ID__c || null; + const reviewItemId = bapMatch?.CSB_Review_Item_ID__c || null; + const rebateStatus = + bapMatch?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null; + const lastModified = bapMatch?.CSB_Modified_Full_String__c || null; + + submissions[rebateId || `_${formioSubmission._id}`] = { + application: { + formio: { ...formioSubmission }, + bap: { comboKey, rebateId, reviewItemId, rebateStatus, lastModified }, + }, + paymentRequest: { formio: null, bap: null }, + closeOut: { formio: null, bap: null }, + }; + } + + /** + * Iterate over Formio Payment Request form submissions, matching them with + * submissions returned from the BAP, so we can set the Payment Request form + * submission data. + */ + for (const formioSubmission of formioPaymentRequestSubmissions.data) { + // const bapMatch = bapPaymentRequestSubmissions.data.find((bapSubmission) => { + // return bapSubmission; // TODO + // }); + + const rebateId = formioSubmission.data.hidden_bap_rebate_id; + + submissions[rebateId].paymentRequest = { + formio: { ...formioSubmission }, + bap: null, + }; + } + + return submissions; +} + +function ApplicationSubmission({ + rebate, + setMessage, +}: { + rebate: Rebate; + setMessage: React.Dispatch>; +}) { + const navigate = useNavigate(); + const { csbData } = useCsbState(); + + if (csbData.status !== "success") return null; + const { enrollmentClosed } = csbData.data; + + const { application } = rebate; + + const { + applicantUEI, + applicantEfti, + applicantEfti_display, + applicantOrganizationName, + schoolDistrictName, + last_updated_by, + } = application.formio.data; + + const date = new Date(application.formio.modified).toLocaleDateString(); + const time = new Date(application.formio.modified).toLocaleTimeString(); + + /** + * The application has been updated since the last time the BAP's submissions + * ETL process has last succesfully run. + */ + const applicationHasBeenUpdated = application.bap?.lastModified + ? new Date(application.formio.modified) > + new Date(application.bap.lastModified) + : false; + + const applicationNeedsEdits = + application.bap?.rebateStatus === "Edits Requested" && + (application.formio.state === "draft" || + (application.formio.state === "submitted" && !applicationHasBeenUpdated)); + + const applicationHasBeenWithdrawn = + application.bap?.rebateStatus === "Withdrawn"; + + const statusStyles = applicationNeedsEdits + ? "csb-needs-edits" + : enrollmentClosed || application.formio.state === "submitted" + ? "text-italic" + : ""; + + /** + * NOTE on the usage of `TextWithTooltip` below: + * When a form is first initially created, and the user has not yet clicked + * the "Next" or "Save" buttons, any fields that the Formio form definition + * sets automatically (based on hidden fields we inject on form creation) will + * not yet be part of the form submission data. As soon as the user clicks the + * "Next" or "Save" buttons the first time, those fields will be set and + * stored in the submission. Since we display some of those fields in the + * table below, we need to check if their values exist, and if they don't (for + * cases where the user has not yet advanced past the first screen of the + * form...which we believe is a bit of an edge case, as most users will likely + * do that after starting a new application), indicate to the user they need + * to first save the form for the fields to be displayed. + */ + return ( + + + + + + + + + + + + + + ); +} + +function PaymentRequestSubmission({ + rebate, + setMessage, +}: { + rebate: Rebate; + setMessage: React.Dispatch>; +}) { const navigate = useNavigate(); + const { epaUserData } = useUserState(); + const { samEntities } = useBapState(); + + if (epaUserData.status !== "success") return null; + if (samEntities.status !== "success") return null; + + const email = epaUserData.data.mail; + const { application, paymentRequest } = rebate; + + const applicationHasBeenSelected = + application.bap?.rebateStatus === "Selected"; + + /** matched SAM.gov entity for the application */ + const entity = samEntities.data.entities.find((entity) => { + return ( + entity.ENTITY_STATUS__c === "Active" && + entity.ENTITY_COMBO_KEY__c === + application.formio.data.bap_hidden_entity_combo_key + ); + }); + + if (applicationHasBeenSelected) { + return ( + + + + ); + } + + if (paymentRequest.formio?._id) { + return ( + + + + ); + } + + return null; +} + +export function AllRebates() { const { content } = useContentState(); const { epaUserData } = useUserState(); const { csbData } = useCsbState(); @@ -109,11 +596,7 @@ export function AllRebates() { paymentRequestSubmissions: formioPaymentRequestSubmissions, } = useFormioState(); - const [message, setMessage] = useState<{ - displayed: boolean; - type: "info" | "success" | "warning" | "error"; - text: string; - }>({ + const [message, setMessage] = useState({ displayed: false, type: "info", text: "", @@ -125,15 +608,17 @@ export function AllRebates() { useFetchedFormioPaymentRequestSubmissions(); useFetchedBapPaymentRequestSubmissions(); + const submissions = useCombinedSubmissions(); + if ( csbData.status !== "success" || epaUserData.status !== "success" || samEntities.status === "idle" || samEntities.status === "pending" || - bapApplicationSubmissions.status === "idle" || - bapApplicationSubmissions.status === "pending" || formioApplicationSubmissions.status === "idle" || formioApplicationSubmissions.status === "pending" || + bapApplicationSubmissions.status === "idle" || + bapApplicationSubmissions.status === "pending" || formioPaymentRequestSubmissions.status === "idle" || formioPaymentRequestSubmissions.status === "pending" ) { @@ -145,8 +630,8 @@ export function AllRebates() { } if ( - bapApplicationSubmissions.status === "failure" || - formioApplicationSubmissions.status === "failure" + formioApplicationSubmissions.status === "failure" || + bapApplicationSubmissions.status === "failure" ) { return ; } @@ -157,68 +642,9 @@ export function AllRebates() { ); } - const { enrollmentClosed } = csbData.data; - const email = epaUserData.data.mail; - - /** - * Formio Application submissions, merged with submissions returned from the - * BAP, so we can include CSB rebate status, CSB review item ID, and last - * updated datetime. - */ - const applicationSubmissions = formioApplicationSubmissions.data.map( - (formioSubmission) => { - const match = bapApplicationSubmissions.data.find((bapSubmission) => { - return bapSubmission.CSB_Form_ID__c === formioSubmission._id; - }); - - return { - type: "Application", - formio: { - ...formioSubmission, - }, - bap: { - comboKey: match?.UEI_EFTI_Combo_Key__c || null, - rebateId: match?.Parent_Rebate_ID__c || null, - reviewItemId: match?.CSB_Review_Item_ID__c || null, - rebateStatus: - match?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null, - lastModified: match?.CSB_Modified_Full_String__c || null, - }, - }; - } - ); - - /** - * Formio Payment Request submissions, merged with submissions returned from - * the BAP, so we can include...(TODO) - */ - const paymentRequestSubmissions = formioPaymentRequestSubmissions.data.map( - (formioSubmission) => { - const match = bapPaymentRequestSubmissions.data.find((bapSubmission) => { - return bapSubmission; - }); - - return { - type: "Payment Request", - formio: { - ...formioSubmission, - }, - bap: { - comboKey: match ? null : null, - rebateId: match ? null : null, - reviewItemId: match ? null : null, - rebateStatus: match ? null : null, - lastModified: match ? null : null, - }, - }; - } - ); - - const submissions = [...applicationSubmissions, ...paymentRequestSubmissions]; - return ( <> - {submissions.length === 0 ? ( + {Object.keys(submissions).length === 0 ? (
@@ -304,368 +730,32 @@ export function AllRebates() {
- {submissions.map(({ type, formio, bap }, index) => { - const { _id, state, modified, data } = formio; - const { - bap_hidden_entity_combo_key, - applicantUEI, - applicantEfti, - applicantEfti_display, - applicantOrganizationName, - schoolDistrictName, - last_updated_by, - } = data; - - const date = new Date(modified).toLocaleDateString(); - const time = new Date(modified).toLocaleTimeString(); - - /** - * The submission has been updated since the last time the - * BAP's submissions ETL process has last succesfully run. - */ - const submissionHasBeenUpdated = bap.lastModified - ? new Date(modified) > new Date(bap.lastModified) - : false; - - const submissionNeedsEdits = - bap.rebateStatus === "Edits Requested" && - (state === "draft" || - (state === "submitted" && !submissionHasBeenUpdated)); - - const submissionHasBeenWithdrawn = - bap.rebateStatus === "Withdrawn"; - - const submissionHasBeenSelected = false; - // const submissionHasBeenSelected = - // bap.rebateStatus === "Selected"; - - const statusStyles = submissionNeedsEdits - ? "csb-needs-edits" - : enrollmentClosed || state === "submitted" - ? "text-italic" - : ""; - - /** - * matched SAM.gov entity for each submission (used to set the - * user's name and title in a new payment request form) - */ - const entity = samEntities.data.entities.find((entity) => { + {Object.entries(submissions).map( + ([rebateId, rebate], index) => { return ( - entity.ENTITY_STATUS__c === "Active" && - entity.ENTITY_COMBO_KEY__c === bap_hidden_entity_combo_key - ); - }); - - /* NOTE: when a form is first initially created, and the user -has not yet clicked the "Next" or "Save" buttons, any fields that the Formio -form definition sets automatically (based on hidden fields we inject on form -creation) will not yet be part of the form submission data. As soon as the user -clicks the "Next" or "Save" buttons the first time, those fields will be set and -stored in the submission. Since we display some of those fields in the table -below, we need to check if their values exist, and if they don't (for cases -where the user has not yet advanced past the first screen of the form...which we -believe is a bit of an edge case, as most users will likely do that after -starting a new application), indicate to the user they need to first save the -form for the fields to be displayed. */ - return ( - - - - - - - - - - - - - - - - {submissionHasBeenSelected && ( - - - - )} - - { - /* empty row after each submission but the last one */ - index !== submissions.length - 1 && ( + + + + + + {/* blank row after each rebate but the last one */} + {index !== Object.keys(submissions).length - 1 && ( - - ) - } - - ); - })} + )} + + ); + } + )}
+ +
{submissionNeedsEdits ? ( + {bap.rebateId ? ( {bap.rebateId} @@ -319,7 +334,7 @@ form for the fields to be displayed. */ )} + Application
@@ -358,7 +373,7 @@ form for the fields to be displayed. */
+ <> {Boolean(applicantUEI) ? ( applicantUEI @@ -372,12 +387,12 @@ form for the fields to be displayed. */ { /* NOTE: Initial version of the application form definition included the `applicantEfti` -field, which is configured via the form definition (in formio/forms.gov) to set +field, which is configured via the form definition (in Formio/Forms.gov) to set its value based on the value of the `sam_hidden_applicant_efti` field, which we inject on initial form submission. That value comes from the BAP (SAM.gov data), which could be an empty string. -To handle the potentially empty string, the formio form definition was updated +To handle the potentially empty string, the Formio form definition was updated to include a new `applicantEfti_display` field that's configured in the form definition to set it's value to the string '0000' if the `applicantEfti` field's value is an empty string. This logic (again, built into the form definition) @@ -421,7 +436,7 @@ save the form for the EFT indicator to be displayed. */ + <> {Boolean(applicantOrganizationName) ? ( applicantOrganizationName @@ -443,12 +458,37 @@ save the form for the EFT indicator to be displayed. */ + {last_updated_by}
{date}
+ + + + + New Payment Request + + + +
- { + // TODO: create new payment request form draft submission and redirect to page rendering Form + }} > - +
{submissionNeedsEdits ? ( @@ -515,7 +578,27 @@ save the form for the EFT indicator to be displayed. */
{submissionNeedsEdits ? (
+   +
+ {applicationNeedsEdits ? ( + + ) : enrollmentClosed || application.formio.state === "submitted" ? ( + + + + View + + + ) : application.formio.state === "draft" ? ( + + + + Edit + + + ) : null} + + {application.bap?.rebateId ? ( + + {application.bap.rebateId} + + ) : ( + + )} + + Application +
+ + + + { + applicationNeedsEdits || applicationHasBeenWithdrawn + ? application.bap?.rebateStatus + : application.formio.state === "draft" + ? "Draft" + : application.formio.state === "submitted" + ? "Submitted" + : application.formio.state // fallback, not used + } + + +
+ <> + {Boolean(applicantUEI) ? ( + applicantUEI + ) : ( + + )} +
+ { + /* NOTE: +Initial version of the application form definition included the `applicantEfti` +field, which is configured via the form definition (in Formio/Forms.gov) to set +its value based on the value of the `sam_hidden_applicant_efti` field, which we +inject on initial form submission. That value comes from the BAP (SAM.gov data), +which could be an empty string. + +To handle the potentially empty string, the Formio form definition was updated +to include a new `applicantEfti_display` field that's configured in the form +definition to set it's value to the string '0000' if the `applicantEfti` field's +value is an empty string. This logic (again, built into the form definition) +works great for new form submissions that have taken place after the form +definition has been updated to include this `applicantEfti_display` field... */ + Boolean(applicantEfti_display) ? ( + applicantEfti_display + ) : /* NOTE: +...but we need to handle old/existing submissions that were submitted before the +form definition was updated to include the new `applicantEfti_display` field, +and where the user has already advanced past the first screen (e.g. they've hit +the "Next" or "Save" buttons at least once). + +At this point the form definition logic has already kicked in that sets the +`applicaitonEfti` field, but it's value _could_ be an empty string (it won't +necessairly be, but it could be). Since the `applicantEfti` field's value could +be an empty string (which is falsy in JavaScript), we need to check another +field's value that will also set at this point, and whose value will always be +truthy. We'll check the `applicantUEI` field's value, as it's value will always +be set for users that have advanced past the first screen (we could have just as +easily used another field, like the `applicantOrganizationName` field for the +same result). */ + Boolean(applicantUEI) ? ( + /* NOTE: +If the `applicantUEI` field's value is truthy, we know the user has advanced +past the first screen, so we'll render the value of the `applicantEfti` field, +and fall back to "0000", which will be used in cases where the `applicantEfti` +field's value is an empty string. */ + applicantEfti || "0000" + ) : ( + /* NOTE: +At this point in the conditional logic, we know the user has not advanced past +the first screen, so we'll render the tooltip, indicating the user must edit and +save the form for the EFT indicator to be displayed. */ + + ) + } + +
+ <> + {Boolean(applicantOrganizationName) ? ( + applicantOrganizationName + ) : ( + + )} +
+ {Boolean(schoolDistrictName) ? ( + schoolDistrictName + ) : ( + + )} + +
+ {last_updated_by} +
+ {date} +
+ +
+ (Payment Request submission fields) +
- {submissionNeedsEdits ? ( - - ) : enrollmentClosed || state === "submitted" ? ( - - - - View - - - ) : state === "draft" ? ( - - - - Edit - - - ) : null} - - {bap.rebateId ? ( - - {bap.rebateId} - - ) : ( - - )} - - Application -
- - - - { - submissionNeedsEdits || - submissionHasBeenWithdrawn - ? bap.rebateStatus - : state === "draft" - ? "Draft" - : state === "submitted" - ? "Submitted" - : state // fallback, not used - } - - -
- <> - {Boolean(applicantUEI) ? ( - applicantUEI - ) : ( - - )} -
- { - /* NOTE: -Initial version of the application form definition included the `applicantEfti` -field, which is configured via the form definition (in Formio/Forms.gov) to set -its value based on the value of the `sam_hidden_applicant_efti` field, which we -inject on initial form submission. That value comes from the BAP (SAM.gov data), -which could be an empty string. - -To handle the potentially empty string, the Formio form definition was updated -to include a new `applicantEfti_display` field that's configured in the form -definition to set it's value to the string '0000' if the `applicantEfti` field's -value is an empty string. This logic (again, built into the form definition) -works great for new form submissions that have taken place after the form -definition has been updated to include this `applicantEfti_display` field... */ - Boolean(applicantEfti_display) ? ( - applicantEfti_display - ) : /* NOTE: -...but we need to handle old/existing submissions that were submitted before the -form definition was updated to include the new `applicantEfti_display` field, -and where the user has already advanced past the first screen (e.g. they've hit -the "Next" or "Save" buttons at least once). - -At this point the form definition logic has already kicked in that sets the -`applicaitonEfti` field, but it's value _could_ be an empty string (it won't -necessairly be, but it could be). Since the `applicantEfti` field's value could -be an empty string (which is falsy in JavaScript), we need to check another -field's value that will also set at this point, and whose value will always be -truthy. We'll check the `applicantUEI` field's value, as it's value will always -be set for users that have advanced past the first screen (we could have just as -easily used another field, like the `applicantOrganizationName` field for the -same result). */ - Boolean(applicantUEI) ? ( - /* NOTE: -If the `applicantUEI` field's value is truthy, we know the user has advanced -past the first screen, so we'll render the value of the `applicantEfti` field, -and fall back to "0000", which will be used in cases where the `applicantEfti` -field's value is an empty string. */ - applicantEfti || "0000" - ) : ( - /* NOTE: -At this point in the conditional logic, we know the user has not advanced past -the first screen, so we'll render the tooltip, indicating the user must edit and -save the form for the EFT indicator to be displayed. */ - - ) - } - -
- <> - {Boolean(applicantOrganizationName) ? ( - applicantOrganizationName - ) : ( - - )} -
- {Boolean(schoolDistrictName) ? ( - schoolDistrictName - ) : ( - - )} - -
- {last_updated_by} -
- {date} -
- -
+  
From e9a8f5f52f79877d4f735659018c16982d0d5f68 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 15 Sep 2022 21:49:29 -0400 Subject: [PATCH 065/128] Reorder PaymentRequestSubmission's conditional rendering so it renders the formio payment request submission (in place of the button) if it exists --- app/client/src/routes/allRebates.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index e55a0c63..b76af998 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -520,6 +520,16 @@ function PaymentRequestSubmission({ ); }); + if (paymentRequest.formio?._id) { + return ( + + + (Payment Request submission fields) + + + ); + } + if (applicationHasBeenSelected) { return ( @@ -569,16 +579,6 @@ function PaymentRequestSubmission({ ); } - if (paymentRequest.formio?._id) { - return ( - - - (Payment Request submission fields) - - - ); - } - return null; } From d0d5390c687644b06b604b770b72833614cd4c25 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 16 Sep 2022 09:30:51 -0400 Subject: [PATCH 066/128] Update allRebates table to show buttons linking to existing payment request forms --- app/client/src/contexts/bap.tsx | 17 +- app/client/src/routes/allRebates.tsx | 254 +++++++++++++++++++++------ 2 files changed, 214 insertions(+), 57 deletions(-) diff --git a/app/client/src/contexts/bap.tsx b/app/client/src/contexts/bap.tsx index 0d9117b4..e060474c 100644 --- a/app/client/src/contexts/bap.tsx +++ b/app/client/src/contexts/bap.tsx @@ -62,7 +62,22 @@ type BapApplicationSubmission = { }; type BapPaymentRequestSubmission = { - // TODO + UEI_EFTI_Combo_Key__c: string; + CSB_Form_ID__c: string; // MongoDB ObjectId string + CSB_Modified_Full_String__c: string; // ISO 8601 date string + CSB_Review_Item_ID__c: string; // CSB Rebate ID w/ form/version ID (9 digits) + Parent_Rebate_ID__c: string; // CSB Rebate ID (6 digits) + Parent_CSB_Rebate__r: { + CSB_Rebate_Status__c: + | "Draft" + | "Submitted" + | "Edits Requested" + | "Withdrawn" + | "Selected" + | "Not Selected"; + attributes: { type: string; url: string }; + }; + attributes: { type: string; url: string }; }; type State = { diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index b76af998..bee68b37 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -32,7 +32,13 @@ type Rebate = { }; paymentRequest: { formio: FormioPaymentRequestSubmission | null; - bap: null; + bap: { + comboKey: string | null; + rebateId: string | null; + reviewItemId: string | null; + rebateStatus: string | null; + lastModified: string | null; + } | null; }; closeOut: { formio: null; @@ -123,7 +129,32 @@ function useFetchedFormioPaymentRequestSubmissions() { /** Custom hook to fetch Payment Request form submissions from the BAP */ function useFetchedBapPaymentRequestSubmissions() { - // TODO + const dispatch = useBapDispatch(); + + useEffect(() => { + dispatch({ type: "FETCH_BAP_PAYMENT_REQUEST_SUBMISSIONS_REQUEST" }); + + // TODO: update query to fetch BAP data once the BAP team has things set up + dispatch({ + type: "FETCH_BAP_PAYMENT_REQUEST_SUBMISSIONS_SUCCESS", + payload: { + paymentRequestSubmissions: [ + { + attributes: { type: "", url: "" }, + UEI_EFTI_Combo_Key__c: "", + CSB_Form_ID__c: "", + CSB_Modified_Full_String__c: "", + CSB_Review_Item_ID__c: "", + Parent_Rebate_ID__c: "", + Parent_CSB_Rebate__r: { + attributes: { type: "", url: "" }, + CSB_Rebate_Status__c: "Draft", + }, + }, + ], + }, + }); + }, [dispatch]); } /** @@ -140,15 +171,15 @@ function useCombinedSubmissions() { const { applicationSubmissions: bapApplicationSubmissions, - // paymentRequestSubmissions: bapPaymentRequestSubmissions, + paymentRequestSubmissions: bapPaymentRequestSubmissions, } = useBapState(); // ensure form submissions data has been fetched from both Formio and the BAP if ( formioApplicationSubmissions.status !== "success" || formioPaymentRequestSubmissions.status !== "success" || - bapApplicationSubmissions.status !== "success" - // bapPaymentRequestSubmissions.status !== "success" + bapApplicationSubmissions.status !== "success" || + bapPaymentRequestSubmissions.status !== "success" ) { return {}; } @@ -195,15 +226,23 @@ function useCombinedSubmissions() { * submission data. */ for (const formioSubmission of formioPaymentRequestSubmissions.data) { - // const bapMatch = bapPaymentRequestSubmissions.data.find((bapSubmission) => { - // return bapSubmission; // TODO - // }); + const bapMatch = bapPaymentRequestSubmissions.data.find((bapSubmission) => { + return ( + bapSubmission.Parent_Rebate_ID__c === + formioSubmission.data.hidden_bap_rebate_id + ); + }); - const rebateId = formioSubmission.data.hidden_bap_rebate_id; + const comboKey = bapMatch?.UEI_EFTI_Combo_Key__c || null; + const rebateId = bapMatch?.Parent_Rebate_ID__c || null; + const reviewItemId = bapMatch?.CSB_Review_Item_ID__c || null; + const rebateStatus = + bapMatch?.Parent_CSB_Rebate__r?.CSB_Rebate_Status__c || null; + const lastModified = bapMatch?.CSB_Modified_Full_String__c || null; - submissions[rebateId].paymentRequest = { + submissions[formioSubmission.data.hidden_bap_rebate_id].paymentRequest = { formio: { ...formioSubmission }, - bap: null, + bap: { comboKey, rebateId, reviewItemId, rebateStatus, lastModified }, }; } @@ -511,32 +550,18 @@ function PaymentRequestSubmission({ const applicationHasBeenSelected = application.bap?.rebateStatus === "Selected"; - /** matched SAM.gov entity for the application */ - const entity = samEntities.data.entities.find((entity) => { - return ( - entity.ENTITY_STATUS__c === "Active" && - entity.ENTITY_COMBO_KEY__c === - application.formio.data.bap_hidden_entity_combo_key - ); - }); - - if (paymentRequest.formio?._id) { - return ( - - - (Payment Request submission fields) - - - ); - } - - if (applicationHasBeenSelected) { + // Application has been selected, but a Payment Request submission has not yet been created + if (applicationHasBeenSelected && !paymentRequest.formio) { return ( + ) : paymentRequest.formio.state === "submitted" ? ( + + + + View + + + ) : paymentRequest.formio.state === "draft" ? ( + + + + Edit + + + ) : null} + + + + (Remaining Payment Request submission fields) + + + ); } export function AllRebates() { @@ -731,30 +875,28 @@ export function AllRebates() { {Object.entries(submissions).map( - ([rebateId, rebate], index) => { - return ( - - - - - - {/* blank row after each rebate but the last one */} - {index !== Object.keys(submissions).length - 1 && ( - - -   - - - )} - - ); - } + ([rebateId, rebate], index) => ( + + + + + + {/* blank row after all rebates but the last one */} + {index !== Object.keys(submissions).length - 1 && ( + + +   + + + )} + + ) )} From ab9bc50f46831187b6cbfbc679c6cfc1c3d82b95 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 16 Sep 2022 15:21:48 -0400 Subject: [PATCH 067/128] Remove fallback value for new payment requst submission data for primary applicant fields, org name, and school district name --- app/server/app/routes/api.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index 8813ff50..7d34f7e8 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -431,17 +431,17 @@ router.post( bap_hidden_entity_combo_key: comboKey, hidden_bap_rebate_id: rebateId, hidden_bap_district_id: CSB_NCES_ID__c, - hidden_bap_primary_name: Primary_Applicant__r?.Name || "", - hidden_bap_primary_title: Primary_Applicant__r?.Title || "", - hidden_bap_primary_phone_number: Primary_Applicant__r?.Phone || "", - hidden_bap_primary_email: Primary_Applicant__r?.Email || "", + hidden_bap_primary_name: Primary_Applicant__r?.Name, + hidden_bap_primary_title: Primary_Applicant__r?.Title, + hidden_bap_primary_phone_number: Primary_Applicant__r?.Phone, + hidden_bap_primary_email: Primary_Applicant__r?.Email, hidden_bap_alternate_name: Alternate_Applicant__r?.Name || "", hidden_bap_alternate_title: Alternate_Applicant__r?.Title || "", hidden_bap_alternate_phone_number: Alternate_Applicant__r?.Phone || "", hidden_bap_alternate_email: Alternate_Applicant__r?.Email || "", - hidden_bap_org_name: Applicant_Organization__r?.Name || "", - hidden_bap_district_name: CSB_School_District__r?.Name || "", + hidden_bap_org_name: Applicant_Organization__r?.Name, + hidden_bap_district_name: CSB_School_District__r?.Name, hidden_bap_fleet_name: Fleet_Name__c, hidden_bap_prioritized: School_District_Prioritized__c, hidden_bap_requested_funds: Total_Rebate_Funds_Requested__c, From ef9534ef22365274201bfef65483927a615cf9a9 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 16 Sep 2022 16:14:10 -0400 Subject: [PATCH 068/128] Update Application submission icon and status if BAP status returns 'Selected' and render more Payment Request submission fields in the table --- app/client/src/routes/allRebates.tsx | 76 +++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index bee68b37..8fb70983 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -264,6 +264,9 @@ function ApplicationSubmission({ const { application } = rebate; + const applicationHasBeenSelected = + application.bap?.rebateStatus === "Selected"; + const { applicantUEI, applicantEfti, @@ -403,14 +406,18 @@ function ApplicationSubmission({
{ - applicationNeedsEdits || applicationHasBeenWithdrawn + applicationHasBeenSelected || + applicationNeedsEdits || + applicationHasBeenWithdrawn ? application.bap?.rebateStatus : application.formio.state === "draft" ? "Draft" @@ -719,9 +728,55 @@ function PaymentRequestSubmission({ ) : null} - - (Remaining Payment Request submission fields) - +   + + + Payment Request +
+ + + + { + paymentRequestNeedsEdits || paymentRequestHasBeenWithdrawn + ? paymentRequest.bap?.rebateStatus + : paymentRequest.formio.state === "draft" + ? "Draft" + : paymentRequest.formio.state === "submitted" + ? "Submitted" + : paymentRequest.formio.state // fallback, not used + } + + + + +   + +   + + + {last_updated_by} +
+ {date} + ); } @@ -764,7 +819,9 @@ export function AllRebates() { bapApplicationSubmissions.status === "idle" || bapApplicationSubmissions.status === "pending" || formioPaymentRequestSubmissions.status === "idle" || - formioPaymentRequestSubmissions.status === "pending" + formioPaymentRequestSubmissions.status === "pending" || + bapPaymentRequestSubmissions.status === "idle" || + bapPaymentRequestSubmissions.status === "pending" ) { return ; } @@ -780,7 +837,10 @@ export function AllRebates() { return ; } - if (formioPaymentRequestSubmissions.status === "failure") { + if ( + formioPaymentRequestSubmissions.status === "failure" || + bapPaymentRequestSubmissions.status === "failure" + ) { return ( ); From 5e5427bcedfed044f7ac92b045096dcbce2dee28 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 16 Sep 2022 16:34:20 -0400 Subject: [PATCH 069/128] Add ability to debug combined submissions on allRebates page --- app/client/src/routes/allRebates.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 8fb70983..7065cd68 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -1,5 +1,5 @@ import { Fragment, useState, useEffect } from "react"; -import { Link, useNavigate } from "react-router-dom"; +import { Link, useNavigate, useSearchParams } from "react-router-dom"; import icons from "uswds/img/sprite.svg"; // --- import { serverUrl, messages, getData, postData } from "../config"; @@ -782,6 +782,8 @@ function PaymentRequestSubmission({ } export function AllRebates() { + const [searchParams] = useSearchParams(); + const { content } = useContentState(); const { epaUserData } = useUserState(); const { csbData } = useCsbState(); @@ -809,6 +811,14 @@ export function AllRebates() { const submissions = useCombinedSubmissions(); + // log combined 'submissions' object if 'debug' search parameter exists + useEffect(() => { + const submissionsAreSet = Boolean(Object.keys(submissions).length); + if (searchParams.has("debug") && submissionsAreSet) { + console.log(submissions); + } + }, [searchParams, submissions]); + if ( csbData.status !== "success" || epaUserData.status !== "success" || From 533e76ec6888694da2c039032b99226311a6f459 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Fri, 16 Sep 2022 16:49:44 -0400 Subject: [PATCH 070/128] Move assigning of entity up to reflect moved code that uses it in allRebates --- app/client/src/routes/allRebates.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 7065cd68..68e3d067 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -559,6 +559,15 @@ function PaymentRequestSubmission({ const applicationHasBeenSelected = application.bap?.rebateStatus === "Selected"; + /** matched SAM.gov entity for the application */ + const entity = samEntities.data.entities.find((entity) => { + return ( + entity.ENTITY_STATUS__c === "Active" && + entity.ENTITY_COMBO_KEY__c === + application.formio.data.bap_hidden_entity_combo_key + ); + }); + // Application has been selected, but a Payment Request submission has not yet been created if (applicationHasBeenSelected && !paymentRequest.formio) { return ( @@ -645,15 +654,6 @@ function PaymentRequestSubmission({ ? "text-italic" : ""; - /** matched SAM.gov entity for the application */ - const entity = samEntities.data.entities.find((entity) => { - return ( - entity.ENTITY_STATUS__c === "Active" && - entity.ENTITY_COMBO_KEY__c === - application.formio.data.bap_hidden_entity_combo_key - ); - }); - return ( From 9b0bacf50dbc960811ac5f6b609953719bdab698 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Mon, 19 Sep 2022 13:57:22 -0400 Subject: [PATCH 071/128] Update BAP bus table query to search via an equal to match of 'Old Bus' instead of a not equal to match of 'Infrastructure', as the MQL 'neq' syntax doesnt seem to work with jsforce --- app/server/app/utilities/bap.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/server/app/utilities/bap.js b/app/server/app/utilities/bap.js index d2862966..7f5cbb72 100644 --- a/app/server/app/utilities/bap.js +++ b/app/server/app/utilities/bap.js @@ -265,7 +265,7 @@ async function queryForApplicationSubmission(req, reviewItemId) { /* SOQL */ // `SELECT - // id, + // Id, // UEI_EFTI_Combo_Key__c, // CSB_NCES_ID__c, // Primary_Applicant__r.Name, @@ -363,7 +363,7 @@ async function queryForApplicationSubmission(req, reviewItemId) { // WHERE // recordtypeid = '${busTableId}' AND // Related_Order_Request__c = '${formsTableRecordId}' AND - // CSB_Rebate_Item_Type__c != 'Infrastructure'` + // CSB_Rebate_Item_Type__c = 'Old Bus'` const busTableRecordsQuery = await bapConnection .sobject(BAP_BUS_TABLE) @@ -371,7 +371,7 @@ async function queryForApplicationSubmission(req, reviewItemId) { { recordtypeid: busTableId, Related_Order_Request__c: formsTableRecordId, - CSB_Rebate_Item_Type__c: { $neq: "Infrastructure" }, + CSB_Rebate_Item_Type__c: "Old Bus", }, { // "*": 1, From 5cbfb2fb0f6440d633c755179c7df44b866138b6 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 20 Sep 2022 13:34:51 -0400 Subject: [PATCH 072/128] Organize ApplicationFormProvider state (move message fields into message object) --- app/client/src/routes/applicationForm.tsx | 81 +++++++++++++---------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/app/client/src/routes/applicationForm.tsx b/app/client/src/routes/applicationForm.tsx index bc390148..8afa33b6 100644 --- a/app/client/src/routes/applicationForm.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -30,9 +30,11 @@ type Props = { }; type State = { - displayed: boolean; - type: "info" | "success" | "warning" | "error"; - text: string; + message: { + displayed: boolean; + type: "info" | "success" | "warning" | "error"; + text: string; + }; }; type Action = @@ -63,9 +65,11 @@ function reducer(state: State, action: Action): State { const { text } = action.payload; return { ...state, - displayed: true, - type: "info", - text, + message: { + displayed: true, + type: "info", + text, + }, }; } @@ -73,9 +77,11 @@ function reducer(state: State, action: Action): State { const { text } = action.payload; return { ...state, - displayed: true, - type: "success", - text, + message: { + displayed: true, + type: "success", + text, + }, }; } @@ -83,9 +89,11 @@ function reducer(state: State, action: Action): State { const { text } = action.payload; return { ...state, - displayed: true, - type: "warning", - text, + message: { + displayed: true, + type: "warning", + text, + }, }; } @@ -93,18 +101,22 @@ function reducer(state: State, action: Action): State { const { text } = action.payload; return { ...state, - displayed: true, - type: "error", - text, + message: { + displayed: true, + type: "error", + text, + }, }; } case "RESET_MESSAGE": { return { ...state, - displayed: false, - type: "info", - text: "", + message: { + displayed: false, + type: "info", + text: "", + }, }; } @@ -117,9 +129,11 @@ function reducer(state: State, action: Action): State { function ApplicationFormProvider({ children }: Props) { const initialState: State = { - displayed: false, - type: "info", - text: "", + message: { + displayed: false, + type: "info", + text: "", + }, }; const [state, dispatch] = useReducer(reducer, initialState); @@ -226,9 +240,9 @@ type SubmissionState = }; function FormMessage() { - const { displayed, type, text } = useApplicationFormState(); - if (!displayed) return null; - return ; + const { message } = useApplicationFormState(); + if (!message.displayed) return null; + return ; } function ApplicationFormContent() { @@ -287,14 +301,11 @@ function ApplicationFormContent() { return s3Provider(s3Formio); }; - // remove `ncesDataSource` and `ncesDataLookup` fields const data = { ...res.submissionData.data }; - if (data.hasOwnProperty("ncesDataSource")) { - delete data.ncesDataSource; - } - if (data.hasOwnProperty("ncesDataLookup")) { - delete data.ncesDataLookup; - } + + // remove `ncesDataSource` and `ncesDataLookup` fields + if (data.hasOwnProperty("ncesDataSource")) delete data.ncesDataSource; + if (data.hasOwnProperty("ncesDataLookup")) delete data.ncesDataLookup; setStoredSubmissionData((prevData) => { storedSubmissionDataRef.current = data; @@ -461,7 +472,7 @@ function ApplicationFormContent() { // button (the component w/ the key "busInformation") is clicked // the `storedSubmissionDataRef` value is mutated, which invalidates // the isEqual() early return "dirty check" used in the onNextPage - // event callback below (as the two object being compared are now + // event callback below (as the two objects being compared are now // equal). That means if the user changed any of the bus info fields // (which are displayed via a Formio "Edit Grid" component, which // includes its own "Save" button that must be clicked) and clicked @@ -482,8 +493,9 @@ function ApplicationFormContent() { data: FormioSubmissionData; metadata: unknown; }) => { - // remove `ncesDataSource` and `ncesDataLookup` fields const data = { ...submission.data }; + + // remove `ncesDataSource` and `ncesDataLookup` fields if (data.hasOwnProperty("ncesDataSource")) { delete data.ncesDataSource; } @@ -560,8 +572,9 @@ function ApplicationFormContent() { metadata: unknown; }; }) => { - // remove `ncesDataSource` and `ncesDataLookup` fields const data = { ...submission.data }; + + // remove `ncesDataSource` and `ncesDataLookup` fields if (data.hasOwnProperty("ncesDataSource")) { delete data.ncesDataSource; } From 3c359d224460f65a3151b7bc5c653353ad9569a1 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Tue, 20 Sep 2022 17:34:16 -0400 Subject: [PATCH 073/128] Update naming of stored formio submission data in components --- app/client/src/contexts/formio.tsx | 1 - app/client/src/routes/applicationForm.tsx | 119 +++++++------------ app/client/src/routes/helpdesk.tsx | 63 ++++------ app/client/src/routes/paymentRequestForm.tsx | 86 +++++++------- app/server/app/routes/api.js | 8 +- app/server/app/routes/help.js | 4 +- 6 files changed, 112 insertions(+), 169 deletions(-) diff --git a/app/client/src/contexts/formio.tsx b/app/client/src/contexts/formio.tsx index 61899d44..44348362 100644 --- a/app/client/src/contexts/formio.tsx +++ b/app/client/src/contexts/formio.tsx @@ -83,7 +83,6 @@ type Action = } | { type: "FETCH_FORMIO_APPLICATION_SUBMISSIONS_FAILURE" } | { type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_REQUEST" } - | { type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_REQUEST" } | { type: "FETCH_FORMIO_PAYMENT_REQUEST_SUBMISSIONS_SUCCESS"; payload: { diff --git a/app/client/src/routes/applicationForm.tsx b/app/client/src/routes/applicationForm.tsx index 8afa33b6..6012b676 100644 --- a/app/client/src/routes/applicationForm.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -190,53 +190,39 @@ type FormioSubmissionData = { hidden_current_user_title?: string; hidden_current_user_name?: string; bap_hidden_entity_combo_key?: string; - ncesDataSource?: string; - ncesDataLookup?: string[]; }; +type FormioSubmission = { + [field: string]: unknown; + _id: string; // MongoDB ObjectId string + data: FormioSubmissionData; + state: "submitted" | "draft"; +}; + +type NoFormioData = { userAccess: false; formSchema: null; submission: null }; + type SubmissionState = | { status: "idle"; - data: { - userAccess: false; - formSchema: null; - submissionData: null; - }; + data: NoFormioData; } | { status: "pending"; - data: { - userAccess: false; - formSchema: null; - submissionData: null; - }; + data: NoFormioData; } | { status: "success"; data: + | NoFormioData | { userAccess: true; formSchema: { url: string; json: object }; - submissionData: { - [field: string]: unknown; - _id: string; // MongoDB ObjectId string - data: object; - state: "submitted" | "draft"; - }; - } - | { - userAccess: false; - formSchema: null; - submissionData: null; + submission: FormioSubmission; }; } | { status: "failure"; - data: { - userAccess: false; - formSchema: null; - submissionData: null; - }; + data: NoFormioData; }; function FormMessage() { @@ -257,11 +243,7 @@ function ApplicationFormContent() { const [formioSubmission, setFormioSubmission] = useState({ status: "idle", - data: { - userAccess: false, - formSchema: null, - submissionData: null, - }, + data: { userAccess: false, formSchema: null, submission: null }, }); // set when form submission data is initially fetched, and then re-set each @@ -283,11 +265,7 @@ function ApplicationFormContent() { useEffect(() => { setFormioSubmission({ status: "pending", - data: { - userAccess: false, - formSchema: null, - submissionData: null, - }, + data: { userAccess: false, formSchema: null, submission: null }, }); getData(`${serverUrl}/api/formio-application-submission/${mongoId}`) @@ -296,12 +274,12 @@ function ApplicationFormContent() { const s3Provider = Formio.Providers.providers.storage.s3; Formio.Providers.providers.storage.s3 = function (formio: any) { const s3Formio = cloneDeep(formio); - const comboKey = res.submissionData.data.bap_hidden_entity_combo_key; + const comboKey = res.submission.data.bap_hidden_entity_combo_key; s3Formio.formUrl = `${serverUrl}/api/${mongoId}/${comboKey}`; return s3Provider(s3Formio); }; - const data = { ...res.submissionData.data }; + const data = { ...res.submission.data }; // remove `ncesDataSource` and `ncesDataLookup` fields if (data.hasOwnProperty("ncesDataSource")) delete data.ncesDataSource; @@ -320,11 +298,7 @@ function ApplicationFormContent() { .catch((err) => { setFormioSubmission({ status: "failure", - data: { - userAccess: false, - formSchema: null, - submissionData: null, - }, + data: { userAccess: false, formSchema: null, submission: null }, }); }); }, [mongoId]); @@ -337,13 +311,13 @@ function ApplicationFormContent() { return ; } - const { userAccess, formSchema, submissionData } = formioSubmission.data; + const { userAccess, formSchema, submission } = formioSubmission.data; if ( formioSubmission.status === "failure" || !userAccess || !formSchema || - !submissionData + !submission ) { return (
- Application ID: {submissionData._id} + Application ID: {submission._id}
@@ -449,24 +423,20 @@ function ApplicationFormContent() { options={{ readOnly: (enrollmentClosed && !submissionNeedsEdits) || - submissionData.state === "submitted" + submission.state === "submitted" ? true : false, noAlerts: true, }} - onChange={(submission: { + onChange={(onChangeSubmission: { + [field: string]: unknown; changed: { + [field: string]: unknown; component: { [field: string]: unknown; key: string; }; - flags: unknown; - instance: unknown; - value: unknown; }; - data: FormioSubmissionData; - isValid: boolean; - metadata: unknown; }) => { // NOTE: For some unknown reason, whenever the bus info's "Save" // button (the component w/ the key "busInformation") is clicked @@ -484,16 +454,18 @@ function ApplicationFormContent() { // clicked (which must be clicked to close the bus info fields) to // guarantee the "dirty check" succeeds the next time the form's // "Next" button is clicked. - if (submission?.changed?.component?.key === "busInformation") { + if ( + onChangeSubmission?.changed?.component?.key === "busInformation" + ) { storedSubmissionDataRef.current = {}; } }} - onSubmit={(submission: { + onSubmit={(onSubmitSubmission: { state: "submitted" | "draft"; data: FormioSubmissionData; metadata: unknown; }) => { - const data = { ...submission.data }; + const data = { ...onSubmitSubmission.data }; // remove `ncesDataSource` and `ncesDataLookup` fields if (data.hasOwnProperty("ncesDataSource")) { @@ -503,14 +475,14 @@ function ApplicationFormContent() { delete data.ncesDataLookup; } - if (submission.state === "submitted") { + if (onSubmitSubmission.state === "submitted") { dispatch({ type: "DISPLAY_INFO_MESSAGE", payload: { text: "Submitting form..." }, }); } - if (submission.state === "draft") { + if (onSubmitSubmission.state === "draft") { dispatch({ type: "DISPLAY_INFO_MESSAGE", payload: { text: "Saving form..." }, @@ -520,8 +492,8 @@ function ApplicationFormContent() { setPendingSubmissionData(data); postData( - `${serverUrl}/api/formio-application-submission/${submissionData._id}`, - { ...submission, data } + `${serverUrl}/api/formio-application-submission/${submission._id}`, + { ...onSubmitSubmission, data } ) .then((res) => { setStoredSubmissionData((prevData) => { @@ -531,7 +503,7 @@ function ApplicationFormContent() { setPendingSubmissionData({}); - if (submission.state === "submitted") { + if (onSubmitSubmission.state === "submitted") { dispatch({ type: "DISPLAY_SUCCESS_MESSAGE", payload: { text: "Form successfully submitted." }, @@ -544,7 +516,7 @@ function ApplicationFormContent() { return; } - if (submission.state === "draft") { + if (onSubmitSubmission.state === "draft") { dispatch({ type: "DISPLAY_SUCCESS_MESSAGE", payload: { text: "Draft successfully saved." }, @@ -562,17 +534,14 @@ function ApplicationFormContent() { }); }); }} - onNextPage={({ - page, - submission, - }: { + onNextPage={(onNextParam: { page: number; submission: { data: FormioSubmissionData; metadata: unknown; }; }) => { - const data = { ...submission.data }; + const data = { ...onNextParam.submission.data }; // remove `ncesDataSource` and `ncesDataLookup` fields if (data.hasOwnProperty("ncesDataSource")) { @@ -584,7 +553,7 @@ function ApplicationFormContent() { // don't post an update if form is not in draft state // (form has been already submitted, and fields are read-only) - if (submissionData.state !== "draft") return; + if (submission.state !== "draft") return; // don't post an update if no changes have been made to the form // (ignoring current user fields) @@ -606,8 +575,8 @@ function ApplicationFormContent() { setPendingSubmissionData(data); postData( - `${serverUrl}/api/formio-application-submission/${submissionData._id}`, - { ...submission, data, state: "draft" } + `${serverUrl}/api/formio-application-submission/${submission._id}`, + { ...onNextParam.submission, data, state: "draft" } ) .then((res) => { setStoredSubmissionData((prevData) => { diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index d7f47101..c1d1b044 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -15,26 +15,22 @@ import { useUserState } from "contexts/user"; import { useCsbState } from "contexts/csb"; import { useDialogDispatch } from "contexts/dialog"; +type NoFormioData = { formSchema: null; submission: null }; + type SubmissionState = | { status: "idle"; - data: { - formSchema: null; - submissionData: null; - }; + data: NoFormioData; } | { status: "pending"; - data: { - formSchema: null; - submissionData: null; - }; + data: NoFormioData; } | { status: "success"; data: { formSchema: { url: string; json: object }; - submissionData: { + submission: { [field: string]: unknown; _id: string; // MongoDB ObjectId string state: "submitted" | "draft"; @@ -49,10 +45,7 @@ type SubmissionState = } | { status: "failure"; - data: { - formSchema: null; - submissionData: null; - }; + data: NoFormioData; }; export function Helpdesk() { @@ -71,10 +64,7 @@ export function Helpdesk() { const [formioApplicationSubmission, setFormioApplicationSubmission] = useState({ status: "idle", - data: { - formSchema: null, - submissionData: null, - }, + data: { formSchema: null, submission: null }, }); if ( @@ -92,7 +82,7 @@ export function Helpdesk() { const { enrollmentClosed } = csbData.data; - const { formSchema, submissionData } = formioApplicationSubmission.data; + const { formSchema, submission } = formioApplicationSubmission.data; return ( <> @@ -115,17 +105,14 @@ export function Helpdesk() { setFormioApplicationSubmission({ status: "pending", - data: { - formSchema: null, - submissionData: null, - }, + data: { formSchema: null, submission: null }, }); getData( `${serverUrl}/help/formio-application-submission/${searchText}` ) .then((res) => { - setFormId(res.submissionData._id); + setFormId(res.submission._id); setFormioApplicationSubmission({ status: "success", data: res, @@ -135,10 +122,7 @@ export function Helpdesk() { setFormId(""); setFormioApplicationSubmission({ status: "failure", - data: { - formSchema: null, - submissionData: null, - }, + data: { formSchema: null, submission: null }, }); }); }} @@ -186,7 +170,7 @@ export function Helpdesk() { {formioApplicationSubmission.status === "success" && formSchema && - submissionData && ( + submission && ( <>
- - - + + + - + diff --git a/app/server/app/routes/api.js b/app/server/app/routes/api.js index 73f2a081..7019c4dc 100644 --- a/app/server/app/routes/api.js +++ b/app/server/app/routes/api.js @@ -431,7 +431,6 @@ router.post( // issue with the field being changed to an object when the form loads const newSubmission = { data: { - last_updated_by: email, hidden_current_user_email: email, hidden_current_user_title: title, hidden_current_user_name: name, From ccd5a9c4f58e4b8828de4629ff490d4d3426c3ef Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 28 Sep 2022 16:33:53 -0400 Subject: [PATCH 086/128] Update applicationForm to fetch BAP Application form submissions --- app/client/src/routes/allRebates.tsx | 6 +++++- app/client/src/routes/applicationForm.tsx | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index 317c2916..b31165f9 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -53,6 +53,8 @@ function useFetchedFormioApplicationSubmissions() { const dispatch = useFormioDispatch(); useEffect(() => { + // while not used in this code, SAM.gov entities are used in the server + // app's `/api/formio-application-submissions` route controller if (samEntities.status !== "success" || !samEntities.data.results) { return; } @@ -73,11 +75,13 @@ function useFetchedFormioApplicationSubmissions() { } /** Custom hook to fetch Application form submissions from the BAP */ -function useFetchedBapApplicationSubmissions() { +export function useFetchedBapApplicationSubmissions() { const { samEntities } = useBapState(); const dispatch = useBapDispatch(); useEffect(() => { + // while not used in this code, SAM.gov entities are used in the server + // app's `/api/bap-application-submissions` route controller if (samEntities.status !== "success" || !samEntities.data.results) { return; } diff --git a/app/client/src/routes/applicationForm.tsx b/app/client/src/routes/applicationForm.tsx index a7812ced..7da60102 100644 --- a/app/client/src/routes/applicationForm.tsx +++ b/app/client/src/routes/applicationForm.tsx @@ -6,6 +6,7 @@ import icons from "uswds/img/sprite.svg"; // --- import { serverUrl, getData, postData } from "../config"; import { getUserInfo } from "../utilities"; +import { useFetchedBapApplicationSubmissions } from "routes/allRebates"; import { Loading } from "components/loading"; import { Message } from "components/message"; import { MarkdownContent } from "components/markdownContent"; @@ -37,6 +38,8 @@ export function ApplicationForm() { dispatch({ type: "RESET_STATE" }); }, [dispatch]); + useFetchedBapApplicationSubmissions(); + // set when form submission data is initially fetched, and then re-set each // time a successful update of the submission data is posted to forms.gov const [storedSubmissionData, setStoredSubmissionData] = From f34015d080f2e6b55026afb731d854a5e1fda745 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 29 Sep 2022 12:36:00 -0400 Subject: [PATCH 087/128] Update allRebates to sort submissions by most recient formio modified date, regardless of the form (Application, Payment Request, or Close-Out) --- app/client/src/contexts/formio.tsx | 10 ++++ app/client/src/routes/allRebates.tsx | 85 +++++++++++++++++++--------- 2 files changed, 68 insertions(+), 27 deletions(-) diff --git a/app/client/src/contexts/formio.tsx b/app/client/src/contexts/formio.tsx index 44348362..1b707764 100644 --- a/app/client/src/contexts/formio.tsx +++ b/app/client/src/contexts/formio.tsx @@ -60,6 +60,16 @@ export type FormioPaymentRequestSubmission = { }; }; +export type FormioCloseOutSubmission = { + [field: string]: unknown; + _id: string; // MongoDB ObjectId string + state: "submitted" | "draft"; + modified: string; // ISO 8601 date string + data: { + [field: string]: unknown; + }; +}; + type State = { applicationSubmissions: | { status: "idle"; data: [] } diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index b31165f9..de410341 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -14,6 +14,7 @@ import { useCsbState } from "contexts/csb"; import { FormioApplicationSubmission, FormioPaymentRequestSubmission, + FormioCloseOutSubmission, useFormioState, useFormioDispatch, } from "contexts/formio"; @@ -42,7 +43,7 @@ type Rebate = { } | null; }; closeOut: { - formio: null; + formio: FormioCloseOutSubmission | null; bap: null; }; }; @@ -188,8 +189,8 @@ function useCombinedSubmissions() { /** * Iterate over Formio Application form submissions, matching them with * submissions returned from the BAP, so we can build up each rebate object - * with the Application form submission data, initial Payment Request form - * and Close-out Form submission data structure (both to be updated/replaced) + * with the Application form submission data and initialize Payment Request + * form and Close-out Form submission data structure (both to be updated). */ for (const formioSubmission of formioApplicationSubmissions.data) { const bapMatch = bapApplicationSubmissions.data.find((bapSubmission) => { @@ -222,7 +223,8 @@ function useCombinedSubmissions() { /** * Iterate over Formio Payment Request form submissions, matching them with * submissions returned from the BAP, so we can set the Payment Request form - * submission data. + * submission data (NOTE: `hidden_bap_rebate_id` is injected upon creation of + * a brand new Payment Request form submission, so it will always be there). */ for (const formioSubmission of formioPaymentRequestSubmissions.data) { const bapMatch = bapPaymentRequestSubmissions.data.find((bapSubmission) => { @@ -232,6 +234,8 @@ function useCombinedSubmissions() { ); }); + // TODO: update this once the BAP team sets up the ETL process for ingesting + // Payment Request form submissions from forms.gov const comboKey = bapMatch?.UEI_EFTI_Combo_Key__c || null; const rebateId = bapMatch?.Parent_Rebate_ID__c || null; const reviewItemId = bapMatch?.CSB_Review_Item_ID__c || null; @@ -248,6 +252,34 @@ function useCombinedSubmissions() { return submissions; } +/** + * Custom hook that sorts submissions by most recient formio modified date, + * regardless of form (Application, Payment Request, or Close-Out). + **/ +function useSortedSubmissions(submissions: { [rebateId: string]: Rebate }) { + return Object.entries(submissions) + .map(([rebateId, rebate]) => ({ rebateId, ...rebate })) + .sort((rebateA, rebateB) => { + const mostRecientRebateAModified = [ + Date.parse(rebateA.application.formio.modified), + Date.parse(rebateA.paymentRequest.formio?.modified || ""), + Date.parse(rebateA.closeOut.formio?.modified || ""), + ].reduce((previous, current) => { + return current > previous ? current : previous; + }); + + const mostRecientRebateBModified = [ + Date.parse(rebateB.application.formio.modified), + Date.parse(rebateB.paymentRequest.formio?.modified || ""), + Date.parse(rebateB.closeOut.formio?.modified || ""), + ].reduce((previous, current) => { + return current > previous ? current : previous; + }); + + return mostRecientRebateBModified - mostRecientRebateAModified; + }); +} + function ApplicationSubmission({ rebate }: { rebate: Rebate }) { const navigate = useNavigate(); @@ -811,14 +843,15 @@ export function AllRebates() { useFetchedBapPaymentRequestSubmissions(); const submissions = useCombinedSubmissions(); + const sortedSubmissions = useSortedSubmissions(submissions); - // log combined 'submissions' object if 'debug' search parameter exists + // log combined 'sortedSubmissions' array if 'debug' search parameter exists useEffect(() => { - const submissionsAreSet = Boolean(Object.keys(submissions).length); + const submissionsAreSet = sortedSubmissions.length > 0; if (searchParams.has("debug") && submissionsAreSet) { - console.log(submissions); + console.log(sortedSubmissions); } - }, [searchParams, submissions]); + }, [searchParams, sortedSubmissions]); if ( csbData.status !== "success" || @@ -859,7 +892,7 @@ export function AllRebates() { return ( <> - {Object.keys(submissions).length === 0 ? ( + {sortedSubmissions.length === 0 ? (
@@ -945,24 +978,22 @@ export function AllRebates() {
- {Object.entries(submissions).map( - ([rebateId, rebate], index) => ( - - - - - - {/* blank row after all rebates but the last one */} - {index !== Object.keys(submissions).length - 1 && ( - - - - )} - - ) - )} + {sortedSubmissions.map((rebate, index) => ( + + + + + + {/* blank row after all rebates but the last one */} + {index !== sortedSubmissions.length - 1 && ( + + + + )} + + ))}
{submissionData._id}{submissionData.data.applicantOrganizationName}{submissionData.data.last_updated_by}{submission._id}{submission.data.applicantOrganizationName}{submission.data.last_updated_by} - {new Date(submissionData.modified).toLocaleDateString()} + {new Date(submission.modified).toLocaleDateString()} {submissionData.state}{submission.state} From ddd943d822ae163109d5b1bd545717a9833b024b Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 28 Sep 2022 15:16:24 -0400 Subject: [PATCH 085/128] Remove 'last_updated_by' field from Payment Request form, and update allRebates to use the 'hidden_current_user_email' field in the table instead --- app/client/src/routes/allRebates.tsx | 5 +++-- app/server/app/routes/api.js | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index ec9dc32c..317c2916 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -618,7 +618,8 @@ function PaymentRequestSubmission({ rebate }: { rebate: Rebate }) { // return if a Payment Request submission has not been created for this rebate if (!paymentRequest.formio) return null; - const { last_updated_by, hidden_bap_rebate_id } = paymentRequest.formio.data; + const { hidden_current_user_email, hidden_bap_rebate_id } = + paymentRequest.formio.data; const date = new Date(paymentRequest.formio.modified).toLocaleDateString(); const time = new Date(paymentRequest.formio.modified).toLocaleTimeString(); @@ -768,7 +769,7 @@ function PaymentRequestSubmission({ rebate }: { rebate: Rebate }) {   - {last_updated_by} + {hidden_current_user_email}
{date}
-   -
+   +
From 2d9e09f50303370b1ddc9c50cc6cca96fdf7da5a Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Thu, 29 Sep 2022 15:20:59 -0400 Subject: [PATCH 088/128] Highlight table rows where application has been selected, but no payment request has been created yet, and update sorting to display those rows first in the table (while keeping previously set last modified formio sorting across all three forms) --- app/client/src/routes/allRebates.tsx | 29 +++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/app/client/src/routes/allRebates.tsx b/app/client/src/routes/allRebates.tsx index de410341..1f8de111 100644 --- a/app/client/src/routes/allRebates.tsx +++ b/app/client/src/routes/allRebates.tsx @@ -48,6 +48,9 @@ type Rebate = { }; }; +const defaultTableRowClassNames = "bg-gray-5"; +const highlightedTableRowClassNames = "bg-primary-lighter"; + /** Custom hook to fetch Application form submissions from Forms.gov */ function useFetchedFormioApplicationSubmissions() { const { samEntities } = useBapState(); @@ -276,7 +279,16 @@ function useSortedSubmissions(submissions: { [rebateId: string]: Rebate }) { return current > previous ? current : previous; }); - return mostRecientRebateBModified - mostRecientRebateAModified; + // Application has been selected, but a Payment Request submission has not yet been created + const rebateASelectedButNoPaymentRequest = + rebateA.application.bap?.rebateStatus === "Selected" && + !rebateA.paymentRequest.formio; + + // first sort by selected Applications that still need Payment Requests, + // then sort by most recient formio modified date + return rebateASelectedButNoPaymentRequest + ? -1 + : mostRecientRebateBModified - mostRecientRebateAModified; }); } @@ -289,7 +301,7 @@ function ApplicationSubmission({ rebate }: { rebate: Rebate }) { if (csbData.status !== "success") return null; const { enrollmentClosed } = csbData.data; - const { application } = rebate; + const { application, paymentRequest } = rebate; const applicationHasBeenSelected = application.bap?.rebateStatus === "Selected"; @@ -344,7 +356,14 @@ function ApplicationSubmission({ rebate }: { rebate: Rebate }) { * to first save the form for the fields to be displayed. */ return ( - + {applicationNeedsEdits ? ( @@ -75,7 +77,7 @@ export function ConfirmationDialog() { - {submission._id} - {submission.data.applicantOrganizationName as string} - {submission.data.last_updated_by as string} + + {formType === "application" ? ( + {submission._id} + ) : formType === "paymentRequest" ? ( + {submission.data.hidden_bap_rebate_id as string} + ) : ( +   + )} + + {formType === "application" ? ( + + {submission.data.applicantOrganizationName as string} + + ) : formType === "paymentRequest" ? ( + {submission.data.hidden_bap_org_name as string} + ) : ( +   + )} + + {formType === "application" ? ( + {submission.data.last_updated_by as string} + ) : formType === "paymentRequest" ? ( + {submission.data.hidden_current_user_email} + ) : ( +   + )} + {new Date(submission.modified).toLocaleDateString()} + {submission.state} + @@ -316,7 +313,15 @@ export function Helpdesk() { {new Date(submission.modified).toLocaleDateString()} - {submission.state} + + { + submission.state === "draft" + ? "Draft" + : submission.state === "submitted" + ? "Submitted" + : submission.state // fallback, not used + } + diff --git a/app/server/app/content/helpdesk-intro.md b/app/server/app/content/helpdesk-intro.md index 6cad73ea..c2b54776 100644 --- a/app/server/app/content/helpdesk-intro.md +++ b/app/server/app/content/helpdesk-intro.md @@ -1,3 +1,3 @@ -## Change a Submitted Form’s State +## Change a Submitted Form’s Formio Status -Please search for a form by ID below. If the submission exists, it will be displayed in a table below the search box. In the table, select the pencil icon to view an existing submission or select the update icon to change a submission’s state from _submitted_ back to _draft_. +Please search for a form by ID below (use _Application ID_ for Application form submissions or _Rebate ID_ for Payment Request form submissions). If the submission exists, it will be displayed in a table below the search box. In the table, view an existing submission or update it's Formio submission state from _Submitted_ back to _Draft_. From e4f947659bdb95b3fa6730d6dfac2498947bb355 Mon Sep 17 00:00:00 2001 From: Courtney Myers Date: Wed, 5 Oct 2022 13:51:25 -0400 Subject: [PATCH 108/128] Remove no longer used formId from helpdesk component --- app/client/src/routes/helpdesk.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/app/client/src/routes/helpdesk.tsx b/app/client/src/routes/helpdesk.tsx index 6812aead..b6c0e284 100644 --- a/app/client/src/routes/helpdesk.tsx +++ b/app/client/src/routes/helpdesk.tsx @@ -40,7 +40,6 @@ export function Helpdesk() { const [formType, setFormType] = useState("application"); const [searchId, setSearchId] = useState(""); - const [formId, setFormId] = useState(""); const [formDisplayed, setFormDisplayed] = useState(false); if ( @@ -141,26 +140,19 @@ export function Helpdesk() { role="search" onSubmit={(ev) => { ev.preventDefault(); - - setFormId(""); setFormDisplayed(false); - pageDispatch({ type: "FETCH_FORMIO_DATA_REQUEST" }); - getData( `${serverUrl}/help/formio-submission/${formType}/${searchId}` ) .then((res: FormioFetchedResponse) => { if (!res.submission) return; - - setFormId(res.submission._id); pageDispatch({ type: "FETCH_FORMIO_DATA_SUCCESS", payload: { data: res }, }); }) .catch((err) => { - setFormId(""); pageDispatch({ type: "FETCH_FORMIO_DATA_FAILURE" }); }); }} @@ -269,7 +261,7 @@ export function Helpdesk() {