Skip to content

Commit

Permalink
Merge pull request #396 from USEPA/feature/update-helpdesk-functionality
Browse files Browse the repository at this point in the history
Feature/update helpdesk functionality
  • Loading branch information
courtneymyers authored Mar 1, 2024
2 parents ed093ef + 236c1be commit f83863f
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 22 deletions.
93 changes: 74 additions & 19 deletions app/client/src/routes/helpdesk.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query";
import { useQueryClient, useQuery } from "@tanstack/react-query";
import { Form } from "@formio/react";
import clsx from "clsx";
import icon from "uswds/img/usa-icons-bg/search--white.svg";
Expand All @@ -22,12 +22,11 @@ import {
type FormioFRF2023Submission,
type BapSubmission,
getData,
postData,
useContentData,
useHelpdeskAccess,
submissionNeedsEdits,
} from "@/utilities";
import { Loading } from "@/components/loading";
import { Loading, LoadingButtonIcon } from "@/components/loading";
import { Message } from "@/components/message";
import { MarkdownContent } from "@/components/markdownContent";
import { TextWithTooltip } from "@/components/tooltip";
Expand Down Expand Up @@ -61,7 +60,7 @@ function formatTime(dateTimeString: string | null) {
return dateTimeString ? new Date(dateTimeString).toLocaleTimeString() : "";
}

function ResultTableRows(props: {
function ResultTableRow(props: {
lastSearchedText: string;
formType: FormType;
formio:
Expand All @@ -74,6 +73,31 @@ function ResultTableRows(props: {
const { lastSearchedText, formType, formio, bap } = props;
const { rebateYear } = useRebateYearState();

const queryClient = useQueryClient();

useEffect(() => {
queryClient.resetQueries({ queryKey: ["helpdesk/pdf"] });
}, [queryClient]);

const [downloadPending, setDownloadPending] = useState(false);

const url = `${serverUrl}/api/help/formio/pdf/${formio.form}/${formio._id}`;

const query = useQuery({
queryKey: ["helpdesk/pdf"],
queryFn: () => getData<string>(url),
onSuccess: (res) => {
setDownloadPending(false);
const link = document.createElement("a");
link.setAttribute("href", `data:application/pdf;base64,${res}`);
link.setAttribute("download", `${formio._id}.pdf`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
enabled: false,
});

const date = formatDate(formio.modified);
const time = formatTime(formio.modified);

Expand Down Expand Up @@ -101,6 +125,29 @@ function ResultTableRows(props: {
<td>
<span title={`${date} ${time}`}>{date}</span>
</td>

<td className={clsx("!tw-text-right")}>
<button
className="usa-button font-sans-2xs margin-right-0 padding-x-105 padding-y-1"
onClick={(_ev) => {
setDownloadPending(true);
query.refetch();
}}
>
<span className="display-flex flex-align-center">
<svg
className="usa-icon"
aria-hidden="true"
focusable="false"
role="img"
>
<use href={`${icons}#arrow_downward`} />
</svg>
<span className="margin-left-1">Download</span>
{downloadPending && <LoadingButtonIcon position="end" />}
</span>
</button>
</td>
</>
);
}
Expand All @@ -121,23 +168,18 @@ export function Helpdesk() {
const [formDisplayed, setFormDisplayed] = useState(false);

useEffect(() => {
queryClient.resetQueries({ queryKey: ["helpdesk"] });
queryClient.resetQueries({ queryKey: ["helpdesk/submission"] });
}, [queryClient]);

const url = `${serverUrl}/api/help/formio/${rebateYear}/${formType}/${searchText}`;
const url = `${serverUrl}/api/help/formio/submission/${rebateYear}/${formType}/${searchText}`;

const query = useQuery({
queryKey: ["helpdesk"],
queryKey: ["helpdesk/submission"],
queryFn: () => getData<ServerResponse>(url),
onSuccess: (_res) => setResultDisplayed(true),
enabled: false,
});

const mutation = useMutation({
mutationFn: () => postData<ServerResponse>(url, {}),
onSuccess: (data) => queryClient.setQueryData(["helpdesk"], data),
});

const { formSchema, formio, bap } = query.data ?? {};

if (helpdeskAccess === "pending") {
Expand Down Expand Up @@ -175,7 +217,7 @@ export function Helpdesk() {
onChange={(ev) => {
setRebateYear(ev.target.value as RebateYear);
setResultDisplayed(false);
queryClient.resetQueries({ queryKey: ["helpdesk"] });
queryClient.resetQueries({ queryKey: ["helpdesk/submission"] });
}}
defaultValue={rebateYear}
>
Expand All @@ -197,7 +239,9 @@ export function Helpdesk() {
onChange={(ev) => {
setFormType(ev.target.value as FormType);
setResultDisplayed(false);
queryClient.resetQueries({ queryKey: ["helpdesk"] });
queryClient.resetQueries({
queryKey: ["helpdesk/submission"],
});
}}
/>
<label
Expand All @@ -219,7 +263,9 @@ export function Helpdesk() {
onChange={(ev) => {
setFormType(ev.target.value as FormType);
setResultDisplayed(false);
queryClient.resetQueries({ queryKey: ["helpdesk"] });
queryClient.resetQueries({
queryKey: ["helpdesk/submission"],
});
}}
/>
<label
Expand All @@ -241,7 +287,9 @@ export function Helpdesk() {
onChange={(ev) => {
setFormType(ev.target.value as FormType);
setResultDisplayed(false);
queryClient.resetQueries({ queryKey: ["helpdesk"] });
queryClient.resetQueries({
queryKey: ["helpdesk/submission"],
});
}}
/>
<label
Expand Down Expand Up @@ -290,9 +338,9 @@ export function Helpdesk() {
</div>
</div>

{query.isFetching || mutation.isLoading ? (
{query.isFetching ? (
<Loading />
) : query.isError || mutation.isError ? (
) : query.isError ? (
<Message type="error" text={messages.helpdeskSubmissionSearchError} />
) : query.isSuccess && !!formio && !!bap && resultDisplayed ? (
<>
Expand Down Expand Up @@ -350,6 +398,13 @@ export function Helpdesk() {
tooltip="Last date this form was updated"
/>
</th>

<th scope="col" className={clsx("tw-text-right")}>
<TextWithTooltip
text="Download PDF"
tooltip="Download a PDF of this submission"
/>
</th>
</tr>
</thead>

Expand All @@ -374,7 +429,7 @@ export function Helpdesk() {
</button>
</th>

<ResultTableRows
<ResultTableRow
lastSearchedText={lastSearchedText}
formType={formType}
formio={formio}
Expand Down
3 changes: 2 additions & 1 deletion app/client/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ export type FormType = "frf" | "prf" | "crf";

type FormioSubmission = {
[field: string]: unknown;
_id: string; // MongoDB ObjectId string
_id: string; // MongoDB ObjectId string – submission ID
form: string; // MongoDB ObjectId string – form ID
state: "submitted" | "draft";
modified: string; // ISO 8601 date time string
metadata: {
Expand Down
1 change: 1 addition & 0 deletions app/server/app/config/formio.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ const formioCSBMetadata = {

module.exports = {
axiosFormio,
formioProjectUrl,
formUrl,
submissionPeriodOpen,
formioCSBMetadata,
Expand Down
68 changes: 66 additions & 2 deletions app/server/app/routes/help.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const express = require("express");
const ObjectId = require("mongodb").ObjectId;
// ---
const { axiosFormio, formUrl } = require("../config/formio");
const { axiosFormio, formioProjectUrl, formUrl } = require("../config/formio");
const { ensureAuthenticated, ensureHelpdesk } = require("../middleware");
const { getBapFormSubmissionData } = require("../utilities/bap");

Expand All @@ -12,7 +12,7 @@ router.use(ensureAuthenticated);
router.use(ensureHelpdesk);

// --- get an existing form's submission data from Formio
router.get("/formio/:rebateYear/:formType/:id", (req, res) => {
router.get("/formio/submission/:rebateYear/:formType/:id", (req, res) => {
const { rebateYear, formType, id } = req.params;

const rebateId = id.length === 6 ? id : null;
Expand Down Expand Up @@ -94,4 +94,68 @@ router.get("/formio/:rebateYear/:formType/:id", (req, res) => {
});
});

// --- get a PDF of an existing form's submission from Formio
router.get("/formio/pdf/:formId/:mongoId", (req, res) => {
const { formId, mongoId } = req.params;

/** NOTE: verifyMongoObjectId */
if (!ObjectId.isValid(formId)) {
const errorStatus = 400;
const errorMessage = `MongoDB ObjectId validation error for: '${formId}'.`;
return res.status(errorStatus).json({ message: errorMessage });
}

/** NOTE: verifyMongoObjectId */
if (!ObjectId.isValid(mongoId)) {
const errorStatus = 400;
const errorMessage = `MongoDB ObjectId validation error for: '${mongoId}'.`;
return res.status(errorStatus).json({ message: errorMessage });
}

axiosFormio(req)
.get(formioProjectUrl)
.then((axiosRes) => axiosRes.data)
.then((project) => {
const headers = {
"x-allow": `GET:/project/${project._id}/form/${formId}/submission/${mongoId}/download`,
"x-expire": 3600,
};

axiosFormio(req)
.get(`${formioProjectUrl}/token`, { headers })
.then((axiosRes) => axiosRes.data)
.then((json) => {
const url = `${formioProjectUrl}/form/${formId}/submission/${mongoId}/download?token=${json.key}`;

axiosFormio(req)
.get(url, { responseType: "arraybuffer" })
.then((axiosRes) => axiosRes.data)
.then((fileData) => {
const base64String = Buffer.from(fileData).toString("base64");
res.attachment(`${mongoId}.pdf`);
res.type("application/pdf");
res.send(base64String);
})
.catch((error) => {
// NOTE: error is logged in axiosFormio response interceptor
const errorStatus = error.response?.status || 500;
const errorMessage = `Error getting Formio submission PDF.`;
return res.status(errorStatus).json({ message: errorMessage });
});
})
.catch((error) => {
// NOTE: error is logged in axiosFormio response interceptor
const errorStatus = error.response?.status || 500;
const errorMessage = `Error getting Formio download token.`;
return res.status(errorStatus).json({ message: errorMessage });
});
})
.catch((error) => {
// NOTE: error is logged in axiosFormio response interceptor
const errorStatus = error.response?.status || 500;
const errorMessage = `Error getting Formio project data.`;
return res.status(errorStatus).json({ message: errorMessage });
});
});

module.exports = router;

0 comments on commit f83863f

Please sign in to comment.