Skip to content

Commit

Permalink
Merge pull request #16792 from CDCgov/experience/16070/message-bank-page
Browse files Browse the repository at this point in the history
Experience/16070/message bank page
  • Loading branch information
etanb authored Dec 17, 2024
2 parents 4dc59ca + adcf66e commit 6a4717c
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 159 deletions.
5 changes: 5 additions & 0 deletions frontend-react/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +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"));
/* Content Pages */
const Home = lazy(lazyRouteMarkdown(() => import("./content/home/index.mdx")));
const About = lazy(lazyRouteMarkdown(() => import("./content/about/index.mdx")));
Expand Down Expand Up @@ -437,6 +438,10 @@ export const appRoutes: RouteObject[] = [
path: "orgreceiversettings/org/:orgname/receiver/:receivername/action/:action",
element: <EditReceiverSettingsPage />,
},
{
path: "orgreceiversettings/org/:orgname/receiver/:receivername/action/:action/message-testing",
element: <ReportTestingPage />,
},
{
path: "orgsendersettings/org/:orgname/sender/:sendername/action/:action",
element: <EditSenderSettingsPage />,
Expand Down
178 changes: 42 additions & 136 deletions frontend-react/src/components/Admin/EditReceiverSettings.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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<SetStateAction<RSMessage[] | null>>;
setOpenCustomMessage: (value: boolean) => void;
}) => {
const [text, setText] = useState("");
const handleTextareaChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
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 (
<div className="width-full">
<p className="text-bold">Enter custom message</p>
<p>Custom messages do not save to the bank after you log out.</p>
<Textarea
value={text}
onChange={handleTextareaChange}
id="custom-message-text"
name="custom-message-text"
className="width-full maxw-full margin-bottom-205"
/>
<div className="width-full text-right">
<Button
type="button"
outline
onClick={() => {
setOpenCustomMessage(false);
}}
>
Cancel
</Button>
<Button type="button" onClick={handleAddCustomMessage} disabled={text.length === 0}>
Add
</Button>
</div>
</div>
);
};
114 changes: 114 additions & 0 deletions frontend-react/src/components/Admin/MessageTesting/MessageTesting.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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<EditReceiverSettingsParams>();
const { testMessages, isDisabled, isLoading } = useTestMessages();
const [selectedOption, setSelectedOption] = useState<string | null>(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 <AdminFetchAlert />;
}
if (isLoading || !currentTestMessages) return <Spinner />;

const handleSelect = (event: ChangeEvent<HTMLInputElement>) => {
setSelectedOption(event.target.value);
};

const handleAddCustomMessage = () => {
setSelectedOption(null);
setOpenCustomMessage(true);
};

return (
<>
<Helmet>
<title>Message testing - ReportStream</title>
</Helmet>
<GridContainer>
<Crumbs {...crumbProps}></Crumbs>
</GridContainer>
<AdminFormWrapper
header={
<>
<Title title={"Message testing"} />
<h2 className="margin-bottom-0">
<span className="text-normal font-body-md text-base margin-bottom-0">
Org name: {orgname}
<br />
Receiver name: {receivername}
</span>
</h2>
</>
}
>
<GridContainer>
<p>
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.
</p>
<hr />
<p className="font-sans-xl text-bold">Test message bank</p>
<form>
<fieldset className="usa-fieldset bg-base-lightest padding-3">
{currentTestMessages?.map((item, index) => (
<RadioField
key={index}
index={index}
title={item.fileName}
body={item.reportBody}
handleSelect={handleSelect}
selectedOption={selectedOption}
/>
))}
{openCustomMessage && (
<CustomMessage
customMessageNumber={customMessageNumber}
currentTestMessages={currentTestMessages}
setCustomMessageNumber={setCustomMessageNumber}
setCurrentTestMessages={setCurrentTestMessages}
setOpenCustomMessage={setOpenCustomMessage}
/>
)}
</fieldset>
<div className="padding-top-4">
<Button type="button" outline onClick={handleAddCustomMessage}>
Test custom message
</Button>
<Button disabled={!selectedOption} type="button">
Run test
</Button>
</div>
</form>
</GridContainer>
</AdminFormWrapper>
</>
);
}

export default ReportTesting;
57 changes: 57 additions & 0 deletions frontend-react/src/components/Admin/MessageTesting/RadioField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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<HTMLInputElement>) => 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 (
<Radio
id={`message-${index}`}
name="message-test-form"
value={body}
onChange={handleSelect}
checked={selectedOption === body}
className="usa-radio bg-base-lightest padding-2 border-bottom-1px border-gray-30"
label={
<>
{" "}
{title}{" "}
<Button type="button" unstyled onClick={openTextInNewTab}>
View message
<Icon.Visibility className="text-tbottom margin-left-05" aria-label="View message" />
</Button>
</>
}
/>
);
};
18 changes: 18 additions & 0 deletions frontend-react/src/config/endpoints/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export enum ServicesUrls {
PUBLIC_KEYS = "/settings/organizations/:orgName/public-keys",
}

export enum ReportsUrls {
TESTING = "/reports/testing",
}

export interface RSSettings {
version: number;
createdAt: string;
Expand All @@ -22,6 +26,12 @@ 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[];
Expand Down Expand Up @@ -106,3 +116,11 @@ export const servicesEndpoints: RSApiEndpoints = {
queryKey: "createPublicKey",
}),
};

export const reportsEndpoints: RSApiEndpoints = {
testing: new RSEndpoint({
path: ReportsUrls.TESTING,
method: HTTPMethods.GET,
queryKey: "reportsTesting",
}),
};
48 changes: 48 additions & 0 deletions frontend-react/src/hooks/api/messages/UseTestMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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";

const { testing } = reportsEndpoints;

/**
* Custom hook to fetch and manage "Test Messages" data for the current session.
*
* @description
* This hook fetches "Test Messages" from the backend. While the UI and design refer to this feature as
* "Test Messages," the corresponding API endpoint is `/api/reports/testing`, which uses the "Reports" nomenclature.
* 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.
* - `isDisabled` (`boolean`): Indicates whether the feature is disabled for the current user.
* - Other properties from `useSuspenseQuery` (e.g., `isLoading`, `isError`, `error`).
*/

const useTestMessages = () => {
const { activeMembership, authorizedFetch } = useSessionContext();
const parsedName = activeMembership?.parsedName;
const isAdmin = Boolean(parsedName) && parsedName === Organizations.PRIMEADMINS;

const memoizedDataFetch = useCallback(() => {
if (isAdmin) {
return authorizedFetch<RSMessage[]>({}, testing);
}
return null;
}, [isAdmin, authorizedFetch]);
const useSuspenseQueryResult = useSuspenseQuery({
queryKey: [testing.queryKey, activeMembership],
queryFn: memoizedDataFetch,
});

const { data } = useSuspenseQueryResult;

return {
...useSuspenseQueryResult,
testMessages: data,
isDisabled: !isAdmin,
};
};

export default useTestMessages;
12 changes: 7 additions & 5 deletions frontend-react/src/utils/FeatureName.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export enum FeatureName {
ADMIN = "Admin",
DAILY_DATA = "Daily Data",
DATA_DASHBOARD = "Data Dashboard",
FACILITIES_PROVIDERS = "All facilities & providers",
MESSAGE_TESTING = "Message testing",
PUBLIC_KEY = "Public Key",
RECEIVER_SETTINGS = "Receiver settings",
REPORT_DETAILS = "Report Details",
SUBMISSIONS = "Submissions",
SUPPORT = "Support",
ADMIN = "Admin",
UPLOAD = "Upload",
FACILITIES_PROVIDERS = "All facilities & providers",
DATA_DASHBOARD = "Data Dashboard",
REPORT_DETAILS = "Report Details",
PUBLIC_KEY = "Public Key",
}
Loading

0 comments on commit 6a4717c

Please sign in to comment.