From 747cd1beb3af7f89bfcb6e7334212f199f9a4f86 Mon Sep 17 00:00:00 2001
From: Joseph Andersen <12385932+jpandersen87@users.noreply.github.com>
Date: Tue, 17 Dec 2024 17:52:25 -0600
Subject: [PATCH 01/19] Create Test Result Page (without "Download Results")
Fixes #16074 - first pass
---
frontend-react/src/AppRouter.tsx | 4 +-
.../AddCustomTestMessageForm.tsx | 51 +++++++
.../Admin/MessageTesting/CustomMessage.tsx | 64 ---------
.../Admin/MessageTesting/MessageTesting.tsx | 114 ---------------
.../MessageTesting/MessageTestingBody.tsx | 19 +++
.../MessageTesting/MessageTestingForm.tsx | 56 ++++++++
.../MessageTesting/MessageTestingFormBase.tsx | 133 ++++++++++++++++++
.../MessageTestingResult.fixtures.ts | 45 ++++++
.../MessageTesting/MessageTestingResult.tsx | 107 ++++++++++++++
.../Admin/MessageTesting/RadioField.tsx | 57 --------
.../Admin/MessageTesting/TestMessageLabel.tsx | 22 +++
.../Admin/MessageTesting/language.json | 8 ++
.../src/config/endpoints/reports.ts | 35 +++++
.../src/config/endpoints/settings.ts | 18 ---
.../{ => UseTestMessages}/UseTestMessages.ts | 10 +-
.../AdminReportTestingPage.tsx | 50 +++++++
.../src/utils/OpenAsBlob/OpenAsBlob.test.ts | 31 ++++
.../src/utils/OpenAsBlob/OpenAsBlob.ts | 21 +++
18 files changed, 585 insertions(+), 260 deletions(-)
create mode 100644 frontend-react/src/components/Admin/MessageTesting/AddCustomTestMessageForm.tsx
delete mode 100644 frontend-react/src/components/Admin/MessageTesting/CustomMessage.tsx
delete mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTesting.tsx
create mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingBody.tsx
create mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
create mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingFormBase.tsx
create mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.fixtures.ts
create mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
delete mode 100644 frontend-react/src/components/Admin/MessageTesting/RadioField.tsx
create mode 100644 frontend-react/src/components/Admin/MessageTesting/TestMessageLabel.tsx
create mode 100644 frontend-react/src/components/Admin/MessageTesting/language.json
create mode 100644 frontend-react/src/config/endpoints/reports.ts
rename frontend-react/src/hooks/api/messages/{ => UseTestMessages}/UseTestMessages.ts (81%)
create mode 100644 frontend-react/src/pages/admin/AdminReportTestingPage/AdminReportTestingPage.tsx
create mode 100644 frontend-react/src/utils/OpenAsBlob/OpenAsBlob.test.ts
create mode 100644 frontend-react/src/utils/OpenAsBlob/OpenAsBlob.ts
diff --git a/frontend-react/src/AppRouter.tsx b/frontend-react/src/AppRouter.tsx
index b2c78dc8a94..73a8d0e1116 100644
--- a/frontend-react/src/AppRouter.tsx
+++ b/frontend-react/src/AppRouter.tsx
@@ -7,7 +7,7 @@ import { SenderType } from "./utils/DataDashboardUtils";
import { lazyRouteMarkdown } from "./utils/LazyRouteMarkdown";
import { PERMISSIONS } from "./utils/UsefulTypes";
-const ReportTestingPage = lazy(() => import("./components/Admin/MessageTesting/MessageTesting"));
+const AdminReportTestingPage = lazy(() => import("./pages/admin/AdminReportTestingPage/AdminReportTestingPage"));
/* Content Pages */
const Home = lazy(lazyRouteMarkdown(() => import("./content/home/index.mdx")));
const About = lazy(lazyRouteMarkdown(() => import("./content/about/index.mdx")));
@@ -440,7 +440,7 @@ export const appRoutes: RouteObject[] = [
},
{
path: "orgreceiversettings/org/:orgname/receiver/:receivername/action/:action/message-testing",
- element: ,
+ element: ,
},
{
path: "orgsendersettings/org/:orgname/sender/:sendername/action/:action",
diff --git a/frontend-react/src/components/Admin/MessageTesting/AddCustomTestMessageForm.tsx b/frontend-react/src/components/Admin/MessageTesting/AddCustomTestMessageForm.tsx
new file mode 100644
index 00000000000..dcca397171e
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/AddCustomTestMessageForm.tsx
@@ -0,0 +1,51 @@
+import { Button, Textarea } from "@trussworks/react-uswds";
+import { type ComponentProps, type FormEventHandler, useCallback, useState } from "react";
+
+export interface AddCustomMessageFormProps extends ComponentProps<"form"> {
+ onCancel: () => void;
+}
+
+export interface AddCustomMessageFormValues {
+ customMessageTestBody: string;
+}
+
+const AddCustomTestMessageFormProps = ({ onCancel, onChange, ...props }: AddCustomMessageFormProps) => {
+ const [isSubmitEnabled, setIsSubmitEnabled] = useState(false);
+
+ const handleFormChange = useCallback>(
+ (e) => {
+ setIsSubmitEnabled(e.currentTarget.checkValidity());
+ onChange?.(e);
+ },
+ [onChange],
+ );
+
+ return (
+
+ );
+};
+
+export default AddCustomTestMessageFormProps;
diff --git a/frontend-react/src/components/Admin/MessageTesting/CustomMessage.tsx b/frontend-react/src/components/Admin/MessageTesting/CustomMessage.tsx
deleted file mode 100644
index 699b76abbdf..00000000000
--- a/frontend-react/src/components/Admin/MessageTesting/CustomMessage.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { Button, Textarea } from "@trussworks/react-uswds";
-import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";
-import { RSMessage } from "../../../config/endpoints/settings";
-
-export const CustomMessage = ({
- customMessageNumber,
- currentTestMessages,
- setCustomMessageNumber,
- setCurrentTestMessages,
- setOpenCustomMessage,
-}: {
- customMessageNumber: number;
- currentTestMessages: { fileName: string; reportBody: string }[];
- setCustomMessageNumber: (value: number) => void;
- setCurrentTestMessages: Dispatch>;
- setOpenCustomMessage: (value: boolean) => void;
-}) => {
- const [text, setText] = useState("");
- const handleTextareaChange = (event: ChangeEvent) => {
- setText(event.target.value);
- };
- const handleAddCustomMessage = () => {
- const dateCreated = new Date();
- setCurrentTestMessages([
- ...currentTestMessages,
- {
- dateCreated: dateCreated.toString(),
- fileName: `Custom message ${customMessageNumber}`,
- reportBody: text,
- },
- ]);
- setCustomMessageNumber(customMessageNumber + 1);
- setText("");
- setOpenCustomMessage(false);
- };
-
- return (
-
-
Enter custom message
-
Custom messages do not save to the bank after you log out.
-
-
- {
- setOpenCustomMessage(false);
- }}
- >
- Cancel
-
-
- Add
-
-
-
- );
-};
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTesting.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTesting.tsx
deleted file mode 100644
index 92bfda83ce7..00000000000
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTesting.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import { Button, GridContainer } from "@trussworks/react-uswds";
-import { ChangeEvent, useState } from "react";
-import { Helmet } from "react-helmet-async";
-import { useParams } from "react-router";
-import { CustomMessage } from "./CustomMessage";
-import { RadioField } from "./RadioField";
-import useTestMessages from "../../../hooks/api/messages/UseTestMessages";
-import { FeatureName } from "../../../utils/FeatureName";
-import AdminFetchAlert from "../../alerts/AdminFetchAlert";
-import Crumbs, { CrumbsProps } from "../../Crumbs";
-import Spinner from "../../Spinner";
-import Title from "../../Title";
-import { AdminFormWrapper } from "../AdminFormWrapper";
-import { EditReceiverSettingsParams } from "../EditReceiverSettings";
-
-function ReportTesting() {
- const { orgname, receivername } = useParams();
- const { testMessages, isDisabled, isLoading } = useTestMessages();
- const [selectedOption, setSelectedOption] = useState(null);
- const [currentTestMessages, setCurrentTestMessages] = useState(testMessages);
- const [openCustomMessage, setOpenCustomMessage] = useState(false);
- const [customMessageNumber, setCustomMessageNumber] = useState(1);
- const crumbProps: CrumbsProps = {
- crumbList: [
- {
- label: FeatureName.RECEIVER_SETTINGS,
- path: `/admin/orgreceiversettings/org/${orgname}/receiver/${receivername}/action/edit`,
- },
- { label: FeatureName.MESSAGE_TESTING },
- ],
- };
-
- if (isDisabled) {
- return ;
- }
- if (isLoading || !currentTestMessages) return ;
-
- const handleSelect = (event: ChangeEvent) => {
- setSelectedOption(event.target.value);
- };
-
- const handleAddCustomMessage = () => {
- setSelectedOption(null);
- setOpenCustomMessage(true);
- };
-
- return (
- <>
-
- Message testing - ReportStream
-
-
-
-
-
-
-
-
- Org name: {orgname}
-
- Receiver name: {receivername}
-
-
- >
- }
- >
-
-
- Test a message from the message bank or by entering a custom message. You can view test results
- in this window while you are logged in. To save for later reference, you can open messages, test
- results and output messages in separate tabs.
-
-
- Test message bank
-
-
- {currentTestMessages?.map((item, index) => (
-
- ))}
- {openCustomMessage && (
-
- )}
-
-
-
- Test custom message
-
-
- Run test
-
-
-
-
-
- >
- );
-}
-
-export default ReportTesting;
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingBody.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingBody.tsx
new file mode 100644
index 00000000000..5d827479573
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingBody.tsx
@@ -0,0 +1,19 @@
+import { GridContainer } from "@trussworks/react-uswds";
+import MessageTestingForm from "./MessageTestingForm";
+
+const MessageTestingBody = () => {
+ return (
+
+
+ Test a message from the message bank or by entering a custom message. You can view test results in this
+ window while you are logged in. To save for later reference, you can open messages, test results and
+ output messages in separate tabs.
+
+
+ Test message bank
+
+
+ );
+};
+
+export default MessageTestingBody;
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
new file mode 100644
index 00000000000..47f1f1866aa
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
@@ -0,0 +1,56 @@
+import { FormEventHandler, useCallback, useState } from "react";
+import MessageTestingFormBase, {
+ type MessageTestingFormBaseProps,
+ type MessageTestingFormValues,
+} from "./MessageTestingFormBase";
+import MessageTestingResult from "./MessageTestingResult";
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import { errorMessageResult, passMessageResult, warningMessageResult } from "./MessageTestingResult.fixtures";
+import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
+import useTestMessages from "../../../hooks/api/messages/UseTestMessages/UseTestMessages";
+import AdminFetchAlert from "../../alerts/AdminFetchAlert";
+
+export interface MessageTestingFormProps extends Omit {}
+
+export interface RSSubmittedMessage extends Omit {
+ dateCreated: Date;
+}
+
+const fakeResultData = warningMessageResult;
+
+/**
+ * Data fetching wrapper for {@link MessageTestingFormBase}
+ * @see {@link MessageTestingFormBase}
+ */
+const MessageTestingForm = () => {
+ const { data, isDisabled } = useTestMessages();
+ // TODO: Replace with submission hook
+ const [resultData, setResultData] = useState(null);
+ const [submittedMessage, setSubmittedMessage] = useState(null);
+
+ const handleSubmit = useCallback>((e) => {
+ const formData = Object.fromEntries(
+ new FormData(e.currentTarget).entries(),
+ ) as unknown as MessageTestingFormValues;
+
+ // TODO: Remove fake result data usage, and Submit formData.testMessageBody to server
+ setSubmittedMessage({
+ fileName: formData.testMessage,
+ reportBody: formData.testMessageBody,
+ dateCreated: new Date(),
+ });
+ setResultData(fakeResultData);
+ }, []);
+
+ if (isDisabled) {
+ return ;
+ }
+
+ if (submittedMessage && resultData) {
+ return ;
+ }
+
+ return ;
+};
+
+export default MessageTestingForm;
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingFormBase.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingFormBase.tsx
new file mode 100644
index 00000000000..ed6c7c7d9ad
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingFormBase.tsx
@@ -0,0 +1,133 @@
+import { Button, Radio } from "@trussworks/react-uswds";
+import { type ComponentPropsWithoutRef, type FormEventHandler, useCallback, useMemo, useRef, useState } from "react";
+import AddCustomMessageForm, { type AddCustomMessageFormValues } from "./AddCustomTestMessageForm";
+import TestMessageLabel from "./TestMessageLabel";
+import type { RSMessage } from "../../../config/endpoints/reports";
+
+export interface MessageTestingFormBaseProps extends ComponentPropsWithoutRef<"form"> {
+ id: string;
+ testMessages: RSMessage[];
+}
+
+export interface MessageTestingFormValuesInternal {
+ testMessage?: string;
+ testMessageBody?: string;
+}
+
+export interface MessageTestingFormValues {
+ testMessage: string;
+ testMessageBody: string;
+}
+
+/**
+ * AKA Report Testing
+ * Form holds selected test message name and body (hidden).
+ */
+const MessageTestingFormBase = ({ testMessages, onChange, onSubmit, id, ...props }: MessageTestingFormBaseProps) => {
+ const [customTestMessages, setCustomTestMessages] = useState([]);
+ const allTestMessages = useMemo(() => [...testMessages, ...customTestMessages], [customTestMessages, testMessages]);
+
+ const inputRef = useRef(null);
+
+ const [isCustomMessageFormOpen, setIsCustomMessageFormOpen] = useState(false);
+ const [isSubmitEnabled, setIsSubmitEnabled] = useState(false);
+
+ const handleOpenCustomMessageForm = useCallback(() => {
+ setIsCustomMessageFormOpen(true);
+ }, [setIsCustomMessageFormOpen]);
+
+ const handleAddCustomMessageCancel = useCallback(() => {
+ setIsCustomMessageFormOpen(false);
+ }, [setIsCustomMessageFormOpen]);
+
+ const handleChange = useCallback>(
+ (e) => {
+ setIsSubmitEnabled(e.currentTarget.checkValidity());
+ onChange?.(e);
+ },
+ [onChange],
+ );
+
+ /**
+ * Insert selected message body into hidden field so that parent submit handler has complete form
+ */
+ const handleSubmit = useCallback>(
+ (e) => {
+ e.preventDefault();
+
+ const formData = Object.fromEntries(
+ new FormData(e.currentTarget).entries(),
+ ) as MessageTestingFormValuesInternal;
+ const testMessage = allTestMessages.find((m) => m.fileName === formData.testMessage);
+
+ if (testMessage == null) throw new Error("Invalid message");
+ if (inputRef.current == null) throw new Error("Input ref missing");
+
+ inputRef.current.value = testMessage.reportBody;
+ onSubmit?.(e);
+ },
+ [allTestMessages, onSubmit],
+ );
+
+ const handleAddCustomMessageSubmit = useCallback>(
+ (e) => {
+ e.preventDefault();
+
+ const formData = Object.fromEntries(
+ new FormData(e.currentTarget).entries(),
+ ) as unknown as AddCustomMessageFormValues;
+ const dateCreated = new Date();
+ const customTestMessage = {
+ dateCreated: dateCreated.toString(),
+ fileName: `Custom message ${customTestMessages.length + 1}`,
+ reportBody: formData.customMessageTestBody,
+ };
+ setCustomTestMessages((m) => [...m, customTestMessage]);
+ setIsCustomMessageFormOpen(false);
+ },
+ [customTestMessages],
+ );
+
+ return (
+ <>
+
+ {!allTestMessages.length && No test messages available
}
+ {!!allTestMessages.length && (
+
+
+ {allTestMessages?.map((item, index) => (
+ {item.fileName}}
+ key={item.fileName}
+ title={item.fileName}
+ required={true}
+ />
+ ))}
+
+
+
+ )}
+ {isCustomMessageFormOpen && (
+
+ )}
+
+
+
+ Test custom message
+
+
+ Run test
+
+
+ >
+ );
+};
+
+export default MessageTestingFormBase;
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.fixtures.ts b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.fixtures.ts
new file mode 100644
index 00000000000..36aa9b2c4f2
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.fixtures.ts
@@ -0,0 +1,45 @@
+import type { RSMessageResult } from "../../../config/endpoints/reports";
+
+export const passMessageResult: RSMessageResult = {
+ senderTransformPassed: true,
+ senderTransformErrors: [],
+ senderTransformWarnings: [],
+ enrichmentSchemaPassed: true,
+ enrichmentSchemaErrors: [],
+ enrichmentSchemaWarnings: [],
+ receiverTransformPassed: true,
+ receiverTransformErrors: [],
+ receiverTransformWarnings: [],
+ filterErrors: [],
+ filtersPassed: true,
+ message: "pass output message",
+};
+
+export const errorMessageResult: RSMessageResult = {
+ senderTransformPassed: false,
+ senderTransformErrors: ["senderTransformError 1", "senderTransformError 2"],
+ senderTransformWarnings: [],
+ enrichmentSchemaPassed: false,
+ enrichmentSchemaErrors: ["enrichmentSchemaError 1", "enrichmentSchemaError 2"],
+ enrichmentSchemaWarnings: [],
+ receiverTransformPassed: false,
+ receiverTransformErrors: ["receiverTransformError 1", "receiverTransformError 2"],
+ receiverTransformWarnings: [],
+ filterErrors: [],
+ filtersPassed: false,
+};
+
+export const warningMessageResult: RSMessageResult = {
+ senderTransformPassed: true,
+ senderTransformErrors: [],
+ senderTransformWarnings: ["senderTransformWarning 1", "senderTransformWarning 2"],
+ enrichmentSchemaPassed: true,
+ enrichmentSchemaErrors: [],
+ enrichmentSchemaWarnings: ["enrichmentSchemaWarning 1", "enrichmentSchemaWarning 2"],
+ receiverTransformPassed: true,
+ receiverTransformErrors: [],
+ receiverTransformWarnings: ["receiverTransformWarning 1", "receiverTransformWarning 2"],
+ filterErrors: [],
+ filtersPassed: true,
+ message: "warning output message",
+};
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
new file mode 100644
index 00000000000..65c5721ea06
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -0,0 +1,107 @@
+import { Accordion } from "@trussworks/react-uswds";
+import type { PropsWithChildren } from "react";
+import language from "./language.json";
+import type { RSSubmittedMessage } from "./MessageTestingForm";
+import type { RSMessageResult } from "../../../config/endpoints/reports";
+import Alert, { type AlertProps } from "../../../shared/Alert/Alert";
+import { USLinkButton } from "../../USLink";
+
+export interface MessageTestingResultProps extends PropsWithChildren {
+ message: RSSubmittedMessage;
+ result: RSMessageResult;
+}
+
+const errorFields: (keyof RSMessageResult)[] = [
+ "senderTransformErrors",
+ "enrichmentSchemaErrors",
+ "receiverTransformErrors",
+ "filterErrors",
+];
+const warningFields: (keyof RSMessageResult)[] = [
+ "senderTransformWarnings",
+ "enrichmentSchemaWarnings",
+ "receiverTransformWarnings",
+];
+
+const MessageTestingResult = ({ result, message, ...props }: MessageTestingResultProps) => {
+ const isPassed =
+ result.senderTransformPassed &&
+ result.filtersPassed &&
+ result.enrichmentSchemaPassed &&
+ result.receiverTransformPassed;
+ const isWarned =
+ !!result.senderTransformWarnings.length &&
+ !!result.enrichmentSchemaWarnings.length &&
+ !!result.receiverTransformWarnings;
+
+ const alertType: AlertProps["type"] = !isPassed ? "error" : isWarned ? "warning" : "success";
+ const alertHeading = language[`${alertType}AlertHeading`];
+ const alertBody = language[`${alertType}AlertBody`];
+
+ return (
+
+ Test results: {message.fileName}
+ {"<"} Select new message
+ Test run: {message.dateCreated.toISOString()}
+
+ {alertBody}
+
+ {errorFields.map((f) => {
+ const arr = result[f];
+ if (arr != null && !Array.isArray(arr)) throw new Error("Invalid result");
+ if (!arr?.length) return null;
+
+ return (
+
+ );
+ })}
+ {warningFields.map((f) => {
+ const arr = result[f];
+ if (arr != null && !Array.isArray(arr)) throw new Error("Invalid result");
+ if (!arr?.length) return null;
+
+ return (
+
+ );
+ })}
+ {result.message && (
+
+ )}
+
+
+ );
+};
+
+export default MessageTestingResult;
diff --git a/frontend-react/src/components/Admin/MessageTesting/RadioField.tsx b/frontend-react/src/components/Admin/MessageTesting/RadioField.tsx
deleted file mode 100644
index 1eb30b500c9..00000000000
--- a/frontend-react/src/components/Admin/MessageTesting/RadioField.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Button, Icon, Radio } from "@trussworks/react-uswds";
-import { ChangeEvent } from "react";
-
-export const RadioField = ({
- title,
- body,
- index,
- handleSelect,
- selectedOption,
-}: {
- title: string;
- body: string;
- index: number;
- handleSelect: (event: ChangeEvent) => void;
- selectedOption: string | null;
-}) => {
- const openTextInNewTab = () => {
- let formattedContent = body;
-
- // Check if the content is JSON and format it
- try {
- formattedContent = JSON.stringify(JSON.parse(body), null, 2);
- } catch {
- formattedContent = body;
- }
-
- const blob = new Blob([formattedContent], { type: "text/plain" });
-
- const url = URL.createObjectURL(blob);
-
- window.open(url, "_blank");
-
- // Revoke the URL to free up memory
- URL.revokeObjectURL(url);
- };
-
- return (
-
- {" "}
- {title}{" "}
-
- View message
-
-
- >
- }
- />
- );
-};
diff --git a/frontend-react/src/components/Admin/MessageTesting/TestMessageLabel.tsx b/frontend-react/src/components/Admin/MessageTesting/TestMessageLabel.tsx
new file mode 100644
index 00000000000..fc925daedce
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/TestMessageLabel.tsx
@@ -0,0 +1,22 @@
+import { Button, Icon } from "@trussworks/react-uswds";
+import { type PropsWithChildren, useCallback } from "react";
+import openAsBlob from "../../../utils/OpenAsBlob/OpenAsBlob";
+
+export interface TestMessageLabelProps extends PropsWithChildren {
+ data: string;
+}
+
+const TestMessageLabel = ({ data, children }: TestMessageLabelProps) => {
+ const handleClick = useCallback(() => openAsBlob(data), [data]);
+ return (
+ <>
+ {children}{" "}
+
+ View message
+
+
+ >
+ );
+};
+
+export default TestMessageLabel;
diff --git a/frontend-react/src/components/Admin/MessageTesting/language.json b/frontend-react/src/components/Admin/MessageTesting/language.json
new file mode 100644
index 00000000000..77b3484c6ac
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/language.json
@@ -0,0 +1,8 @@
+{
+ "successAlertHeading": "Test passed",
+ "successAlertBody": "No issues with filters or transforms.",
+ "warningAlertHeading": "Test passed with warnings",
+ "warningAlertBody": "Message passed, but there is at least one warning to view.",
+ "errorAlertHeading": "Test failed",
+ "errorAlertBody": "Message did not pass test. Check filters triggered or transform errors."
+}
diff --git a/frontend-react/src/config/endpoints/reports.ts b/frontend-react/src/config/endpoints/reports.ts
new file mode 100644
index 00000000000..59630c44450
--- /dev/null
+++ b/frontend-react/src/config/endpoints/reports.ts
@@ -0,0 +1,35 @@
+import { HTTPMethods, type RSApiEndpoints, RSEndpoint } from ".";
+
+export enum ReportsUrls {
+ TESTING = "/reports/testing",
+}
+
+export interface RSMessage {
+ dateCreated?: string;
+ fileName: string;
+ reportBody: string;
+}
+
+export interface RSMessageResult {
+ message?: string;
+ bundle?: string;
+ senderTransformPassed: boolean;
+ senderTransformErrors: string[];
+ senderTransformWarnings: string[];
+ enrichmentSchemaPassed: boolean;
+ enrichmentSchemaErrors: string[];
+ enrichmentSchemaWarnings: string[];
+ receiverTransformPassed: boolean;
+ receiverTransformErrors: string[];
+ receiverTransformWarnings: string[];
+ filterErrors: string[];
+ filtersPassed: boolean;
+}
+
+export const reportsEndpoints: RSApiEndpoints = {
+ testing: new RSEndpoint({
+ path: ReportsUrls.TESTING,
+ method: HTTPMethods.GET,
+ queryKey: "reportsTesting",
+ }),
+};
diff --git a/frontend-react/src/config/endpoints/settings.ts b/frontend-react/src/config/endpoints/settings.ts
index 696d1b177ab..eabc90f3af6 100644
--- a/frontend-react/src/config/endpoints/settings.ts
+++ b/frontend-react/src/config/endpoints/settings.ts
@@ -8,10 +8,6 @@ export enum ServicesUrls {
PUBLIC_KEYS = "/settings/organizations/:orgName/public-keys",
}
-export enum ReportsUrls {
- TESTING = "/reports/testing",
-}
-
export interface RSSettings {
version: number;
createdAt: string;
@@ -26,12 +22,6 @@ export interface RSService extends RSSettings {
customerStatus?: string;
}
-export interface RSMessage {
- dateCreated?: string;
- fileName: string;
- reportBody: string;
-}
-
export interface RSOrganizationSettings extends RSSettings {
description: string;
filters: string[];
@@ -116,11 +106,3 @@ export const servicesEndpoints: RSApiEndpoints = {
queryKey: "createPublicKey",
}),
};
-
-export const reportsEndpoints: RSApiEndpoints = {
- testing: new RSEndpoint({
- path: ReportsUrls.TESTING,
- method: HTTPMethods.GET,
- queryKey: "reportsTesting",
- }),
-};
diff --git a/frontend-react/src/hooks/api/messages/UseTestMessages.ts b/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
similarity index 81%
rename from frontend-react/src/hooks/api/messages/UseTestMessages.ts
rename to frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
index 973d1307fa8..1c587c4b3da 100644
--- a/frontend-react/src/hooks/api/messages/UseTestMessages.ts
+++ b/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
@@ -1,8 +1,8 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { useCallback } from "react";
-import { reportsEndpoints, RSMessage } from "../../../config/endpoints/settings";
-import useSessionContext from "../../../contexts/Session/useSessionContext";
-import { Organizations } from "../../UseAdminSafeOrganizationName/UseAdminSafeOrganizationName";
+import { reportsEndpoints, RSMessage } from "../../../../config/endpoints/reports";
+import useSessionContext from "../../../../contexts/Session/useSessionContext";
+import { Organizations } from "../../../UseAdminSafeOrganizationName/UseAdminSafeOrganizationName";
const { testing } = reportsEndpoints;
@@ -15,7 +15,7 @@ const { testing } = reportsEndpoints;
* This discrepancy exists between the backend naming convention ("Reports") and the frontend display ("Messages").
*
* @returns {object} The hook returns the following:
- * - `testMessages` (`RSMessage[] | undefined`): The fetched array of test messages.
+ * - `data` (`RSMessage[] | undefined`): The fetched array of test messages.
* - `isDisabled` (`boolean`): Indicates whether the feature is disabled for the current user.
* - Other properties from `useSuspenseQuery` (e.g., `isLoading`, `isError`, `error`).
*/
@@ -40,7 +40,7 @@ const useTestMessages = () => {
return {
...useSuspenseQueryResult,
- testMessages: data,
+ data: data ?? [],
isDisabled: !isAdmin,
};
};
diff --git a/frontend-react/src/pages/admin/AdminReportTestingPage/AdminReportTestingPage.tsx b/frontend-react/src/pages/admin/AdminReportTestingPage/AdminReportTestingPage.tsx
new file mode 100644
index 00000000000..e041b93e81b
--- /dev/null
+++ b/frontend-react/src/pages/admin/AdminReportTestingPage/AdminReportTestingPage.tsx
@@ -0,0 +1,50 @@
+import { GridContainer } from "@trussworks/react-uswds";
+import { Helmet } from "react-helmet-async";
+import { useParams } from "react-router";
+import { AdminFormWrapper } from "../../../components/Admin/AdminFormWrapper";
+import { EditReceiverSettingsParams } from "../../../components/Admin/EditReceiverSettings";
+import MessageTestingBody from "../../../components/Admin/MessageTesting/MessageTestingBody";
+import Crumbs, { CrumbsProps } from "../../../components/Crumbs";
+import Title from "../../../components/Title";
+import { FeatureName } from "../../../utils/FeatureName";
+
+const AdminReportTestingPage = () => {
+ const { orgname, receivername } = useParams();
+ const crumbProps: CrumbsProps = {
+ crumbList: [
+ {
+ label: FeatureName.RECEIVER_SETTINGS,
+ path: `/admin/orgreceiversettings/org/${orgname}/receiver/${receivername}/action/edit`,
+ },
+ { label: FeatureName.MESSAGE_TESTING },
+ ],
+ };
+ return (
+ <>
+
+ Message testing - ReportStream
+
+
+
+
+
+
+
+
+ Org name: {orgname}
+
+ Receiver name: {receivername}
+
+
+ >
+ }
+ >
+
+
+ >
+ );
+};
+
+export default AdminReportTestingPage;
diff --git a/frontend-react/src/utils/OpenAsBlob/OpenAsBlob.test.ts b/frontend-react/src/utils/OpenAsBlob/OpenAsBlob.test.ts
new file mode 100644
index 00000000000..62b8863aac3
--- /dev/null
+++ b/frontend-react/src/utils/OpenAsBlob/OpenAsBlob.test.ts
@@ -0,0 +1,31 @@
+import openAsBlob from "./OpenAsBlob";
+
+describe("OpenAsBlob", () => {
+ const objectURL = "fake-url";
+ const data = "test";
+ const blob = new Blob([data], { type: "text/plain" });
+
+ beforeEach(() => {
+ vi.stubGlobal(
+ "Blob",
+ vi.fn(() => blob),
+ );
+ vi.stubGlobal("URL", { ...URL, createObjectURL: vi.fn(() => objectURL), revokeObjectURL: vi.fn(() => void 0) });
+ vi.spyOn(window, "open").mockImplementation(() => null);
+ });
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ });
+
+ test("opens blob data url in new window", () => {
+ const mockBlob = vi.mocked(Blob);
+ const mockURL = vi.mocked(URL);
+ const mockWindow = vi.mocked(window);
+ openAsBlob(data);
+
+ expect(mockBlob).toBeCalledWith([data], { type: "text/plain" });
+ expect(mockURL.createObjectURL).toBeCalledWith(blob);
+ expect(mockURL.revokeObjectURL).toBeCalledWith(objectURL);
+ expect(mockWindow.open).toBeCalledWith(objectURL, "_blank");
+ });
+});
diff --git a/frontend-react/src/utils/OpenAsBlob/OpenAsBlob.ts b/frontend-react/src/utils/OpenAsBlob/OpenAsBlob.ts
new file mode 100644
index 00000000000..7f17b23071e
--- /dev/null
+++ b/frontend-react/src/utils/OpenAsBlob/OpenAsBlob.ts
@@ -0,0 +1,21 @@
+const openAsBlob = (data: string) => {
+ let formattedContent;
+
+ // Check if the content is JSON and format it
+ try {
+ formattedContent = JSON.stringify(JSON.parse(data), null, 2);
+ } catch {
+ formattedContent = data;
+ }
+
+ const blob = new Blob([formattedContent], { type: "text/plain" });
+
+ const url = URL.createObjectURL(blob);
+
+ window.open(url, "_blank");
+
+ // Revoke the URL to free up memory
+ URL.revokeObjectURL(url);
+};
+
+export default openAsBlob;
From e33f3ae13a7157d44e88a76bbfe09dc69d08396c Mon Sep 17 00:00:00 2001
From: etanb
Date: Thu, 26 Dec 2024 10:26:51 -0800
Subject: [PATCH 02/19] naming fixes
---
frontend-react/src/AppRouter.tsx | 4 ++--
.../MessageTesting/MessageTestingBody.tsx | 19 -------------------
.../AdminMessageTestingPage.tsx} | 17 +++++++++++++----
3 files changed, 15 insertions(+), 25 deletions(-)
delete mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingBody.tsx
rename frontend-react/src/pages/admin/{AdminReportTestingPage/AdminReportTestingPage.tsx => AdminMessageTestingPage/AdminMessageTestingPage.tsx} (69%)
diff --git a/frontend-react/src/AppRouter.tsx b/frontend-react/src/AppRouter.tsx
index 73a8d0e1116..b8653aab579 100644
--- a/frontend-react/src/AppRouter.tsx
+++ b/frontend-react/src/AppRouter.tsx
@@ -7,7 +7,7 @@ import { SenderType } from "./utils/DataDashboardUtils";
import { lazyRouteMarkdown } from "./utils/LazyRouteMarkdown";
import { PERMISSIONS } from "./utils/UsefulTypes";
-const AdminReportTestingPage = lazy(() => import("./pages/admin/AdminReportTestingPage/AdminReportTestingPage"));
+const AdminMessageTestingPage = lazy(() => import("./pages/admin/AdminMessageTestingPage/AdminMessageTestingPage"));
/* Content Pages */
const Home = lazy(lazyRouteMarkdown(() => import("./content/home/index.mdx")));
const About = lazy(lazyRouteMarkdown(() => import("./content/about/index.mdx")));
@@ -440,7 +440,7 @@ export const appRoutes: RouteObject[] = [
},
{
path: "orgreceiversettings/org/:orgname/receiver/:receivername/action/:action/message-testing",
- element: ,
+ element: ,
},
{
path: "orgsendersettings/org/:orgname/sender/:sendername/action/:action",
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingBody.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingBody.tsx
deleted file mode 100644
index 5d827479573..00000000000
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingBody.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { GridContainer } from "@trussworks/react-uswds";
-import MessageTestingForm from "./MessageTestingForm";
-
-const MessageTestingBody = () => {
- return (
-
-
- Test a message from the message bank or by entering a custom message. You can view test results in this
- window while you are logged in. To save for later reference, you can open messages, test results and
- output messages in separate tabs.
-
-
- Test message bank
-
-
- );
-};
-
-export default MessageTestingBody;
diff --git a/frontend-react/src/pages/admin/AdminReportTestingPage/AdminReportTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
similarity index 69%
rename from frontend-react/src/pages/admin/AdminReportTestingPage/AdminReportTestingPage.tsx
rename to frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index e041b93e81b..393fe528afa 100644
--- a/frontend-react/src/pages/admin/AdminReportTestingPage/AdminReportTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -3,12 +3,12 @@ import { Helmet } from "react-helmet-async";
import { useParams } from "react-router";
import { AdminFormWrapper } from "../../../components/Admin/AdminFormWrapper";
import { EditReceiverSettingsParams } from "../../../components/Admin/EditReceiverSettings";
-import MessageTestingBody from "../../../components/Admin/MessageTesting/MessageTestingBody";
+import MessageTestingForm from "../../../components/Admin/MessageTesting/MessageTestingForm";
import Crumbs, { CrumbsProps } from "../../../components/Crumbs";
import Title from "../../../components/Title";
import { FeatureName } from "../../../utils/FeatureName";
-const AdminReportTestingPage = () => {
+const AdminMessageTestingPage = () => {
const { orgname, receivername } = useParams();
const crumbProps: CrumbsProps = {
crumbList: [
@@ -41,10 +41,19 @@ const AdminReportTestingPage = () => {
>
}
>
-
+
+
+ Test a message from the message bank or by entering a custom message. You can view test results
+ in this window while you are logged in. To save for later reference, you can open messages, test
+ results and output messages in separate tabs.
+
+
+ Test message bank
+
+
>
);
};
-export default AdminReportTestingPage;
+export default AdminMessageTestingPage;
From b4d5ee6008cda1448d314f8358a0fcab3e213080 Mon Sep 17 00:00:00 2001
From: etanb
Date: Fri, 27 Dec 2024 09:26:30 -0800
Subject: [PATCH 03/19] merging form approaches
---
.../MessageTestingCustomMessage.tsx | 64 +++++++++
.../MessageTesting/MessageTestingForm.tsx | 134 +++++++++++++-----
.../MessageTesting/MessageTestingFormBase.tsx | 133 -----------------
.../MessageTestingRadioField.tsx | 38 +++++
.../AdminMessageTestingPage.tsx | 10 +-
5 files changed, 210 insertions(+), 169 deletions(-)
create mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
delete mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingFormBase.tsx
create mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingRadioField.tsx
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
new file mode 100644
index 00000000000..5380c936cd6
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
@@ -0,0 +1,64 @@
+import { Button, Textarea } from "@trussworks/react-uswds";
+import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";
+import { RSMessage } from "../../../config/endpoints/reports";
+
+export const MessageTestingCustomMessage = ({
+ customMessageNumber,
+ currentTestMessages,
+ setCustomMessageNumber,
+ setCurrentTestMessages,
+ setOpenCustomMessage,
+}: {
+ customMessageNumber: number;
+ currentTestMessages: { fileName: string; reportBody: string }[];
+ setCustomMessageNumber: (value: number) => void;
+ setCurrentTestMessages: Dispatch>;
+ setOpenCustomMessage: (value: boolean) => void;
+}) => {
+ const [text, setText] = useState("");
+ const handleTextareaChange = (event: ChangeEvent) => {
+ setText(event.target.value);
+ };
+ const handleAddCustomMessage = () => {
+ const dateCreated = new Date();
+ setCurrentTestMessages([
+ ...currentTestMessages,
+ {
+ dateCreated: dateCreated.toString(),
+ fileName: `Custom message ${customMessageNumber}`,
+ reportBody: text,
+ },
+ ]);
+ setCustomMessageNumber(customMessageNumber + 1);
+ setText("");
+ setOpenCustomMessage(false);
+ };
+
+ return (
+
+
Enter custom message
+
Custom messages do not save to the bank after you log out.
+
+
+ {
+ setOpenCustomMessage(false);
+ }}
+ >
+ Cancel
+
+
+ Add
+
+
+
+ );
+};
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
index 47f1f1866aa..fbba7fc731d 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
@@ -1,56 +1,120 @@
-import { FormEventHandler, useCallback, useState } from "react";
-import MessageTestingFormBase, {
- type MessageTestingFormBaseProps,
- type MessageTestingFormValues,
-} from "./MessageTestingFormBase";
-import MessageTestingResult from "./MessageTestingResult";
+import { Button } from "@trussworks/react-uswds";
+import { ChangeEvent, useState } from "react";
+import { MessageTestingCustomMessage } from "./MessageTestingCustomMessage";
+import { MessageTestingRadioField } from "./MessageTestingRadioField";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { errorMessageResult, passMessageResult, warningMessageResult } from "./MessageTestingResult.fixtures";
-import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
+import { warningMessageResult } from "./MessageTestingResult.fixtures";
+import type { RSMessage } from "../../../config/endpoints/reports";
import useTestMessages from "../../../hooks/api/messages/UseTestMessages/UseTestMessages";
import AdminFetchAlert from "../../alerts/AdminFetchAlert";
-export interface MessageTestingFormProps extends Omit {}
-
export interface RSSubmittedMessage extends Omit {
dateCreated: Date;
}
const fakeResultData = warningMessageResult;
-/**
- * Data fetching wrapper for {@link MessageTestingFormBase}
- * @see {@link MessageTestingFormBase}
- */
const MessageTestingForm = () => {
+ // // TODO: Replace with submission hook
+ // const [resultData, setResultData] = useState(null);
+ // const [submittedMessage, setSubmittedMessage] = useState(null);
+
+ // const handleSubmit = useCallback>((e) => {
+ // const formData = Object.fromEntries(
+ // new FormData(e.currentTarget).entries(),
+ // ) as unknown as MessageTestingFormValues;
+
+ // // TODO: Remove fake result data usage, and Submit formData.testMessageBody to server
+ // setSubmittedMessage({
+ // fileName: formData.testMessage,
+ // reportBody: formData.testMessageBody,
+ // dateCreated: new Date(),
+ // });
+ // setResultData(fakeResultData);
+ // }, []);
+
+ // if (submittedMessage && resultData) {
+ // return ;
+ // }
+
const { data, isDisabled } = useTestMessages();
- // TODO: Replace with submission hook
- const [resultData, setResultData] = useState(null);
- const [submittedMessage, setSubmittedMessage] = useState(null);
-
- const handleSubmit = useCallback>((e) => {
- const formData = Object.fromEntries(
- new FormData(e.currentTarget).entries(),
- ) as unknown as MessageTestingFormValues;
-
- // TODO: Remove fake result data usage, and Submit formData.testMessageBody to server
- setSubmittedMessage({
- fileName: formData.testMessage,
- reportBody: formData.testMessageBody,
- dateCreated: new Date(),
- });
- setResultData(fakeResultData);
- }, []);
+ const [selectedOption, setSelectedOption] = useState(null);
+ const [currentTestMessages, setCurrentTestMessages] = useState(data);
+ const [openCustomMessage, setOpenCustomMessage] = useState(false);
+ const [customMessageNumber, setCustomMessageNumber] = useState(1);
+
+ const handleSelect = (event: ChangeEvent) => {
+ setSelectedOption(event.target.value);
+ };
+
+ const handleAddCustomMessage = () => {
+ setSelectedOption(null);
+ setOpenCustomMessage(true);
+ };
if (isDisabled) {
return ;
}
- if (submittedMessage && resultData) {
- return ;
- }
+ /**
+ * Insert selected message body into hidden field so that parent submit handler has complete form
+ */
+ // const handleSubmit = useCallback>(
+ // (e) => {
+ // e.preventDefault();
+
+ // const formData = Object.fromEntries(
+ // new FormData(e.currentTarget).entries(),
+ // ) as MessageTestingFormValuesInternal;
+ // const testMessage = allTestMessages.find((m) => m.fileName === formData.testMessage);
+
+ // if (testMessage == null) throw new Error("Invalid message");
+ // if (inputRef.current == null) throw new Error("Input ref missing");
+
+ // inputRef.current.value = testMessage.reportBody;
+ // onSubmit?.(e);
+ // },
+ // [allTestMessages, onSubmit],
+ // );
- return ;
+ return (
+
+ {!currentTestMessages.length && No test messages available
}
+ {!!currentTestMessages.length && (
+
+
+ {currentTestMessages?.map((item, index) => (
+
+ ))}
+ {openCustomMessage && (
+
+ )}
+
+
+
+ Test custom message
+
+
+ Run test
+
+
+
+ )}
+
+ );
};
export default MessageTestingForm;
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingFormBase.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingFormBase.tsx
deleted file mode 100644
index ed6c7c7d9ad..00000000000
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingFormBase.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import { Button, Radio } from "@trussworks/react-uswds";
-import { type ComponentPropsWithoutRef, type FormEventHandler, useCallback, useMemo, useRef, useState } from "react";
-import AddCustomMessageForm, { type AddCustomMessageFormValues } from "./AddCustomTestMessageForm";
-import TestMessageLabel from "./TestMessageLabel";
-import type { RSMessage } from "../../../config/endpoints/reports";
-
-export interface MessageTestingFormBaseProps extends ComponentPropsWithoutRef<"form"> {
- id: string;
- testMessages: RSMessage[];
-}
-
-export interface MessageTestingFormValuesInternal {
- testMessage?: string;
- testMessageBody?: string;
-}
-
-export interface MessageTestingFormValues {
- testMessage: string;
- testMessageBody: string;
-}
-
-/**
- * AKA Report Testing
- * Form holds selected test message name and body (hidden).
- */
-const MessageTestingFormBase = ({ testMessages, onChange, onSubmit, id, ...props }: MessageTestingFormBaseProps) => {
- const [customTestMessages, setCustomTestMessages] = useState([]);
- const allTestMessages = useMemo(() => [...testMessages, ...customTestMessages], [customTestMessages, testMessages]);
-
- const inputRef = useRef(null);
-
- const [isCustomMessageFormOpen, setIsCustomMessageFormOpen] = useState(false);
- const [isSubmitEnabled, setIsSubmitEnabled] = useState(false);
-
- const handleOpenCustomMessageForm = useCallback(() => {
- setIsCustomMessageFormOpen(true);
- }, [setIsCustomMessageFormOpen]);
-
- const handleAddCustomMessageCancel = useCallback(() => {
- setIsCustomMessageFormOpen(false);
- }, [setIsCustomMessageFormOpen]);
-
- const handleChange = useCallback>(
- (e) => {
- setIsSubmitEnabled(e.currentTarget.checkValidity());
- onChange?.(e);
- },
- [onChange],
- );
-
- /**
- * Insert selected message body into hidden field so that parent submit handler has complete form
- */
- const handleSubmit = useCallback>(
- (e) => {
- e.preventDefault();
-
- const formData = Object.fromEntries(
- new FormData(e.currentTarget).entries(),
- ) as MessageTestingFormValuesInternal;
- const testMessage = allTestMessages.find((m) => m.fileName === formData.testMessage);
-
- if (testMessage == null) throw new Error("Invalid message");
- if (inputRef.current == null) throw new Error("Input ref missing");
-
- inputRef.current.value = testMessage.reportBody;
- onSubmit?.(e);
- },
- [allTestMessages, onSubmit],
- );
-
- const handleAddCustomMessageSubmit = useCallback>(
- (e) => {
- e.preventDefault();
-
- const formData = Object.fromEntries(
- new FormData(e.currentTarget).entries(),
- ) as unknown as AddCustomMessageFormValues;
- const dateCreated = new Date();
- const customTestMessage = {
- dateCreated: dateCreated.toString(),
- fileName: `Custom message ${customTestMessages.length + 1}`,
- reportBody: formData.customMessageTestBody,
- };
- setCustomTestMessages((m) => [...m, customTestMessage]);
- setIsCustomMessageFormOpen(false);
- },
- [customTestMessages],
- );
-
- return (
- <>
-
- {!allTestMessages.length && No test messages available
}
- {!!allTestMessages.length && (
-
-
- {allTestMessages?.map((item, index) => (
- {item.fileName}}
- key={item.fileName}
- title={item.fileName}
- required={true}
- />
- ))}
-
-
-
- )}
- {isCustomMessageFormOpen && (
-
- )}
-
-
-
- Test custom message
-
-
- Run test
-
-
- >
- );
-};
-
-export default MessageTestingFormBase;
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingRadioField.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingRadioField.tsx
new file mode 100644
index 00000000000..aead4a4f9da
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingRadioField.tsx
@@ -0,0 +1,38 @@
+import { Button, Icon, Radio } from "@trussworks/react-uswds";
+import { ChangeEvent } from "react";
+import openAsBlob from "../../../utils/OpenAsBlob/OpenAsBlob";
+
+export const MessageTestingRadioField = ({
+ title,
+ body,
+ index,
+ handleSelect,
+ selectedOption,
+}: {
+ title: string;
+ body: string;
+ index: number;
+ handleSelect: (event: ChangeEvent) => void;
+ selectedOption: string | null;
+}) => {
+ return (
+
+ {" "}
+ {title}{" "}
+ openAsBlob(body)}>
+ View message
+
+
+ >
+ }
+ />
+ );
+};
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index 393fe528afa..c084041bcb6 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -7,6 +7,7 @@ import MessageTestingForm from "../../../components/Admin/MessageTesting/Message
import Crumbs, { CrumbsProps } from "../../../components/Crumbs";
import Title from "../../../components/Title";
import { FeatureName } from "../../../utils/FeatureName";
+import { useState } from "react";
const AdminMessageTestingPage = () => {
const { orgname, receivername } = useParams();
@@ -19,6 +20,12 @@ const AdminMessageTestingPage = () => {
{ label: FeatureName.MESSAGE_TESTING },
],
};
+ enum MessageTestingSteps {
+ StepOne = "MessageTestSelection",
+ StepTwo = "MessageTestResults",
+ }
+ const [currentMessageTestStep, setCurrentMessageTestStep] = useState(MessageTestingSteps.StepOne);
+
return (
<>
@@ -49,7 +56,8 @@ const AdminMessageTestingPage = () => {
Test message bank
-
+ {currentMessageTestStep === MessageTestingSteps.StepOne && }
+ {currentMessageTestStep === MessageTestingSteps.StepTwo && }
>
From a640bd6a49d68e543d9d628af12f083ef5743d54 Mon Sep 17 00:00:00 2001
From: etanb
Date: Fri, 27 Dec 2024 15:07:31 -0800
Subject: [PATCH 04/19] move some component logic to parent cmp
---
.../MessageTesting/MessageTestingForm.tsx | 20 ++++++++++++++-----
.../AdminMessageTestingPage.tsx | 18 +++++++++++++++--
2 files changed, 31 insertions(+), 7 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
index fbba7fc731d..ed9a3327b80 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
@@ -5,16 +5,29 @@ import { MessageTestingRadioField } from "./MessageTestingRadioField";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { warningMessageResult } from "./MessageTestingResult.fixtures";
import type { RSMessage } from "../../../config/endpoints/reports";
-import useTestMessages from "../../../hooks/api/messages/UseTestMessages/UseTestMessages";
import AdminFetchAlert from "../../alerts/AdminFetchAlert";
export interface RSSubmittedMessage extends Omit {
dateCreated: Date;
}
+interface MessageTestingFormProps {
+ isDisabled: boolean;
+ currentTestMessages: RSMessage[];
+ setCurrentTestMessages: (messages: RSMessage[]) => void;
+ customMessageNumber: number;
+ setCustomMessageNumber: (number: number) => void;
+}
+
const fakeResultData = warningMessageResult;
-const MessageTestingForm = () => {
+const MessageTestingForm = ({
+ isDisabled,
+ currentTestMessages,
+ setCurrentTestMessages,
+ customMessageNumber,
+ setCustomMessageNumber,
+}: MessageTestingFormProps) => {
// // TODO: Replace with submission hook
// const [resultData, setResultData] = useState(null);
// const [submittedMessage, setSubmittedMessage] = useState(null);
@@ -37,11 +50,8 @@ const MessageTestingForm = () => {
// return ;
// }
- const { data, isDisabled } = useTestMessages();
const [selectedOption, setSelectedOption] = useState(null);
- const [currentTestMessages, setCurrentTestMessages] = useState(data);
const [openCustomMessage, setOpenCustomMessage] = useState(false);
- const [customMessageNumber, setCustomMessageNumber] = useState(1);
const handleSelect = (event: ChangeEvent) => {
setSelectedOption(event.target.value);
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index c084041bcb6..c003aa1f4fb 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -1,4 +1,5 @@
import { GridContainer } from "@trussworks/react-uswds";
+import { useState } from "react";
import { Helmet } from "react-helmet-async";
import { useParams } from "react-router";
import { AdminFormWrapper } from "../../../components/Admin/AdminFormWrapper";
@@ -6,8 +7,8 @@ import { EditReceiverSettingsParams } from "../../../components/Admin/EditReceiv
import MessageTestingForm from "../../../components/Admin/MessageTesting/MessageTestingForm";
import Crumbs, { CrumbsProps } from "../../../components/Crumbs";
import Title from "../../../components/Title";
+import useTestMessages from "../../../hooks/api/messages/UseTestMessages/UseTestMessages";
import { FeatureName } from "../../../utils/FeatureName";
-import { useState } from "react";
const AdminMessageTestingPage = () => {
const { orgname, receivername } = useParams();
@@ -24,7 +25,12 @@ const AdminMessageTestingPage = () => {
StepOne = "MessageTestSelection",
StepTwo = "MessageTestResults",
}
+
+ // Sets which step of the Message Testing process the user is at
const [currentMessageTestStep, setCurrentMessageTestStep] = useState(MessageTestingSteps.StepOne);
+ const { data, isDisabled } = useTestMessages();
+ const [currentTestMessages, setCurrentTestMessages] = useState(data);
+ const [customMessageNumber, setCustomMessageNumber] = useState(1);
return (
<>
@@ -56,7 +62,15 @@ const AdminMessageTestingPage = () => {
Test message bank
- {currentMessageTestStep === MessageTestingSteps.StepOne && }
+ {currentMessageTestStep === MessageTestingSteps.StepOne && (
+
+ )}
{currentMessageTestStep === MessageTestingSteps.StepTwo && }
From 785091313eb7dc2cc9fff868b61236df4837c77a Mon Sep 17 00:00:00 2001
From: etanb
Date: Fri, 27 Dec 2024 15:59:14 -0800
Subject: [PATCH 05/19] merge two approaches
---
.../MessageTesting/MessageTestingForm.tsx | 6 ++-
.../MessageTesting/MessageTestingResult.tsx | 52 +++++++++----------
.../AdminMessageTestingPage.tsx | 41 +++++++++++++--
3 files changed, 68 insertions(+), 31 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
index ed9a3327b80..49447d53054 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
@@ -17,6 +17,7 @@ interface MessageTestingFormProps {
setCurrentTestMessages: (messages: RSMessage[]) => void;
customMessageNumber: number;
setCustomMessageNumber: (number: number) => void;
+ handleSubmit: () => void;
}
const fakeResultData = warningMessageResult;
@@ -27,6 +28,7 @@ const MessageTestingForm = ({
setCurrentTestMessages,
customMessageNumber,
setCustomMessageNumber,
+ handleSubmit,
}: MessageTestingFormProps) => {
// // TODO: Replace with submission hook
// const [resultData, setResultData] = useState(null);
@@ -91,7 +93,7 @@ const MessageTestingForm = ({
{!currentTestMessages.length && No test messages available
}
{!!currentTestMessages.length && (
-
+
{currentTestMessages?.map((item, index) => (
Test custom message
-
+
Run test
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index 65c5721ea06..b15cca0e9b3 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -7,8 +7,8 @@ import Alert, { type AlertProps } from "../../../shared/Alert/Alert";
import { USLinkButton } from "../../USLink";
export interface MessageTestingResultProps extends PropsWithChildren {
- message: RSSubmittedMessage;
- result: RSMessageResult;
+ submittedMessage: RSSubmittedMessage;
+ resultData: RSMessageResult;
}
const errorFields: (keyof RSMessageResult)[] = [
@@ -23,16 +23,16 @@ const warningFields: (keyof RSMessageResult)[] = [
"receiverTransformWarnings",
];
-const MessageTestingResult = ({ result, message, ...props }: MessageTestingResultProps) => {
+const MessageTestingResult = ({ resultData, submittedMessage, ...props }: MessageTestingResultProps) => {
const isPassed =
- result.senderTransformPassed &&
- result.filtersPassed &&
- result.enrichmentSchemaPassed &&
- result.receiverTransformPassed;
+ resultData.senderTransformPassed &&
+ resultData.filtersPassed &&
+ resultData.enrichmentSchemaPassed &&
+ resultData.receiverTransformPassed;
const isWarned =
- !!result.senderTransformWarnings.length &&
- !!result.enrichmentSchemaWarnings.length &&
- !!result.receiverTransformWarnings;
+ !!resultData.senderTransformWarnings.length &&
+ !!resultData.enrichmentSchemaWarnings.length &&
+ !!resultData.receiverTransformWarnings;
const alertType: AlertProps["type"] = !isPassed ? "error" : isWarned ? "warning" : "success";
const alertHeading = language[`${alertType}AlertHeading`];
@@ -40,15 +40,15 @@ const MessageTestingResult = ({ result, message, ...props }: MessageTestingResul
return (
- Test results: {message.fileName}
- {"<"} Select new message
- Test run: {message.dateCreated.toISOString()}
+ Test results: {submittedMessage.fileName}
+ {"<"} Select new submittedMessage
+ Test run: {submittedMessage.dateCreated.toISOString()}
{alertBody}
{errorFields.map((f) => {
- const arr = result[f];
- if (arr != null && !Array.isArray(arr)) throw new Error("Invalid result");
+ const arr = resultData[f];
+ if (arr != null && !Array.isArray(arr)) throw new Error("Invalid resultData");
if (!arr?.length) return null;
return (
@@ -61,8 +61,8 @@ const MessageTestingResult = ({ result, message, ...props }: MessageTestingResul
);
})}
{warningFields.map((f) => {
- const arr = result[f];
- if (arr != null && !Array.isArray(arr)) throw new Error("Invalid result");
+ const arr = resultData[f];
+ if (arr != null && !Array.isArray(arr)) throw new Error("Invalid resultData");
if (!arr?.length) return null;
return (
@@ -74,29 +74,29 @@ const MessageTestingResult = ({ result, message, ...props }: MessageTestingResul
/>
);
})}
- {result.message && (
+ {resultData.submittedMessage && (
)}
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index c003aa1f4fb..cd88a57babd 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -1,15 +1,28 @@
import { GridContainer } from "@trussworks/react-uswds";
-import { useState } from "react";
+import { FormEventHandler, useCallback, useState } from "react";
import { Helmet } from "react-helmet-async";
import { useParams } from "react-router";
import { AdminFormWrapper } from "../../../components/Admin/AdminFormWrapper";
import { EditReceiverSettingsParams } from "../../../components/Admin/EditReceiverSettings";
-import MessageTestingForm from "../../../components/Admin/MessageTesting/MessageTestingForm";
+import MessageTestingForm, { RSSubmittedMessage } from "../../../components/Admin/MessageTesting/MessageTestingForm";
+import MessageTestingResult from "../../../components/Admin/MessageTesting/MessageTestingResult";
+import { warningMessageResult } from "../../../components/Admin/MessageTesting/MessageTestingResult.fixtures";
import Crumbs, { CrumbsProps } from "../../../components/Crumbs";
import Title from "../../../components/Title";
+import { RSMessageResult } from "../../../config/endpoints/reports";
import useTestMessages from "../../../hooks/api/messages/UseTestMessages/UseTestMessages";
import { FeatureName } from "../../../utils/FeatureName";
+export interface MessageTestingFormValuesInternal {
+ testMessage?: string;
+ testMessageBody?: string;
+}
+
+export interface MessageTestingFormValues {
+ testMessage: string;
+ testMessageBody: string;
+}
+
const AdminMessageTestingPage = () => {
const { orgname, receivername } = useParams();
const crumbProps: CrumbsProps = {
@@ -28,9 +41,28 @@ const AdminMessageTestingPage = () => {
// Sets which step of the Message Testing process the user is at
const [currentMessageTestStep, setCurrentMessageTestStep] = useState(MessageTestingSteps.StepOne);
+ // Sets data required for the MessageTestingForm
const { data, isDisabled } = useTestMessages();
const [currentTestMessages, setCurrentTestMessages] = useState(data);
const [customMessageNumber, setCustomMessageNumber] = useState(1);
+ const fakeResultData = warningMessageResult;
+ const handleSubmit = useCallback>((e) => {
+ const formData = Object.fromEntries(
+ new FormData(e.currentTarget).entries(),
+ ) as unknown as MessageTestingFormValues;
+
+ // TODO: Remove fake result data usage, and Submit formData.testMessageBody to server
+ setSubmittedMessage({
+ fileName: formData.testMessage,
+ reportBody: formData.testMessageBody,
+ dateCreated: new Date(),
+ });
+ setResultData(fakeResultData);
+ setCurrentMessageTestStep(MessageTestingSteps.StepTwo);
+ }, []);
+ // Sets data required for the MessageTestingResult
+ const [resultData, setResultData] = useState(null);
+ const [submittedMessage, setSubmittedMessage] = useState(null);
return (
<>
@@ -69,9 +101,12 @@ const AdminMessageTestingPage = () => {
setCurrentTestMessages={setCurrentTestMessages}
customMessageNumber={customMessageNumber}
setCustomMessageNumber={setCustomMessageNumber}
+ handleSubmit={handleSubmit}
/>
)}
- {currentMessageTestStep === MessageTestingSteps.StepTwo && }
+ {currentMessageTestStep === MessageTestingSteps.StepTwo && (
+
+ )}
>
From 5078a3dc122f38654808fc1673aca6476323b091 Mon Sep 17 00:00:00 2001
From: etanb
Date: Tue, 31 Dec 2024 15:25:47 -0800
Subject: [PATCH 06/19] creating test result hook + cleanup
---
.../MessageTestingCustomMessage.tsx | 4 +-
.../MessageTesting/MessageTestingForm.tsx | 77 +++------------
.../MessageTesting/MessageTestingResult.tsx | 18 ++--
.../src/config/endpoints/reports.ts | 10 +-
.../UseTestMessageResult.ts | 66 +++++++++++++
.../UseTestMessages/UseTestMessages.ts | 6 +-
.../AdminMessageTestingPage.tsx | 98 +++++++++++--------
7 files changed, 156 insertions(+), 123 deletions(-)
create mode 100644 frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
index 5380c936cd6..be61940e8b7 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
@@ -1,5 +1,5 @@
import { Button, Textarea } from "@trussworks/react-uswds";
-import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";
+import { ChangeEvent, useState } from "react";
import { RSMessage } from "../../../config/endpoints/reports";
export const MessageTestingCustomMessage = ({
@@ -12,7 +12,7 @@ export const MessageTestingCustomMessage = ({
customMessageNumber: number;
currentTestMessages: { fileName: string; reportBody: string }[];
setCustomMessageNumber: (value: number) => void;
- setCurrentTestMessages: Dispatch>;
+ setCurrentTestMessages: (messages: RSMessage[]) => void;
setOpenCustomMessage: (value: boolean) => void;
}) => {
const [text, setText] = useState("");
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
index 49447d53054..84ff80b4c45 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
@@ -1,62 +1,34 @@
import { Button } from "@trussworks/react-uswds";
-import { ChangeEvent, useState } from "react";
+import { useState } from "react";
import { MessageTestingCustomMessage } from "./MessageTestingCustomMessage";
import { MessageTestingRadioField } from "./MessageTestingRadioField";
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import { warningMessageResult } from "./MessageTestingResult.fixtures";
import type { RSMessage } from "../../../config/endpoints/reports";
-import AdminFetchAlert from "../../alerts/AdminFetchAlert";
export interface RSSubmittedMessage extends Omit {
dateCreated: Date;
}
interface MessageTestingFormProps {
- isDisabled: boolean;
currentTestMessages: RSMessage[];
setCurrentTestMessages: (messages: RSMessage[]) => void;
- customMessageNumber: number;
- setCustomMessageNumber: (number: number) => void;
- handleSubmit: () => void;
+ handleSubmit: (e: React.FormEvent) => void;
+ setSelectedOption: (message: RSMessage | null) => void;
+ selectedOption: RSMessage | null;
}
-const fakeResultData = warningMessageResult;
-
const MessageTestingForm = ({
- isDisabled,
currentTestMessages,
setCurrentTestMessages,
- customMessageNumber,
- setCustomMessageNumber,
handleSubmit,
+ setSelectedOption,
+ selectedOption,
}: MessageTestingFormProps) => {
- // // TODO: Replace with submission hook
- // const [resultData, setResultData] = useState(null);
- // const [submittedMessage, setSubmittedMessage] = useState(null);
-
- // const handleSubmit = useCallback>((e) => {
- // const formData = Object.fromEntries(
- // new FormData(e.currentTarget).entries(),
- // ) as unknown as MessageTestingFormValues;
-
- // // TODO: Remove fake result data usage, and Submit formData.testMessageBody to server
- // setSubmittedMessage({
- // fileName: formData.testMessage,
- // reportBody: formData.testMessageBody,
- // dateCreated: new Date(),
- // });
- // setResultData(fakeResultData);
- // }, []);
-
- // if (submittedMessage && resultData) {
- // return ;
- // }
-
- const [selectedOption, setSelectedOption] = useState(null);
const [openCustomMessage, setOpenCustomMessage] = useState(false);
- const handleSelect = (event: ChangeEvent) => {
- setSelectedOption(event.target.value);
+ const [customMessageNumber, setCustomMessageNumber] = useState(1);
+
+ const handleSelect = (item: RSMessage) => {
+ setSelectedOption(item);
};
const handleAddCustomMessage = () => {
@@ -64,31 +36,6 @@ const MessageTestingForm = ({
setOpenCustomMessage(true);
};
- if (isDisabled) {
- return ;
- }
-
- /**
- * Insert selected message body into hidden field so that parent submit handler has complete form
- */
- // const handleSubmit = useCallback>(
- // (e) => {
- // e.preventDefault();
-
- // const formData = Object.fromEntries(
- // new FormData(e.currentTarget).entries(),
- // ) as MessageTestingFormValuesInternal;
- // const testMessage = allTestMessages.find((m) => m.fileName === formData.testMessage);
-
- // if (testMessage == null) throw new Error("Invalid message");
- // if (inputRef.current == null) throw new Error("Input ref missing");
-
- // inputRef.current.value = testMessage.reportBody;
- // onSubmit?.(e);
- // },
- // [allTestMessages, onSubmit],
- // );
-
return (
{!currentTestMessages.length && No test messages available
}
@@ -101,8 +48,8 @@ const MessageTestingForm = ({
index={index}
title={item.fileName}
body={item.reportBody}
- handleSelect={handleSelect}
- selectedOption={selectedOption}
+ handleSelect={() => handleSelect(item)}
+ selectedOption={selectedOption?.reportBody ? selectedOption.reportBody : null}
/>
))}
{openCustomMessage && (
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index b15cca0e9b3..0d25afd9c34 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -1,14 +1,14 @@
import { Accordion } from "@trussworks/react-uswds";
import type { PropsWithChildren } from "react";
import language from "./language.json";
-import type { RSSubmittedMessage } from "./MessageTestingForm";
-import type { RSMessageResult } from "../../../config/endpoints/reports";
+import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
import Alert, { type AlertProps } from "../../../shared/Alert/Alert";
import { USLinkButton } from "../../USLink";
export interface MessageTestingResultProps extends PropsWithChildren {
- submittedMessage: RSSubmittedMessage;
- resultData: RSMessageResult;
+ submittedMessage: RSMessage | null;
+ isLoading: boolean;
+ resultData: any; // temporary typing
}
const errorFields: (keyof RSMessageResult)[] = [
@@ -40,9 +40,9 @@ const MessageTestingResult = ({ resultData, submittedMessage, ...props }: Messag
return (
- Test results: {submittedMessage.fileName}
+ Test results: {submittedMessage?.fileName}
{"<"} Select new submittedMessage
- Test run: {submittedMessage.dateCreated.toISOString()}
+ Test run: {submittedMessage?.dateCreated}
{alertBody}
@@ -74,13 +74,13 @@ const MessageTestingResult = ({ resultData, submittedMessage, ...props }: Messag
/>
);
})}
- {resultData.submittedMessage && (
+ {resultData.message && (
{
+ const { activeMembership, authorizedFetch } = useSessionContext();
+ const { receivername } = useParams();
+ const parsedName = activeMembership?.parsedName;
+ const isAdmin = Boolean(parsedName) && parsedName === Organizations.PRIMEADMINS;
+ const adminSafeOrgName = useAdminSafeOrganizationName(parsedName);
+
+ const [requestBody, setRequestBody] = useState(null);
+
+ const fetchData = useCallback(() => {
+ return authorizedFetch(
+ {
+ params: {
+ receiverName: receivername,
+ organizationName: adminSafeOrgName,
+ },
+ data: requestBody,
+ },
+ testResult,
+ );
+ }, [authorizedFetch, receivername, adminSafeOrgName, requestBody]);
+
+ // Use 'enabled' to conditionally run the query whenever `requestBody` changes
+ // and the user is an admin. If requestBody is empty or user isn't admin, no fetch is made.
+ const { data, isLoading, isError, error, status } = useQuery({
+ queryKey: [testResult.queryKey, activeMembership, receivername, adminSafeOrgName, requestBody],
+ queryFn: fetchData,
+ enabled: isAdmin && Boolean(requestBody),
+ });
+
+ return {
+ data: data ?? [],
+ setRequestBody,
+ isLoading,
+ isError,
+ error,
+ status,
+ isDisabled: !isAdmin,
+ };
+};
+
+export default useTestMessageResult;
diff --git a/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts b/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
index 1c587c4b3da..d91dc1c1b41 100644
--- a/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
+++ b/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
@@ -4,7 +4,7 @@ import { reportsEndpoints, RSMessage } from "../../../../config/endpoints/report
import useSessionContext from "../../../../contexts/Session/useSessionContext";
import { Organizations } from "../../../UseAdminSafeOrganizationName/UseAdminSafeOrganizationName";
-const { testing } = reportsEndpoints;
+const { test } = reportsEndpoints;
/**
* Custom hook to fetch and manage "Test Messages" data for the current session.
@@ -27,12 +27,12 @@ const useTestMessages = () => {
const memoizedDataFetch = useCallback(() => {
if (isAdmin) {
- return authorizedFetch({}, testing);
+ return authorizedFetch({}, test);
}
return null;
}, [isAdmin, authorizedFetch]);
const useSuspenseQueryResult = useSuspenseQuery({
- queryKey: [testing.queryKey, activeMembership],
+ queryKey: [test.queryKey, activeMembership],
queryFn: memoizedDataFetch,
});
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index cd88a57babd..664f38fc39d 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -1,15 +1,17 @@
import { GridContainer } from "@trussworks/react-uswds";
-import { FormEventHandler, useCallback, useState } from "react";
+import { useState } from "react";
import { Helmet } from "react-helmet-async";
import { useParams } from "react-router";
import { AdminFormWrapper } from "../../../components/Admin/AdminFormWrapper";
import { EditReceiverSettingsParams } from "../../../components/Admin/EditReceiverSettings";
-import MessageTestingForm, { RSSubmittedMessage } from "../../../components/Admin/MessageTesting/MessageTestingForm";
+import MessageTestingForm from "../../../components/Admin/MessageTesting/MessageTestingForm";
import MessageTestingResult from "../../../components/Admin/MessageTesting/MessageTestingResult";
-import { warningMessageResult } from "../../../components/Admin/MessageTesting/MessageTestingResult.fixtures";
import Crumbs, { CrumbsProps } from "../../../components/Crumbs";
+import Spinner from "../../../components/Spinner";
+import { StaticAlert, StaticAlertType } from "../../../components/StaticAlert";
import Title from "../../../components/Title";
-import { RSMessageResult } from "../../../config/endpoints/reports";
+import { RSMessage } from "../../../config/endpoints/reports";
+import useTestMessageResult from "../../../hooks/api/messages/UseTestMessageResult/UseTestMessageResult";
import useTestMessages from "../../../hooks/api/messages/UseTestMessages/UseTestMessages";
import { FeatureName } from "../../../utils/FeatureName";
@@ -23,6 +25,11 @@ export interface MessageTestingFormValues {
testMessageBody: string;
}
+enum MessageTestingSteps {
+ StepOne = "MessageTestSelection",
+ StepTwo = "MessageTestResults",
+}
+
const AdminMessageTestingPage = () => {
const { orgname, receivername } = useParams();
const crumbProps: CrumbsProps = {
@@ -34,35 +41,33 @@ const AdminMessageTestingPage = () => {
{ label: FeatureName.MESSAGE_TESTING },
],
};
- enum MessageTestingSteps {
- StepOne = "MessageTestSelection",
- StepTwo = "MessageTestResults",
- }
- // Sets which step of the Message Testing process the user is at
- const [currentMessageTestStep, setCurrentMessageTestStep] = useState(MessageTestingSteps.StepOne);
- // Sets data required for the MessageTestingForm
- const { data, isDisabled } = useTestMessages();
- const [currentTestMessages, setCurrentTestMessages] = useState(data);
- const [customMessageNumber, setCustomMessageNumber] = useState(1);
- const fakeResultData = warningMessageResult;
- const handleSubmit = useCallback>((e) => {
- const formData = Object.fromEntries(
- new FormData(e.currentTarget).entries(),
- ) as unknown as MessageTestingFormValues;
+ // Sets step of the Message Testing process laid out in the MessageTestingSteps enum
+ const [currentMessageTestStep, setCurrentMessageTestStep] = useState(
+ MessageTestingSteps.StepOne,
+ );
- // TODO: Remove fake result data usage, and Submit formData.testMessageBody to server
- setSubmittedMessage({
- fileName: formData.testMessage,
- reportBody: formData.testMessageBody,
- dateCreated: new Date(),
- });
- setResultData(fakeResultData);
+ // Sets data required for the MessageTestingForm
+ const { data: messageData, isDisabled } = useTestMessages();
+ const { setRequestBody, isLoading, data: testResultData } = useTestMessageResult();
+ const [selectedOption, setSelectedOption] = useState(null);
+ const [currentTestMessages, setCurrentTestMessages] = useState(messageData);
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ setRequestBody(selectedOption?.reportBody ? selectedOption.reportBody : null);
setCurrentMessageTestStep(MessageTestingSteps.StepTwo);
- }, []);
- // Sets data required for the MessageTestingResult
- const [resultData, setResultData] = useState(null);
- const [submittedMessage, setSubmittedMessage] = useState(null);
+ };
+
+ // If a user is _not_ a PrimeAdmin, block this feature
+ if (isDisabled) {
+ return (
+
+ );
+ }
return (
<>
@@ -94,18 +99,27 @@ const AdminMessageTestingPage = () => {
Test message bank
- {currentMessageTestStep === MessageTestingSteps.StepOne && (
-
- )}
- {currentMessageTestStep === MessageTestingSteps.StepTwo && (
-
+ {isLoading ? (
+
+ ) : (
+ <>
+ {currentMessageTestStep === MessageTestingSteps.StepOne && (
+
+ )}
+ {currentMessageTestStep === MessageTestingSteps.StepTwo && (
+
+ )}
+ >
)}
From 9b8709366a6ab889d5c2d2e48a851b9b59bce63e Mon Sep 17 00:00:00 2001
From: etanb
Date: Mon, 6 Jan 2025 15:12:14 -0800
Subject: [PATCH 07/19] general style test result page
---
.../MessageTesting/MessageTestingForm.tsx | 75 ++++++-----
.../MessageTesting/MessageTestingResult.tsx | 127 +++++++++++++-----
.../UseTestMessageResult.ts | 39 +++---
.../AdminMessageTestingPage.tsx | 5 +-
frontend-react/src/utils/misc.ts | 5 +
5 files changed, 161 insertions(+), 90 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
index 84ff80b4c45..f4ccfd52ea9 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
@@ -37,42 +37,45 @@ const MessageTestingForm = ({
};
return (
-
- {!currentTestMessages.length && No test messages available
}
- {!!currentTestMessages.length && (
-
-
- {currentTestMessages?.map((item, index) => (
- handleSelect(item)}
- selectedOption={selectedOption?.reportBody ? selectedOption.reportBody : null}
- />
- ))}
- {openCustomMessage && (
-
- )}
-
-
-
- Test custom message
-
-
- Run test
-
-
-
- )}
-
+ <>
+ Test message bank
+
+ {!currentTestMessages.length && No test messages available
}
+ {!!currentTestMessages.length && (
+
+
+ {currentTestMessages?.map((item, index) => (
+ handleSelect(item)}
+ selectedOption={selectedOption?.reportBody ? selectedOption.reportBody : null}
+ />
+ ))}
+ {openCustomMessage && (
+
+ )}
+
+
+
+ Test custom message
+
+
+ Run test
+
+
+
+ )}
+
+ >
);
};
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index 0d25afd9c34..b43e70e0f87 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -1,14 +1,15 @@
-import { Accordion } from "@trussworks/react-uswds";
+import { Accordion, Icon, Tag } from "@trussworks/react-uswds";
import type { PropsWithChildren } from "react";
import language from "./language.json";
import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
import Alert, { type AlertProps } from "../../../shared/Alert/Alert";
import { USLinkButton } from "../../USLink";
+import { camelCaseToTitle } from "../../../utils/misc";
export interface MessageTestingResultProps extends PropsWithChildren {
submittedMessage: RSMessage | null;
- isLoading: boolean;
resultData: any; // temporary typing
+ handleGoBack: () => void;
}
const errorFields: (keyof RSMessageResult)[] = [
@@ -23,7 +24,7 @@ const warningFields: (keyof RSMessageResult)[] = [
"receiverTransformWarnings",
];
-const MessageTestingResult = ({ resultData, submittedMessage, ...props }: MessageTestingResultProps) => {
+const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...props }: MessageTestingResultProps) => {
const isPassed =
resultData.senderTransformPassed &&
resultData.filtersPassed &&
@@ -37,69 +38,121 @@ const MessageTestingResult = ({ resultData, submittedMessage, ...props }: Messag
const alertType: AlertProps["type"] = !isPassed ? "error" : isWarned ? "warning" : "success";
const alertHeading = language[`${alertType}AlertHeading`];
const alertBody = language[`${alertType}AlertBody`];
-
+ const timeOptions = {
+ timeZone: "UTC",
+ month: "2-digit",
+ day: "2-digit",
+ year: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ hour12: true,
+ };
+ const dateCreated = new Date(submittedMessage?.dateCreated);
+ console.log("resultData = ", resultData);
return (
Test results: {submittedMessage?.fileName}
- {"<"} Select new submittedMessage
- Test run: {submittedMessage?.dateCreated}
-
- {alertBody}
-
+
+ {"<"} Select new message
+
+
+
+
Test run: {dateCreated.toLocaleString("en-US", timeOptions)} UTC
+
+
+
+
{errorFields.map((f) => {
const arr = resultData[f];
if (arr != null && !Array.isArray(arr)) throw new Error("Invalid resultData");
if (!arr?.length) return null;
return (
-
+
+
+
+ {camelCaseToTitle(f)}
+ {arr.length}
+ >
+ ),
+ content: arr.join("\n"),
+ expanded: false,
+ headingLevel: "h3",
+ id: `${f}-list`,
+ },
+ ]}
+ />
+
);
})}
+
{warningFields.map((f) => {
const arr = resultData[f];
if (arr != null && !Array.isArray(arr)) throw new Error("Invalid resultData");
if (!arr?.length) return null;
return (
+
+
+
+ {camelCaseToTitle(f)}
+ {arr.length}
+ >
+ ),
+ content: arr.join("\n"),
+ expanded: false,
+ headingLevel: "h3",
+ id: `${f}-list`,
+ },
+ ]}
+ />
+
+ );
+ })}
+ {resultData.message && (
+
- );
- })}
- {resultData.message && (
+
+ )}
+
);
};
diff --git a/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts b/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
index 4d37cda9611..85b4cec7b47 100644
--- a/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
+++ b/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
@@ -13,7 +13,7 @@ const { testResult } = reportsEndpoints;
* Custom hook to fetch validation for a single test report.
*
* @returns {object} The hook returns the following:
- * @property {RSMessageResult} data - The fetched test message results (empty array if none).
+ * @property {RSMessageResult | []} data - The fetched test message results (empty array if none).
* @property {function} setRequestBody - A setter for the request body used by the query.
* @property {boolean} isLoading - `true` while the query is fetching.
* @property {boolean} isError - `true` if the query encountered an error.
@@ -21,7 +21,6 @@ const { testResult } = reportsEndpoints;
* @property {"loading" | "error" | "success" | "idle"} status - The status of the query.
* @property {boolean} isDisabled - Indicates whether the feature is disabled (for non-admin users).
*/
-
const useTestMessageResult = () => {
const { activeMembership, authorizedFetch } = useSessionContext();
const { receivername } = useParams();
@@ -31,22 +30,34 @@ const useTestMessageResult = () => {
const [requestBody, setRequestBody] = useState(null);
- const fetchData = useCallback(() => {
- return authorizedFetch(
- {
- params: {
- receiverName: receivername,
- organizationName: adminSafeOrgName,
+ const fetchData = useCallback(async () => {
+ try {
+ // Attempt the fetch
+ const result = await authorizedFetch(
+ {
+ params: {
+ receiverName: receivername,
+ organizationName: adminSafeOrgName,
+ },
+ data: requestBody,
},
- data: requestBody,
- },
- testResult,
- );
+ testResult,
+ );
+
+ return result;
+ } catch (err) {
+ // Ensure we're rejecting with an actual Error object
+ if (err instanceof Error) {
+ return Promise.reject(err);
+ } else {
+ return Promise.reject(new Error(String(err)));
+ }
+ }
}, [authorizedFetch, receivername, adminSafeOrgName, requestBody]);
// Use 'enabled' to conditionally run the query whenever `requestBody` changes
// and the user is an admin. If requestBody is empty or user isn't admin, no fetch is made.
- const { data, isLoading, isError, error, status } = useQuery({
+ const { data, isLoading, status } = useQuery({
queryKey: [testResult.queryKey, activeMembership, receivername, adminSafeOrgName, requestBody],
queryFn: fetchData,
enabled: isAdmin && Boolean(requestBody),
@@ -56,8 +67,6 @@ const useTestMessageResult = () => {
data: data ?? [],
setRequestBody,
isLoading,
- isError,
- error,
status,
isDisabled: !isAdmin,
};
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index 664f38fc39d..d9362fa7190 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -98,7 +98,6 @@ const AdminMessageTestingPage = () => {
results and output messages in separate tabs.
- Test message bank
{isLoading ? (
) : (
@@ -114,9 +113,11 @@ const AdminMessageTestingPage = () => {
)}
{currentMessageTestStep === MessageTestingSteps.StepTwo && (
{
+ setCurrentMessageTestStep(MessageTestingSteps.StepOne);
+ }}
/>
)}
>
diff --git a/frontend-react/src/utils/misc.ts b/frontend-react/src/utils/misc.ts
index 0d68966f305..9e1432219b0 100644
--- a/frontend-react/src/utils/misc.ts
+++ b/frontend-react/src/utils/misc.ts
@@ -165,3 +165,8 @@ export const parseFileLocation = (
export const removeHTMLFromString = (input: string, options = {}) => {
return convert(input, options);
};
+
+export const camelCaseToTitle = (s: string) => {
+ const result = s.replace(/([A-Z])/g, " $1");
+ return result.charAt(0).toUpperCase() + result.slice(1);
+};
From 9452fbbfbcca66ac6c67377d68e7f9e8cd73ffab Mon Sep 17 00:00:00 2001
From: etanb
Date: Mon, 6 Jan 2025 17:20:39 -0800
Subject: [PATCH 08/19] clean up styles and code
---
.../MessageTesting/MessageTestingResult.tsx | 64 +++++++++++++------
frontend-react/src/utils/misc.ts | 47 +++++++++++++-
2 files changed, 88 insertions(+), 23 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index b43e70e0f87..d71d5e636ef 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -3,8 +3,8 @@ import type { PropsWithChildren } from "react";
import language from "./language.json";
import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
import Alert, { type AlertProps } from "../../../shared/Alert/Alert";
+import { convertCase } from "../../../utils/misc";
import { USLinkButton } from "../../USLink";
-import { camelCaseToTitle } from "../../../utils/misc";
export interface MessageTestingResultProps extends PropsWithChildren {
submittedMessage: RSMessage | null;
@@ -38,7 +38,7 @@ const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...p
const alertType: AlertProps["type"] = !isPassed ? "error" : isWarned ? "warning" : "success";
const alertHeading = language[`${alertType}AlertHeading`];
const alertBody = language[`${alertType}AlertBody`];
- const timeOptions = {
+ const timeOptions: Intl.DateTimeFormatOptions = {
timeZone: "UTC",
month: "2-digit",
day: "2-digit",
@@ -47,17 +47,21 @@ const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...p
minute: "numeric",
hour12: true,
};
- const dateCreated = new Date(submittedMessage?.dateCreated);
- console.log("resultData = ", resultData);
+
return (
Test results: {submittedMessage?.fileName}
-
- {"<"} Select new message
+
+ Select new message
-
Test run: {dateCreated.toLocaleString("en-US", timeOptions)} UTC
+
+ Test run:{" "}
+ {submittedMessage?.dateCreated
+ ? `${new Date(submittedMessage.dateCreated).toLocaleString("en-US", timeOptions)} UTC`
+ : "N/A"}{" "}
+
@@ -72,19 +76,24 @@ const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...p
if (!arr?.length) return null;
return (
-
+
-
- {camelCaseToTitle(f)}
- {arr.length}
+
+ {convertCase(f, "camel", "sentence")}
+ {arr.length}
>
),
- content: arr.join("\n"),
+ content: (
+
+ {arr.join("\n")}
+
+ ),
expanded: false,
headingLevel: "h3",
id: `${f}-list`,
@@ -106,14 +115,19 @@ const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...p
key={`${f}-accordion`}
items={[
{
+ className: "bg-gray-5",
title: (
<>
-
- {camelCaseToTitle(f)}
- {arr.length}
+
+ {convertCase(f, "camel", "sentence")}
+ {arr.length}
>
),
- content: arr.join("\n"),
+ content: (
+
+ {arr.join("\n")}
+
+ ),
expanded: false,
headingLevel: "h3",
id: `${f}-list`,
@@ -129,8 +143,13 @@ const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...p
key={`output-submittedMessage-accordion`}
items={[
{
- title: "Output message",
- content: resultData.message,
+ className: "bg-gray-5",
+ title: Output message ,
+ content: (
+
+ {resultData.message}
+
+ ),
expanded: false,
headingLevel: "h3",
id: `output-submittedMessage-list`,
@@ -144,8 +163,13 @@ const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...p
key="test-submittedMessage-accordion"
items={[
{
- title: "Test message",
- content: submittedMessage?.reportBody,
+ className: "bg-gray-5",
+ title: Test message ,
+ content: (
+
+ {submittedMessage?.reportBody}
+
+ ),
expanded: false,
headingLevel: "h3",
id: "test-submittedMessage-list",
diff --git a/frontend-react/src/utils/misc.ts b/frontend-react/src/utils/misc.ts
index 9e1432219b0..ad90f8bb266 100644
--- a/frontend-react/src/utils/misc.ts
+++ b/frontend-react/src/utils/misc.ts
@@ -166,7 +166,48 @@ export const removeHTMLFromString = (input: string, options = {}) => {
return convert(input, options);
};
-export const camelCaseToTitle = (s: string) => {
- const result = s.replace(/([A-Z])/g, " $1");
- return result.charAt(0).toUpperCase() + result.slice(1);
+export const convertCase = (str: string, inputCase: string, outputCase: string) => {
+ let words;
+
+ // break the original string into an array of lowercase words
+ switch (inputCase) {
+ case "camel":
+ case "pascal":
+ words = str
+ .replace(/([A-Z])/g, " $1")
+ .trim()
+ .toLowerCase()
+ .split(/\s+/);
+ break;
+ case "snake":
+ words = str.toLowerCase().split("_");
+ break;
+ case "kebab":
+ words = str.toLowerCase().split("-");
+ break;
+ case "constant":
+ words = str.toLowerCase().split("_");
+ break;
+ default:
+ throw new Error(`Unknown inputCase: "${inputCase}"`);
+ }
+
+ let result;
+ switch (outputCase) {
+ case "sentence":
+ result = words.join(" ");
+ if (result.length > 0) {
+ result = result.charAt(0).toUpperCase() + result.slice(1);
+ }
+ break;
+
+ case "title":
+ result = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
+ break;
+
+ default:
+ throw new Error(`Unknown outputCase: "${outputCase}"`);
+ }
+
+ return result;
};
From 8b02bec53fa79aae84d8c109041de412fc343347 Mon Sep 17 00:00:00 2001
From: etanb
Date: Mon, 6 Jan 2025 17:23:59 -0800
Subject: [PATCH 09/19] clear results on back
---
.../admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index d9362fa7190..6e83b19ce08 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -116,6 +116,7 @@ const AdminMessageTestingPage = () => {
resultData={testResultData}
submittedMessage={selectedOption}
handleGoBack={() => {
+ setRequestBody(null);
setCurrentMessageTestStep(MessageTestingSteps.StepOne);
}}
/>
From e0bcd4bc87d53cf1203785eaa536d1d222783972 Mon Sep 17 00:00:00 2001
From: etanb
Date: Tue, 7 Jan 2025 14:27:12 -0800
Subject: [PATCH 10/19] typing clean up + navigation button addition
---
.../src/components/Admin/EditReceiverSettings.tsx | 13 ++++++++++++-
.../Admin/MessageTesting/MessageTestingResult.tsx | 12 ++++++------
.../AdminMessageTestingPage.tsx | 4 ++--
3 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/frontend-react/src/components/Admin/EditReceiverSettings.tsx b/frontend-react/src/components/Admin/EditReceiverSettings.tsx
index cbc9e724721..8cd9a1e0cdc 100644
--- a/frontend-react/src/components/Admin/EditReceiverSettings.tsx
+++ b/frontend-react/src/components/Admin/EditReceiverSettings.tsx
@@ -22,6 +22,7 @@ import {
} from "../../utils/TemporarySettingsAPITypes";
import { ModalConfirmDialog, ModalConfirmRef } from "../ModalConfirmDialog";
import { EnumTooltip, ObjectTooltip } from "../tooltips/ObjectTooltip";
+import { USLinkButton } from "../USLink";
const { RS_API_URL } = config;
@@ -338,7 +339,17 @@ export function EditReceiverSettingsPage() {
const { orgname, receivername, action } = useParams();
return (
- }>
+
+
+
+
+ Test a sample message
+
+
+ }
+ >
void;
}
@@ -26,14 +26,14 @@ const warningFields: (keyof RSMessageResult)[] = [
const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...props }: MessageTestingResultProps) => {
const isPassed =
- resultData.senderTransformPassed &&
- resultData.filtersPassed &&
- resultData.enrichmentSchemaPassed &&
- resultData.receiverTransformPassed;
+ !!resultData.senderTransformErrors.length &&
+ !!resultData.filterErrors.length &&
+ !!resultData.enrichmentSchemaErrors.length &&
+ !!resultData.receiverTransformErrors.length;
const isWarned =
!!resultData.senderTransformWarnings.length &&
!!resultData.enrichmentSchemaWarnings.length &&
- !!resultData.receiverTransformWarnings;
+ !!resultData.receiverTransformWarnings.length;
const alertType: AlertProps["type"] = !isPassed ? "error" : isWarned ? "warning" : "success";
const alertHeading = language[`${alertType}AlertHeading`];
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index 6e83b19ce08..6f462746975 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -10,7 +10,7 @@ import Crumbs, { CrumbsProps } from "../../../components/Crumbs";
import Spinner from "../../../components/Spinner";
import { StaticAlert, StaticAlertType } from "../../../components/StaticAlert";
import Title from "../../../components/Title";
-import { RSMessage } from "../../../config/endpoints/reports";
+import { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
import useTestMessageResult from "../../../hooks/api/messages/UseTestMessageResult/UseTestMessageResult";
import useTestMessages from "../../../hooks/api/messages/UseTestMessages/UseTestMessages";
import { FeatureName } from "../../../utils/FeatureName";
@@ -113,7 +113,7 @@ const AdminMessageTestingPage = () => {
)}
{currentMessageTestStep === MessageTestingSteps.StepTwo && (
{
setRequestBody(null);
From 30f18216671ccad42ccb32e460f5defcba73fd99 Mon Sep 17 00:00:00 2001
From: etanb
Date: Tue, 7 Jan 2025 15:06:50 -0800
Subject: [PATCH 11/19] add refetch button
---
.../MessageTesting/MessageTestingResult.tsx | 20 ++++++++++++++++---
.../UseTestMessageResult.ts | 3 ++-
.../AdminMessageTestingPage.tsx | 3 ++-
3 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index 7a02112f9e4..35d6e0738cb 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -1,4 +1,5 @@
-import { Accordion, Icon, Tag } from "@trussworks/react-uswds";
+import { QueryObserverResult } from "@tanstack/react-query";
+import { Accordion, Button, Icon, Tag } from "@trussworks/react-uswds";
import type { PropsWithChildren } from "react";
import language from "./language.json";
import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
@@ -10,6 +11,7 @@ export interface MessageTestingResultProps extends PropsWithChildren {
submittedMessage: RSMessage | null;
resultData: RSMessageResult;
handleGoBack: () => void;
+ refetch: () => Promise>;
}
const errorFields: (keyof RSMessageResult)[] = [
@@ -24,7 +26,13 @@ const warningFields: (keyof RSMessageResult)[] = [
"receiverTransformWarnings",
];
-const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...props }: MessageTestingResultProps) => {
+const MessageTestingResult = ({
+ resultData,
+ submittedMessage,
+ handleGoBack,
+ refetch,
+ ...props
+}: MessageTestingResultProps) => {
const isPassed =
!!resultData.senderTransformErrors.length &&
!!resultData.filterErrors.length &&
@@ -50,7 +58,13 @@ const MessageTestingResult = ({ resultData, submittedMessage, handleGoBack, ...p
return (
- Test results: {submittedMessage?.fileName}
+
+
Test results: {submittedMessage?.fileName}
+
+ void refetch()}>
+ Rerun test
+
+
Select new message
diff --git a/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts b/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
index 85b4cec7b47..861a8c74414 100644
--- a/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
+++ b/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
@@ -57,7 +57,7 @@ const useTestMessageResult = () => {
// Use 'enabled' to conditionally run the query whenever `requestBody` changes
// and the user is an admin. If requestBody is empty or user isn't admin, no fetch is made.
- const { data, isLoading, status } = useQuery({
+ const { data, isLoading, status, refetch } = useQuery({
queryKey: [testResult.queryKey, activeMembership, receivername, adminSafeOrgName, requestBody],
queryFn: fetchData,
enabled: isAdmin && Boolean(requestBody),
@@ -69,6 +69,7 @@ const useTestMessageResult = () => {
isLoading,
status,
isDisabled: !isAdmin,
+ refetch,
};
};
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index 6f462746975..aa401c55572 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -49,7 +49,7 @@ const AdminMessageTestingPage = () => {
// Sets data required for the MessageTestingForm
const { data: messageData, isDisabled } = useTestMessages();
- const { setRequestBody, isLoading, data: testResultData } = useTestMessageResult();
+ const { setRequestBody, isLoading, data: testResultData, refetch } = useTestMessageResult();
const [selectedOption, setSelectedOption] = useState(null);
const [currentTestMessages, setCurrentTestMessages] = useState(messageData);
const handleSubmit = (e: React.FormEvent) => {
@@ -119,6 +119,7 @@ const AdminMessageTestingPage = () => {
setRequestBody(null);
setCurrentMessageTestStep(MessageTestingSteps.StepOne);
}}
+ refetch={refetch}
/>
)}
>
From 35c072016e148487b13fd02a10b9c24926ff5501 Mon Sep 17 00:00:00 2001
From: etanb
Date: Thu, 9 Jan 2025 10:10:51 -0800
Subject: [PATCH 12/19] remove unneeded isPrimeAdmin check
---
.../messages/UseTestMessages/UseTestMessages.ts | 2 --
.../AdminMessageTestingPage.tsx | 14 +-------------
2 files changed, 1 insertion(+), 15 deletions(-)
diff --git a/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts b/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
index d91dc1c1b41..54d03e95729 100644
--- a/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
+++ b/frontend-react/src/hooks/api/messages/UseTestMessages/UseTestMessages.ts
@@ -16,7 +16,6 @@ const { test } = reportsEndpoints;
*
* @returns {object} The hook returns the following:
* - `data` (`RSMessage[] | undefined`): The fetched array of test messages.
- * - `isDisabled` (`boolean`): Indicates whether the feature is disabled for the current user.
* - Other properties from `useSuspenseQuery` (e.g., `isLoading`, `isError`, `error`).
*/
@@ -41,7 +40,6 @@ const useTestMessages = () => {
return {
...useSuspenseQueryResult,
data: data ?? [],
- isDisabled: !isAdmin,
};
};
diff --git a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
index aa401c55572..c32c1bb8ec7 100644
--- a/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
+++ b/frontend-react/src/pages/admin/AdminMessageTestingPage/AdminMessageTestingPage.tsx
@@ -8,7 +8,6 @@ import MessageTestingForm from "../../../components/Admin/MessageTesting/Message
import MessageTestingResult from "../../../components/Admin/MessageTesting/MessageTestingResult";
import Crumbs, { CrumbsProps } from "../../../components/Crumbs";
import Spinner from "../../../components/Spinner";
-import { StaticAlert, StaticAlertType } from "../../../components/StaticAlert";
import Title from "../../../components/Title";
import { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
import useTestMessageResult from "../../../hooks/api/messages/UseTestMessageResult/UseTestMessageResult";
@@ -48,7 +47,7 @@ const AdminMessageTestingPage = () => {
);
// Sets data required for the MessageTestingForm
- const { data: messageData, isDisabled } = useTestMessages();
+ const { data: messageData } = useTestMessages();
const { setRequestBody, isLoading, data: testResultData, refetch } = useTestMessageResult();
const [selectedOption, setSelectedOption] = useState(null);
const [currentTestMessages, setCurrentTestMessages] = useState(messageData);
@@ -58,17 +57,6 @@ const AdminMessageTestingPage = () => {
setCurrentMessageTestStep(MessageTestingSteps.StepTwo);
};
- // If a user is _not_ a PrimeAdmin, block this feature
- if (isDisabled) {
- return (
-
- );
- }
-
return (
<>
From 74893d5c946171f4b193c52a6674f18ed8d6e53e Mon Sep 17 00:00:00 2001
From: etanb
Date: Fri, 10 Jan 2025 16:30:07 -0800
Subject: [PATCH 13/19] design PR feedback
---
.../MessageTestingCustomMessage.tsx | 17 +++++++++--------
.../Admin/MessageTesting/MessageTestingForm.tsx | 1 +
.../MessageTesting/MessageTestingResult.tsx | 7 +++----
3 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
index be61940e8b7..7c739bc12f3 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingCustomMessage.tsx
@@ -8,12 +8,14 @@ export const MessageTestingCustomMessage = ({
setCustomMessageNumber,
setCurrentTestMessages,
setOpenCustomMessage,
+ setSelectedOption,
}: {
customMessageNumber: number;
currentTestMessages: { fileName: string; reportBody: string }[];
setCustomMessageNumber: (value: number) => void;
setCurrentTestMessages: (messages: RSMessage[]) => void;
setOpenCustomMessage: (value: boolean) => void;
+ setSelectedOption: (message: RSMessage) => void;
}) => {
const [text, setText] = useState("");
const handleTextareaChange = (event: ChangeEvent) => {
@@ -21,16 +23,15 @@ export const MessageTestingCustomMessage = ({
};
const handleAddCustomMessage = () => {
const dateCreated = new Date();
- setCurrentTestMessages([
- ...currentTestMessages,
- {
- dateCreated: dateCreated.toString(),
- fileName: `Custom message ${customMessageNumber}`,
- reportBody: text,
- },
- ]);
+ const customTestMessage = {
+ dateCreated: dateCreated.toString(),
+ fileName: `Custom message ${customMessageNumber}`,
+ reportBody: text,
+ };
+ setCurrentTestMessages([...currentTestMessages, customTestMessage]);
setCustomMessageNumber(customMessageNumber + 1);
setText("");
+ setSelectedOption(customTestMessage);
setOpenCustomMessage(false);
};
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
index f4ccfd52ea9..37f34e6e2ab 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingForm.tsx
@@ -61,6 +61,7 @@ const MessageTestingForm = ({
setCustomMessageNumber={setCustomMessageNumber}
setCurrentTestMessages={setCurrentTestMessages}
setOpenCustomMessage={setOpenCustomMessage}
+ setSelectedOption={setSelectedOption}
/>
)}
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index 35d6e0738cb..b74577cb5a7 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -4,7 +4,6 @@ import type { PropsWithChildren } from "react";
import language from "./language.json";
import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
import Alert, { type AlertProps } from "../../../shared/Alert/Alert";
-import { convertCase } from "../../../utils/misc";
import { USLinkButton } from "../../USLink";
export interface MessageTestingResultProps extends PropsWithChildren {
@@ -99,7 +98,7 @@ const MessageTestingResult = ({
title: (
<>
- {convertCase(f, "camel", "sentence")}
+ Transform warnings
{arr.length}
>
),
@@ -133,7 +132,7 @@ const MessageTestingResult = ({
title: (
<>
- {convertCase(f, "camel", "sentence")}
+ Filters triggered
{arr.length}
>
),
@@ -151,7 +150,7 @@ const MessageTestingResult = ({
);
})}
- {resultData.message && (
+ {resultData.message && isPassed && (
Date: Wed, 15 Jan 2025 09:35:37 -0800
Subject: [PATCH 14/19] remove ignore as default orgName param
---
.../UseTestMessageResult/UseTestMessageResult.ts | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts b/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
index 861a8c74414..ac17a31092c 100644
--- a/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
+++ b/frontend-react/src/hooks/api/messages/UseTestMessageResult/UseTestMessageResult.ts
@@ -3,9 +3,7 @@ import { useCallback, useState } from "react";
import { useParams } from "react-router";
import { reportsEndpoints, RSMessageResult } from "../../../../config/endpoints/reports";
import useSessionContext from "../../../../contexts/Session/useSessionContext";
-import useAdminSafeOrganizationName, {
- Organizations,
-} from "../../../UseAdminSafeOrganizationName/UseAdminSafeOrganizationName";
+import { Organizations } from "../../../UseAdminSafeOrganizationName/UseAdminSafeOrganizationName";
const { testResult } = reportsEndpoints;
@@ -23,10 +21,9 @@ const { testResult } = reportsEndpoints;
*/
const useTestMessageResult = () => {
const { activeMembership, authorizedFetch } = useSessionContext();
- const { receivername } = useParams();
+ const { orgname, receivername } = useParams();
const parsedName = activeMembership?.parsedName;
const isAdmin = Boolean(parsedName) && parsedName === Organizations.PRIMEADMINS;
- const adminSafeOrgName = useAdminSafeOrganizationName(parsedName);
const [requestBody, setRequestBody] = useState(null);
@@ -37,7 +34,7 @@ const useTestMessageResult = () => {
{
params: {
receiverName: receivername,
- organizationName: adminSafeOrgName,
+ organizationName: orgname,
},
data: requestBody,
},
@@ -53,12 +50,12 @@ const useTestMessageResult = () => {
return Promise.reject(new Error(String(err)));
}
}
- }, [authorizedFetch, receivername, adminSafeOrgName, requestBody]);
+ }, [authorizedFetch, orgname, receivername, requestBody]);
// Use 'enabled' to conditionally run the query whenever `requestBody` changes
// and the user is an admin. If requestBody is empty or user isn't admin, no fetch is made.
const { data, isLoading, status, refetch } = useQuery({
- queryKey: [testResult.queryKey, activeMembership, receivername, adminSafeOrgName, requestBody],
+ queryKey: [testResult.queryKey, activeMembership, receivername, requestBody],
queryFn: fetchData,
enabled: isAdmin && Boolean(requestBody),
});
From 2e1dc6cdd4bbdd456c5ac852930822c0de21e42a Mon Sep 17 00:00:00 2001
From: etanb
Date: Thu, 16 Jan 2025 10:51:53 -0800
Subject: [PATCH 15/19] categorization feedback
---
.../MessageTestingAccordion.tsx | 68 +++++++++
.../MessageTesting/MessageTestingResult.tsx | 129 +++++++-----------
2 files changed, 115 insertions(+), 82 deletions(-)
create mode 100644 frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
new file mode 100644
index 00000000000..2196e068e98
--- /dev/null
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
@@ -0,0 +1,68 @@
+import { Accordion, Icon, Tag } from "@trussworks/react-uswds";
+import { RSMessageResult } from "../../../config/endpoints/reports";
+
+export const MessageTestingAccordion = ({
+ accordionTitle,
+ priority,
+ resultData,
+ fieldsToRender,
+ expandAccordions,
+}: {
+ accordionTitle: string;
+ priority: "error" | "warning";
+ resultData: RSMessageResult;
+ fieldsToRender: (keyof RSMessageResult)[];
+ expandAccordions: boolean;
+}) => {
+ const fieldID = accordionTitle.toLowerCase().split(" ").join("-");
+ const existingFields = fieldsToRender.filter((field) => Object.keys(resultData).includes(field));
+ const combinedFieldData = existingFields.flatMap((field) => resultData[field]);
+
+ // Immediately return if there's no warning/error data to display
+ if (combinedFieldData.length === 0) return;
+
+ return (
+
+
+ {priority === "error" && }
+
+ {priority === "warning" && (
+
+ )}
+
+ {accordionTitle}
+
+ {priority === "error" && (
+ {combinedFieldData.length}
+ )}
+
+ {priority === "warning" && (
+ {combinedFieldData.length}
+ )}
+ >
+ ),
+ content: (
+
+ {combinedFieldData.map((item, index) => (
+
+
{item}
+ {index < combinedFieldData.length - 1 &&
}
+
+ ))}
+
+ ),
+ expanded: expandAccordions,
+ headingLevel: "h3",
+ id: `${fieldID}-list`,
+ },
+ ]}
+ />
+
+ );
+};
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index b74577cb5a7..9474c61f801 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -1,7 +1,8 @@
import { QueryObserverResult } from "@tanstack/react-query";
-import { Accordion, Button, Icon, Tag } from "@trussworks/react-uswds";
-import type { PropsWithChildren } from "react";
+import { Accordion, Button, Icon } from "@trussworks/react-uswds";
+import { type PropsWithChildren, useState } from "react";
import language from "./language.json";
+import { MessageTestingAccordion } from "./MessageTestingAccordion";
import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
import Alert, { type AlertProps } from "../../../shared/Alert/Alert";
import { USLinkButton } from "../../USLink";
@@ -13,12 +14,14 @@ export interface MessageTestingResultProps extends PropsWithChildren {
refetch: () => Promise>;
}
-const errorFields: (keyof RSMessageResult)[] = [
+const filterFields: (keyof RSMessageResult)[] = ["filterErrors"];
+
+const transformFields: (keyof RSMessageResult)[] = [
"senderTransformErrors",
"enrichmentSchemaErrors",
"receiverTransformErrors",
- "filterErrors",
];
+
const warningFields: (keyof RSMessageResult)[] = [
"senderTransformWarnings",
"enrichmentSchemaWarnings",
@@ -33,14 +36,14 @@ const MessageTestingResult = ({
...props
}: MessageTestingResultProps) => {
const isPassed =
- !!resultData.senderTransformErrors.length &&
- !!resultData.filterErrors.length &&
- !!resultData.enrichmentSchemaErrors.length &&
- !!resultData.receiverTransformErrors.length;
+ resultData.senderTransformErrors.length === 0 &&
+ resultData.filterErrors.length === 0 &&
+ resultData.enrichmentSchemaErrors.length === 0 &&
+ resultData.receiverTransformErrors.length === 0;
const isWarned =
- !!resultData.senderTransformWarnings.length &&
- !!resultData.enrichmentSchemaWarnings.length &&
- !!resultData.receiverTransformWarnings.length;
+ resultData.senderTransformWarnings.length > 0 &&
+ resultData.enrichmentSchemaWarnings.length > 0 &&
+ resultData.receiverTransformWarnings.length > 0;
const alertType: AlertProps["type"] = !isPassed ? "error" : isWarned ? "warning" : "success";
const alertHeading = language[`${alertType}AlertHeading`];
@@ -54,12 +57,17 @@ const MessageTestingResult = ({
minute: "numeric",
hour12: true,
};
+ const [expandAccordions, setExpandAccordions] = useState(false);
return (
Test results: {submittedMessage?.fileName}
+ setExpandAccordions(true)}>
+ Expand All
+
+
void refetch()}>
Rerun test
@@ -83,77 +91,34 @@ const MessageTestingResult = ({
- {errorFields.map((f) => {
- const arr = resultData[f];
- if (arr != null && !Array.isArray(arr)) throw new Error("Invalid resultData");
- if (!arr?.length) return null;
-
- return (
-
-
-
- Transform warnings
- {arr.length}
- >
- ),
- content: (
-
- {arr.join("\n")}
-
- ),
- expanded: false,
- headingLevel: "h3",
- id: `${f}-list`,
- },
- ]}
- />
-
- );
- })}
-
- {warningFields.map((f) => {
- const arr = resultData[f];
- if (arr != null && !Array.isArray(arr)) throw new Error("Invalid resultData");
- if (!arr?.length) return null;
-
- return (
-
-
-
- Filters triggered
- {arr.length}
- >
- ),
- content: (
-
- {arr.join("\n")}
-
- ),
- expanded: false,
- headingLevel: "h3",
- id: `${f}-list`,
- },
- ]}
- />
-
- );
- })}
+
+
+
+
+
+
{resultData.message && isPassed && (
),
- expanded: false,
+ expanded: expandAccordions,
headingLevel: "h3",
id: `output-submittedMessage-list`,
},
@@ -173,7 +138,7 @@ const MessageTestingResult = ({
)}
),
- expanded: false,
+ expanded: expandAccordions,
headingLevel: "h3",
id: "test-submittedMessage-list",
},
From 803655567d5d2645e68229131378a894bf35cb0a Mon Sep 17 00:00:00 2001
From: etanb
Date: Thu, 16 Jan 2025 11:00:27 -0800
Subject: [PATCH 16/19] IA feedback + accordion simplification
---
.../MessageTesting/MessageTestingAccordion.tsx | 6 ++----
.../MessageTesting/MessageTestingResult.tsx | 18 +++++-------------
2 files changed, 7 insertions(+), 17 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
index 2196e068e98..8f2fd987cde 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
@@ -6,13 +6,11 @@ export const MessageTestingAccordion = ({
priority,
resultData,
fieldsToRender,
- expandAccordions,
}: {
accordionTitle: string;
priority: "error" | "warning";
resultData: RSMessageResult;
fieldsToRender: (keyof RSMessageResult)[];
- expandAccordions: boolean;
}) => {
const fieldID = accordionTitle.toLowerCase().split(" ").join("-");
const existingFields = fieldsToRender.filter((field) => Object.keys(resultData).includes(field));
@@ -24,7 +22,7 @@ export const MessageTestingAccordion = ({
return (
),
- expanded: expandAccordions,
+ expanded: false,
headingLevel: "h3",
id: `${fieldID}-list`,
},
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index 9474c61f801..ae6ad0a77b8 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -1,6 +1,6 @@
import { QueryObserverResult } from "@tanstack/react-query";
import { Accordion, Button, Icon } from "@trussworks/react-uswds";
-import { type PropsWithChildren, useState } from "react";
+import { type PropsWithChildren } from "react";
import language from "./language.json";
import { MessageTestingAccordion } from "./MessageTestingAccordion";
import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
@@ -57,17 +57,12 @@ const MessageTestingResult = ({
minute: "numeric",
hour12: true,
};
- const [expandAccordions, setExpandAccordions] = useState(false);
return (
Test results: {submittedMessage?.fileName}
-
setExpandAccordions(true)}>
- Expand All
-
-
void refetch()}>
Rerun test
@@ -96,7 +91,6 @@ const MessageTestingResult = ({
priority="error"
resultData={resultData}
fieldsToRender={filterFields}
- expandAccordions={expandAccordions}
/>
{resultData.message && isPassed && (
),
- expanded: expandAccordions,
+ expanded: false,
headingLevel: "h3",
id: `output-submittedMessage-list`,
},
@@ -138,7 +130,7 @@ const MessageTestingResult = ({
)}
),
- expanded: expandAccordions,
+ expanded: false,
headingLevel: "h3",
id: "test-submittedMessage-list",
},
From 1611a22a70df5744096e846801c07e85662ab18f Mon Sep 17 00:00:00 2001
From: etanb
Date: Thu, 23 Jan 2025 12:20:27 -0800
Subject: [PATCH 17/19] design updates
---
.../MessageTestingAccordion.tsx | 2 +-
.../MessageTesting/MessageTestingResult.tsx | 6 +-
.../src/shared/HL7Message/HL7Message.tsx | 32 ++++
frontend-react/src/styles/_components.scss | 137 +++++++++---------
frontend-react/src/utils/misc.ts | 12 ++
5 files changed, 120 insertions(+), 69 deletions(-)
create mode 100644 frontend-react/src/shared/HL7Message/HL7Message.tsx
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
index 8f2fd987cde..89e5d673059 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingAccordion.tsx
@@ -50,7 +50,7 @@ export const MessageTestingAccordion = ({
{combinedFieldData.map((item, index) => (
{item}
- {index < combinedFieldData.length - 1 &&
}
+ {index < combinedFieldData.length - 1 &&
}
))}
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index ae6ad0a77b8..fd0ef022530 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -5,6 +5,8 @@ import language from "./language.json";
import { MessageTestingAccordion } from "./MessageTestingAccordion";
import type { RSMessage, RSMessageResult } from "../../../config/endpoints/reports";
import Alert, { type AlertProps } from "../../../shared/Alert/Alert";
+import HL7Message from "../../../shared/HL7Message/HL7Message";
+import { prettifyJSON } from "../../../utils/misc";
import { USLinkButton } from "../../USLink";
export interface MessageTestingResultProps extends PropsWithChildren {
@@ -117,7 +119,7 @@ const MessageTestingResult = ({
title: Output message ,
content: (
- {resultData.message}
+
),
expanded: false,
@@ -137,7 +139,7 @@ const MessageTestingResult = ({
title: Test message ,
content: (
- {submittedMessage?.reportBody}
+
{prettifyJSON(submittedMessage?.reportBody ?? "")}
),
expanded: false,
diff --git a/frontend-react/src/shared/HL7Message/HL7Message.tsx b/frontend-react/src/shared/HL7Message/HL7Message.tsx
new file mode 100644
index 00000000000..ffee01a05ff
--- /dev/null
+++ b/frontend-react/src/shared/HL7Message/HL7Message.tsx
@@ -0,0 +1,32 @@
+interface HL7MessageProps {
+ message: string;
+}
+
+const HL7Message = ({ message }: HL7MessageProps) => {
+ const normalized = message.replace(/\r/g, "\n");
+ const lines = normalized.split("\n");
+
+ return (
+
+ {lines.map((line, index) => {
+ const delimiterIndex = line.indexOf("|");
+
+ if (delimiterIndex === -1) {
+ return
{line}
;
+ }
+
+ const segmentCode = line.slice(0, delimiterIndex);
+ const message = line.slice(delimiterIndex);
+
+ return (
+
+ {segmentCode}
+ {message}
+
+ );
+ })}
+
+ );
+};
+
+export default HL7Message;
diff --git a/frontend-react/src/styles/_components.scss b/frontend-react/src/styles/_components.scss
index 5f5f7d2c921..47d5f54bd11 100644
--- a/frontend-react/src/styles/_components.scss
+++ b/frontend-react/src/styles/_components.scss
@@ -6,112 +6,117 @@ $theme-hr-alternate-border-color: "primary";
.usa-prose hr,
hr {
- border: 1px solid color($theme-hr-border-color);
- @include u-margin-bottom(4);
- @include u-margin-top(4);
-
- &.rs-hr--alternate {
- @include u-border("2px", "blue-warm-70v");
- margin-bottom: unset;
- }
-
- &.rs-hr--base-lighter-alternate {
- margin-block-start: 32px;
- }
+ border: 1px solid color($theme-hr-border-color);
+ @include u-margin-bottom(4);
+ @include u-margin-top(4);
+
+ &.rs-hr--half-margin {
+ @include u-margin-bottom(2);
+ @include u-margin-top(2);
+ }
+
+ &.rs-hr--alternate {
+ @include u-border("2px", "blue-warm-70v");
+ margin-bottom: unset;
+ }
+
+ &.rs-hr--base-lighter-alternate {
+ margin-block-start: 32px;
+ }
}
.usa-link--unstyled {
- all: unset;
- cursor: pointer;
+ all: unset;
+ cursor: pointer;
}
.usa-icon-button {
- border-radius: 100%;
- padding: 0.25rem 0.25rem;
+ border-radius: 100%;
+ padding: 0.25rem 0.25rem;
}
.font-size-18 {
- font-size: 18px;
+ font-size: 18px;
}
.usa-timestamp {
- color: color("gray-cool-50");
+ color: color("gray-cool-50");
}
// FileHandler.tsx
.file-handler-table {
- .usa-table {
- // @override _table.scss
- td {
- background-color: transparent;
- }
+ .usa-table {
+ // @override _table.scss
+ td {
+ background-color: transparent;
}
+ }
}
// End -- FileHandler.tsx
// FileHandlerMessaging.tsx
.validation-section-header {
- font-size: 20px;
- font-weight: bold;
- margin: 0;
+ font-size: 20px;
+ font-weight: bold;
+ margin: 0;
}
// End -- FileHandlerMessaging.tsx
// Message Tracker specific styles for Accordion component
.message-tracker {
- .usa-accordion__button {
- color: white;
- background-color: color("primary");
- @include add-background-svg("usa-icons-bg/remove--white");
-
- &[aria-expanded="false"] {
- @include add-background-svg("usa-icons-bg/add--white");
- }
- }
- .usa-accordion__button:hover,
- .usa-accordion__button:focus {
- color: white;
- background-color: color("primary");
- }
- .usa-accordion--bordered .usa-accordion__content {
- border-color: color("primary");
- border-width: medium;
+ .usa-accordion__button {
+ color: white;
+ background-color: color("primary");
+ @include add-background-svg("usa-icons-bg/remove--white");
+
+ &[aria-expanded="false"] {
+ @include add-background-svg("usa-icons-bg/add--white");
}
+ }
+ .usa-accordion__button:hover,
+ .usa-accordion__button:focus {
+ color: white;
+ background-color: color("primary");
+ }
+ .usa-accordion--bordered .usa-accordion__content {
+ border-color: color("primary");
+ border-width: medium;
+ }
}
.manage-public-key {
- .usa-icon {
- font-size: 20px;
- }
+ .usa-icon {
+ font-size: 20px;
+ }
}
.manage-public-key-configured {
- .usa-icon {
- font-size: 18px;
- margin-left: 5px;
- }
+ .usa-icon {
+ font-size: 18px;
+ margin-left: 5px;
+ }
}
// FAQ
.faq-wrapper {
- .heading {
- h3 {
- padding-bottom: 20px;
- color: #71767a;
- font-weight: 500;
- }
+ .heading {
+ h3 {
+ padding-bottom: 20px;
+ color: #71767a;
+ font-weight: 500;
}
+ }
- section {
- margin-top: 30px;
- border-top: 1px solid #dfe1e2;
- }
+ section {
+ margin-top: 30px;
+ border-top: 1px solid #dfe1e2;
+ }
- h3 {
- font-size: 22px;
- }
+ h3 {
+ font-size: 22px;
+ }
- h4 {
- margin-top: 40px;
- }
+ h4 {
+ margin-top: 40px;
+ }
}
diff --git a/frontend-react/src/utils/misc.ts b/frontend-react/src/utils/misc.ts
index ad90f8bb266..c26172d90d4 100644
--- a/frontend-react/src/utils/misc.ts
+++ b/frontend-react/src/utils/misc.ts
@@ -211,3 +211,15 @@ export const convertCase = (str: string, inputCase: string, outputCase: string)
return result;
};
+
+export const prettifyJSON = (str: string) => {
+ let prettyStr = str;
+
+ try {
+ const parsed = JSON.parse(str);
+ prettyStr = JSON.stringify(parsed, null, 2);
+ } catch (e) {
+ console.warn("Invalid JSON:", e);
+ }
+ return prettyStr;
+};
From 400f3dd6e989228fa6c0f55fb2b458e8b6842505 Mon Sep 17 00:00:00 2001
From: etanb
Date: Thu, 23 Jan 2025 12:27:45 -0800
Subject: [PATCH 18/19] color update
---
.../components/Admin/MessageTesting/MessageTestingResult.tsx | 2 +-
frontend-react/src/shared/HL7Message/HL7Message.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index fd0ef022530..dd516c9a5c2 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -109,7 +109,7 @@ const MessageTestingResult = ({
fieldsToRender={warningFields}
/>
- {resultData.message && isPassed && (
+ {resultData.message && (
{
return (
- {segmentCode}
+ {segmentCode}
{message}
);
From 36918b19a9fafab66358762ee341691467f9350a Mon Sep 17 00:00:00 2001
From: etanb
Date: Thu, 23 Jan 2025 14:48:10 -0800
Subject: [PATCH 19/19] PR comments
---
.../Admin/MessageTesting/MessageTestingResult.tsx | 2 +-
.../messages/UseTestMessageResult/UseTestMessageResult.ts | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
index dd516c9a5c2..fd0ef022530 100644
--- a/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
+++ b/frontend-react/src/components/Admin/MessageTesting/MessageTestingResult.tsx
@@ -109,7 +109,7 @@ const MessageTestingResult = ({
fieldsToRender={warningFields}
/>
- {resultData.message && (
+ {resultData.message && isPassed && (
{
// Use 'enabled' to conditionally run the query whenever `requestBody` changes
// and the user is an admin. If requestBody is empty or user isn't admin, no fetch is made.
- const { data, isLoading, status, refetch } = useQuery({
+ const useQueryResult = useQuery({
queryKey: [testResult.queryKey, activeMembership, receivername, requestBody],
queryFn: fetchData,
enabled: isAdmin && Boolean(requestBody),
});
+ const { data } = useQueryResult;
+
return {
+ ...useQueryResult,
data: data ?? [],
setRequestBody,
- isLoading,
- status,
isDisabled: !isAdmin,
- refetch,
};
};