From e345e330d2096494e53c440f2c8c748649586e1d Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Mon, 13 Jan 2025 14:38:48 -0700 Subject: [PATCH 01/17] fix(waivers): Change to ensure waiver emails are sending correctly (#1009) * Adjusted for missing Waiver types and prob broke some tests. * Update * Removed unused emails and updated waiver mapping * Update ProcesEmails * get authority from item in main for respond-to-rai * update cpoc and srt getter methods * Update cpoc and srt logic * Merge main into e-wave * feat(test) additional coverage for get from (#1010) * feat(test) add test for getfrom * feat(test) additional coverage for getfrom * fix(ui): issues at `700px` (#924) --------- Co-authored-by: Thomas Walker Co-authored-by: rmuntaqim <146774930+rmuntaqim@users.noreply.github.com> --- lib/lambda/processEmails.test.ts | 29 +- lib/lambda/processEmails.ts | 37 +- lib/lambda/processEmailsHandlers.test.ts | 134 - .../submissionPayloads/respond-to-rai.ts | 19 +- lib/libs/email/content/email-components.tsx | 80 +- .../withdrawRai/emailTemplates/AppKCMS.tsx | 22 +- .../withdrawRai/emailTemplates/AppKState.tsx | 21 +- .../withdrawRai/emailTemplates/ChipSpaCMS.tsx | 27 - .../emailTemplates/ChipSpaState.tsx | 27 - .../withdrawRai/emailTemplates/MedSpaCMS.tsx | 28 - .../emailTemplates/MedSpaState.tsx | 27 - .../emailTemplates/Waiver1915bCMS.tsx | 46 +- .../emailTemplates/Waiver1915bState.tsx | 26 +- .../withdrawRai/emailTemplates/index.tsx | 4 - lib/libs/email/content/withdrawRai/index.tsx | 227 +- lib/libs/email/errors.ts | 14 + lib/libs/email/index.ts | 58 +- lib/libs/email/mock-data/respond-to-rai.ts | 1 + lib/libs/email/mock-data/withdraw-rai.ts | 31 +- .../CMS/InitialSubmissionCMS.test.tsx | 2 +- .../CMS/{ => __snapshots__}/AppK.tsx | 6 +- .../email/preview/Withdraw Rai/CMS/AppK.tsx | 32 - .../preview/Withdraw Rai/CMS/CHIP_SPA.tsx | 29 - .../preview/Withdraw Rai/CMS/Medicaid_SPA.tsx | 29 - .../Withdraw Rai/CMS/Waiver_Contracting.tsx | 29 - .../Withdraw Rai/CMS/WithdrwRaiCMS.test.tsx | 29 - .../__snapshots__/WithdrwRaiCMS.test.tsx.snap | 6289 ----------------- .../email/preview/Withdraw Rai/State/AppK.tsx | 32 - .../preview/Withdraw Rai/State/CHIP_SPA.tsx | 29 - .../Withdraw Rai/State/Medicaid_SPA.tsx | 29 - .../Withdraw Rai/State/Waiver_Contracting.tsx | 29 - .../State/WithdrawRaiState.test.tsx | 29 - .../WithdrawRaiState.test.tsx.snap | 5564 --------------- lib/libs/email/types.ts | 12 +- lib/packages/shared-types/events/index.ts | 2 + 35 files changed, 369 insertions(+), 12660 deletions(-) delete mode 100644 lib/lambda/processEmailsHandlers.test.ts delete mode 100644 lib/libs/email/content/withdrawRai/emailTemplates/ChipSpaCMS.tsx delete mode 100644 lib/libs/email/content/withdrawRai/emailTemplates/ChipSpaState.tsx delete mode 100644 lib/libs/email/content/withdrawRai/emailTemplates/MedSpaCMS.tsx delete mode 100644 lib/libs/email/content/withdrawRai/emailTemplates/MedSpaState.tsx create mode 100644 lib/libs/email/errors.ts rename lib/libs/email/preview/Initial Submissions/CMS/{ => __snapshots__}/AppK.tsx (67%) delete mode 100644 lib/libs/email/preview/Withdraw Rai/CMS/AppK.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/CMS/CHIP_SPA.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/CMS/Medicaid_SPA.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/CMS/Waiver_Contracting.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/CMS/WithdrwRaiCMS.test.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/CMS/__snapshots__/WithdrwRaiCMS.test.tsx.snap delete mode 100644 lib/libs/email/preview/Withdraw Rai/State/AppK.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/State/CHIP_SPA.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/State/Medicaid_SPA.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/State/Waiver_Contracting.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/State/WithdrawRaiState.test.tsx delete mode 100644 lib/libs/email/preview/Withdraw Rai/State/__snapshots__/WithdrawRaiState.test.tsx.snap diff --git a/lib/lambda/processEmails.test.ts b/lib/lambda/processEmails.test.ts index e78f67e834..b5a5b1eee0 100644 --- a/lib/lambda/processEmails.test.ts +++ b/lib/lambda/processEmails.test.ts @@ -4,7 +4,7 @@ import { SESClient } from "@aws-sdk/client-ses"; import { sendEmail, validateEmailTemplate, handler } from "./processEmails"; import { KafkaRecord, KafkaEvent } from "shared-types"; -describe("process emails Handler", () => { +describe("process emails Handler", () => { it("should return 200 with a proper email", async () => { const params = { Source: "sender@example.com", @@ -13,10 +13,12 @@ describe("process emails Handler", () => { Subject: { Data: "Mocked Email", Charset: "UTF-8" }, Body: { Text: { Data: "This is a mocked email body.", Charset: "UTF-8" } }, }, + ConfigurationSetName: "test-config", }; const test = await sendEmail(params, "us-east-1"); expect(test.status).toStrictEqual(200); }); + it("should throw an error", async () => { const params = { Source: "sender@example.com", @@ -25,17 +27,33 @@ describe("process emails Handler", () => { Subject: { Data: "Mocked Email", Charset: "UTF-8" }, Body: { Text: { Data: "This is a mocked email body.", Charset: "UTF-8" } }, }, + ConfigurationSetName: "test-config", }; await expect(sendEmail(params, "bad-test")).rejects.toThrowError(); }); - it("should validate the email template and throw an error", async () => { + + it("should validate the email template and throw an error for missing fields", async () => { + const template = { + to: "Person", + from: "Other Guy", + body: "body", + // missing required 'subject' field + }; + expect(() => validateEmailTemplate(template)).toThrowError( + "Email template missing required fields: subject", + ); + }); + + it("should validate a complete email template without throwing", () => { const template = { to: "Person", from: "Other Guy", body: "body", + subject: "Test Subject", }; - expect(() => validateEmailTemplate(template)).toThrowError(); + expect(() => validateEmailTemplate(template)).not.toThrow(); }); + it("should make a handler", async () => { const callback = vi.fn(); const secSPY = vi.spyOn(SESClient.prototype, "send"); @@ -66,7 +84,8 @@ describe("process emails Handler", () => { await handler(mockEvent, {} as Context, callback); expect(secSPY).toHaveBeenCalledTimes(2); }); - it("should not be mako therefor not do an event", async () => { + + it("should not be mako therefore not do an event", async () => { const callback = vi.fn(); const mockEvent: KafkaEvent = { records: { @@ -96,6 +115,7 @@ describe("process emails Handler", () => { await handler(mockEvent, {} as Context, callback); expect(secSPY).toHaveBeenCalledTimes(0); }); + it("should be missing a value, so nothing sent", async () => { const callback = vi.fn(); const mockEvent: KafkaEvent = { @@ -119,6 +139,7 @@ describe("process emails Handler", () => { await handler(mockEvent, {} as Context, callback); expect(secSPY).toHaveBeenCalledTimes(0); }); + it("should be missing an environment variable", async () => { const callback = vi.fn(); delete process.env.osDomain; diff --git a/lib/lambda/processEmails.ts b/lib/lambda/processEmails.ts index efe4f96fde..2b4d22e372 100644 --- a/lib/lambda/processEmails.ts +++ b/lib/lambda/processEmails.ts @@ -8,6 +8,7 @@ import { EMAIL_CONFIG, getCpocEmail, getSrtEmails } from "libs/email/content/ema import { htmlToText, HtmlToTextOptions } from "html-to-text"; import pLimit from "p-limit"; import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs"; +import { getNamespace } from "libs/utils"; class TemporaryError extends Error { constructor(message: string) { @@ -20,7 +21,7 @@ interface ProcessEmailConfig { emailAddressLookupSecretName: string; applicationEndpointUrl: string; osDomain: string; - indexNamespace: string; + indexNamespace?: string; region: string; DLQ_URL: string; userPoolId: string; @@ -33,7 +34,6 @@ export const handler: Handler = async (event) => { "emailAddressLookupSecretName", "applicationEndpointUrl", "osDomain", - "indexNamespace", "region", "DLQ_URL", "userPoolId", @@ -49,7 +49,7 @@ export const handler: Handler = async (event) => { const emailAddressLookupSecretName = process.env.emailAddressLookupSecretName!; const applicationEndpointUrl = process.env.applicationEndpointUrl!; const osDomain = process.env.osDomain!; - const indexNamespace = process.env.indexNamespace!; + const indexNamespace = process.env.indexNamespace; const region = process.env.region!; const DLQ_URL = process.env.DLQ_URL!; const userPoolId = process.env.userPoolId!; @@ -67,6 +67,8 @@ export const handler: Handler = async (event) => { isDev: isDev === "true", }; + console.log("config: ", JSON.stringify(config, null, 2)); + try { const results = await Promise.allSettled( Object.values(event.records) @@ -74,11 +76,14 @@ export const handler: Handler = async (event) => { .map((rec) => processRecord(rec, config)), ); + console.log("results: ", JSON.stringify(results, null, 2)); + const failures = results.filter((r) => r.status === "rejected"); if (failures.length > 0) { - console.error("Some records failed:", failures); + console.error("Some records failed:", JSON.stringify(failures, null, 2)); throw new TemporaryError("Some records failed processing"); } + console.log("All records processed successfully", JSON.stringify(failures, null, 2)); } catch (error) { console.error("Permanent failure:", error); @@ -106,7 +111,12 @@ export const handler: Handler = async (event) => { }; export async function processRecord(kafkaRecord: KafkaRecord, config: ProcessEmailConfig) { + console.log("processRecord called with kafkaRecord: ", JSON.stringify(kafkaRecord, null, 2)); const { key, value, timestamp } = kafkaRecord; + if (typeof key !== "string") { + console.log("key is not a string ", JSON.stringify(key, null, 2)); + throw new Error("Key is not a string"); + } const id: string = decodeBase64WithUtf8(key); if (!value) { @@ -118,14 +128,14 @@ export async function processRecord(kafkaRecord: KafkaRecord, config: ProcessEma timestamp, ...JSON.parse(decodeBase64WithUtf8(value)), }; + console.log("record: ", JSON.stringify(record, null, 2)); if (record.origin !== "mako") { - console.log("Kafka event is not of mako origin. Doing nothing."); + console.log("Kafka event is not of mako origin. Doing nothing."); return; } try { - console.log("Processing record:", JSON.stringify(record, null, 2)); console.log("Config:", JSON.stringify(config, null, 2)); await processAndSendEmails(record, id, config); } catch (error) { @@ -144,10 +154,7 @@ export function validateEmailTemplate(template: any) { } export async function processAndSendEmails(record: any, id: string, config: ProcessEmailConfig) { - const templates = await getEmailTemplates( - record.event, - record.authority.toLowerCase(), - ); + const templates = await getEmailTemplates(record.event, record.authority); if (!templates) { console.log( @@ -164,10 +171,10 @@ export async function processAndSendEmails(record: any, id: string, config: Proc const sec = await getSecret(config.emailAddressLookupSecretName); - const item = await os.getItem(config.osDomain, `${config.indexNamespace}main`, id); + const item = await os.getItem(config.osDomain, getNamespace("main"), id); + const cpocEmail = [...getCpocEmail(item)]; + const srtEmails = [...getSrtEmails(item)]; - const cpocEmail = getCpocEmail(item); - const srtEmails = getSrtEmails(item); const emails: EmailAddresses = JSON.parse(sec); const allStateUsersEmails = allStateUsers.map((user) => user.formattedEmailAddress); @@ -216,11 +223,11 @@ export function createEmailParams( baseUrl: string, isDev: boolean, ): SendEmailCommandInput { - const toAddresses = isDev ? [`State Submitter <${EMAIL_CONFIG.DEV_EMAIL}>`] : filledTemplate.to; const params = { Destination: { - ToAddresses: toAddresses, + ToAddresses: filledTemplate.to, CcAddresses: filledTemplate.cc, + BccAddresses: isDev ? [`State Submitter <${EMAIL_CONFIG.DEV_EMAIL}>`] : [], // this is so emails can be tested in dev as they should have the correct recipients but be blind copied on all emails on dev }, Message: { Body: { diff --git a/lib/lambda/processEmailsHandlers.test.ts b/lib/lambda/processEmailsHandlers.test.ts deleted file mode 100644 index 91017c9649..0000000000 --- a/lib/lambda/processEmailsHandlers.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; -import { Context } from "aws-lambda"; -import { SESClient } from "@aws-sdk/client-ses"; -import { handler } from "./processEmails"; -import { KafkaRecord, KafkaEvent } from "shared-types"; -import { Authority } from "shared-types"; - -const nms = "new-medicaid-submission"; -const ncs = "new-chip-submission"; -const tempExtension = "temp-extension"; -const withdrawPackage = "withdraw-package"; -const contractingInitial = "contracting-initial"; -const capitatedInitial = "capitated-initial"; - -describe("process emails Handler", () => { - it.each([ - [`should send an email for ${nms} with ${Authority.MED_SPA}`, Authority.MED_SPA, nms], - [`should send an email for ${nms} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, nms], - [`should send an email for ${nms} with ${Authority["1915b"]}`, Authority["1915b"], nms], - [`should send an email for ${nms} with ${Authority["1915c"]}`, Authority["1915c"], nms], - [`should send an email for ${ncs} with ${Authority.MED_SPA}`, Authority.MED_SPA, ncs], - [`should send an email for ${ncs} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, ncs], - [`should send an email for ${ncs} with ${Authority["1915b"]}`, Authority["1915b"], ncs], - [`should send an email for ${ncs} with ${Authority["1915c"]}`, Authority["1915c"], ncs], - [ - `should send an email for ${tempExtension} with ${Authority.MED_SPA}`, - Authority.MED_SPA, - tempExtension, - ], - [ - `should send an email for ${tempExtension} with ${Authority.CHIP_SPA}`, - Authority.CHIP_SPA, - tempExtension, - ], - [ - `should send an email for ${tempExtension} with ${Authority["1915b"]}`, - Authority["1915b"], - tempExtension, - ], - [ - `should send an email for ${tempExtension} with ${Authority["1915c"]}`, - Authority["1915c"], - tempExtension, - ], - [ - `should send an email for ${withdrawPackage} with ${Authority.MED_SPA}`, - Authority.MED_SPA, - withdrawPackage, - ], - [ - `should send an email for ${withdrawPackage} with ${Authority.CHIP_SPA}`, - Authority.CHIP_SPA, - withdrawPackage, - ], - [ - `should send an email for ${withdrawPackage} for ${ncs} with ${Authority["1915b"]}`, - Authority["1915b"], - withdrawPackage, - ], - [ - `should send an email for ${withdrawPackage} with ${Authority["1915c"]}`, - Authority["1915c"], - withdrawPackage, - ], - [ - `should send an email for ${contractingInitial} with ${Authority.MED_SPA}`, - Authority.MED_SPA, - contractingInitial, - ], - [ - `should send an email for ${contractingInitial} with ${Authority.CHIP_SPA}`, - Authority.CHIP_SPA, - contractingInitial, - ], - [ - `should send an email for ${contractingInitial} with ${Authority["1915b"]}`, - Authority["1915b"], - contractingInitial, - ], - [ - `should send an email for ${contractingInitial} with ${Authority["1915c"]}`, - Authority["1915c"], - contractingInitial, - ], - [ - `should send an email for ${capitatedInitial} with ${Authority.MED_SPA}`, - Authority.MED_SPA, - capitatedInitial, - ], - [ - `should send an email for ${capitatedInitial} with ${Authority.CHIP_SPA}`, - Authority.CHIP_SPA, - capitatedInitial, - ], - [ - `should send an email for ${capitatedInitial} with ${Authority["1915b"]}`, - Authority["1915b"], - capitatedInitial, - ], - [ - `should send an email for ${capitatedInitial} with ${Authority["1915c"]}`, - Authority["1915c"], - capitatedInitial, - ], - ])("%s", async (_, auth, eventType) => { - const callback = vi.fn(); - const secSPY = vi.spyOn(SESClient.prototype, "send"); - const mockEvent: KafkaEvent = { - records: { - "mock-topic": [ - { - key: Buffer.from("VA").toString("base64"), - value: Buffer.from( - JSON.stringify({ - origin: "mako", - event: eventType, - authority: auth, - }), - ).toString("base64"), - headers: {}, - timestamp: 1732645041557, - offset: "0", - partition: 0, - topic: "mock-topic", - } as unknown as KafkaRecord, - ], - }, - eventSource: "", - bootstrapServers: "", - }; - await handler(mockEvent, {} as Context, callback); - expect(secSPY).toHaveBeenCalledTimes(2); - }); -}); diff --git a/lib/lambda/submit/submissionPayloads/respond-to-rai.ts b/lib/lambda/submit/submissionPayloads/respond-to-rai.ts index af53355b24..0d1827e539 100644 --- a/lib/lambda/submit/submissionPayloads/respond-to-rai.ts +++ b/lib/lambda/submit/submissionPayloads/respond-to-rai.ts @@ -1,18 +1,14 @@ import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; +import { getDomain, getNamespace } from "libs/utils"; +import * as os from "libs/opensearch-lib"; export const respondToRai = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["respond-to-rai"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["respond-to-rai"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -27,16 +23,15 @@ export const respondToRai = async (event: APIGatewayEvent) => { throw "Item Doesn't Exist"; } + const item = await os.getItem(getDomain(), getNamespace("main"), parsedResult.data.id); const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; const transformedData = events["respond-to-rai"].schema.parse({ ...parsedResult.data, + authority: item?._source.authority, submitterName, submitterEmail, timestamp: Date.now(), diff --git a/lib/libs/email/content/email-components.tsx b/lib/libs/email/content/email-components.tsx index 59f1746b1b..f0ffde4a88 100644 --- a/lib/libs/email/content/email-components.tsx +++ b/lib/libs/email/content/email-components.tsx @@ -1,8 +1,15 @@ import { Column, Heading, Hr, Link, Row, Section, Text } from "@react-email/components"; import { ReactNode } from "react"; -import { Attachment, AttachmentKey, AttachmentTitle } from "shared-types"; +import { + Attachment, + AttachmentKey, + AttachmentTitle, + CommonEmailVariables, + EmailAddresses, + Events, +} from "shared-types"; import { styles } from "./email-styles"; - +import * as os from "shared-types/opensearch"; export const EMAIL_CONFIG = { DEV_EMAIL: "mako.stateuser+dev-to@gmail.com", CHIP_EMAIL: "CHIPSPASubmissionMailBox@cms.hhs.gov", @@ -271,40 +278,55 @@ const BasicFooter = () => ( ); -const WithdrawRAI = ({ - id, - submitterName, - submitterEmail, -}: { - id: string; - submitterName: string; - submitterEmail: string; -}) => ( -
- - The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are - receiving this email notification as the Formal RAI for {id} was withdrawn by {submitterName}{" "} - {submitterEmail}. - -
-); +export interface WithdrawRAIProps { + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }; + relatedEvent: Events["RespondToRai"]; +} -const getCpocEmail = (item: any): string[] => { +const WithdrawRAI: React.FC = ({ variables, relatedEvent }) => { + if (!relatedEvent) { + return ( +
+ + {`The OneMAC Submission Portal received a request to withdraw a Formal RAI Response. You are receiving this email notification as the Formal RAI was withdrawn by ${variables.submitterName} ${variables.submitterEmail}.`} + + + Note: The original RAI response details could not be retrieved. + +
+ ); + } + + return ( +
+ + {`The OneMAC Submission Portal received a request to withdraw the Formal RAI Response ${relatedEvent.id}. You are receiving this email notification as the Formal RAI for ${relatedEvent.id} was withdrawn by ${variables.submitterName} ${variables.submitterEmail}.`} + +
+ ); +}; + +const getCpocEmail = (item?: os.main.ItemResult): string[] => { try { - const { leadAnalystName, leadAnalystEmail } = item._source; - return [`${leadAnalystName} <${leadAnalystEmail}>`]; + if (item?._source?.leadAnalystEmail && item?._source?.leadAnalystName) { + const cpocEmail = `${item._source.leadAnalystName} <${item._source.leadAnalystEmail}>`; + return [cpocEmail]; + } + return []; } catch (e) { - console.error("Error getting CPCO email", e); + console.error("Error getting CPOC email", e); return []; } }; -const getSrtEmails = (item: any): string[] => { +const getSrtEmails = (item?: os.main.ItemResult): string[] => { try { - const reviewTeam = item._source.reviewTeam; - if (!reviewTeam) return []; - - return reviewTeam.map((reviewer: any) => `${reviewer.name} <${reviewer.email}>`); + if (item?._source?.reviewTeam && item._source.reviewTeam.length > 0) { + return item._source.reviewTeam.map( + (reviewer: { name: string; email: string }) => `${reviewer.name} <${reviewer.email}>`, + ); + } + return []; } catch (e) { console.error("Error getting SRT emails", e); return []; @@ -324,7 +346,7 @@ export { FollowUpNotice, BasicFooter, WithdrawRAI, + EmailFooter, getCpocEmail, getSrtEmails, - EmailFooter, }; diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/AppKCMS.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/AppKCMS.tsx index 0374384999..23750a4364 100644 --- a/lib/libs/email/content/withdrawRai/emailTemplates/AppKCMS.tsx +++ b/lib/libs/email/content/withdrawRai/emailTemplates/AppKCMS.tsx @@ -1,24 +1,24 @@ -import { CommonEmailVariables, Events, RelatedEventType } from "shared-types"; -import { Attachments, PackageDetails, BasicFooter } from "../../email-components"; +import { WithdrawRAI, PackageDetails, BasicFooter, Attachments } from "../../email-components"; +import { WithdrawRAIProps } from "../../email-components"; import { BaseEmailTemplate } from "../../email-templates"; -export const AppKCMSEmail = (props: { - variables: Events["RespondToRai"] & CommonEmailVariables; - relatedEvent: RelatedEventType; -}) => { - const { variables, relatedEvent } = { ...props }; +export const AppKCMSEmail = ({ variables, relatedEvent }: WithdrawRAIProps) => { + const previewText = `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`; + const heading = `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`; + return ( } > + { +export const AppKStateEmail = (props: WithdrawRAIProps) => { const { variables, relatedEvent } = { ...props }; + const previewText = `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`; + const heading = `The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for ${relatedEvent.id} was withdrawn by ${variables.submitterName} ${variables.submitterEmail}.`; return ( } > + - diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/ChipSpaCMS.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/ChipSpaCMS.tsx deleted file mode 100644 index 882ae4a435..0000000000 --- a/lib/libs/email/content/withdrawRai/emailTemplates/ChipSpaCMS.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { CommonEmailVariables, Events } from "shared-types"; -import { Container, Html } from "@react-email/components"; -import { WithdrawRAI, PackageDetails, BasicFooter } from "../../email-components"; - -export const ChipSpaCMSEmail = (props: { - variables: Events["RespondToRai"] & CommonEmailVariables; - relatedEvent: any; -}) => { - const { variables, relatedEvent } = { ...props }; - return ( - - - - - - - - ); -}; diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/ChipSpaState.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/ChipSpaState.tsx deleted file mode 100644 index 344c482361..0000000000 --- a/lib/libs/email/content/withdrawRai/emailTemplates/ChipSpaState.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { CommonEmailVariables, Events } from "shared-types"; -import { Container, Html } from "@react-email/components"; -import { WithdrawRAI, PackageDetails, FollowUpNotice } from "../../email-components"; - -export const ChipSpaStateEmail = (props: { - variables: Events["RespondToRai"] & CommonEmailVariables; - relatedEvent: any; -}) => { - const { variables, relatedEvent } = { ...props }; - return ( - - - - - - - - ); -}; diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/MedSpaCMS.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/MedSpaCMS.tsx deleted file mode 100644 index 25d0fa589e..0000000000 --- a/lib/libs/email/content/withdrawRai/emailTemplates/MedSpaCMS.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { CommonEmailVariables, Events } from "shared-types"; -import { Container, Html } from "@react-email/components"; -import { WithdrawRAI, PackageDetails, BasicFooter } from "../../email-components"; - -export const MedSpaCMSEmail = (props: { - variables: Events["RespondToRai"] & CommonEmailVariables; - relatedEvent: any; -}) => { - const { variables, relatedEvent } = { ...props }; - return ( - - - - - - - - ); -}; diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/MedSpaState.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/MedSpaState.tsx deleted file mode 100644 index 25a9d540aa..0000000000 --- a/lib/libs/email/content/withdrawRai/emailTemplates/MedSpaState.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { CommonEmailVariables, Events } from "shared-types"; -import { Container, Html } from "@react-email/components"; -import { WithdrawRAI, PackageDetails, FollowUpNotice } from "../../email-components"; - -export const MedSpaStateEmail = (props: { - variables: Events["RespondToRai"] & CommonEmailVariables; - relatedEvent: any; -}) => { - const { variables, relatedEvent } = { ...props }; - return ( - - - - - - - - ); -}; diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bCMS.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bCMS.tsx index 86b9f70c30..10464ab011 100644 --- a/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bCMS.tsx +++ b/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bCMS.tsx @@ -1,27 +1,27 @@ -import { CommonEmailVariables, Events } from "shared-types"; -import { Container, Html } from "@react-email/components"; -import { WithdrawRAI, PackageDetails, BasicFooter } from "../../email-components"; +import { WithdrawRAI, PackageDetails, BasicFooter, WithdrawRAIProps } from "../../email-components"; +import { BaseEmailTemplate } from "../../email-templates"; + +export const Waiver1915bCMSEmail = ({ variables, relatedEvent }: WithdrawRAIProps) => { + const previewText = `Waiver Package ${relatedEvent.id} withdrawn`; + const heading = `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`; -export const Waiver1915bCMSEmail = (props: { - variables: Events["RespondToRai"] & CommonEmailVariables; - relatedEvent: any; -}) => { - const { variables, relatedEvent } = { ...props }; return ( - - - - - - - + } + > + + + ); }; diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bState.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bState.tsx index 991b51ef47..00c9d80614 100644 --- a/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bState.tsx +++ b/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bState.tsx @@ -1,38 +1,32 @@ -import { CommonEmailVariables, Events } from "shared-types"; import { WithdrawRAI, PackageDetails, FollowUpNotice, MailboxNotice, - Attachments, + WithdrawRAIProps, } from "../../email-components"; import { BaseEmailTemplate } from "../../email-templates"; -export const Waiver1915bStateEmail = (props: { - variables: Events["RespondToRai"] & CommonEmailVariables; - relatedEvent: any; -}) => { - const { variables, relatedEvent } = { ...props }; - const previewText = `Waiver ${variables.id} Withdrawn`; +export const Waiver1915bStateEmail = (props: WithdrawRAIProps) => { + const previewText = `Waiver ${props.relatedEvent.id} Withdrawn`; const heading = "This response confirms you have withdrawn a Waiver from CMS for review"; return ( } > - + - ); diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/index.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/index.tsx index c414f4aa2b..6a9b3d4662 100644 --- a/lib/libs/email/content/withdrawRai/emailTemplates/index.tsx +++ b/lib/libs/email/content/withdrawRai/emailTemplates/index.tsx @@ -1,7 +1,3 @@ -export { MedSpaCMSEmail } from "./MedSpaCMS"; -export { MedSpaStateEmail } from "./MedSpaState"; -export { ChipSpaCMSEmail } from "./ChipSpaCMS"; -export { ChipSpaStateEmail } from "./ChipSpaState"; export { Waiver1915bCMSEmail } from "./Waiver1915bCMS"; export { Waiver1915bStateEmail } from "./Waiver1915bState"; export { AppKCMSEmail } from "./AppKCMS"; diff --git a/lib/libs/email/content/withdrawRai/index.tsx b/lib/libs/email/content/withdrawRai/index.tsx index d2787d6d95..eb7e6bc152 100644 --- a/lib/libs/email/content/withdrawRai/index.tsx +++ b/lib/libs/email/content/withdrawRai/index.tsx @@ -1,113 +1,144 @@ -import { Action, Authority, CommonEmailVariables, EmailAddresses } from "shared-types"; +import { CommonEmailVariables, EmailAddresses, Events, Authority } from "shared-types"; import { AuthoritiesWithUserTypesTemplate, getLatestMatchingEvent } from "../.."; - -import { - MedSpaCMSEmail, - MedSpaStateEmail, - ChipSpaCMSEmail, - ChipSpaStateEmail, - Waiver1915bCMSEmail, - Waiver1915bStateEmail, - AppKCMSEmail, -} from "./emailTemplates"; +import { Waiver1915bCMSEmail, Waiver1915bStateEmail, AppKCMSEmail } from "./emailTemplates"; import { render } from "@react-email/render"; +import { EmailProcessingError } from "../../errors"; + +const getWithdrawRaiEvent = async (id: string) => { + const event = await getLatestMatchingEvent(id, "WithdrawRai"); + + if (!event) { + return null; + } + + return event; +}; export const withdrawRai: AuthoritiesWithUserTypesTemplate = { - [Authority.MED_SPA]: { + [Authority["1915b"]]: { cms: async ( - variables: any & - CommonEmailVariables & { emails: EmailAddresses } & { - emails: EmailAddresses; - }, + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }, ) => { - const relatedEvent = await getLatestMatchingEvent(variables.id, Action.RESPOND_TO_RAI); - return { - to: [ - ...variables.emails.osgEmail, - ...variables.emails.dpoEmail, - ...variables.emails.cpocEmail, - ...variables.emails.srtEmails, - ], - subject: `Withdraw Formal RAI Response for SPA Package ${variables.id}`, - body: await render(), - }; + try { + const relatedEvent = await getLatestMatchingEvent(variables.id, "RespondToRai"); + if (!relatedEvent) { + throw new EmailProcessingError( + `Failed to find original RAI response event for withdrawal (ID: ${variables.id})`, + { + id: variables.id, + actionType: "RespondToRai", + emailType: "withdrawRai", + severity: "ERROR", + }, + ); + } + return { + to: [ + ...variables.emails.dmcoEmail, + ...variables.emails.osgEmail, + ...variables.emails.cpocEmail, + ...variables.emails.srtEmails, + ], + subject: `Withdraw Formal RAI Response for Waiver Package ${variables.id}`, + body: await render( + , + ), + }; + } catch (error) { + console.error(error); + throw error; + } }, - state: async (variables: any & CommonEmailVariables & { emails: EmailAddresses }) => { - const relatedEvent = await getLatestMatchingEvent(variables.id, Action.RESPOND_TO_RAI); - - return { - to: [`${variables.submitterName} <${variables.submitterEmail}>`], - cc: variables.allStateUsersEmails, - subject: `Withdraw Formal RAI Response for SPA Package ${variables.id}`, - body: await render(), - }; + state: async ( + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }, + ) => { + try { + const relatedEvent = await getWithdrawRaiEvent(variables.id); + if (!relatedEvent) { + throw new EmailProcessingError( + `Failed to find original RAI response event for withdrawal (ID: ${variables.id})`, + { + id: variables.id, + actionType: "RespondToRai", + emailType: "withdrawRai", + severity: "ERROR", + }, + ); + } + return { + to: variables.allStateUsersEmails || [], + subject: `Withdraw Formal RAI Response for Waiver Package ${variables.id}`, + body: await render( + , + ), + }; + } catch (error) { + console.error(error); + throw error; + } }, }, - [Authority.CHIP_SPA]: { + [Authority["1915c"]]: { cms: async ( - variables: any & - CommonEmailVariables & { emails: EmailAddresses } & { - emails: EmailAddresses; - }, + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }, ) => { - const relatedEvent = await getLatestMatchingEvent(variables.id, Action.RESPOND_TO_RAI); - return { - to: variables.emails.chipInbox, - cc: [...variables.emails.cpocEmail, ...variables.emails.srtEmails], - subject: `Withdraw Formal RAI Response for CHIP SPA Package ${variables.id}`, - body: await render(), - }; + try { + const relatedEvent = await getWithdrawRaiEvent(variables.id); + if (!relatedEvent) { + throw new EmailProcessingError( + `Failed to find original RAI response event for withdrawal (ID: ${variables.id})`, + { + id: variables.id, + actionType: "RespondToRai", + emailType: "withdrawRai", + severity: "ERROR", + }, + ); + } + return { + to: [ + ...variables.emails.osgEmail, + ...variables.emails.dhcbsooEmail, + ...variables.emails.cpocEmail, + ...variables.emails.srtEmails, + ], + subject: `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`, + body: await render( + , + ), + }; + } catch (error) { + console.error(error); + throw error; + } }, - state: async (variables: any & CommonEmailVariables & { emails: EmailAddresses }) => { - const relatedEvent = await getLatestMatchingEvent(variables.id, Action.RESPOND_TO_RAI); - return { - to: [`${variables.submitterName} <${variables.submitterEmail}>`], - subject: `Withdraw Formal RAI Response for CHIP SPA Package ${variables.id}`, - body: await render(), - }; - }, - }, - [Authority["1915b"]]: { - cms: async (variables: any & CommonEmailVariables & { emails: EmailAddresses }) => { - const relatedEvent = await getLatestMatchingEvent(variables.id, Action.RESPOND_TO_RAI); - return { - to: [ - ...variables.emails.dmcoEmail, - ...variables.emails.osgEmail, - ...variables.emails.cpocEmail, - ...variables.emails.srtEmails, - ], - subject: `Withdraw Formal RAI Response for Waiver Package ${variables.id} `, - body: await render( - , - ), - }; - }, - state: async (variables: any & CommonEmailVariables & { emails: EmailAddresses }) => { - const relatedEvent = await getLatestMatchingEvent(variables.id, Action.RESPOND_TO_RAI); - return { - to: [`${variables.submitterName} <${variables.submitterEmail}>`], // TODO: change to ALL state users - cc: variables.allStateUsersEmails, - subject: `Withdraw Formal RAI Response for Waiver Package ${variables.id}`, - body: await render( - , - ), - }; - }, - }, - [Authority["1915c"]]: { - cms: async (variables: any & CommonEmailVariables & { emails: EmailAddresses }) => { - const relatedEvent = await getLatestMatchingEvent(variables.id, Action.RESPOND_TO_RAI); - return { - to: [ - ...variables.emails.osgEmail, - ...variables.emails.dhcbsooEmail, - ...variables.emails.cpocEmail, - ...variables.emails.srtEmails, - ], - subject: `Withdraw Formal RAI Response for Waiver Package ${variables.id} `, - body: await render(), - }; + state: async ( + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }, + ) => { + try { + const relatedEvent = await getWithdrawRaiEvent(variables.id); + if (!relatedEvent) { + throw new EmailProcessingError( + `Failed to find original RAI response event for withdrawal (ID: ${variables.id})`, + { + id: variables.id, + actionType: "RespondToRai", + emailType: "withdrawRai", + severity: "ERROR", + }, + ); + } + return { + to: variables.allStateUsersEmails || [], + subject: `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`, + body: await render( + , + ), + }; + } catch (error) { + console.error(error); + throw error; + } }, }, }; diff --git a/lib/libs/email/errors.ts b/lib/libs/email/errors.ts new file mode 100644 index 0000000000..043b0332ec --- /dev/null +++ b/lib/libs/email/errors.ts @@ -0,0 +1,14 @@ +export class EmailProcessingError extends Error { + constructor( + message: string, + public readonly context: { + id: string; + actionType: string; + emailType: string; + [key: string]: any; + }, + ) { + super(message); + this.name = "EmailProcessingError"; + } +} diff --git a/lib/libs/email/index.ts b/lib/libs/email/index.ts index a7e0534ba5..27f9002b3d 100644 --- a/lib/libs/email/index.ts +++ b/lib/libs/email/index.ts @@ -1,6 +1,7 @@ import { Authority } from "shared-types"; import { getPackageChangelog } from "../api/package"; import * as EmailContent from "./content"; +import { changelog } from "shared-types/opensearch"; export type UserType = "cms" | "state"; @@ -27,6 +28,15 @@ export type EmailTemplates = { "withdraw-rai": AuthoritiesWithUserTypesTemplate; "contracting-initial": AuthoritiesWithUserTypesTemplate; "capitated-initial": AuthoritiesWithUserTypesTemplate; + "contracting-amendment": AuthoritiesWithUserTypesTemplate; + "capitated-amendment": AuthoritiesWithUserTypesTemplate; + "contracting-renewal": AuthoritiesWithUserTypesTemplate; + "capitated-renewal": AuthoritiesWithUserTypesTemplate; + "contracting-amendment-state": AuthoritiesWithUserTypesTemplate; + "capitated-amendment-state": AuthoritiesWithUserTypesTemplate; + "contracting-renewal-state": AuthoritiesWithUserTypesTemplate; + "capitated-renewal-state": AuthoritiesWithUserTypesTemplate; + "respond-to-rai": AuthoritiesWithUserTypesTemplate; }; // Create a type-safe mapping of email templates @@ -38,6 +48,15 @@ const emailTemplates: EmailTemplates = { "withdraw-rai": EmailContent.withdrawRai, "contracting-initial": EmailContent.newSubmission, "capitated-initial": EmailContent.newSubmission, + "contracting-amendment": EmailContent.newSubmission, + "capitated-amendment": EmailContent.newSubmission, + "contracting-renewal": EmailContent.newSubmission, + "capitated-renewal": EmailContent.newSubmission, + "contracting-amendment-state": EmailContent.newSubmission, + "capitated-amendment-state": EmailContent.newSubmission, + "contracting-renewal-state": EmailContent.newSubmission, + "capitated-renewal-state": EmailContent.newSubmission, + "respond-to-rai": EmailContent.respondToRai, }; // Create a type-safe lookup function @@ -61,7 +80,7 @@ export async function getEmailTemplates( action: keyof EmailTemplates, authority: Authority, ): Promise[] | null> { - const template = getEmailTemplate(action); + const template = getEmailTemplate(action || "new-medicaid-submission"); if (!template) { console.log("No template found"); return null; @@ -82,15 +101,42 @@ export async function getEmailTemplates( } // I think this needs to be written to handle not finding any matching events and so forth -export async function getLatestMatchingEvent(id: string, actionType: string) { +export async function getLatestMatchingEvent(id: string, actionType: string): Promise { try { const item = await getPackageChangelog(id); - const events = item.hits.hits.filter((hit: any) => hit._source.actionType === actionType); - events.sort((a: any, b: any) => b._source.timestamp - a._source.timestamp); - const latestMatchingEvent = events[0]._source; + + // Check if item exists and has hits + if (!item?.hits?.hits?.length) { + console.log(`No changelog found for package ${id}`); + return null; + } + + // Filter matching events + const events = item.hits.hits.filter((event) => event._source.actionType === actionType); + + // Check if any matching events were found + if (!events.length) { + console.log(`No events found with for package ${id}`); + return null; + } + + // Sort events by timestamp (most recent first) + events.sort((a, b) => { + const timestampA = a._source?.timestamp ?? 0; + const timestampB = b._source?.timestamp ?? 0; + return timestampB - timestampA; + }); + + // Get the latest event + const latestMatchingEvent = events[0]?._source; + if (!latestMatchingEvent) { + console.log(`Latest event for ${id} with has no source data`); + return null; + } + return latestMatchingEvent; } catch (error) { - console.error({ error }) + console.error("Error getting latest matching event:", { id, error }); return null; } } diff --git a/lib/libs/email/mock-data/respond-to-rai.ts b/lib/libs/email/mock-data/respond-to-rai.ts index ea185f975e..af5211bed7 100644 --- a/lib/libs/email/mock-data/respond-to-rai.ts +++ b/lib/libs/email/mock-data/respond-to-rai.ts @@ -1,4 +1,5 @@ export const emailTemplateValue = { + event: "respond-to-rai" as const, applicationEndpointUrl: "https://mako-dev.cms.gov/", get timestamp() { return Date.now(); diff --git a/lib/libs/email/mock-data/withdraw-rai.ts b/lib/libs/email/mock-data/withdraw-rai.ts index ce53bb2ea7..fe6ee50a33 100644 --- a/lib/libs/email/mock-data/withdraw-rai.ts +++ b/lib/libs/email/mock-data/withdraw-rai.ts @@ -1,16 +1,10 @@ -export const emailTemplateValue = { - territory: "CO", - applicationEndpointUrl: "https://mako-dev.cms.gov/", - actionType: "Withdrawal", - origin: "mako", - get requestedDate() { - return Date.now() - 5 * 24 * 60 * 60; - }, - get withdrawnDate() { - return Date.now(); - }, +import { Events } from "lib/packages/shared-types/events"; + +export const emailTemplateValue: Omit = { + event: "withdraw-rai" as const, + origin: "mako" as const, attachments: { - cmsForm179: { + supportingDocumentation: { files: [ { filename: "withdraw-documentation.pdf", @@ -22,18 +16,7 @@ export const emailTemplateValue = { ], label: "CMS Form 179", }, - spaPages: { - files: [ - { - filename: "Addditional Information.pdf", - title: "Addditional Information", - bucket: "mako-outbox-attachments-635052997545", - key: "f581c0ec-cbb2-4875-a384-86c06136f4c4.pdf", - uploadDate: 1728493784252, - }, - ], - label: "SPA Pages", - }, + }, additionalInformation: "This some additional information about the request to withdraw and what makes it important.", diff --git a/lib/libs/email/preview/Initial Submissions/CMS/InitialSubmissionCMS.test.tsx b/lib/libs/email/preview/Initial Submissions/CMS/InitialSubmissionCMS.test.tsx index 499b4976c4..fb7e15d576 100644 --- a/lib/libs/email/preview/Initial Submissions/CMS/InitialSubmissionCMS.test.tsx +++ b/lib/libs/email/preview/Initial Submissions/CMS/InitialSubmissionCMS.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect } from "vitest"; import { render } from "@testing-library/react"; -import AppKCMSEmailPreview from "./AppK"; +import AppKCMSEmailPreview from "./__snapshots__/AppK"; import ChipSpaCMSEmailPreview from "./CHIP_SPA"; import Medicaid_SPA from "./Medicaid_SPA"; import TempExtCMSPreview from "./Temp_Extension"; diff --git a/lib/libs/email/preview/Initial Submissions/CMS/AppK.tsx b/lib/libs/email/preview/Initial Submissions/CMS/__snapshots__/AppK.tsx similarity index 67% rename from lib/libs/email/preview/Initial Submissions/CMS/AppK.tsx rename to lib/libs/email/preview/Initial Submissions/CMS/__snapshots__/AppK.tsx index 2b25f8f683..617696b365 100644 --- a/lib/libs/email/preview/Initial Submissions/CMS/AppK.tsx +++ b/lib/libs/email/preview/Initial Submissions/CMS/__snapshots__/AppK.tsx @@ -1,6 +1,6 @@ -import { AppKCMSEmail } from "../../../content/new-submission/emailTemplates"; -import { emailTemplateValue } from "../../../mock-data/new-submission"; -import * as attachments from "../../../mock-data/attachments"; +import { AppKCMSEmail } from "../../../../content/new-submission/emailTemplates"; +import { emailTemplateValue } from "../../../../mock-data/new-submission"; +import * as attachments from "../../../../mock-data/attachments"; const AppKCMSEmailPreview = () => { return ( ( - -); diff --git a/lib/libs/email/preview/Withdraw Rai/CMS/CHIP_SPA.tsx b/lib/libs/email/preview/Withdraw Rai/CMS/CHIP_SPA.tsx deleted file mode 100644 index 02e91163ba..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/CMS/CHIP_SPA.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { ChipSpaCMSEmail } from "libs/email/content/withdrawRai/emailTemplates"; -import { emailTemplateValue } from "libs/email/mock-data/withdraw-rai"; -import { relatedEvent } from "./AppK"; -import * as attachments from "../../../mock-data/attachments"; - -export default () => { - return ( - - ); -}; diff --git a/lib/libs/email/preview/Withdraw Rai/CMS/Medicaid_SPA.tsx b/lib/libs/email/preview/Withdraw Rai/CMS/Medicaid_SPA.tsx deleted file mode 100644 index d4211183e9..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/CMS/Medicaid_SPA.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { MedSpaCMSEmail } from "libs/email/content/withdrawRai/emailTemplates"; -import { emailTemplateValue } from "libs/email/mock-data/withdraw-rai"; -import { relatedEvent } from "./AppK"; -import * as attachments from "../../../mock-data/attachments"; - -export default () => { - return ( - - ); -}; diff --git a/lib/libs/email/preview/Withdraw Rai/CMS/Waiver_Contracting.tsx b/lib/libs/email/preview/Withdraw Rai/CMS/Waiver_Contracting.tsx deleted file mode 100644 index 7194a7c24f..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/CMS/Waiver_Contracting.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Waiver1915bCMSEmail } from "libs/email/content/withdrawRai/emailTemplates"; -import { emailTemplateValue } from "libs/email/mock-data/withdraw-rai"; -import { relatedEvent } from "./AppK"; -import * as attachments from "../../../mock-data/attachments"; - -export default () => { - return ( - - ); -}; diff --git a/lib/libs/email/preview/Withdraw Rai/CMS/WithdrwRaiCMS.test.tsx b/lib/libs/email/preview/Withdraw Rai/CMS/WithdrwRaiCMS.test.tsx deleted file mode 100644 index 4851005c7b..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/CMS/WithdrwRaiCMS.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { render } from "@testing-library/react"; -import Appk from "./AppK"; -import CHIP_SPA from "./CHIP_SPA"; -import Medicaid_SPA from "./Medicaid_SPA"; -import Waiver_Contracting from "./Waiver_Contracting"; - -describe("Withdraw RAI CMS Email Snapshot Test", () => { - it("renders a Appk Preview Template", () => { - const template = render(); - - expect(template).toMatchSnapshot(); - }); - it("renders a ChipSPA Preview Template", () => { - const template = render(); - - expect(template).toMatchSnapshot(); - }); - it("renders a Medicaid_SPA Preview Template", () => { - const template = render(); - - expect(template).toMatchSnapshot(); - }); - it("renders a Waiver Capitated Preview Template", () => { - const template = render(); - - expect(template).toMatchSnapshot(); - }); -}); diff --git a/lib/libs/email/preview/Withdraw Rai/CMS/__snapshots__/WithdrwRaiCMS.test.tsx.snap b/lib/libs/email/preview/Withdraw Rai/CMS/__snapshots__/WithdrwRaiCMS.test.tsx.snap deleted file mode 100644 index 36f5515517..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/CMS/__snapshots__/WithdrwRaiCMS.test.tsx.snap +++ /dev/null @@ -1,6289 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Withdraw RAI CMS Email Snapshot Test > renders a Appk Preview Template 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- Files: -

- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Budget Documents - : -

- -
-
-

- - fy2024-budget.xlsx - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-oct-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-nov-2024.pdf - -

-
- - - - - - - -
- - -

- Tribal Consultation - : -

- -
-
-

- - tribal-consultation-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
- , - "container":
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- Files: -

- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Budget Documents - : -

- -
-
-

- - fy2024-budget.xlsx - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-oct-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-nov-2024.pdf - -

-
- - - - - - - -
- - -

- Tribal Consultation - : -

- -
-
-

- - tribal-consultation-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`Withdraw RAI CMS Email Snapshot Test > renders a ChipSPA Preview Template 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- Files: -

- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Budget Documents - : -

- -
-
-

- - fy2024-budget.xlsx - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-oct-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-nov-2024.pdf - -

-
- - - - - - - -
- - -

- Tribal Consultation - : -

- -
-
-

- - tribal-consultation-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- CHIP SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
- , - "container":
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- CHIP SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`Withdraw RAI CMS Email Snapshot Test > renders a Medicaid_SPA Preview Template 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- Files: -

- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Budget Documents - : -

- -
-
-

- - fy2024-budget.xlsx - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-oct-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-nov-2024.pdf - -

-
- - - - - - - -
- - -

- Tribal Consultation - : -

- -
-
-

- - tribal-consultation-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- CHIP SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
- , - "container":
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`Withdraw RAI CMS Email Snapshot Test > renders a Waiver Capitated Preview Template 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- Files: -

- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Budget Documents - : -

- -
-
-

- - fy2024-budget.xlsx - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-oct-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-nov-2024.pdf - -

-
- - - - - - - -
- - -

- Tribal Consultation - : -

- -
-
-

- - tribal-consultation-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- CHIP SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
- , - "container":
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
- -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/lib/libs/email/preview/Withdraw Rai/State/AppK.tsx b/lib/libs/email/preview/Withdraw Rai/State/AppK.tsx deleted file mode 100644 index 10c87465d3..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/State/AppK.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { AppKStateEmail } from "libs/email/content/withdrawRai/emailTemplates"; -import { emailTemplateValue } from "libs/email/mock-data/withdraw-rai"; -import * as attachments from "../../../mock-data/attachments"; - -export const relatedEvent = { - submitterName: "George", - submitterEmail: "test@email.com", -}; - -export default () => ( - -); diff --git a/lib/libs/email/preview/Withdraw Rai/State/CHIP_SPA.tsx b/lib/libs/email/preview/Withdraw Rai/State/CHIP_SPA.tsx deleted file mode 100644 index 38d6ac5b98..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/State/CHIP_SPA.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { ChipSpaStateEmail } from "libs/email/content/withdrawRai/emailTemplates"; -import { emailTemplateValue } from "libs/email/mock-data/withdraw-rai"; -import { relatedEvent } from "./AppK"; -import * as attachments from "../../../mock-data/attachments"; - -export default () => { - return ( - - ); -}; diff --git a/lib/libs/email/preview/Withdraw Rai/State/Medicaid_SPA.tsx b/lib/libs/email/preview/Withdraw Rai/State/Medicaid_SPA.tsx deleted file mode 100644 index a1950b3677..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/State/Medicaid_SPA.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { MedSpaStateEmail } from "libs/email/content/withdrawRai/emailTemplates"; -import { emailTemplateValue } from "libs/email/mock-data/withdraw-rai"; -import { relatedEvent } from "./AppK"; -import * as attachments from "../../../mock-data/attachments"; - -export default () => { - return ( - - ); -}; diff --git a/lib/libs/email/preview/Withdraw Rai/State/Waiver_Contracting.tsx b/lib/libs/email/preview/Withdraw Rai/State/Waiver_Contracting.tsx deleted file mode 100644 index 91900a1010..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/State/Waiver_Contracting.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Waiver1915bStateEmail } from "libs/email/content/withdrawRai/emailTemplates"; -import { emailTemplateValue } from "libs/email/mock-data/withdraw-rai"; -import { relatedEvent } from "./AppK"; -import * as attachments from "../../../mock-data/attachments"; - -export default () => { - return ( - - ); -}; diff --git a/lib/libs/email/preview/Withdraw Rai/State/WithdrawRaiState.test.tsx b/lib/libs/email/preview/Withdraw Rai/State/WithdrawRaiState.test.tsx deleted file mode 100644 index c160f70844..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/State/WithdrawRaiState.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { render } from "@testing-library/react"; -import Appk from "./AppK"; -import CHIP_SPA from "./CHIP_SPA"; -import Medicaid_SPA from "./Medicaid_SPA"; -import Waiver_Contracting from "./Waiver_Contracting"; - -describe("Withdraw RAI State Email Snapshot Test", () => { - it("renders a Appk Preview Template", () => { - const template = render(); - - expect(template).toMatchSnapshot(); - }); - it("renders a ChipSPA Preview Template", () => { - const template = render(); - - expect(template).toMatchSnapshot(); - }); - it("renders a Medicaid_SPA Preview Template", () => { - const template = render(); - - expect(template).toMatchSnapshot(); - }); - it("renders a Waiver Capitated Preview Template", () => { - const template = render(); - - expect(template).toMatchSnapshot(); - }); -}); diff --git a/lib/libs/email/preview/Withdraw Rai/State/__snapshots__/WithdrawRaiState.test.tsx.snap b/lib/libs/email/preview/Withdraw Rai/State/__snapshots__/WithdrawRaiState.test.tsx.snap deleted file mode 100644 index db237f0d08..0000000000 --- a/lib/libs/email/preview/Withdraw Rai/State/__snapshots__/WithdrawRaiState.test.tsx.snap +++ /dev/null @@ -1,5564 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Withdraw RAI State Email Snapshot Test > renders a Appk Preview Template 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- This mailbox is for the submittal of Section 1915(b) and 1915(c) Waivers, responses to Requests for Additional Information (RAI) on Waivers, and extension requests on Waivers only. - Any other correspondence will be disregarded. -

-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
- , - "container":
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- This mailbox is for the submittal of Section 1915(b) and 1915(c) Waivers, responses to Requests for Additional Information (RAI) on Waivers, and extension requests on Waivers only. - Any other correspondence will be disregarded. -

-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`Withdraw RAI State Email Snapshot Test > renders a ChipSPA Preview Template 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- This mailbox is for the submittal of Section 1915(b) and 1915(c) Waivers, responses to Requests for Additional Information (RAI) on Waivers, and extension requests on Waivers only. - Any other correspondence will be disregarded. -

-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- CHIP SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
- - - - - - -
-

- If you have any questions, please contact - - - CHIPSPASubmissionMailBox@cms.hhs.gov - - or your state lead. -

-
-
- -
- , - "container":
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- CHIP SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
- - - - - - -
-

- If you have any questions, please contact - - - CHIPSPASubmissionMailBox@cms.hhs.gov - - or your state lead. -

-
-
- -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`Withdraw RAI State Email Snapshot Test > renders a Medicaid_SPA Preview Template 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- This mailbox is for the submittal of Section 1915(b) and 1915(c) Waivers, responses to Requests for Additional Information (RAI) on Waivers, and extension requests on Waivers only. - Any other correspondence will be disregarded. -

-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- CHIP SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
- - - - - - -
-

- If you have any questions, please contact - - - CHIPSPASubmissionMailBox@cms.hhs.gov - - or your state lead. -

-
-
- -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Medicaid SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-
- -
- , - "container":
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Medicaid SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-
- -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`Withdraw RAI State Email Snapshot Test > renders a Waiver Capitated Preview Template 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - - - -
- Withdraw Formal RAI Response for Waiver Package -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for CO-1234.R21.00 was withdrawn by George Harrison george@example.com. -

- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-1234.R21.00 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- This mailbox is for the submittal of Section 1915(b) and 1915(c) Waivers, responses to Requests for Additional Information (RAI) on Waivers, and extension requests on Waivers only. - Any other correspondence will be disregarded. -

-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-

- Thank you. -

-
- - - - - - -
- - - - - - -
-

- U.S. Centers for Medicare & Medicaid Services -

-

- © - 2023 - | 7500 Security Boulevard, Baltimore, MD 21244 -

-
-
-
- - -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- CHIP SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
- - - - - - -
-

- If you have any questions, please contact - - - CHIPSPASubmissionMailBox@cms.hhs.gov - - or your state lead. -

-
-
- -
-
- - - - - - - -
- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Medicaid SPA Package ID - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-
- -
-
- - - - - -
- Waiver CO-24-1234 Withdrawn -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- This response confirms you have withdrawn a Waiver from CMS for review -

- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- Files: -

- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Budget Documents - : -

- -
-
-

- - fy2024-budget.xlsx - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-oct-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-nov-2024.pdf - -

-
- - - - - - - -
- - -

- Tribal Consultation - : -

- -
-
-

- - tribal-consultation-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
-

- This mailbox is for the submittal of Section 1915(b) and 1915(c) Waivers, responses to Requests for Additional Information (RAI) on Waivers, and extension requests on Waivers only. - Any other correspondence will be disregarded. -

-

- Thank you. -

-
- - - - - - -
-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-
-
- - -
- , - "container":
- - - - - -
- Waiver CO-24-1234 Withdrawn -
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ -
-
- - - - - - - -
- - - - - - -
- - OneMAC Logo - -
-
-

- This response confirms you have withdrawn a Waiver from CMS for review -

- - - - - - -
-

- The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for - CO-24-1234 - was withdrawn by - George Harrison - - george@example.com - . -

-
- - - - - - -
- - - - - - - -
-

- State or Territory - : -

-
-

- CO -

-
- - - - - - - -
-

- Name - : -

-
-

- George -

-
- - - - - - - -
-

- Email Address - : -

-
-

- test@email.com -

-
- - - - - - - -
-

- Waiver Number - : -

-
-

- CO-24-1234 -

-
- - - -
-

-

- Summary: -

-

-

- This some additional information about the request to withdraw and what makes it important. -

- - -
-
-
-

- Files: -

- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
- - - - - - - -
- - -

- Budget Documents - : -

- -
-
-

- - fy2024-budget.xlsx - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-oct-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Public Notice - : -

- -
-
-

- - public-notice-nov-2024.pdf - -

-
- - - - - - - -
- - -

- Tribal Consultation - : -

- -
-
-

- - tribal-consultation-sept-2024.pdf - -

-
- - - - - - - -
- - -

- Other - : -

- -
-
-

- - misc-documents.pdf - -

-
-

- This mailbox is for the submittal of Section 1915(b) and 1915(c) Waivers, responses to Requests for Additional Information (RAI) on Waivers, and extension requests on Waivers only. - Any other correspondence will be disregarded. -

-

- Thank you. -

-
- - - - - - -
-
- - - - - - -
-

- If you have any questions or did not expect this email, please contact - - spa@cms.hhs.gov - - or your state lead. -

-
-
-
- - -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/lib/libs/email/types.ts b/lib/libs/email/types.ts index e3d1fc5807..011dd7f514 100644 --- a/lib/libs/email/types.ts +++ b/lib/libs/email/types.ts @@ -1,4 +1,4 @@ -import { Authority, CommonEmailVariables, EmailAddresses, Events, RaiWithdraw, WithdrawPackage } from "shared-types"; +import { Authority, CommonEmailVariables, EmailAddresses, Events } from "shared-types"; // Base email template props export interface BaseEmailProps { @@ -32,20 +32,10 @@ export interface UserTypeTemplate { // Template types for different authorities export type AuthorityTemplate = Record; -// Event specific template props -export interface RaiWithdrawTemplateProps { - variables: RaiWithdraw & CommonEmailVariables; - relatedEvent: any; -} - export interface NewSubmissionTemplateProps { variables: Events[T] & CommonEmailVariables; } -export interface WithdrawPackageTemplateProps { - variables: WithdrawPackage & CommonEmailVariables; -} - export interface TempExtensionTemplateProps { variables: Events["TempExtension"] & CommonEmailVariables; } diff --git a/lib/packages/shared-types/events/index.ts b/lib/packages/shared-types/events/index.ts index d105594595..6a61d3a879 100644 --- a/lib/packages/shared-types/events/index.ts +++ b/lib/packages/shared-types/events/index.ts @@ -58,4 +58,6 @@ export type Events = { RespondToRai: z.infer; UploadSubsequentDocuments: z.infer; WithdrawPackage: z.infer; + WithdrawRai: z.infer; + ToggleWithdrawRai: z.infer; }; From 048fbc8c40b0660f47034a0fb52618df94429a15 Mon Sep 17 00:00:00 2001 From: Thomas Walker Date: Tue, 14 Jan 2025 07:38:50 -0500 Subject: [PATCH 02/17] feat(test) topics-lib test (#1013) * feat(test) topics-lib test * update to configresourcetypes * Update vitest descriptions --- lib/libs/topics-lib.test.ts | 41 ++++++++++++++++ lib/vitest.setup.ts | 8 +++- mocks/helpers/kafka-test-helpers.ts | 72 +++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 lib/libs/topics-lib.test.ts diff --git a/lib/libs/topics-lib.test.ts b/lib/libs/topics-lib.test.ts new file mode 100644 index 0000000000..fda60f3087 --- /dev/null +++ b/lib/libs/topics-lib.test.ts @@ -0,0 +1,41 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { createTopics, deleteTopics } from "./topics-lib"; +import { mockedAdmin, TOPIC_ONE, TOPIC_THREE } from "mocks"; + +describe("topics-lib test", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("Creates and modifies existing topics", async () => { + const spyCreate = vi.spyOn(mockedAdmin, "createTopics"); + const spyUpdate = vi.spyOn(mockedAdmin, "createPartitions"); + await createTopics("", [ + { + topic: TOPIC_ONE, + numPartitions: 2, + }, + { + topic: TOPIC_THREE, + numPartitions: 2, + }, + { + topic: "topic-4--part1--section1--div3", + numPartitions: 1, + }, + ]); + + expect(spyCreate).toBeCalledTimes(1); + expect(spyUpdate).toBeCalledTimes(1); + }); + it("tries to delete existing topics fails for bad filename", async () => { + const spyDelete = vi.spyOn(mockedAdmin, "deleteTopics"); + await deleteTopics("", [TOPIC_ONE]); + expect(spyDelete).toBeCalledTimes(1); + }); + it("deletes existing topics fails for bad filename", async () => { + await expect(deleteTopics("", ["topic1"])).rejects.toThrowError( + "ERROR: The deleteTopics function only operates against topics that match /.*--.*--.*--.*/g", + ); + }); +}); diff --git a/lib/vitest.setup.ts b/lib/vitest.setup.ts index 3e3c11585b..b53c0282fe 100644 --- a/lib/vitest.setup.ts +++ b/lib/vitest.setup.ts @@ -18,18 +18,22 @@ import { setDefaultStateSubmitter, mockedKafka, } from "mocks"; +import { ConfigResourceTypes, Kafka } from "kafkajs"; import { mockedServiceServer as mockedServer } from "mocks/server"; import { Amplify } from "aws-amplify"; - +type CreateType = T & { default: T }; Amplify.configure({ API: API_CONFIG, Auth: AUTH_CONFIG, }); vi.mock("kafkajs", async (importOriginal) => { + const original = await importOriginal>(); + const configOriginal = await importOriginal>(); return { - ...(await importOriginal()), + ...original, Kafka: mockedKafka, + ConfigResourceTypes: configOriginal, }; }); diff --git a/mocks/helpers/kafka-test-helpers.ts b/mocks/helpers/kafka-test-helpers.ts index 368a7c076c..1423b74be5 100644 --- a/mocks/helpers/kafka-test-helpers.ts +++ b/mocks/helpers/kafka-test-helpers.ts @@ -6,12 +6,84 @@ export const mockedProducer = { disconnect: vi.fn(), }; +export const TOPIC_ONE = "topic-1--part1--section1--div3"; +export const TOPIC_TWO = "topic-2--part1--section1--div3"; +export const TOPIC_THREE = "topic-3--part1--section1--div3"; +const topics = [TOPIC_ONE, TOPIC_TWO, TOPIC_THREE]; + +const topicMetaData = { + topics: [ + { + name: TOPIC_ONE, + partitions: [ + { + partitionErrorCode: 0, // default: 0 + partitionId: 1, + leader: 1, + replicas: [], + isr: [], + }, + ], + }, + { + name: TOPIC_THREE, + partitions: [ + { + partitionErrorCode: 0, // default: 0 + partitionId: 1, + leader: 1, + replicas: [], + isr: [], + }, + ], + }, + { + name: TOPIC_TWO, + partitions: [ + { + partitionErrorCode: 0, // default: 0 + partitionId: 1, + leader: 1, + replicas: [], + isr: [], + }, + ], + }, + ], +}; +const describeConfigs = { + resources: [ + { + configEntries: [ + { + configName: "cleanup.policy", + configValue: "delete", + isDefault: true, + configSource: 5, + isSensitive: false, + readOnly: false, + }, + ], + errorCode: 0, + errorMessage: null, + resourceName: "topic-name", + resourceType: 2, + }, + ], + throttleTime: 0, +}; export const mockedAdmin = { connect: vi.fn(), describeGroups: vi.fn().mockResolvedValue({ groups: [{ state: "Stable" }], }), + listTopics: vi.fn().mockReturnValue(topics), + createTopics: vi.fn(), + createPartitions: vi.fn(), + deleteTopics: vi.fn(), + fetchTopicMetadata: vi.fn().mockReturnValue(topicMetaData), fetchTopicOffsets: vi.fn().mockResolvedValue([{ offset: "100" }]), + describeConfigs: vi.fn().mockResolvedValue(describeConfigs), fetchOffsets: vi.fn().mockResolvedValue([{ partitions: [{ offset: "100" }] }]), disconnect: vi.fn(), }; From 85cce99686831abd5c2b0f4cc1b6c490e2662c0b Mon Sep 17 00:00:00 2001 From: Tiffany Forkner Date: Tue, 14 Jan 2025 09:23:04 -0500 Subject: [PATCH 03/17] adding tests to sinkMainProcessors (#1011) * added tests to sinkMainProcessors * added test to sinkChangelog --- lib/lambda/sinkBackup.ts | 54 - lib/lambda/sinkChangelog.test.ts | 594 +++++++++++ lib/lambda/sinkChangelog.ts | 8 +- lib/lambda/sinkCpocs.ts | 6 +- lib/lambda/sinkMain.test.ts | 21 +- lib/lambda/sinkMainProcessors.test.ts | 983 +++++++++++------- lib/lambda/sinkMainProcessors.ts | 2 +- .../shared-types/events/capitated-renewal.ts | 16 +- .../events/contracting-renewal.ts | 12 +- .../main/transforms/respond-to-rai.ts | 2 +- lib/packages/shared-types/statusHelper.ts | 4 +- .../shared-utils/s3-url-parser.test.ts | 14 +- lib/packages/shared-utils/s3-url-parser.ts | 12 +- lib/stacks/data.ts | 2 +- lib/vitest.setup.ts | 20 +- mocks/consts.ts | 4 +- mocks/data/items.ts | 2 + mocks/data/submit/attachments.ts | 61 +- mocks/data/submit/base.ts | 105 +- mocks/data/submit/changelog.ts | 193 ++++ mocks/handlers/opensearch/main.ts | 21 +- mocks/helpers/kafka-test-helpers.ts | 31 + mocks/index.d.ts | 2 + 23 files changed, 1597 insertions(+), 572 deletions(-) delete mode 100644 lib/lambda/sinkBackup.ts create mode 100644 lib/lambda/sinkChangelog.test.ts create mode 100644 mocks/data/submit/changelog.ts diff --git a/lib/lambda/sinkBackup.ts b/lib/lambda/sinkBackup.ts deleted file mode 100644 index 91ecf6b141..0000000000 --- a/lib/lambda/sinkBackup.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Handler } from "aws-lambda"; -import { KafkaEvent, KafkaRecord } from "shared-types"; -import { ErrorType, logError } from "../libs/sink-lib"; -import { sortBy } from "lodash"; -import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; -const client = new S3Client({ - maxAttempts: 3, -}); -const bucket = process.env.bucket; - -export const handler: Handler = async (event) => { - const loggableEvent = { ...event, records: "too large to display" }; - try { - for (const topicPartition of Object.keys(event.records)) { - const events: KafkaRecord[] = event.records[topicPartition]; - const orderedEvents = sortBy(events, "offset"); - let consecutiveEvents: KafkaRecord[] = []; - for (let i = 0; i < orderedEvents.length; i++) { - consecutiveEvents.push(orderedEvents[i]); - if ( - i == orderedEvents.length - 1 || - orderedEvents[i + 1].offset != orderedEvents[i].offset + 1 - ) { - const offsets = consecutiveEvents.map(function (event) { - return event.offset; - }); - const minOffset = Math.min(...offsets); - const maxOffset = Math.max(...offsets); - const key = `${topicPartition}/${minOffset}.json`; - console.log( - ` Sinking offsets ${minOffset} through ${maxOffset} to s3://${bucket}/${key}`, - ); - - try { - await client.send( - new PutObjectCommand({ - Bucket: bucket, - Key: key, - Body: Buffer.from(JSON.stringify(consecutiveEvents, null, 2)), - }), - ); - } catch (err) { - console.error("ERROR: Put Object Command failure", err); - } - consecutiveEvents = []; - } - } - console.log(topicPartition); - } - } catch (error) { - logError({ type: ErrorType.UNKNOWN, metadata: { event: loggableEvent } }); - throw error; - } -}; diff --git a/lib/lambda/sinkChangelog.test.ts b/lib/lambda/sinkChangelog.test.ts new file mode 100644 index 0000000000..0135b18dfb --- /dev/null +++ b/lib/lambda/sinkChangelog.test.ts @@ -0,0 +1,594 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { handler } from "./sinkChangelog"; +import { Context } from "aws-lambda"; +import * as os from "libs/opensearch-lib"; +import * as sink from "libs/sink-lib"; +import { + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, + WITHDRAWN_CHANGELOG_ITEM_ID as TEST_ITEM_ID, + convertObjToBase64, + createKafkaEvent, + createKafkaRecord, +} from "mocks"; +import items from "mocks/data/items"; +import { + appkBase, + capitatedInitial, + capitatedAmendmentBase, + capitatedRenewal, + contractingInitial, + contractingAmendment, + contractingRenewal, + newChipSubmission, + newMedicaidSubmission, + uploadSubsequentDocuments, + temporaryExtension, + respondToRai, + toggleWithdrawRai, + withdrawPackage, + withdrawRai, +} from "mocks/data/submit/changelog"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}changelog`; +const TOPIC = "aws.onemac.migration.cdc"; +const TEST_ITEM = items[TEST_ITEM_ID]; +const TEST_ITEM_KEY = Buffer.from(TEST_ITEM_ID).toString("base64"); +const TEST_ITEM_UPDATE_ID = "MD-0005.R01.01"; +const TEST_ITEM_UPDATE_KEY = Buffer.from(TEST_ITEM_UPDATE_ID).toString("base64"); +const TIMESTAMP = 1732645041557; + +describe("syncing Changelog events", () => { + const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); + const logErrorSpy = vi.spyOn(sink, "logError"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it.each([ + [ + "app-k", + appkBase, + { + actionType: "Amend", + }, + ], + [ + "capitated-initial", + capitatedInitial, + { + actionType: "Initial", + }, + ], + [ + "capitated-amend", + capitatedAmendmentBase, + { + actionType: "Amend", + }, + ], + [ + "capitated-renew", + capitatedRenewal, + { + actionType: "Renew", + }, + ], + [ + "contracting-initial", + contractingInitial, + { + actionType: "Initial", + }, + ], + [ + "contracting-amendment", + contractingAmendment, + { + actionType: "Amend", + }, + ], + [ + "contracting-renewal", + contractingRenewal, + { + actionType: "Renew", + }, + ], + [ + "new-chip-submission", + newChipSubmission, + { + actionType: "Amend", + }, + ], + ["new-medicaid-submission", newMedicaidSubmission, {}], + ["upload-subsequent-documents", uploadSubsequentDocuments, {}], + [ + "temporary-extension", + temporaryExtension, + { + actionType: "Extend", + }, + ], + ["respond-to-rai", respondToRai, {}], + ["withdraw-package", withdrawPackage, {}], + ["withdraw-rai", withdrawRai, {}], + ])("should handle valid changelog event for %s", async (_, log, expectations) => { + const event = createKafkaEvent({ + [`${TOPIC}-01`]: [ + createKafkaRecord({ + topic: `${TOPIC}-01`, + key: Buffer.from(log.id).toString("base64"), + value: convertObjToBase64({ + ...log, + packageId: log.id, + origin: "mako", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: TIMESTAMP, + }), + offset: 1, + }), + ], + }); + + await handler(event, {} as Context, vi.fn()); + const files: object[] = Object.values(log?.attachments).reduce((acc: object[], curr) => { + return acc.concat( + curr.files.map((file) => ({ + ...file, + title: curr.label, + })), + ); + }, []); + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...log, + ...expectations, + attachments: files, + id: `${log.id}-1`, + packageId: log.id, + origin: "mako", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: TIMESTAMP, + }, + ]); + }); + + it("should handle a valid admin id update", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-02`]: [ + createKafkaRecord({ + topic: `${TOPIC}-01`, + key: TEST_ITEM_UPDATE_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_UPDATE_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + changeMade: "update id of the change", + isAdminChange: true, + adminChangeType: "update-id", + idToBeUpdated: TEST_ITEM_ID, + timestamp: TIMESTAMP, + }), + offset: 3, + }), + ], + }); + + const changelogs = TEST_ITEM._source!.changelog!; + const expectedChangelogs = changelogs.map((log) => ({ + ...log?._source, + id: log?._id?.replace(TEST_ITEM_ID, TEST_ITEM_UPDATE_ID), + packageId: TEST_ITEM_UPDATE_ID, + })); + + await handler(event, {} as Context, vi.fn()); + expect(bulkUpdateDataSpy).toHaveBeenCalledWith( + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX, + expectedChangelogs, + ); + }); + + it("should handle a valid admin value update", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-02`]: [ + createKafkaRecord({ + topic: `${TOPIC}-02`, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + changeMade: "update additional information of the change", + isAdminChange: true, + adminChangeType: "update-values", + additionalInformation: "changed additional information", + timestamp: TIMESTAMP, + }), + offset: 3, + }), + ], + }); + + await handler(event, {} as Context, vi.fn()); + + const { attachments: _, ...changes } = TEST_ITEM?._source?.changelog?.[3]?._source || {}; + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...changes, + isAdminChange: true, + adminChangeType: "update-values", + changeMade: "update additional information of the change", + event: "update-values", + id: `${TEST_ITEM_ID}-3`, + packageId: TEST_ITEM_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + additionalInformation: "changed additional information", + timestamp: expect.any(Number), + }, + ]); + }); + + it("should handle a valid admin value and id update", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-02`]: [ + createKafkaRecord({ + topic: `${TOPIC}-02`, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + changeMade: "update additional information of the change", + isAdminChange: true, + adminChangeType: "update-values", + additionalInformation: "changed additional information", + timestamp: TIMESTAMP, + }), + offset: 3, + }), + createKafkaRecord({ + topic: `${TOPIC}-02`, + key: TEST_ITEM_UPDATE_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_UPDATE_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + changeMade: "update id of the change", + isAdminChange: true, + adminChangeType: "update-id", + idToBeUpdated: TEST_ITEM_ID, + timestamp: TIMESTAMP, + }), + offset: 3, + }), + ], + }); + + const changelogs = TEST_ITEM._source!.changelog!; + const expectedChangelogs = changelogs.map((log) => ({ + ...log?._source, + id: log?._id?.replace(TEST_ITEM_ID, TEST_ITEM_UPDATE_ID), + packageId: TEST_ITEM_UPDATE_ID, + })); + + await handler(event, {} as Context, vi.fn()); + + const { attachments: _, ...changes } = TEST_ITEM?._source?.changelog?.[3]?._source || {}; + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...changes, + isAdminChange: true, + adminChangeType: "update-values", + changeMade: "update additional information of the change", + event: "update-values", + id: `${TEST_ITEM_ID}-3`, + packageId: TEST_ITEM_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + additionalInformation: "changed additional information", + timestamp: expect.any(Number), + }, + { + ...changes, + isAdminChange: true, + adminChangeType: "update-values", + changeMade: "update additional information of the change", + event: "update-values", + id: `${TEST_ITEM_UPDATE_ID}-3`, + packageId: TEST_ITEM_UPDATE_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + additionalInformation: "changed additional information", + timestamp: expect.any(Number), + }, + ...expectedChangelogs, + ]); + }); + + it("should handle a valid admin toggle withdraw rai", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-01`]: [ + createKafkaRecord({ + topic: `${TOPIC}-01`, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + ...toggleWithdrawRai, + packageId: toggleWithdrawRai.id, + origin: "mako", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: TIMESTAMP, + }), + offset: 1, + }), + ], + }); + + await handler(event, {} as Context, vi.fn()); + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...toggleWithdrawRai, + id: `${toggleWithdrawRai.id}-1`, + packageId: toggleWithdrawRai.id, + origin: "mako", + isAdminChange: true, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: TIMESTAMP, + }, + ]); + }); + + it("should handle a valid admin delete", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-02`]: [ + createKafkaRecord({ + topic: `${TOPIC}-02`, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_ID, + isAdminChange: true, + adminChangeType: "delete", + deleted: false, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + }), + offset: 3, + }), + ], + }); + + await handler(event, {} as Context, vi.fn()); + + const { + attachments: _a, + additionalInformation: _ai, + ...changes + } = TEST_ITEM?._source?.changelog?.[3]?._source || {}; + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...changes, + isAdminChange: true, + adminChangeType: "delete", + event: "delete", + deleted: false, + id: `${TEST_ITEM_ID}-3`, + packageId: TEST_ITEM_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: expect.any(Number), + }, + ]); + }); + + it("should throw an error if the topic is undefined", async () => { + const event = createKafkaEvent({ + undefined: [ + createKafkaRecord({ + // @ts-expect-error ignore topic undefined + topic: undefined, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_ID, + isAdminChange: true, + adminChangeType: "delete", + deleted: false, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + }), + offset: 3, + }), + ], + }); + + await expect(() => handler(event, {} as Context, vi.fn())).rejects.toThrowError( + "topic (undefined) is invalid", + ); + + expect(logErrorSpy).toHaveBeenCalledWith({ + type: sink.ErrorType.BADTOPIC, + }); + + expect(logErrorSpy).toHaveBeenCalledWith({ + type: sink.ErrorType.UNKNOWN, + metadata: { + event: { + eventSource: "SelfManagedKafka", + bootstrapServers: "kafka", + records: "too large to display", + }, + }, + }); + }); + + it("should throw an error if the topic is invalid", async () => { + const event = createKafkaEvent({ + "invalid-topic": [ + createKafkaRecord({ + topic: "invalid-topic", + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_ID, + isAdminChange: true, + adminChangeType: "delete", + deleted: false, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + }), + offset: 3, + }), + ], + }); + + await expect(() => handler(event, {} as Context, vi.fn())).rejects.toThrowError( + "topic (invalid-topic) is invalid", + ); + + expect(logErrorSpy).toHaveBeenCalledWith({ + type: sink.ErrorType.BADTOPIC, + }); + + expect(logErrorSpy).toHaveBeenCalledWith({ + type: sink.ErrorType.UNKNOWN, + metadata: { + event: { + eventSource: "SelfManagedKafka", + bootstrapServers: "kafka", + records: "too large to display", + }, + }, + }); + }); + + it("should skip updates for legacy tombstone", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-02`]: [ + { + key: TEST_ITEM_KEY, + offset: 15, + // @ts-expect-error - value must be undefined for this test + value: undefined, + }, + ], + }); + await handler(event, {} as Context, vi.fn()); + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + }); + + it("should skip invalid admin id update", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-02`]: [ + createKafkaRecord({ + topic: `${TOPIC}-02`, + key: TEST_ITEM_UPDATE_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_UPDATE_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + changeMade: "update id of the change", + isAdminChange: true, + adminChangeType: "update-id", + timestamp: TIMESTAMP, + }), + offset: 3, + }), + ], + }); + + await handler(event, {} as Context, vi.fn()); + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should skip non-mako updates", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-01`]: [ + createKafkaRecord({ + topic: `${TOPIC}-01`, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_ID, + packageId: TEST_ITEM_ID, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: TIMESTAMP, + }), + offset: 1, + }), + ], + }); + + await handler(event, {} as Context, vi.fn()); + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should skip invalid update", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-01`]: [ + createKafkaRecord({ + topic: `${TOPIC}-01`, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + ...appkBase, + id: TEST_ITEM_ID, + packageId: TEST_ITEM_ID, + title: undefined, + origin: "mako", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: TIMESTAMP, + }), + offset: 1, + }), + ], + }); + + await handler(event, {} as Context, vi.fn()); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.VALIDATION, + }), + ); + }); + + it("should handle errors in update", async () => { + const event = createKafkaEvent({ + [`${TOPIC}-01`]: [ + createKafkaRecord({ + topic: `${TOPIC}-01`, + key: TEST_ITEM_KEY, + value: JSON.stringify({ + ...appkBase, + id: TEST_ITEM_ID, + packageId: TEST_ITEM_ID, + title: undefined, + origin: "mako", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: TIMESTAMP, + }), + offset: 1, + }), + ], + }); + + await handler(event, {} as Context, vi.fn()); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); +}); diff --git a/lib/lambda/sinkChangelog.ts b/lib/lambda/sinkChangelog.ts index 038c003afd..03f164b0db 100644 --- a/lib/lambda/sinkChangelog.ts +++ b/lib/lambda/sinkChangelog.ts @@ -18,9 +18,6 @@ export const handler: Handler = async (event) => { for (const topicPartition of Object.keys(event.records)) { const topic = getTopic(topicPartition); switch (topic) { - case undefined: - logError({ type: ErrorType.BADTOPIC }); - throw new Error(); case "aws.onemac.migration.cdc": // await legacyAdminChanges( // event.records[topicPartition], @@ -33,6 +30,9 @@ export const handler: Handler = async (event) => { topicPartition: topicPartition, }); break; + default: + logError({ type: ErrorType.BADTOPIC }); + throw new Error(`topic (${topicPartition}) is invalid`); } } } catch (error) { @@ -83,6 +83,7 @@ const processAndIndex = async ({ packageId: result.data.id, }); }); + const packageChangelogs = await getPackageChangelog(result.data.idToBeUpdated); packageChangelogs.hits.hits.forEach((log) => { @@ -124,7 +125,6 @@ const processAndIndex = async ({ }); continue; } - console.log(JSON.stringify(result.data, null, 2)); docs.push(result.data); } else { console.log(`No transform found for event: ${record.event}`); diff --git a/lib/lambda/sinkCpocs.ts b/lib/lambda/sinkCpocs.ts index cd6357dbfb..d9e9cb8908 100644 --- a/lib/lambda/sinkCpocs.ts +++ b/lib/lambda/sinkCpocs.ts @@ -10,12 +10,12 @@ export const handler: Handler = async (event) => { for (const topicPartition of Object.keys(event.records)) { const topic = getTopic(topicPartition); switch (topic) { - case undefined: - logError({ type: ErrorType.BADTOPIC }); - throw new Error(); case "aws.seatool.debezium.cdc.SEA.dbo.Officers": await officers(event.records[topicPartition], topicPartition); break; + default: + logError({ type: ErrorType.BADTOPIC }); + throw new Error(`topic (${topicPartition}) is invalid`); } } } catch (error) { diff --git a/lib/lambda/sinkMain.test.ts b/lib/lambda/sinkMain.test.ts index 54516cd6ff..ae5d843168 100644 --- a/lib/lambda/sinkMain.test.ts +++ b/lib/lambda/sinkMain.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect, vi, afterEach } from "vitest"; +import { describe, it, expect, vi } from "vitest"; import { handler } from "./sinkMain"; +import { Context } from "aws-lambda"; import * as sinkMainProcessors from "./sinkMainProcessors"; import { KafkaEvent } from "shared-types"; @@ -10,24 +11,12 @@ const createKafkaEvent = (records: KafkaEvent["records"]) => ({ }); describe("sinkMain handler", () => { - vi.stubEnv("osDomain", "os-domain"); - vi.stubEnv("indexNamespace", "index-namespace"); - - afterEach(() => { - vi.restoreAllMocks(); - vi.resetModules(); - }); - it("handles aws.onemac.migration.cdc topic successfully", async () => { const spiedOnProcessAndIndex = vi .spyOn(sinkMainProcessors, "insertOneMacRecordsFromKafkaIntoMako") .mockImplementation(vi.fn()); - await handler( - createKafkaEvent({ "aws.onemac.migration.cdc-0": [] }), - expect.anything(), - vi.fn(), - ); + await handler(createKafkaEvent({ "aws.onemac.migration.cdc-0": [] }), {} as Context, vi.fn()); expect(spiedOnProcessAndIndex).toBeCalledWith([], "aws.onemac.migration.cdc-0"); }); @@ -39,7 +28,7 @@ describe("sinkMain handler", () => { await handler( createKafkaEvent({ "aws.seatool.ksql.onemac.three.agg.State_Plan-0": [] }), - expect.anything(), + {} as Context, vi.fn(), ); @@ -56,7 +45,7 @@ describe("sinkMain handler", () => { await handler( createKafkaEvent({ "aws.seatool.debezium.changed_date.SEA.dbo.State_Plan-0": [] }), - expect.anything(), + {} as Context, vi.fn(), ); diff --git a/lib/lambda/sinkMainProcessors.test.ts b/lib/lambda/sinkMainProcessors.test.ts index c40744308a..2f0dae2530 100644 --- a/lib/lambda/sinkMainProcessors.test.ts +++ b/lib/lambda/sinkMainProcessors.test.ts @@ -1,213 +1,416 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi, afterEach } from "vitest"; import { insertNewSeatoolRecordsFromKafkaIntoMako, insertOneMacRecordsFromKafkaIntoMako, syncSeatoolRecordDatesFromKafkaWithMako, } from "./sinkMainProcessors"; -import * as sinkLib from "libs"; -import { Document, seatool } from "shared-types/opensearch/main"; +import { seatool } from "shared-types/opensearch/main"; import { offsetToUtc } from "shared-utils"; -import { KafkaRecord } from "shared-types"; - -const convertObjToBase64 = (obj: object) => Buffer.from(JSON.stringify(obj)).toString("base64"); - -const createKafkaRecord = ({ - topic, - key, - value, -}: { - topic: string; - key: string; - value: string; -}): KafkaRecord => ({ - topic, - partition: 0, - offset: 0, - timestamp: 1732645041557, - timestampType: "CREATE_TIME", - headers: {}, - key, - value, -}); +import { SEATOOL_STATUS, statusToDisplayToCmsUser, statusToDisplayToStateUser } from "shared-types"; +import * as sink from "libs/sink-lib"; +import * as os from "libs/opensearch-lib"; +import { + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, + TEST_ITEM_ID, + EXISTING_ITEM_TEMPORARY_EXTENSION_ID, + convertObjToBase64, + createKafkaRecord, +} from "mocks"; +import { + appkBase, + capitatedInitial, + capitatedAmendmentBase, + capitatedRenewal, + contractingInitial, + contractingAmendment, + contractingRenewal, + newChipSubmission, + newMedicaidSubmission, + uploadSubsequentDocuments, + temporaryExtension, + respondToRai, + toggleWithdrawRai, + withdrawPackage, + withdrawRai, +} from "mocks/data/submit/base"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}main`; +const TEST_ITEM_KEY = Buffer.from(TEST_ITEM_ID).toString("base64"); +const TIMESTAMP = 1732645041557; +const ISO_DATETIME = "2024-11-26T18:17:21.557Z"; +const EARLIER_TIMESTAMP = 1722645041557; +const EARLIER_ISO_DATETIME = "2024-08-03T00:30:41.557Z"; +const LATER_TIMESTAMP = 1742645041557; +const LATER_ISO_DATETIME = "2025-03-22T12:04:01.557Z"; + +const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); +const logErrorSpy = vi.spyOn(sink, "logError"); describe("insertOneMacRecordsFromKafkaIntoMako", () => { - const spiedOnBulkUpdateDataWrapper = vi.fn(); const TOPIC = "--mako--branch-name--aws.onemac.migration.cdc"; - beforeEach(() => { + afterEach(() => { vi.clearAllMocks(); - vi.useFakeTimers(); - - vi.spyOn(sinkLib, "bulkUpdateDataWrapper").mockImplementation(spiedOnBulkUpdateDataWrapper); - vi.stubEnv("osDomain", "osDomain"); - vi.stubEnv("indexNamespace", "indexNamespace"); }); - it("handles valid kafka records", () => { - insertOneMacRecordsFromKafkaIntoMako( + type BulkUpdateRequestBody = { + initialIntakeNeeded: boolean; + actionType?: string; + title?: string; + additionalInformation?: string; + originalWaiverNumber?: string; + cmsStatus?: string; + stateStatus?: string; + }; + + it.each([ + [ + "app-k", + appkBase, + SEATOOL_STATUS.PENDING, + { + title: appkBase.title, + proposedDate: appkBase.proposedEffectiveDate, + additionalInformation: appkBase.additionalInformation, + actionType: "Amend", + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "capitated-initial", + capitatedInitial, + SEATOOL_STATUS.PENDING, + { + proposedDate: capitatedInitial.proposedEffectiveDate, + additionalInformation: capitatedInitial.additionalInformation, + actionType: "Initial", + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "capitated-amendment", + capitatedAmendmentBase, + SEATOOL_STATUS.PENDING, + { + proposedDate: capitatedAmendmentBase.proposedEffectiveDate, + additionalInformation: capitatedAmendmentBase.additionalInformation, + actionType: "Amend", + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "capitated-renewal", + capitatedRenewal, + SEATOOL_STATUS.PENDING, + { + proposedDate: capitatedRenewal.proposedEffectiveDate, + additionalInformation: capitatedRenewal.additionalInformation, + actionType: "Renew", + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "contracting-initial", + contractingInitial, + SEATOOL_STATUS.PENDING, + { + proposedDate: contractingInitial.proposedEffectiveDate, + additionalInformation: contractingInitial.additionalInformation, + actionType: "Initial", + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "contracting-amendment", + contractingAmendment, + SEATOOL_STATUS.PENDING, + { + proposedDate: contractingAmendment.proposedEffectiveDate, + additionalInformation: contractingAmendment.additionalInformation, + actionType: "Amend", + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "contracting-renewal", + contractingRenewal, + SEATOOL_STATUS.PENDING, + { + proposedDate: contractingRenewal.proposedEffectiveDate, + additionalInformation: contractingRenewal.additionalInformation, + actionType: "Renew", + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "new-chip-submission", + newChipSubmission, + SEATOOL_STATUS.PENDING, + { + proposedDate: newChipSubmission.proposedEffectiveDate, + additionalInformation: newChipSubmission.additionalInformation, + actionType: "Amend", + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "new-medicaid-submission", + newMedicaidSubmission, + SEATOOL_STATUS.PENDING, + { + proposedDate: newMedicaidSubmission.proposedEffectiveDate, + additionalInformation: newMedicaidSubmission.additionalInformation, + initialIntakeNeeded: true, + } as BulkUpdateRequestBody, + ], + [ + "temporary-extension", + temporaryExtension, + SEATOOL_STATUS.PENDING, + { + originalWaiverNumber: temporaryExtension.waiverNumber, + additionalInformation: temporaryExtension.additionalInformation, + actionType: "Extend", + initialIntakeNeeded: false, + cmsStatus: "Requested", + stateStatus: "Submitted", + } as BulkUpdateRequestBody, + ], + ])("should handle valid kafka records for %s", async (_, event, seatoolStatus, expectation) => { + await insertOneMacRecordsFromKafkaIntoMako( [ createKafkaRecord({ topic: TOPIC, key: "TUQtMjQtMjMwMA==", value: convertObjToBase64({ - event: "new-medicaid-submission", - attachments: { - cmsForm179: { - files: [ - { - filename: "Screenshot 2024-07-08 at 11.42.35 AM.png", - title: "Screenshot 2024-07-08 at 11.42.35 AM", - bucket: "mako-refactor-tests-sink-attachments-635052997545", - key: "13513eea-ba62-4cba-af31-2ec3c160b5e1.png", - uploadDate: 1732645033529, - }, - ], - label: "CMS Form 179", - }, - spaPages: { - files: [ - { - filename: "Screenshot 2024-07-08 at 11.42.35 AM.png", - title: "Screenshot 2024-07-08 at 11.42.35 AM", - bucket: "mako-refactor-tests-sink-attachments-635052997545", - key: "bbdfa95f-f67c-4983-8517-2745cc08d3b6.png", - uploadDate: 1732645038805, - }, - ], - label: "SPA Pages", - }, - coverLetter: { label: "Cover Letter" }, - tribalEngagement: { label: "Document Demonstrating Good-Faith Tribal Engagement" }, - existingStatePlanPages: { label: "Existing State Plan Page(s)" }, - publicNotice: { label: "Public Notice" }, - sfq: { label: "Standard Funding Questions (SFQs)" }, - tribalConsultation: { label: "Tribal Consultation" }, - other: { label: "Other" }, - }, - authority: "Medicaid SPA", - proposedEffectiveDate: 1732597200000, - id: "MD-24-2300", + ...event, origin: "mako", submitterName: "George Harrison", submitterEmail: "george@example.com", - timestamp: 1732645041526, + timestamp: TIMESTAMP, }), }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith( + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...expectation, + id: event.id, + authority: event.authority, + seatoolStatus, + cmsStatus: expectation.cmsStatus || statusToDisplayToCmsUser[seatoolStatus], + stateStatus: expectation.stateStatus || statusToDisplayToStateUser[seatoolStatus], + changedDate: ISO_DATETIME, + makoChangedDate: ISO_DATETIME, + statusDate: offsetToUtc(new Date(TIMESTAMP)).toISOString(), + submissionDate: ISO_DATETIME, + state: "VA", + origin: "OneMAC", + raiWithdrawEnabled: false, + description: null, + subject: null, + submitterEmail: "george@example.com", + submitterName: "George Harrison", + }, + ]); + }); + + it.each([ + ["upload-subsequent-documents", uploadSubsequentDocuments, {}], + [ + "respond-to-rai", + respondToRai, + { + raiReceivedDate: ISO_DATETIME, + raiWithdrawEnabled: false, + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.PENDING_RAI], + stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.PENDING_RAI], + locked: true, + }, + ], + [ + "withdraw-rai", + withdrawRai, + { + seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.PENDING_RAI], + stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.PENDING_RAI], + raiWithdrawEnabled: false, + locked: true, + }, + ], + [ + "toggle-withdraw-rai", + toggleWithdrawRai, + { + raiWithdrawEnabled: true, + }, + ], + [ + "withdraw-package", + withdrawPackage, + { + seatoolStatus: SEATOOL_STATUS.WITHDRAWN, + cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.WITHDRAWN], + stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.WITHDRAWN], + locked: true, + finalDispositionDate: ISO_DATETIME, + initialIntakeNeeded: false, + raiWithdrawEnabled: false, + }, + ], + ])("should handle valid kafka records for %s", async (_, event, expectation) => { + await insertOneMacRecordsFromKafkaIntoMako( [ - { - additionalInformation: undefined, - authority: "Medicaid SPA", - changedDate: "2024-11-26T18:17:21.526Z", - cmsStatus: "Pending", - description: null, - id: "MD-24-2300", - makoChangedDate: "2024-11-26T18:17:21.526Z", - origin: "OneMAC", - raiWithdrawEnabled: false, - seatoolStatus: "Pending", - state: "MD", - stateStatus: "Under Review", - statusDate: offsetToUtc(new Date(1732645041526)).toISOString(), - proposedDate: 1732597200000, - subject: null, - submissionDate: "2024-11-26T18:17:21.526Z", - submitterEmail: "george@example.com", - submitterName: "George Harrison", - initialIntakeNeeded: true, - }, + createKafkaRecord({ + topic: TOPIC, + key: "TUQtMjQtMjMwMA==", + value: convertObjToBase64({ + ...event, + origin: "mako", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + timestamp: TIMESTAMP, + }), + }), ], - "main", + TOPIC, ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...expectation, + id: event.id, + makoChangedDate: ISO_DATETIME, + }, + ]); }); - it("handles valid kafka admin records", () => { - insertOneMacRecordsFromKafkaIntoMako( + it("handles valid kafka admin record to update id", async () => { + await insertOneMacRecordsFromKafkaIntoMako( [ createKafkaRecord({ topic: TOPIC, key: "TUQtMjQtMjMwMA==", value: convertObjToBase64({ id: "MD-24-2301", - submitterName: "George Harrison", - submitterEmail: "george@example.com", - changeMade: "ID has been updated.", isAdminChange: true, adminChangeType: "update-id", idToBeUpdated: "MD-24-2300", + changeMade: "ID has been updated.", + submitterName: "George Harrison", + submitterEmail: "george@example.com", }), }), + ], + TOPIC, + ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: "MD-24-2301", + isAdminChange: true, + adminChangeType: "update-id", + idToBeUpdated: "MD-24-2300", + changeMade: "ID has been updated.", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + }, + ]); + }); + + it("handles valid kafka admin record to update value", async () => { + await insertOneMacRecordsFromKafkaIntoMako( + [ createKafkaRecord({ topic: TOPIC, key: "TUQtMjQtMjMwMA==", value: convertObjToBase64({ id: "MD-24-2301", - submitterName: "George Harrison", - submitterEmail: "george@example.com", - changeMade: "title has been updated.", isAdminChange: true, adminChangeType: "update-values", title: "updated title", + changeMade: "title has been updated.", + submitterName: "George Harrison", + submitterEmail: "george@example.com", }), }), + ], + TOPIC, + ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: "MD-24-2301", + isAdminChange: true, + adminChangeType: "update-values", + title: "updated title", + changeMade: "title has been updated.", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + }, + ]); + }); + + it("handles valid kafka admin record to delete", async () => { + await insertOneMacRecordsFromKafkaIntoMako( + [ createKafkaRecord({ topic: TOPIC, key: "TUQtMjQtMjMwMA==", value: convertObjToBase64({ id: "MD-24-2301", - submitterName: "George Harrison", - submitterEmail: "george@example.com", isAdminChange: true, adminChangeType: "delete", - deleted: true, + deleted: false, + submitterName: "George Harrison", + submitterEmail: "george@example.com", }), }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith( + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: "MD-24-2301", + isAdminChange: true, + adminChangeType: "delete", + deleted: false, + submitterName: "George Harrison", + submitterEmail: "george@example.com", + }, + ]); + }); + + it("skips invalid kafka admin records", async () => { + await insertOneMacRecordsFromKafkaIntoMako( [ - // record deleted - { - id: "MD-24-2301", - submitterName: "George Harrison", - submitterEmail: "george@example.com", - changeMade: "ID has been updated.", - isAdminChange: true, - adminChangeType: "update-id", - idToBeUpdated: "MD-24-2300", - }, - // property updated - { - id: "MD-24-2301", - submitterName: "George Harrison", - submitterEmail: "george@example.com", - changeMade: "title has been updated.", - isAdminChange: true, - adminChangeType: "update-values", - title: "updated title", - }, - // id updated - { - id: "MD-24-2301", - submitterName: "George Harrison", - submitterEmail: "george@example.com", - isAdminChange: true, - adminChangeType: "delete", - deleted: true, - }, + createKafkaRecord({ + topic: TOPIC, + key: "TUQtMjQtMjMwMA==", + value: convertObjToBase64({ + id: "MD-24-2301", + submitterName: "George Harrison", + submitterEmail: "george@example.com", + changeMade: "ID has been updated.", + isAdminChange: true, + adminChangeType: "update-id", + }), + }), ], - "main", + TOPIC, ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); - it("skips value-less kafka records", () => { - insertOneMacRecordsFromKafkaIntoMako( + it("skips value-less kafka records", async () => { + await insertOneMacRecordsFromKafkaIntoMako( [ createKafkaRecord({ topic: TOPIC, @@ -218,97 +421,59 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); - it("skips kafka records with invalid event name", () => { - insertOneMacRecordsFromKafkaIntoMako( + it("skips kafka records with invalid event name", async () => { + await insertOneMacRecordsFromKafkaIntoMako( [ createKafkaRecord({ topic: TOPIC, key: "TUQtMjQtMjMwMA==", // encoded string with `invalid-event-name` as 'record.event` value: convertObjToBase64({ + ...newMedicaidSubmission, event: "invalid-event-name", - attachments: { - cmsForm179: { - files: [ - { - filename: "Screenshot 2024-07-08 at 11.42.35 AM.png", - title: "Screenshot 2024-07-08 at 11.42.35 AM", - bucket: "mako-refactor-tests-sink-attachments-635052997545", - key: "13513eea-ba62-4cba-af31-2ec3c160b5e1.png", - uploadDate: 1732645033529, - }, - ], - label: "CMS Form 179", - }, - spaPages: { - files: [ - { - filename: "Screenshot 2024-07-08 at 11.42.35 AM.png", - title: "Screenshot 2024-07-08 at 11.42.35 AM", - bucket: "mako-refactor-tests-sink-attachments-635052997545", - key: "bbdfa95f-f67c-4983-8517-2745cc08d3b6.png", - uploadDate: 1732645038805, - }, - ], - label: "SPA Pages", - }, - coverLetter: { label: "Cover Letter" }, - tribalEngagement: { label: "Document Demonstrating Good-Faith Tribal Engagement" }, - existingStatePlanPages: { label: "Existing State Plan Page(s)" }, - publicNotice: { label: "Public Notice" }, - sfq: { label: "Standard Funding Questions (SFQs)" }, - tribalConsultation: { label: "Tribal Consultation" }, - other: { label: "Other" }, - }, - authority: "Medicaid SPA", - proposedEffectiveDate: 1732597200000, - id: "MD-24-2300", origin: "mako", submitterName: "George Harrison", submitterEmail: "george@example.com", - timestamp: 1732645041526, + timestamp: TIMESTAMP, }), }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); - it("skips kafka records with invalid properties", () => { - insertOneMacRecordsFromKafkaIntoMako( + it("skips kafka records with invalid properties", async () => { + await insertOneMacRecordsFromKafkaIntoMako( [ createKafkaRecord({ topic: TOPIC, key: "TUQtMjQtMjMwMA==", // encoded string with `attachments` as an empty {} value: convertObjToBase64({ - event: "new-medicaid-submission", + ...newMedicaidSubmission, attachments: {}, - authority: "Medicaid SPA", - proposedEffectiveDate: 1732597200000, - id: "MD-24-2300", origin: "mako", submitterName: "George Harrison", submitterEmail: "george@example.com", - timestamp: 1732645041526, + timestamp: TIMESTAMP, }), }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); - it("skips kafka records with invalid JSON", () => { - const spiedOnLogError = vi.spyOn(sinkLib, "logError").mockImplementation(vi.fn()); + it("skips kafka records with invalid JSON", async () => { + const logErrorSpy = vi.spyOn(sink, "logError"); - insertOneMacRecordsFromKafkaIntoMako( + await insertOneMacRecordsFromKafkaIntoMako( [ createKafkaRecord({ topic: TOPIC, @@ -319,8 +484,8 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); - expect(spiedOnLogError).toBeCalledWith({ + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toBeCalledWith({ type: "badparse", error: expect.any(Object), metadata: expect.any(Object), @@ -329,44 +494,10 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { }); describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { - // @ts-expect-error – cannot bother typing out unnecessary Document properties - const spiedOnGetItems = vi.fn<() => Promise>(() => - Promise.resolve([ - { - id: "MD-24-2300", - changedDate: "2024-02-04 23:12:36", - }, - { - id: "WA-22-2100", - changedDate: "2024-06-12 13:24:43", - }, - { - id: "NY-23-2200", - changedDate: "2024-10-12 09:04:52", - }, - { - id: "WV-24-3230", - changedDate: "2024-03-21 09:51:23", - }, - { - id: "IL-25-3130", - changedDate: "2025-03-21 17:51:23", - }, - ]), - ); - const spiedOnBulkUpdateDataWrapper = vi.fn(); - const spiedOnLogError = vi.fn(); const TOPIC = "--mako--branch-name--aws.seatool.ksql.onemac.three.agg.State_Plan"; - beforeEach(() => { + afterEach(() => { vi.clearAllMocks(); - - vi.spyOn(sinkLib, "getItems").mockImplementation(spiedOnGetItems); - vi.spyOn(sinkLib, "bulkUpdateDataWrapper").mockImplementation(spiedOnBulkUpdateDataWrapper); - vi.spyOn(sinkLib, "logError").mockImplementation(spiedOnLogError); - - vi.stubEnv("osDomain", "osDomain"); - vi.stubEnv("indexNamespace", "indexNamespace"); }); it("outputs kafka records into mako records", async () => { @@ -374,9 +505,9 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: convertObjToBase64({ - id: "MD-24-2300", + id: TEST_ITEM_ID, ACTION_OFFICERS: [ { FIRST_NAME: "John", @@ -408,12 +539,12 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { STATE_PLAN: { PLAN_TYPE: 123, SPW_STATUS_ID: 4, - APPROVED_EFFECTIVE_DATE: 1707088356000, - CHANGED_DATE: 1704163200000, + APPROVED_EFFECTIVE_DATE: TIMESTAMP, + CHANGED_DATE: EARLIER_TIMESTAMP, SUMMARY_MEMO: "Sample summary", TITLE_NAME: "Sample Title", - STATUS_DATE: 1704240000000, - SUBMISSION_DATE: 1704326400000, + STATUS_DATE: EARLIER_TIMESTAMP, + SUBMISSION_DATE: TIMESTAMP, LEAD_ANALYST_ID: 67890, ACTUAL_EFFECTIVE_DATE: null, PROPOSED_DATE: null, @@ -429,60 +560,170 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith( + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + actionType: "Initial Review", + approvedEffectiveDate: ISO_DATETIME, + authority: "1915(c)", + changed_date: EARLIER_TIMESTAMP, + cmsStatus: "Approved", + description: "Sample summary", + finalDispositionDate: EARLIER_ISO_DATETIME, + id: TEST_ITEM_ID, + initialIntakeNeeded: false, + leadAnalystEmail: "michael.chen@cms.hhs.gov", + leadAnalystName: "Michael Chen", + leadAnalystOfficerId: 67890, + locked: false, + proposedDate: null, + raiReceivedDate: null, + raiRequestedDate: null, + raiWithdrawEnabled: false, + raiWithdrawnDate: null, + reviewTeam: [ + { + email: "john.doe@medicaid.gov", + name: "John Doe", + }, + { + email: "emily.rodriguez@medicaid.gov", + name: "Emily Rodriguez", + }, + ], + seatoolStatus: "Approved", + secondClock: false, + state: "10", + stateStatus: "Approved", + statusDate: EARLIER_ISO_DATETIME, + subTypes: [ + { + TYPE_ID: 1, + TYPE_NAME: "SubType X", + }, + ], + subject: "Sample Title", + submissionDate: ISO_DATETIME, + types: [ + { + SPA_TYPE_ID: 1, + SPA_TYPE_NAME: "Type A", + }, + ], + }, + ]); + }); + + it("outputs kafka records into mako records without changedDates", async () => { + await insertNewSeatoolRecordsFromKafkaIntoMako( [ - { - actionType: "Initial Review", - approvedEffectiveDate: "2024-02-04T23:12:36.000Z", - authority: "1915(c)", - changed_date: 1704163200000, - cmsStatus: "Approved", - description: "Sample summary", - finalDispositionDate: "2024-01-03T00:00:00.000Z", - id: "MD-24-2300", - initialIntakeNeeded: false, - leadAnalystEmail: "michael.chen@cms.hhs.gov", - leadAnalystName: "Michael Chen", - leadAnalystOfficerId: 67890, - locked: false, - proposedDate: null, - raiReceivedDate: null, - raiRequestedDate: null, - raiWithdrawEnabled: false, - raiWithdrawnDate: null, - reviewTeam: [ - { - email: "john.doe@medicaid.gov", - name: "John Doe", - }, - { - email: "emily.rodriguez@medicaid.gov", - name: "Emily Rodriguez", - }, - ], - seatoolStatus: "Approved", - secondClock: false, - state: "10", - stateStatus: "Approved", - statusDate: "2024-01-03T00:00:00.000Z", - subTypes: [ - { - TYPE_ID: 1, - TYPE_NAME: "SubType X", - }, - ], - subject: "Sample Title", - submissionDate: "2024-01-04T00:00:00.000Z", - types: [ - { - SPA_TYPE_ID: 1, - SPA_TYPE_NAME: "Type A", + createKafkaRecord({ + topic: TOPIC, + key: Buffer.from(EXISTING_ITEM_TEMPORARY_EXTENSION_ID).toString("base64"), + value: convertObjToBase64({ + id: EXISTING_ITEM_TEMPORARY_EXTENSION_ID, + ACTION_OFFICERS: [ + { + FIRST_NAME: "John", + LAST_NAME: "Doe", + EMAIL: "john.doe@medicaid.gov", + OFFICER_ID: 12345, + DEPARTMENT: "State Plan Review", + PHONE: "202-555-1234", + }, + { + FIRST_NAME: "Emily", + LAST_NAME: "Rodriguez", + EMAIL: "emily.rodriguez@medicaid.gov", + OFFICER_ID: 12346, + DEPARTMENT: "Compliance Division", + PHONE: "202-555-5678", + }, + ], + LEAD_ANALYST: [ + { + FIRST_NAME: "Michael", + LAST_NAME: "Chen", + EMAIL: "michael.chen@cms.hhs.gov", + OFFICER_ID: 67890, + DEPARTMENT: "Medicaid Innovation Center", + PHONE: "202-555-9012", + }, + ], + STATE_PLAN: { + PLAN_TYPE: 123, + SPW_STATUS_ID: 4, + APPROVED_EFFECTIVE_DATE: TIMESTAMP, + CHANGED_DATE: EARLIER_TIMESTAMP, + SUMMARY_MEMO: "Sample summary", + TITLE_NAME: "Sample Title", + STATUS_DATE: EARLIER_TIMESTAMP, + SUBMISSION_DATE: TIMESTAMP, + LEAD_ANALYST_ID: 67890, + ACTUAL_EFFECTIVE_DATE: null, + PROPOSED_DATE: null, + STATE_CODE: "10", }, - ], - }, + RAI: [], + ACTIONTYPES: [{ ACTION_NAME: "Initial Review", ACTION_ID: 1, PLAN_TYPE_ID: 123 }], + STATE_PLAN_SERVICETYPES: [{ SPA_TYPE_ID: 1, SPA_TYPE_NAME: "Type A" }], + STATE_PLAN_SERVICE_SUBTYPES: [{ TYPE_ID: 1, TYPE_NAME: "SubType X" }], + }), + }), ], - "main", + TOPIC, ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + actionType: "Initial Review", + approvedEffectiveDate: ISO_DATETIME, + authority: "1915(c)", + changed_date: EARLIER_TIMESTAMP, + cmsStatus: "Approved", + description: "Sample summary", + finalDispositionDate: EARLIER_ISO_DATETIME, + id: EXISTING_ITEM_TEMPORARY_EXTENSION_ID, + initialIntakeNeeded: false, + leadAnalystEmail: "michael.chen@cms.hhs.gov", + leadAnalystName: "Michael Chen", + leadAnalystOfficerId: 67890, + locked: false, + proposedDate: null, + raiReceivedDate: null, + raiRequestedDate: null, + raiWithdrawEnabled: false, + raiWithdrawnDate: null, + reviewTeam: [ + { + email: "john.doe@medicaid.gov", + name: "John Doe", + }, + { + email: "emily.rodriguez@medicaid.gov", + name: "Emily Rodriguez", + }, + ], + seatoolStatus: "Approved", + secondClock: false, + state: "10", + stateStatus: "Approved", + statusDate: EARLIER_ISO_DATETIME, + subTypes: [ + { + TYPE_ID: 1, + TYPE_NAME: "SubType X", + }, + ], + subject: "Sample Title", + submissionDate: ISO_DATETIME, + types: [ + { + SPA_TYPE_ID: 1, + SPA_TYPE_NAME: "Type A", + }, + ], + }, + ]); }); it("skips newer mako records", async () => { @@ -490,9 +731,9 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { [ createKafkaRecord({ topic: TOPIC, - key: "V1YtMjQtMzIzMA==", + key: TEST_ITEM_KEY, value: convertObjToBase64({ - id: "WV-24-3230", + id: TEST_ITEM_ID, ACTION_OFFICERS: [ { FIRST_NAME: "Lisa", @@ -524,12 +765,12 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { STATE_PLAN: { PLAN_TYPE: 121, SPW_STATUS_ID: 11, - APPROVED_EFFECTIVE_DATE: 1711065600000, - CHANGED_DATE: 1711065600000, + APPROVED_EFFECTIVE_DATE: LATER_TIMESTAMP, + CHANGED_DATE: LATER_TIMESTAMP, SUMMARY_MEMO: "1115 Demonstration Waiver Review", TITLE_NAME: "WV 1115 Medicaid Demonstration Project", - STATUS_DATE: 1710979200000, - SUBMISSION_DATE: 1711065600000, + STATUS_DATE: LATER_TIMESTAMP, + SUBMISSION_DATE: LATER_TIMESTAMP, LEAD_ANALYST_ID: 23456, ACTUAL_EFFECTIVE_DATE: null, PROPOSED_DATE: null, @@ -545,54 +786,51 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); it("tombstones records with no value property", async () => { - const spiedOnTombstone = vi.spyOn(seatool, "tombstone"); + const tombstoneSpy = vi.spyOn(seatool, "tombstone"); await insertNewSeatoolRecordsFromKafkaIntoMako( [ createKafkaRecord({ topic: TOPIC, - key: "TlktMjMtMjIwMA==", + key: TEST_ITEM_KEY, value: "", // <-- missing value }), ], TOPIC, ); - expect(spiedOnTombstone).toBeCalledWith("NY-23-2200"); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith( - [ - { - actionType: null, - approvedEffectiveDate: null, - authority: null, - changedDate: null, - cmsStatus: null, - description: null, - finalDispositionDate: null, - id: "NY-23-2200", - leadAnalystName: null, - leadAnalystOfficerId: null, - proposedDate: null, - raiReceivedDate: null, - raiRequestedDate: null, - raiWithdrawnDate: null, - reviewTeam: null, - seatoolStatus: null, - state: null, - stateStatus: null, - statusDate: null, - subTypes: null, - subject: null, - submissionDate: null, - types: null, - }, - ], - "main", - ); + expect(tombstoneSpy).toBeCalledWith(TEST_ITEM_ID); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + actionType: null, + approvedEffectiveDate: null, + authority: null, + changedDate: null, + cmsStatus: null, + description: null, + finalDispositionDate: null, + id: TEST_ITEM_ID, + leadAnalystName: null, + leadAnalystOfficerId: null, + proposedDate: null, + raiReceivedDate: null, + raiRequestedDate: null, + raiWithdrawnDate: null, + reviewTeam: null, + seatoolStatus: null, + state: null, + stateStatus: null, + statusDate: null, + subTypes: null, + subject: null, + submissionDate: null, + types: null, + }, + ]); }); it("skips over records with no key property", async () => { @@ -655,7 +893,7 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); it("skips over records with invalid properties", async () => { @@ -671,8 +909,8 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); - expect(spiedOnLogError).toBeCalledWith({ + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toBeCalledWith({ type: "badparse", metadata: expect.any(Object), error: expect.any(Object), @@ -684,9 +922,9 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: convertObjToBase64({ - id: "MD-24-2300", + id: TEST_ITEM_ID, ACTION_OFFICERS: [ { FIRST_NAME: "John", @@ -739,7 +977,7 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); it("skips over records that fail SEATOOL safeParse", async () => { @@ -747,7 +985,7 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: convertObjToBase64({ ACTION_OFFICERS: [ { @@ -801,8 +1039,8 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); - expect(spiedOnLogError).toBeCalledWith({ + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toBeCalledWith({ type: "validation", metadata: expect.any(Object), error: expect.any(Array), @@ -811,19 +1049,10 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { }); describe("syncSeatoolRecordDatesFromKafkaWithMako", () => { - const spiedOnBulkUpdateDataWrapper = vi.fn(); - const spiedOnLogError = vi.fn(); - const TOPIC = "--mako--branch-name--aws.seatool.debezium.changed_date.SEA.dbo.State_Plan"; - beforeEach(() => { - vi.resetAllMocks(); - - vi.spyOn(sinkLib, "bulkUpdateDataWrapper").mockImplementation(spiedOnBulkUpdateDataWrapper); - vi.spyOn(sinkLib, "logError").mockImplementation(spiedOnLogError); - - vi.stubEnv("osDomain", "osDomain"); - vi.stubEnv("indexNamespace", "indexNamespace"); + afterEach(() => { + vi.clearAllMocks(); }); it("processes a valid date change to mako", async () => { @@ -831,32 +1060,29 @@ describe("syncSeatoolRecordDatesFromKafkaWithMako", () => { [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: convertObjToBase64({ - payload: { after: { ID_Number: "12345", Changed_Date: 1672531200000 } }, + payload: { after: { ID_Number: "12345", Changed_Date: LATER_TIMESTAMP } }, }), }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith( - [ - { - changedDate: "2023-01-01T00:00:00.000Z", - id: "12345", - }, - ], - "main", - ); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + changedDate: LATER_ISO_DATETIME, + id: "12345", + }, + ]); }); - it("processes a date change that's null to mako", async () => { + it("processes a date change that is null to mako", async () => { await syncSeatoolRecordDatesFromKafkaWithMako( [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: convertObjToBase64({ payload: { after: { ID_Number: "67890", Changed_Date: null } }, }), @@ -865,15 +1091,12 @@ describe("syncSeatoolRecordDatesFromKafkaWithMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith( - [ - { - changedDate: null, - id: "67890", - }, - ], - "main", - ); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + changedDate: null, + id: "67890", + }, + ]); }); it("skips records with no value property", async () => { @@ -881,35 +1104,35 @@ describe("syncSeatoolRecordDatesFromKafkaWithMako", () => { [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: "", }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); - it("skips payloads that're null or undefined", async () => { + it("skips payloads that are null or undefined", async () => { await syncSeatoolRecordDatesFromKafkaWithMako( [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: convertObjToBase64({ payload: { after: null } }), }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); await syncSeatoolRecordDatesFromKafkaWithMako( [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: convertObjToBase64({ payload: { after: undefined, @@ -920,7 +1143,7 @@ describe("syncSeatoolRecordDatesFromKafkaWithMako", () => { TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); }); it("skips payloads with missing Id property", async () => { @@ -928,16 +1151,16 @@ describe("syncSeatoolRecordDatesFromKafkaWithMako", () => { [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, // missing required Id property - value: convertObjToBase64({ payload: { after: { Changed_Date: 1672531200000 } } }), + value: convertObjToBase64({ payload: { after: { Changed_Date: LATER_TIMESTAMP } } }), }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); - expect(spiedOnLogError).toBeCalledWith({ + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toBeCalledWith({ type: "validation", error: expect.any(Object), metadata: expect.any(Object), @@ -949,15 +1172,15 @@ describe("syncSeatoolRecordDatesFromKafkaWithMako", () => { [ createKafkaRecord({ topic: TOPIC, - key: "TUQtMjQtMjMwMA==", + key: TEST_ITEM_KEY, value: "bunch-of-gibberish", }), ], TOPIC, ); - expect(spiedOnBulkUpdateDataWrapper).toBeCalledWith([], "main"); - expect(spiedOnLogError).toBeCalledWith({ + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toBeCalledWith({ type: "badparse", error: expect.any(Object), metadata: expect.any(Object), diff --git a/lib/lambda/sinkMainProcessors.ts b/lib/lambda/sinkMainProcessors.ts index c95359a25d..a3e5c25952 100644 --- a/lib/lambda/sinkMainProcessors.ts +++ b/lib/lambda/sinkMainProcessors.ts @@ -146,7 +146,7 @@ const getMakoDocTimestamps = async (kafkaRecords: KafkaRecord[]) => { const openSearchRecords = await getItems(kafkaIds); return openSearchRecords.reduce>((map, item) => { - if (item.changedDate !== null) { + if (item?.changedDate) { map.set(item.id, new Date(item.changedDate).getTime()); } diff --git a/lib/packages/shared-types/events/capitated-renewal.ts b/lib/packages/shared-types/events/capitated-renewal.ts index 0fffe6e7f7..ecad383ac9 100644 --- a/lib/packages/shared-types/events/capitated-renewal.ts +++ b/lib/packages/shared-types/events/capitated-renewal.ts @@ -13,6 +13,14 @@ export const baseSchema = z.object({ }), proposedEffectiveDate: z.number(), attachments: z.object({ + bCapIndependentAssessment: z.object({ + label: z + .string() + .default( + "1915(b) Comprehensive (Capitated) Waiver Independent Assessment (first two renewals only)", + ), + files: attachmentArraySchemaOptional(), + }), bCapWaiverApplication: z.object({ label: z.string().default("1915(b) Comprehensive (Capitated) Waiver Application Pre-print"), files: attachmentArraySchema(), @@ -23,14 +31,6 @@ export const baseSchema = z.object({ .default("1915(b) Comprehensive (Capitated) Waiver Cost Effectiveness Spreadsheets"), files: attachmentArraySchema(), }), - bCapIndependentAssessment: z.object({ - label: z - .string() - .default( - "1915(b) Comprehensive (Capitated) Waiver Independent Assessment (first two renewals only)", - ), - files: attachmentArraySchemaOptional(), - }), tribalConsultation: z.object({ label: z.string().default("Tribal Consultation"), files: attachmentArraySchemaOptional(), diff --git a/lib/packages/shared-types/events/contracting-renewal.ts b/lib/packages/shared-types/events/contracting-renewal.ts index 5bee7b66c5..cb2b50c13e 100644 --- a/lib/packages/shared-types/events/contracting-renewal.ts +++ b/lib/packages/shared-types/events/contracting-renewal.ts @@ -13,12 +13,6 @@ export const baseSchema = z.object({ }), proposedEffectiveDate: z.number(), attachments: z.object({ - b4WaiverApplication: z.object({ - label: z - .string() - .default("1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print"), - files: attachmentArraySchema(), - }), b4IndependentAssessment: z.object({ label: z .string() @@ -27,6 +21,12 @@ export const baseSchema = z.object({ ), files: attachmentArraySchemaOptional(), }), + b4WaiverApplication: z.object({ + label: z + .string() + .default("1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print"), + files: attachmentArraySchema(), + }), tribalConsultation: z.object({ label: z.string().default("Tribal Consultation"), files: attachmentArraySchemaOptional(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/respond-to-rai.ts b/lib/packages/shared-types/opensearch/main/transforms/respond-to-rai.ts index 85eee1c924..674fb5aaa8 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/respond-to-rai.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/respond-to-rai.ts @@ -1,7 +1,7 @@ import { events, getStatus, SEATOOL_STATUS } from "shared-types"; export const transform = () => { return events["respond-to-rai"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING_RAI); return { id: data.id, raiWithdrawEnabled: false, diff --git a/lib/packages/shared-types/statusHelper.ts b/lib/packages/shared-types/statusHelper.ts index 8714c3138f..e79de3bf4f 100644 --- a/lib/packages/shared-types/statusHelper.ts +++ b/lib/packages/shared-types/statusHelper.ts @@ -12,7 +12,7 @@ export const SEATOOL_STATUS = { PENDING_OFF_THE_CLOCK: "Pending-Off the Clock", }; -const statusToDisplayToStateUser = { +export const statusToDisplayToStateUser = { [SEATOOL_STATUS.PENDING]: "Under Review", [SEATOOL_STATUS.PENDING_RAI]: "RAI Issued", [SEATOOL_STATUS.APPROVED]: "Approved", @@ -25,7 +25,7 @@ const statusToDisplayToStateUser = { [SEATOOL_STATUS.PENDING_OFF_THE_CLOCK]: "Pending - Off the Clock", }; -const statusToDisplayToCmsUser = { +export const statusToDisplayToCmsUser = { [SEATOOL_STATUS.PENDING]: "Pending", [SEATOOL_STATUS.PENDING_RAI]: "Pending - RAI", [SEATOOL_STATUS.APPROVED]: "Approved", diff --git a/lib/packages/shared-utils/s3-url-parser.test.ts b/lib/packages/shared-utils/s3-url-parser.test.ts index e202704a20..8451cd7b0a 100644 --- a/lib/packages/shared-utils/s3-url-parser.test.ts +++ b/lib/packages/shared-utils/s3-url-parser.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { s3ParseUrl } from "."; +import { s3ParseUrl } from "./s3-url-parser"; describe("s3ParseUrl", () => { it("parses URL with format http://s3.amazonaws.com/bucket/key1/key2", () => { @@ -12,9 +12,7 @@ describe("s3ParseUrl", () => { }); it("parses URL with format http://s3-aws-region.amazonaws.com/bucket/key1/key2", () => { - const result = s3ParseUrl( - "http://s3-us-west-2.amazonaws.com/bucket/key1/key2", - ); + const result = s3ParseUrl("http://s3-us-west-2.amazonaws.com/bucket/key1/key2"); expect(result).toEqual({ bucket: "bucket", key: "key1/key2", @@ -32,9 +30,7 @@ describe("s3ParseUrl", () => { }); it("parses URL with format http://bucket.s3-aws-region.amazonaws.com/key1/key2", () => { - const result = s3ParseUrl( - "http://bucket.s3-us-west-2.amazonaws.com/key1/key2", - ); + const result = s3ParseUrl("http://bucket.s3-us-west-2.amazonaws.com/key1/key2"); expect(result).toEqual({ bucket: "bucket", key: "key1/key2", @@ -43,9 +39,7 @@ describe("s3ParseUrl", () => { }); it("parses URL with format http://bucket.s3.aws-region.amazonaws.com/key1/key2", () => { - const result = s3ParseUrl( - "http://bucket.s3.us-west-2.amazonaws.com/key1/key2", - ); + const result = s3ParseUrl("http://bucket.s3.us-west-2.amazonaws.com/key1/key2"); expect(result).toEqual({ bucket: "bucket", key: "key1/key2", diff --git a/lib/packages/shared-utils/s3-url-parser.ts b/lib/packages/shared-utils/s3-url-parser.ts index 6cb2a8e03e..da1a5c9aff 100644 --- a/lib/packages/shared-utils/s3-url-parser.ts +++ b/lib/packages/shared-utils/s3-url-parser.ts @@ -2,9 +2,7 @@ export function s3ParseUrl(url: string) { const _decodedUrl = decodeURIComponent(url); // http://s3.amazonaws.com/bucket/key1/key2 - const _match_1 = _decodedUrl.match( - /^https?:\/\/s3.amazonaws.com\/([^/]+)\/?(.*?)$/, - ); + const _match_1 = _decodedUrl.match(/^https?:\/\/s3.amazonaws.com\/([^/]+)\/?(.*?)$/); if (_match_1) { return { bucket: _match_1[1], @@ -14,9 +12,7 @@ export function s3ParseUrl(url: string) { } // http://s3-aws-region.amazonaws.com/bucket/key1/key2 - const _match_2 = _decodedUrl.match( - /^https?:\/\/s3-([^.]+).amazonaws.com\/([^/]+)\/?(.*?)$/, - ); + const _match_2 = _decodedUrl.match(/^https?:\/\/s3-([^.]+).amazonaws.com\/([^/]+)\/?(.*?)$/); if (_match_2) { return { bucket: _match_2[2], @@ -26,9 +22,7 @@ export function s3ParseUrl(url: string) { } // http://bucket.s3.amazonaws.com/key1/key2 - const _match_3 = _decodedUrl.match( - /^https?:\/\/([^.]+).s3.amazonaws.com\/?(.*?)$/, - ); + const _match_3 = _decodedUrl.match(/^https?:\/\/([^.]+).s3.amazonaws.com\/?(.*?)$/); if (_match_3) { return { bucket: _match_3[1], diff --git a/lib/stacks/data.ts b/lib/stacks/data.ts index 3d22e7d1b2..fd8f03307b 100644 --- a/lib/stacks/data.ts +++ b/lib/stacks/data.ts @@ -461,12 +461,12 @@ export class Data extends cdk.NestedStack { const functionConfigs = { sinkChangelog: { provisionedConcurrency: 2 }, + sinkCpocs: { provisionedConcurrency: 0 }, sinkInsights: { provisionedConcurrency: 0 }, sinkLegacyInsights: { provisionedConcurrency: 0 }, sinkMain: { provisionedConcurrency: 2 }, sinkSubtypes: { provisionedConcurrency: 0 }, sinkTypes: { provisionedConcurrency: 0 }, - sinkCpocs: { provisionedConcurrency: 0 }, }; const lambdaFunctions = Object.entries(functionConfigs).reduce((acc, [name, config]) => { diff --git a/lib/vitest.setup.ts b/lib/vitest.setup.ts index b53c0282fe..573631b2b1 100644 --- a/lib/vitest.setup.ts +++ b/lib/vitest.setup.ts @@ -12,13 +12,15 @@ import { USER_POOL_CLIENT_DOMAIN, USER_POOL_CLIENT_ID, USER_POOL_ID, + BUCKET_NAME, + BUCKET_REGION, ATTACHMENT_BUCKET_NAME, ATTACHMENT_BUCKET_REGION, KAFKA_BROKERS, setDefaultStateSubmitter, mockedKafka, } from "mocks"; -import { ConfigResourceTypes, Kafka } from "kafkajs"; +import { ConfigResourceTypes } from "kafkajs"; import { mockedServiceServer as mockedServer } from "mocks/server"; import { Amplify } from "aws-amplify"; type CreateType = T & { default: T }; @@ -27,15 +29,11 @@ Amplify.configure({ Auth: AUTH_CONFIG, }); -vi.mock("kafkajs", async (importOriginal) => { - const original = await importOriginal>(); - const configOriginal = await importOriginal>(); - return { - ...original, - Kafka: mockedKafka, - ConfigResourceTypes: configOriginal, - }; -}); +vi.mock("kafkajs", async (importOriginal) => ({ + ...(await importOriginal()), + ConfigResourceTypes: await importOriginal>(), + Kafka: mockedKafka, +})); beforeAll(() => { setDefaultStateSubmitter(); @@ -63,6 +61,8 @@ beforeEach(() => { process.env.idmClientIssuer = USER_POOL_CLIENT_DOMAIN; process.env.osDomain = OPENSEARCH_DOMAIN; process.env.indexNamespace = OPENSEARCH_INDEX_NAMESPACE; + process.env.bucket = BUCKET_NAME; + process.env.bucketRegion = BUCKET_REGION; process.env.attachmentsBucketName = ATTACHMENT_BUCKET_NAME; process.env.attachmentsBucketRegion = ATTACHMENT_BUCKET_REGION; process.env.emailAddressLookupSecretName = "mock-email-secret"; // pragma: allowlist secret diff --git a/mocks/consts.ts b/mocks/consts.ts index 6e6d2a1d63..6228e82b25 100644 --- a/mocks/consts.ts +++ b/mocks/consts.ts @@ -10,7 +10,9 @@ export const COGNITO_IDP_DOMAIN = `https://cognito-idp.${REGION}.amazonaws.com/$ export const OPENSEARCH_DOMAIN = `https://vpc-opensearchdomain-mock-domain.${REGION}.es.amazonaws.com`; export const OPENSEARCH_INDEX_NAMESPACE = "test-namespace-"; export const CLOUDFORMATION_NOTIFICATION_DOMAIN = "https://test-cfn.amazonaws.com"; -export const ATTACHMENT_BUCKET_NAME = "test-bucket"; +export const BUCKET_NAME = "test-bucket"; +export const BUCKET_REGION = REGION; +export const ATTACHMENT_BUCKET_NAME = "test-attachment-bucket"; export const ATTACHMENT_BUCKET_REGION = REGION; export const KAFKA_BROKERS = "kafka1:9092,kafka2:9092"; diff --git a/mocks/data/items.ts b/mocks/data/items.ts index 07e6d9bb18..50b9f1e05a 100644 --- a/mocks/data/items.ts +++ b/mocks/data/items.ts @@ -141,6 +141,7 @@ const items: Record = { actionType: "New", state: "MD", origin: "OneMAC", + changedDate: "2024-11-26T18:17:21.557Z", changelog: [ { _id: `${TEST_ITEM_ID}-001`, @@ -162,6 +163,7 @@ const items: Record = { seatoolStatus: SEATOOL_STATUS.APPROVED, actionType: "Amend", authority: "Medicaid SPA", + changedDate: undefined, origin: "OneMAC", state: "MD", }, diff --git a/mocks/data/submit/attachments.ts b/mocks/data/submit/attachments.ts index 30ce31acc4..162f23ab74 100644 --- a/mocks/data/submit/attachments.ts +++ b/mocks/data/submit/attachments.ts @@ -1,4 +1,4 @@ -const bCapWaiverApplication = { +export const bCapWaiverApplication = { bCapWaiverApplication: { label: "1915(b) Comprehensive (Capitated) Waiver Application Pre-print", files: [ @@ -12,7 +12,8 @@ const bCapWaiverApplication = { ], }, }; -const bCapCostSpreadsheets = { + +export const bCapCostSpreadsheets = { bCapCostSpreadsheets: { label: "1915(b) Comprehensive (Capitated) Waiver Cost Effectiveness Spreadsheets", files: [ @@ -26,7 +27,8 @@ const bCapCostSpreadsheets = { ], }, }; -const tribalConsultation = { + +export const tribalConsultation = { tribalConsultation: { label: "Tribal Consultation", files: [ @@ -40,7 +42,8 @@ const tribalConsultation = { ], }, }; -const other = { + +export const other = { other: { files: [ { @@ -54,7 +57,8 @@ const other = { label: "Other", }, }; -const appk = { + +export const appk = { appk: { label: "Appendix K Template", files: [ @@ -68,7 +72,8 @@ const appk = { ], }, }; -const b4Waiver = { + +export const b4Waiver = { b4WaiverApplication: { label: "1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print", files: [ @@ -82,7 +87,8 @@ const b4Waiver = { ], }, }; -const b4IndependentAssessment = { + +export const b4IndependentAssessment = { b4IndependentAssessment: { label: "1915(b)(4) FFS Selective Contracting (Streamlined) Independent Assessment (first two renewals only)", @@ -97,7 +103,8 @@ const b4IndependentAssessment = { ], }, }; -const currentStatePlan = { + +export const currentStatePlan = { currentStatePlan: { label: "Current State Plan", files: [ @@ -111,7 +118,8 @@ const currentStatePlan = { ], }, }; -const amendedLanguage = { + +export const amendedLanguage = { amendedLanguage: { label: "Amended State Plan Language", files: [ @@ -125,7 +133,8 @@ const amendedLanguage = { ], }, }; -const coverLetter = { + +export const coverLetter = { coverLetter: { label: "Cover Letter", files: [ @@ -139,7 +148,8 @@ const coverLetter = { ], }, }; -const budgetDocuments = { + +export const budgetDocuments = { budgetDocuments: { label: "Budget Document", files: [ @@ -153,7 +163,8 @@ const budgetDocuments = { ], }, }; -const publicNotice = { + +export const publicNotice = { publicNotice: { label: "Public Notice", files: [ @@ -167,7 +178,8 @@ const publicNotice = { ], }, }; -const cmsForm = { + +export const cmsForm = { cmsForm179: { label: "CMS Form 179", files: [ @@ -181,6 +193,7 @@ const cmsForm = { ], }, }; + export const spaPages = { spaPages: { label: "Spa Pages", @@ -195,7 +208,8 @@ export const spaPages = { ], }, }; -const existingStatePlanPages = { + +export const existingStatePlanPages = { existingStatePlanPages: { label: "Existing State Plan Page(s)", files: [ @@ -209,7 +223,8 @@ const existingStatePlanPages = { ], }, }; -const sfq = { + +export const sfq = { sfq: { label: "Standard Funding Questions (SFQs)", files: [ @@ -223,7 +238,8 @@ const sfq = { ], }, }; -const tribalEngagement = { + +export const tribalEngagement = { tribalEngagement: { label: "Document Demonstrating Good-Faith Tribal Engagement", files: [ @@ -237,7 +253,8 @@ const tribalEngagement = { ], }, }; -const raiResponseLetter = { + +export const raiResponseLetter = { raiResponseLetter: { label: "RAI Response Letter", files: [ @@ -251,7 +268,8 @@ const raiResponseLetter = { ], }, }; -const waiverExtensionRequest = { + +export const waiverExtensionRequest = { waiverExtensionRequest: { label: "RAI Response Letter", files: [ @@ -265,7 +283,8 @@ const waiverExtensionRequest = { ], }, }; -const supportingDocumentation = { + +export const supportingDocumentation = { supportingDocumentation: { label: "Supporting Documentation", files: [ @@ -279,7 +298,8 @@ const supportingDocumentation = { ], }, }; -const bCapIndependentAssessment = { + +export const bCapIndependentAssessment = { bCapIndependentAssessment: { label: "1915(b) Comprehensive (Capitated) Waiver Independent Assessment (first two renewals only)", @@ -294,6 +314,7 @@ const bCapIndependentAssessment = { ], }, }; + export const attachments = { amendedLanguage, appk, diff --git a/mocks/data/submit/base.ts b/mocks/data/submit/base.ts index 4429b13ff7..9f38c0dd6c 100644 --- a/mocks/data/submit/base.ts +++ b/mocks/data/submit/base.ts @@ -1,34 +1,37 @@ import { attachments } from "./attachments"; -const capitatedAmendmentBase = { + +export const appkBase = { id: "VA-1234.R11.01", - event: "capitated-amendment", + event: "app-k", authority: "1915(c)", proposedEffectiveDate: 1700000000, title: "Sample Title for Appendix K", attachments: { - ...attachments.bCapWaiverApplication, - ...attachments.bCapCostSpreadsheets, - ...attachments.tribalConsultation, + ...attachments.appk, ...attachments.other, }, additionalInformation: "Some additional information about this submission.", - waiverNumber: "VA-1111.R11.11", }; -const appkBase = { - id: "VA-1234.R11.01", - event: "app-k", + +export const capitatedInitial = { + id: "VA-1234.R00.00", + event: "capitated-initial", authority: "1915(c)", proposedEffectiveDate: 1700000000, title: "Sample Title for Appendix K", attachments: { - ...attachments.appk, + ...attachments.bCapWaiverApplication, + ...attachments.bCapCostSpreadsheets, + ...attachments.tribalConsultation, ...attachments.other, }, additionalInformation: "Some additional information about this submission.", + waiverNumber: "VA-1111.R11.11", }; -const capitatedInitial = { - id: "VA-1234.R00.00", - event: "capitated-initial", + +export const capitatedAmendmentBase = { + id: "VA-1234.R11.01", + event: "capitated-amendment", authority: "1915(c)", proposedEffectiveDate: 1700000000, title: "Sample Title for Appendix K", @@ -41,7 +44,8 @@ const capitatedInitial = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.11", }; -const capitatedRenewal = { + +export const capitatedRenewal = { id: "VA-1234.R01.00", event: "capitated-renewal", authority: "1915(c)", @@ -57,9 +61,10 @@ const capitatedRenewal = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.11", }; -const contractingAmendment = { - id: "VA-1234.R11.01", - event: "contracting-amendment", + +export const contractingInitial = { + id: "VA-1234.R00.00", + event: "contracting-initial", authority: "1915(b)", proposedEffectiveDate: 1700000000, title: "Sample Title for Appendix K", @@ -71,9 +76,10 @@ const contractingAmendment = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.11", }; -const contractingInitial = { - id: "VA-1234.R00.00", - event: "contracting-initial", + +export const contractingAmendment = { + id: "VA-1234.R11.01", + event: "contracting-amendment", authority: "1915(b)", proposedEffectiveDate: 1700000000, title: "Sample Title for Appendix K", @@ -85,7 +91,8 @@ const contractingInitial = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.11", }; -const contractingRenewal = { + +export const contractingRenewal = { id: "VA-1234.R01.00", event: "contracting-renewal", authority: "1915(b)", @@ -100,7 +107,8 @@ const contractingRenewal = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R01.00", }; -const newChipSubmission = { + +export const newChipSubmission = { id: "VA-11-2021", event: "new-chip-submission", authority: "1915(b)", @@ -118,7 +126,8 @@ const newChipSubmission = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.11", }; -const newMedicaidSubmission = { + +export const newMedicaidSubmission = { id: "VA-11-2021", event: "new-medicaid-submission", authority: "1915(b)", @@ -138,20 +147,19 @@ const newMedicaidSubmission = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.11", }; -const respondToRai = { - id: "VA-11-2020", - event: "respond-to-rai", + +export const uploadSubsequentDocuments = { + id: "VA-1111.R11.00", + event: "upload-subsequent-documents", authority: "1915(b)", proposedEffectiveDate: 1700000000, title: "Sample Title for Appendix K", - attachments: { - ...attachments.raiResponseLetter, - ...attachments.other, - }, + attachments: {}, additionalInformation: "Some additional information about this submission.", - waiverNumber: "VA-1111.R11.11", + waiverNumber: "VA-1111.R11.00", }; -const temporaryExtension = { + +export const temporaryExtension = { id: "VA-1234.R11.TE12", event: "temporary-extension", authority: "1915(b)", @@ -164,14 +172,30 @@ const temporaryExtension = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.00", }; -const toggleWithdrawRai = { + +export const respondToRai = { + id: "VA-11-2020", + event: "respond-to-rai", + authority: "1915(b)", + proposedEffectiveDate: 1700000000, + title: "Sample Title for Appendix K", + attachments: { + ...attachments.raiResponseLetter, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", + waiverNumber: "VA-1111.R11.11", +}; + +export const toggleWithdrawRai = { id: "VA-11-2020", event: "toggle-withdraw-rai", authority: "1915(b)", raiWithdrawEnabled: true, proposedEffectiveDate: 1700000000, }; -const withdrawPackage = { + +export const withdrawPackage = { id: "VA-11-2020", event: "withdraw-package", authority: "1915(b)", @@ -183,7 +207,8 @@ const withdrawPackage = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.00", }; -const withdrawRai = { + +export const withdrawRai = { id: "VA-11-2020", event: "withdraw-rai", authority: "1915(b)", @@ -195,16 +220,6 @@ const withdrawRai = { additionalInformation: "Some additional information about this submission.", waiverNumber: "VA-1111.R11.00", }; -const uploadSubsequentDocuments = { - id: "VA-1111.R11.00", - event: "upload-subsequent-documents", - authority: "1915(b)", - proposedEffectiveDate: 1700000000, - title: "Sample Title for Appendix K", - attachments: {}, - additionalInformation: "Some additional information about this submission.", - waiverNumber: "VA-1111.R11.00", -}; export const eventsAttachmentRequired = [ appkBase, diff --git a/mocks/data/submit/changelog.ts b/mocks/data/submit/changelog.ts new file mode 100644 index 0000000000..60ffb3eaed --- /dev/null +++ b/mocks/data/submit/changelog.ts @@ -0,0 +1,193 @@ +import { attachments } from "./attachments"; + +export const appkBase = { + id: "VA-1234.R11.01", + event: "app-k", + authority: "1915(c)", + proposedEffectiveDate: 1700000000, + title: "Sample Title for Appendix K", + attachments: { + ...attachments.appk, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", +}; + +export const capitatedInitial = { + id: "VA-1234.R00.00", + event: "capitated-initial", + authority: "1915(c)", + proposedEffectiveDate: 1700000000, + attachments: { + ...attachments.bCapWaiverApplication, + ...attachments.bCapCostSpreadsheets, + ...attachments.tribalConsultation, + ...attachments.other, + }, +}; + +export const capitatedAmendmentBase = { + id: "VA-1234.R11.01", + event: "capitated-amendment", + authority: "1915(c)", + proposedEffectiveDate: 1700000000, + attachments: { + ...attachments.bCapWaiverApplication, + ...attachments.bCapCostSpreadsheets, + ...attachments.tribalConsultation, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", + waiverNumber: "VA-1111.R11.11", +}; + +export const capitatedRenewal = { + id: "VA-1234.R01.00", + event: "capitated-renewal", + authority: "1915(c)", + proposedEffectiveDate: 1700000000, + attachments: { + ...attachments.bCapIndependentAssessment, + ...attachments.bCapWaiverApplication, + ...attachments.bCapCostSpreadsheets, + ...attachments.tribalConsultation, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", + waiverNumber: "VA-1111.R11.11", +}; + +export const contractingInitial = { + id: "VA-1234.R00.00", + event: "contracting-initial", + authority: "1915(b)", + proposedEffectiveDate: 1700000000, + attachments: { + ...attachments.b4Waiver, + ...attachments.tribalConsultation, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", +}; + +export const contractingAmendment = { + id: "VA-1234.R11.01", + event: "contracting-amendment", + authority: "1915(b)", + proposedEffectiveDate: 1700000000, + attachments: { + ...attachments.b4Waiver, + ...attachments.tribalConsultation, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", + waiverNumber: "VA-1111.R11.11", +}; + +export const contractingRenewal = { + id: "VA-1234.R01.00", + event: "contracting-renewal", + authority: "1915(b)", + proposedEffectiveDate: 1700000000, + attachments: { + ...attachments.b4IndependentAssessment, + ...attachments.b4Waiver, + ...attachments.tribalConsultation, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", + waiverNumber: "VA-1111.R01.00", +}; + +export const newChipSubmission = { + id: "VA-11-2021", + event: "new-chip-submission", + authority: "1915(b)", + proposedEffectiveDate: 1700000000, + attachments: { + ...attachments.currentStatePlan, + ...attachments.amendedLanguage, + ...attachments.coverLetter, + ...attachments.budgetDocuments, + ...attachments.publicNotice, + ...attachments.tribalConsultation, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", +}; + +export const newMedicaidSubmission = { + id: "VA-11-2021", + event: "new-medicaid-submission", + authority: "1915(b)", + proposedEffectiveDate: 1700000000, + attachments: { + ...attachments.cmsForm, + ...attachments.spaPages, + ...attachments.coverLetter, + ...attachments.tribalEngagement, + ...attachments.existingStatePlanPages, + ...attachments.publicNotice, + ...attachments.sfq, + ...attachments.tribalConsultation, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", +}; + +export const uploadSubsequentDocuments = { + id: "VA-1111.R11.00", + event: "upload-subsequent-documents", + attachments: {}, + additionalInformation: "Some additional information about this submission.", +}; + +export const temporaryExtension = { + id: "VA-1234.R11.TE12", + event: "temporary-extension", + authority: "1915(b)", + attachments: { + ...attachments.waiverExtensionRequest, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", + waiverNumber: "VA-1111.R11.00", +}; + +export const respondToRai = { + id: "VA-11-2020", + event: "respond-to-rai", + attachments: { + ...attachments.raiResponseLetter, + ...attachments.other, + }, + additionalInformation: "Some additional information about this submission.", +}; + +export const toggleWithdrawRai = { + id: "VA-11-2020", + event: "toggle-withdraw-rai", + authority: "1915(b)", + raiWithdrawEnabled: true, +}; + +export const withdrawPackage = { + id: "VA-11-2020", + event: "withdraw-package", + authority: "1915(b)", + attachments: { + ...attachments.supportingDocumentation, + }, + additionalInformation: "Some additional information about this submission.", +}; + +export const withdrawRai = { + id: "VA-11-2020-1", + packageId: "VA-11-2020", + event: "withdraw-rai", + authority: "1915(b)", + attachments: { + ...attachments.supportingDocumentation, + }, + additionalInformation: "Some additional information about this submission.", +}; diff --git a/mocks/handlers/opensearch/main.ts b/mocks/handlers/opensearch/main.ts index 517f1154d0..fdcd6efb7f 100644 --- a/mocks/handlers/opensearch/main.ts +++ b/mocks/handlers/opensearch/main.ts @@ -7,6 +7,7 @@ import { TestAppkItemResult, TestItemResult, TestMainDocument, + GetMultiItemBody, } from "../../index.d"; import { getTermKeys, filterItemsByTerm, getTermValues } from "./util"; @@ -14,6 +15,7 @@ const defaultMainDocumentHandler = http.get( `https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_doc/:id`, async ({ params }) => { const { id } = params; + if (id == GET_ERROR_ITEM_ID) { return new HttpResponse("Internal server error", { status: 500 }); } @@ -27,6 +29,19 @@ const defaultMainDocumentHandler = http.get( }, ); +const defaultMainMultiDocumentHandler = http.post( + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_mget", + async ({ request }) => { + const { ids } = await request.json(); + + const mItems = Object.values(items).filter((item) => ids.includes(item?._id || "")) || []; + + return HttpResponse.json({ + docs: mItems, + }); + }, +); + const defaultMainSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_search", async ({ request }) => { @@ -135,4 +150,8 @@ const defaultMainSearchHandler = http.post( }, ); -export const mainSearchHandlers = [defaultMainDocumentHandler, defaultMainSearchHandler]; +export const mainSearchHandlers = [ + defaultMainDocumentHandler, + defaultMainMultiDocumentHandler, + defaultMainSearchHandler, +]; diff --git a/mocks/helpers/kafka-test-helpers.ts b/mocks/helpers/kafka-test-helpers.ts index 1423b74be5..76eb2fdf26 100644 --- a/mocks/helpers/kafka-test-helpers.ts +++ b/mocks/helpers/kafka-test-helpers.ts @@ -1,4 +1,5 @@ import { vi } from "vitest"; +import { KafkaEvent, KafkaRecord } from "shared-types"; export const mockedProducer = { connect: vi.fn(), @@ -92,3 +93,33 @@ export const mockedKafka = vi.fn(() => ({ producer: vi.fn(() => mockedProducer), admin: vi.fn(() => mockedAdmin), })); + +export const convertObjToBase64 = (obj: object) => + Buffer.from(JSON.stringify(obj)).toString("base64"); + +export const createKafkaEvent = (records: KafkaEvent["records"]) => ({ + eventSource: "SelfManagedKafka", + bootstrapServers: "kafka", + records, +}); + +export const createKafkaRecord = ({ + topic, + key, + value, + offset = 0, +}: { + topic: string; + key: string; + value: string; + offset?: number; +}): KafkaRecord => ({ + key, + value, + offset, + topic, + partition: 0, + timestamp: Date.now(), + timestampType: "CREATE_TIME", + headers: {}, +}); diff --git a/mocks/index.d.ts b/mocks/index.d.ts index ab1ab37c07..2def0c55ee 100644 --- a/mocks/index.d.ts +++ b/mocks/index.d.ts @@ -134,6 +134,8 @@ export type SearchQueryBody = { export type GetItemBody = { id: string }; +export type GetMultiItemBody = { ids: string[] }; + export type EventRequestContext = Partial; export type TestEventSourceMapping = { From 8c4be801d0755b2dc8a04dc2ff481c63e76aa296 Mon Sep 17 00:00:00 2001 From: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:58:04 -0500 Subject: [PATCH 04/17] fix(lambda): ignore `null` values for Proposed Effective Date (#999) * fix: ignore `proposedDate` if value from opensearch is `null` * chore: update test and add comment * chore: comment update * chore: revert logic and rely on zod to invalidate * feat: show Pending instead of dashes * chore: revert test change --- lib/packages/shared-types/events/seatool.ts | 36 +++++++++---------- .../package/package-details/hooks.tsx | 2 +- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/packages/shared-types/events/seatool.ts b/lib/packages/shared-types/events/seatool.ts index f20a2dd249..83fd3bb1e0 100644 --- a/lib/packages/shared-types/events/seatool.ts +++ b/lib/packages/shared-types/events/seatool.ts @@ -12,26 +12,22 @@ export type SeatoolOfficer = z.infer; export const seatoolSchema = z.object({ ACTION_OFFICERS: z.array(seatoolOfficerSchema).nullish(), LEAD_ANALYST: z.array(seatoolOfficerSchema).nullable(), - STATE_PLAN_SERVICETYPES: z - .array( - z - .object({ - SPA_TYPE_ID: z.number(), - SPA_TYPE_NAME: z.string(), - }) - .nullable(), - ) - .nullable(), - STATE_PLAN_SERVICE_SUBTYPES: z - .array( - z - .object({ - TYPE_ID: z.number(), - TYPE_NAME: z.string(), - }) - .nullable(), - ) - .nullable(), + STATE_PLAN_SERVICETYPES: z.array( + z + .object({ + SPA_TYPE_ID: z.number(), + SPA_TYPE_NAME: z.string(), + }) + .nullable(), + ), + STATE_PLAN_SERVICE_SUBTYPES: z.array( + z + .object({ + TYPE_ID: z.number(), + TYPE_NAME: z.string(), + }) + .nullable(), + ), STATE_PLAN: z.object({ SUBMISSION_DATE: z.number().nullable(), PLAN_TYPE: z.number().nullable(), diff --git a/react-app/src/features/package/package-details/hooks.tsx b/react-app/src/features/package/package-details/hooks.tsx index 15fadab519..c438237073 100644 --- a/react-app/src/features/package/package-details/hooks.tsx +++ b/react-app/src/features/package/package-details/hooks.tsx @@ -98,7 +98,7 @@ export const recordDetails = (data: opensearch.main.Document): DetailSectionItem }, { label: "Proposed effective date", - value: data.proposedDate ? formatSeatoolDate(data.proposedDate) : BLANK_VALUE, + value: data.proposedDate ? formatSeatoolDate(data.proposedDate) : "Pending", canView: () => { return !(data.actionType === "Extend"); }, From aef7de7b9b2a68ea4e098990797c0eea4cd2cc8c Mon Sep 17 00:00:00 2001 From: RanyeM <40363896+RanyeM@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:15:06 -0500 Subject: [PATCH 05/17] fix(ui): Fixed MACPro Logo (#994) * fix(ui): Fixed MACPro Logo * Update react-app/src/features/selection-flow/external-landing/ExternalAppLandingPage.tsx Co-authored-by: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> --------- Co-authored-by: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> --- react-app/src/assets/macpro.png | Bin 0 -> 16666 bytes .../external-landing/ExternalAppLandingPage.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 react-app/src/assets/macpro.png diff --git a/react-app/src/assets/macpro.png b/react-app/src/assets/macpro.png new file mode 100644 index 0000000000000000000000000000000000000000..82e441d3bc6f24e859ee70390259d265929dc8e9 GIT binary patch literal 16666 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 literal 0 HcmV?d00001 diff --git a/react-app/src/features/selection-flow/external-landing/ExternalAppLandingPage.tsx b/react-app/src/features/selection-flow/external-landing/ExternalAppLandingPage.tsx index 86f66066ca..c7c5b5f617 100644 --- a/react-app/src/features/selection-flow/external-landing/ExternalAppLandingPage.tsx +++ b/react-app/src/features/selection-flow/external-landing/ExternalAppLandingPage.tsx @@ -16,7 +16,7 @@ export interface ExternalAppLandingPageConfig { buttonLabel: string; buttonLink: EXTERNAL_APP; } -const MACProLogo = () => {"MACPro; +const MACProLogo = () => MACPro system logo; const FAQHelperText = () => ( From e3deb2cbf750f3c531131df44a8a42a258851dbf Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 14 Jan 2025 12:01:53 -0700 Subject: [PATCH 06/17] =?UTF-8?q?fix(=F0=9F=90=AA):=20Rename=20some=20fold?= =?UTF-8?q?ers=20to=20CamelCase=20(#1019)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename some files to CamelCase * Fix path * Fix merge issues * fixing imports * one more time --------- Co-authored-by: Thomas --- lib/lambda/processEmailsHandler.test.ts | 134 +++++ lib/libs/email/content/index.ts | 6 +- .../emailTemplates/AppKCMS.tsx | 0 .../emailTemplates/AppKState.tsx | 0 .../emailTemplates/ChipSpaCMS.tsx | 5 +- .../emailTemplates/ChipSpaState.tsx | 0 .../emailTemplates/MedSpaCMS.tsx | 0 .../emailTemplates/MedSpaState.tsx | 0 .../emailTemplates/Waiver1915bCMS.tsx | 0 .../emailTemplates/Waiver1915bState.tsx | 0 .../emailTemplates/index.tsx | 0 .../index.tsx | 0 .../emailTemplates/AppKCMS.tsx | 0 .../emailTemplates/AppKState.tsx | 0 .../emailTemplates/ChipSpaCMS.tsx | 0 .../emailTemplates/ChipSpaState.tsx | 0 .../emailTemplates/MedSpaCMS.tsx | 8 +- .../emailTemplates/MedSpaState.tsx | 0 .../emailTemplates/Waiver1915BCMS.tsx | 0 .../emailTemplates/Waiver1915BState.tsx | 0 .../emailTemplates/index.tsx | 4 +- .../index.tsx | 0 .../withdrawRai/emailTemplates/AppKCMS.tsx | 20 +- .../withdrawRai/emailTemplates/AppKState.tsx | 17 +- .../emailTemplates/Waiver1915bCMS.tsx | 11 +- .../emailTemplates/Waiver1915bState.tsx | 33 +- .../withdrawRai/emailTemplates/index.tsx | 4 +- lib/libs/email/content/withdrawRai/index.tsx | 25 +- .../CMS/CHIP_SPA.tsx | 2 +- .../CMS/InitialSubmissionCMS.test.tsx | 0 .../CMS/Medicaid_SPA.tsx | 2 +- .../CMS/Temp_Extension.tsx | 0 .../CMS/Waiver_Capitated.tsx | 4 +- .../CMS/Waiver_Contracting.tsx | 4 +- .../CMS/__snapshots__/AppK.tsx | 2 +- .../InitialSubmissionCMS.test.tsx.snap | 0 .../State/AppK.tsx | 2 +- .../State/CHIP_SPA.tsx | 2 +- .../State/InitialSubmissionState.test.tsx | 0 .../State/Medicaid_SPA.tsx | 2 +- .../State/Temp_Extension.tsx | 0 .../State/Waiver_Capitated.tsx | 2 +- .../State/Waiver_Contracting.tsx | 2 +- .../InitialSubmissionState.test.tsx.snap | 0 .../CMS/Temp_Extension.tsx | 14 + .../CMS/AppK.tsx | 0 .../CMS/CHIP_SPA.tsx | 0 .../CMS/Medicaid_SPA.tsx | 0 .../CMS/ResToRaiCMS.test.tsx | 0 .../CMS/Waiver_Capitated.tsx | 0 .../__snapshots__/ResToRaiCMS.test.tsx.snap | 0 .../State/AppK.tsx | 0 .../State/CHIP_SPA.tsx | 0 .../State/Medicaid_SPA.tsx | 0 .../State/ResToRaiState.test.tsx | 0 .../State/Waiver_Capitated.tsx | 2 +- .../__snapshots__/ResToRaiState.test.tsx.snap | 0 .../CMS/AppK.tsx | 2 +- .../CMS/CHIP_SPA.tsx | 2 +- .../CMS/MED_SPA.tsx | 2 +- .../CMS/UpSubDocCMS.test.tsx | 0 .../CMS/Waiver1915b.tsx | 2 +- .../__snapshots__/UpSubDocCMS.test.tsx.snap | 0 .../State/AppK.tsx | 6 +- .../State/CHIP_SPA.tsx | 4 +- .../State/MED_SPA.tsx | 2 +- .../State/UpSubDocState.test.tsx | 0 .../State/Waiver1915b.tsx | 2 +- .../__snapshots__/UpSubDocState.test.tsx.snap | 472 ++++++------------ .../CMS/AppK.tsx | 0 .../CMS/CHIP_SPA.tsx | 0 .../CMS/Medicaid_SPA.tsx | 0 .../CMS/Waiver_Capitated.tsx | 0 .../CMS/WithdrawPackageCMS.test.tsx | 0 .../WithdrawPackageCMS.test.tsx.snap | 0 .../State/AppK.tsx | 0 .../State/CHIP_SPA.tsx | 0 .../State/Medicaid_SPA.tsx | 0 .../State/Waiver_Capitated.tsx | 0 .../State/WithdrawPackageState.test.tsx | 0 .../WithdrawPackageState.test.tsx.snap | 0 81 files changed, 399 insertions(+), 402 deletions(-) create mode 100644 lib/lambda/processEmailsHandler.test.ts rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/AppKCMS.tsx (100%) rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/AppKState.tsx (100%) rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/ChipSpaCMS.tsx (93%) rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/ChipSpaState.tsx (100%) rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/MedSpaCMS.tsx (100%) rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/MedSpaState.tsx (100%) rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/Waiver1915bCMS.tsx (100%) rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/Waiver1915bState.tsx (100%) rename lib/libs/email/content/{new-submission => newSubmission}/emailTemplates/index.tsx (100%) rename lib/libs/email/content/{new-submission => newSubmission}/index.tsx (100%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/AppKCMS.tsx (100%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/AppKState.tsx (100%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/ChipSpaCMS.tsx (100%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/ChipSpaState.tsx (100%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/MedSpaCMS.tsx (91%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/MedSpaState.tsx (100%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/Waiver1915BCMS.tsx (100%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/Waiver1915BState.tsx (100%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/emailTemplates/index.tsx (72%) rename lib/libs/email/content/{upload-subsequent-documents => uploadSubsequentDocuments}/index.tsx (100%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/CMS/CHIP_SPA.tsx (89%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/CMS/InitialSubmissionCMS.test.tsx (100%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/CMS/Medicaid_SPA.tsx (90%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/CMS/Temp_Extension.tsx (100%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/CMS/Waiver_Capitated.tsx (96%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/CMS/Waiver_Contracting.tsx (95%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/CMS/__snapshots__/AppK.tsx (88%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/CMS/__snapshots__/InitialSubmissionCMS.test.tsx.snap (100%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/State/AppK.tsx (88%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/State/CHIP_SPA.tsx (89%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/State/InitialSubmissionState.test.tsx (100%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/State/Medicaid_SPA.tsx (91%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/State/Temp_Extension.tsx (100%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/State/Waiver_Capitated.tsx (98%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/State/Waiver_Contracting.tsx (97%) rename lib/libs/email/preview/{Initial Submissions => InitialSubmissions}/State/__snapshots__/InitialSubmissionState.test.tsx.snap (100%) create mode 100644 lib/libs/email/preview/Initial_Submissions/CMS/Temp_Extension.tsx rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/CMS/AppK.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/CMS/CHIP_SPA.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/CMS/Medicaid_SPA.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/CMS/ResToRaiCMS.test.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/CMS/Waiver_Capitated.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/CMS/__snapshots__/ResToRaiCMS.test.tsx.snap (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/State/AppK.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/State/CHIP_SPA.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/State/Medicaid_SPA.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/State/ResToRaiState.test.tsx (100%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/State/Waiver_Capitated.tsx (92%) rename lib/libs/email/preview/{Respond to Rai => RespondToRai}/State/__snapshots__/ResToRaiState.test.tsx.snap (100%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/CMS/AppK.tsx (87%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/CMS/CHIP_SPA.tsx (89%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/CMS/MED_SPA.tsx (90%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/CMS/UpSubDocCMS.test.tsx (100%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/CMS/Waiver1915b.tsx (88%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/CMS/__snapshots__/UpSubDocCMS.test.tsx.snap (100%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/State/AppK.tsx (67%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/State/CHIP_SPA.tsx (87%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/State/MED_SPA.tsx (89%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/State/UpSubDocState.test.tsx (100%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/State/Waiver1915b.tsx (88%) rename lib/libs/email/preview/{Upload Subsequent Documents => UploadSubsequentDocuments}/State/__snapshots__/UpSubDocState.test.tsx.snap (96%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/CMS/AppK.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/CMS/CHIP_SPA.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/CMS/Medicaid_SPA.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/CMS/Waiver_Capitated.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/CMS/WithdrawPackageCMS.test.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/CMS/__snapshots__/WithdrawPackageCMS.test.tsx.snap (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/State/AppK.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/State/CHIP_SPA.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/State/Medicaid_SPA.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/State/Waiver_Capitated.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/State/WithdrawPackageState.test.tsx (100%) rename lib/libs/email/preview/{Withdraw Package => WithdrawPackage}/State/__snapshots__/WithdrawPackageState.test.tsx.snap (100%) diff --git a/lib/lambda/processEmailsHandler.test.ts b/lib/lambda/processEmailsHandler.test.ts new file mode 100644 index 0000000000..af672db109 --- /dev/null +++ b/lib/lambda/processEmailsHandler.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect, vi } from "vitest"; +import { Context } from "aws-lambda"; +import { SESClient } from "@aws-sdk/client-ses"; +import { handler } from "./processEmails"; +import { KafkaRecord, KafkaEvent } from "shared-types"; +import { Authority } from "shared-types"; + +const nms = "new-medicaid-submission"; +const ncs = "new-chip-submission"; +const tempExtension = "temp-extension"; +const withdrawPackage = "withdraw-package"; +const contractingInitial = "contracting-initial"; +const capitatedInitial = "capitated-initial"; + +describe("process emails Handler", () => { + it.each([ + [`should send an email for ${nms} with ${Authority.MED_SPA}`, Authority.MED_SPA, nms], + [`should send an email for ${nms} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, nms], + [`should send an email for ${nms} with ${Authority["1915b"]}`, Authority["1915b"], nms], + [`should send an email for ${nms} with ${Authority["1915c"]}`, Authority["1915c"], nms], + [`should send an email for ${ncs} with ${Authority.MED_SPA}`, Authority.MED_SPA, ncs], + [`should send an email for ${ncs} with ${Authority.CHIP_SPA}`, Authority.CHIP_SPA, ncs], + [`should send an email for ${ncs} with ${Authority["1915b"]}`, Authority["1915b"], ncs], + [`should send an email for ${ncs} with ${Authority["1915c"]}`, Authority["1915c"], ncs], + [ + `should send an email for ${tempExtension} with ${Authority.MED_SPA}`, + Authority.MED_SPA, + tempExtension, + ], + [ + `should send an email for ${tempExtension} with ${Authority.CHIP_SPA}`, + Authority.CHIP_SPA, + tempExtension, + ], + [ + `should send an email for ${tempExtension} with ${Authority["1915b"]}`, + Authority["1915b"], + tempExtension, + ], + [ + `should send an email for ${tempExtension} with ${Authority["1915c"]}`, + Authority["1915c"], + tempExtension, + ], + [ + `should send an email for ${withdrawPackage} with ${Authority.MED_SPA}`, + Authority.MED_SPA, + withdrawPackage, + ], + [ + `should send an email for ${withdrawPackage} with ${Authority.CHIP_SPA}`, + Authority.CHIP_SPA, + withdrawPackage, + ], + [ + `should send an email for ${withdrawPackage} for ${ncs} with ${Authority["1915b"]}`, + Authority["1915b"], + withdrawPackage, + ], + [ + `should send an email for ${withdrawPackage} with ${Authority["1915c"]}`, + Authority["1915c"], + withdrawPackage, + ], + [ + `should send an email for ${contractingInitial} with ${Authority.MED_SPA}`, + Authority.MED_SPA, + contractingInitial, + ], + [ + `should send an email for ${contractingInitial} with ${Authority.CHIP_SPA}`, + Authority.CHIP_SPA, + contractingInitial, + ], + [ + `should send an email for ${contractingInitial} with ${Authority["1915b"]}`, + Authority["1915b"], + contractingInitial, + ], + [ + `should send an email for ${contractingInitial} with ${Authority["1915c"]}`, + Authority["1915c"], + contractingInitial, + ], + [ + `should send an email for ${capitatedInitial} with ${Authority.MED_SPA}`, + Authority.MED_SPA, + capitatedInitial, + ], + [ + `should send an email for ${capitatedInitial} with ${Authority.CHIP_SPA}`, + Authority.CHIP_SPA, + capitatedInitial, + ], + [ + `should send an email for ${capitatedInitial} with ${Authority["1915b"]}`, + Authority["1915b"], + capitatedInitial, + ], + [ + `should send an email for ${capitatedInitial} with ${Authority["1915c"]}`, + Authority["1915c"], + capitatedInitial, + ], + ])("%s", async (_, auth, eventType) => { + const callback = vi.fn(); + const secSPY = vi.spyOn(SESClient.prototype, "send"); + const mockEvent: KafkaEvent = { + records: { + "mock-topic": [ + { + key: Buffer.from("VA").toString("base64"), + value: Buffer.from( + JSON.stringify({ + origin: "mako", + event: eventType, + authority: auth, + }), + ).toString("base64"), + headers: {}, + timestamp: 1732645041557, + offset: "0", + partition: 0, + topic: "mock-topic", + } as unknown as KafkaRecord, + ], + }, + eventSource: "", + bootstrapServers: "", + }; + await handler(mockEvent, {} as Context, callback); + expect(secSPY).toHaveBeenCalledTimes(2); + }); +}); \ No newline at end of file diff --git a/lib/libs/email/content/index.ts b/lib/libs/email/content/index.ts index 7ae4b2c128..2b3e35790c 100644 --- a/lib/libs/email/content/index.ts +++ b/lib/libs/email/content/index.ts @@ -1,8 +1,8 @@ -export * from "./new-submission"; +export * from "./newSubmission"; export * from "./tempExtension"; -export * from "./respondToRai"; export * from "./withdrawRai"; export * from "./withdrawPackage"; +export * from "./withdrawConfirmation"; export * from "./email-components"; export * from "./respondToRai"; -export * from "./upload-subsequent-documents"; +export * from "./uploadSubsequentDocuments"; diff --git a/lib/libs/email/content/new-submission/emailTemplates/AppKCMS.tsx b/lib/libs/email/content/newSubmission/emailTemplates/AppKCMS.tsx similarity index 100% rename from lib/libs/email/content/new-submission/emailTemplates/AppKCMS.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/AppKCMS.tsx diff --git a/lib/libs/email/content/new-submission/emailTemplates/AppKState.tsx b/lib/libs/email/content/newSubmission/emailTemplates/AppKState.tsx similarity index 100% rename from lib/libs/email/content/new-submission/emailTemplates/AppKState.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/AppKState.tsx diff --git a/lib/libs/email/content/new-submission/emailTemplates/ChipSpaCMS.tsx b/lib/libs/email/content/newSubmission/emailTemplates/ChipSpaCMS.tsx similarity index 93% rename from lib/libs/email/content/new-submission/emailTemplates/ChipSpaCMS.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/ChipSpaCMS.tsx index d6ec0a379d..a4d9c67679 100644 --- a/lib/libs/email/content/new-submission/emailTemplates/ChipSpaCMS.tsx +++ b/lib/libs/email/content/newSubmission/emailTemplates/ChipSpaCMS.tsx @@ -7,10 +7,11 @@ import { } from "../../email-components"; import { BaseEmailTemplate } from "../../email-templates"; -export const ChipSpaCMSEmail = (props: { +export const ChipSpaCMSEmail = ({ + variables, +}: { variables: Events["NewChipSubmission"] & CommonEmailVariables; }) => { - const variables = props.variables; const previewText = `CHIP SPA ${variables.id} Submitted`; const heading = "The OneMAC Submission Portal received a CHIP State Plan Amendment:"; return ( diff --git a/lib/libs/email/content/new-submission/emailTemplates/ChipSpaState.tsx b/lib/libs/email/content/newSubmission/emailTemplates/ChipSpaState.tsx similarity index 100% rename from lib/libs/email/content/new-submission/emailTemplates/ChipSpaState.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/ChipSpaState.tsx diff --git a/lib/libs/email/content/new-submission/emailTemplates/MedSpaCMS.tsx b/lib/libs/email/content/newSubmission/emailTemplates/MedSpaCMS.tsx similarity index 100% rename from lib/libs/email/content/new-submission/emailTemplates/MedSpaCMS.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/MedSpaCMS.tsx diff --git a/lib/libs/email/content/new-submission/emailTemplates/MedSpaState.tsx b/lib/libs/email/content/newSubmission/emailTemplates/MedSpaState.tsx similarity index 100% rename from lib/libs/email/content/new-submission/emailTemplates/MedSpaState.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/MedSpaState.tsx diff --git a/lib/libs/email/content/new-submission/emailTemplates/Waiver1915bCMS.tsx b/lib/libs/email/content/newSubmission/emailTemplates/Waiver1915bCMS.tsx similarity index 100% rename from lib/libs/email/content/new-submission/emailTemplates/Waiver1915bCMS.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/Waiver1915bCMS.tsx diff --git a/lib/libs/email/content/new-submission/emailTemplates/Waiver1915bState.tsx b/lib/libs/email/content/newSubmission/emailTemplates/Waiver1915bState.tsx similarity index 100% rename from lib/libs/email/content/new-submission/emailTemplates/Waiver1915bState.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/Waiver1915bState.tsx diff --git a/lib/libs/email/content/new-submission/emailTemplates/index.tsx b/lib/libs/email/content/newSubmission/emailTemplates/index.tsx similarity index 100% rename from lib/libs/email/content/new-submission/emailTemplates/index.tsx rename to lib/libs/email/content/newSubmission/emailTemplates/index.tsx diff --git a/lib/libs/email/content/new-submission/index.tsx b/lib/libs/email/content/newSubmission/index.tsx similarity index 100% rename from lib/libs/email/content/new-submission/index.tsx rename to lib/libs/email/content/newSubmission/index.tsx diff --git a/lib/libs/email/content/upload-subsequent-documents/emailTemplates/AppKCMS.tsx b/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/AppKCMS.tsx similarity index 100% rename from lib/libs/email/content/upload-subsequent-documents/emailTemplates/AppKCMS.tsx rename to lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/AppKCMS.tsx diff --git a/lib/libs/email/content/upload-subsequent-documents/emailTemplates/AppKState.tsx b/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/AppKState.tsx similarity index 100% rename from lib/libs/email/content/upload-subsequent-documents/emailTemplates/AppKState.tsx rename to lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/AppKState.tsx diff --git a/lib/libs/email/content/upload-subsequent-documents/emailTemplates/ChipSpaCMS.tsx b/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/ChipSpaCMS.tsx similarity index 100% rename from lib/libs/email/content/upload-subsequent-documents/emailTemplates/ChipSpaCMS.tsx rename to lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/ChipSpaCMS.tsx diff --git a/lib/libs/email/content/upload-subsequent-documents/emailTemplates/ChipSpaState.tsx b/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/ChipSpaState.tsx similarity index 100% rename from lib/libs/email/content/upload-subsequent-documents/emailTemplates/ChipSpaState.tsx rename to lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/ChipSpaState.tsx diff --git a/lib/libs/email/content/upload-subsequent-documents/emailTemplates/MedSpaCMS.tsx b/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/MedSpaCMS.tsx similarity index 91% rename from lib/libs/email/content/upload-subsequent-documents/emailTemplates/MedSpaCMS.tsx rename to lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/MedSpaCMS.tsx index 873c4c9acb..f994c33117 100644 --- a/lib/libs/email/content/upload-subsequent-documents/emailTemplates/MedSpaCMS.tsx +++ b/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/MedSpaCMS.tsx @@ -7,11 +7,11 @@ import { } from "../../email-components"; import { BaseEmailTemplate } from "../../email-templates"; -export const MedSpaCMSEmail = (props: { - variables: Events["UploadSubsequentDocuments"] & CommonEmailVariables; +export const MedSpaCMSEmail = ({ + variables, +}: { + variables: Events["UploadSubsequentDocuments"] & CommonEmailVariables }) => { - const variables = props.variables; - return ( { +export const AppKCMSEmail = ({ + variables, + relatedEvent, +}: { + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }; + relatedEvent: Events["RespondToRai"]; +}) => { const previewText = `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`; - const heading = `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`; - + const heading = `The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for ${relatedEvent.id} was withdrawn by ${variables.submitterName} ${variables.submitterEmail}.`; return ( { applicationEndpointUrl={variables.applicationEndpointUrl} footerContent={} > - { - const { variables, relatedEvent } = { ...props }; +export const AppKStateEmail = ({ + variables, + relatedEvent, +}: { + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }; + relatedEvent: Events["RespondToRai"]; +}) => { const previewText = `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`; const heading = `The OneMAC Submission Portal received a request to withdraw the Formal RAI Response. You are receiving this email notification as the Formal RAI for ${relatedEvent.id} was withdrawn by ${variables.submitterName} ${variables.submitterEmail}.`; return ( @@ -23,10 +28,10 @@ export const AppKStateEmail = (props: WithdrawRAIProps) => { diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bCMS.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bCMS.tsx index 10464ab011..18e049e21b 100644 --- a/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bCMS.tsx +++ b/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bCMS.tsx @@ -1,7 +1,14 @@ -import { WithdrawRAI, PackageDetails, BasicFooter, WithdrawRAIProps } from "../../email-components"; +import { CommonEmailVariables, Events, EmailAddresses } from "shared-types"; +import { WithdrawRAI, PackageDetails, BasicFooter } from "../../email-components"; import { BaseEmailTemplate } from "../../email-templates"; -export const Waiver1915bCMSEmail = ({ variables, relatedEvent }: WithdrawRAIProps) => { +export const Waiver1915bCMSEmail = ({ + variables, + relatedEvent, +}: { + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }; + relatedEvent: Events["RespondToRai"]; +}) => { const previewText = `Waiver Package ${relatedEvent.id} withdrawn`; const heading = `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`; diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bState.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bState.tsx index 00c9d80614..d2f41403c4 100644 --- a/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bState.tsx +++ b/lib/libs/email/content/withdrawRai/emailTemplates/Waiver1915bState.tsx @@ -1,30 +1,31 @@ -import { - WithdrawRAI, - PackageDetails, - FollowUpNotice, - MailboxNotice, - WithdrawRAIProps, -} from "../../email-components"; +import { CommonEmailVariables, EmailAddresses, Events } from "shared-types"; +import { WithdrawRAI, PackageDetails, FollowUpNotice, MailboxNotice } from "../../email-components"; import { BaseEmailTemplate } from "../../email-templates"; -export const Waiver1915bStateEmail = (props: WithdrawRAIProps) => { - const previewText = `Waiver ${props.relatedEvent.id} Withdrawn`; +export const Waiver1915bStateEmail = ({ + variables, + relatedEvent, +}: { + variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }; + relatedEvent: Events["RespondToRai"]; +}) => { + const previewText = `Waiver ${relatedEvent.id} Withdrawn`; const heading = "This response confirms you have withdrawn a Waiver from CMS for review"; return ( } > - + diff --git a/lib/libs/email/content/withdrawRai/emailTemplates/index.tsx b/lib/libs/email/content/withdrawRai/emailTemplates/index.tsx index 6a9b3d4662..669a424035 100644 --- a/lib/libs/email/content/withdrawRai/emailTemplates/index.tsx +++ b/lib/libs/email/content/withdrawRai/emailTemplates/index.tsx @@ -1,4 +1,4 @@ -export { Waiver1915bCMSEmail } from "./Waiver1915bCMS"; -export { Waiver1915bStateEmail } from "./Waiver1915bState"; export { AppKCMSEmail } from "./AppKCMS"; export { AppKStateEmail } from "./AppKState"; +export { Waiver1915bCMSEmail } from "./Waiver1915bCMS"; +export { Waiver1915bStateEmail } from "./Waiver1915bState"; diff --git a/lib/libs/email/content/withdrawRai/index.tsx b/lib/libs/email/content/withdrawRai/index.tsx index eb7e6bc152..5dde78064f 100644 --- a/lib/libs/email/content/withdrawRai/index.tsx +++ b/lib/libs/email/content/withdrawRai/index.tsx @@ -2,7 +2,7 @@ import { CommonEmailVariables, EmailAddresses, Events, Authority } from "shared- import { AuthoritiesWithUserTypesTemplate, getLatestMatchingEvent } from "../.."; import { Waiver1915bCMSEmail, Waiver1915bStateEmail, AppKCMSEmail } from "./emailTemplates"; import { render } from "@react-email/render"; -import { EmailProcessingError } from "../../errors"; +import { EmailProcessingError } from "libs/email/errors"; const getWithdrawRaiEvent = async (id: string) => { const event = await getLatestMatchingEvent(id, "WithdrawRai"); @@ -20,7 +20,10 @@ export const withdrawRai: AuthoritiesWithUserTypesTemplate = { variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }, ) => { try { - const relatedEvent = await getLatestMatchingEvent(variables.id, "RespondToRai"); + const relatedEvent = (await getLatestMatchingEvent( + variables.id, + "respond-to-rai", + )) as unknown as Events["RespondToRai"]; if (!relatedEvent) { throw new EmailProcessingError( `Failed to find original RAI response event for withdrawal (ID: ${variables.id})`, @@ -41,7 +44,7 @@ export const withdrawRai: AuthoritiesWithUserTypesTemplate = { ], subject: `Withdraw Formal RAI Response for Waiver Package ${variables.id}`, body: await render( - , + , ), }; } catch (error) { @@ -53,7 +56,10 @@ export const withdrawRai: AuthoritiesWithUserTypesTemplate = { variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }, ) => { try { - const relatedEvent = await getWithdrawRaiEvent(variables.id); + const relatedEvent = (await getLatestMatchingEvent( + variables.id, + "respond-to-rai", + )) as unknown as Events["RespondToRai"]; if (!relatedEvent) { throw new EmailProcessingError( `Failed to find original RAI response event for withdrawal (ID: ${variables.id})`, @@ -69,7 +75,7 @@ export const withdrawRai: AuthoritiesWithUserTypesTemplate = { to: variables.allStateUsersEmails || [], subject: `Withdraw Formal RAI Response for Waiver Package ${variables.id}`, body: await render( - , + , ), }; } catch (error) { @@ -83,7 +89,10 @@ export const withdrawRai: AuthoritiesWithUserTypesTemplate = { variables: Events["WithdrawRai"] & CommonEmailVariables & { emails: EmailAddresses }, ) => { try { - const relatedEvent = await getWithdrawRaiEvent(variables.id); + const relatedEvent = (await getLatestMatchingEvent( + variables.id, + "respond-to-rai", + )) as unknown as Events["RespondToRai"]; if (!relatedEvent) { throw new EmailProcessingError( `Failed to find original RAI response event for withdrawal (ID: ${variables.id})`, @@ -103,9 +112,7 @@ export const withdrawRai: AuthoritiesWithUserTypesTemplate = { ...variables.emails.srtEmails, ], subject: `Withdraw Formal RAI Response for Waiver Package ${relatedEvent.id}`, - body: await render( - , - ), + body: await render(), }; } catch (error) { console.error(error); diff --git a/lib/libs/email/preview/Initial Submissions/CMS/CHIP_SPA.tsx b/lib/libs/email/preview/InitialSubmissions/CMS/CHIP_SPA.tsx similarity index 89% rename from lib/libs/email/preview/Initial Submissions/CMS/CHIP_SPA.tsx rename to lib/libs/email/preview/InitialSubmissions/CMS/CHIP_SPA.tsx index 82d19a4fe4..019ff3aa50 100644 --- a/lib/libs/email/preview/Initial Submissions/CMS/CHIP_SPA.tsx +++ b/lib/libs/email/preview/InitialSubmissions/CMS/CHIP_SPA.tsx @@ -1,4 +1,4 @@ -import { ChipSpaCMSEmail } from "libs/email/content/new-submission/emailTemplates/ChipSpaCMS"; +import { ChipSpaCMSEmail } from "libs/email/content/newSubmission/emailTemplates"; import { emailTemplateValue } from "libs/email/mock-data/new-submission"; import * as attachments from "libs/email/mock-data/attachments"; diff --git a/lib/libs/email/preview/Initial Submissions/CMS/InitialSubmissionCMS.test.tsx b/lib/libs/email/preview/InitialSubmissions/CMS/InitialSubmissionCMS.test.tsx similarity index 100% rename from lib/libs/email/preview/Initial Submissions/CMS/InitialSubmissionCMS.test.tsx rename to lib/libs/email/preview/InitialSubmissions/CMS/InitialSubmissionCMS.test.tsx diff --git a/lib/libs/email/preview/Initial Submissions/CMS/Medicaid_SPA.tsx b/lib/libs/email/preview/InitialSubmissions/CMS/Medicaid_SPA.tsx similarity index 90% rename from lib/libs/email/preview/Initial Submissions/CMS/Medicaid_SPA.tsx rename to lib/libs/email/preview/InitialSubmissions/CMS/Medicaid_SPA.tsx index b7e3c7340a..d177d47f83 100644 --- a/lib/libs/email/preview/Initial Submissions/CMS/Medicaid_SPA.tsx +++ b/lib/libs/email/preview/InitialSubmissions/CMS/Medicaid_SPA.tsx @@ -1,4 +1,4 @@ -import { MedSpaCMSEmail } from "../../../content/new-submission/emailTemplates/MedSpaCMS"; +import { MedSpaCMSEmail } from "../../../content/newSubmission/emailTemplates/MedSpaCMS"; import { emailTemplateValue } from "../../../mock-data/new-submission"; import * as attachments from "../../../mock-data/attachments"; diff --git a/lib/libs/email/preview/Initial Submissions/CMS/Temp_Extension.tsx b/lib/libs/email/preview/InitialSubmissions/CMS/Temp_Extension.tsx similarity index 100% rename from lib/libs/email/preview/Initial Submissions/CMS/Temp_Extension.tsx rename to lib/libs/email/preview/InitialSubmissions/CMS/Temp_Extension.tsx diff --git a/lib/libs/email/preview/Initial Submissions/CMS/Waiver_Capitated.tsx b/lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Capitated.tsx similarity index 96% rename from lib/libs/email/preview/Initial Submissions/CMS/Waiver_Capitated.tsx rename to lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Capitated.tsx index 941d7331e3..d2d072419f 100644 --- a/lib/libs/email/preview/Initial Submissions/CMS/Waiver_Capitated.tsx +++ b/lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Capitated.tsx @@ -1,5 +1,5 @@ -import { Waiver1915bCMSEmail } from "libs/email/content/new-submission/emailTemplates/Waiver1915bCMS"; -import { emailTemplateValue } from "../../../mock-data/new-submission"; +import { Waiver1915bCMSEmail } from "libs/email/content/newSubmission/emailTemplates"; +import { emailTemplateValue } from "libs/email/mock-data/new-submission" ; export const Waiver1915bCMSCapitatedInitialEmailPreview = () => { return ( diff --git a/lib/libs/email/preview/Initial Submissions/CMS/Waiver_Contracting.tsx b/lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Contracting.tsx similarity index 95% rename from lib/libs/email/preview/Initial Submissions/CMS/Waiver_Contracting.tsx rename to lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Contracting.tsx index 18b1831615..baa42ffb87 100644 --- a/lib/libs/email/preview/Initial Submissions/CMS/Waiver_Contracting.tsx +++ b/lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Contracting.tsx @@ -1,5 +1,5 @@ -import { Waiver1915bCMSEmail } from "libs/email/content/new-submission/emailTemplates/Waiver1915bCMS"; -import { emailTemplateValue } from "../../../mock-data/new-submission"; +import { Waiver1915bCMSEmail } from "libs/email/content/newSubmission/emailTemplates"; +import { emailTemplateValue } from "libs/email/mock-data/new-submission"; export const Waiver1915bCMSContractingInitialEmailPreview = () => { return ( diff --git a/lib/libs/email/preview/Initial Submissions/CMS/__snapshots__/AppK.tsx b/lib/libs/email/preview/InitialSubmissions/CMS/__snapshots__/AppK.tsx similarity index 88% rename from lib/libs/email/preview/Initial Submissions/CMS/__snapshots__/AppK.tsx rename to lib/libs/email/preview/InitialSubmissions/CMS/__snapshots__/AppK.tsx index 617696b365..7bcfedf6ac 100644 --- a/lib/libs/email/preview/Initial Submissions/CMS/__snapshots__/AppK.tsx +++ b/lib/libs/email/preview/InitialSubmissions/CMS/__snapshots__/AppK.tsx @@ -1,4 +1,4 @@ -import { AppKCMSEmail } from "../../../../content/new-submission/emailTemplates"; +import { AppKCMSEmail } from "../../../../content/newSubmission/emailTemplates"; import { emailTemplateValue } from "../../../../mock-data/new-submission"; import * as attachments from "../../../../mock-data/attachments"; const AppKCMSEmailPreview = () => { diff --git a/lib/libs/email/preview/Initial Submissions/CMS/__snapshots__/InitialSubmissionCMS.test.tsx.snap b/lib/libs/email/preview/InitialSubmissions/CMS/__snapshots__/InitialSubmissionCMS.test.tsx.snap similarity index 100% rename from lib/libs/email/preview/Initial Submissions/CMS/__snapshots__/InitialSubmissionCMS.test.tsx.snap rename to lib/libs/email/preview/InitialSubmissions/CMS/__snapshots__/InitialSubmissionCMS.test.tsx.snap diff --git a/lib/libs/email/preview/Initial Submissions/State/AppK.tsx b/lib/libs/email/preview/InitialSubmissions/State/AppK.tsx similarity index 88% rename from lib/libs/email/preview/Initial Submissions/State/AppK.tsx rename to lib/libs/email/preview/InitialSubmissions/State/AppK.tsx index 93d6b44c9c..f6c1647eb6 100644 --- a/lib/libs/email/preview/Initial Submissions/State/AppK.tsx +++ b/lib/libs/email/preview/InitialSubmissions/State/AppK.tsx @@ -1,4 +1,4 @@ -import { AppKStateEmail } from "../../../content/new-submission/emailTemplates"; +import { AppKStateEmail } from "../../../content/newSubmission/emailTemplates"; import { emailTemplateValue } from "../../../mock-data/new-submission"; import * as attachments from "../../../mock-data/attachments"; diff --git a/lib/libs/email/preview/Initial Submissions/State/CHIP_SPA.tsx b/lib/libs/email/preview/InitialSubmissions/State/CHIP_SPA.tsx similarity index 89% rename from lib/libs/email/preview/Initial Submissions/State/CHIP_SPA.tsx rename to lib/libs/email/preview/InitialSubmissions/State/CHIP_SPA.tsx index 0da31143fb..a5823077a1 100644 --- a/lib/libs/email/preview/Initial Submissions/State/CHIP_SPA.tsx +++ b/lib/libs/email/preview/InitialSubmissions/State/CHIP_SPA.tsx @@ -1,5 +1,5 @@ import { emailTemplateValue } from "libs/email/mock-data/new-submission"; -import { ChipSpaStateEmail } from "libs/email/content/new-submission/emailTemplates"; +import { ChipSpaStateEmail } from "libs/email/content/newSubmission/emailTemplates/ChipSpaState"; import * as attachments from "libs/email/mock-data/attachments"; const ChipSpaStateEmailPreview = () => { return ( diff --git a/lib/libs/email/preview/Initial Submissions/State/InitialSubmissionState.test.tsx b/lib/libs/email/preview/InitialSubmissions/State/InitialSubmissionState.test.tsx similarity index 100% rename from lib/libs/email/preview/Initial Submissions/State/InitialSubmissionState.test.tsx rename to lib/libs/email/preview/InitialSubmissions/State/InitialSubmissionState.test.tsx diff --git a/lib/libs/email/preview/Initial Submissions/State/Medicaid_SPA.tsx b/lib/libs/email/preview/InitialSubmissions/State/Medicaid_SPA.tsx similarity index 91% rename from lib/libs/email/preview/Initial Submissions/State/Medicaid_SPA.tsx rename to lib/libs/email/preview/InitialSubmissions/State/Medicaid_SPA.tsx index 1f3a2e2132..3ea4e1ffb6 100644 --- a/lib/libs/email/preview/Initial Submissions/State/Medicaid_SPA.tsx +++ b/lib/libs/email/preview/InitialSubmissions/State/Medicaid_SPA.tsx @@ -1,4 +1,4 @@ -import { MedSpaStateEmail } from "../../../content/new-submission/emailTemplates"; +import { MedSpaStateEmail } from "../../../content/newSubmission/emailTemplates"; import { emailTemplateValue } from "../../../mock-data/new-submission"; import * as attachments from "../../../mock-data/attachments"; diff --git a/lib/libs/email/preview/Initial Submissions/State/Temp_Extension.tsx b/lib/libs/email/preview/InitialSubmissions/State/Temp_Extension.tsx similarity index 100% rename from lib/libs/email/preview/Initial Submissions/State/Temp_Extension.tsx rename to lib/libs/email/preview/InitialSubmissions/State/Temp_Extension.tsx diff --git a/lib/libs/email/preview/Initial Submissions/State/Waiver_Capitated.tsx b/lib/libs/email/preview/InitialSubmissions/State/Waiver_Capitated.tsx similarity index 98% rename from lib/libs/email/preview/Initial Submissions/State/Waiver_Capitated.tsx rename to lib/libs/email/preview/InitialSubmissions/State/Waiver_Capitated.tsx index 91781bfa5c..14039778e2 100644 --- a/lib/libs/email/preview/Initial Submissions/State/Waiver_Capitated.tsx +++ b/lib/libs/email/preview/InitialSubmissions/State/Waiver_Capitated.tsx @@ -1,4 +1,4 @@ -import { Waiver1915bStateEmail } from "../../../content/new-submission/emailTemplates/Waiver1915bState"; +import { Waiver1915bStateEmail } from "../../../content/newSubmission/emailTemplates/Waiver1915bState"; import { emailTemplateValue } from "../../../mock-data/new-submission"; export const Waiver1915bStateCapitatedInitialEmailPreview = () => { diff --git a/lib/libs/email/preview/Initial Submissions/State/Waiver_Contracting.tsx b/lib/libs/email/preview/InitialSubmissions/State/Waiver_Contracting.tsx similarity index 97% rename from lib/libs/email/preview/Initial Submissions/State/Waiver_Contracting.tsx rename to lib/libs/email/preview/InitialSubmissions/State/Waiver_Contracting.tsx index 1f5261da70..4faee3f0c4 100644 --- a/lib/libs/email/preview/Initial Submissions/State/Waiver_Contracting.tsx +++ b/lib/libs/email/preview/InitialSubmissions/State/Waiver_Contracting.tsx @@ -1,4 +1,4 @@ -import { Waiver1915bStateEmail } from "../../../content/new-submission/emailTemplates/Waiver1915bState"; +import { Waiver1915bStateEmail } from "../../../content/newSubmission/emailTemplates/Waiver1915bState"; import { emailTemplateValue } from "../../../mock-data/new-submission"; export const Waiver1915bContractingStateInitialEmailPreview = () => { diff --git a/lib/libs/email/preview/Initial Submissions/State/__snapshots__/InitialSubmissionState.test.tsx.snap b/lib/libs/email/preview/InitialSubmissions/State/__snapshots__/InitialSubmissionState.test.tsx.snap similarity index 100% rename from lib/libs/email/preview/Initial Submissions/State/__snapshots__/InitialSubmissionState.test.tsx.snap rename to lib/libs/email/preview/InitialSubmissions/State/__snapshots__/InitialSubmissionState.test.tsx.snap diff --git a/lib/libs/email/preview/Initial_Submissions/CMS/Temp_Extension.tsx b/lib/libs/email/preview/Initial_Submissions/CMS/Temp_Extension.tsx new file mode 100644 index 0000000000..44c222ebf1 --- /dev/null +++ b/lib/libs/email/preview/Initial_Submissions/CMS/Temp_Extension.tsx @@ -0,0 +1,14 @@ +import { TempExtCMSEmail } from "../../../content/tempExtension/emailTemplates"; +import { emailTemplateValue } from "../../../mock-data/temp-extension"; + +const TempExtCMSPreview = () => { + return ( + + ); +}; + +export default TempExtCMSPreview; diff --git a/lib/libs/email/preview/Respond to Rai/CMS/AppK.tsx b/lib/libs/email/preview/RespondToRai/CMS/AppK.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/CMS/AppK.tsx rename to lib/libs/email/preview/RespondToRai/CMS/AppK.tsx diff --git a/lib/libs/email/preview/Respond to Rai/CMS/CHIP_SPA.tsx b/lib/libs/email/preview/RespondToRai/CMS/CHIP_SPA.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/CMS/CHIP_SPA.tsx rename to lib/libs/email/preview/RespondToRai/CMS/CHIP_SPA.tsx diff --git a/lib/libs/email/preview/Respond to Rai/CMS/Medicaid_SPA.tsx b/lib/libs/email/preview/RespondToRai/CMS/Medicaid_SPA.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/CMS/Medicaid_SPA.tsx rename to lib/libs/email/preview/RespondToRai/CMS/Medicaid_SPA.tsx diff --git a/lib/libs/email/preview/Respond to Rai/CMS/ResToRaiCMS.test.tsx b/lib/libs/email/preview/RespondToRai/CMS/ResToRaiCMS.test.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/CMS/ResToRaiCMS.test.tsx rename to lib/libs/email/preview/RespondToRai/CMS/ResToRaiCMS.test.tsx diff --git a/lib/libs/email/preview/Respond to Rai/CMS/Waiver_Capitated.tsx b/lib/libs/email/preview/RespondToRai/CMS/Waiver_Capitated.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/CMS/Waiver_Capitated.tsx rename to lib/libs/email/preview/RespondToRai/CMS/Waiver_Capitated.tsx diff --git a/lib/libs/email/preview/Respond to Rai/CMS/__snapshots__/ResToRaiCMS.test.tsx.snap b/lib/libs/email/preview/RespondToRai/CMS/__snapshots__/ResToRaiCMS.test.tsx.snap similarity index 100% rename from lib/libs/email/preview/Respond to Rai/CMS/__snapshots__/ResToRaiCMS.test.tsx.snap rename to lib/libs/email/preview/RespondToRai/CMS/__snapshots__/ResToRaiCMS.test.tsx.snap diff --git a/lib/libs/email/preview/Respond to Rai/State/AppK.tsx b/lib/libs/email/preview/RespondToRai/State/AppK.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/State/AppK.tsx rename to lib/libs/email/preview/RespondToRai/State/AppK.tsx diff --git a/lib/libs/email/preview/Respond to Rai/State/CHIP_SPA.tsx b/lib/libs/email/preview/RespondToRai/State/CHIP_SPA.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/State/CHIP_SPA.tsx rename to lib/libs/email/preview/RespondToRai/State/CHIP_SPA.tsx diff --git a/lib/libs/email/preview/Respond to Rai/State/Medicaid_SPA.tsx b/lib/libs/email/preview/RespondToRai/State/Medicaid_SPA.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/State/Medicaid_SPA.tsx rename to lib/libs/email/preview/RespondToRai/State/Medicaid_SPA.tsx diff --git a/lib/libs/email/preview/Respond to Rai/State/ResToRaiState.test.tsx b/lib/libs/email/preview/RespondToRai/State/ResToRaiState.test.tsx similarity index 100% rename from lib/libs/email/preview/Respond to Rai/State/ResToRaiState.test.tsx rename to lib/libs/email/preview/RespondToRai/State/ResToRaiState.test.tsx diff --git a/lib/libs/email/preview/Respond to Rai/State/Waiver_Capitated.tsx b/lib/libs/email/preview/RespondToRai/State/Waiver_Capitated.tsx similarity index 92% rename from lib/libs/email/preview/Respond to Rai/State/Waiver_Capitated.tsx rename to lib/libs/email/preview/RespondToRai/State/Waiver_Capitated.tsx index e50ba37acd..bc53bb7412 100644 --- a/lib/libs/email/preview/Respond to Rai/State/Waiver_Capitated.tsx +++ b/lib/libs/email/preview/RespondToRai/State/Waiver_Capitated.tsx @@ -1,6 +1,6 @@ import { WaiverStateEmail } from "libs/email/content/respondToRai/emailTemplates"; import { emailTemplateValue } from "libs/email/mock-data/respond-to-rai"; -import * as attachments from "../../../mock-data/attachments"; +import * as attachments from "libs/email/mock-data/attachments"; export default () => { return ( diff --git a/lib/libs/email/preview/Respond to Rai/State/__snapshots__/ResToRaiState.test.tsx.snap b/lib/libs/email/preview/RespondToRai/State/__snapshots__/ResToRaiState.test.tsx.snap similarity index 100% rename from lib/libs/email/preview/Respond to Rai/State/__snapshots__/ResToRaiState.test.tsx.snap rename to lib/libs/email/preview/RespondToRai/State/__snapshots__/ResToRaiState.test.tsx.snap diff --git a/lib/libs/email/preview/Upload Subsequent Documents/CMS/AppK.tsx b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/AppK.tsx similarity index 87% rename from lib/libs/email/preview/Upload Subsequent Documents/CMS/AppK.tsx rename to lib/libs/email/preview/UploadSubsequentDocuments/CMS/AppK.tsx index b18abb99bb..58a5412c26 100644 --- a/lib/libs/email/preview/Upload Subsequent Documents/CMS/AppK.tsx +++ b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/AppK.tsx @@ -1,4 +1,4 @@ -import { AppKCMSEmail } from "../../../content/upload-subsequent-documents/emailTemplates"; +import { AppKCMSEmail } from "../../../content/uploadSubsequentDocuments/emailTemplates"; import { emailTemplateValue } from "../../../mock-data/new-submission"; import * as attachments from "../../../mock-data/attachments"; const AppKCMSEmailPreview = () => { diff --git a/lib/libs/email/preview/Upload Subsequent Documents/CMS/CHIP_SPA.tsx b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/CHIP_SPA.tsx similarity index 89% rename from lib/libs/email/preview/Upload Subsequent Documents/CMS/CHIP_SPA.tsx rename to lib/libs/email/preview/UploadSubsequentDocuments/CMS/CHIP_SPA.tsx index 94c86da233..6020a1b165 100644 --- a/lib/libs/email/preview/Upload Subsequent Documents/CMS/CHIP_SPA.tsx +++ b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/CHIP_SPA.tsx @@ -1,4 +1,4 @@ -import { ChipSpaCMSEmail } from "libs/email/content/upload-subsequent-documents/emailTemplates"; +import { ChipSpaCMSEmail } from "libs/email/content/uploadSubsequentDocuments/emailTemplates"; import { emailTemplateValue } from "libs/email/mock-data/upload-subsequent-documents"; import * as attachments from "libs/email/mock-data/attachments"; diff --git a/lib/libs/email/preview/Upload Subsequent Documents/CMS/MED_SPA.tsx b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/MED_SPA.tsx similarity index 90% rename from lib/libs/email/preview/Upload Subsequent Documents/CMS/MED_SPA.tsx rename to lib/libs/email/preview/UploadSubsequentDocuments/CMS/MED_SPA.tsx index 74b4f57c42..dd114250af 100644 --- a/lib/libs/email/preview/Upload Subsequent Documents/CMS/MED_SPA.tsx +++ b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/MED_SPA.tsx @@ -1,4 +1,4 @@ -import { MedSpaCMSEmail } from "libs/email/content/upload-subsequent-documents/emailTemplates"; +import { MedSpaCMSEmail } from "libs/email/content/uploadSubsequentDocuments/emailTemplates"; import { emailTemplateValue } from "libs/email/mock-data/upload-subsequent-documents"; import * as attachments from "libs/email/mock-data/attachments"; diff --git a/lib/libs/email/preview/Upload Subsequent Documents/CMS/UpSubDocCMS.test.tsx b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/UpSubDocCMS.test.tsx similarity index 100% rename from lib/libs/email/preview/Upload Subsequent Documents/CMS/UpSubDocCMS.test.tsx rename to lib/libs/email/preview/UploadSubsequentDocuments/CMS/UpSubDocCMS.test.tsx diff --git a/lib/libs/email/preview/Upload Subsequent Documents/CMS/Waiver1915b.tsx b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/Waiver1915b.tsx similarity index 88% rename from lib/libs/email/preview/Upload Subsequent Documents/CMS/Waiver1915b.tsx rename to lib/libs/email/preview/UploadSubsequentDocuments/CMS/Waiver1915b.tsx index 5e72eaf2ed..e96a8bfab4 100644 --- a/lib/libs/email/preview/Upload Subsequent Documents/CMS/Waiver1915b.tsx +++ b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/Waiver1915b.tsx @@ -1,4 +1,4 @@ -import { WaiversEmailCMS } from "libs/email/content/upload-subsequent-documents/emailTemplates/Waiver1915BCMS"; +import { WaiversEmailCMS } from "libs/email/content/uploadSubsequentDocuments/emailTemplates/Waiver1915BCMS"; import { emailTemplateValue } from "libs/email/mock-data/upload-subsequent-documents"; import * as attachments from "libs/email/mock-data/attachments"; diff --git a/lib/libs/email/preview/Upload Subsequent Documents/CMS/__snapshots__/UpSubDocCMS.test.tsx.snap b/lib/libs/email/preview/UploadSubsequentDocuments/CMS/__snapshots__/UpSubDocCMS.test.tsx.snap similarity index 100% rename from lib/libs/email/preview/Upload Subsequent Documents/CMS/__snapshots__/UpSubDocCMS.test.tsx.snap rename to lib/libs/email/preview/UploadSubsequentDocuments/CMS/__snapshots__/UpSubDocCMS.test.tsx.snap diff --git a/lib/libs/email/preview/Upload Subsequent Documents/State/AppK.tsx b/lib/libs/email/preview/UploadSubsequentDocuments/State/AppK.tsx similarity index 67% rename from lib/libs/email/preview/Upload Subsequent Documents/State/AppK.tsx rename to lib/libs/email/preview/UploadSubsequentDocuments/State/AppK.tsx index 62b6e62897..1d88221486 100644 --- a/lib/libs/email/preview/Upload Subsequent Documents/State/AppK.tsx +++ b/lib/libs/email/preview/UploadSubsequentDocuments/State/AppK.tsx @@ -1,6 +1,6 @@ -import { AppKStateEmail } from "../../../content/upload-subsequent-documents/emailTemplates"; -import { emailTemplateValue } from "../../../mock-data/new-submission"; -import * as attachments from "../../../mock-data/attachments"; +import { AppKStateEmail } from "libs/email/content/uploadSubsequentDocuments/emailTemplates/AppKState"; +import { emailTemplateValue } from "libs/email/mock-data/new-submission"; +import * as attachments from "libs/email/mock-data/attachments"; const AppKStateEmailPreview = () => { return ( { return ( - { return ( diff --git a/lib/libs/email/preview/Upload Subsequent Documents/State/UpSubDocState.test.tsx b/lib/libs/email/preview/UploadSubsequentDocuments/State/UpSubDocState.test.tsx similarity index 100% rename from lib/libs/email/preview/Upload Subsequent Documents/State/UpSubDocState.test.tsx rename to lib/libs/email/preview/UploadSubsequentDocuments/State/UpSubDocState.test.tsx diff --git a/lib/libs/email/preview/Upload Subsequent Documents/State/Waiver1915b.tsx b/lib/libs/email/preview/UploadSubsequentDocuments/State/Waiver1915b.tsx similarity index 88% rename from lib/libs/email/preview/Upload Subsequent Documents/State/Waiver1915b.tsx rename to lib/libs/email/preview/UploadSubsequentDocuments/State/Waiver1915b.tsx index 9ce41a53ac..7811021bd2 100644 --- a/lib/libs/email/preview/Upload Subsequent Documents/State/Waiver1915b.tsx +++ b/lib/libs/email/preview/UploadSubsequentDocuments/State/Waiver1915b.tsx @@ -1,4 +1,4 @@ -import { WaiversEmailState } from "libs/email/content/upload-subsequent-documents/emailTemplates/Waiver1915BState"; +import { WaiversEmailState } from "libs/email/content/uploadSubsequentDocuments/emailTemplates/Waiver1915BState"; import { emailTemplateValue } from "libs/email/mock-data/upload-subsequent-documents"; import * as attachments from "libs/email/mock-data/attachments"; diff --git a/lib/libs/email/preview/Upload Subsequent Documents/State/__snapshots__/UpSubDocState.test.tsx.snap b/lib/libs/email/preview/UploadSubsequentDocuments/State/__snapshots__/UpSubDocState.test.tsx.snap similarity index 96% rename from lib/libs/email/preview/Upload Subsequent Documents/State/__snapshots__/UpSubDocState.test.tsx.snap rename to lib/libs/email/preview/UploadSubsequentDocuments/State/__snapshots__/UpSubDocState.test.tsx.snap index a1a2f3801d..08ae6cd292 100644 --- a/lib/libs/email/preview/Upload Subsequent Documents/State/__snapshots__/UpSubDocState.test.tsx.snap +++ b/lib/libs/email/preview/UploadSubsequentDocuments/State/__snapshots__/UpSubDocState.test.tsx.snap @@ -1406,9 +1406,9 @@ exports[`Upload Subsequent Document CMS Email Snapshot Test > renders a ChipSPA
- Additional documents submitted for CHIP SPA CO-24-1234 + Action required: review new documents for CHIP SPA CO-24-1234 in OneMAC.
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ +  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
renders a ChipSPA

- You’ve successfully submitted the following to CMS reviewers for CHIP SPA CO-24-1234 + New documents have been submitted for CHIP SPA CO-24-1234 in OneMAC.

renders a ChipSPA
- - - - - - - -
-

- Name - : -

-
-

- George Harrison -

-
- - - - - - - -
-

- Email Address - : -

-
-

- george@example.com -

-
renders a ChipSPA style="width: 100%; border-top: 1px solid #0071BD; margin: 16px 0px;" />

- If you have questions or did not expect this email, please contact your CPOC. + How to Access:

+
    +
  • +

    + These documents can be found in OneMAC through this link + + + https://mako-dev.cms.gov/ + + . +

    +
  • +
  • +

    + If you are not already logged in, click “Login” at the top of the page and log in using your Enterprise User Administration (EUA) credentials. +

    +
  • +
  • +

    + After you logged in, click the submission ID number on the dashboard page to view details. +

    +
  • +

@@ -2309,9 +2265,9 @@ exports[`Upload Subsequent Document CMS Email Snapshot Test > renders a ChipSPA

- Additional documents submitted for CHIP SPA CO-24-1234 + Action required: review new documents for CHIP SPA CO-24-1234 in OneMAC.
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ +  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
renders a ChipSPA

- You’ve successfully submitted the following to CMS reviewers for CHIP SPA CO-24-1234 + New documents have been submitted for CHIP SPA CO-24-1234 in OneMAC.

renders a ChipSPA
- - - - - - - -
-

- Name - : -

-
-

- George Harrison -

-
- - - - - - - -
-

- Email Address - : -

-
-

- george@example.com -

-
renders a ChipSPA style="width: 100%; border-top: 1px solid #0071BD; margin: 16px 0px;" />

- If you have questions or did not expect this email, please contact your CPOC. + How to Access:

+
    +
  • +

    + These documents can be found in OneMAC through this link + + + https://mako-dev.cms.gov/ + + . +

    +
  • +
  • +

    + If you are not already logged in, click “Login” at the top of the page and log in using your Enterprise User Administration (EUA) credentials. +

    +
  • +
  • +

    + After you logged in, click the submission ID number on the dashboard page to view details. +

    +
  • +

@@ -3711,9 +3623,9 @@ exports[`Upload Subsequent Document CMS Email Snapshot Test > renders a Medicaid

- Additional documents submitted for CHIP SPA CO-24-1234 + Action required: review new documents for CHIP SPA CO-24-1234 in OneMAC.
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ +  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
renders a Medicaid

- You’ve successfully submitted the following to CMS reviewers for CHIP SPA CO-24-1234 + New documents have been submitted for CHIP SPA CO-24-1234 in OneMAC.

renders a Medicaid
- - - - - - - -
-

- Name - : -

-
-

- George Harrison -

-
- - - - - - - -
-

- Email Address - : -

-
-

- george@example.com -

-
renders a Medicaid style="width: 100%; border-top: 1px solid #0071BD; margin: 16px 0px;" />

- If you have questions or did not expect this email, please contact your CPOC. + How to Access:

+
    +
  • +

    + These documents can be found in OneMAC through this link + + + https://mako-dev.cms.gov/ + + . +

    +
  • +
  • +

    + If you are not already logged in, click “Login” at the top of the page and log in using your Enterprise User Administration (EUA) credentials. +

    +
  • +
  • +

    + After you logged in, click the submission ID number on the dashboard page to view details. +

    +
  • +

@@ -6912,9 +6780,9 @@ exports[`Upload Subsequent Document CMS Email Snapshot Test > renders a Waiver C

- Additional documents submitted for CHIP SPA CO-24-1234 + Action required: review new documents for CHIP SPA CO-24-1234 in OneMAC.
-  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ +  ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
renders a Waiver C

- You’ve successfully submitted the following to CMS reviewers for CHIP SPA CO-24-1234 + New documents have been submitted for CHIP SPA CO-24-1234 in OneMAC.

renders a Waiver C
- - - - - - - -
-

- Name - : -

-
-

- George Harrison -

-
- - - - - - - -
-

- Email Address - : -

-
-

- george@example.com -

-
renders a Waiver C style="width: 100%; border-top: 1px solid #0071BD; margin: 16px 0px;" />

- If you have questions or did not expect this email, please contact your CPOC. + How to Access:

+
    +
  • +

    + These documents can be found in OneMAC through this link + + + https://mako-dev.cms.gov/ + + . +

    +
  • +
  • +

    + If you are not already logged in, click “Login” at the top of the page and log in using your Enterprise User Administration (EUA) credentials. +

    +
  • +
  • +

    + After you logged in, click the submission ID number on the dashboard page to view details. +

    +
  • +

diff --git a/lib/libs/email/preview/Withdraw Package/CMS/AppK.tsx b/lib/libs/email/preview/WithdrawPackage/CMS/AppK.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/CMS/AppK.tsx rename to lib/libs/email/preview/WithdrawPackage/CMS/AppK.tsx diff --git a/lib/libs/email/preview/Withdraw Package/CMS/CHIP_SPA.tsx b/lib/libs/email/preview/WithdrawPackage/CMS/CHIP_SPA.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/CMS/CHIP_SPA.tsx rename to lib/libs/email/preview/WithdrawPackage/CMS/CHIP_SPA.tsx diff --git a/lib/libs/email/preview/Withdraw Package/CMS/Medicaid_SPA.tsx b/lib/libs/email/preview/WithdrawPackage/CMS/Medicaid_SPA.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/CMS/Medicaid_SPA.tsx rename to lib/libs/email/preview/WithdrawPackage/CMS/Medicaid_SPA.tsx diff --git a/lib/libs/email/preview/Withdraw Package/CMS/Waiver_Capitated.tsx b/lib/libs/email/preview/WithdrawPackage/CMS/Waiver_Capitated.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/CMS/Waiver_Capitated.tsx rename to lib/libs/email/preview/WithdrawPackage/CMS/Waiver_Capitated.tsx diff --git a/lib/libs/email/preview/Withdraw Package/CMS/WithdrawPackageCMS.test.tsx b/lib/libs/email/preview/WithdrawPackage/CMS/WithdrawPackageCMS.test.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/CMS/WithdrawPackageCMS.test.tsx rename to lib/libs/email/preview/WithdrawPackage/CMS/WithdrawPackageCMS.test.tsx diff --git a/lib/libs/email/preview/Withdraw Package/CMS/__snapshots__/WithdrawPackageCMS.test.tsx.snap b/lib/libs/email/preview/WithdrawPackage/CMS/__snapshots__/WithdrawPackageCMS.test.tsx.snap similarity index 100% rename from lib/libs/email/preview/Withdraw Package/CMS/__snapshots__/WithdrawPackageCMS.test.tsx.snap rename to lib/libs/email/preview/WithdrawPackage/CMS/__snapshots__/WithdrawPackageCMS.test.tsx.snap diff --git a/lib/libs/email/preview/Withdraw Package/State/AppK.tsx b/lib/libs/email/preview/WithdrawPackage/State/AppK.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/State/AppK.tsx rename to lib/libs/email/preview/WithdrawPackage/State/AppK.tsx diff --git a/lib/libs/email/preview/Withdraw Package/State/CHIP_SPA.tsx b/lib/libs/email/preview/WithdrawPackage/State/CHIP_SPA.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/State/CHIP_SPA.tsx rename to lib/libs/email/preview/WithdrawPackage/State/CHIP_SPA.tsx diff --git a/lib/libs/email/preview/Withdraw Package/State/Medicaid_SPA.tsx b/lib/libs/email/preview/WithdrawPackage/State/Medicaid_SPA.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/State/Medicaid_SPA.tsx rename to lib/libs/email/preview/WithdrawPackage/State/Medicaid_SPA.tsx diff --git a/lib/libs/email/preview/Withdraw Package/State/Waiver_Capitated.tsx b/lib/libs/email/preview/WithdrawPackage/State/Waiver_Capitated.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/State/Waiver_Capitated.tsx rename to lib/libs/email/preview/WithdrawPackage/State/Waiver_Capitated.tsx diff --git a/lib/libs/email/preview/Withdraw Package/State/WithdrawPackageState.test.tsx b/lib/libs/email/preview/WithdrawPackage/State/WithdrawPackageState.test.tsx similarity index 100% rename from lib/libs/email/preview/Withdraw Package/State/WithdrawPackageState.test.tsx rename to lib/libs/email/preview/WithdrawPackage/State/WithdrawPackageState.test.tsx diff --git a/lib/libs/email/preview/Withdraw Package/State/__snapshots__/WithdrawPackageState.test.tsx.snap b/lib/libs/email/preview/WithdrawPackage/State/__snapshots__/WithdrawPackageState.test.tsx.snap similarity index 100% rename from lib/libs/email/preview/Withdraw Package/State/__snapshots__/WithdrawPackageState.test.tsx.snap rename to lib/libs/email/preview/WithdrawPackage/State/__snapshots__/WithdrawPackageState.test.tsx.snap From cec5f86392f2e82e2d96f58f9508b13696e000e3 Mon Sep 17 00:00:00 2001 From: tiffanyvu Date: Tue, 14 Jan 2025 15:20:05 -0800 Subject: [PATCH 07/17] fix(login): Redirect after login based on user roles (#1004) * change dashboard access based on user role and add tests * logs * log * rm log --- .../src/components/Layout/index.test.tsx | 30 ++++++++++++++++++- react-app/src/components/Layout/index.tsx | 9 ++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/react-app/src/components/Layout/index.test.tsx b/react-app/src/components/Layout/index.test.tsx index 80b8394c86..7d1093a6bc 100644 --- a/react-app/src/components/Layout/index.test.tsx +++ b/react-app/src/components/Layout/index.test.tsx @@ -6,7 +6,7 @@ import { Auth } from "aws-amplify"; import * as hooks from "@/hooks"; import * as api from "@/api"; import { renderWithQueryClientAndMemoryRouter } from "@/utils/test-helpers/renderForm"; -import { setMockUsername, makoStateSubmitter, AUTH_CONFIG } from "mocks"; +import { setMockUsername, makoStateSubmitter, noRoleUser, AUTH_CONFIG } from "mocks"; /** * Mock Configurations @@ -187,6 +187,34 @@ describe("Layout", () => { }); }); + describe("Navigation for logged-in users", () => { + it("navigates to dashboard if user has appropriate roles", async () => { + const setupLayoutTest = async ( + viewMode: ViewMode = VIEW_MODES.DESKTOP, + userData = makoStateSubmitter, + ) => { + setMockUsername(userData); + mockMediaQuery(viewMode); + await renderLayout(); + }; + await setupLayoutTest(VIEW_MODES.DESKTOP); + expect(screen.getByText("Dashboard")).toBeInTheDocument(); + }); + + it("navigates to home page if user doesn't have appropriate roles", async () => { + const setupLayoutTest = async ( + viewMode: ViewMode = VIEW_MODES.DESKTOP, + userData = noRoleUser, + ) => { + setMockUsername(userData); + mockMediaQuery(viewMode); + await renderLayout(); + }; + await setupLayoutTest(VIEW_MODES.DESKTOP); + expect(screen.queryByText("Dashboard")).not.toBeInTheDocument(); + }); + }); + describe("Navigation for logged-out users", () => { beforeEach(() => { setMockUsername(null); diff --git a/react-app/src/components/Layout/index.tsx b/react-app/src/components/Layout/index.tsx index 0e978547c5..c192eea819 100644 --- a/react-app/src/components/Layout/index.tsx +++ b/react-app/src/components/Layout/index.tsx @@ -12,7 +12,7 @@ import config from "@/config"; import { ScrollToTop, SimplePageContainer, UserPrompt, Banner } from "@/components"; import { isFaqPage, isProd } from "@/utils"; import MMDLAlertBanner from "@/components/Banner/MMDLSpaBanner"; - +import { UserRoles } from "shared-types"; /** * Custom hook that generates a list of navigation links based on the user's status and whether the current page is the FAQ page. * @@ -35,7 +35,12 @@ const useGetLinks = () => { { name: "Dashboard", link: "/dashboard", - condition: userObj.user && userObj.user["custom:cms-roles"], + condition: + userObj.user && + userObj.user["custom:cms-roles"] && + Object.values(UserRoles).some((role) => + userObj.user["custom:cms-roles"].includes(role), + ), }, { name: "FAQ", From 74b026783ee0fc338fcb2de2a0f8f166176683c0 Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 14 Jan 2025 22:05:34 -0700 Subject: [PATCH 08/17] fix(emails): few issues slipped through and exposed some problems with the seatool transform (#1022) * Update * attempt fix for emails * Update --- lib/lambda/processEmails.ts | 5 +++- .../opensearch/main/transforms/seatool.ts | 25 +++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/lambda/processEmails.ts b/lib/lambda/processEmails.ts index 2b4d22e372..0d62cd5e05 100644 --- a/lib/lambda/processEmails.ts +++ b/lib/lambda/processEmails.ts @@ -154,7 +154,10 @@ export function validateEmailTemplate(template: any) { } export async function processAndSendEmails(record: any, id: string, config: ProcessEmailConfig) { - const templates = await getEmailTemplates(record.event, record.authority); + const templates = await getEmailTemplates( + record.event, + record.authority.toLowerCase(), + ); if (!templates) { console.log( diff --git a/lib/packages/shared-types/opensearch/main/transforms/seatool.ts b/lib/packages/shared-types/opensearch/main/transforms/seatool.ts index bada36dfcd..b6732da5d2 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/seatool.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/seatool.ts @@ -74,15 +74,15 @@ const getRaiDate = (data: SeaTool) => { const getDateStringOrNullFromEpoc = (epocDate: number | null | undefined) => epocDate !== null && epocDate !== undefined ? new Date(epocDate).toISOString() : null; - const compileSrtList = ( - officers: SeatoolOfficer[] | null | undefined, - ): { name: string; email: string }[] => - officers?.length - ? officers.map((o) => ({ - name: `${o.FIRST_NAME || ""} ${o.LAST_NAME || ""}`, - email: o.EMAIL || "", - })) - : []; +const compileSrtList = ( + officers: SeatoolOfficer[] | null | undefined, +): { name: string; email: string }[] => + officers?.length + ? officers.map((o) => ({ + name: `${o.FIRST_NAME || ""} ${o.LAST_NAME || ""}`, + email: o.EMAIL || "", + })) + : []; const getFinalDispositionDate = (status: string, record: SeaTool) => { return status && finalDispositionStatuses.includes(status) @@ -129,8 +129,7 @@ export const transform = (id: string) => { id: id.toUpperCase(), actionType: data.ACTIONTYPES?.[0].ACTION_NAME, approvedEffectiveDate: getDateStringOrNullFromEpoc( - data.STATE_PLAN.APPROVED_EFFECTIVE_DATE || - data.STATE_PLAN.ACTUAL_EFFECTIVE_DATE, + data.STATE_PLAN.APPROVED_EFFECTIVE_DATE || data.STATE_PLAN.ACTUAL_EFFECTIVE_DATE, ), changed_date: data.STATE_PLAN.CHANGED_DATE, description: data.STATE_PLAN.SUMMARY_MEMO, @@ -148,7 +147,7 @@ export const transform = (id: string) => { SPA_TYPE_ID: type.SPA_TYPE_ID, SPA_TYPE_NAME: type.SPA_TYPE_NAME.replace(/–|—/g, "-"), }; - }) || null, + }) || [], subTypes: data.STATE_PLAN_SERVICE_SUBTYPES?.filter( (subType): subType is NonNullable => subType != null, @@ -157,7 +156,7 @@ export const transform = (id: string) => { TYPE_ID: subType.TYPE_ID, TYPE_NAME: subType.TYPE_NAME.replace(/–|—/g, "-"), }; - }) || null, + }) || [], proposedDate: getDateStringOrNullFromEpoc(data.STATE_PLAN.PROPOSED_DATE), raiReceivedDate, raiRequestedDate, From 8003585c160c94dc4c59360306159197279941ff Mon Sep 17 00:00:00 2001 From: Tiffany Forkner Date: Wed, 15 Jan 2025 09:40:31 -0500 Subject: [PATCH 09/17] feat(test): adding tests for syncing cpocs, types, and subtypes (#1023) * added tests for syncing cpocs, types, and subtypes with seatool --- lib/lambda/getCpocs.test.ts | 4 +- lib/lambda/getSubTypes.test.ts | 59 +++--- lib/lambda/getSubTypes.ts | 6 + lib/lambda/getTypes.test.ts | 50 +++-- lib/lambda/getTypes.ts | 6 + lib/lambda/sinkChangelog.test.ts | 24 +-- lib/lambda/sinkCpocs.test.ts | 267 +++++++++++++++++++++----- lib/lambda/sinkSubtypes.test.ts | 200 +++++++++++++++++++ lib/lambda/sinkSubtypes.ts | 6 +- lib/lambda/sinkTypes.test.ts | 199 +++++++++++++++++++ lib/lambda/sinkTypes.ts | 6 +- lib/lambda/submit/index.test.ts | 25 ++- lib/lambda/submit/index.ts | 6 +- mocks/data/cpocs.ts | 112 ++++++----- mocks/data/types.ts | 96 ++++----- mocks/handlers/api/index.ts | 14 +- mocks/handlers/api/types.ts | 42 ++-- mocks/handlers/opensearch/cpocs.ts | 49 ++--- mocks/handlers/opensearch/index.ts | 2 + mocks/handlers/opensearch/subtypes.ts | 35 ++-- mocks/handlers/opensearch/types.ts | 23 ++- mocks/handlers/opensearch/util.ts | 120 ++++++++++-- mocks/index.d.ts | 4 + react-app/src/api/useGetTypes.test.ts | 49 +++-- vitest.config.ts | 1 + 25 files changed, 1078 insertions(+), 327 deletions(-) create mode 100644 lib/lambda/sinkSubtypes.test.ts create mode 100644 lib/lambda/sinkTypes.test.ts diff --git a/lib/lambda/getCpocs.test.ts b/lib/lambda/getCpocs.test.ts index 79c86c3491..c50f172e0b 100644 --- a/lib/lambda/getCpocs.test.ts +++ b/lib/lambda/getCpocs.test.ts @@ -3,7 +3,7 @@ import { APIGatewayEvent } from "aws-lambda"; import { handler } from "./getCpocs"; import { mockedServiceServer } from "mocks/server"; import { emptyCpocSearchHandler, errorCpocSearchHandler } from "mocks"; -import { cpocs } from "mocks/data/cpocs"; +import { cpocsList } from "mocks/data/cpocs"; describe("getCpocs Handler", () => { it("should return 400 if event body is missing", async () => { @@ -35,7 +35,7 @@ describe("getCpocs Handler", () => { const body = JSON.parse(res.body); expect(res.statusCode).toEqual(200); - expect(body.hits.hits).toEqual(cpocs); + expect(body.hits.hits).toEqual(cpocsList); }); it("should return 500 if an error occurs during processing", async () => { diff --git a/lib/lambda/getSubTypes.test.ts b/lib/lambda/getSubTypes.test.ts index 7c34e22793..abb94ef66b 100644 --- a/lib/lambda/getSubTypes.test.ts +++ b/lib/lambda/getSubTypes.test.ts @@ -9,11 +9,11 @@ import { TYPE_TWO_ID, TYPE_THREE_ID, DO_NOT_USE_TYPE_ID, - ERROR_AUTHORITY_ID, medicaidSubtypes, chipSubtypes, } from "mocks/data/types"; -import { TestSubtypeItemResult } from "mocks"; +import { TestSubtypeItemResult, errorSubtypeSearchHandler } from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; describe("getSubTypes Handler", () => { it("should return 400 if event body is missing", async () => { @@ -25,12 +25,10 @@ describe("getSubTypes Handler", () => { expect(res.body).toEqual(JSON.stringify({ message: "Event body required" })); }); - // TODO - should this be removed? when will the result be empty and not - // just a result with an empty hit array - it.skip("should return 400 if no subtypes are found", async () => { + it("should return 400 if authority id is undefined", async () => { const event = { body: JSON.stringify({ - authorityId: NOT_FOUND_AUTHORITY_ID, + authorityId: undefined, typeIds: [TYPE_ONE_ID, TYPE_TWO_ID], }), } as APIGatewayEvent; @@ -38,9 +36,38 @@ describe("getSubTypes Handler", () => { const res = await handler(event); expect(res.statusCode).toEqual(400); - expect(res.body).toEqual( - JSON.stringify({ message: "No record found for the given authority" }), - ); + expect(res.body).toEqual(JSON.stringify({ message: "Authority Id is required" })); + }); + + it("should return 500 if there is a server error", async () => { + mockedServer.use(errorSubtypeSearchHandler); + + const event = { + body: JSON.stringify({ + authorityId: MEDICAID_SPA_AUTHORITY_ID, + typeIds: [TYPE_ONE_ID, TYPE_TWO_ID], + }), + } as APIGatewayEvent; + + const res = await handler(event); + + expect(res.statusCode).toEqual(500); + expect(res.body).toEqual(JSON.stringify({ message: "Internal server error" })); + }); + + it("should return 200 and no hits if no subtypes are found", async () => { + const event = { + body: JSON.stringify({ + authorityId: NOT_FOUND_AUTHORITY_ID, + typeIds: [TYPE_ONE_ID, TYPE_TWO_ID], + }), + } as APIGatewayEvent; + + const res = await handler(event); + const body = JSON.parse(res.body); + + expect(res.statusCode).toEqual(200); + expect(body.hits.hits).toEqual([]); }); it("should return 200 with the result if subtypes are found", async () => { @@ -76,18 +103,4 @@ describe("getSubTypes Handler", () => { expect(type?._source?.name?.match(/Do Not Use/)).toBeFalsy(); }); }); - - it("should return 500 if an error occurs during processing", async () => { - const event = { - body: JSON.stringify({ - authorityId: ERROR_AUTHORITY_ID, - typeIds: [TYPE_ONE_ID, TYPE_TWO_ID], - }), - } as APIGatewayEvent; - - const res = await handler(event); - - expect(res.statusCode).toEqual(500); - expect(res.body).toEqual(JSON.stringify({ message: "Internal server error" })); - }); }); diff --git a/lib/lambda/getSubTypes.ts b/lib/lambda/getSubTypes.ts index 95ae6ff9f3..39a9c5f3b0 100644 --- a/lib/lambda/getSubTypes.ts +++ b/lib/lambda/getSubTypes.ts @@ -59,6 +59,12 @@ export const getSubTypes = async (event: APIGatewayEvent) => { }); } const body = JSON.parse(event.body) as GetSubTypesBody; + if (!body.authorityId) { + return response({ + statusCode: 400, + body: { message: "Authority Id is required" }, + }); + } try { const result = await querySubTypes(body.authorityId, body.typeIds); diff --git a/lib/lambda/getTypes.test.ts b/lib/lambda/getTypes.test.ts index 4cfce9211f..370a417b82 100644 --- a/lib/lambda/getTypes.test.ts +++ b/lib/lambda/getTypes.test.ts @@ -5,11 +5,11 @@ import { CHIP_SPA_AUTHORITY_ID, MEDICAID_SPA_AUTHORITY_ID, NOT_FOUND_AUTHORITY_ID, - ERROR_AUTHORITY_ID, medicaidTypes, chipTypes, } from "mocks/data/types"; -import { TestTypeItemResult } from "mocks"; +import { TestTypeItemResult, errorTypeSearchHandler } from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; describe("getTypes Handler", () => { it("should return 400 if event body is missing", async () => { @@ -21,19 +21,40 @@ describe("getTypes Handler", () => { expect(res.body).toEqual(JSON.stringify({ message: "Event body required" })); }); - // TODO - should this be removed? when will the result be empty and not - // just a result with an empty hit array - it.skip("should return 400 if no types are found", async () => { + it("should return 400 if authority id is undefined", async () => { const event = { - body: JSON.stringify({ authorityId: NOT_FOUND_AUTHORITY_ID }), + body: JSON.stringify({ authorityId: undefined }), } as APIGatewayEvent; const res = await handler(event); expect(res.statusCode).toEqual(400); - expect(res.body).toEqual( - JSON.stringify({ message: "No record found for the given authority" }), - ); + expect(res.body).toEqual(JSON.stringify({ message: "Authority Id is required" })); + }); + + it("should return 500 if there is a server error", async () => { + mockedServer.use(errorTypeSearchHandler); + + const event = { + body: JSON.stringify({ authorityId: MEDICAID_SPA_AUTHORITY_ID }), + } as APIGatewayEvent; + + const res = await handler(event); + + expect(res.statusCode).toEqual(500); + expect(res.body).toEqual(JSON.stringify({ message: "Internal server error" })); + }); + + it("should return 200 and no hits if no types are found", async () => { + const event = { + body: JSON.stringify({ authorityId: NOT_FOUND_AUTHORITY_ID }), + } as APIGatewayEvent; + + const res = await handler(event); + const body = JSON.parse(res.body); + + expect(res.statusCode).toEqual(200); + expect(body.hits.hits).toEqual([]); }); it("should return 200 with the result if types are found", async () => { @@ -63,15 +84,4 @@ describe("getTypes Handler", () => { expect(type?._source?.name?.match(/Do Not Use/)).toBeFalsy(); }); }); - - it("should return 500 if an error occurs during processing", async () => { - const event = { - body: JSON.stringify({ authorityId: ERROR_AUTHORITY_ID }), - } as APIGatewayEvent; - - const res = await handler(event); - - expect(res.statusCode).toEqual(500); - expect(res.body).toEqual(JSON.stringify({ message: "Internal server error" })); - }); }); diff --git a/lib/lambda/getTypes.ts b/lib/lambda/getTypes.ts index ff3f5c0e2f..6f87a24903 100644 --- a/lib/lambda/getTypes.ts +++ b/lib/lambda/getTypes.ts @@ -52,6 +52,12 @@ export const getTypes = async (event: APIGatewayEvent) => { }); } const body = JSON.parse(event.body) as GetTypesBody; + if (!body.authorityId) { + return response({ + statusCode: 400, + body: { message: "Authority Id is required" }, + }); + } try { const result = await queryTypes(body.authorityId); if (!result) diff --git a/lib/lambda/sinkChangelog.test.ts b/lib/lambda/sinkChangelog.test.ts index 0135b18dfb..4813300a62 100644 --- a/lib/lambda/sinkChangelog.test.ts +++ b/lib/lambda/sinkChangelog.test.ts @@ -31,7 +31,7 @@ import { } from "mocks/data/submit/changelog"; const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}changelog`; -const TOPIC = "aws.onemac.migration.cdc"; +const TOPIC = "--mako--branch-name--aws.onemac.migration.cdc"; const TEST_ITEM = items[TEST_ITEM_ID]; const TEST_ITEM_KEY = Buffer.from(TEST_ITEM_ID).toString("base64"); const TEST_ITEM_UPDATE_ID = "MD-0005.R01.01"; @@ -160,9 +160,9 @@ describe("syncing Changelog events", () => { it("should handle a valid admin id update", async () => { const event = createKafkaEvent({ - [`${TOPIC}-02`]: [ + [`${TOPIC}-03`]: [ createKafkaRecord({ - topic: `${TOPIC}-01`, + topic: `${TOPIC}-03`, key: TEST_ITEM_UPDATE_KEY, value: convertObjToBase64({ id: TEST_ITEM_UPDATE_ID, @@ -196,9 +196,9 @@ describe("syncing Changelog events", () => { it("should handle a valid admin value update", async () => { const event = createKafkaEvent({ - [`${TOPIC}-02`]: [ + [`${TOPIC}-03`]: [ createKafkaRecord({ - topic: `${TOPIC}-02`, + topic: `${TOPIC}-03`, key: TEST_ITEM_KEY, value: convertObjToBase64({ id: TEST_ITEM_ID, @@ -237,9 +237,9 @@ describe("syncing Changelog events", () => { it("should handle a valid admin value and id update", async () => { const event = createKafkaEvent({ - [`${TOPIC}-02`]: [ + [`${TOPIC}-03`]: [ createKafkaRecord({ - topic: `${TOPIC}-02`, + topic: `${TOPIC}-03`, key: TEST_ITEM_KEY, value: convertObjToBase64({ id: TEST_ITEM_ID, @@ -254,7 +254,7 @@ describe("syncing Changelog events", () => { offset: 3, }), createKafkaRecord({ - topic: `${TOPIC}-02`, + topic: `${TOPIC}-03`, key: TEST_ITEM_UPDATE_KEY, value: convertObjToBase64({ id: TEST_ITEM_UPDATE_ID, @@ -348,9 +348,9 @@ describe("syncing Changelog events", () => { it("should handle a valid admin delete", async () => { const event = createKafkaEvent({ - [`${TOPIC}-02`]: [ + [`${TOPIC}-03`]: [ createKafkaRecord({ - topic: `${TOPIC}-02`, + topic: `${TOPIC}-03`, key: TEST_ITEM_KEY, value: convertObjToBase64({ id: TEST_ITEM_ID, @@ -484,9 +484,9 @@ describe("syncing Changelog events", () => { it("should skip invalid admin id update", async () => { const event = createKafkaEvent({ - [`${TOPIC}-02`]: [ + [`${TOPIC}-03`]: [ createKafkaRecord({ - topic: `${TOPIC}-02`, + topic: `${TOPIC}-03`, key: TEST_ITEM_UPDATE_KEY, value: convertObjToBase64({ id: TEST_ITEM_UPDATE_ID, diff --git a/lib/lambda/sinkCpocs.test.ts b/lib/lambda/sinkCpocs.test.ts index b2ad02db9e..f03453de09 100644 --- a/lib/lambda/sinkCpocs.test.ts +++ b/lib/lambda/sinkCpocs.test.ts @@ -1,55 +1,224 @@ -import { describe, expect, it, vi } from "vitest"; -import { handler as sinkCpocLamda } from "./sinkCpocs"; -import * as sinkLib from "../libs/sink-lib"; -import { ErrorType } from "../libs/sink-lib"; - -// key: base64EncodedString and when decoded is a string based id of a record -// value: base64EncodedString and when decoded is a json string with the entire record - -// const kafkaRecord: KafkaRecord = { -// topic: "testprefix--aws.seatool.debezium.cdc.SEA.dbo.Officers-xyz", -// headers: {}, -// key: Buffer.from("OH-0001.R00.00").toString("base64"), -// value: Buffer.from(JSON.stringify({})).toString("base64"), -// offset: 1, -// partition: 1, -// timestamp: new Date().getTime(), -// timestampType: "", -// }; - -// export function getTopic(topicPartition: string) { -// return topicPartition.split("--").pop()?.split("-").slice(0, -1)[0]; -// } -vi.stubEnv("osDomain", "testDomain"); - -describe("test sink cpoc", () => { - it("calls log error when topic is undefined", async () => { - const logErrorSpy = vi - .spyOn(sinkLib, "logError") - .mockImplementation(({ type }: { type: ErrorType }) => { - console.log({ type }); - }); - try { - await sinkCpocLamda( - { - bootstrapServers: "123", - eventSource: "", - records: { - bad: [], - }, - }, - {} as any, +import { describe, expect, it, vi, afterEach } from "vitest"; +import { handler } from "./sinkCpocs"; +import { Context } from "aws-lambda"; +import * as os from "libs/opensearch-lib"; +import * as sink from "../libs/sink-lib"; +import { + convertObjToBase64, + createKafkaEvent, + createKafkaRecord, + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, +} from "mocks"; +import cpocs, { MUHAMMAD_BASHAR_ID } from "mocks/data/cpocs"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}cpocs`; +const TOPIC = "--mako--branch-name--aws.seatool.debezium.cdc.SEA.dbo.Officers"; +const MUHAMMAD_BASHAR_KEY = Buffer.from(`${MUHAMMAD_BASHAR_ID}`).toString("base64"); +const MUHAMMAD_BASHAR = cpocs[MUHAMMAD_BASHAR_ID]; + +describe("test sync cpoc", () => { + const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); + const logErrorSpy = vi.spyOn(sink, "logError"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should throw an error if the topic is undefined", async () => { + await expect(() => + handler( + createKafkaEvent({ + undefined: [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (undefined) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should throw an error if the topic is invalid", async () => { + await expect(() => + handler( + createKafkaEvent({ + "invalid-topic": [], + }), + {} as Context, vi.fn(), - ); - } catch { - expect(logErrorSpy).toHaveBeenCalledWith({ type: ErrorType.BADTOPIC }); - } - expect(logErrorSpy).toHaveBeenLastCalledWith( - expect.objectContaining({ type: ErrorType.UNKNOWN }), + ), + ).rejects.toThrowError("topic (invalid-topic) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should skip if the key is invalid", async () => { + await handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + // @ts-expect-error need key undefined for test + key: undefined, + value: convertObjToBase64({ + id: MUHAMMAD_BASHAR_ID, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should skip if record has no value", async () => { + await handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + // @ts-expect-error needs to be undefined for the test + value: undefined, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should skip if there is no payload", async () => { + await handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + value: convertObjToBase64({ + id: MUHAMMAD_BASHAR_KEY, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should skip if there is no record", async () => { + await handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + value: convertObjToBase64({ + payload: { + after: undefined, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: `${MUHAMMAD_BASHAR_ID}`, + delete: true, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); }); - it("has an empty array set in docs when given one undefined value", () => { - // spy on the bulkDataUpdateWrapper to determine if this test is true + it("should handle an invalid record", async () => { + await handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + value: convertObjToBase64({ + payload: { + after: { + Officer_ID: MUHAMMAD_BASHAR_ID, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.VALIDATION, + }), + ); + }); + + it("should handle a valid record", async () => { + await handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + value: convertObjToBase64({ + payload: { + after: { + Officer_ID: MUHAMMAD_BASHAR_ID, + First_Name: MUHAMMAD_BASHAR._source?.firstName, + Last_Name: MUHAMMAD_BASHAR._source?.lastName, + Email: MUHAMMAD_BASHAR._source?.email, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...MUHAMMAD_BASHAR._source, + }, + ]); }); }); diff --git a/lib/lambda/sinkSubtypes.test.ts b/lib/lambda/sinkSubtypes.test.ts new file mode 100644 index 0000000000..cb950e65dd --- /dev/null +++ b/lib/lambda/sinkSubtypes.test.ts @@ -0,0 +1,200 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { handler } from "./sinkSubtypes"; +import { Context } from "aws-lambda"; +import * as os from "libs/opensearch-lib"; +import * as sink from "../libs/sink-lib"; +import { + convertObjToBase64, + createKafkaEvent, + createKafkaRecord, + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, +} from "mocks"; +import { subtypes } from "mocks/data/types"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}subtypes`; +const TEST_SUBTYPE = subtypes[0]; +const TEST_SUBTYPE_ID = TEST_SUBTYPE._source.id; +const TEST_SUBTYPE_KEY = Buffer.from(`${TEST_SUBTYPE_ID}`).toString("base64"); +const TOPIC = `--mako--branch-name--aws.seatool.debezium.cdc.SEA.dbo.Type-${TEST_SUBTYPE_ID}`; + +describe("test sync subtypes", () => { + const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); + const logErrorSpy = vi.spyOn(sink, "logError"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should throw an error if the topic is undefined", async () => { + await expect(() => + handler( + createKafkaEvent({ + undefined: [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (undefined) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should throw an error if the topic is invalid", async () => { + await expect(() => + handler( + createKafkaEvent({ + "invalid-topic": [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (invalid-topic) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should skip if the record has no value", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_SUBTYPE_KEY, + // @ts-expect-error needs to be undefined for test + value: undefined, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should skip if there is no payload", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_SUBTYPE_KEY, + value: convertObjToBase64({ + id: TEST_SUBTYPE._source.id, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should skip if there is no record", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_SUBTYPE_KEY, + value: convertObjToBase64({ + payload: { + after: undefined, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should handle an invalid record", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_SUBTYPE_KEY, + value: convertObjToBase64({ + payload: { + after: { + Type_Id: TEST_SUBTYPE._source.id, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.VALIDATION, + }), + ); + }); + + it("should handle a valid record", async () => { + const { id, typeId, name, authorityId } = TEST_SUBTYPE._source; + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_SUBTYPE_KEY, + value: convertObjToBase64({ + payload: { + after: { + Type_Id: id, + Type_Name: name, + Type_Class: typeId, + Plan_Type_ID: authorityId, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...TEST_SUBTYPE._source, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/lambda/sinkSubtypes.ts b/lib/lambda/sinkSubtypes.ts index ed460ec4f4..d6139c26be 100644 --- a/lib/lambda/sinkSubtypes.ts +++ b/lib/lambda/sinkSubtypes.ts @@ -10,12 +10,12 @@ export const handler: Handler = async (event) => { for (const topicPartition of Object.keys(event.records)) { const topic = getTopic(topicPartition); switch (topic) { - case undefined: - logError({ type: ErrorType.BADTOPIC }); - throw new Error(); case "aws.seatool.debezium.cdc.SEA.dbo.Type": await subtypes(event.records[topicPartition], topicPartition); break; + default: + logError({ type: ErrorType.BADTOPIC }); + throw new Error(`topic (${topicPartition}) is invalid`); } } } catch (error) { diff --git a/lib/lambda/sinkTypes.test.ts b/lib/lambda/sinkTypes.test.ts new file mode 100644 index 0000000000..116efb5a49 --- /dev/null +++ b/lib/lambda/sinkTypes.test.ts @@ -0,0 +1,199 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { handler } from "./sinkTypes"; +import { Context } from "aws-lambda"; +import * as os from "libs/opensearch-lib"; +import * as sink from "../libs/sink-lib"; +import { + convertObjToBase64, + createKafkaEvent, + createKafkaRecord, + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, +} from "mocks"; +import { types } from "mocks/data/types"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}types`; +const TEST_TYPE = types[0]; +const TEST_TYPE_ID = TEST_TYPE._source.id; +const TEST_TYPE_KEY = Buffer.from(`${TEST_TYPE_ID}`).toString("base64"); +const TOPIC = `--mako--branch-name--aws.seatool.debezium.cdc.SEA.dbo.SPA_Type-${TEST_TYPE_ID}`; + +describe("test sync types", () => { + const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); + const logErrorSpy = vi.spyOn(sink, "logError"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should throw an error if the topic is undefined", async () => { + await expect(() => + handler( + createKafkaEvent({ + undefined: [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (undefined) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should throw an error if the topic is invalid", async () => { + await expect(() => + handler( + createKafkaEvent({ + "invalid-topic": [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (invalid-topic) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should skip if the record has no value", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_TYPE_KEY, + // @ts-expect-error needs to be undefined for test + value: undefined, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should skip if there is no payload", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_TYPE_KEY, + value: convertObjToBase64({ + id: TEST_TYPE._source.id, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should skip if there is no record", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_TYPE_KEY, + value: convertObjToBase64({ + payload: { + after: undefined, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should handle an invalid record", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_TYPE_KEY, + value: convertObjToBase64({ + payload: { + after: { + SPA_Type_ID: TEST_TYPE._source.id, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.VALIDATION, + }), + ); + }); + + it("should handle a valid record", async () => { + const { id, name, authorityId } = TEST_TYPE._source; + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_TYPE_KEY, + value: convertObjToBase64({ + payload: { + after: { + SPA_Type_ID: id, + SPA_Type_Name: name, + Plan_Type_ID: authorityId, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...TEST_TYPE._source, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/lambda/sinkTypes.ts b/lib/lambda/sinkTypes.ts index 922bb476b8..d6b5cce5f0 100644 --- a/lib/lambda/sinkTypes.ts +++ b/lib/lambda/sinkTypes.ts @@ -10,12 +10,12 @@ export const handler: Handler = async (event) => { for (const topicPartition of Object.keys(event.records)) { const topic = getTopic(topicPartition); switch (topic) { - case undefined: - logError({ type: ErrorType.BADTOPIC }); - throw new Error(); case "aws.seatool.debezium.cdc.SEA.dbo.SPA_Type": await types(event.records[topicPartition], topicPartition); break; + default: + logError({ type: ErrorType.BADTOPIC }); + throw new Error(`topic (${topicPartition}) is invalid`); } } } catch (error) { diff --git a/lib/lambda/submit/index.test.ts b/lib/lambda/submit/index.test.ts index 405fbd8499..293265b87c 100644 --- a/lib/lambda/submit/index.test.ts +++ b/lib/lambda/submit/index.test.ts @@ -1,18 +1,18 @@ import { describe, it, expect } from "vitest"; import { submit } from "./index"; import { APIGatewayEvent } from "node_modules/shared-types"; -import { getRequestContext } from "mocks"; -import { events } from "mocks/data/submit/base"; +import { getRequestContext, NOT_FOUND_ITEM_ID } from "mocks"; +import { events, uploadSubsequentDocuments } from "mocks/data/submit/base"; describe("submit Lambda function", () => { - it("should have no body", async () => { + it("should handle a submission with no body", async () => { const event = {} as APIGatewayEvent; const result = await submit(event); expect(result.statusCode).toEqual(400); expect(result.body).toEqual('"Event body required"'); }); - it("should have no event in the body", async () => { + it("should handle a submission with no event in the body", async () => { const event = { body: `{"event": ""}`, } as unknown as APIGatewayEvent; @@ -21,7 +21,7 @@ describe("submit Lambda function", () => { expect(result.body).toEqual('{"message":"Bad Request - Missing event name in body"}'); }); - it("should have a bad event in the body", async () => { + it("should handle a submission with a bad event in the body", async () => { const event = { body: `{"event": "Not a real event"}`, } as unknown as APIGatewayEvent; @@ -30,6 +30,21 @@ describe("submit Lambda function", () => { expect(result.body).toEqual('{"message":"Bad Request - Unknown event type Not a real event"}'); }); + it("should handle an upload-subsequent-document with an invalid item ID", async () => { + const event = { + body: JSON.stringify({ + ...uploadSubsequentDocuments, + id: NOT_FOUND_ITEM_ID, + waiverNumber: NOT_FOUND_ITEM_ID, + }), + requestContext: getRequestContext(), + } as unknown as APIGatewayEvent; + const result = await submit(event); + + expect(result.statusCode).toEqual(500); + expect(result.body).toEqual('{"message":"Internal server error"}'); + }); + describe("successfully submit event types", () => { it.each(events.map((event) => [event.event, JSON.stringify(event)]))( "should successfully submit %s event", diff --git a/lib/lambda/submit/index.ts b/lib/lambda/submit/index.ts index e93023e4a5..3b6975845c 100644 --- a/lib/lambda/submit/index.ts +++ b/lib/lambda/submit/index.ts @@ -33,11 +33,7 @@ export const submit = async (event: APIGatewayEvent) => { try { const eventBody = await submissionPayloads[body.event](event); - await produceMessage( - process.env.topicName as string, - body.id, - JSON.stringify(eventBody), - ); + await produceMessage(process.env.topicName as string, body.id, JSON.stringify(eventBody)); return response({ statusCode: 200, diff --git a/mocks/data/cpocs.ts b/mocks/data/cpocs.ts index bb240840a1..22c4935211 100644 --- a/mocks/data/cpocs.ts +++ b/mocks/data/cpocs.ts @@ -1,101 +1,109 @@ -export const MUHAMMAD_BASHAR_ID = ""; -export const ELTON_BEATTY_ID = ""; -export const JEDIDIAH_HAYES_ID = ""; -export const MARSHALL_HENDERSON_ID = ""; -export const BRIDGET_HERMANN_ID = ""; -export const MAGGIE_LOWENSTEIN_ID = ""; -export const THOMAS_MANN_ID = ""; -export const DESIREE_MAYER_ID = ""; -export const WINSTON_OCONNOR_ID = ""; -export const MACY_TREMBLAY_ID = ""; +import { TestCpocsItemResult } from "../index.d"; -export const cpocs = [ - { - _id: MUHAMMAD_BASHAR_ID, +export const MUHAMMAD_BASHAR_ID = 100; +export const ELTON_BEATTY_ID = 101; +export const JEDIDIAH_HAYES_ID = 102; +export const MARSHALL_HENDERSON_ID = 103; +export const BRIDGET_HERMANN_ID = 104; +export const MAGGIE_LOWENSTEIN_ID = 105; +export const THOMAS_MANN_ID = 106; +export const DESIREE_MAYER_ID = 107; +export const WINSTON_OCONNOR_ID = 108; +export const MACY_TREMBLAY_ID = 109; + +const cpocs: Record = { + [MUHAMMAD_BASHAR_ID]: { + _id: `${MUHAMMAD_BASHAR_ID}`, _source: { id: MUHAMMAD_BASHAR_ID, firstName: "Muhammad", lastName: "Bashar", - email: "muhammad.bashar@example.com" - } + email: "muhammad.bashar@example.com", + }, }, - { - _id: ELTON_BEATTY_ID, + [ELTON_BEATTY_ID]: { + _id: `${ELTON_BEATTY_ID}`, _source: { id: ELTON_BEATTY_ID, firstName: "Elton", lastName: "Beatty", - email: "elton.beatty@example.com" - } + email: "elton.beatty@example.com", + }, }, - { - _id: JEDIDIAH_HAYES_ID, + [JEDIDIAH_HAYES_ID]: { + _id: `${JEDIDIAH_HAYES_ID}`, _source: { id: JEDIDIAH_HAYES_ID, firstName: "Jedidiah", lastName: "Hayes", - email: "jedidiah.hayes@example.com" - } + email: "jedidiah.hayes@example.com", + }, }, - { - _id: MARSHALL_HENDERSON_ID, + [MARSHALL_HENDERSON_ID]: { + _id: `${MARSHALL_HENDERSON_ID}`, _source: { id: MARSHALL_HENDERSON_ID, firstName: "Marshall", lastName: "Henderson", - email: "marshall.henderson@example.com" - } + email: "marshall.henderson@example.com", + }, }, - { - _id: BRIDGET_HERMANN_ID, + [BRIDGET_HERMANN_ID]: { + _id: `${BRIDGET_HERMANN_ID}`, _source: { id: BRIDGET_HERMANN_ID, firstName: "Bridget", lastName: "Hermann", - email: "bridget.hermann@example.com" - } - },{ - _id: MAGGIE_LOWENSTEIN_ID, + email: "bridget.hermann@example.com", + }, + }, + [MAGGIE_LOWENSTEIN_ID]: { + _id: `${MAGGIE_LOWENSTEIN_ID}`, _source: { id: MAGGIE_LOWENSTEIN_ID, firstName: "Maggie", lastName: "Lowenstein", - email: "maggie.lowenstein@example.com" - } + email: "maggie.lowenstein@example.com", + }, }, - { - _id: THOMAS_MANN_ID, + [THOMAS_MANN_ID]: { + _id: `${THOMAS_MANN_ID}`, _source: { id: THOMAS_MANN_ID, firstName: "Thomas", lastName: "Mann", - email: "thomas.mann@example.com" - } + email: "thomas.mann@example.com", + }, }, - { - _id: DESIREE_MAYER_ID, + [DESIREE_MAYER_ID]: { + _id: `${DESIREE_MAYER_ID}`, _source: { id: DESIREE_MAYER_ID, firstName: "Desiree", lastName: "Mayer", - email: "desiree.mayer@example.com" - } - },{ - _id: WINSTON_OCONNOR_ID, + email: "desiree.mayer@example.com", + }, + }, + [WINSTON_OCONNOR_ID]: { + _id: `${WINSTON_OCONNOR_ID}`, _source: { id: WINSTON_OCONNOR_ID, firstName: "Winston", lastName: "O'Connor", - email: "winston.oconnor@example.com" - } + email: "winston.oconnor@example.com", + }, }, - { - _id: MACY_TREMBLAY_ID, + [MACY_TREMBLAY_ID]: { + _id: `${MACY_TREMBLAY_ID}`, _source: { id: MACY_TREMBLAY_ID, firstName: "Macy", lastName: "Tremblay", - email: "macy.tremblay@example.com" - } + email: "macy.tremblay@example.com", + }, }, -] +}; + +export default cpocs; + +export const cpocsList = Object.values(cpocs); diff --git a/mocks/data/types.ts b/mocks/data/types.ts index 17df65441a..95bd65202b 100644 --- a/mocks/data/types.ts +++ b/mocks/data/types.ts @@ -1,68 +1,75 @@ -export const CHIP_SPA_AUTHORITY_ID = "124"; -export const MEDICAID_SPA_AUTHORITY_ID = "125"; -export const NOT_FOUND_AUTHORITY_ID = "10"; +export const CHIP_SPA_AUTHORITY_ID = 124; +export const MEDICAID_SPA_AUTHORITY_ID = 125; +export const NOT_FOUND_AUTHORITY_ID = 10; export const ERROR_AUTHORITY_ID = "throw error"; -export const TYPE_ONE_ID = "1"; -export const TYPE_TWO_ID = "2"; -export const TYPE_THREE_ID = "3"; -export const DO_NOT_USE_TYPE_ID = "4"; +export const TYPE_ONE_ID = 1; +export const TYPE_TWO_ID = 2; +export const TYPE_THREE_ID = 3; +export const DO_NOT_USE_TYPE_ID = 4; -export const medicaidTypes = [{ - _source: { - id: TYPE_ONE_ID, - authorityId: MEDICAID_SPA_AUTHORITY_ID, - name: "Type One" +export const medicaidTypes = [ + { + _source: { + id: TYPE_ONE_ID, + authorityId: MEDICAID_SPA_AUTHORITY_ID, + name: "Type One", }, }, { - _source: { - id: TYPE_TWO_ID, - authorityId: MEDICAID_SPA_AUTHORITY_ID, - name: "Type Two" + _source: { + id: TYPE_TWO_ID, + authorityId: MEDICAID_SPA_AUTHORITY_ID, + name: "Type Two", }, - }]; + }, +]; -export const chipTypes = [{ - _source: { - id: TYPE_THREE_ID, - authorityId: CHIP_SPA_AUTHORITY_ID, - name: "Type Three" +export const chipTypes = [ + { + _source: { + id: TYPE_THREE_ID, + authorityId: CHIP_SPA_AUTHORITY_ID, + name: "Type Three", }, - }] + }, +]; export const types = [ ...medicaidTypes, ...chipTypes, { - _source: { - id: DO_NOT_USE_TYPE_ID, - authorityId: CHIP_SPA_AUTHORITY_ID, - name: "Do Not Use Type Four" + _source: { + id: DO_NOT_USE_TYPE_ID, + authorityId: CHIP_SPA_AUTHORITY_ID, + name: "Do Not Use Type Four", }, }, ]; -export const medicaidSubtypes = [{ - _source: { - id: "4", - authorityId: MEDICAID_SPA_AUTHORITY_ID, - name: "Sub Type Four", - typeId: TYPE_ONE_ID +export const medicaidSubtypes = [ + { + _source: { + id: 4, + authorityId: MEDICAID_SPA_AUTHORITY_ID, + name: "Sub Type Four", + typeId: TYPE_ONE_ID, }, }, { - _source: { - id: "5", - authorityId: MEDICAID_SPA_AUTHORITY_ID, - name: "Sub Type Five", - typeId: TYPE_TWO_ID + _source: { + id: 5, + authorityId: MEDICAID_SPA_AUTHORITY_ID, + name: "Sub Type Five", + typeId: TYPE_TWO_ID, }, - }] + }, +]; -export const chipSubtypes = [{ +export const chipSubtypes = [ + { _source: { - id: "6", + id: 6, authorityId: CHIP_SPA_AUTHORITY_ID, name: "Sub Type Six", typeId: TYPE_THREE_ID, @@ -70,19 +77,20 @@ export const chipSubtypes = [{ }, { _source: { - id: "7", + id: 7, authorityId: CHIP_SPA_AUTHORITY_ID, name: "Sub Type Seven", typeId: TYPE_THREE_ID, }, - }] + }, +]; export const subtypes = [ ...medicaidSubtypes, ...chipSubtypes, { _source: { - id: "8", + id: 8, authorityId: CHIP_SPA_AUTHORITY_ID, name: "Do Not Use Sub Type Eight", typeId: DO_NOT_USE_TYPE_ID, diff --git a/mocks/handlers/api/index.ts b/mocks/handlers/api/index.ts index dade0e5f72..abce82fd4e 100644 --- a/mocks/handlers/api/index.ts +++ b/mocks/handlers/api/index.ts @@ -2,14 +2,8 @@ import { itemHandlers } from "./items"; import { submissionHandlers } from "./submissions"; import { typeHandlers } from "./types"; -export const apiHandlers = [ - ...itemHandlers, - ...submissionHandlers, - ...typeHandlers -]; +export const apiHandlers = [...itemHandlers, ...submissionHandlers, ...typeHandlers]; -export { - mockCurrentAuthenticatedUser, - mockUseGetUser, - mockUserAttributes, -} from "./user"; +export { mockCurrentAuthenticatedUser, mockUseGetUser, mockUserAttributes } from "./user"; + +export { errorSubTypesHandler, errorTypeHandler } from "./types"; diff --git a/mocks/handlers/api/types.ts b/mocks/handlers/api/types.ts index abb15ad130..fda5255468 100644 --- a/mocks/handlers/api/types.ts +++ b/mocks/handlers/api/types.ts @@ -1,17 +1,17 @@ import { http, HttpResponse } from "msw"; -import { types, subtypes, ERROR_AUTHORITY_ID } from "../../data/types"; +import { types, subtypes } from "../../data/types"; -type GetTypesBody = { authorityId: string }; -type GetSubTypesBody = { authorityId: string; typeIds: string[] }; +type GetTypesBody = { authorityId: number }; +type GetSubTypesBody = { authorityId: number; typeIds: number[] }; const defaultTypeHandler = http.post(/\/getTypes$/, async ({ request }) => { const { authorityId } = await request.json(); - if (authorityId === ERROR_AUTHORITY_ID) { - throw Error("useGetTypes > mockFetch: Expected error thrown by test."); - } - - const hits = types.filter(type => type?._source?.authorityId == authorityId && !type?._source?.name.match(/Do Not Use/)) || [] + const hits = + types.filter( + (type) => + type?._source?.authorityId == authorityId && !type?._source?.name.match(/Do Not Use/), + ) || []; return HttpResponse.json({ hits: { @@ -20,20 +20,23 @@ const defaultTypeHandler = http.post(/\/getTypes$/, async ({ }); }); +export const errorTypeHandler = http.post( + /\/getTypes$/, + async () => new HttpResponse("Internal server error", { status: 500 }), +); + const defaultSubTypesHandler = http.post( /\/getSubTypes$/, async ({ request }) => { const { authorityId, typeIds } = await request.json(); - if (authorityId === ERROR_AUTHORITY_ID) { - throw Error("useGetSubTypes > mockFetch: Expected error thrown by test."); - } - - const hits = subtypes.filter(type => - type?._source?.authorityId == authorityId - && typeIds.includes(type?._source?.typeId) - && !type?._source?.name.match(/Do Not Use/) - ) || [] + const hits = + subtypes.filter( + (type) => + type?._source?.authorityId == authorityId && + typeIds.includes(type?._source?.typeId) && + !type?._source?.name.match(/Do Not Use/), + ) || []; return HttpResponse.json({ hits: { @@ -43,4 +46,9 @@ const defaultSubTypesHandler = http.post( }, ); +export const errorSubTypesHandler = http.post( + /\/getSubTypes$/, + () => new HttpResponse("Internal server error", { status: 500 }), +); + export const typeHandlers = [defaultTypeHandler, defaultSubTypesHandler]; diff --git a/mocks/handlers/opensearch/cpocs.ts b/mocks/handlers/opensearch/cpocs.ts index 7a1ec302e9..8e603ca865 100644 --- a/mocks/handlers/opensearch/cpocs.ts +++ b/mocks/handlers/opensearch/cpocs.ts @@ -1,36 +1,37 @@ import { http, HttpResponse } from "msw"; -import { cpocs } from "../../data/cpocs"; +import { cpocsList } from "../../data/cpocs"; const defaultCpocSearchHandler = http.post( - "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", - () => HttpResponse.json({ - "took": 3, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": { - "value": 654, - "relation": "eq" + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", + () => + HttpResponse.json({ + took: 3, + timed_out: false, + _shards: { + total: 5, + successful: 5, + skipped: 0, + failed: 0, }, - "max_score": 1, - "hits": cpocs - } - }) -) + hits: { + total: { + value: 654, + relation: "eq", + }, + max_score: 1, + hits: cpocsList, + }, + }), +); export const emptyCpocSearchHandler = http.post( - "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", - () => new HttpResponse() + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", + () => new HttpResponse(), ); export const errorCpocSearchHandler = http.post( - "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", - () => new HttpResponse("Internal server error", { status: 500 }) + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-cpocs/_search", + () => new HttpResponse("Internal server error", { status: 500 }), ); export const cpocSearchHandlers = [defaultCpocSearchHandler]; diff --git a/mocks/handlers/opensearch/index.ts b/mocks/handlers/opensearch/index.ts index bf9cf4fff8..5d0c78199b 100644 --- a/mocks/handlers/opensearch/index.ts +++ b/mocks/handlers/opensearch/index.ts @@ -24,3 +24,5 @@ export { errorDeleteIndexHandler, } from "./indices"; export { errorSecurityRolesMappingHandler } from "./security"; +export { errorSubtypeSearchHandler } from "./subtypes"; +export { errorTypeSearchHandler } from "./types"; diff --git a/mocks/handlers/opensearch/subtypes.ts b/mocks/handlers/opensearch/subtypes.ts index d6b3a5058f..94151831d3 100644 --- a/mocks/handlers/opensearch/subtypes.ts +++ b/mocks/handlers/opensearch/subtypes.ts @@ -1,28 +1,28 @@ import { http, HttpResponse, PathParams } from "msw"; -import { subtypes, ERROR_AUTHORITY_ID } from "../../data/types" -import { - SearchQueryBody, -} from "../../index.d"; -import { getFilterValue } from "./util"; +import { subtypes } from "../../data/types"; +import { SearchQueryBody } from "../../index.d"; +import { getFilterValueAsNumber, getFilterValueAsNumberArray } from "./util"; const defaultSubtypeSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-subtypes/_search", async ({ request }) => { const { query } = await request.json(); const must = query?.bool?.must; - - const authorityId = getFilterValue(must, 'match', 'authorityId'); - const typeIds = getFilterValue(must, 'terms', 'typeId') || []; - if (authorityId === ERROR_AUTHORITY_ID) { - return new HttpResponse("Internal server error", { status: 500 }); + const authorityId = getFilterValueAsNumber(must, "match", "authorityId"); + const typeIds = getFilterValueAsNumberArray(must, "terms", "typeId"); + + if (authorityId === undefined) { + return new HttpResponse("Invalid authority Id", { status: 400 }); } - const hits = subtypes.filter(type => - type?._source?.authorityId == authorityId - && typeIds.includes(type?._source?.typeId) - && !type?._source?.name.match(/Do Not Use/) - ) || [] + const hits = + subtypes.filter( + (type) => + type?._source?.authorityId == authorityId && + typeIds.includes(type?._source?.typeId) && + !type?._source?.name.match(/Do Not Use/), + ) || []; return HttpResponse.json({ took: 5, @@ -45,4 +45,9 @@ const defaultSubtypeSearchHandler = http.post( }, ); +export const errorSubtypeSearchHandler = http.post( + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-subtypes/_search", + () => new HttpResponse("Internal server error", { status: 500 }), +); + export const subtypeSearchHandlers = [defaultSubtypeSearchHandler]; diff --git a/mocks/handlers/opensearch/types.ts b/mocks/handlers/opensearch/types.ts index f81f9abff4..5c298ecd94 100644 --- a/mocks/handlers/opensearch/types.ts +++ b/mocks/handlers/opensearch/types.ts @@ -1,21 +1,25 @@ import { http, HttpResponse, PathParams } from "msw"; -import { types, ERROR_AUTHORITY_ID } from "../../data/types" +import { types } from "../../data/types"; import { SearchQueryBody } from "../../index.d"; -import { getFilterValue } from "./util"; +import { getFilterValueAsNumber } from "./util"; const defaultTypeSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-types/_search", async ({ request }) => { const { query } = await request.json(); const must = query?.bool?.must; - - const authorityId = getFilterValue(must, 'match', 'authorityId'); - if (authorityId === ERROR_AUTHORITY_ID) { - return new HttpResponse("Internal server error", { status: 500 }); + const authorityId = getFilterValueAsNumber(must, "match", "authorityId"); + + if (authorityId === undefined) { + return new HttpResponse("Invalid authority Id", { status: 400 }); } - const hits = types.filter(type => type?._source?.authorityId == authorityId && !type?._source?.name.match(/Do Not Use/)) || [] + const hits = + types.filter( + (type) => + type?._source?.authorityId == authorityId && !type?._source?.name.match(/Do Not Use/), + ) || []; return HttpResponse.json({ took: 5, @@ -38,4 +42,9 @@ const defaultTypeSearchHandler = http.post( }, ); +export const errorTypeSearchHandler = http.post( + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-types/_search", + () => new HttpResponse("Internal server error", { status: 500 }), +); + export const typeSearchHandlers = [defaultTypeSearchHandler]; diff --git a/mocks/handlers/opensearch/util.ts b/mocks/handlers/opensearch/util.ts index 9f26b4011a..53ffd55d8e 100644 --- a/mocks/handlers/opensearch/util.ts +++ b/mocks/handlers/opensearch/util.ts @@ -6,46 +6,125 @@ export const getFilterValue = ( filterName: string, ): string | string[] | undefined => { if (query) { - const rules: QueryContainer[] = (Array.isArray(query)) ? query : [query]; + const rules: QueryContainer[] = Array.isArray(query) ? query : [query]; const values: string[] = []; - rules.forEach(rule => { + rules.forEach((rule) => { if (rule?.[queryKey]?.[filterName] !== undefined) { if (Array.isArray(rule[queryKey][filterName])) { - rule[queryKey][filterName].forEach(value => { + rule[queryKey][filterName].forEach((value) => { if (value !== undefined) { - values.push(value.toString()) + values.push(value.toString()); } - }) + }); } else { values.push(rule[queryKey][filterName].toString()); } } - }) + }); if (values.length === 0) return undefined; if (values.length === 1) return values[0].toString(); - return values.map(value => value.toString()); + return values.map((value) => value.toString()); } return undefined; }; +export const getFilterValueAsBoolean = ( + query: QueryContainer | QueryContainer[] | undefined, + queryKey: keyof QueryContainer, + filterName: string, +): boolean | undefined => { + const value = getFilterValue(query, queryKey, filterName); + + if (value === undefined) { + return undefined; + } + + const parseSingleStringBoolean = (value: string | undefined): boolean | undefined => { + if (typeof value === "string") { + if (value.toLowerCase() === "true" || value.toLowerCase() === "yes") { + return true; + } + if (value.toLowerCase() === "false" || value.toLowerCase() === "no") { + return false; + } + } + return undefined; + }; + + if (typeof value === "string") { + return parseSingleStringBoolean(value); + } + + if (Array.isArray(value) && value.length > 0) { + const boolValues = value + .map((val) => parseSingleStringBoolean(val)) + .filter((val) => val !== undefined); + if (boolValues.length > 0) { + return boolValues.every((val) => val === true); + } + } + + return undefined; +}; + +export const getFilterValueAsNumber = ( + query: QueryContainer | QueryContainer[] | undefined, + queryKey: keyof QueryContainer, + filterName: string, +): number | undefined => { + const value = getFilterValue(query, queryKey, filterName); + + const intValues = parseValueAsNumberArray(value); + + if (intValues.length > 0) { + return intValues[0]; + } + + return undefined; +}; + +export const getFilterValueAsNumberArray = ( + query: QueryContainer | QueryContainer[] | undefined, + queryKey: keyof QueryContainer, + filterName: string, +): number[] => { + const value = getFilterValue(query, queryKey, filterName); + + return parseValueAsNumberArray(value); +}; + +const parseValueAsNumberArray = (value: string | string[] | undefined): number[] => { + if (value == undefined) { + return []; + } + + if (typeof value === "string") { + return [Number.parseInt(value)]; + } + + return ( + value.filter((val) => val && typeof val === "string").map((val) => Number.parseInt(val)) || [] + ); +}; + export const getTermValues = ( query: QueryContainer | QueryContainer[] | undefined, filterName: string, ): string | string[] | undefined => { - const term = getFilterValue(query, 'term', filterName); - const terms = getFilterValue(query, 'terms', filterName); + const term = getFilterValue(query, "term", filterName); + const terms = getFilterValue(query, "terms", filterName); if (term && terms) { const values: string[] = []; - values.concat(Array.isArray(term) ? term : [term]) + values.concat(Array.isArray(term) ? term : [term]); values.concat(Array.isArray(terms) ? terms : [terms]); return values; } - + return term || terms; -} +}; export const getTermKeys = (query: QueryContainer[] | QueryContainer | undefined): string[] => { const filterKeys: string[] = []; @@ -61,10 +140,10 @@ export const getTermKeys = (query: QueryContainer[] | QueryContainer | undefined }); } else { if ((query as QueryContainer)?.term !== undefined) { - filterKeys.push(...Object.keys((query.term as Record))); + filterKeys.push(...Object.keys(query.term as Record)); } if ((query as QueryContainer)?.terms !== undefined) { - filterKeys.push(...Object.keys((query.terms as TermsQuery))); + filterKeys.push(...Object.keys(query.terms as TermsQuery)); } } } @@ -75,7 +154,7 @@ export const matchFilter = ( item: T | null | undefined, filterTerm: keyof T | null | undefined, filterValue: string | string[] | null | undefined, -): boolean => { +): boolean => { if (!item || !filterTerm || !filterValue) { return false; } @@ -87,14 +166,15 @@ export const matchFilter = ( } return filterValue?.toString()?.toLocaleLowerCase() == itemValue; -} +}; export const filterItemsByTerm = ( hits: TestHit[], filterTerm: keyof D, - filterValue: string | string[] + filterValue: string | string[], ): TestHit[] => { return hits.filter( - (hit) => (hit as TestHit)?._source && matchFilter((hit._source as D), filterTerm, filterValue) - ) -} + (hit) => + (hit as TestHit)?._source && matchFilter(hit._source as D, filterTerm, filterValue), + ); +}; diff --git a/mocks/index.d.ts b/mocks/index.d.ts index 2def0c55ee..4b8d79e68d 100644 --- a/mocks/index.d.ts +++ b/mocks/index.d.ts @@ -32,6 +32,10 @@ export type TestSubtypeItemResult = DeepPartial; export type TestSubtypeDocument = TestSubtypeItemResult["_source"]; +export type TestCpocsItemResult = DeepPartial; + +export type TestCpocsDocument = TestCpocsItemResult["_source"]; + export type TestSecretData = Partial> & { CreatedDate: number; DeletedDate?: number; diff --git a/react-app/src/api/useGetTypes.test.ts b/react-app/src/api/useGetTypes.test.ts index 37bcb75cd4..dacf6a638a 100644 --- a/react-app/src/api/useGetTypes.test.ts +++ b/react-app/src/api/useGetTypes.test.ts @@ -1,30 +1,32 @@ import { describe, expect, it } from "vitest"; import { fetchData } from "./useGetTypes"; -import { - MEDICAID_SPA_AUTHORITY_ID, - CHIP_SPA_AUTHORITY_ID, - NOT_FOUND_AUTHORITY_ID, - ERROR_AUTHORITY_ID, +import { + MEDICAID_SPA_AUTHORITY_ID, + CHIP_SPA_AUTHORITY_ID, + NOT_FOUND_AUTHORITY_ID, + ERROR_AUTHORITY_ID, TYPE_ONE_ID, TYPE_TWO_ID, TYPE_THREE_ID, DO_NOT_USE_TYPE_ID, - medicaidTypes, + medicaidTypes, medicaidSubtypes, chipTypes, - chipSubtypes -} from "mocks/data/types" + chipSubtypes, +} from "mocks/data/types"; +import { errorSubTypesHandler, errorTypeHandler } from "mocks"; +import { mockedApiServer as mockedServer } from "mocks/server"; describe("fetchData", () => { describe("fetchTypes", () => { it("makes an AWS Amplify post request for types", async () => { const types = await fetchData({ authorityId: MEDICAID_SPA_AUTHORITY_ID }); - expect(types).toEqual(medicaidTypes.map(type => type?._source)); + expect(types).toEqual(medicaidTypes.map((type) => type?._source)); }); it("successfully fetches types for a given authorityId", async () => { const types = await fetchData({ authorityId: CHIP_SPA_AUTHORITY_ID }); - expect(types).toEqual(chipTypes.map(type => type?._source)); + expect(types).toEqual(chipTypes.map((type) => type?._source)); }); it("returns an empty array when there are no types", async () => { @@ -33,27 +35,42 @@ describe("fetchData", () => { }); it("throws an error when fetch fails", async () => { - await expect(fetchData({ authorityId: ERROR_AUTHORITY_ID })).rejects.toThrow("Failed to fetch types"); + mockedServer.use(errorTypeHandler); + + await expect(fetchData({ authorityId: ERROR_AUTHORITY_ID })).rejects.toThrow( + "Failed to fetch types", + ); }); }); describe("fetchSubTypes", () => { it("makes an AWS Amplify post request for subtypes", async () => { - const subtypes = await fetchData({ authorityId: MEDICAID_SPA_AUTHORITY_ID, typeIds: [TYPE_ONE_ID, TYPE_TWO_ID] }); - expect(subtypes).toEqual(medicaidSubtypes.map(subtype => subtype?._source)); + const subtypes = await fetchData({ + authorityId: MEDICAID_SPA_AUTHORITY_ID, + typeIds: [TYPE_ONE_ID, TYPE_TWO_ID], + }); + expect(subtypes).toEqual(medicaidSubtypes.map((subtype) => subtype?._source)); }); it("successfully fetches subtypes for a given authorityId and typeIds", async () => { - const subtypes = await fetchData({ authorityId: CHIP_SPA_AUTHORITY_ID, typeIds: [TYPE_THREE_ID, DO_NOT_USE_TYPE_ID] }); - expect(subtypes).toEqual(chipSubtypes.map(subtype => subtype?._source)); + const subtypes = await fetchData({ + authorityId: CHIP_SPA_AUTHORITY_ID, + typeIds: [TYPE_THREE_ID, DO_NOT_USE_TYPE_ID], + }); + expect(subtypes).toEqual(chipSubtypes.map((subtype) => subtype?._source)); }); it("returns an empty array when there are no subtypes", async () => { - const subtypes = await fetchData({ authorityId: CHIP_SPA_AUTHORITY_ID, typeIds: [DO_NOT_USE_TYPE_ID] }); + const subtypes = await fetchData({ + authorityId: CHIP_SPA_AUTHORITY_ID, + typeIds: [DO_NOT_USE_TYPE_ID], + }); expect(subtypes).toEqual([]); }); it("throws an error when fetch fails", async () => { + mockedServer.use(errorSubTypesHandler); + await expect(fetchData({ authorityId: ERROR_AUTHORITY_ID, typeIds: [] })).rejects.toThrow( "Failed to fetch subtypes", ); diff --git a/vitest.config.ts b/vitest.config.ts index 3a81640c05..b1518d5c67 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -17,6 +17,7 @@ export default defineConfig({ ".cdk", "docs/**", "lib/libs/webforms/**", + "lib/libs/email/mock-data/**", "react-app/src/features/webforms/**", "TestWrapper.tsx", "lib/stacks/**", From db402ba86ae0231fe4afce98120ed05429a8c623 Mon Sep 17 00:00:00 2001 From: Thomas Walker Date: Wed, 15 Jan 2025 10:27:47 -0500 Subject: [PATCH 10/17] feat(test) post auth test (#1020) * feat(test) post auth test * update test for mostly full coverage * update word to not look like a secret * some test clean up and refactoring * clean up * allow bad secret value for test * refined a test, and made pr changes --- lib/lambda/postAuth.test.ts | 99 +++++++++++++++++++++++++++++ lib/lambda/postAuth.ts | 54 +++++----------- lib/vitest.setup.ts | 2 + mocks/data/users/idmUsers.ts | 40 ++++++++++++ mocks/data/users/index.ts | 1 + mocks/data/users/stateSubmitters.ts | 34 ++++++++++ mocks/handlers/aws/cognito.ts | 4 +- mocks/handlers/aws/idm.ts | 30 +++++++++ mocks/handlers/aws/index.ts | 2 + 9 files changed, 228 insertions(+), 38 deletions(-) create mode 100644 lib/lambda/postAuth.test.ts create mode 100644 mocks/data/users/idmUsers.ts create mode 100644 mocks/handlers/aws/idm.ts diff --git a/lib/lambda/postAuth.test.ts b/lib/lambda/postAuth.test.ts new file mode 100644 index 0000000000..480a06ae74 --- /dev/null +++ b/lib/lambda/postAuth.test.ts @@ -0,0 +1,99 @@ +import { describe, it, expect, vi, afterAll } from "vitest"; +import { Context } from "aws-lambda"; +import { handler } from "./postAuth"; +import { + makoStateSubmitter, + setMockUsername, + superUser, + TEST_IDM_USERS, + USER_POOL_ID, +} from "mocks"; + +const callback = vi.fn(); +describe("process emails Handler", () => { + afterAll(() => { + setMockUsername(makoStateSubmitter); + }); + it("should return an error due to missing arn", async () => { + delete process.env.idmAuthzApiKeyArn; + + await expect(handler({ test: "test" }, {} as Context, callback)).rejects.toThrowError( + "ERROR: process.env.idmAuthzApiKeyArn is required", + ); + }); + it("should return an error due to a missing endpoint", async () => { + delete process.env.idmAuthzApiEndpoint; + await expect(handler({ test: "test" }, {} as Context, callback)).rejects.toThrowError( + "ERROR: process.env.idmAuthzApiEndpoint is required", + ); + }); + it("should return an error due to the arn being incorrect", async () => { + process.env.idmAuthzApiKeyArn = "bad-ARN"; // pragma: allowlist secret + await expect(handler({ test: "test" }, {} as Context, callback)).rejects.toThrowError( + "Failed to fetch secret bad-ARN: Secret bad-ARN has no SecretString field present in response", + ); + }); + + it("should return the request if it is missing an identity", async () => { + const consoleSpy = vi.spyOn(console, "log"); + const missingIdentity = await handler( + { + request: { + userAttributes: TEST_IDM_USERS.testStateIDMUserMissingIdentity, + }, + }, + {} as Context, + callback, + ); + expect(consoleSpy).toBeCalledWith("User is not managed externally. Nothing to do."); + expect(missingIdentity).toStrictEqual({ + request: { + userAttributes: TEST_IDM_USERS.testStateIDMUserMissingIdentity, + }, + }); + }); + it("should log an error since it cannot authorize the user", async () => { + const errorSpy = vi.spyOn(console, "error"); + const missingIdentity = await handler( + { + request: { + userAttributes: TEST_IDM_USERS.testStateIDMUser, + }, + }, + {} as Context, + callback, + ); + const error = new Error("Network response was not ok. Response was 401: Unauthorized"); + expect(errorSpy).toHaveBeenCalledWith("Error performing post auth:", error); + expect(errorSpy).toBeCalledTimes(1); + expect(missingIdentity).toStrictEqual({ + request: { + userAttributes: TEST_IDM_USERS.testStateIDMUser, + }, + }); + }); + it("should return the user and update the user in the service", async () => { + const consoleSpy = vi.spyOn(console, "log"); + const validUser = await handler( + { + request: { + userAttributes: TEST_IDM_USERS.testStateIDMUserGood, + }, + userName: superUser.Username, + userPoolId: USER_POOL_ID, + }, + {} as Context, + callback, + ); + expect(consoleSpy).toBeCalledWith( + `Attributes for user ${superUser.Username} updated successfully.`, + ); + expect(validUser).toStrictEqual({ + request: { + userAttributes: TEST_IDM_USERS.testStateIDMUserGood, + }, + userName: superUser.Username, + userPoolId: USER_POOL_ID, + }); + }); +}); diff --git a/lib/lambda/postAuth.ts b/lib/lambda/postAuth.ts index 433ac8e94c..f27c2bf882 100644 --- a/lib/lambda/postAuth.ts +++ b/lib/lambda/postAuth.ts @@ -8,17 +8,16 @@ import { import { getSecret } from "shared-utils"; // Initialize Cognito client -const client = new CognitoIdentityProviderClient({}); - +const client = new CognitoIdentityProviderClient({ + region: process.env.region || process.env.REGION_A || "us-east-1", +}); export const handler: Handler = async (event) => { - console.log(JSON.stringify(event, null, 2)); - // Check if idmInfoSecretArn is provided if (!process.env.idmAuthzApiKeyArn) { throw "ERROR: process.env.idmAuthzApiKeyArn is required"; } if (!process.env.idmAuthzApiEndpoint) { - throw "ERROR: process.env.idmAuthzApiKeyArn is required"; + throw "ERROR: process.env.idmAuthzApiEndpoint is required"; } const apiEndpoint: string = process.env.idmAuthzApiEndpoint; @@ -32,7 +31,6 @@ export const handler: Handler = async (event) => { const { request } = event; const { userAttributes } = request; - if (!userAttributes.identities) { console.log("User is not managed externally. Nothing to do."); } else { @@ -40,18 +38,14 @@ export const handler: Handler = async (event) => { try { const username = userAttributes["custom:username"]; // This is the four-letter IDM username - const response = await fetch( - `${apiEndpoint}/api/v1/authz/id/all?userId=${username}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - "x-api-key": apiKey, - }, + const response = await fetch(`${apiEndpoint}/api/v1/authz/id/all?userId=${username}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-api-key": apiKey, }, - ); + }); if (!response.ok) { - console.log(response); throw new Error( `Network response was not ok. Response was ${response.status}: ${response.statusText}`, ); @@ -108,22 +102,14 @@ async function updateUserAttributes(params: any): Promise { const user = await client.send(getUserCommand); // Check for existing "custom:cms-roles" - const cmsRolesAttribute = user.UserAttributes?.find( - (attr) => attr.Name === "custom:cms-roles", - ); + const cmsRolesAttribute = user.UserAttributes?.find((attr) => attr.Name === "custom:cms-roles"); const existingRoles = - cmsRolesAttribute && cmsRolesAttribute.Value - ? cmsRolesAttribute.Value.split(",") - : []; + cmsRolesAttribute && cmsRolesAttribute.Value ? cmsRolesAttribute.Value.split(",") : []; // Check for existing "custom:state" - const stateAttribute = user.UserAttributes?.find( - (attr) => attr.Name === "custom:state", - ); + const stateAttribute = user.UserAttributes?.find((attr) => attr.Name === "custom:state"); const existingStates = - stateAttribute && stateAttribute.Value - ? stateAttribute.Value.split(",") - : []; + stateAttribute && stateAttribute.Value ? stateAttribute.Value.split(",") : []; // Prepare for updating user attributes const attributeData: any = { @@ -146,8 +132,7 @@ async function updateUserAttributes(params: any): Promise { ), ) : new Set(["onemac-micro-super"]); // Ensure "onemac-micro-super" is always included - attributeData.UserAttributes[rolesIndex].Value = - Array.from(newRoles).join(","); + attributeData.UserAttributes[rolesIndex].Value = Array.from(newRoles).join(","); } else { // Add "custom:cms-roles" with "onemac-micro-super" attributeData.UserAttributes.push({ @@ -165,14 +150,9 @@ async function updateUserAttributes(params: any): Promise { if (stateIndex !== -1) { // Only merge if new states are not empty const newStates = attributeData.UserAttributes[stateIndex].Value - ? new Set( - attributeData.UserAttributes[stateIndex].Value.split(",").concat( - "ZZ", - ), - ) + ? new Set(attributeData.UserAttributes[stateIndex].Value.split(",").concat("ZZ")) : new Set(["ZZ"]); // Ensure "ZZ" is always included - attributeData.UserAttributes[stateIndex].Value = - Array.from(newStates).join(","); + attributeData.UserAttributes[stateIndex].Value = Array.from(newStates).join(","); } else { // Add "custom:state" with "ZZ" attributeData.UserAttributes.push({ diff --git a/lib/vitest.setup.ts b/lib/vitest.setup.ts index 573631b2b1..61012f372a 100644 --- a/lib/vitest.setup.ts +++ b/lib/vitest.setup.ts @@ -69,6 +69,8 @@ beforeEach(() => { process.env.DLQ_URL = "https://sqs.us-east-1.amazonaws.com/123/test"; process.env.configurationSetName = "SES"; process.env.brokerString = KAFKA_BROKERS; + process.env.idmAuthzApiKeyArn = "test-secret"; // pragma: allowlist secret + process.env.idmAuthzApiEndpoint = "https://dimAuthzEndpoint.com"; }); afterEach(() => { diff --git a/mocks/data/users/idmUsers.ts b/mocks/data/users/idmUsers.ts new file mode 100644 index 0000000000..e324958b12 --- /dev/null +++ b/mocks/data/users/idmUsers.ts @@ -0,0 +1,40 @@ +export const testStateIDMUserMissingIdentity = { + sub: "0000aaaa-0000-00aa-0a0a-aaaaaa000000", + "custom:cms-roles": "onemac-micro-statesubmitter", + "custom:state": "VA,OH,SC,CO,GA,MD", + email_verified: true, + given_name: "State", + family_name: "Person", + username: "abcd", + email: "stateperson@example.com", +}; + +export const testStateIDMUser = { + sub: "0000aaaa-0000-00aa-0a0a-aaaaaa000000", + "custom:cms-roles": "onemac-micro-statesubmitter", + "custom:state": "VA,OH,SC,CO,GA,MD", + email_verified: true, + given_name: "State", + family_name: "Person", + "custom:username": "fail", + email: "stateperson@example.com", + identities: + '[{"dateCreated":"1709308952587","userId":"abc123","providerName":"IDM","providerType":"OIDC","issuer":null,"primary":"true"}]', +}; +export const testStateIDMUserGood = { + sub: "0000aaaa-0000-00aa-0a0a-aaaaaa000000", + "custom:cms-roles": "onemac-micro-super", + "custom:state": "VA,OH,SC,CO,GA,MD", + email_verified: true, + given_name: "State", + family_name: "Person", + "custom:username": "abcd", + email: "stateperson@example.com", + identities: + '[{"dateCreated":"1709308952587","userId":"abc123","providerName":"IDM","providerType":"OIDC","issuer":null,"primary":"true"}]', +}; +export const TEST_IDM_USERS = { + testStateIDMUser, + testStateIDMUserGood, + testStateIDMUserMissingIdentity, +}; diff --git a/mocks/data/users/index.ts b/mocks/data/users/index.ts index b84c402249..953555aca0 100644 --- a/mocks/data/users/index.ts +++ b/mocks/data/users/index.ts @@ -47,3 +47,4 @@ export * from "./helpDeskUsers"; export * from "./mockStorage"; export * from "./readOnlyCMSUsers"; export * from "./stateSubmitters"; +export * from "./idmUsers"; diff --git a/mocks/data/users/stateSubmitters.ts b/mocks/data/users/stateSubmitters.ts index b2e5bb67db..ceb2470c42 100644 --- a/mocks/data/users/stateSubmitters.ts +++ b/mocks/data/users/stateSubmitters.ts @@ -34,6 +34,39 @@ export const makoStateSubmitter: TestUserData = { ], Username: "cd400c39-9e7c-4341-b62f-234e2ecb339d", }; +export const superUser: TestUserData = { + UserAttributes: [ + { + Name: "email", + Value: "mako.stateuser@gmail.com", + }, + { + Name: "email_verified", + Value: "true", + }, + { + Name: "given_name", + Value: "Stateuser", + }, + { + Name: "family_name", + Value: "Tester", + }, + { + Name: "custom:state", + Value: "ZZ", + }, + { + Name: "custom:cms-roles", + Value: "onemac-micro-super", + }, + { + Name: "sub", + Value: "cd400c39-9e7c-4341-b62f-234e2ecb339e", + }, + ], + Username: "cd400c39-9e7c-4341-b62f-234e2ecb339e", +}; export const stateSubmitter: TestUserData = { UserAttributes: [ @@ -275,6 +308,7 @@ export const testNewStateSubmitter: TestUserData = { export const stateSubmitters: TestUserData[] = [ makoStateSubmitter, + superUser, stateSubmitter, noDataStateSubmitter, coStateSubmitter, diff --git a/mocks/handlers/aws/cognito.ts b/mocks/handlers/aws/cognito.ts index 84a58d7ddd..969cafea99 100644 --- a/mocks/handlers/aws/cognito.ts +++ b/mocks/handlers/aws/cognito.ts @@ -286,7 +286,9 @@ export const identityProviderServiceHandler = http.post< ); return passthrough(); } - + if (target == "AWSCognitoIdentityProviderService.AdminUpdateUserAttributes") { + return new HttpResponse(null, { status: 200 }); + } if (target == "AWSCognitoIdentityProviderService.GetUser") { const { AccessToken } = (await request.json()) as IdpRequestSessionBody; const username = getUsernameFromAccessToken(AccessToken) || process.env.MOCK_USER_USERNAME; diff --git a/mocks/handlers/aws/idm.ts b/mocks/handlers/aws/idm.ts new file mode 100644 index 0000000000..2875fa9d8a --- /dev/null +++ b/mocks/handlers/aws/idm.ts @@ -0,0 +1,30 @@ +import { http, HttpResponse } from "msw"; + +const defaultIDMHandler = http.get( + "https://dimauthzendpoint.com/api/v1/authz/id/all", + async ({ request }) => { + const url = new URL(request.url); + const id = url.searchParams.get("userId"); + + if (id === "fail") { + return HttpResponse.json({ text: "Failed to retrieve user" }, { status: 401 }); + } else if (id === "abcd") { + return HttpResponse.json( + { + userProfileAppRoles: { + userRolesInfoList: [ + { + roleName: "onemac-micro-statesubmitter", + roleAttributes: [{ name: "State/Territory", value: "VA" }], + }, + ], + }, + }, + { status: 200 }, + ); + } + return HttpResponse.json({ text: "Failed to retrieve user" }, { status: 200 }); + }, +); + +export const idmHandlers = [defaultIDMHandler]; diff --git a/mocks/handlers/aws/index.ts b/mocks/handlers/aws/index.ts index 9b4f5cfb7c..f1faae9d5e 100644 --- a/mocks/handlers/aws/index.ts +++ b/mocks/handlers/aws/index.ts @@ -5,6 +5,7 @@ import { lambdaHandlers } from "./lambda"; import { secretsManagerHandlers } from "./secretsManager"; import { stepFunctionHandlers } from "./stepFunctions"; import { emailHandlers } from "./email"; +import { idmHandlers } from "./idm"; export const awsHandlers = [ ...cloudFormationHandlers, ...cognitoHandlers, @@ -13,6 +14,7 @@ export const awsHandlers = [ ...secretsManagerHandlers, ...stepFunctionHandlers, ...emailHandlers, + ...idmHandlers, ]; export { errorCloudFormationHandler } from "./cloudFormation"; From d647fc8e22cdab88c45b7b84b89bf45f60c8cb8a Mon Sep 17 00:00:00 2001 From: 13bfrancis <40218571+13bfrancis@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:35:11 -0500 Subject: [PATCH 11/17] feat(align-statuses): Align Statuses in Upgrade (MAKO) with Legacy (OneMAC) (#927) * add submitted status for initial medicaid submission * change intake needed language * change status for rai response to submitted * add placeholder for rai withdraw response * change status to say withdraw requested * adjust withdraw package rule * add a placeholder status check and apply to withdraw * set status to submitted for new submissions * Make chip toggle hide when status is a placeholder * add a temp log (revert me) * fix authority? * Revert "fix authority?" This reverts commit 18800d17d388eca895648f76d7310ac9c7848d5e. * fix check? * Change app-k status to submitted * remove un-used variable from import * remove intake needed * fix second clock to be cleared off * adjust sub doc to not show when pending rai * whoops * fix chip spa withdraw package bug * fix success banner language * change package activity section to reflect new status * change language for rai withdraw to have requested * don't set the final disposition date when requesting withdraw * changing to be parity with Legacy * Changed rai response alert to align with legacy * update post sub banner for rai response withdraw to be parity * add additional checks for pending concurrence & approval * adjust rules for pending concurrence and approval * Set second clock to false when withdrawing * Update * Revert "Update" This reverts commit e2f4b3394c0adbaa4a2309209c263a9d37f4bbd8. and removes a spa for good measure * Revert "Update" This reverts commit e2f4b3394c0adbaa4a2309209c263a9d37f4bbd8. * change language for chip confirmation * fix banner * Make casing consistant * Don't show substatus when in certain conditions * fix sub status display * fix broken unit tests * Update react-app/src/features/forms/post-submission/withdraw-package/index.tsx Co-authored-by: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> * Update react-app/src/features/forms/post-submission/withdraw-package/index.tsx Co-authored-by: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> * refactor to use functions and explain whats happening * Update react-app/src/features/forms/post-submission/withdraw-package/index.tsx Co-authored-by: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> --------- Co-authored-by: Benjamin Paige Co-authored-by: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> --- lib/lambda/sinkMainProcessors.test.ts | 42 +++++------ .../opensearch/main/transforms/app-k.ts | 4 +- .../main/transforms/capitated-amendment.ts | 4 +- .../main/transforms/capitated-initial.ts | 4 +- .../main/transforms/capitated-renewal.ts | 4 +- .../main/transforms/contracting-amendment.ts | 4 +- .../main/transforms/contracting-initial.ts | 4 +- .../main/transforms/contracting-renewal.ts | 4 +- .../main/transforms/new-chip-submission.ts | 4 +- .../transforms/new-medicaid-submission.ts | 4 +- .../main/transforms/respond-to-rai.ts | 5 +- .../opensearch/main/transforms/seatool.ts | 4 +- .../main/transforms/withdraw-package.ts | 8 +-- .../main/transforms/withdraw-rai-response.ts | 5 +- lib/packages/shared-types/statusHelper.ts | 9 +++ .../shared-utils/package-actions/rules.ts | 22 ++++-- lib/packages/shared-utils/package-check.ts | 6 ++ .../main/Filtering/Drawer/consts.ts | 2 +- .../features/dashboard/Lists/spas/consts.tsx | 70 ++++++------------- .../dashboard/Lists/waivers/consts.tsx | 65 +++++------------ .../post-submission/respond-to-rai/index.tsx | 15 ++++ .../withdraw-package/index.tsx | 19 ++--- .../post-submission/withdraw-rai/index.tsx | 8 +-- .../features/package/admin-changes/index.tsx | 4 +- .../package/package-activity/index.test.tsx | 2 +- .../package/package-activity/index.tsx | 10 +-- .../features/package/package-status/index.tsx | 38 ++++++---- 27 files changed, 184 insertions(+), 186 deletions(-) diff --git a/lib/lambda/sinkMainProcessors.test.ts b/lib/lambda/sinkMainProcessors.test.ts index 2f0dae2530..c4b05ab5a9 100644 --- a/lib/lambda/sinkMainProcessors.test.ts +++ b/lib/lambda/sinkMainProcessors.test.ts @@ -68,7 +68,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "app-k", appkBase, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { title: appkBase.title, proposedDate: appkBase.proposedEffectiveDate, @@ -80,7 +80,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "capitated-initial", capitatedInitial, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { proposedDate: capitatedInitial.proposedEffectiveDate, additionalInformation: capitatedInitial.additionalInformation, @@ -91,7 +91,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "capitated-amendment", capitatedAmendmentBase, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { proposedDate: capitatedAmendmentBase.proposedEffectiveDate, additionalInformation: capitatedAmendmentBase.additionalInformation, @@ -102,7 +102,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "capitated-renewal", capitatedRenewal, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { proposedDate: capitatedRenewal.proposedEffectiveDate, additionalInformation: capitatedRenewal.additionalInformation, @@ -113,7 +113,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "contracting-initial", contractingInitial, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { proposedDate: contractingInitial.proposedEffectiveDate, additionalInformation: contractingInitial.additionalInformation, @@ -124,7 +124,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "contracting-amendment", contractingAmendment, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { proposedDate: contractingAmendment.proposedEffectiveDate, additionalInformation: contractingAmendment.additionalInformation, @@ -135,7 +135,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "contracting-renewal", contractingRenewal, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { proposedDate: contractingRenewal.proposedEffectiveDate, additionalInformation: contractingRenewal.additionalInformation, @@ -146,7 +146,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "new-chip-submission", newChipSubmission, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { proposedDate: newChipSubmission.proposedEffectiveDate, additionalInformation: newChipSubmission.additionalInformation, @@ -157,7 +157,7 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { [ "new-medicaid-submission", newMedicaidSubmission, - SEATOOL_STATUS.PENDING, + SEATOOL_STATUS.SUBMITTED, { proposedDate: newMedicaidSubmission.proposedEffectiveDate, additionalInformation: newMedicaidSubmission.additionalInformation, @@ -226,9 +226,10 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { { raiReceivedDate: ISO_DATETIME, raiWithdrawEnabled: false, - seatoolStatus: SEATOOL_STATUS.PENDING_RAI, - cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.PENDING_RAI], - stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.PENDING_RAI], + seatoolStatus: SEATOOL_STATUS.SUBMITTED, + cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.SUBMITTED], + stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.SUBMITTED], + initialIntakeNeeded: true, locked: true, }, ], @@ -236,9 +237,10 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { "withdraw-rai", withdrawRai, { - seatoolStatus: SEATOOL_STATUS.PENDING_RAI, - cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.PENDING_RAI], - stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.PENDING_RAI], + seatoolStatus: SEATOOL_STATUS.RAI_RESPONSE_WITHDRAW_REQUESTED, + cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.RAI_RESPONSE_WITHDRAW_REQUESTED], + stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.RAI_RESPONSE_WITHDRAW_REQUESTED], + secondClock: false, raiWithdrawEnabled: false, locked: true, }, @@ -254,12 +256,12 @@ describe("insertOneMacRecordsFromKafkaIntoMako", () => { "withdraw-package", withdrawPackage, { - seatoolStatus: SEATOOL_STATUS.WITHDRAWN, - cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.WITHDRAWN], - stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.WITHDRAWN], + seatoolStatus: SEATOOL_STATUS.WITHDRAW_REQUESTED, + cmsStatus: statusToDisplayToCmsUser[SEATOOL_STATUS.WITHDRAW_REQUESTED], + stateStatus: statusToDisplayToStateUser[SEATOOL_STATUS.WITHDRAW_REQUESTED], locked: true, - finalDispositionDate: ISO_DATETIME, - initialIntakeNeeded: false, + secondClock: false, + initialIntakeNeeded: true, raiWithdrawEnabled: false, }, ], diff --git a/lib/packages/shared-types/opensearch/main/transforms/app-k.ts b/lib/packages/shared-types/opensearch/main/transforms/app-k.ts index b5863849f7..05df9ed197 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/app-k.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/app-k.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["app-k"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -18,7 +18,7 @@ export const transform = () => { makoChangedDate: timestampDate.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/capitated-amendment.ts b/lib/packages/shared-types/opensearch/main/transforms/capitated-amendment.ts index 8b42a5adbd..d9e1598ec1 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/capitated-amendment.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/capitated-amendment.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["capitated-amendment"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -17,7 +17,7 @@ export const transform = () => { makoChangedDate: timestampDate.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/capitated-initial.ts b/lib/packages/shared-types/opensearch/main/transforms/capitated-initial.ts index 38b66e7737..82b873f290 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/capitated-initial.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/capitated-initial.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["capitated-initial"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -17,7 +17,7 @@ export const transform = () => { makoChangedDate: timestampDate.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/capitated-renewal.ts b/lib/packages/shared-types/opensearch/main/transforms/capitated-renewal.ts index 4a9b4d7fdb..9144bd2dea 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/capitated-renewal.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/capitated-renewal.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["capitated-renewal"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -17,7 +17,7 @@ export const transform = () => { makoChangedDate: timestampDate.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/contracting-amendment.ts b/lib/packages/shared-types/opensearch/main/transforms/contracting-amendment.ts index 577f91da6a..e0dd1bdc9f 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/contracting-amendment.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/contracting-amendment.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["contracting-amendment"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -17,7 +17,7 @@ export const transform = () => { makoChangedDate: timestampDate.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/contracting-initial.ts b/lib/packages/shared-types/opensearch/main/transforms/contracting-initial.ts index 6448c395dd..16e5060491 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/contracting-initial.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/contracting-initial.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["contracting-initial"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -17,7 +17,7 @@ export const transform = () => { makoChangedDate: timestampDate.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/contracting-renewal.ts b/lib/packages/shared-types/opensearch/main/transforms/contracting-renewal.ts index 05fb371dcc..69ec2dffa3 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/contracting-renewal.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/contracting-renewal.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["contracting-renewal"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -17,7 +17,7 @@ export const transform = () => { makoChangedDate: timestampDate?.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/new-chip-submission.ts b/lib/packages/shared-types/opensearch/main/transforms/new-chip-submission.ts index 0c3255b92d..677e0fcc70 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/new-chip-submission.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/new-chip-submission.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["new-chip-submission"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -17,7 +17,7 @@ export const transform = () => { makoChangedDate: timestampDate.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/new-medicaid-submission.ts b/lib/packages/shared-types/opensearch/main/transforms/new-medicaid-submission.ts index d75fcd61d1..c824485967 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/new-medicaid-submission.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/new-medicaid-submission.ts @@ -3,7 +3,7 @@ import { seaToolFriendlyTimestamp } from "../../../../shared-utils/seatool-date- export const transform = () => { return events["new-medicaid-submission"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); const timestampDate = new Date(data.timestamp); const todayEpoch = seaToolFriendlyTimestamp(timestampDate); @@ -17,7 +17,7 @@ export const transform = () => { makoChangedDate: timestampDate.toISOString(), origin: "OneMAC", raiWithdrawEnabled: false, // Set to false for new submissions - seatoolStatus: SEATOOL_STATUS.PENDING, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, state: data.id?.split("-")?.[0], stateStatus, statusDate: new Date(todayEpoch).toISOString(), diff --git a/lib/packages/shared-types/opensearch/main/transforms/respond-to-rai.ts b/lib/packages/shared-types/opensearch/main/transforms/respond-to-rai.ts index 674fb5aaa8..b34a2db759 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/respond-to-rai.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/respond-to-rai.ts @@ -1,7 +1,7 @@ import { events, getStatus, SEATOOL_STATUS } from "shared-types"; export const transform = () => { return events["respond-to-rai"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING_RAI); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.SUBMITTED); return { id: data.id, raiWithdrawEnabled: false, @@ -9,7 +9,8 @@ export const transform = () => { cmsStatus, stateStatus, raiReceivedDate: data.timestamp ? new Date(data.timestamp).toISOString() : null, - seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + seatoolStatus: SEATOOL_STATUS.SUBMITTED, + initialIntakeNeeded: true, locked: true, }; }); diff --git a/lib/packages/shared-types/opensearch/main/transforms/seatool.ts b/lib/packages/shared-types/opensearch/main/transforms/seatool.ts index b6732da5d2..6056309305 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/seatool.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/seatool.ts @@ -8,7 +8,7 @@ import { seatoolSchema, } from "../../.."; -import { Authority, SEATOOL_AUTHORITIES } from "shared-types"; +import { SEATOOL_AUTHORITIES } from "shared-types"; function getLeadAnalyst(eventData: SeaTool) { let leadAnalystOfficerId: null | number = null; @@ -97,7 +97,7 @@ const isInSecondClock = ( authority: any, ) => { if ( - authority != Authority.CHIP_SPA && // if it's not a chip + authority !== "CHIP SPA" && // if it's not a chip [ SEATOOL_STATUS.PENDING, SEATOOL_STATUS.PENDING_CONCURRENCE, diff --git a/lib/packages/shared-types/opensearch/main/transforms/withdraw-package.ts b/lib/packages/shared-types/opensearch/main/transforms/withdraw-package.ts index 6d6ec1d0cf..f9951182cd 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/withdraw-package.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/withdraw-package.ts @@ -2,16 +2,16 @@ import { events, getStatus, SEATOOL_STATUS } from "shared-types"; export const transform = () => { return events["withdraw-package"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.WITHDRAWN); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.WITHDRAW_REQUESTED); return { id: data.id, raiWithdrawEnabled: false, makoChangedDate: data.timestamp ? new Date(data.timestamp).toISOString() : null, cmsStatus, stateStatus, - finalDispositionDate: data.timestamp ? new Date(data.timestamp).toISOString() : null, - seatoolStatus: SEATOOL_STATUS.WITHDRAWN, - initialIntakeNeeded: false, + secondClock: false, + seatoolStatus: SEATOOL_STATUS.WITHDRAW_REQUESTED, + initialIntakeNeeded: true, locked: true, }; }); diff --git a/lib/packages/shared-types/opensearch/main/transforms/withdraw-rai-response.ts b/lib/packages/shared-types/opensearch/main/transforms/withdraw-rai-response.ts index 938fd5ebd2..380931ba74 100644 --- a/lib/packages/shared-types/opensearch/main/transforms/withdraw-rai-response.ts +++ b/lib/packages/shared-types/opensearch/main/transforms/withdraw-rai-response.ts @@ -2,14 +2,15 @@ import { events, getStatus, SEATOOL_STATUS } from "shared-types"; export const transform = () => { return events["withdraw-rai"].schema.transform((data) => { - const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.PENDING_RAI); + const { stateStatus, cmsStatus } = getStatus(SEATOOL_STATUS.RAI_RESPONSE_WITHDRAW_REQUESTED); return { id: data.id, raiWithdrawEnabled: false, makoChangedDate: data.timestamp ? new Date(data.timestamp).toISOString() : null, cmsStatus, stateStatus, - seatoolStatus: SEATOOL_STATUS.PENDING_RAI, + seatoolStatus: SEATOOL_STATUS.RAI_RESPONSE_WITHDRAW_REQUESTED, + secondClock: false, locked: true, }; }); diff --git a/lib/packages/shared-types/statusHelper.ts b/lib/packages/shared-types/statusHelper.ts index e79de3bf4f..609ad3b6cb 100644 --- a/lib/packages/shared-types/statusHelper.ts +++ b/lib/packages/shared-types/statusHelper.ts @@ -10,6 +10,9 @@ export const SEATOOL_STATUS = { PENDING_APPROVAL: "Pending-Approval", UNKNOWN: "Unknown", PENDING_OFF_THE_CLOCK: "Pending-Off the Clock", + SUBMITTED: "Submitted", + RAI_RESPONSE_WITHDRAW_REQUESTED: "Formal RAI Response - Withdrawal Requested", + WITHDRAW_REQUESTED: "Withdrawal Requested", }; export const statusToDisplayToStateUser = { @@ -23,6 +26,9 @@ export const statusToDisplayToStateUser = { [SEATOOL_STATUS.UNSUBMITTED]: "Unsubmitted", [SEATOOL_STATUS.PENDING_APPROVAL]: "Under Review", [SEATOOL_STATUS.PENDING_OFF_THE_CLOCK]: "Pending - Off the Clock", + [SEATOOL_STATUS.SUBMITTED]: "Submitted", + [SEATOOL_STATUS.RAI_RESPONSE_WITHDRAW_REQUESTED]: "Formal RAI Response - Withdrawal Requested", + [SEATOOL_STATUS.WITHDRAW_REQUESTED]: "Withdrawal Requested", }; export const statusToDisplayToCmsUser = { @@ -36,6 +42,9 @@ export const statusToDisplayToCmsUser = { [SEATOOL_STATUS.UNSUBMITTED]: "Unsubmitted", [SEATOOL_STATUS.PENDING_APPROVAL]: "Pending - Approval", [SEATOOL_STATUS.PENDING_OFF_THE_CLOCK]: "Pending - Off the Clock", + [SEATOOL_STATUS.SUBMITTED]: "Submitted - Intake Needed", + [SEATOOL_STATUS.RAI_RESPONSE_WITHDRAW_REQUESTED]: "Formal RAI Response - Withdrawal Requested", + [SEATOOL_STATUS.WITHDRAW_REQUESTED]: "Submitted - Intake Needed", }; export const finalDispositionStatuses = [ diff --git a/lib/packages/shared-utils/package-actions/rules.ts b/lib/packages/shared-utils/package-actions/rules.ts index efb60e1373..d2df782cf9 100644 --- a/lib/packages/shared-utils/package-actions/rules.ts +++ b/lib/packages/shared-utils/package-actions/rules.ts @@ -47,7 +47,9 @@ const arEnableWithdrawRaiResponse: ActionRule = { checker.hasRaiResponse && !checker.hasEnabledRaiWithdraw && isCmsWriteUser(user) && - !checker.hasStatus(finalDispositionStatuses) + !checker.hasStatus(finalDispositionStatuses) && + !checker.hasStatus([SEATOOL_STATUS.PENDING_CONCURRENCE, SEATOOL_STATUS.PENDING_APPROVAL]) && + !checker.isPlaceholderStatus ); } @@ -58,7 +60,8 @@ const arEnableWithdrawRaiResponse: ActionRule = { !checker.hasEnabledRaiWithdraw && checker.isInSecondClock && isCmsWriteUser(user) && - !checker.hasStatus(finalDispositionStatuses) + !checker.hasStatus(finalDispositionStatuses) && + !checker.hasStatus([SEATOOL_STATUS.PENDING_CONCURRENCE, SEATOOL_STATUS.PENDING_APPROVAL]) ); }, }; @@ -71,7 +74,8 @@ const arDisableWithdrawRaiResponse: ActionRule = { checker.hasRaiResponse && checker.hasEnabledRaiWithdraw && isCmsWriteUser(user) && - !checker.hasStatus(finalDispositionStatuses), + !checker.hasStatus(finalDispositionStatuses) && + !checker.hasStatus([SEATOOL_STATUS.PENDING_CONCURRENCE, SEATOOL_STATUS.PENDING_APPROVAL]), }; const arWithdrawRaiResponse: ActionRule = { @@ -82,6 +86,7 @@ const arWithdrawRaiResponse: ActionRule = { checker.hasRaiResponse && // safety; prevent bad status from causing overwrite !checker.hasRaiWithdrawal && + !checker.hasStatus([SEATOOL_STATUS.PENDING_CONCURRENCE, SEATOOL_STATUS.PENDING_APPROVAL]) && checker.hasEnabledRaiWithdraw && isStateUser(user) && !checker.isLocked, @@ -90,7 +95,10 @@ const arWithdrawRaiResponse: ActionRule = { const arWithdrawPackage: ActionRule = { action: Action.WITHDRAW_PACKAGE, check: (checker, user) => - !checker.isTempExtension && !checker.hasStatus(finalDispositionStatuses) && isStateUser(user), + !checker.isTempExtension && + !checker.hasStatus(finalDispositionStatuses) && + isStateUser(user) && + !checker.isPlaceholderStatus, }; const arUpdateId: ActionRule = { @@ -119,7 +127,11 @@ const arUploadSubsequentDocuments: ActionRule = { return false; } - if (checker.hasStatus([SEATOOL_STATUS.PENDING, SEATOOL_STATUS.PENDING_RAI])) { + if (checker.hasStatus([SEATOOL_STATUS.PENDING_RAI])) { + return false; + } + + if (checker.hasStatus([SEATOOL_STATUS.PENDING])) { if (checker.hasRequestedRai) { return false; } diff --git a/lib/packages/shared-utils/package-check.ts b/lib/packages/shared-utils/package-check.ts index c03b4afb33..d28368304b 100644 --- a/lib/packages/shared-utils/package-check.ts +++ b/lib/packages/shared-utils/package-check.ts @@ -66,6 +66,12 @@ export const PackageCheck = ({ * object attributes! **/ hasStatus: (authorizedStatuses: string | string[]) => checkStatus(seatoolStatus, authorizedStatuses), + /** Is Placeholder Status is a check that is used to determine if the record is in one of the in between status while waiting on seatool intake */ + isPlaceholderStatus: [ + SEATOOL_STATUS.RAI_RESPONSE_WITHDRAW_REQUESTED, + SEATOOL_STATUS.SUBMITTED, + SEATOOL_STATUS.WITHDRAW_REQUESTED, + ].includes(seatoolStatus), /** If submission date exists */ hasSubmissionDate: submissionDate !== undefined, isLocked: locked, diff --git a/react-app/src/components/Opensearch/main/Filtering/Drawer/consts.ts b/react-app/src/components/Opensearch/main/Filtering/Drawer/consts.ts index a8b4ae4acb..b4b60e9c3a 100644 --- a/react-app/src/components/Opensearch/main/Filtering/Drawer/consts.ts +++ b/react-app/src/components/Opensearch/main/Filtering/Drawer/consts.ts @@ -52,7 +52,7 @@ export const CHECK_STATESTATUS: DrawerFilterableGroup = { }; export const BOOL_INITIALINTAKENEEDED: DrawerFilterableGroup = { - label: "Initial Intake Needed", + label: "Intake Needed", field: "initialIntakeNeeded", component: "boolean", prefix: "must", diff --git a/react-app/src/features/dashboard/Lists/spas/consts.tsx b/react-app/src/features/dashboard/Lists/spas/consts.tsx index 5e6f2fa7b1..c31e6b726e 100644 --- a/react-app/src/features/dashboard/Lists/spas/consts.tsx +++ b/react-app/src/features/dashboard/Lists/spas/consts.tsx @@ -1,12 +1,8 @@ import { removeUnderscoresAndCapitalize } from "@/utils"; import { OsTableColumn } from "@/components"; -import { CMS_READ_ONLY_ROLES, UserRoles } from "shared-types"; +import { CMS_READ_ONLY_ROLES, SEATOOL_STATUS, UserRoles } from "shared-types"; import { useGetUser } from "@/api"; -import { - CellDetailsLink, - renderCellActions, - renderCellDate, -} from "../renderCells"; +import { CellDetailsLink, renderCellActions, renderCellDate } from "../renderCells"; import { BLANK_VALUE } from "@/consts"; import { formatSeatoolDate } from "shared-utils"; @@ -17,9 +13,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { return [ // hide actions column for: readonly,help desk - ...(!CMS_READ_ONLY_ROLES.some((UR) => - props.user?.["custom:cms-roles"].includes(UR), - ) + ...(!CMS_READ_ONLY_ROLES.some((UR) => props.user?.["custom:cms-roles"].includes(UR)) ? [ { locked: true, @@ -35,9 +29,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { label: "SPA ID", locked: true, transform: (data) => data.id, - cell: ({ id, authority }) => ( - - ), + cell: ({ id, authority }) => , }, { field: "state.keyword", @@ -50,9 +42,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { label: "Authority", transform: (data) => data.authority ?? BLANK_VALUE, cell: (data) => - data?.authority - ? removeUnderscoresAndCapitalize(data.authority) - : BLANK_VALUE, + data?.authority ? removeUnderscoresAndCapitalize(data.authority) : BLANK_VALUE, }, { field: props?.isCms ? "cmsStatus.keyword" : "stateStatus.keyword", @@ -66,17 +56,14 @@ export const useSpaTableColumns = (): OsTableColumn[] => { return data.cmsStatus; })(); - const subStatusRAI = data.raiWithdrawEnabled - ? " (Withdraw Formal RAI Response - Enabled)" - : ""; + const subStatusRAI = + data.raiWithdrawEnabled && + data.seatoolStatus !== SEATOOL_STATUS.PENDING_APPROVAL && + data.seatoolStatus !== SEATOOL_STATUS.PENDING_CONCURRENCE + ? " (Withdraw Formal RAI Response - Enabled)" + : ""; - const subStatusInitialIntake = (() => { - if (!props?.isCms) return ""; - if (!data.initialIntakeNeeded) return ""; - return " (Initial Intake Needed)"; - })(); - - return `${status}${subStatusRAI}${subStatusInitialIntake}`; + return `${status}${subStatusRAI}`; }, cell: (data) => { const status = (() => { @@ -89,14 +76,11 @@ export const useSpaTableColumns = (): OsTableColumn[] => { return ( <>

{status}

- {data.raiWithdrawEnabled && ( -

- · Withdraw Formal RAI Response - Enabled -

- )} - {props?.isCms && data.initialIntakeNeeded && ( -

· Initial Intake Needed

- )} + {data.raiWithdrawEnabled && + data.seatoolStatus !== SEATOOL_STATUS.PENDING_APPROVAL && + data.seatoolStatus !== SEATOOL_STATUS.PENDING_CONCURRENCE && ( +

· Withdraw Formal RAI Response - Enabled

+ )} ); }, @@ -105,9 +89,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { field: "submissionDate", label: "Initial Submission", transform: (data) => - data?.submissionDate - ? formatSeatoolDate(data.submissionDate) - : BLANK_VALUE, + data?.submissionDate ? formatSeatoolDate(data.submissionDate) : BLANK_VALUE, cell: renderCellDate("submissionDate"), }, { @@ -115,9 +97,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { label: "Final Disposition", hidden: true, transform: (data) => - data?.finalDispositionDate - ? formatSeatoolDate(data.finalDispositionDate) - : BLANK_VALUE, + data?.finalDispositionDate ? formatSeatoolDate(data.finalDispositionDate) : BLANK_VALUE, cell: renderCellDate("finalDispositionDate"), }, { @@ -131,9 +111,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { field: "makoChangedDate", label: "Latest Package Activity", transform: (data) => - data.makoChangedDate - ? formatSeatoolDate(data.makoChangedDate) - : BLANK_VALUE, + data.makoChangedDate ? formatSeatoolDate(data.makoChangedDate) : BLANK_VALUE, cell: renderCellDate("makoChangedDate"), }, { @@ -141,9 +119,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { label: "Formal RAI Requested", hidden: true, transform: (data) => { - return data.raiRequestedDate - ? formatSeatoolDate(data.raiRequestedDate) - : BLANK_VALUE; + return data.raiRequestedDate ? formatSeatoolDate(data.raiRequestedDate) : BLANK_VALUE; }, cell: renderCellDate("raiRequestedDate"), }, @@ -151,9 +127,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { field: "raiReceivedDate", label: "Formal RAI Response", transform: (data) => { - return data.raiReceivedDate - ? formatSeatoolDate(data.raiReceivedDate) - : BLANK_VALUE; + return data.raiReceivedDate ? formatSeatoolDate(data.raiReceivedDate) : BLANK_VALUE; }, cell: renderCellDate("raiReceivedDate"), }, diff --git a/react-app/src/features/dashboard/Lists/waivers/consts.tsx b/react-app/src/features/dashboard/Lists/waivers/consts.tsx index bca7d3042c..02ebdc1437 100644 --- a/react-app/src/features/dashboard/Lists/waivers/consts.tsx +++ b/react-app/src/features/dashboard/Lists/waivers/consts.tsx @@ -1,13 +1,9 @@ import { removeUnderscoresAndCapitalize, LABELS } from "@/utils"; import { OsTableColumn } from "@/components"; import { BLANK_VALUE } from "@/consts"; -import { CMS_READ_ONLY_ROLES, UserRoles } from "shared-types"; +import { CMS_READ_ONLY_ROLES, SEATOOL_STATUS, UserRoles } from "shared-types"; import { useGetUser } from "@/api"; -import { - CellDetailsLink, - renderCellActions, - renderCellDate, -} from "../renderCells"; +import { CellDetailsLink, renderCellActions, renderCellDate } from "../renderCells"; import { formatSeatoolDate } from "shared-utils"; export const useWaiverTableColumns = (): OsTableColumn[] => { @@ -17,9 +13,7 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { return [ // hide actions column for: readonly,help desk - ...(!CMS_READ_ONLY_ROLES.some((UR) => - props.user?.["custom:cms-roles"].includes(UR), - ) + ...(!CMS_READ_ONLY_ROLES.some((UR) => props.user?.["custom:cms-roles"].includes(UR)) ? [ { locked: true, @@ -35,9 +29,7 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { label: "Waiver Number", locked: true, transform: (data) => data.id, - cell: ({ id, authority }) => ( - - ), + cell: ({ id, authority }) => , }, { field: "state.keyword", @@ -50,18 +42,14 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { label: "Authority", transform: (data) => data.authority ?? BLANK_VALUE, cell: (data) => - data?.authority - ? removeUnderscoresAndCapitalize(data.authority) - : BLANK_VALUE, + data?.authority ? removeUnderscoresAndCapitalize(data.authority) : BLANK_VALUE, }, { field: "actionType.keyword", label: "Action Type", transform: (data) => { if (data.actionType === undefined) return BLANK_VALUE; - return ( - LABELS[data.actionType as keyof typeof LABELS] || data.actionType - ); + return LABELS[data.actionType as keyof typeof LABELS] || data.actionType; }, cell: (data) => data.actionType @@ -84,13 +72,7 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { ? " (Withdraw Formal RAI Response - Enabled)" : ""; - const subStatusInitialIntake = (() => { - if (!props?.isCms) return ""; - if (!data.initialIntakeNeeded) return ""; - return " (Initial Intake Needed)"; - })(); - - return `${status}${subStatusRAI}${subStatusInitialIntake}`; + return `${status}${subStatusRAI}`; }, cell: (data) => { const status = (() => { @@ -103,14 +85,11 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { return ( <>

{status}

- {data.raiWithdrawEnabled && ( -

- · Withdraw Formal RAI Response - Enabled -

- )} - {props?.isCms && data.initialIntakeNeeded && ( -

· Initial Intake Needed

- )} + {data.raiWithdrawEnabled && + data.seatoolStatus !== SEATOOL_STATUS.PENDING_APPROVAL && + data.seatoolStatus !== SEATOOL_STATUS.PENDING_CONCURRENCE && ( +

· Withdraw Formal RAI Response - Enabled

+ )} ); }, @@ -119,9 +98,7 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { field: "submissionDate", label: "Initial Submission", transform: (data) => - data?.submissionDate - ? formatSeatoolDate(data.submissionDate) - : BLANK_VALUE, + data?.submissionDate ? formatSeatoolDate(data.submissionDate) : BLANK_VALUE, cell: renderCellDate("submissionDate"), }, { @@ -129,9 +106,7 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { label: "Final Disposition", hidden: true, transform: (data) => - data?.finalDispositionDate - ? formatSeatoolDate(data.finalDispositionDate) - : BLANK_VALUE, + data?.finalDispositionDate ? formatSeatoolDate(data.finalDispositionDate) : BLANK_VALUE, cell: renderCellDate("finalDispositionDate"), }, { @@ -147,9 +122,7 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { field: "makoChangedDate", label: "Latest Package Activity", transform: (data) => - data.makoChangedDate - ? formatSeatoolDate(data.makoChangedDate) - : BLANK_VALUE, + data.makoChangedDate ? formatSeatoolDate(data.makoChangedDate) : BLANK_VALUE, cell: renderCellDate("makoChangedDate"), }, { @@ -157,9 +130,7 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { label: "Formal RAI Requested", hidden: true, transform: (data) => { - return data.raiRequestedDate - ? formatSeatoolDate(data.raiRequestedDate) - : BLANK_VALUE; + return data.raiRequestedDate ? formatSeatoolDate(data.raiRequestedDate) : BLANK_VALUE; }, cell: renderCellDate("raiRequestedDate"), }, @@ -167,9 +138,7 @@ export const useWaiverTableColumns = (): OsTableColumn[] => { field: "raiReceivedDate", label: "Formal RAI Response", transform: (data) => { - return data.raiReceivedDate - ? formatSeatoolDate(data.raiReceivedDate) - : BLANK_VALUE; + return data.raiReceivedDate ? formatSeatoolDate(data.raiReceivedDate) : BLANK_VALUE; }, cell: renderCellDate("raiReceivedDate"), }, diff --git a/react-app/src/features/forms/post-submission/respond-to-rai/index.tsx b/react-app/src/features/forms/post-submission/respond-to-rai/index.tsx index a1f6bd9798..60170d9ec0 100644 --- a/react-app/src/features/forms/post-submission/respond-to-rai/index.tsx +++ b/react-app/src/features/forms/post-submission/respond-to-rai/index.tsx @@ -13,6 +13,11 @@ export const RespondToRaiMedicaid = () => { attachments={{ faqLink: "/faq/medicaid-spa-rai-attachments", }} + promptPreSubmission={{ + acceptButtonText: "Yes, Submit", + header: "Do you want to submit your official formal RAI response?", + body: "By clicking Yes, Submit, you are submitting your official formal RAI Response to start the 90 day clock review process.", + }} documentPollerArgs={{ property: "id", documentChecker: (check) => check.recordExists, @@ -43,6 +48,11 @@ export const RespondToRaiWaiver = () => { attachments={{ faqLink: "/faq/waiverb-rai-attachments", }} + promptPreSubmission={{ + acceptButtonText: "Yes, Submit", + header: "Do you want to submit your official formal RAI response?", + body: "By clicking Yes, Submit, you are submitting your official formal RAI Response to start the 90 day clock review process.", + }} documentPollerArgs={{ property: "id", documentChecker: (check) => check.recordExists, @@ -71,6 +81,11 @@ export const RespondToRaiChip = () => { attachments={{ faqLink: "/faq/chip-spa-rai-attachments", }} + promptPreSubmission={{ + acceptButtonText: "Yes, Submit", + header: "Do you want to submit your official formal RAI response?", + body: "By clicking Yes, Submit, you are submitting your official formal RAI Response to restart the SPA review process and a new 90th day will be identified.", + }} documentPollerArgs={{ property: "id", documentChecker: (check) => check.recordExists, diff --git a/react-app/src/features/forms/post-submission/withdraw-package/index.tsx b/react-app/src/features/forms/post-submission/withdraw-package/index.tsx index e0585abcc7..6fef0a3528 100644 --- a/react-app/src/features/forms/post-submission/withdraw-package/index.tsx +++ b/react-app/src/features/forms/post-submission/withdraw-package/index.tsx @@ -2,7 +2,6 @@ import { useGetItem } from "@/api"; import { ActionForm, LoadingSpinner, PackageSection } from "@/components"; import { formSchemas } from "@/formSchemas"; import { useParams } from "react-router"; -import { SEATOOL_STATUS } from "shared-types"; export const WithdrawPackageActionWaiver = () => { const { authority, id } = useParams(); @@ -44,12 +43,14 @@ export const WithdrawPackageActionWaiver = () => { documentChecker: (check) => check.recordExists, }} bannerPostSubmission={{ - header: "Package withdrawn", - body: `The package ${id} has been withdrawn.`, + header: "Withdraw package request has been submitted", + body: "If CMS needs any additional information, they will follow up by email.", variant: "success", }} breadcrumbText="Withdraw Package" - formDescription={`Complete this form to withdraw ${authority === "1915(c)" ? "this 1915(c) Appendix K" : "a"} package. Once complete, you will not be able to resubmit this package. CMS will be notified and will use this content to review your request. If CMS needs any additional information, they will follow up by email.`} + formDescription={`Complete this form to withdraw ${ + authority === "1915(c)" ? "this 1915(c) Appendix K" : "a" + } package. Once complete, you will not be able to resubmit this package. CMS will be notified and will use this content to review your request. If CMS needs any additional information, they will follow up by email.`} preSubmissionMessage="Once complete, you will not be able to resubmit this package. CMS will be notified and will use this content to review your request. If CMS needs any additional information, they will follow up by email." additionalInformation={{ required: false, @@ -96,8 +97,8 @@ export const WithdrawPackageAction = () => { documentChecker: (check) => check.recordExists, }} bannerPostSubmission={{ - header: "Package withdrawn", - body: `The package ${id} has been withdrawn.`, + header: "Withdraw package request has been submitted", + body: "If CMS needs any additional information, they will follow up by email.", variant: "success", }} breadcrumbText="Withdraw Package" @@ -135,13 +136,13 @@ export const WithdrawPackageActionChip = () => { "Official withdrawal letters are required and must be on state letterhead signed by the State Medicaid Director or CHIP Director.", }} bannerPostSubmission={{ - header: "Package withdrawn", - body: `The package ${id} has been withdrawn.`, + header: "Withdraw package request has been submitted", + body: "If CMS needs any additional information, they will follow up by email.", variant: "success", }} documentPollerArgs={{ property: "id", - documentChecker: (check) => check.hasStatus(SEATOOL_STATUS.WITHDRAWN), + documentChecker: (check) => check.recordExists, }} breadcrumbText="Withdraw Package" additionalInformation={{ diff --git a/react-app/src/features/forms/post-submission/withdraw-rai/index.tsx b/react-app/src/features/forms/post-submission/withdraw-rai/index.tsx index eea6bf0bab..8d66a83d9f 100644 --- a/react-app/src/features/forms/post-submission/withdraw-rai/index.tsx +++ b/react-app/src/features/forms/post-submission/withdraw-rai/index.tsx @@ -34,8 +34,8 @@ export const WithdrawRaiForm = () => { you and CMS will receive an email confirmation." preSubmissionMessage="Once complete, you and CMS will receive an email confirmation." bannerPostSubmission={{ - header: "RAI response withdrawn", - body: `The RAI response for ${id} has been withdrawn. CMS may follow up if additional information is needed.`, + header: "Withdraw Formal RAI Response request has been submitted.", + body: "Your Formal RAI Response has been withdrawn successfully. If CMS needs any additional information, they will follow up by email.", variant: "success", }} additionalInformation={{ @@ -44,8 +44,8 @@ export const WithdrawRaiForm = () => { label: "Explain your need for withdrawal.", }} promptPreSubmission={{ - header: "Withdraw RAI response?", - body: `The RAI response for ${id} will be withdrawn, and CMS will be notified.`, + header: "Withdraw Formal RAI response?", + body: `You are about to withdraw the Formal RAI Response for ${id}. CMS will be notified.`, acceptButtonText: "Yes, withdraw response", cancelButtonText: "Cancel", }} diff --git a/react-app/src/features/package/admin-changes/index.tsx b/react-app/src/features/package/admin-changes/index.tsx index 403827f0ca..798ae11832 100644 --- a/react-app/src/features/package/admin-changes/index.tsx +++ b/react-app/src/features/package/admin-changes/index.tsx @@ -55,9 +55,9 @@ export const AdminChange: FC = (props) => { switch (props.event) { case "toggle-withdraw-rai": { if (props.raiWithdrawEnabled) { - return ["Enable formal RAI response withdraw", AC_WithdrawEnabled]; + return ["Enable Formal RAI Response Withdraw", AC_WithdrawEnabled]; } - return ["Disable formal RAI response withdraw", AC_WithdrawDisabled]; + return ["Disable Formal RAI Response Withdraw", AC_WithdrawDisabled]; } case "legacy-admin-change": return [props.changeType || "Manual Update", AC_LegacyAdminChange]; diff --git a/react-app/src/features/package/package-activity/index.test.tsx b/react-app/src/features/package/package-activity/index.test.tsx index d30fe430f2..9474bf2bd9 100644 --- a/react-app/src/features/package/package-activity/index.test.tsx +++ b/react-app/src/features/package/package-activity/index.test.tsx @@ -37,7 +37,7 @@ describe("Package Activity", () => { await renderFormWithPackageSectionAsync(, WITHDRAWN_CHANGELOG_ITEM_ID); expect(screen.getByText("Package Activity (7)")); - expect(screen.getByText("Initial package submitted")); + expect(screen.getByText("Initial Package Submitted")); expect(screen.getByText("Download all documents")); expect(screen.getByText("Contract Amendment")); }); diff --git a/react-app/src/features/package/package-activity/index.tsx b/react-app/src/features/package/package-activity/index.tsx index 29479345cf..9621b7e608 100644 --- a/react-app/src/features/package/package-activity/index.tsx +++ b/react-app/src/features/package/package-activity/index.tsx @@ -106,18 +106,18 @@ const PackageActivity = ({ packageActivity }: PackageActivityProps) => { case "new-medicaid-submission": case "temporary-extension": case "app-k": - return "Initial package submitted"; + return "Initial Package Submitted"; case "withdraw-package": - return "Package withdrawn"; + return "Package - Withdrawal Requested"; case "withdraw-rai": - return "RAI response withdrawn"; + return "Formal RAI Response - Withdrawal Requested"; case "respond-to-rai": - return "RAI response submitted"; + return "RAI Response Submitted"; case "upload-subsequent-documents": - return "Subsequent documentation uploaded"; + return "Subsequent Documentation Uploaded"; default: return BLANK_VALUE; diff --git a/react-app/src/features/package/package-status/index.tsx b/react-app/src/features/package/package-status/index.tsx index 00c19fba7e..10033f8b60 100644 --- a/react-app/src/features/package/package-status/index.tsx +++ b/react-app/src/features/package/package-status/index.tsx @@ -1,5 +1,5 @@ import { useGetItem, useGetUser } from "@/api"; -import { UserRoles } from "shared-types"; +import { SEATOOL_STATUS, UserRoles } from "shared-types"; import { DetailCardWrapper } from ".."; import { FC } from "react"; @@ -9,38 +9,46 @@ export const PackageStatusCard: FC<{ id: string }> = ({ id }) => { if (!data) return null; + // This really a check to determine if we should show the status of an RAI Withdraw being enabled + // We have a flag that we monitor but there are certain things that can be done outside of onemac, + // specifically in seatool that will invalidate the raiWithdrawEnabled flag such as the two statuses + // below (Pending Approval, and Pending Concurrence). In the future we should build logic into the + // seatool sink that allows us to simply clear these flags + const isInRAIWithdrawEnabledSubStatus = + data._source.raiWithdrawEnabled && + data._source.seatoolStatus !== SEATOOL_STATUS.PENDING_APPROVAL && + data._source.seatoolStatus !== SEATOOL_STATUS.PENDING_CONCURRENCE; + + // Similar to the above check their are certain things that occur in seatool that invalidate the secondClock + // flag. Additionally second clock sub status only displays for CMS users + const isInActiveSecondClockStatus = + user?.isCms && + data._source.secondClock && + data._source.seatoolStatus !== SEATOOL_STATUS.PENDING_APPROVAL && + data._source.seatoolStatus !== SEATOOL_STATUS.PENDING_CONCURRENCE; + return (
- {user?.isCms && - !user.user?.["custom:cms-roles"].includes(UserRoles.HELPDESK) + {user?.isCms && !user.user?.["custom:cms-roles"].includes(UserRoles.HELPDESK) ? data?._source.cmsStatus : data?._source.stateStatus}
- {data._source.raiWithdrawEnabled && ( + {isInRAIWithdrawEnabledSubStatus && (

·

-

- Withdraw Formal RAI Response - Enabled -

+

Withdraw Formal RAI Response - Enabled

)} - {user?.isCms && data._source.secondClock && ( + {isInActiveSecondClockStatus && (

·

2nd Clock

)} - - {user?.isCms && data._source.initialIntakeNeeded && ( -
-

·

-

Initial Intake Needed

-
- )}
From 29d2c4887c506fff0d8d982a5ecc6aed21980c40 Mon Sep 17 00:00:00 2001 From: Ty Bolt Date: Wed, 15 Jan 2025 14:47:47 -0500 Subject: [PATCH 12/17] feat(formatting): adds prettier and related run scripts (#914) --- .vscode/settings.json | 10 +- bin/app.ts | 9 +- bin/cli/src/commands/deploy.ts | 35 +- bin/cli/src/commands/destroy.ts | 27 +- bin/cli/src/commands/get-cost.ts | 22 +- bin/cli/src/commands/logs.ts | 30 +- bin/cli/src/commands/open.ts | 16 +- bin/cli/src/commands/test.ts | 4 +- bin/cli/src/commands/ui.ts | 7 +- bin/cli/src/commands/watch.ts | 13 +- bin/cli/src/lib/env.ts | 4 +- bin/cli/src/lib/sts.ts | 9 +- bin/cli/tsconfig.json | 4 +- bun.lockb | Bin 1032264 -> 1035056 bytes eslint.config.mjs | 14 +- lib/lambda/processEmailsHandler.test.ts | 2 +- lib/lambda/submit/submissionPayloads/app-k.ts | 15 +- .../submissionPayloads/capitated-initial.ts | 15 +- .../submissionPayloads/capitated-renewal.ts | 15 +- .../contracting-amendment.ts | 15 +- .../submissionPayloads/contracting-initial.ts | 15 +- .../submissionPayloads/contracting-renewal.ts | 15 +- .../submissionPayloads/new-chip-submission.ts | 15 +- .../new-medicaid-submission.ts | 11 +- .../submissionPayloads/temporary-extension.ts | 15 +- .../submissionPayloads/toggle-withdraw-rai.ts | 15 +- .../submit/submissionPayloads/withdraw-rai.ts | 15 +- lib/lambda/update/adminChangeSchemas.ts | 2 +- lib/libs/api/kafka.ts | 12 +- lib/libs/api/statusMemo.ts | 2 +- .../emailTemplates/MedSpaCMS.tsx | 2 +- lib/libs/email/getAllStateUsers.ts | 11 +- lib/libs/email/index.ts | 5 +- lib/libs/email/mock-data/withdraw-rai.ts | 1 - .../CMS/Waiver_Capitated.tsx | 2 +- lib/libs/env.ts | 4 +- lib/libs/handler-lib.ts | 11 +- lib/libs/tests/env.test.ts | 4 +- lib/libs/topics-lib.ts | 23 +- lib/libs/webforms/ABP1/v202401.ts | 342 ++++++---------- lib/libs/webforms/ABP1/v202402.ts | 369 ++++++----------- lib/libs/webforms/ABP10/v202401.ts | 6 +- lib/libs/webforms/ABP2A/v202401.ts | 36 +- lib/libs/webforms/ABP2B/v202401.ts | 33 +- lib/libs/webforms/ABP2C/v202401.ts | 18 +- lib/libs/webforms/ABP3/v202401.ts | 24 +- lib/libs/webforms/ABP3_1/v202401.ts | 57 +-- lib/libs/webforms/ABP4/v202401.ts | 9 +- lib/libs/webforms/ABP5/v202401.ts | 51 +-- lib/libs/webforms/ABP6/v202401.ts | 15 +- lib/libs/webforms/ABP7/v202401.ts | 33 +- lib/libs/webforms/ABP8/sections/v202401.ts | 94 ++--- lib/libs/webforms/ABP8/v202401.ts | 15 +- lib/libs/webforms/ABP9/v202401.ts | 9 +- lib/libs/webforms/CS11/v202401.ts | 75 ++-- lib/libs/webforms/CS12/v202401.ts | 27 +- lib/libs/webforms/CS15/v202401.ts | 6 +- lib/libs/webforms/CS3/v202401.ts | 6 +- lib/libs/webforms/CS7/index.ts | 2 +- lib/libs/webforms/CS7/v202401.ts | 63 +-- lib/libs/webforms/CS8/v202401.ts | 39 +- lib/libs/webforms/CS9/v202401.ts | 36 +- lib/libs/webforms/ER/v202401.ts | 290 +++++--------- lib/libs/webforms/G1/v202401.ts | 15 +- lib/libs/webforms/G2A/v202401.ts | 30 +- lib/libs/webforms/G2B/v202401.ts | 27 +- lib/libs/webforms/G2C/v202401.ts | 6 +- lib/libs/webforms/G3/v202401.ts | 48 +-- .../iam-permissions-boundary/index.ts | 15 +- lib/local-constructs/clamav-scanning/index.ts | 45 +-- .../clamav-scanning/src/handlers/scan.test.ts | 6 +- .../clamav-scanning/src/handlers/scan.ts | 13 +- .../clamav-scanning/src/lib/clamav.ts | 144 +++---- .../clamav-scanning/src/lib/clamd.ts | 6 +- .../clamav-scanning/src/lib/constants.ts | 18 +- .../clamav-scanning/src/lib/file-ext.ts | 13 +- .../clamav-scanning/src/lib/s3.ts | 17 +- .../cleanup-kafka/index.test.ts | 8 +- lib/local-constructs/cleanup-kafka/index.ts | 84 ++-- .../cleanup-kafka/src/cleanupKafka.test.ts | 5 +- .../cleanup-kafka/src/cleanupKafka.ts | 21 +- .../cloudwatch-logs-resource-policy/index.ts | 54 ++- .../cloudwatch-to-s3/index.test.ts | 15 +- .../cloudwatch-to-s3/index.ts | 16 +- .../create-topics/index.test.ts | 16 +- lib/local-constructs/create-topics/index.ts | 18 +- .../create-topics/src/createTopics.test.ts | 9 +- .../create-topics/src/createTopics.ts | 8 +- .../empty-buckets/index.test.ts | 12 +- lib/local-constructs/empty-buckets/index.ts | 4 +- .../manage-users/index.test.ts | 16 +- lib/local-constructs/manage-users/index.ts | 109 +++--- lib/local-constructs/waf/index.test.ts | 15 +- lib/local-constructs/waf/index.ts | 26 +- lib/packages/shared-types/attachments.ts | 9 +- lib/packages/shared-types/authority.ts | 6 +- .../events/capitated-amendment.ts | 7 +- .../events/contracting-amendment.ts | 16 +- .../events/new-chip-submission.ts | 12 +- .../events/new-medicaid-submission.ts | 20 +- .../events/temporary-extension.ts | 12 +- .../events/toggle-withdraw-rai.ts | 1 - lib/packages/shared-types/forms.ts | 7 +- lib/packages/shared-types/inputs.ts | 18 +- .../shared-types/opensearch/cpocs/index.ts | 8 +- .../shared-types/opensearch/subtypes/index.ts | 8 +- .../shared-types/opensearch/types/index.ts | 8 +- .../shared-types/seatool-tables/index.ts | 2 +- lib/packages/shared-utils/cloudformation.ts | 10 +- lib/packages/shared-utils/regex.ts | 5 +- .../shared-utils/seatool-date-helper.test.ts | 18 +- .../shared-utils/seatool-date-helper.ts | 4 +- lib/packages/shared-utils/secrets-manager.ts | 9 +- lib/stacks/alerts.ts | 6 +- lib/stacks/api.ts | 61 ++- lib/stacks/data.ts | 29 +- lib/stacks/ui-infra.ts | 46 +-- lib/stacks/uploads.ts | 9 +- mocks/handlers/api/user.ts | 4 +- mocks/handlers/authUtils.ts | 4 +- mocks/handlers/opensearch/changelog.ts | 9 +- package.json | 5 + react-app/components.json | 16 - react-app/index.html | 2 +- react-app/src/api/getAttachmentUrl.ts | 2 +- react-app/src/api/useGetCPOCs.ts | 10 +- react-app/src/api/useGetCounties.ts | 14 +- react-app/src/api/useGetForm.ts | 9 +- react-app/src/api/useGetItem.ts | 10 +- react-app/src/api/useGetPackageActions.ts | 4 +- react-app/src/api/useGetTypes.ts | 18 +- react-app/src/api/useGetUser.test.ts | 55 ++- react-app/src/api/useSearch.ts | 19 +- react-app/src/components/Alert/index.tsx | 22 +- .../src/components/BreadCrumb/BreadCrumb.tsx | 6 +- .../BreadCrumb/create-breadcrumbs.ts | 8 +- .../components/Cards/CardWithTopBorder.tsx | 4 +- .../src/components/Cards/SectionCard.tsx | 16 +- react-app/src/components/Chip/Chip.test.tsx | 4 +- react-app/src/components/Chip/index.tsx | 7 +- .../ConfirmationDialog/ConfirmationDialog.tsx | 6 +- .../ConfirmationDialog/userPrompt.tsx | 4 +- .../components/Container/Accordion/index.tsx | 8 +- .../DetailsSection/DetailsSection.test.tsx | 6 +- react-app/src/components/Dialog/index.tsx | 18 +- .../Form/content/ContentWrappers.tsx | 6 +- .../src/components/Inputs/button.test.tsx | 41 +- react-app/src/components/Inputs/button.tsx | 20 +- react-app/src/components/Inputs/calendar.tsx | 21 +- .../src/components/Inputs/checkbox.test.tsx | 118 +++--- .../src/components/Inputs/date-picker.tsx | 8 +- react-app/src/components/Inputs/form.tsx | 24 +- react-app/src/components/Inputs/input.tsx | 7 +- react-app/src/components/Inputs/label.tsx | 9 +- .../src/components/Inputs/radio-group.tsx | 8 +- react-app/src/components/Inputs/textarea.tsx | 9 +- react-app/src/components/Inputs/toggle.tsx | 5 +- .../src/components/Inputs/upload.utilities.ts | 6 +- .../components/MaintenanceBanner/index.tsx | 12 +- .../main/Filtering/Chipbar/index.tsx | 27 +- .../Filtering/Drawer/Filterable/DateRange.tsx | 80 +--- .../Opensearch/main/Filtering/Drawer/hooks.ts | 8 +- .../main/Filtering/Drawer/index.tsx | 19 +- .../main/Filtering/Export/index.tsx | 4 +- .../main/Filtering/FilterProvider.tsx | 4 +- .../Opensearch/main/Filtering/index.tsx | 6 +- .../Opensearch/main/Provider/index.tsx | 14 +- .../Opensearch/main/Settings/Visibility.tsx | 14 +- .../Opensearch/main/Table/index.tsx | 3 +- .../src/components/Opensearch/main/types.ts | 6 +- react-app/src/components/Opensearch/utils.ts | 15 +- react-app/src/components/Pagination/index.tsx | 4 +- react-app/src/components/Popover/index.tsx | 2 +- react-app/src/components/RHF/Field.tsx | 19 +- react-app/src/components/RHF/FieldArray.tsx | 29 +- react-app/src/components/RHF/FormGroup.tsx | 4 +- react-app/src/components/RHF/Section.tsx | 11 +- react-app/src/components/RHF/Slot.tsx | 8 +- react-app/src/components/RHF/SlotField.tsx | 71 +--- .../src/components/RHF/dependencyWrapper.tsx | 48 +-- .../RHF/tests/depedencyWrapper.test.tsx | 12 +- .../components/RHF/tests/initializer.test.tsx | 4 +- .../components/RHF/utils/additionalRules.ts | 53 +-- .../src/components/RHF/utils/initializer.ts | 21 +- react-app/src/components/RHF/utils/is.ts | 31 +- .../src/components/RHF/utils/validator.ts | 59 +-- react-app/src/components/Sheet/index.tsx | 27 +- react-app/src/components/Table/index.tsx | 41 +- react-app/src/components/Tooltip/index.tsx | 22 +- .../features/dashboard/Lists/spas/consts.tsx | 8 +- .../dashboard/Lists/waivers/consts.tsx | 6 +- react-app/src/features/forms/index.ts | 2 +- react-app/src/features/guides/index.tsx | 3 +- react-app/src/features/package/hooks.tsx | 4 +- react-app/src/features/package/index.tsx | 4 +- .../src/features/selection-flow/options.tsx | 32 +- .../src/formSchemas/temporary-extension.ts | 8 +- react-app/src/formSchemas/withdraw-package.ts | 3 +- react-app/src/hooks/UseDebounce.test.tsx | 7 +- .../src/hooks/useCountdown/index.test.ts | 10 +- react-app/src/hooks/useCountdown/index.ts | 4 +- react-app/src/hooks/useIdle/index.test.ts | 8 +- react-app/src/hooks/useIdle/index.ts | 4 +- react-app/src/index.css | 4 +- react-app/src/utils/Poller/DataPoller.ts | 3 +- react-app/src/utils/Poller/documentPoller.ts | 5 +- react-app/src/utils/createContextProvider.ts | 8 +- react-app/src/utils/crumbs.test.ts | 41 +- react-app/src/utils/crumbs.ts | 22 +- react-app/src/utils/formOrigin.test.ts | 40 +- react-app/src/utils/formOrigin.ts | 7 +- react-app/src/utils/location.ts | 3 +- react-app/src/utils/stateName.test.ts | 6 +- react-app/src/utils/stateNames.ts | 3 +- .../src/utils/test-helpers/uploadFiles.ts | 13 +- react-app/src/utils/textHelpers.test.ts | 20 +- react-app/src/utils/textHelpers.ts | 4 +- react-app/src/utils/user.test.ts | 12 +- react-app/src/utils/user.ts | 15 +- react-app/src/utils/zod.test.ts | 15 +- react-app/src/utils/zod.ts | 8 +- react-app/src/zodIdValidator.ts | 5 +- test/e2e/pages/faq.page.ts | 6 +- test/e2e/pages/home.page.ts | 22 +- test/e2e/pages/index.ts | 2 +- test/e2e/playwright.config.ts | 4 +- .../components/AddIssueForm/index.ts | 4 +- test/e2e/selectors/navigation/index.ts | 6 +- test/e2e/tests/FAQs/faq.spec.ts | 370 +++++++++++------- test/e2e/tests/a11y/index.spec.ts | 14 +- test/e2e/tests/fixtures/routes.ts | 2 +- .../home/disableRAIresponsewithdraw.spec.ts | 2 +- .../home/enableRAIresponsewithdraw.spec.ts | 2 +- test/e2e/tests/home/index.spec.ts | 25 +- test/e2e/tests/home/noauth.spec.ts | 64 +-- test/e2e/tests/home/respondtorai.spec.ts | 2 +- .../tests/home/subsequentdocuments.spec.ts | 2 +- .../tests/home/withdrawRAIresponse.spec.ts | 2 +- test/e2e/tests/home/withdrawpackage.spec.ts | 2 +- test/e2e/tests/perf/index.spec.ts | 12 +- .../tests/sub-doc/medicaidSPADetail.spec.ts | 32 +- .../sub-doc/medicaidSubDocDetail.spec.ts | 43 +- test/e2e/utils/auth.setup.ts | 5 +- test/e2e/utils/setup.ts | 5 +- 244 files changed, 1866 insertions(+), 3620 deletions(-) mode change 100644 => 100755 bun.lockb delete mode 100644 react-app/components.json diff --git a/.vscode/settings.json b/.vscode/settings.json index f2e33e6d9e..84221c8762 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,6 @@ { "editor.formatOnSave": true, - "tailwindCSS.experimental.classRegex": [ - ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] - ], + "tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]], "typescript.tsdk": "node_modules/typescript/lib", - "cSpell.words": [ - "Cpoc", - "Cpocs", - "opensearch" - ] + "cSpell.words": ["Cpoc", "Cpocs", "opensearch"] } diff --git a/bin/app.ts b/bin/app.ts index b874a0c7a2..4c0634f209 100644 --- a/bin/app.ts +++ b/bin/app.ts @@ -4,10 +4,7 @@ import * as cdk from "aws-cdk-lib"; import { ParentStack } from "../lib/stacks/parent"; import { DeploymentConfig } from "../lib/config/deployment-config"; import { getSecret, validateEnvVariable } from "shared-utils"; -import { - IamPathAspect, - IamPermissionsBoundaryAspect, -} from "../lib/local-aspects"; +import { IamPathAspect, IamPermissionsBoundaryAspect } from "../lib/local-aspects"; async function main() { try { @@ -36,9 +33,7 @@ async function main() { }); cdk.Aspects.of(app).add( - new IamPermissionsBoundaryAspect( - deploymentConfig.config.iamPermissionsBoundary, - ), + new IamPermissionsBoundaryAspect(deploymentConfig.config.iamPermissionsBoundary), ); cdk.Aspects.of(app).add(new IamPathAspect(deploymentConfig.config.iamPath)); } catch (error) { diff --git a/bin/cli/src/commands/deploy.ts b/bin/cli/src/commands/deploy.ts index 7ccbba928a..f363153ef0 100644 --- a/bin/cli/src/commands/deploy.ts +++ b/bin/cli/src/commands/deploy.ts @@ -1,17 +1,8 @@ import { Argv } from "yargs"; -import { - checkIfAuthenticated, - runCommand, - project, - region, - writeUiEnvFile, -} from "../lib/"; +import { checkIfAuthenticated, runCommand, project, region, writeUiEnvFile } from "../lib/"; import path from "path"; import { execSync } from "child_process"; -import { - CloudFrontClient, - CreateInvalidationCommand, -} from "@aws-sdk/client-cloudfront"; +import { CloudFrontClient, CreateInvalidationCommand } from "@aws-sdk/client-cloudfront"; import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; export const deploy = { @@ -22,11 +13,7 @@ export const deploy = { }, handler: async (options: { stage: string; stack?: string }) => { await checkIfAuthenticated(); - await runCommand( - "cdk", - ["deploy", "-c", `stage=${options.stage}`, "--all"], - ".", - ); + await runCommand("cdk", ["deploy", "-c", `stage=${options.stage}`, "--all"], "."); await writeUiEnvFile(options.stage); @@ -57,16 +44,8 @@ export const deploy = { // There's a mime type issue when aws s3 syncing files up // Empirically, this issue never presents itself if the bucket is cleared just before. // Until we have a neat way of ensuring correct mime types, we'll remove all files from the bucket. - await runCommand( - "aws", - ["s3", "rm", `s3://${s3BucketName}/`, "--recursive"], - ".", - ); - await runCommand( - "aws", - ["s3", "sync", buildDir, `s3://${s3BucketName}/`], - ".", - ); + await runCommand("aws", ["s3", "rm", `s3://${s3BucketName}/`, "--recursive"], "."); + await runCommand("aws", ["s3", "sync", buildDir, `s3://${s3BucketName}/`], "."); const cloudfrontClient = new CloudFrontClient({ region, @@ -82,9 +61,7 @@ export const deploy = { }, }; - await cloudfrontClient.send( - new CreateInvalidationCommand(invalidationParams), - ); + await cloudfrontClient.send(new CreateInvalidationCommand(invalidationParams)); console.log( `Deployed UI to S3 bucket ${s3BucketName} and invalidated CloudFront distribution ${cloudfrontDistributionId}`, diff --git a/bin/cli/src/commands/destroy.ts b/bin/cli/src/commands/destroy.ts index 5444e3c0ae..1e0c245824 100644 --- a/bin/cli/src/commands/destroy.ts +++ b/bin/cli/src/commands/destroy.ts @@ -4,21 +4,10 @@ import { DeleteStackCommand, waitUntilStackDeleteComplete, } from "@aws-sdk/client-cloudformation"; -import { - checkIfAuthenticated, - confirmDestroyCommand, - project, - region, -} from "../lib"; +import { checkIfAuthenticated, confirmDestroyCommand, project, region } from "../lib"; -const waitForStackDeleteComplete = async ( - client: CloudFormationClient, - stackName: string, -) => { - return waitUntilStackDeleteComplete( - { client, maxWaitTime: 3600 }, - { StackName: stackName }, - ); +const waitForStackDeleteComplete = async (client: CloudFormationClient, stackName: string) => { + return waitUntilStackDeleteComplete({ client, maxWaitTime: 3600 }, { StackName: stackName }); }; export const destroy = { @@ -33,15 +22,7 @@ export const destroy = { demandOption: false, default: true, }), - handler: async ({ - stage, - wait, - verify, - }: { - stage: string; - wait: boolean; - verify: boolean; - }) => { + handler: async ({ stage, wait, verify }: { stage: string; wait: boolean; verify: boolean }) => { await checkIfAuthenticated(); const stackName = `${project}-${stage}`; diff --git a/bin/cli/src/commands/get-cost.ts b/bin/cli/src/commands/get-cost.ts index 7679785cce..142a636b5a 100644 --- a/bin/cli/src/commands/get-cost.ts +++ b/bin/cli/src/commands/get-cost.ts @@ -1,8 +1,5 @@ import { Argv } from "yargs"; -import { - CostExplorerClient, - GetCostAndUsageCommand, -} from "@aws-sdk/client-cost-explorer"; +import { CostExplorerClient, GetCostAndUsageCommand } from "@aws-sdk/client-cost-explorer"; import { checkIfAuthenticated, setStageFromBranch, project } from "../lib"; export const getCost = { @@ -34,21 +31,14 @@ export const getCost = { const dailyCosts = await getDailyStackCosts(tags, start, end); const yesterdayCost = dailyCosts[dailyCosts.length - 1].cost; - const averageDailyCost = - dailyCosts.reduce((acc, day) => acc + day.cost, 0) / dailyCosts.length; + const averageDailyCost = dailyCosts.reduce((acc, day) => acc + day.cost, 0) / dailyCosts.length; console.log(`Daily costs for the last 14 days:`); dailyCosts.forEach((day) => { console.log(`${day.date}: $${day.cost.toFixed(2)}`); }); - console.log( - `Average daily cost over the past 14 days: $${averageDailyCost.toFixed( - 2, - )}`, - ); - console.log( - `Yesterday, the stack ${stage} cost $${yesterdayCost.toFixed(2)}.`, - ); + console.log(`Average daily cost over the past 14 days: $${averageDailyCost.toFixed(2)}`); + console.log(`Yesterday, the stack ${stage} cost $${yesterdayCost.toFixed(2)}.`); }, }; @@ -90,9 +80,7 @@ export async function getDailyStackCosts( return results.map((result) => ({ date: result.TimePeriod?.Start || "", - cost: result.Total?.BlendedCost?.Amount - ? parseFloat(result.Total.BlendedCost.Amount) - : 0, + cost: result.Total?.BlendedCost?.Amount ? parseFloat(result.Total.BlendedCost.Amount) : 0, })); } catch (error) { throw new Error(`Failed to fetch cost: ${error}`); diff --git a/bin/cli/src/commands/logs.ts b/bin/cli/src/commands/logs.ts index 91009385f0..8f7d306711 100644 --- a/bin/cli/src/commands/logs.ts +++ b/bin/cli/src/commands/logs.ts @@ -1,11 +1,5 @@ import { Argv } from "yargs"; -import { - checkIfAuthenticated, - runCommand, - project, - region, - setStageFromBranch, -} from "../lib/"; +import { checkIfAuthenticated, runCommand, project, region, setStageFromBranch } from "../lib/"; import { ResourceGroupsTaggingAPIClient, GetResourcesCommand, @@ -24,13 +18,11 @@ export const logs = { command: "logs", describe: "Stream a lambda's cloudwatch logs.", builder: (yargs: Argv) => - yargs - .option("stage", { type: "string", demandOption: false }) - .option("functionName", { - alias: "f", - type: "string", - demandOption: true, - }), + yargs.option("stage", { type: "string", demandOption: false }).option("functionName", { + alias: "f", + type: "string", + demandOption: true, + }), handler: async (options: { stage?: string; functionName: string }) => { await checkIfAuthenticated(); const stage = options.stage || (await setStageFromBranch()); @@ -77,11 +69,7 @@ export const logs = { const lambdaLogGroup = await getLambdaLogGroup(lambda); // Stream the logs - await runCommand( - "awslogs", - ["get", lambdaLogGroup, "-s10m", "--watch"], - ".", - ); + await runCommand("awslogs", ["get", lambdaLogGroup, "-s10m", "--watch"], "."); }, }; @@ -115,9 +103,7 @@ async function getLambdasWithTags(tags: Tag[]): Promise { } // Extract Lambda function ARNs from the response - const lambdaArns = data.ResourceTagMappingList.map( - (resource) => resource.ResourceARN!, - ); + const lambdaArns = data.ResourceTagMappingList.map((resource) => resource.ResourceARN!); // Fetch Lambda function names from their ARNs const lambdaNames = await Promise.all( diff --git a/bin/cli/src/commands/open.ts b/bin/cli/src/commands/open.ts index 32b0ec432f..7de1e19cdd 100644 --- a/bin/cli/src/commands/open.ts +++ b/bin/cli/src/commands/open.ts @@ -1,21 +1,11 @@ import { Argv } from "yargs"; -import { - checkIfAuthenticated, - openUrl, - project, - setStageFromBranch, -} from "../lib"; +import { checkIfAuthenticated, openUrl, project, setStageFromBranch } from "../lib"; import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; -const createOpenCommand = ( - name: string, - describe: string, - exportName: string, -) => ({ +const createOpenCommand = (name: string, describe: string, exportName: string) => ({ command: name, describe: describe, - builder: (yargs: Argv) => - yargs.option("stage", { type: "string", demandOption: false }), + builder: (yargs: Argv) => yargs.option("stage", { type: "string", demandOption: false }), handler: async (options: { stage?: string }) => { await checkIfAuthenticated(); const stage = options.stage || (await setStageFromBranch()); diff --git a/bin/cli/src/commands/test.ts b/bin/cli/src/commands/test.ts index 172e030a20..4c0cf188f9 100644 --- a/bin/cli/src/commands/test.ts +++ b/bin/cli/src/commands/test.ts @@ -16,9 +16,7 @@ export const test = { }) .check((argv) => { if (argv.coverage && argv.ui) { - throw new Error( - "You cannot use both --watch and --ui at the same time.", - ); + throw new Error("You cannot use both --watch and --ui at the same time."); } return true; }); diff --git a/bin/cli/src/commands/ui.ts b/bin/cli/src/commands/ui.ts index 5c16344aee..bc19729f4a 100644 --- a/bin/cli/src/commands/ui.ts +++ b/bin/cli/src/commands/ui.ts @@ -1,10 +1,5 @@ import { Argv } from "yargs"; -import { - checkIfAuthenticated, - runCommand, - setStageFromBranch, - writeUiEnvFile, -} from "../lib"; +import { checkIfAuthenticated, runCommand, setStageFromBranch, writeUiEnvFile } from "../lib"; export const ui = { command: "ui", diff --git a/bin/cli/src/commands/watch.ts b/bin/cli/src/commands/watch.ts index 57d42ba726..12e43cfc34 100644 --- a/bin/cli/src/commands/watch.ts +++ b/bin/cli/src/commands/watch.ts @@ -1,10 +1,5 @@ import { Argv } from "yargs"; -import { - checkIfAuthenticated, - runCommand, - setStageFromBranch, - writeUiEnvFile, -} from "../lib/"; +import { checkIfAuthenticated, runCommand, setStageFromBranch, writeUiEnvFile } from "../lib/"; export const watch = { command: "watch", @@ -18,10 +13,6 @@ export const watch = { await writeUiEnvFile(stage); - await runCommand( - "cdk", - ["watch", "-c", `stage=${stage}`, "--no-rollback"], - ".", - ); + await runCommand("cdk", ["watch", "-c", `stage=${stage}`, "--no-rollback"], "."); }, }; diff --git a/bin/cli/src/lib/env.ts b/bin/cli/src/lib/env.ts index 7c5609529b..98d68d4baa 100644 --- a/bin/cli/src/lib/env.ts +++ b/bin/cli/src/lib/env.ts @@ -1,9 +1,7 @@ export function validateEnvVariable(variableName: string): string { const value = process.env[variableName]; if (!value) { - throw new Error( - `Environment variable ${variableName} is required but not set`, - ); + throw new Error(`Environment variable ${variableName} is required but not set`); } return value; } diff --git a/bin/cli/src/lib/sts.ts b/bin/cli/src/lib/sts.ts index dbf3beb4a8..3b0c418bee 100644 --- a/bin/cli/src/lib/sts.ts +++ b/bin/cli/src/lib/sts.ts @@ -8,17 +8,12 @@ export async function checkIfAuthenticated(): Promise { await client.send(command); } catch (error) { if (error instanceof Error) { - if ( - error.message.includes("Could not load credentials from any providers") - ) { + if (error.message.includes("Could not load credentials from any providers")) { console.error( `\x1b[31m\x1b[1mERROR: This command requires AWS credentials available to your terminal. Please configure AWS credentials and try again.\x1b[0m`, ); } else { - console.error( - "Error occurred while checking authentication:", - error.message, - ); + console.error("Error occurred while checking authentication:", error.message); } } else { console.error("An unknown error occurred:", error); diff --git a/bin/cli/tsconfig.json b/bin/cli/tsconfig.json index e6cf1f168d..60b2c84d60 100644 --- a/bin/cli/tsconfig.json +++ b/bin/cli/tsconfig.json @@ -7,9 +7,7 @@ "incremental": true /* Enable incremental compilation */, "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, - "lib": [ - "ES2021" - ] /* Specify library files to be included in the compilation. */, + "lib": ["ES2021"] /* Specify library files to be included in the compilation. */, // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/bun.lockb b/bun.lockb old mode 100644 new mode 100755 index b16cf8952ee70d77ff921632c9dd4b01b930d239..4dad98675f5391aa0ed24aeda164b7c32c6d045c GIT binary patch delta 201468 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�Zc8@^S=JQecjX@RsRp}FqPY7}M?+tA29@xu^ zbQPFv3I3`Eq`*~CDk}Rj)5+#96`z70i{6atxnr^MTvQEOV)h-Vf}i?c+TCjo%gjNl zf*wTGfQL~P^r-1$sDi&LeH~m4DmH!G{G>Y360;AhYO#bsSN>DrApR$lcMr_ZUzPuP z<5IP&%xo!df9wnF>dxO67~=iqeM;C&f*SOSIUY$hL)YT#$akEF_1OSO~{)npJy_WuuHODW5qQCJ=&oLn^DypD0stP)2k@+vG3Xe7})fsj+TdJOk zKvhmROFz!irOKbv-GEdDoM5)Us-RwQ6%>UkW3<_QOk+?5snSn1TdJWMVD^8}oc}Jt z%T>_nmQgBupxII_kVDM=C#pYwCE~9WNGBC7l^Tl!|R zUqcn7-l!shF6kE@Idx(cmi~9DdfrnubQ`L6e1PgfYlr2Rs=RM3eHSYK-IgwuU1v7x zyBq~n;CJTm1FC}dnxjD@bIpfcpzJThC zH=)W`j%w<>V)m=3g1;(#D_jkD!_r+rLttC?Km)f){qjzSH?53H^Er~LZB>@;uj1Q` zOVznGX8Wu74$dolg3W&9CUWA~yu$$h5#O@A@{4OPVl%x*%J?;xuA7s@YP0!O3b z9R*EQZfCPQw_!R6bOlHwEW^8PI6VbB-*;S~5R26<`{3EkJLxWt#A9feqgQ*}@JHAI% z?OwD0g{t_UjW?hQQu+O1>V5H}1YQJ*x73RvQ}ss>wlvr{dbSs|0;qyeR1Ij0s(_=6 zA8ov|@d&ezMFZT3rois{1BfRYQ>#jURK*QI6{Jc(16BQljQgv)cc`VuOI;PkdLI&o zoBNSe#hp!hFEkBRHM7j$U*&g|rC)<;-escVS>`WQ{+m%X|CY8k4~cjkb5ORy@Z4(- z%Tazj5AjPGA2EGQkRTN=M%A4qWx>V^MQT0R@#-XZk zyzz@s1*yhrkzpsR0OIZRkSz1 zG&E7hr7E|N+0xc}NFHe5pQsu*$O;;as)8Yw|46C|&osZY%+Fu-w{OpI66AQUIryv2 zECH^eNwRclpoL-h&MPP{TweO=qT)-75b z?d^o)Van6P(oQn%jjE15W}k}cr2CmY!0dr$$C?g7HEl;wp4znqB?;=X6MA3gTKl#4z4pk)6$QmN^Z0^o@;idrAyV-HyNL2++UUdX1IE6fu&3JVHwXtB5L97s47@u4*seF?l3Nu-`%MA zJ;wh|<+s%Qq})h652ER6(Ib{1)tRh8b^Shtc1Pbu)xd42f>eGVqAK_!RBO_9RORhJ z)xcer{w*5lvRkp{s+fJ4(w{B)7qc5o51`8ar={zSNZBn>6(5SK+SaCROxvPrRy$OF z9Z}WO#nQWWV7XC2-OMo(Rq$68&>gM2dw^0SD z(%&&#s-f6s_J5+3d$H#OE9jr78u*Dd;B!d7zARxZCaa~sR^ttCs9{X4Uz znmYSX74t9S{wluTxK#OnLu1gBc#}yT-y0S0gQ|o3>&+*h`u*&a|Gg*@0Z_uRrrl8$ z(8KgZ)1HRc7L-^M}u5sSc|TqDsGstiw|qj+xx6{fq7kIdNC@uWV0us znyAxIweku}PqXw{W@n%}?W>Jnhsys3OP_0+W$8C}WbsvoY=DAPe;{6J4$Dy$P+;~- zRQP&3?=DZPRy91*!5?`*;4tvZnnSbOy98dH!b~d z)OYE9O0X>S(!^p2As(hXG`5066V1)5*=64*bp6ib4;VnAH5>7#tp&zP(PD3@u zgDicp@u8^vhFSUuv^)0Ys2Va8)f~yN^x3E?oMU>urQc+B&OD3UY?_U#p!sGmF#9%C z73UhyL)FkbP&M=(RQZ;fU0~_=n?8VQ9eEU$-&&N0<#^T^SnmU#Cr}mqr0LVfH<zQ04moJqi8QYcu|T5K)l!Mo;9!SSqk5ssf`-`=IK9 zzGk0hI?y!MbO@^8uUbDw!j*rt`Hex3*Y$U`f$IdYmzr)sHMZ}Y?nO2Eenr)=M$XCe@uqXo6S40@ z)q@47%6$M;g{#bd1XV-Vb!GfdAo3PK8QwwFfGSi$sxkWzRfcLyud#Hg{68~WD*JQO z?Z%~AL4QD1&OXy$^?>f}N`M-00F`~vY<+x94G2b+9%6PolsC^j$D;D@Zh8W$AeH|~ z#(Sfx_!P7IqUyoe90Nm81*tN|qZ;dTQH|{dsP1;DsAlyHRDLs2y_lM9_Vws7*mt4o z`g>6|aD{0Bs)graOV4@C5{gls=~I^QEUK=2&iIR{3Mw~!1yw^TP@Ty(;~$xRimJR? zRBxdDfT}@1nf{DYevaoCB62){>Wl;Vkft1iQFU1xeQ48E4QOZSM;n*QzYD4ccQp-1 zHAKBoJ=?^gDu0;iNYwj}DTzc>(HK+(j78O@m!Mo(o@-H_>pi!k8j8D64aG{c*P_a| z&a@a+kcvNUwp8W4f~vuevcymCshx8kDn^8M|B4KET2?-zuAAMYQWFp()vt&*NWF8x>;tF@ZZi8w zs`NieSAm-P8ltwk1pXV9Ul{4qcE?PTnb?S@OV`AJp4$)X1CRY#tIYJ}p9OI6HZ(;>#C;zP}rs@yYCjra)Te%jjWFp`8YSJJITSNBr4 z79s!Om-ka86Ue6eF1B)|vL~7*8<&cwpjs5B8uwS7-{s~v1J#6@i>m!urt?rOCbu2K z#882Y0J=7pSi&+?d^xIcBvpa;S-zF1>>~4*D*uDVA2KeL-@|4L#SI+fTprrnEg*wLl9*CM^a7B5UVE?)jybc+FJ%`G!C(- z7NT*eGK@#nkO`9j zssZ!N_SZh}$KY!Cd*~_X0aOh?X#P^={}Yv;!XP`m@5&GMBHtegzWTL!7@D>&3MGdUEbveQh{Q3a_UgEBcZ z7yjFAXh_psxK-n?v0B8T@IO&iw1h*qhNT<|QZ?{i83d{7Ss{aPNMjB~q;e?W&>7#) zp%yl8LqjJJ-s4b*Hg7|t-XQ#g{cZlCpRNIde}SE`JJ8*lk)2TWd}q@xrdIJZLvw@oshb(`KiLOIlj=r(N$2X9 zsowZ#-iD@m8=B^AXqva7Y2JpWc^jJMZD{^C_O;RTUGp|HzUSlSZD^Pb&D+p4Z$rZy zwt79-ybVo=-f|-Nt5(c5aJ{STzx|B-WUJ?zx1nj?hDO`b=;dqkHZ=a*&*&ZaSg4b2#OLKo6Fl#k^#Z$s0(4NdblG|k)4G;c%W zyJ0kML({wsjcpdvybX=_Qw+`9&@^vD)4UB$^ENci+t4&`L({wsP4hN1&D+p4Z$s0( z4UPYHH2Sn+^ENa`+KxtV!8UJ0^MBln#`_Pk&D+p4Z$tB+Y$wB|_CMMer+FKi|DU#@ zIoST7)WUQN?v*8%94=j9p5;0|H?HX zUtiwQ^G^4KW3Jluz%}D$#%%4p{>;pdr+)U+>8;;+C+qWduepRWmw%vTxJx?Fa&&-m zg$G(rbP0d740BBag)Z?AK!ZTZAApsvQD9vYAhiilG%0WQLpMY|K z)h_f;z(#?LKLL-pGQf(ops*E@L8AlKtVj=<=)waqt1#EPhyd&gY?mDXD0YI4dc0ZGAtQdbxZNN5FU5-4+ttpE)IC9MFPT%*9c5I|}OpxhOQ0FpxiVWEJ{E+rHY z(i%`Mu*HS825c0_XbpJHl?kM^0YtU|Y<1~v0O4%`RRV9ih_-+Vf$X+`N>?e683u?6 z1HA3B!T?e20JQ>DF1j6{S|Gn2;5}C(klP**-yX2d<+TUI9tEfu_|U~21=u4{bQIuY zS0_-=0g%)I@Tn{807y6*&?Hdf5|0Kn2$UQR_}n!Ltm_C!?FguK#T^03od98-0AIS4 zPJockfO3HyF0?aXqd-Pyz)n{tkk$nd*#)r6rFQ{@cLh`l>~;}d0Tlw-T>*8jQXn%N z5EBmg-erXYq9Opb0`)FB0#GfG9|73wY6NnR0mL5z*yr+&0mOC#)C=r)aoqrW1d6%= zesOgI1;+xCjs^VY3XcUOL;{)w8eL)}ph2KS6Z?Q`6j*m0AoVyvlPf+BklY;*)*bMt zOX&^>IUZ0h5a2?O2W%9`I3Cc#l?kNv07Uix1iADcfbbIlRRXPC#0h{3f$S3ip{`OO z^F%<*iGVgP>qJ1*Nq|~`Fc*CipjsgRBtUytBaqt@5Z^OsGz0EiX7sz|rzZ?L*!8#F zp=;JZdF%cYpLuf6`$cuPY%aXv;w4$FQ**zMo;~pNUwUp@e&G#s|I8ozUd#P+j;}ko z;QIWjgCgf`?|#owaW1A;PmK5=91 zn%5UzQ9eHRjHDhZKczf)#qymO^f-TdyKmRr643GTMVsCUsb3%dEDK+N$F~4>^l#nl zr=oUjUHn0>PZxiD-W|(+-8Q8B(#b#HJYm<*BTjg5^99)hhRk~L`qlknBXXCYJ^ae_ z)X1?5-@I_Zh`2N{vIbVNrX2<-C5=zhLT>r_$ znLo7Ob64T*PoMW_)CG%{k55aScH7viM?@T(5m9x`%gg&cwDpV4_l=o;Y52dMSnt=o zt}d@9gPjn?VAnI)0TC{)7ob6)s28A{s}ook4M^$@h;)U$0m&x=ngqJL#3(>WA3#YI zpoeP|*eH-14LH#iM+4GM0fe0l=;=~U2871|$_0A6&^~|)fs8(YXjdkXc`6|C6hI%B zehMI}FQ7^w#zn*css*xR0DWDhKyE)k%&CC>F6&f4Y=1zlzyKHB7qF*)P+NCr-=OmY z2D%!7g46Jb?}x`Am)8%FFaS_55a;6h0~!R1`U8fzaT5URP6s5N28efsrvZ`&0-6NQ za)|=~A!h(e1^|Y;MuCk2siyVplBpuo~sjBHwuuH0LXTQ34r9$ zfF^+)mzW3$IUi7x2w3161vUz#jsh%n#iIaeNr14?fJH84G$4Eopj=?F3q2oDA&_xC zAkUQvWR3+yCIRkn=}Ca73jkFD`7UA%pjse%4B&28DUdr35Hl9A)MbqY#9j!f6i#K(#=23g9VMDUf?9Am$Rl2A6dSAT||H zEAXs~o&?wleCVRD0PGRSzXI^Fs}U$j2gJ_=eCqOM0urtS)C<(OxHLe6Kv5dtb5|#@ZWbUZ z9Z>5E(*em>0h$E9bct62LNWj)R|0mpMuCk2sj~n(UGXeH+SP!ts{p%P%2j~yYXIc} zyIp7oph6%c15oG61TwD$L|zT}-lbm+h`J6?B~b4ot^rgFWM2c=>na6uX9Hrc1?+QK z*8*ba0BQyHyXflxdj#^Y1N`D@1PZPP#Lou&=JI9(5^ez03pBd8Ie-R%qB(#Au1;Xx zjew-<0Zp#(dO-4AK$F0qF7XCHNG71`b0m1I~8-gx&8%0uYgtQ2DkKYJM%YuZ> zg#-n=OXfntZ-SIVcvcL}q?ihUj7&hND-+0^2Z+o9v~lTKfT)`RRRUoy;wC_~K=w_5 z_O4POHyaQ$575D7%>%^V0;m<}=%Q~1>=DSn8PM6)2o&T1;j6OQDnPBk92Z>#*dvf%1h~P~2oyX7h<^|;*X2D3NLUT1 z7szsPs{joGMXLbwT%EwWhXF|s0kU1;LxAK*08IipE^##=!=LEg<%BK&`+s7rhR!M<9P4V1=s@C|D1OF9tZ5R}4sa z0#Gkd=;9s+Gzb(u4p`~x1lE-RlGX!?T;X~^@{@ojfmJT?2|&nGfRZNwt6ihOMuF54 zz$31>1d#SLAnZxN8kh1UAbbO$TwtvWeF{(^knt3t*p&%nJ_Cq+8nE7_KMja_7EmQn z;vzNxss*w)0G@J{0=drtVx9qPa9Pg)VxI@p3Owtgp9Sm@$bS~_ysHr?cmWXq9H7+Y zJqJiA1=I_axwz*64FW~a12(xjfpr@JNiP7(UEvFWl>#*2VTS0<2I4v5?Yc-y6K0z|zG zs1m4h5ibF%1+rfPyyq$fayJ8F$^qM4RyiQ{6+o@Phc5bMz#f78mjNHU8i9f>fcVXT zPhH+-K*FnldVv}j_X?mvpy(CA=dMm*-D`lPEr42AxCM~>I-p75OPBa6AY?0`f$TQ{dtIeK?puJE3cx;>RRM^78&E5--$hpf_6X!x0)BBd0tN2? z;@<-N=JMVGBvb+F1sYx4+kggvqPGDDT%EwWcL7Q70GeFkJAmZ(g4(%*?*xs~BS;k> z090ue6m6F`GN(IkZvbh#0b#oUBV5WZK=>X&xxh#l z`YoVBAmdv=qAL@~tOG>u28?#;y8%((0jdO&T*MwgwLtbBz*tu)ko!F#rVcR9Wz_*< ze*n}9T;!s^1MCsV{|+$0)d&>S1LD62OmunQ0}_4&)C;7zxE}xw0!2RnCb>F+b$bCx z^?+1YSPw}43D6`k*(Lr62-ycH`4KSHH41DLNZkvV?uz#U(*6Yq`w1|^rThd4-w!Ak znCU|I0V)JC_5spenLy^xfXIIVX1Vl#0iu2ZR0(9bi2Z?Q#PE zF@FM#cgmVkPJLKhbZXb>n0 z1gvy*0_%bRNi6_HuCN6lIT+9+u*xO21cbB#l(Yn_c8vlX1yX|mkGSF>Kw1bOEEurH zr33@QLjmOiYh7q7K!rd?D?qU;6Ub~0hztR&cj+O3s5XEqff5%H3aA#y4h1~rDg|=e z0%BSNHn^lVt-!M`x(#5DKz~(SQnpjH3aSu1p}aGa#}f;BA-Q5fIe{P$f|1B02%81+qH<-gA`# zxm^JH(+{sCNTzL&_m~`iP>K3W1C$K&UGd$m|1%j0UuE>ALu% zP61R2gt>^50o4N8Cj;8MN`c%MKujM%2ba|c5PK@1R-mJcJ_WExApaCVXICRo&=(LN z1L*4VVgL#K0QCY9F78x7gFw-#fNri%U|oMeQeQx%E9?tMJ`K<$(A_2W1B46!l=K7i zaE$^R1ycJ1PISfn0cocL!cGJ9bSb9+!UqD%1$w*C0e}jDi~)dXS0<2o1|afuKp&TW zIv{Egph_UdMGOQ~3uF%j^mUa2xv_wlGXVWv))|1ryfa(S_Ugdu=>fjAcz2WSu|iUSOBbpq>#0+I#;;$7ijKyo~wN#HD(I0O)KCZJ>p zV7O}(*eH-X6mX6!9tuc13lJ6$7~xXl0pY^{KX+$3Z#w%On1d20coQEVF`d4E+qjFJ{nLiFw=!50xASD5&`M1Od#`oK;$UE zESEkC5S0X|63B27qXE?d*`op1xJrTCF@Tu!0oS>#^8vAA0kr~iTyzp(k3fDB;09MC zP;dbtehgr)%Nqko7zd~q$Z~OG0Sy90V*&GAoxr*a0ZA7CvR&Z?faHq+O#(SCaU39I zJfLJ8V1a8C*eH;CAz+~^z7UW$0T6Z(V3A9?2oQcTpj=?F3mp%r5Xcx0$a7@^nG*q# z699L(^a;Te1MYNNB>67lV&pD&o#bv;DY?h>n20QOS(1C*HpwyxuRqd85JXwE8^cqt%cGN9yAz-reh zuu&j274V2FP6eb*0fb!!SmRPI1B6cnlnbnNp_2g>0vVG5#jZ>sa~dFW3ShlUp8|-Q z4yY0+aS>Ai)dJa50Z+L~f!xahG1CAWT-G!|>z!ujiuu&j24e**PP6MRP0)(Xlwz`ybK=@UFa)CEp=#_v9fs88wm99)6GXoGg z3-Go}p9P4z8c-!rha=gtdHCR$;Yln@`>wz z4f3hWlT^DMk{TCxE%KRLD*4>iNw&M;*CDm8Q1XS_C;8GP&PKj+t0g;JqvUHhZVs~3 z6-&Nx0oNnDT#Dpdw?VSoh2DVdaZ@FAu1xZs>u@9Ty-P=2R2HMNJ%iDycM)>|)mfZ# z_FT?+ud5Wuy$KML3E1bdG6Avk0JQ@9U33;;k3fDF;1^dTP;fIK{wBb0F7GBlLN=gY zpwY$612hN}%>x{8bpq>d0VLfFXmW)&1Cnz9O#*+q#B4ywd|LWMcJTRu-1L9X)}zCG zTvBi0L7_#cd;AtXIxK*M<>=8N)LoLJM~7P>s`;F7s4El5T!=^H z0zBHd^aX&Z+W=JpVJ_lUK(#>jt$_BfQXqE`AZ8(;gUeb7h`k+9E6~wJ-$w6tatkD# zU5%uR>%R!;>hdJvZigho#odk^Z3k)cA8F|6oSvd0Z zf_E~tu3Hj3AflYFmSpRs`O~jTKlREvzFja5@}H|+O84Cxyg0xee^>BB?xCTrTKe*x zc<;)V?x_6Wkf0WkeCeaxQV{$l=)Sukn~&#f9ZUDE3f>$julMf>4)N9fVSwzX@5SfW zeteSKb@(i}Z@`Aq`g?<2Ku~Ec`)hbn=*+9GnWj${Ww|ZOgGYwe^ObsR5IFz0(he(v z*SGK$c0h%dzI1=^@d3Vb=vzt~H;RU4Or1Sz#^rp+U+K1@;70-i-f=ssgZnDwP{}vA z4Xc6&gqBYAo%JQ9^{awMw`B2J{ZsHJIbM-@?bO+GX3T!2G+<5eMZrP4olc6=Y^53^ zALX0f*yVA4j|VspRGWs}n11~XwK~l$cp|uW(0-EIm+n{@ygMLh1nwOjJ-M08?$W2| zfsY<1r+VocZ!c}%0Gsw4rK*y(-qW~pPE6L+jH^6%mKHr5ygwk|!P2GA2bTtP9$o4E zl1Fc)TZ!|%>ho(#Q%i#c7`Cn`9N*ZH!s)^2HD@ZF#ui$i?n9_SdLjZ(d>zG3E7=~rZ3IcFkYN7nX`XTTLM z;H}{4t#|O9WE$>M`?`CogU?mrPXzjYFS~YSYgMy7xK&7yx50l6Nol`r!Qlaa&6O8R z$91{>Uj|PP z+A@P${z~n9?d%!XU!Oi>_MO)Rc)L{l$Q-w9+>_q<>pRhFRZQ5m>6c$Wd&UgUQG5}V zT^MuI(x>ZN5E`-9%7t3&I_wSZ81&VnmNd&-vM;HWi{r$Aph-_DjmbPGz#a8Ma7#CAe{eF7Wn58y z=WuAqG;n)VNLWz?`Dn_tuNmLcsinbVTPpFCziJ5F+2qEwXmwoR@`LW`7Ofr&+8r23 z@6)f|m86tWUl-Wp!ft`yX0=vG66UOUg`>)UE(`6Yp#4m9&!G+aZ!Neyh1G2{B#*U49$5C&44aR!{Y1jF=I~r0%gNcvIM9kf!qXnIt*N(jw{FhBJG+&63w4^am*L zq-#opBU+7V9aMflX@^haiNLk=ldrQ&ll!!KA|U9`*EuKeVi}e(XD-e1c-&TH;`HXU z@D0mL{aQWOCgBa=6o2C5z|y^|g5z4yfS64?ea-S;?2p9G`iB13vNcl8?l-ViNS6zC z(TSXqR*9>oW=y-B74(5!Zp^?|)0jIO2DaME?Pm2Et%mro(%WNOB?S0xL)}!;`Rkc! zyLqYBYGK&Ft)y<>C&9D2wD>(x-#-3t_o7KpExv3>E6tbufKu*5r5$xQn&uw*C1hFY zunR(37Prate)+J+^YpM#7qF>qM>nl8owv^6)LO3i?h5K4$6NiJoh~8$w-=-$Od)=<6MnBz&ST z#nTq1_Az%nJ&m=4sUGbF+RK=}`ayYF#5^Y(>tuQLb?Y(4y1-5#uHDeM^Lj(9FK>?b zD^^c87-t2t)_cw{HrNW}qWEhAtf9u33ZA2IRwK@YX>(tO&Qr~@s1rEP7`G@-9m}ga zbPS9ikG_`jU43V?dUOIL9MzY->kFlIrWr65tZ&VI6sB;sG5M}Bb`4CG>3fDBGj^T% z$^3xEXf*US5o(~uA~@wv1P_`G%g=&un5ZyY92gnY=yCtU=xhpXH0V=)|i8-(V83mjNK1Y zqkD6lZtMZe8wDE}X5ZOcWRB6mw&u9X95u0?)OZjcGS-LqGsadMI|a7M*u%zRV0UVE z5*{&jD)AZ2Vomr*TQUA>VPB3b3_gaV=7eVIm74B^$1QVz;!_z!T^Z|P{CG~|n67~( zJY#+Xh+kpsS!0^yGmSk5Q@sPVKwNF`1%sOT*BIMqnFqnHhpFN+^NS^Zqxo$z76+Sa ztQ@8e8O)Jo>{XafPG1K-52pG(Z&>D`#D{9C5-MPtH1QnwTenuiG{$Fgtc0m&-!XO; z@du2(XXOopHBpF0bepl^#Py#-Vjs4mevQM~9NcWZVY?N04pwWJ9KVFAG3Ro$G4_?Q z5wLcacZad_U`LtX*TzP|IvCq&EJ5er$>29Ym6OQP+1R(nM!~uo+hdtW!}NTk2G$un zpLnFP?~EnEcv$fIeQ)KBf%Onm|JR%2SmHP7GzmW%yMXvYV|$H_gN>IH;U{Ak5})8D z9?w1)WqU5-xY*c!^HaWL4lM&e!*aB`DPszULK`N9GArX`0YVr|ZJ5ZRiKqpp9ZY#u z*w418^fW(}^&3n}b}wU>5N|ZU-o_?r{{3z+%HX9qZZ{SU(}YXq7|m?cdVh-fT}FJe zu^3}&u>5onIMvt`^Aqc9Y%1{~tizo&{{4XTg!g;H&-Fsi(j2G5M;ePYb~&uk*7-P? zdTR#98!+|A5Mx&mKlD$a@i3jpOpYh;)BR(Z%G3C#aqQOJgD_kWP3O>=sGhTpT}fPL zEOw5uS;UtBXrt#`V^QVD5ov6s`CSdmdD<#YFvn|v&lnqJj@QCA85?cv zI@n9b&Nnt2_Oh`gD{l^L7VCn-So6D{_*F1&a5x|t3n`3OYF%9Vr#^xBC51VUD%ed-Tz;RKI!5a+T3XCzPwOwbtkmERGTE{h>w{gs4 z7Il}xw5Bi8p_xUSwQrbg#;+;8koAItIF1zc(HE_1va_JFZ_tiXFvWU`i%*S?2qQk2m&!F$deJg`ZGltbq6= zbqC==V}-;ogPn-3GIl@lq2%p}K6GfDR$|0kfve5&0oYmQ_^`1e*tJ@%2#*+hka$o0 zqR>ZSI+0Zzy^K9(eh>kK|j{2dk=g<@lm5Z8*O@VK!@iO;p? zh4sePz%q?JVeBzjma!6JYhgD9>-r-+X>c9!Y;%0dSTStAv8Rna4qISs159VUo+HKB zv*z~%Og(Wb$8*L?h!5uE`l8Q=P`@V9lNbBujy znag44T7f%^y$l;^emjkAhBXpb*k$Y$;s=cF4q^P&!Yv$n5FCu`F~?Vl2N?SfCcoD> z0*!rd>~&ZRtM~_FTVX-Q>W#et3pVznu{SmUe#TJPYp{a2RyKv7P>oY1M--DQ9{rcG zw}?NkfhDL6`Mu5Y1nf-oS7YxGe*|_G+5ppuRB@~k)A*~41mETOj#7rBzZ-jxc)c-o zn<{yqqlU=YXcJ6r*v9d)3L&V6vV?zRp+&m@2O2m}{(~`F(*sfgVxlWb8}gEspX%;C43l73|OU9?vBj|1MVG4&YC~ zNocq^eog!v*rjNMv7N-V224edG4>5{tpQ@)jO`-cvYT&pJr<@j{+45VCw>vS%TMFK zo8$BjzK6);E%P2&=`p;BM0*&kBmN>x;RIvf$>7?ZiJWNcd*V$IO7M72f~n#kIF^%l z7TVkV>WSxE3cQL)6j0szBgcDWz8XE*GVdk+rWF`tnSX+X?uQS%q{QdE?KoIR?T4j4d?37O+;ZeDd68j6aTe zhFN14S>7PnaAS9wAAdRVoNX-UZgXq}JjY-GtThGmrxVY)#vV2n3L9bU3Cr6WcAl~4 zVd}v)upexuZ-kuyYYVG4_8E*G%<+WD(XQD>Af~4szizd0{~e}oZ4a9Q)3e+k=64jV zulY5>T9U?Je?0w-1#lM1do*k!m+=bXEnt`){vPC+*pBhP52K|){vhP}CX6RsSdeAz z4EqkI&sL0~&$ z-LQ3CujXiP>{wV8*So?|#v)<+t-ua2o$+z7OJVDYcQn85ut_XZ&%inv%Q+r6)nI3X zTF+PEq$idx#!i4G;`cnPtFaSdUodnpz`~841p5G13X3q-6ZWC8V_@pRUN9b1a=cF` zk>=PNsHOEq9F8*<1=G?h*4=MRX;dq$N_+;25*e2p980!PmWvOtI^_Qp}S)Kg))R#o6Bmbou%w)w>v>j%5u*r_m;*B^F+v3|z*Yn^9-vD0Ac zg#o(%-fD25Ii3#Fb*?bT*g%-BX*DJehUqzjU$>jzVDlRU%QZF>rYj;Aw#3*l%Nqx~ z1IGEYKv?F%z&p+H9CI83%Qtqev7s=n8*0o5WAU(i%}~b`WP?e-lW^1n;}n=$JqC6*r}RGY=`fw$SlBtnX28@l7r;guyVCsl^RlOg zL*XizqWqcJ^O>;>SdJ>W2%ud;^bmP1P>jDkd+y}WbKo4yJOTS+j!$6M8 zI}uweo#;4%Sc=|%(Tvq|+f4>9!PfKISFm};Cc%E|=zDy+8Kz#i6xNTY zrrpGEF~3w;YaVF6haFm3FN0|n)f3Qs^P3F296#^;Utn+wa0iE;fNnK56imJ18j_+J6sV0Wyg<~S4fjIm|L(qPZR z^i;G0reR8lJ!gIe=65CRd1Ln*n*}R1_JFagU^yENK4=AI0LzT6Hpi=BFB*FUhUvM6 zUz?0QYV2CrOE5KX4NOIqRNt*ScrcdUjR4{Z;7ds_InL^R?A_p>4O?H&*)qZHLuX zq7e+}&CgD&ee2j!{~scLhp1AowuT>}?Xub$tMx(KZ6mL>T3@t1)^45E`l0Q$+Ip+? zN84w$4QQ$v4gLFlHo+~{Z~)=~t8GP7%?w0SgS(G^yR{pHwvek^UFGhu+F)s16Y4^B zr`0}2TV%EGto8}oVypRfS#b#B5-aXTQ{(y+?Q5&;vvxz#mRoJV)rO&cW3>ZlD)eV) z-&*aUwfh`xmDLWRF>v{uL1v8=kK4#2FkEM~lQ!~5v<+4}W$i|xZL-=ARvV4B9Zi2f zqG@y-gSOM!T}0CeJr-?u3iUt!uGz@r5I?YyZ&>XMwBBgCuD^w*ni-Ea&}z5QbUY`Z z4YJx@G=c$!pUwBhL3FIFVRL@?XlISppCKG z6EqcOD%v=k-gBGYG_>(n^KeC_Jbcp;8zMd@5Cc(rHv_Gu)ncJ3!I@~StQL$$Fklux z9jz8(wb^LzSuL*B=Ad=9T0E=GMeCA+b-oR7b( z)skDg1!(20=KcrQg=p2Rrt3r~OA-!?lRtVi*gzAeu_O4*w>r z6|s@mqiwcY2^)6<+HR|rwAx0rJyz4dlqKjqq}pe-(jnw8aWg;rtysnyZb3U>wX$d` z^;WbAXt8niC{+dAhJU)%bb}(h?PxQsR^6tz18srTYFKTjuKyNVv8ENj!*H?HYFTX; z+7hePw%TsArB765H9ZrS-9EGd(scZr+64FGA7%}kS?vJY zXI5)&weQh}TdjrF4x){)nr`O^1{~sNq}5uXDdWRvbiSh2k|Jc`!T zYMpF?#}vtGovn5pt*zC%SnUK_d#iP|+DWtyR_kW9Q@Z}^XvOY`D&c7iE20JC_Ciy4 z`T_s5Wd4T=y=~+(XwR+oq1Apw(;S`<+>fkw7Qe@8eb97B&Y{J0G>(6NYj_?p$Qt^r zb^$Gx)drxcFc;ChRvTpPenJbj+F&%DbS|OAwc1b{_h+>DR{KnG)&DLdCbZ(`*6<2i zVylg?+EujVRvV?r#QlYz6lnSzZSAh1g;~3?Xgb?nM@wz(Cfm3-(0pmF;g?ptiFliV zAujF|tKGtX$7)lp_AA=&R-0zE-_ZWB+H^Ek^=-8KR-0w*?w~#JYrcTlR{S0Dp*5U? zrmFq}?TOXCvUYdTo?2}lnyUI9+B2)^KQk-teKbw-(vB>(aUb9hwAwPqj{1Ly7}JW& zZRAI2Ay!*~rYd}lR>@wPt+#eh(DZyKG44h*oPekNydB^FfN2w&YT-|`H_`OB)!IEn ztLQ^aif5Zm@Hu|{+tg&ZJFKC0CzaK9+5|ml>1~3$t(|7jWU$&EtHnUeXtljoi;0%S zYQB9o!63xj_KDW_)-V>@1FIdhT5L4!aS94}$ZB!WPQ~@_-C?VF(KPo%e@CpQ={)zj z!A(Qlqkhfj%;$M%#beemE{2cM^miOhRTvMgDOy@0pR#tE_oLxBBid~jEFa7;!HO&P|rsqG|F+6L<#E9y*IpCbtlA!6TR)6QMra43Nt#$!T1=O4& z-K6O6lGT!<>4rpqS8QC(BhmxRT;y?81C1i5M2uk#uUkV+E()^R4XdR>)045>M80V? zW+XdL!KB?nBN&jHpUl?oH#GGUO-#}ySw8IU+PFT=OiD;VfA_4I4oz3O5g6XLT6(mt zl)50=1FLBUlkT4N_t0t?(RBA*2)jpC%Y>#oVEsL|+8bz}>iLfhpI9+7qOpcg(Fg`) z;it3Jo>{xBXx~#9{XMr@Hnc@(Wk^pm(^RY3(Uw^)2AVeIK%0$LjzG+~9Dl+AIr*7m z#aPxb7g}vJ{l&IgZnWxFi(|DsXqsqHnFPIP$~Z6Dm$voCwRZW?G*>`&@vIi^Lu`sz z1u?!AnK14=FshE0z-sx?n6m9W`$=fE0)#tTEs@nC30H<%#7%6qf`qGBEeRUlfI|G} zJmIUuPcmy*7*S`px{%yzMbOkEq@}Q0Q8YbLc?&J2)rz5o6Rr=TRx6H{-)gC>Rst>3 zYGHBh^3LBr>@r&I%|MQSBSbxy$%Lq5S02MAX!^@y4J)8EvszZG>2X^Nt7WrVCA3yn z%Wl)t;ds|-xvZwcahMYp`%hy*BpgtcAKmA-qcGt%!D?uFup}+QYAhrO*rUk&Y0`q? z*5D_b%_P#=)kMpQ)|I#gtyT*ym(>cP>H0IEHexy(xv)*J4q67QX}Sc#fV%uVv1h>I zR(lKWsntrNDZP4VKUl4lwX2V&>K}+f8Ee-dkp17xX?8GTSt~Zg@G6@A%AslGYJ|1` zZ3uRitX*R?{RgK{(JEW*ZM05kL(!_B>2NkdTa5M@T6JsJ6m16D=equ?VZ~;M;}M5r zSkr3F(T1aqK&xf77HHq2>48LTG-c8ftu5MUw70BXE3|f2t7o-$(ArzAzBC>G)`*!A z#}K)JHGCIMZQ%>FhE{8XrnVrhk=5Fw=@^ekYizZ4XgbEy-nLqMv>Du_>aPhJ!GI3@ z_>{?X44YX)4Km6^nkF0K1iXi)OlA;hVYNuS@R zuj~KTR_taIL=1S>YTa#uy2Wl|wH{Xc8m+C>dZMY&(%M<=eKZw%Ia+(G^+HpjrFC#L z>i+}8qKNwIXbpR#X-{-V{GQc5L{lcxI$7-_G-aZ@gQiTRb+KAso8DHmu2$=( z^n4gf>}JLOHbLFhcDI_&5lT>6533EZ3GPAbX|;h$5Ibq_TWyd{Z!cOes|~j4N%MVR z#gA=*hY@>Q?Gu}zv=6N|#3pzI?IWvwY7>;!$7(~-s&a2}8m+I@hM`rnT0g($3-}CC zV}S1Q`dh=#(bV5{@8+}Ga5VLI-MbC2+6XlD_w#52(bN@3qN%^@ME0?@8-=F+{uA0K zRvV2rJ{iYfXSX3%9D}I-uKTo4tu_`-{av?dL(!DUI5c%xokBmic3+?su(w#lt=)Ju z?c7c5Mp?TFXxcg5Esb{f&LC>otNErPYVRH(PD0e)O+(Y(Jwlse4X2~2N_BTL)oL@)RD05< zS#2iTpWHL(CS$tQX5oKkwHa2Mjh0jMzw|fLigOU3a*ZDscb3)WDzepPTkR{f=4kP7 z=U8nXem#bXk2}|D^YLqfS_0g!thNBZ-UsM!p058We856Pz2!=VJ0C;U!Xo@Vt+oh_ zV8CL2R0}C^m)HcC;8!h3TWYnX_?r+n40oB;mf_EamKyhKG(GJ2T75e)rA~{x(i$$u zKZA?RbhzJI?Hl}h1fKzSmDN_@|J`1UuD04rG!-x#?i#Coi(dtlw$^H^tmeysXPp&S z0%7eS!YHRRoI;FG?XgC3D`I&*HE80!gZXJGI%9q65Y_;|Hr(<6VcZ=0F;MXNw z8QiVz_1{JU#~8WZ#NB2MH{m~Swe42hjMmm(`RuUT7PKXaNs+&uXaobc@-q@UU3%@Z z+BW>-Y}`F)I>g&`{Wso<`)z_dFq~ku1J-aSnz7m;o8Wh7^C+PHj-b&V19qV;vf3$| z-fpzTRy%Fe+k@6!n%e)5)^IPPE|GK{c-CtB@W-~1&slB1wX2DH-f9Q%Kcvg*>gfWS z_U?Q98b4a%Ub5Oj{E=w7IJtu6T>l*+pp13Van&Yx*lN1K_{C~Rtkwbdn$?b4?LFM< zRy$_3&bT+McHC+@_usVI30?mwW1Z)3S@EPb)Or3_tDUmi`?$YZ?X=Z;vqe)%xrB|6#?mRvdtP*J|girqk#>tDU#n$GG>ccEM^oJ3g@5MXL?P zeQ33x@aNxF}v=}=6mk8*|hBNwE!=KSKuTP`jpH{n!|1HJ}jdahfb_KuQWNK7< zZndlUH3n!<)2bu90l(na7$D7KwQKl&8fY}q1zPbseoaVNiW|dfH}Drn(+C#RYB%v$ zvRaVUZlP^N(_bvB{fegk{xxoFtNn&wJIr|F3y5RI+lZQatpUf2rWSk$zandJ(YiqG z$?sOv_z=%(f8g)O`~dyMx7uB_p;k*^wR>pC>{yY|(Ww9Xh`niq8Xyu`!w2|h(%&@} zB(~Z^{L5`uOk%Z1Xe+Fi6pdiOV}4elY0OBDrX6{LKPBmD%m}q{pW@HsvtlYM{)w2+ z4m4pl!DnbCtd_>wJx42PwX{}qazfJ}lnzZ9d-yp^EoczRXwwTsJ7+cD8#Zzb#Gh?- zX)%GSIwsl`t7$QTv>>#Bc8{~#1Y@BMM$@R49ZhGp*l2la>1y%0ZQM9$`K*@5ud)6Q zQRi~?g1pu+7_A`dbCf_)Fx_JEd%}`R;y{XjA)-HqW{aVmK8JMpUc@vji%d7Wqx$KD4%!>KY zlG?&Fw^}&b7i6N$T39Utzj}@`YiYIo_}7q$GHZpV+A4rwd#p5CTf0d7Yq3kN2=7|) zLTUfiWba;PZxQePA_a&P2?e1|7>Pg!=m_sYC+G}apc};JnNI?^YRZ@K<}5#(CvaLg zuZ8kj7(Wl@!(nFP9)shs9}dFjFdRmJ7RQf*(J+Sp_~Q)=nvC~Lv%Hx1gZAt2u7?e< z5jMeQ*aBN&8*GOiuoJ$6J+K!Jv!h4gC>#SVd_Msv;S`*PGw>tqHzSIBQ}9(kZ^Pu9 z2ycchuobq!_IM^wac^3mYE%{27TQ63=m76QC+G}ap&RIE>gefcshO!+sTrw>s7a_T ztL=VIg=_fHaHHWx1E|JEjfEQfG}dWsV=ODHRrJpIrE^UR&?!PERh>kGAp~^t)QPbd zja`fBm%-Pt9KJCJN_fNgIy&_V8_b;&-m1Pscn`x7(8Y!>E_|R{Z{2DSg2A9$Y~51p z4qA82!{9T}U9s+hN5DwX-S22938kPkyhY=!2lb%=wC3WbE$|N7=PbSNNZ>tqhwFmY z&<3$pNP!*~} zzF0Jta6A!^9}1W*g@FtXp3Q!rUKvk#))u9H| zgxYX{P8dj6jUQ-MmGV}I)HD-4^V0LI51==E2p>To=nJ}X9|(itWB9~`m-aUBb;sKS zdcs>Yqk2#u8bM=t8?>6G88n9$&=TrET__As=*v&x96W&T&;xqH`%r@_tO>QCHq?c; zfOX)$fO}Nkeb8cWt?m8+w4S>h2fRAy!C-6nm~)|*#Nt9ch!0J9E7=U1!+l1I2k;QI z-1;#*fv4~%JcH+;rO(@Nx5Ey7&toSZt-D?VIY}rN za1nljOYk#XhAW_D+P}axNX$qV4`z_hOqdF$AwQE~IE;XiFbYP)7#Is$o1LAAIiNiL zl28gtLm4Os@gV^ug2a%-^egKP^M&C}4O+^b7Scfm$OxGrGh~6R@Bt0$Ls&#v7lT%M zYi0KeSP83PEof!8R&s9yt=`sZZ7sys!t0TQwXj+XsmFwJbjRWu2U;3#EsG z7DsC_bWhL%Xf1f|3;m!jXkD|`G52ELqSh%7fuV4ejE=zpI0#ystfk0WdaR|!T3W27 z#FMpJS4)MpG+0Z4hrv(N2mYSW1hfpeJ~V(vkQ9@&JoqB~1ef44`~uhDCOm}4&=%@~mJXj|=)DM9GJFsYL1X&7 zmJ2t9HS~oI&<>hIC{%@#5FfI@Z{Ul^1#o% zrG@Rx-r(Hp&e#md2f>Hd-3iAJ;+%O-+-PD&Hz1O)Dy+YFcqf3bST2GRSHUj zzOzscI2;7&>Gr6ThIdJ#xRTBD@5{oJy!r4a}sL^ zTGm?*N;7XFr1|bS#g;o=nKe>CnxC2Oe@y3K&>29+6$v_kVNe8VWZ62661um-dM(?joAh8sz|f}q8g&)^|EhCkp2e2L8z*hJ=8 z$+S8CickqELlvl+sx_5_r#jStnotXBLmgnu3m5|q$}!@n!kd|%01&^L^O_!)c-TD+;nnp&K>7_|0MYb~{ovIudCLUAYo@zD}M zLP!ih&=F6A)>3Neq?Ss41zH}d<&awbsO66NL8}>`qWzhiU3rd2OBuCHQOgpw3{k5N zwaQSd3M+tC4QiF(EDAFUJ_A;YI}5|L5L}A|wKy;thq5JZD|iRmKwBsUrJ)RHaiA6h zMnF!TK62qn5BV6$t3xp;4kaNKgh6W#X??iN_;VGm!FAArzngFiwlf;*0Z(lT@e@Zz z3;Hg@5$FJI&=$a07zba#&$?t=gr^17AB53E-20FNJv;o2{{rX*uU_6Rq2p^t+GV&3 z-Ju8c1Wk?n0D40|=ntBqsF{YtK(h-!hvA?JgqkoIq#1%^Oi)#CI-ln8O@ry631oVz zt?4xtsa<`!=qCIQkKqaEqhx(SpH$lg`aYBvA|8R`a0)iTW*815VH`|^N$@2Mfhp|w zRDJhh8XkQZE;XcwJA}20u_LsD*;G_@3Zzdeq=B?>7>c14g?tbWNud$#U+V#zKvQT2 z&7mc{1Fhj*(7M0&(9v6yVFXVn=nP$;8)$8x*79lXUN86ndV`kd_JO|8AAB$Xw4P7P z_XeA8)wqY!;yEpr8)?Q@qmH#yO>1ejJnc)E0&hWms0me|8bm@-&^l_Zo7OsMt)&lv zxDXHGLjp($89^V0(1##)fj<7Q2R;RTd*Npou6JuA@Qj2p&oT#g+O%xCa8WN^^DU(=i4w}^Ze!xG4coCh=;vli*= zJ2dGbAs3-qYn&J~vr-czzXvV#(b67Gj?~JoMW9KMngqE5G{-R}XkKF|>1D#r0;@oa zuUKrwl3+Z^*r<7lF+p<-HGgmzjD%4z7RJK_(3HW>aGch08h(KDZ~-=grV8$a?_dsv zp9{6{Yj$6C&=(-|35i6oU!R0IfPuc0G8BfvR@e?Zz`!J!44UGnPXg)n{#7^$$KZR| z19c$+3V`Of)#OOGr8#$j_u(WhtTIpaYQS4i4;pAnNJD50nun%&XU#!#%`~^HHE2Fr z8)ysdpgnYej_@9I0?jYeG_lh7%YvqdX_l8}b!irtW^HMfmL^+ufv%t_LOxA~(L{!9 zl=f%ZViJzO<|l6l%}G8(8#@aT>^ZOSoiDQG#+{2@IOK&tX)i}PB*)++tfp%9)_ptf z4)_v(97siaS`fDf_QE4A3D-;QKj0_$jD(lKLRbWgK@(T@!JFWt8VA5&(7Q{$tNfiE z*v+1|fR>bz(^Pcec@XQwhC6K|qAHp3RvvaUBTU)mp0*PGw$sOx>xcOUryJcLK^7@ojW_!FMNbI=FzJP-)|D8HK6 zdUkdi%!1jVS6knJUT3X@Zy^RSJG0BfU+! z40?lf74DOu-W;{0Y1E~%wB+$Bm9>j)U&C@(0MlS1jE8nmmmPQ;w;5D}nS}L@W@S>2 z)VE}|3Rc4!&`X(L;2NBVpWtUW3+q5HWAx6gA9RO$kO1zI;62c*kjL0O1AT7L12G{U zB!r}p0#bodHGT#oVG|Lzz)C2~#&VDi8bS!#4Qy`1X4nGTVF&Dnz0i{ zrxydcArIt*d~gTb@8J;m8WTZp3c}eC0s0oMzJ052-F^?VK%bu-3sc}MXP0;Baa#G) zl8*E)w1=M18>(?=^kGkZ$Wu!jG_mJTcm~fQfGb`P#DJKA0p{)c-cWA{fw;gw5~%OZ z9{kXFcnt>ib64ngfI@i zv+ywuo^+4_GD0S31P!19RD|zo6$ha-etkV57i7Vo1B&4<4*HJRn@|A)zypC06Z#W( z5R{>SnIS9aLs|=bly(v5Ygzg_mcE9yk>;@(G%-Q*5cK+2^9=OLSMvxoML<&mGzH)k zXbQ*=&=1~$X`p#1nq83}3P3$*02QGd42R;7rX)YvVG8Z>Q_yVt43G)_G)o$J>n3}| z{yc^!a0<@AJ0{KB-nzb;cxypzxJv7d!9mj(rSt)*^Kb$5;i%qJct>zuf8~=s_4jJs zH>R=Hg&3d@M-`xp7lakGI(-XjEd+4z?}pNO+ywPer?;UA+@?u>5Bh#nDk^z5?he=j zTcJ;oN&B|9&P#<4BZIjV{wvTonY5C17Pif3jk(|nVGnL^x3BLhvLle+) z%vSIYIQ^3RoyDP*5zY}opVd18M0~s-XYchjlVp$_+^{}ek`9(r-L0TC zl%;!>gEyf(s1LGi(--g~)xI6p!Wht$`?sJz=#XTF9n{h;*bkdw0t|=I&<6@(6PM#1 z2rq0Do_pd?GVi~+d?O4q;Eb2avVJb;($IF(v}2U;=TiF8=5|)_OX{% zewjvh1unojP`8YPVECGh^+}Emps!r$I}*D<--OUNAoR@#*%zh<=yMI(Y1C=s@skeH z!?HLgZF6ry&ni>9InSHdBk3at8$lm7*bG}>D{O-upbr^*2fJVo+^2&)gr4~Ioq~a& z?-1y_1H)ky=#v5ZL_j^zYX6p?)%;q${~l;{eiyCA*XsM^6u2O+R^4lry;jv%1Ff2` z30f4dMesR6!^L4T*J5`qZr56NtzFkz_3Lm0Zo&a7?i*Y!FV}K%Eg#o%@jb8)_QQeX z9GNkA#(|c3YgxCJaW8;{uo$!oTdS_SK@ZSsYpt%GO5gjK z*7ONZTCpdJ4*2BAH@HH%l1uz@tz^i+h7R}`g_F4=5CV&?5Y2BW7M7YCX5^AqE z!<=TDc7TqcOA<|m?E;m-SB0NjMEV7;!F7l}>S#1-h!zg{ArdB|^#g6wu=j`|?J-2q zS-*gG=$#q8R2Usr4REL6uBzjy1$Cf4XbD_PIELW~I08q(9qM!y za+pkyz(Mpwuow108Mc*!H=zWSgyK*HazIYV1$iI~WYi6ICOj!26cR#Whzs!`06g%V z%>IOD@DT37UAP0k!ZA1nr{N47g@bSiwD(^#-fHc|Q78oSLF+CS`uNdW3(bvQ32Q)W zDyG0Rm=0P`F&oB#vnUK#%Onm%aVP;=@TCP_TF})2Izk4>2$|pw&@zfFkQK5)GVmqm zrwhYESBPH4ud6P1&u22w&Vs+%`^jwk5+;Cwze(5U90xbyze@0L68N{{tKIx-IvoG# z4dR~)QtY*f{KwLGsSvttf3;QqQz2eT;J+B(_jkwnuMS65+DbKeZHuNo{RbMf?=M?) zVKOKPh2WoBa(-+w!+*R$9RGi}i~Um}YSPzhflg@AN6^}w>D-IIQuvEOaVP;X_05GG zkO=f>swj;2fdPWNO$w zj{6Pa74R+eAgo11y%g~At&SojpGy{8`1@~F) zJ6wgQHHKFYayLUTv^?%;$mCLRbo)6V`&A zQIH11wBVk>g7C+ILeaE0@Mnf3kPNQS2Cl+oxCYljnJ?vkzIs#mbRt|zC+P{jpf@Ni zeGIOMzD;Kat&-6wUl!b=xb9R2*lFN~|ITHb&&kL&3ZY!C|G(zD>LX{-+vGHwpW3pUfYzI6eaV|p z9x8#FklL=U;_gSYRVPy>&QcK)pAWK~3GLt%wX1L^TGFRn9}NTFRs!4L12*=CKCp!F zWS9zC>hU$&DoBn$C8UNoAUEUz{#i`Gk0hqm6k0to7+WaCASH9(?558vR$@ zyc{mApa=&IL%NTxM>VS^-P%;G>FQAnC~Co0edI({qX^L-R7ewAEuqyBEnolzoeWpt z0$hS4;P&9d_^VJ8T9?otYJt`xX#IiK8zh1!r1KPHtMvxg^(tf{0j(AI7_>fM2z(Bj z?yqjB2ICGt^6w+g2G|Ii{;%o&aY2b*hgWSkv{Dh)_*6Astn@!e4-DRI}|1FoY#zn;M=9ubK7hKylV5 z2?z<8-;0C7U!|*>S)awHrz){Qldk81=CtVX%tp|Y&^a&*ia|k$ga`14e4jbeFM@%3V4nd3;X2w6Tw}ktz3!8e}B4+=O@tp$%L4zQ+4$Q=l1=Ga0cSj zdVat?1t-D4Y|u2t$uJQn!3j7H^FiB>!BIE@Ux937dl(MF_b?p}ztHpkf^T6Zd;<&NOK=N36Tb>E z1*XQNo2YauwH6B5QaOcEVcb2^HrdUAdEnYhbN5KL3cGQnF96lja$s)*7QxrB43@$Y zGj*CbwojW>9wBqNiXy*TUKLICpd??nKkoL|#gV2WE57_Xs5(g66%Gb(-|;QHMo z?I+3HGI8Epl(p0?rF5qHy+y_=u36*>s=C^1D=i`a)NO=0Oe@=9El z8eNxPx(cd{bkGjLVo)I!SFX}@xtsX=Zv;6uCz5Cg`19EL^>x z%K^p7Nu^M(Dv92WC}-tZ4EHzFX(kg@0vJIOLIQ{n@xTKaF;S)dNpwYdiu(i}!$Wug zxyj-_?mf_mvtmCb)a-`)jBjBYyn!4&uIyI`J!w8L{r;V=W|OQNM@S|D~=M8 z9s^B%B6`Md%TgLK*{+dNO*JllHQ^B4V7)TiNMI*y1`QxVXc_^$xN#sh!~!L*43&ud za@Dq8&Gfn5?(Wp9VHIp5NcSZr;lyD7o$YfS$MI}Pf;NwuO@=Ei9j=;|K0XnKKP8;N zucW3$**J$ee7P~!;>0}W z;v8?1cm)U^APr5E%Wph$z4<)_R!7eDHuv}nvq>l7LQoWnz)aA5>S-_)G~;$Cdr~swGwRP>jAmOnfyE1mWY=j$DdahsT%3nH&k^0rd+(g`r+=Ns(H<6b3 zn}W7!6~Wt}?QVf;;#Ve0OY5Xmc%`THQld80f(Ggi^`RcT1$ALEdomU3;4eW2aXD%Y z@t?=v7`Ku2E8GHqb7*GWR(4x-yN+n>-~#q-aNFCk3hrymkJd3euZ4 zjWC|=t8h2K7x=XbU=(Ok+Hlugz37(o6xLW&k<5^5!9Bnc$_T~&4%a%!|-ePY#&k!?dvA0KXR4L5T z{@%t;;y(2h)%XnS>hm8nV{zz)8RX8r{QW&>`2hOmxiEk1C_xo4SGOR z3RnNMwj^!|C=NxT2o#2bnqV7=rvT)GybvGsZ&l;s#sN*a)W2iZ|A+krG*80|S~_!| zf@n&o-ap5HXM`WaBX|f8U=llb4|gK&@3?p15}bpL>Vb3d%z@eP0j1Vhv>w*MJXiy= z(U^0UAyArZzJld2AEY}&F8;4!K{RbCesv;Ui!8>!2o{1aFc!tcv&49o(cqRv+orU1 z6`^>FqidJdxGO*xBj4aEALXn3WiLBzSKc~>DDJoTS3(};g{#-c(aV>O@GA7R8VjTE zjP7zauwfe+n?NlMOfEI^4EKcX9s!w>sxxr^ssjDzN;jou}}`hIKf!cYhMr*qW2V*TV0i zlLWx~?9g+c$Ds^pfrn{=IMpw0>bLXl+AWwv~cT z93s~?9%1>HkeNt~|3hp)fcHVG_ICk7i{kBW3QE)-Aak z4rQCWFen9n%@Wcqj#sy9+pC%zmaBB!d=&O6AW?yy94~jHTop>Xh#r<-d*v3&jT7Bg z(@GRy^TxDYwXAt$u8nnlFEW&|mS)RX)#hdzJyJfqUE8#DTg$fdMAIVhM^DF1!)E5B zT?88)+CmT+i{q~|^aPNR`~}${|2*1*+(^RG-9(Dn?b-ho!2g*V+uC>Xar1&qM$ zfZGB$44OkTSc{Fmhtm{)WvB!dp#qeLH^J8!qlQox+<`?U(;6wQuCIYRkiwP2e+a!2 zZUt~{RB&CMmq*hhr#EpcLT%7cUIn)n)PSzo6=#Q&VD}wqRgV==!U#u@%-L^wYoM~a<;%uSmV>mA3{rvCXUJwS zybU@kec&VL1@D8NDyouYr-@?rt)CxL+p=EF7KurvxvmPtt{YF(9r&!4RECJCCt<}qf^8m^3InRsixVe!7S5o9#i z4BUW`s%3(yzSf&JgQ{A|X{%h7;w8T+^O-k{tL(K{`&LEUs?)P;sWCtUj=o)_aYo~c z4)8DN)k#$cc^wqSpUdV}2=_AD&u|HTf{Sng#*o%17zsMQ$KZS{CN6yD&Fwo!KnGL@ zLRka(9!^<`f~5?~Z?AoQ%~5Z(*cFWaRckZByj2(Grub#tqr2P)XQ!9CsV;ee_3g z2hyO)<{^IRzr!DJ7w*AloR)e7*5sr~K#zc*;1cV5p%uTE)jozBvmNbyfp+xD3+Xr-dQr}DrO$jH$)$+l3pyh;ZNLH8E z&FJ6p2|pzZJsnegEj5%b8*P(aQd}(oL-RQ+c6EMFiI4)CW2^`|KPq#bA2nGz4QOh< zE;4j6p}|D1w#)U9VS3zjHY_a@WP~}y8^ZR?_}_pmkQK`6GCMORQ%N-vl|k%4xHPUZ z*OxCeGL-;j)P!xS3Ed*?!0#T})nuRos`(ek)yZ52pI4$PUU>nYjrlj%-|A87Z6#RCBKG7D|OthHhT6%>zC+11+(W z7!Fz38!gjRyQ~YfAd;}Y^`Y8uYryU7DzxHidk?BPx{Y>B+gu9cS8Z!m#z$PSImYU} zN^}FRI*O`O5sCp`=OZK+aP1x0b2kI`y0DBHxRpl+jWO3XswR&pnTGhwkyiuUs!#>q zCfo#c$ylGTj{Z*a(B)!1!lwu;ejRMyB3u`ghT=EGZ3JbZI$_0?tu95Y#ipd?Oqy+6 z=GA6f9jFDG<6i@$2{)edR6M1vvNmDc+aP~gTwSuZ#8sDVi~9z4i9r|k#jIOAnwBrh z@4t0%jk*!J3>z;MhkMKw6Tddt?+^%d-Kw7Kt z_l0?igS!k?z%ZDrKGh%3Qdk0`VG%5Z1uze0!AzJA1xau!?iBb2zJ$+VGE9PrU|ho+OZW~ZzbQ|I)_@i%k{W=WFPx%nf=NNrUKnd#} z`9IizUfX_FlGeZ1J*ofKH6|6tEvfcSiOsWn`|<$d-<$vR_@Rtm9Y8jrZG=|`k_~7o z>>7wZpsXgWbmmdOwbA_RqWRZbzZ&>F?A-R@Q{#}J1Qfyb>lj5(tdwnN`_VP5?#0~$ z@4;@^1>eCA*aq7{=YT9WUL>yj0B9chX>y4KhJS$N=df9i)XckQ$OfQb+_5;6Fie{IzHEqDu7N_t;W}@$vINSpc7^ z<9}@Lb-VdLm^mXO$6qbF3B5oKVjq4DLTcd(tAVM9T%vva1RrwU{R1|qL5J-WuHv77 zqi`4$_kG+$@I9zu%cec_fP;`5P4SoLDn=WRK=jD**(f7ruI)-xi79*>_ZT<{5Pn@; zWiC6Vp*OIKr!-DRi>HVEK8ecg92?Gp_V`EKdH+2 ze`|31NyF}|Bh&>nb<6X({!z+5q+Wa_{8!s_tV_{Qb!aaW=VzG5;Z$C)p1gb-=l|1Z zFXFy>@_O~8_3GL6DsirW++SmJC4U{yO>keTUBiC^q`UV}+9qzH{RX!oC=L%CaAV=d z0Db4@3E{`^5dMI>a36j*o`c>boNN=ymGG~JH8D3MoN zwdKB3sx%5}Uqe9q>h7byY2-J_AX#QAsLT{Q7}qVR-z=VLMJCQZMD22@TO8%$WQ>iz zmh?2rrd}SxdYYjx-sFN5kQtIgdPoOpAT1<=)DQ+qAr*u|N=OKaL0@7@;Ouq@B`evj zLxum!uWXgPl2x1}P7)dZDT(BcS*XkuGkWUMk~y0*|6Rr}GIdN7$yB?pU3NA(a+<$Q z!7<4$lT5^V1Jt;bLPp2{FEYWOD9T)Q6}`xc9zCoi{kvdl9recaX$4Pq$O`%rmTRl< zJaToz@@GQ};&?~bTz}42?3K@}WliDaHo`h-oLlyi<@mI#pK)3>Kd3Y1bLSe2m>Qne~ z_;rxlifklNzTLYo7b@oUD|eEjo3!&5o5uQWw% zE<#T#bSJ_D%DaHDJF$H+e$5hIgsX|{nrf^W#^1qaSctwBcROqW&BTvB>)4%5EZ3cB ztn@U^S!sH8cM*ruXyJwuT!N?>-74WU{3^YBd9V_{F377>`77{$1F~C=tDX27auCkO zj${GNe_xHJIiIU=%Rmw0pJm%x{OiE^O8y2LIDoq!_Q7VTO=6n)w+Y;NbldUk{liw= zEwBydlaB5{_Tt|IyCDxXx(oL^*axX6j1@tN=-$V@57ISt z8zPi|!$i{izeBhO;d?j=H{d#4gJ0k(T!G7G>JQ$EJ~e?9(bpZ5K~hKp`m(AQWUH}KUscuDRrRIS z5bNrTtNI#bLP)&2`dRNNk7v9&d(PVugGu0`Q0ojU}2^5w~wCxXoNf2CLe zH=y#+ie2vIn{gu0qkoS?EeuE;!|ear8xfc$#)Qk>h_I{}s{Bp*HtZYv+41vE9{IGp z5vKZOZ*`B)e0kZMKQKm2bLg@+Wu%6V#MqxZw|VNKh>b;Jl4XRmzgj1yE$!D9pI;zW zrz=rgwwf_9f|I6qtd3f%t(V8lPm-eIgD9)ZruG%8_krn%lUf~=6?y|^E$H9j*EpGv zMA@YYGBd7Fl6=-8PNI@I*Lc3zA7xRFEv)t%use1wQ{k6R+oHBKFfC)cCFy9bwgvoJ zG0U~U(ot4Eli?~Q8E1;&glS5yQfrm`sW(Bor1zuj)|)OEL~16flimFR>H5y8H!jLR zQ-N7OIbd2?zI8{hz0){q%R>_$6r8lEQzd%1p%xb}#hcfrE$y`{%1W=Pv}I9+_D#E& z$~q-#OHmW@3$;`W12urdGZUuSk?mT|D1&VAOzB^|DLwhk`NhGZru#46RRv?k_cwtP z@r!g$oUCf)Ku>tMztJbKTl&BAY3>|pzvA8nmFqhFwB z2Z{=O)*P1I9dkp@GZS=+eQRfy7o@0pI|m1aZKN|OYL|%z6Z9Wi%_Q{bgO=)o-H5Akwn5q z@J3yYzeVMDLNX4k_k)v})6(XcZe8i-48#Qj<}!k+4_9TR}c~b`F%xtpU^G0}%n@0D% z13k~pnS0cEa8^#I6yePTU(f3~;?QRlfdixlQQjoDPo_;wx97o0O@sU1bYVHO`43R= z)QdlS9Q#s!vXn7dE-+*6lcnP%HJk6#H_MxSed6l;c%Chh)3W>XiP%Gf6n7=kS27rX>cRqo$7>=Zujgwtv$D zvkWcrb>|JgK^g{Qol%CyT->gCqN2SuR{FQ(Gk+@E*PS-}1`SO3L-Olrs>*Rr4{o;O zj1MjHbtegbT&?!d*$+nh;jSq6P9ZE{amfkCH{5b%SSb;2iq9Mo)Tla>GC-Z zf1G*$DeDxcnVC<$8T_Y$P@F`OAxUF#k)$)_0}Q+yi*ON45+_a0KPl)1lkQG%@|f)3 zHPaj+@-EwzMQGy0@4b~N!7+-N-`_*474;9Vwfeue;m74G7Dx4*1}2e*9oYIO#W`m# z{>f0X+~j@c&H09#+aAsW+WFf%`gNUha{p1{hEpo`@{JOv|1)pO)E{!6qmiLO?PJxO z{=Vm;Ed2d4sHDl0C^%(c;*w@RmY!+jYdcp_?9*1JEvc{%ANOgut>G7Q13e2-oNJQt z(}P39ZWB_Xts8CMHZ8%7pg_+V6b;DFO!en9|FBa2W)&WM=SGeSNn-|jVxy>mIoA%M z)aTYUj7Q6!GsGMoUnV4ZOb*O6bCAeRI5g~y(*A0>*}8GPv&FZ(Nx=*I3!Zm;8p`mR zvr<8Sg=$BXib^_-Y;`%_*fZr!g$L7hk}K#Ru>9L!lS>imk2=^t{%CMp&u&x9<0NK_ znA>zAT<}O)e;1omZ_dI=`x?JPvJvz#_Td~`R5gn}pE+buya%nL>f60~T5L{wg7bSC znA(LyLd_e2!Gk=#%$Qr$#`?hEbe?_YOdvU)Hk-%BbsA6?lc8=%(%8<`&l?p@ju^oa zo~fp34EFAIV=7IbR_9!uYfN2)oqtSC8q3-8z??^V?U3q^`nn<2za^i^9g|#NH>COv zoGWn+seQ2X52-fSab_9PYe!Un)Ypxu{w)X1pUU-hBdXuPx$JgE)M`P&uOCtUhDD7p zh;kh>TPMY}<*H>?1O-?3>@uEMY&mbja8f^F9%)97;nY^%0i|y)=aSkvL2`7%YMENG zxB@O>+Q*`GR5ynejn3Ye%kef7;wCo9V+YsQ$vWY)kYqvmBLmXJFhgT=#MYVhvDu3~ zwfya|O!HC|V=nsgHS_;#R<+IcD}a1 zGo2oq3Uk?5(ligI z?bNgeyOz%A6*JBUjiQohVYkf9U2N8^EB$IlZRuu~5H)NN21!VJL7|FS%C#(<*cwnB zD1!%V(EwQG zRF;ty|1jU$Ees@a8=3wg>|lmQ{u61~pqUwZ2Q0}G6}OOCPU4;#W?x8fPEQ*%CNZHl zCQ)35i5DS>!EHz*HMyjJ+YE{uoP(`P;?jIyGRou{?O`geuG?p)9kL^Rk7F7Ajeu** zaBisLk;MhmF{Xkd(HCS&~IHjx9G`5XI+TAi;Zy*~SlV_!t0WP#@XQL=Kg zw!aLX9dj>RxR%kDvSuqum=p1X3)tks{>eDg=jdrG1 z5@#rzIgQb(Z#BxzLQ~7Q}d?qZyr?>MCTqm1cBqU^2A8>1nzZC)xzlvNrD@Abi)QvikFg@<Psb3yf-0@+#S(`Pude}17 z5u{)gxb)G0Ss&(Y&^@XPyi`Le|E90_TSnNz?*5d9j;^(I%(mfiqEfPB(91h!g0pj$ zwR;q`7pi$izo-VH!OU#Q9<}!`@6O+p>!ob%Ap81Sb}Ri=qul0f2`aFQYCprd&I~56RLATjpNU&m0n-#Urs*$;V-Blrw?cBbdHzP zq1}=Hm1bl6g>xS8S5AbdwyDW^G4SusrvK&{G`4eM{BJ*X{=a^{{Fkcz-|GA?U4Hl* ztf!jkqEWw!Nt8HlQnNZTIGy@q!rH+R&MCldh!vq1l6Ot1g3g6Y4>`-syn>8YZ?yB@ z=={;++Jt5AUi*SEMz2yiVV5!q3egu|%E6ydGgBrJ&(i$qIr;cA``nyY40~j!neY~F zAGZV5Uw6=dG$>-ii@c_T+PwbqdiH;NqkDFG`Rdiy+do#jwR_F5{eNqywhjBG0{X8& zLp}bhdw2Yfc3yt@4CcS2N^YK%;!5eIgxsjQ1+YW^zYI)840lkWoDsNrQe*B`pEm8> zHzhUG>vN~XXSNn%;M_dvf{Xhm=U%DSLhi$Iwl_1%(6f0-^cL})JEKsKb5oSmbZJ2J zQRc9sJ9kE*{wvc^c5q-ba=T{2TjMx)M4^HHF+HWHoN3t=wVAn7lO&z%*HDjh(H`o* zj&o|J65Gz0Zk2JkVyof5jvig*#eL6A5(Q@;Em=p(-ug?sqHUMck;}cWiC-*$VSlky{LZQ{ioJ!;m2J zeOs=H?2(|UQK7EcfABUZf7oAFk3B@rtxobFZrY;Nmnz18x0Bzk5ciP2Wbahq8~!`5 z7dJZ2zS_p&T>XVctHkK-z#d$)v`%oUzq+k?xm+(*y+3WYvR~AY|K>WSSyO{bv^Mrw zM_qcVQ91d#(O+q*W^`>XVQuMcP5Il;D~2yOP-R-c)|6Xu(OsurU%KOXeV=-5_i?+6 z?Gi7pft~&xwWHKgHQVdmI`Qv~e7kw@CT|;t3|j(!mwa)2U+%}&$ldy)V9ubQES55T?IrmCj>A#o zu%2Y{DD7WHJ==JlTU6)$ThuVQ z?kP3D|GqO>IyL;yKlZ=XxVb7gv8QUMfT(WhTuHkf%(?>zHaCFw-AzN&r9Cg|+F+np z;Hws&D*SQZ^g9AQc6w1jPqrjvOPyJJSC8vhxl7cRkIhD+dM25Hv*R)gN3XD%!F5O4 z5);yacXR7(s#k7T{i#OwdhMg)?lz@51n2agvgPV#rq78RYKC{GjBTw$L7)n=@c!`>Ymlr4e(-761%d6_q=8%6Uh@V|y=bGpP=!9Ox;Whv_C5 zq*`EhbmInMl)00RbLd2qs5`q%+w6tokL_7umiGuwmQJtu_WvLDz5+bTWLbOWBSB^Y zB#?w8Geoe(i4eiV0*kvZE(gww1>Zy7DV?S|>Jjf^-mHvUOfy8-k9wdsbl zR1v?xY><6 zsX??4z?K*QI0B&Owl~4{1w4orU+aR z$A6Hz!qlV}`mlv6XF|@%b zd2OedKA0AA9JE~9sbF9HS%%v61-EMMkir-9zUlQw=~MCvxC(Q*P4fYOaJC(N(LXhc z7bsOfQ!e{UyPyq);8U%B(AaaR(R=hBKpab#A30ib^2M7KU{31(z^HzlO2zrFXsX8_ zA*kZW@*9oskE3&2!^SBvdL`hqWn z9Q$yH?w<<`nEMzIUw7o)b{d7y`6`1=o_PMZ!(ag4rjdg&{-$FT zx)p>c%zSV*x*n7I_$PchbjsuyuYCC#bH zFpORH6V!JYLNJ=68b=CETb%RByL&AjYsQ@59RGKY)&S7hgpLg}4V0`xfQFnGJjr6i zF-3PcZUKSC%+ofT@%c)|5ojjWDQdp}&X3C}+Pe^6pHoz2gxG~MuK-j5dNK>2U#QbM ze5%u2uG*L)QPtRv{HDO-_Bkc_95K-@)x`qQEIQybdS>|P7{sMfK z(?kyF+<|IrwXP07Ydm&fQK+rj37S6=0Ew_21Hn#~qDG>nD-%r~lxY;y%yBBld=x^1 zJ=8Q*A-f03X$&wu zq+IxDc4xv5o87b=XQ=TQu&W9UJPbMLPBZxvP6N;4vxQFaUk~Ue*Kj*apBc0$Wjumk zf1`Y3fo=l%?ZIE0ad#~~cc~wK1RGSoN*7ue`?s4h;a>yC>9Ab(qrZT~1rgKvWhTt` z(Eq&LxEI^#g3j;h@|o~=>57l#g{>ZCfrRM75pTF@3`!CUK;~Qs*m^NB0}y8#M?@_C z*QM4ySLDqqf9=(EDYzUxDdDL8BrLh&%tNJ3r0i)Kq9}S2hE7c)kwxU3!Vl&lAX60O zos6PwRC+ReEG5SZlRaki4@6o{Mm5zey>Cbzm^$m8!X+c~{RSFYF+ny}Ce~=Wxd9R* zl5DV!2{9;z`;saKLw2AXpwTqPXtGWPK|)xZQwW~?6irq5w<)x4AC%spso?EwnmrYo zROrEq#xU{0BaSYhnucnAxUbzQ<<}BT|}fAo;yQFVt)wL;L0ckJ3U!xu(N^lXYqRaFCye zsm92S0kR@r0^L4okiyqL57L+KbvjSyg3PtAyX^Q0G)CRv> zCZi%-s72v(V(h&e7Z)f+io`scI}5BqCloD2aoQnlpwR=fbk+sx^f61Yp>qddtu$7PfeH0V+)p zSC?)E0t#Hg9@u=WdmW=?NKCSa!4(%89`>>^r!AUxL+_h)>|dqHh*`YEP*zxqw+F;K z31h-OsvCVff$i-8ntK3Dk*%@tuAK)dS1En$t_UF&bPM@)C)fE92mv1e*+KTCJN3s; zmXc4Uq3=1m>#p=MAIFN(5Cd%Mls@tN4Z_^35Iati)LfsD^$#>H*iPmDFa=wtpzo>B z!{PRmvR^H9mE&)biOeFS2G*vb{zy2Yx`nqSJwj>#cG8XokpJG~v>1$#qHP)cLN>j1 zV&5A-&+9S4#@u+bL-!vnV*t;h=vu+y*~Z;{PH^X?H=wMxR8+Cj5bGP%TLeK~hT4v( zo%Z(BY?*v(vu;&MF5770N>KGD^SIeE((!*NX;lhr{PN}g;qHBy-RCO6d2F_dC zWe}MQ{)oqA7>D?xXnBNuPPwfGs+ zy2e#Tvn5F0TR`B8ngyu zuLfNiamhvUT#JmNX*7RrY@aOiKg!vdb?A{rd%REW23BPua zc>_d8xbmD_i@bFAz${;7z`nzzQZ<~0Tq3SB#ye$*AVY9}#hpL_i!;2L z{C0pbatUsE2sj?4>=Eg=eu+3W5OB~mY$)H-0Ki$3*&D@{NvEZ*_~k|)V8D;*xrof&#tp~3_%O2(aNvN~Dtv9typdmp=K@VhjVW;6zz z++{EfqRI@nfPUWz7fklrl1Q&rdUYU0GI%IGX0%E7xIB67;=`HbMzI)v=6IRT^$cTG zpjIsdQNlYm{gN z*k!61SY9<@c8keZ*<71#Id5x%oBdMqgQCckubg*7iTBt2R0%|9I2GBACMHtt-KLqA zozJDd+Lx}-n?@BT9S0n;B6vUkET!H98ca~q4nS0-<>}^qhquncH1Oti05(pG1Hf38 z>M>mHRC3(vZ0Ys*W%{RVzj;Fqr2IzX0T9>~06biqBJY1W-1tEd0MuUCV3atbr0`-x z-?xL>h& zSD;pGh(QTAnxa_SQQN(DbB?H@>J!~WRZG%UB>*n6__P-q?-G^W2TIOSKmLfNRe+Wu zE#2E^s*pGnu@i`!rKsY5v@#hz8Bjv5su$Ph`a!n|<`;>`U>NNZ0=_M3bx z-b%86{b^s&zzH=tS%s;_^2yfq2+9a=*m@Z+5%Tb~h&nKG{rctpwytMQPt^`!1yqWR z*knmAF2)fqbbzIjvr%}fmJ7XoYxl~Z+fEt-;J-yrfYtKcMb6UjNoPZAReQ=)i@iK_ z=qC9b#GpT>LI=T>Sv2w>&I9wfNns177g4a)kq1o$&?EC9U^?I^Mg3*pRiS&TYQscV z#H9<9sTc#?{UDuyR)LKc=ga+MDge}UlNdOk{8=g&`s0wvQ_K9B@_vE@gwj>iMH_Dp zVI8X!f|LusdZfW&ATGD|y8{Ac53uKV7{ud%;4sGWHl;s;J<#r@q{%)0#J*1PYbrriF8ltW!*mMi?yd-za(pfWX;4k|M{Roz^Wb+x818$ zNoS{Gvoox30IvXB>>|5*7Zo`U+$8|O12JgUw`nbs)u{^rKRNl&QYQew%`P5+&q-Qz z9K+lvv$RvC!=A4kRHjHmSU0@Y! zp$>nf3fNP51?Q|NgVd2$aT90g$O%aL8FD#k3J6^MlVnxOktI^xSnx*?0S-(^*oV^L z@!XOp*`l+D83Og2;W@CBWcAVQDuuy2?iqd?6C5o}_gEFkYwQ_*kC;E6`xI~R;<|)kHmvo9tq+IF} zPS(@d(vhAoJ!*td1u?f{Oq48IFStNZYd4f z;`MDvxj&!!&-CcYRuEKxX93jZ407ypZQrv0jH!ke=#2Xpd9z{ttCfCt%n>&QDol0w z?y4m7m+tPlf^L;W36C%oyFA(Rg#FK^U_KPgQG50A`Z;Y~%0~mvgWv-6$%qu>5`be5 z;%B4Qyyb$F$j}zJ{5kaS1mG;S6@cBqoImdzX*U!Af@0jXNyZC6)P^khSh^{=7EKQx zx<0^iQ6T~;6{#`<3;+Q4{L+^XM|b#-YakE_FJ=^Z-GQYCsBnsLqUvXus-OciE_NF- zb>|yEv%vuO=20X!yOJI=)~$eJA#GS-eS;1|&)*kt@HKIXAbDNH+29!fu;$1$YSN{V zUVV7;KS-V_9)ZiiC8Z8DYj)zNSDjH(K$gJNw7&=g6it4g(bO}V4@_8f&i(>vP7@%t zSa09lV^1fmTmse9{PGN-=UgoxRecB2LILOm{GA5IynZpeUo!x*kBecdO3!cr>xl`_ zRKZL-=TRb4&$hD1ip_482ckUy4P2%G zjt?4peeg1;+@d`JumdGL0So=o^4!sN>Gq2fFf#}fHT7kXdJ_Q704TG5e=Cpjp}zru zw_U)+H&h$|m`U{*4)aau7N_N3Qh!c)+YxX)QP8EyG#&t&cYd089V{q7^SO(@mEtu z)O^Oxn~%g%ofxO*HRxyMO~b#mC$;{Yv|cP>h}PjaWs(s zk|V?0oucEebo?4DJLKRQdY|YB2nm*?k=r#Kl9s=2DxmjN@uPzAEj_so+4)vbI;sUj zT6-2tw!sKWRj!XtTEQr6Lv*LK(I7@PE5%6$!we}b$Ekje8@CFc=+O=Jm3k3Q*-T8v zeB7I3^!PRi&XrYqU7hXQ+!;;%4jPzDSSVCZl*R==C7(IF>oi1&DaSzo48$hafLo@3JR6HkU3F|t(e2G^6z2Um z?hb%WKTD_RL+VVMTtep|gzh8VxMixTxt5@y+op|*Az-D@eL~^58)fILDND*hY0$8o zInR-PqkvXv{|qI=A3|J1Y4{ybzKM3+K|fS<&C@$LU4pN*y8t4E7E_VCD3abfb-W9A zZ4{-xXKHEbRa$Bbr$sM{E;+Q)$%b4Cm_0;INc(KVQtplbfKRz7 z%@Z6A-7YW1$)Vva?|ZpxdD*Z%Ly05WX?Z5!<~u{3cmgU}`9@8ig8q#aP|&844HRIb~>>x#^T;eO1{%*L&C8hNLaR+a1bMNHuQxOUGjewR-_o zcm#;paPRB;xQX4O(whY*g(T=oTUy^A%pjqxm5EG3JByhk>|=(EI2cBoY(ldYh|W57 zTJKeAaG*f$QKy&a{z)2*Pg3R(gZP2=0Kl4r9RwmVmtA;^KXo-q^9qqMNgK2*)#TRE zr*9?SwOID}XON>j37h#83*llk9@f-y1Mn;*V_N6QFnPvYnei3J{;1KTx35fi>|pM5kFAImA!;_-jkXjYZiyZ($(V*v>l>dnQT z-49uXw0DrhGL-Qhw6Jup5M$AXaVFZrcLaVj52Cs>9*lp42tZU2m8`&Rl> zOj@D0CQ~DZn@7Ej=7DN`Wi8%Z8oU(4Z#BAF_6d8;*gs-;M11*b@EyuiSEP87BF&5x#1<5xnJZWhwUB<=h!H{azh10|$l?CVs+^^OWa!WXYMIPj5su>~ z{AIb-QYz`r{qxN@6&(sA)E`nHjZj8A(Ab^u3Bc%7#fL*rkG?niBT^~$5Kdo`Po*bz z=7E}f8|r8eT1(UXH;4v+cDqaSAHa>azx)x#IJV zRGs^^I}ZTo)#&b|^eN2&fr;D`$$AT$R+TzR`=2!+D&5@Li=0uFLnSd+WvmQMOlb}d z{1s5Fj!k);2b3vWy{{NrT$zZyaFp-`Bu}ek+1221Ctky-rq9qsXY}DJ-Nb1y4odw} z;q#8x9ixhdfSo|OEXkVhl*)J z!yj4tRIh`Iya+?v-DtK6%pC#%?qS19(bXST&0bt!_T%vGnG^{C1PVXq&u-e62V&xB zhL{|sfsXhI{uZ;~A-%|rk}_$G(%7b@ytb_=SA8oXQLd~QsM-HVSuQC&riS}5T)&hqnhmLDFy zmiMN`9&Ui4c^4Xkx|a3|qExS+XYANlDz${*@$O4aTtMq^YBCOYM?x~D(Dk42!*WWU z8eG_cihSdaryl?$pp4h^+}gHLAF?xdiDs9 zBdrPQ(YT*trA!xWg;M`hmvhpb0NR_1rALR)(0n%xW>7CFbA|%1yZ=#oG~WTvLjXq0 zPHDw^QFLBRyQMVO$?P7q4)Dxe^Q}UU%1@rCq=uF3ew6ScAz95Hqwa-U-zrR4!&K}- zPn{rfX8^$roHyMRuL}l7C>X&sHE$DxR$5a>AyjaN zWWIQ2ij^=*{akIjn3}snPU111_l6W+rRt=3gl7wyB{SlNyX1EwMwP_eN88LvbsSpW_aDEVMMm-w%Z7LM;SfgT>U(fCJe*vCN)M<{}B#u zG{n=KF6Ho1MngfU5%SG!_Smx3(;Q;d)9SD=>Wl4+EZ$(n_sLxBQ*maQ%ox-6v=G&I zm4B2OVS4JAo@1s$@oP{6AHb-LwTCp3|57PKDn;!r-QXWLa1x)hkMM|6kCx6%kxCtV zWgNPntr#W%JL-C}oy?Kb(ygSXncy3$pk(l^C#}f@Y**=6CiBAtX;ME3@7hV0Ebu~Q z!dKvh719~Jzq;4N+P6BrL;L*B45r}U$^G);@#Va+W%L*y%f4~aT0bdXuTEfP=K?}~ zi?O>zURmMNJ_kU042(;y6Ss3`J{WrpR6ypTIn%(bW-m*6JfzG^;2KwZXU}B~X`vaD z;TN_Xa`dd;_>*0icT1aA7;@PwOtj<2(U5t^)AOuwltLAXTp2n}u3xQmY7rQrphJG|Un>kmiDFDE024P~p z@{1&pXrUtUZ+_}TX+EWor2nc*+A7L+rds2`3ChTwmDcubal zfAPFptF|rQS(U(HWTeNdTTQ9%0g-_3kT)R zS)T(1Pr0*;=hvtbK)NrBE_d!q&iTw0tYxOkCH%ZUTRJ}3Zr4aP0*Go;sc$}A$-C=p zwwa}PjsvClMFR4V$5M)mSYZE9pmeJrkDs;`q?!pr_FP+m(0$XS!s>eQ)T^uQchwZ* zDs~RL^lIO%&F@_e!)ml2nWz8f3n=l;i&sSOCN}D@cKXnB@Qd47=eTn|AtJ`0vfVWq z-Kjqd<}714$tld_5~2eoeAvqo)#W1CGF|f1uYHRuO~dMJ5Ka2g-kc^+w{<9E$GG>} z;zKGGo-YZ7MkDWjCg&7Ii-@2h+T>^cUF$o8y#2v^bq#^Kd7!O7Rx-5!GNw#jBd&sWcK(gqBBxhkl9yW zu23qaVUAo2k>f#R?xhO`uVhA&HK{ddE437P@TpWgpVpu_h5BJ zE~mc?C&6VJViv@MaihHjA$KC)Bz;N=P;Nds7J?E>`c@fv!JXE6J~d>(e{>5}-0SfE z;RPuAog|u~Fv0yRvBgQ;1NEP4`5@YHujNA$bYfS)|K|e3_!k&fEoJVdIWM4yQsx@g z#|xxqYbUA(8{2mgtt zXD*^>Kxiu$(Z@0%E7fAkTNa&nT`V{KW@P)E^~0ErB9KtV2_%Y(S6YUwi>WW_S_4s+ z8RxgU&%@7I=B*a3i7u61Olui?T|h7cHx+d0G<1gtiMeOce>XVJaJk3L&*<3-pu!0&_2F|-HBwelSKg|t#*p2(fULYpN8&$F*_2@yvZ`RI-5?+hK zTd-eAt5H`I(V=`E!oj4P^Gf<$+1vu5+3TvnIGgyXS$(YK*T`mj z?{SWHc5?Y3*dYYC-kR{L8030uC_lbG#-=&0qtjK*75KcY8rp2TjzX$|gyS@q|HR`_ z+p7VW?|OQQFL2>usQ_!IjdD2acK)1Z;{sm}s~4w7eumx~sbh6i*t?MyR)k9ZqZHhCdGJnbH=?v7VB8N4N~zx7McFWmBO&d|KU{Ezr!iWlz#cLMmxx% z2^7yib^5Gpb{NHtaUpv$KF!{YcG@Pgj+W_L-g&maieEk_1I8Cn7!BmfH-_Kavh5-*I#JxW|fdWbT z9&O97Kl&v>6b)Hd=p~KiD;HY-% zPKxdTC3Q;N#cjC&K(;!T-8-t5yjDD10E*oVawM)7W*f9F+OD}EZs=eNu$n|jr6#pWxh z^IJpqNC9(dGPQX`b*F_2yg;lr*7oh839TTzN;_-0_fnMlo5^uZ_;^sb)szKC1xdYd z@Hbl2?SL{viT@c($Kn0qZFHOE;n6m!XI$c4PaVs=_}4>!Pqi|I%DCt}P{01bG$r1e zFhK!`Jo;O2H2h<2@$b0f{vXdk1Zn(_ybV9ec}252oAYbW57L>=W`9esLsBvM8^hKP zOWVICmXP^zWg2|XpUJBWj_}6vEOzz$C^z~FqCo&!gaw}Ke4WAPcc9+9~j2X8L<`T3%R)wGAxig6RqIc2%+7ICGdLF!o1);5;KR!Cgm{5={1Ivlh@b@uqG8WpPQASIsF1<|x!8shHf39>+p7gpbGxe& ztVvEBq19aXwran0igr1h7W9e~5Tf57QNlFmS+m@yYT*&ZP=Z7$-bzn?r`?aR{~@i;q(N zo|qnM0KtQN^sYHDbi?8ofZ#h?u`SV@2KL1IX3tTY#i&jIkOPcDm*(}G`C^_g0E6@9 z=fsO^N9i;}JO>2th}WN7E9_CD2A2ech@JU-lCq+I)E8um^`XZyoZ>Kf5hovcRB6c52aj&^YnRyH-`yLlvT2m3AIQYr zkQMsFYOJNm0p?uT&2Q7kJW>@xgQe|@gQ-N%f59-m0+W$Y{k~=o=czc@V7AZA8kRfP z(BGOm8p|A~QGLzr1Ly3P3G{x&7G~``*vUgI-fc&B6i?Nud;;G7tqW7V!k~QqYVbH=eLeuSe5dL3P((uWTC&J#dL9O8Dt}ro zgU|XL%H88bq4~@noO+`Jbx+fTY7Q>^{NeAXspvpV$YB6xJy^l1{J5pxI@keNMW0v1 zVJ(klp4Ed=`xM&m#b|in4O8SajX_;)$!S_V(45nH062Iueg9zV0gzYD!Of&YfZQ~L zz`!YyvehxwYcHI(W;$KTdx z5)EeHJz%sEXDRn!^xNZ{)Y!`^XN;UR>C6r$SkP<8b&eVXLesyJYi`=xbF`X&mz;C< z=7@G{acxP?tX&d$p;pADh$AW7UhS?+%_vHoR+LI^v$y)Dj--uQi7bno1s`fuZDrml?s zq%f|U-=_BQnnhor#9t0|b25*>vb^Rs`gMdkXPp+;k`hh52-W|9(CcNGFfaIEy zBS0`(hVO?7J4Gy`hCA2j+6auxi)-|D1hlc;bt*a1+|cTHT{7qUiQsshc8@fB%L77p ze4KDFO6d?`qLsvTiK8GDwW6d}TQqWB=A4!OD5|(5<)-NZ4o|?2m#FNu#bA~MXJZ#>i>u-5lwC_@6(zude zitqm7XpCY=;DqE<7;VmJdT>iJPKvghwdHNOh2@xh%bJ)%4|szVm;Ue=4!lhpQ6uw2 zKqRgOR;v;zLRyDAWE}&M%Tihzh1%6-8NNRI$f*Dbm|>LSedm@z-`_UTpKtbuv*LDF z8q8@+R`sdY-6Kj=6$bvmU5Xh4rEu~Zd5?vmPdF5~(n+2#@@#BB7K2yz0qq>RUIrPt}aD$sAB@c=v$zGA_r5 zbY&ctWe&k63@5?ar z5j_tA#Va3C?g@bW{?R;L=yyM&#td;0xH#teQVsWE^*rvu2!ioK_+LDtDHEWYRpad* z)7or6J>W6<=D{~hE9&Tr?*iZO+ z#f0a@CnSbK{qQxl3ZDd@M7{6)tf+&%RyBrBGln*}h_M*HLu2S`0KRi$s7iKxx1*d< zoJYAozR&SDaA*!s$TOdV8=Qy8?6`>^`!vy9J;xJj%Rf(|s*|DKI`FH{&NN^$?wspH zK1Bdo`U%aO3DnBE~aEN=N z=hK3t;&~!>t^G4{n2JuT^XLLo&0c>SJ1%g*Bcym)Vk(Z}<;eUcNooiKEJ@QYF!3cY zLe2u8(Z_K3OyW<)0C$}&w3=)ZsaVCo>E!6+@Rwxqo_ie5YExg6bvlMY7pPaSsS5tq zdzrMLn%~g#8RiOfdb&jx^SR}fvP{AoSdR2Ap;yWrjQwI&8Q8Teb_IXcx zZNF*dK2VxjaY&4ZT`@}MVqoo$Qc^A>faTGJZ!?wdg4+9jRze{Pyrt~y^j+r0g;@^2<)OT|5r@nK{>8Vi)2M<{qw{k_T#AllK33^g- zlY6f8Ni35r^S&4jNP!OvjaXWszR#NxHknoeap()pnhRw$=qsHL#|)YBmF(u3AMnY} z(|OY(BR8_mZxXqNNYieo6M_-L%l`@A;eM{xaK9%Vkb({~! z)a#oxa^D72+udPVWw6Ov{gQ~w%FZL|=KMyxQP&a#NOm0Vj5jsDxG;vZSzx+(S=5+b z%!kG-@r@k*fR<~!PU`$jTBd>%*4wem2m{2Zs(hnRKm_@&mpaw)Y<919S!$;Pgz{7y zqY<{4W0qyfzG|O)e$fbgAzwqMZ&c+z>JA1(dO#e@v2k6qTpv9}UExYrJtIpTmY+Ud z&~8>MS(ig#!@p6~0kBNotH?J}1sGO*BgX~6cL?}+&m`6Un_Uhrev3R9Y?%ljEn1Zv zxpFVKM!63TC;RFNHi#gU@RKe_${h?@cc`@n zTCnWqc550mTD8EOA!Ap7WCF;E^4`877nb(2RSpNpO@LT;10**|%Mq1ioIb)!ZTmzgurt%c^z!AFdmkdCk`*S!v|PRoun zi$CmFqN!rR+`ZGlaB7j+Ps^K(w8iEbh)J8h7z@^&VkxOHAYw<{S`VPC zTgD#SuypIO%}Z<|6_%1#FE_WaCQGG>?wpFuwRczXAbINw);G9yCKdUv0AoA=Ar|U= zbKBoN@a`s>!~Ek3CZ@Sd3f^PUWh^Kgp!x8pd;ymnTW=o%K3zhS#`7c;0b%oi^Xng`qtj0Djz12Hy@M`!*{V@x9&7k0d{7sVfiqm%o-!_dwh=`I51D>A{ zy$UJy+aFDnWvIs`B9Put#zbvb;Z(bfna-_(X0BzXl&fKFrE283Kyf*0di09>C6aaB z=_ArzY3FK0C?@tWgVc-hUSz;S3t87-ou){o&TGuAvuE79y_(h74&((Yle`@H1 z>wtLYgBL>=(vss^b51R@BNbk2_SQ^J)Nn1RbakRhYt3PQ%@eJNiOQ@)jc)0v?>g{Q zDSB~7HUFI>J*_?n8iLYm!t)$sSFP5^Jex1uBmEu zq9=x>X^Pa-f1|D(5usurFQlh1Km@%81gpBwr+el+m~Pfa8$^0%SyFN1k;T!eC$zSe z_&d`{u3HTdKB(JwN=T;^?WVJ^DePTTNt$evr_P&WR~jXl`OLK`&Sc#H$~FUn_2>Pg zQQ@6y`eYS#F*evya;7SP2>J*Jeh#beq#4yR9lNknK!_-xj4rYy?xCjxl;R$psp<- zI3*(UjoP*Ab?Q*e1~EmI>>b>3^r0Kk(?p3sPllbYv=eo$cN9d`%T4wMM+8Di=sd@5 z1kV|J{tTKp3>|ws&DIU}*N@t8G*Bg#KNNVn;BqFmCWr{b5rbh^1}eGYY=DGvNnfETe0%|ovLn!*^|9fPl`zT`Hcte zvi`tG>}wo?4|kj8c=Ymx5mi&uOE3G-tZit`J`>H`j&H6^bejJzjqh31nna!U z!)5W;bL$_(j;2TEQhkl8Fwzj zl&sc8%Xz~b(#tACy}2T(K;EJ1qWl|at#z|%!bx1wcuTjjN6+`S)zmu}hlq6>O)mC~ zRo&w(T9Bxa)2VnVpJj>9VEr$>9*dTew)i}c|5r-9Vs$}DB9vp^g(q=-6)?}NV~ zF(_h^E>WVN6w!%m0yj+!i9L>XF@l0OLIC1CWy3$((8m8upp`^h_WDYP_%sP{p5??w||yc`D{m|f@Hun zQ?7%hC_s5h)+Zul7qio? z#&tYOilC%TA;&p8+Xeq*tNRQkaAU)^<(!)F+14r8D~iA`Z2slsPVkbFH3{bcRsr_~ zRyqt5P%ghz7qy>E9FeU0(&;uT{(E0x>WKRZk3fqjYW87VgAE&C$hdD-*Bniwq9f#) z1k_4?!`ck=l>|jHpj*L)AQ(UR=QJCFBsLLjMN4?^c_Dxvu z$=1);>8T?if)X{%g0jS&Ar|{*`Dr)TO&=(^xTMyxwU3wdd2YjA03|%ijZb+@`?~7T zMB7;Q2qcGNplmiE@&V%hhtEHIv?%hct!_jhg#scdQTJ3(wi@mgJ8)B%dd%DAor(AJ zg_QXjJ_OP@uImr?&>Ik$d)_MUw1=AM5dMa;%J{#l+Q6;<)s(kv?Pd(3YfM??ASqkJ zt_J^^-RXQm8^j$~a{Lq6I|G7Uw1L5SLL2(84brju8fK`vYrb}Mt>yH)gRO2qcWTIW zcL9Q3+kLk^=dCW^=e!LfDaR$)kUs;hL|toAZdS12Mi50a_N1JsU_(;wT?jkfY*&Ix zxk^uC^{cv*uN7UQMua6Xac7IAH(4f~C{CyH|^i8!{IX(Bn0<6Bt+`x z5#4rX)ykx3ry0HQ3tOd%A#{@Kw#MJ=zwMpv^{q|$0qjb0Py~)#hNFa+KjTxCu1IfR z`-u{9Ah0xqtS58_(hb*DR*p%ldOJC{3wfsb$06F1$;;j~%aIJP$N8q!sb34_Nsr_2 zabJ>fY1m7&&p=VnQYCP#5@1W{z}eWN6QkY&Owg~yd#enb4Yukz8{w*{jwNZ{Ih(r| z-xZfIKQN*3ygE&eo9bbl@r;E^%8k;U>-ZjqzIf0V4GS;miw2z;`g4iOuj7_!?Ip7_ z&d+rRTy@@3$U`oWM(irm@!Idxe-RTKo0U9yCSKIpB8iT}b)9z!hFSg`r;80TP~S_o zb&ev0>9iqBh}`zKzUB$AtuWhGq_E58F!{EEyVY7rt}7?!pmW=<7n!0NQ{xw2N6G|I z7u(Z-ZkB8);=RnWPLoS^i2j_BH-Vtk@SnQJ6${=FntcUJB|n9wdYAh<>oh2EMzRIF zfI+<}5&&9gC5pKMMN|!deE90p(lM$^&T?CHKwm@aO5}YN>Te<-*bp9hT<**E%h%;j z3U4U-C%O}Xk`TkTO4J^8f8cw{(MfDc$`4Tb8>Hkri_}^9_N6}J1$?9`Pm?OTy64j! z4H6%5EL9_^@GUH*q_7)>Tbsc%*pTSHlQ2@!^t;;W%a7RAuUdFe$LqG0hnkw!jFED! z)8^gg{6$~>JY0|?(8Ze`w)S_z1T#NhbhvZ$)%3d;5>#-P*Wuy1^4M1qk*n-ZlcLP| z1Fu$<;!?Lo^;*A{?sg7!IcNmF|Anp_hlYNNoPK1J#Cz)cITM!q+Y zZd8uwhJK-5OAXJB=Z}WDN?U_kMdR#BkuEMwsi>W*NuD>s$p8wziNjqHjpR(f9WYtF z5O$F0B97m@)OS!gT`nU+A;*y+KLaI5rqSfJ)`L+JY{L;%?u|}4P-o~b_+I80# zO4{8zbnQ0eJn8N1XLZQ&4&?l69ZiHTI#zo0#u!zCQwVsA2fR&FR}&!&caueK=`m^= zhw4B?p^6My>Qciy*nGj;T6bWmpVy_nck$?8)E)CI{SR5@VXQ}MQKa>*M}OW0&7t+_ z>s@gA=aE$59)3seSc`ku==MrUC-HaSQ9F6kw`ah#&o_(z+6ZmJ*l@IF*9KAxESg&= z|LJ*45t}0p91!s-9xHPp-}`1eE$ADqehj?o*v*+muUlb-o8wjssfC{LT&kbGloZN%2}+Ad=Sqc*Qd+pOB6d8nym zv1=o0{{Z}wH$7@NlGynGgOkWhY@3ewN?{($WY|m6Z_`l^09-- zr(vK;ou*nF0m6iNP^k5XVbiaLk$@2U&Wf{|Olre*y21LfDg=A{BmEqT51G`Sx_qq6 zmG7|<&*!;#@d04c{1+0P0ozT#g9tu@X*afCn%mynIUbA!2D^m;IP$@;$T!UxGzT1G zK9x<~bo%_J)-9cRH2wLs#-K*f+6~BfX7?Ew(el#Dk{CICywy0UEq@pr?6a}<^fjoF z2^@^NY)o z%B^uMrdO5EzG?HDP{S7(y!F784uGXj|62WBp}GqMCiaeFk9-dTfHtaO8E{a~r~~J#&0t;dVUpWE#gt6Y zl;y7m8k>P~vn3<83KcuQXS5{Gnlw8V89`H;Y2skq`XG6Z#WhaD70?;}LoV%p2GaGe z1T`mz=lB~j2BFVkf4_g8u|IT3UcAI{mcH?(ta#7Tofbbw{DzuBIv?~|A*C9fjU!)b z?DckO!iyy9|8>uGEV18aEvWBH=t7;KIIfTyBiu7>dj&Yo1Qn41Ix_-W(u`Na)EQr6 zcGn;aKCwvk(UvFi{v4(;=QU=gI8St^)32ezRj7s~rhyF6D0$()^gDap)YTry@(-1zOpS;Sm zUN#~!;05~d&RqTn?aMRqjEUGP^_Y|1uI5;VR=>w|PV8D>aUZa|mZKf{egMk? z+DQ%mvQie>NLE+$g3nu_mW!Z-RnpH3`%leLXmegM3`ju09hmK?Kk8Z=0Ky3n`|bqT z&)9UY1t65=0rm($a4Yz>P{;YPNAF72Q@@Y65$7w-{0M<5P2*GHy1SDf%^p9vmp74D zd=O^B3S9X~exJ-+`mgbWs^qT-%TYl|H@egA&**^?RC@jy>3Kho zl!4g`b4HEsRyIdD!7On+2}daO3mlA&J!#w*R6{=aQvCgw+1~!Rw5G&Y+|R5BTRQco z=&wMKuQz@CDncCRYY3=9h@)ppgbG%rCf^`~h;(fH3&K6W57$xyV3>rM$Zx=d*vHj? zMC>EY0|F8Jm;qk|KYr%l5&Za+fA@+Z-_pPs5+k#M(mgqp7UAUCIpE4KghTF4t)_;7 z0uc|Ho+gxbNY3$)9aEt)!o3{b@I@SC1dwnXtwO=}tSI2HFqh*}!61BU?$W-8v`&rZo40<^$q;VA$p3#o82 z2aZA$CGi2n(>QqW|8z96o`&Gc)2N?laHZs&2#n-ghnS7)DYAx%Zy4e~5O3+}g0@Ax zWqNvUhyI+QcXkdOu_tJijZj1Tq}tVKRjiEP8(mOnR5Aw-4kPTJT=bNCr&o;M4fSiB ze&U_jnJK{FcyJuJ8CNj%hW>|3^0o$}_<>d^|*!sjp>5 z+Kk+D@?5U3;Wj^gHYzlE2T@)JR4YG-zT!)b9ULlgK;>Hep_)oGj#`SJs-+>$)Gf8c z44pP5_Eaf>z{mYvhgEdbtTf+E8x1k2-f3g$;0V_mX&quEM>gtjE%!lcoa$fG%?vr7 ze5S3Bs=YXP;>c|r3Ds(Uq`i&~Zh_ycL){=18!Yz?#&0M$Vd{-p7MrQ6K&cVJitfY- zXw~Ca<*zQuIWZM3&7RK#!yy^HO?USg&`70%3O5%9#B>qBFFIW2qq~D;e5*0~5_4kM!qyNQjh4uM38E#jo zQit}vH;rCpQ#)zLQ_+l=s*~t%IUcykcQ3$oIl9hH+%?fU zA<+;X3ugQ}Z-@LI6Es5>I7>0}HH#wunt!7bf>KnW z-YhnVPm^hHNw6CiUwCE`Y~aKY$Amc;glca-!qm z_HemR<)OWP7!;N}%BD(@0uz9Ro!WX(@G7Yh8Q`VKKfl8RJvmiNn1ZS&bm}RntBB@s zYATe+)j~Gm}K4GFmB9Fjg@7ZivE33xnYySD8+GgVDfm)9FgE zLyNz{q;g~3aU(ZZqiZ2?5M!U_mjb|{H>6s(c9Mw8u=LmdG9x)rFIU!8xyqJK@Pj!B ztmd>49je! zVy?e!Oqa86DLA*4^H(t{P3BQY+}on{oJUW~I^?w78-a6O&V1|RR_NpJ!)vr~H0n1> zd^|#hQNwy2IGq7eX*SOH&LdR3ttz8(vyQcZkA7g9t1 z9cO7_mGJdGOpP0ZLyZ>7xXgt2O*k6$5*_0|uc_1U&ui-ZkTr9fbXpSrU2HM!t{vN} zq~UaY<|VYZYApOe;X=Zeb*}vD7`>NLnrfgW?^0@C1!G)dsT^bXp>;}Rv~S3PL+V^c zN(D0RYCG6zb(hjk2JZxLjtcv{b>gxMS=(R-6mvj`gie~?X`M;`I;rt%+SsgjAt7@T z{w?osQnwU#CIG9G?~` zmOmg!b>3=TCG%tNkFwM^Hp=E=BRQd<{_7;_8YkY2(YdcT^Ut}T)VS&x)u_ZOU%dlP zt0}tf59jKCot6`;Y3;8VS6qPP+XQW0S}jlDEsoXS4_Uup1hZL$9~v-zUjZ3Z62>Gw zG)I#m2g)z$+z6-lI>ZCi<&vS%{RcF5Ux$l`aT^vN-W!;)^?*GwLnITe&n}=aK(GmG z+YFG2NvNcQ$D;-4NK7Ea6@Z3Mpo2H^hTL=Xo|?i5NoPzU@`eoH_&mn$x<(VH!%H?b z3q6qLBwi}e`v9I-sSk)y7&cCDTUD{^$mfAau%oKu!0g@I5R}5m&H3r9@@vrwWsTPv@U2jpRF#gEfEZpDktIjPoql9*W zoW_pu|NXxG%Pm^H;jM6lG!k(BZlvMxstqpY-;=yY2ZI4k-FwKkVtFP}xQ7^f^ zSsKKHGkp)eAC!`VzSWI~kg-1KHPi8vdQ~rq3tcJJ*NuFXZm;b0StqRDb{ZEYi;Y&3%-AAI%B1 zkS-tpPVLiX7q#yJEt8llF13t4{r*~#^yT4yn|yyas(K4aaSG<)U)vmXw(itMLh{jjYqvJYJCp|KERe~y0%C8rGtw)XDtkl)() zfK+dfsy6FWWpwubXcsOR-z5Ix0CnsSkvfljaL$^kHuA!1&pPFBsjE)e=L58`ze7$_ zse>{FN7bO`{T(XdV!2`iu>Br+h{6UrxOk!Yq;FHcdWgmiz(VrjA-Q9Ga%hPd$1R7G zp()-uLr&0pRg!JlwU4#ZcjBybwQ-u_FkM4kYlg#`2uDAj|7zZPXOedZgc_XehshDK z%RvPI!7TnTKmC!6?UU^T1lCW1NTIBN65caj=04?m?^+?HLGf5t7*DHN!G5FP(2Fu;Ud4#vY(b=9Rlp&S!Svrzqh!4M6iEIx)z>J!lgEIH#a> zm0PE>oQn|?2)sgmQzBK>rt0AcvJQr0aw&qU48~BUIwiSSu*LE&lYeU18jWxQ2?X!~ zu=C-8>y)|iA}IL~Od1J7)rL514%&1|A{yg1 zZGX+no;(~pr0C&pRkHPI)}xbOr2HKvicIxJT+k6faME1w&Kr*Xe0dQo5uA8{LPKbM zS>oh=h}=$xT;LL}>}QBPMPvAVxofD)3aIV72KK)X>}3ZCzF-ma z0L=6DV!p7L;nnZn0kFCu4A)zy83y1QBdPxgsJDJpd>DdY$5K6ftZ7fnG3ZpV`iz2} zD|exu>KyVXC!k)CHz3jhGyQfh+lg_j`JOuk;g1p*lnnc2Z=c}-Z957A1!GE|rl?^U zCDpcZBjp+n_27M)ei`oIZ@qj_UNPyj)A@eod&M}7ksngQps7Y_4iHh(~fW`fc&YlBcLQzkE}_t(pP6C{W(H}0&%vIr3UT~;n{UM(78t4Yp+xAOWB+N z>Ti&DNmLCmL_%@!8Z2N^v)=n=&+Dfdzn+)uHD8cpQhIMg>f@3NNrgh(g7L&QN;n~~ zpMPGj{ppisLJ6x@^huZ(SAGSdRupX>3F&G}4@ZLjd>6@c6h>q&br_AIC~#5fr2$Rx#dsCbdu0?^tF5WInP`Oyf>)%aYBPnvMSeEdi4 z5JdcpZ8BDvn<2lp2x2WhO$c%dA)R#Iq!n3ti|p~wK4JMZ;y5qy7J5s z+Q^%S(AVWI)t`(>A8=QC6xV{vtX{sNPu5IEP633*F(6`U0z>=)2sSb$Kc+r(r0nrp znZ%wsL*RYagOeRL20aG^+rrNymQ1>IvJ(z*Zs8Z!g30lJTyh~zf%fTK%9jOG*pUK- z?e5X)DUd{wi0Mk#r#Q6HFCwR=QypeO9Kvx6chFUcAhY#dieudyZ(3PL42dAHRu}m) z%<}VfmLDFyc9=Jk7!0avQ)*;~b0c_OlP0 z{b~b#M;!nhm?k+iH~>;-DEV;C>h4Eu0Buw|`wdS^XM3oH+Dg{YE^cQ4`P@(C>?4{s z0T{L;?2l#rTR%;i^^6TTOLB6U4&7A+4Y3N3obQyr;Hwa&bSj!_t8q$%4d5lF zUnz)FVP(9=0Jl~FA%a7}!6E}irmWNG&|zCU-fcGpbtoYNiQ8le92SI4EQRq1g$dmN zc{?82G^iOY&W$i5V>&~LTjaT&``xcTh$j5EETRA0@7#0lz280GJ@-53wmm;-AwJg% z6NrnjzQF{ug}9#DP1GalSbRtKoc$EpRfzYC5B3Uji0*37Iqxv8BR_vPhXtc1h&Y3z z&!7Z(2@eeDI%QQ>b|`^&N!l{wa2zH4T*m3{Y@4RI=;`f_uXeN7EkzDlBa?S#6aTJk zCtE3t$bp%$$KeubyA>sfkF|02u2O;O)^?di8tu8t(*pUBX7S;LP*&N5WGfBlBNF2WoV+;Fbh z(rJkJ3O5hIg4xW=Y|N0h@QJIyenD&VM3eUUk2E!3ux1W|ng4$SfE8byp&{>^8*`LJ z`ayRzY&J;Y@qU_t;|;i-EEx7Ypy3hl+($6jiBU9u-tNwCVuEDsHP2>04Xz9FKRXqv z&8>Gpn)ta%{>Ab7S2TG(NE4SPFv?A_`{d7cK}Zu9>#y5?RJ`Gvv8s0uMQAwp!be?B znrkjH$ZcXaq0igj#5kspcT#R%gM(p3mceZXX%bBARP5X%$UNV%1kZU$e$Vv6fPvpT zw(4DxCMQ~{-!^zeeLprRMVh#4+cwKCsXP2X4dnRiE&NMLDnq4PIKJV=A=}`P=X+&H zlb|Ku<@AaV+v8kHVBYv6^2_pKzF2c)NBZ0eXW8*}B; zIiyK9Oy0EldgMOxS7Bf0`F>SlVU3Mx=ON<)lZYxZVRPqJCpr{@hi#5GgfE*LGPZ(O z3uMKMt;JqljGlHE-g%gOIh|2&@Yatjl@$SpQ0 zQK3>wlGSppRwY-?2)^Yuuzz!i=gB7;YTevMXgK&4PXN~B1<+)QE9haCP|D|blO2%lL!N6RS1&3_I<;t#c>LRSc? z7d@NB$gGTvOr$>`=%J5{=hO|&O`;d!*Cx>}%Y)tQbL1WF6#>7Au{Z8*7U5f~yq8w< Z`lxJ6i)gpymOfhfWgjOS*ebH*{|m3<^d|rS delta 199344 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

vUc4*jr~lgPj8$=R?;^Z!bL zXn%Kz{kuYxqpHh;u4r+m&cW}Avb7Fm-EkbcFB0v5q^Xbc?7?>txQIl2Y7&(`P3#J#xaVpE26DJTxQOLrf7v`rVO z?p%y?aoP?$L93Qu=X&uqtbwV#*_sZsp+9N);S7RgdY6|RWA_Snm-f96Ipb&%#M3~0 zNC-#ReiRPFFK`@m@Y(eIKR_-om*6s70bLojtsS(74xqI3A)4!lK-`VGSjZnn^L`<4 z3+IO8m~zL1oBpTp`E8FVY+TK+Qp6ShJ8AbU{`TxB{PQQL6Ux9)&Ip+_{{PcV-2UM{ zUdGU&xS@33XEY_0SIS8=d7{Maq*LjT>fC|-X@7Z^$diOGe+HEUd61}6sWVM z=&egNt18{*P_5~HQmzs;U<0UTWFPmPhBWXyei8D6s0ZCB=@^h}!;f$Xwu3vSw-GN% zZM20hP#)xYAWwsM5CT`o=PJlno&|Cm7)@MG0q=tx0krU+h5oGTr~TFA)nV+6y>u^R_t{QNTn z)Tss$s@mNHYhk`J5vs0QWTYxrdN`;`wXXhU3YL&<>UcU4t)X86+Gi=@WUW)+FXU7p z^p)sdAb+V$1v7PP-`ngd<4xxGXalSYrE7u~&*(+18f*sX=`a-vLSBf0T#yr-cb|mW zAuGHDS)eUhW*|%tk&p}?qs3>QzY)SQ;8!>u5)_X|mNkXn_xkeqkD(j|Ex<1uk8l9?!)G8H+5Q0gU@y#oJ@6e&hfiT9Obekq%0*{4iCr)o zcES!=2isvWtb}c_6~2Nkuo2e7TKEREF7r#!8qL+P3Rb`Zm;!ErXAxH+royz4PJz;? z)bb0mrE&_T!nkLoZ90p&@Hx2l(%dtWtk)~p5f5p|1Y8Z;|cUnLd8FzylcU#-dKn&&Situ zm9Y-A3EaBbOkAz(<|%vkcymIS1Kd(6GwrNi@U7iJY3XWhWv+^J3nSglR0Y+6)KK4p zYE^}ljY8$=?x$Y$B<_!!Ag(?gH&eF&>XPb$+NStmsKVrRm~beZf|F1U?Kt5t@FgSe z1fd@5&k~-2bC8q5_#i9WLrd__RkL}XH$3t(+j@{J!L~n%>z59V#p~wsJa1I{Ye>nE zQh>e}my}Q|jPnxyL0E+_2V5Yo*AKrF>J>y0NDexZvOAA{5%jxWd_w)kry@BeA${3& zneUD8Trnf(v&{1`N>ZkeB#;;qK@enS57p`ewky~Bg!kYtxC6H#52m*WZ-S;CwV+z+ z`3g2@YALm@x`*a~jX*c@J6%COp7Q;aPv{gfeHZRx;AW%@+<4qRs{?AAPU0bK0TnQ= z?IY4|_mI82zY4Ti{WGqyx`U=OcM{Mv7H0}`O75X{)}t#kWuh|*M$@2pJ}+%o9`V?& z=}~<(jJW!+kI<`M*4B~Gl(zvELI|3sfP{nzAU=eGGFOJmL~(_gsIlh{`p9l~&-VGW zPExfdy-*r5Ru8#F8>b|m9JDY^Yh?~VIO!Ci|Is#rFg2ty4;OkPQl%sM6T9lC=qTdB z#M7D_i@cS~x|<}Y1?8yRG7`^Emg7ZXCnkXN_ z-p~)UY_l)n3c{tZ&=X|pE%!!x`kRi+y%|0JHDi{02YWmdP4bmYz7sJ07{-HEe`<}t zR)TJU{=~#9j?JAm|P*8On8y-5BLRshA%Z5 zXAqeV(?E`anwM6?DwqW;KsK6xbUPtUHZx%%%m(S2hdw2~0OrKe<`LI`(yhj4#AP!V zbpPgTyRIfnjaJIShuHg!9?v?w|u`yE0Qox?fpNxEOQ;vWQRzS7B5r*~?Db zRUq9GXy2v8mp}|@zcSZdOWaatC%p`PCB!{LJ<5E6wifonHrNasU_IPn`&Wc(K-I1) zS19|hLAMgJakruK|Bta@9jH?&z6rL%7K?WN0d&CKunTs=4%iOLO!p=V_mKV$z6IGR zKc&C7Y3;KQUbBVTqxP49GX4pE1l`jeAlwf!Rsjxz7S0?ZEJY~K{J9WJbsj@I3P<2D z=*q0Z80?OdcF*=a@!#PboBsxx zr+qadRA9wbJGbDbO{+O{a<@sJC7z4I`>T?;2Y*3#&gd@TeNcuE33WF5dqg}QLrwY~ zhN1ooXN_$wo=O0_iEo9UU&6DJ`JVLMnH; zh2!*>79kSk5~OuMsX;D6TBng1e4qpA;FS1DsJNTfdXD6f67*B0)^;R?=eKLyGnzk+ zpbg5%Jy2Xj#owd7+-(X~DCw@9o7Og+lv^lwpSZT^?f(8uL}Z|9&H%2F8@f9y^DLlr zl(kbl?mlwccI~qf&kl0wcC}o@%kZ-x-Ix$yskD-INrk^PNinE>Zuin`Tu^3@*SjrI5N~NqEK;H_{YbZ^d&CxV$ z>JmOeYeu-8wBC0$CEf%YLn?S1EgoSj3L<}i)`V{mri6y@I;_Ozj-P|QMxq>)g)$(Q zRJo?gMYSGAb)YzC7;0RrVEVXCIW%1(22!{Z#D739OIR9Q8x>qP^IAfvcS|J+%Rn{I zRQ^hNB3j2<89HNFf-?@sa0i*moxdXJLn!)OM+M>wtW6bjd7U>ReQl%~P#s={YV1~v zuqM)Y?uWzVLD8MDewtQ1_P5|B>8+yI0im|(NM8J|LBvKDu~Wx6yX5S z+X|(J5g!4=VJHlOfiMIHgSL+}y*7HYL~GX%VF=l|S&b+C5sZVeX8lHQRH%~U&GC)i zh{y?wVqMH0OyNYdQ6}*w?u@6JI-9&viDjkyRLmKs>{f4hsH$YF8NbP!H@&La-KtO} zkfjqh$2aLY!aT&%zbwucU4>mM4RuW`?sW2qv>Ns_)F4MS?kXrsJhv@J5yBH_$Ke+^ z21nrt=#0j~hoE-d3y0$enMIqudHg?M@%@ zy>TOKfc3Br)FaozH}Exl1z*5wa63&tYUfL{d5bqZ{TgK5o2fazv}TH{GQTv}w|JwH ztLwRKq0p~3jxwFcdlP!bI&sf9r`b*Ct==k$$6~G=WV+YUqrbOODP;8_ZBosZoo*jx*8Qyb;OOYg}uEep7Xu_vKJkQlJQC+%|7Q|0&Y$ykArbGkg@vSIgFHv zjNEajZQA*xIGM?IuBoxz8&TsboBo79AU(2ny-r;EMYsf);R=l68qsA6zMVIHB2=wN15NY<3k#^o48%1H=9#13$`JA4|KiJ zpbH~>jnGFZf5G?=3T??&x5uwDa1)Szz#;T5O#92XP`YfiO?L7zOay5Dprk}}eNIj& z2e5`1YX@EMbZ}kp(n4xD%Fen;(9M8m35D9O(8B?7{0&ND)6&vG6nx5l!`QCn)bb0? z2$`UG0fl8QHRTj0)PeMtL^Yu&oNdI_PU=r8&}tX zl+FsOIahZJrNZcd?y+S1GWgvCWG5jpCuHMnauBK~)`Qw0e>?eTsy5sjaEG)Ct$nq< z8`T`wMkl6iF8WA_YI`Hc=&kmbv4*EIl^2Fvo!X%wAUU5PIYp>`qCfWlitE0v82f6C zqnc9pckaELzNl7@Japr!4}U1{I>bw0Uz@N(g4R?!k@^S?KsSQ5NXN~nCh5cI%Ah(n zHAue-%A^8e9m2X$94e94zOvN~W<}YVmb z;`;oG?n4_9Y8`l2dT&8bjoLe+%M&ONz#6!-(u+CCaa!928i40I^A2Z96#t2#JEPTfkHPv6Pp<@ z9j1Z%5UmcWkrDSnT4~otaWR{H=8>OHRH0CT{EFz{pMeCoYV;b(-ANSxQ0~>%sTG*@0fzf0mHfe{FbD|1auHDvVoFot-k9 zXV3Q82_$ef|MC2x@%sD(vIgx-czz=J0!@Wo0dXgk<)q!_Ss5q3Do%X0jjMyt<3!y4 z;aBI7pbWHw8&?~}&8(>HXxq^>t!^dU0&l`**aRD4J*OU*U=*nRw-MJQ zq#mxcI+%LwS^CEr=*6AekJualHQRnd?f*UOfp0=l!1<5&jpB&;LUQ=h(FW^OM8#2mgO_ayq8@|M?l}2%3iFVZy*H6_`?wK9~Nx zZEEY%bW}C%3HCV-^Jq>@@y}np{F>+gkRQCB4Jo*lM-h;p3F5HG&pqzE&YmQm)p9##UL*5LDH&gBI zNd%6R{!gsj^1J2L-q+w^pcH|_d$m{g1>e;3zbmA$gnK8c(xqW@hMNgcCD znQ70sxl2ppY|i>m2Yzx;$27T2b?Q20XOknR|Hm90lN>Th$6oqilsb`e(6`;v!jpp# z_dPXNUBxZ3c8{A@mPyROqr3^%m#s2GCde4ajHO*iz2WVnk@P~s-FqJ6$BnVu({ef|pH*j|GtpsnR?c_3D6dA8 z({(xAHK%{tVQfwvV~oTtp^lV~U7ardbR3m+2ZcMHG+X>r=`_YPYBWlpme4uF*uOi& z7tK1)?HlRt>(-)XrsrxfeL;@>e0gB}4n?_bsKsGR5LbuNpETvo ziUz;)VObqSLRpL|fZ}>*SA)3Ld+C2Ys7ibbc2x*pg?HIrjj*~+XC5Y~s+;WcQ=Flj=l11M8v z)&iPBbC9NOZQ)Ib=4{^~>;P{=TMF8q@B}CGmY;|WRv~VKDrDZy8c82Ud;sWe<`BZc zFbD>M_U#YdIm>>8eV{k=f}Wt0cpu(_GvsxaS~&wfNNa&~cS3(R{^<%ja~W#cq&Db) zZ9#^8Nh?%sWJb#bpMl=Yt8mKPJ$Qa>hN3Ay{ppvQ!Y~qT1gOtwC5xJPv=)G=<4t12 z=WLirH~~I}k6=8EgR$@-jDZh88OvUahT0KMMVk&;iEH84Y~r&(GgW!CnZ##6M{0LF z=}8oR4(WsLg5%F*z*&4ssP*Pr5Uh2;T7$j;=AwT^xCOLSS_|gmt_^nA{3>+U11mo* zMpm8)DM$j4ONH|L98Jrv*J=TfA)(T{w*yOv>u$RSO|h8xBG8#EB-B|ffGni5b0*n9 ztJIgHX&L7-LT3#g`yXf9O46$!h+6)_#&-~Ihi%~hihrt+nHKP^0WEdYa=3NG^^3zd zgkQs2m`^_28OvLVZ-LG5GF7^Xa3gGh_3#e+pTbsW?jw_% zu}@oeC<))Pla{XSCfo%(VUKxunvb#lNb~^ghacd3*av$-T_739x=VNm^ySW5gf~H1 z-T*z?{7ImS6g!!@`HSKu;Sg7a_@E+phPkKe7J9hy+v&B;(poOJ+Y{`@)I zMt9D^oIc~-nc*?XN1)}`TBZGf_(Ql4eSoG>lYj+E4asL-$LKT3t93Eh&Ca`f6ih&?i&lK`_WxQ)faiLt5Wg)pu(`Y#5(V z-&xi7R>Rhe|J6Izp{H4qm;!}j^0O#><{vByuV{WGh|+~647=Vj#qa%^eAVyR6^x0E zDa1kzQ~aX0T~vDvd>G8kIv{thp>MXrpioR<@(Jo@)?Xx}A?Er;*7eRYVVAs-g}=ZY zuh9XGO6_P-wA#CG2Yd3xD&SSv2VAJT5H-GT; zVDlkil=FW^p*QKsWpA3OE_A9C z*q=DDar&bC>t6}snE9P^7;MI0rfd_5wvVMEl;SR_ndD%V$@345MeM3|OW$S#-ZLl9LO zI~?Z*YzewjIopNcGEeR5nPswTXZCa0OwM8ZSmRcyA9#~yOnLjM@mSL(kuPO@$7-&b zJtvWkuQ9P#$z+eIPLNLP8&h+z#1R!P%vjoE=hOY~hMECaDYo9P$>NZW|8?1? z7B88yiG3*(I7fJp$n3+~lfcA|4NGb6U-hmntPfACy$&UL<=qsis#gv6#KzKBxX$RS zu-ekU_3%77GbHXG>?s&iM4c$;Qet!I8b`g2h5Fg*zcL&hH(~31#w$Y(x z_7tj`^5qYjWXj%PpZTV-g7s$1(Xfb+LVUQ)LW5t2XXc4BjFdze<1Jw4STLAC}M-aNxmX||>!Jx+B-nn$<1 zH9cXb#%=G@EVG$`Bm))^@$vsyAo;Mqi{W7NExk3Tw;k znP=7wRKqLkqwSI(~DD|JK7fTnN_={ zUd;P(^9;eB{)J=Imx6|w@%K3JB(vxq^Irxtete=xPj<72z~sK~O^aJa<@??*JUPt6 z`&3dv6Z-)3vZlI%MCQ#0-U8_^(alt4c|9MSX5Y2EN$#9fOo4(yk6$vYA9(Y77MSRK zVd3W91Mj_KP&rA9D13 zWJoY_7+LF_9rv9Wwd{wi9b7tQ$r zgRkw?pzy1EzMTEh>fV|M16!QSK)9*>*c-pli!K8JtLydxF(PE~Tg{Uf>-lv55HtEQ z#c{3zZgFy%jcA2lbS(&ESI?mxu6)nSAl1UUYQ`>^^p4Szn~b zxh{lHeAgG><6IWpqwkqG))!gmMc0PF-dcOEYr>*R?VENj{qnS@2TwBbIQcqPhH&Ha z_=-g(qyaU2m(IU2yzZ10yBT@RlNx%-O>+;%Q6_e!FLhnoARDHdpT2u-(%u6<4Qj5N zfkMt@;a}&${Mc$9EKR7Z<-pj3@1@N3al?=&+1E3tm3@12pCD=gc4@F1cWwTjNu zd1^P=HIqLK52^IYd^ex+hoiOMLG3+L4EC(x@SM{Frn45*XW_# zf)rr13^fDe`65l*c)qlrK>P*sUkH`Qd^W%u?~`#i;JYPEqWD~p zYnXw_NFFI^%8;yejzCa4_gcNuiNzFNJ-q^ZjM$%cK!JcI(nxqeyWAQP#N>be? z)_474&Ppjmf<4Dj)RD%S6-haxu4d|TE+(IuvLRuqL-O&|V45aCcZA5OyQKru-K92- z8XPaNsRCsy8n`IMGmEf`(*L`s8!nfLo2CKgO)u@4%gnyzOKp~UeM3FfOd%iAOcUEU zOt+HsjwPnf)I?4{DQ~9x)Wns{tBu3*XlsF>F6JUyV3ti8pCa+rz+?<#VtLUdOYg_v zMU!k`OW=(KH*_lEfrVOnc6+9GHjmC-3*#>2K<` z(mWh6aAx2d+zXAxuJEc_fg!(U?xPM_AEi9tw7YY0-piKE^oZ%LyaUQyTERU!J;J%U zxmwGdNX&3eZ0;oXwev)q=1Hi()n@-D=BJO6_}&WUUj0E5WP&J#o-*gl&U^bXZ0qcLbKTQnyg z`qi5|>A2!k?5+j1ka)fFUSkdVO@DXSiKvXXo*H~-u452Ksj z^Ndjz*Jyf^!2CNm!?10cy8V3Y*K4Ndy@*ZNUrHU{p6JL>N*-2Kf}<@B%5|IK0ssW zlO@tu;K?09Z__2xSMk|~{7+L)ddZ1)ljbE#6MVOwS>D>2#-gk7Vxn{*CgH3mc@zy$ z)YPd+P|*>Q(i!j8eG-!h$ZGx0rs z%Zw{a-WkmGSFp@&T2{p}V=*kto7kdmrmfAuR&J&PO!9J=8nd}2rlreaI@gRThP2+S zNJp^GY$lcNeEUHE%3AMkvMviAcc=I0ofh|Dx7$xj&oLuR^a2jx+z8}1by|l-#48+B z&74;5txU_zOsW|7HUrZmOfmzD5G*s}ixX@!p6VQYV{u<-@R7GovKUU~+}q|{V5He!o$PrRGt(r`9yk#G8*R8x;*h&cY(6?( zl!?wrFxWK6$QaFI2If@P;b_sm{N`FlUs{_;H#?UvmaYIbGjVl@JHmU!-mAl{|+DY$2nLM33PaR6k?);=~ovMk9 zy52ePz!BD~&z}{(|Eid6VkU?{6*{#PsK-c8KnlLu#iVxc$g!GpBPzn!Y)F zY16HLFHlW8^L%r9!@wQ0?75PW+t|;A+-}m9okInR8@Cl~=L{SmZkyO@3e?>5ognnd z(Vi63+{w<=`NYs|=C0+}n9Ch#R3+T0?*$WA z+?mNak0&|Ue4TE@lwqdCfUf8zrBe6{{hRsQGqB7u(T$z(h|xRK9b zVoP%K$y~arhP@XFXqpvj^$63wi>H)x?^39guSV3}c7ZoXH@aPzwCv3b6PSK@$F3Xs za5KFW!!2&Uk)HhKqQpQ}&Xp)|Icjb?mPWVj*wxHdDlib;k@fuW`{%8R%|?yY=Z2rn z|DSJU|7V!FH(73b{Bu>?N`1lf`@eg}wO!R7(9YBL2_bM7|D>}7E+Fo`Mcli2_m*gQ zWnab@pP+ib=J4lxPh!JUQluV`DiIVlrD8oNO5u8hAF+ z^U$PLVb0@FOjWLz8BE#6?B+ZUMe-z6XFhv74?&TEJ9Xy@@7ycs*t8>JC zW<_OwfV|z_%xX?f&ONz?6wf!2?kyoVy{*jtH`%GT8Q*|llG*$&!7?+j3RccF*|}+9 zcsqA`KUAS`fA?V1vIhISZR%7da2{-Q-xpnv{G2;8d#4b|Yp@!f!U~ucuW>hfvrFK& zge8qTwtTCO2`o5peg?ObiaWUko_pLU2u(M3i*pfG+Ma{EgBmwk1J2xIPB#f$h~w<# z?wvnQHO83h9iLRilii=Gg#2zj+G-0_lv|QOXDQ$|M4&R=jZdoWzxd1&_4id{o5y)x zc~OnnM;5mx+~$1V{z*$dc}j7r(LOHFH*BYPrUIXIgFxFB_`Bzkz?0q!YdnyxTftB2 zhv#N>FH@`@Z~d%IpcBNoZ=^dFaQA$!f0_($_zJom*;bMB(BM4WJ=2VOWQfx-okKeP z(N>jPbI*m(Jteq(;a|qo3x|*U9>5;?$sMdSz@ItYzypRI`)>6F&Mq(?xSAbhX4V_j zn4Kz~$?3U@b8YN&@=WK=?{0jd>Q=;yjiU6QqGjUtxZlO#N#~~SUE$mw7uJQ{xxbF| z1a7TO?FwP>d8N{&0yD%g)0|=H`P{76ptGe948bQ2{7lbtOXzNWQl_kCbi04OdfWN; zeW>4EJ z(78R$&D!#a&b@BR_|DL>bHR%*b9O+vW97MvAeUfncvCs~{L4+RbEESA=R1@F|6FY5bA~nwE)*sq@&Rwg1 zre=-??Vsi|&Ft;Ma)-sXBo&RPtFM4(qq$rnEYj5M%Jn@^;AajI$lH9|)tAX`j%r8- zTB2~|QLYv?kM_f6yOlzXu0 z-klP}UHVs|XkfiVu7h*C?(eX&3DsoRsD+v1GEQTX_h8ooCKo|et?Bq4QItAUu3RZI zYyYDV7HIOrGPU}yX^ugm53IqetRYSFogW(h)L@a_QuJD%T1k=(-~V*Ww`Pg*zJx&* z^1j>bll}##ef#*SK~i&CWyoXh%dW-@yjHRMXkfZG`uDh%{i$6SllOhfFcyPI3`!@c z)3)n~-EE#4%x6n_wxsV}%kx{`gRIzbi@)9UW7l-2t-*+{Q(rzlqH5x&266gZ)IYRH zPxJVFYAw);B?3L9BkiXp9~p}du6gj?A(?_boj(uMdOsU(@z*@v>EzmG_6;V#G`NTR zq$k}w%zWR|S0F0wyuiYo_@zd^`B7;0q=DZ`d1cB^bm@Ch`h)WWC*Jl*>6H!g6lF1E z(HK^rU~px=soRUEiq^*eGBfWxW+wJ@=l$791@`s~H>LadQf1WdaI+Q!>S_F%ty|k? zj^)>GH>33?x;M8czbr7Fhx^hPUmstJs0Ual=lq^Od@^R{fxTI7#NmcO zE6F*{>}E_KD!-`N+=s(eGsg&`-m)3pJenod>;-2_JLXQ8?Q4?u#m=eS)FHg>{J@m& z%QTY7jPFY}W6Z-3sht_-q^uU2zxuM=A+X?pQ|Z$WlON481$)iFnfzk=pdWA24=*w^ zvh!SdZjrf`gVPB!)%&x1V10yJ;}K@Bn!zq@h&CRY!JO;QjqhCJ@zc~x7TYygNq%2Y zc~zV16k5ZQpVq!KwPdgfgJe`f&#!M42`yH25e8b1z)3hcL`HpyftFSjos?%n^wt!+ zSqjCHNY&C@vtK!6SRUw=uhsdkX3J!&canqFI$)5)WE?;>WH22EQ1WGFIH{<{D+A^o zb7W|>a6+qEx+@MW21sVE4xkc7o8!4Sr-i2EKqkF(s{=pmtTbH)l1VZ%W*iUufxv7S z$eB7dnK}bg=nEEZsHkjt&m_-d3ZEUm5i;KyH^akBV+lw)lmqf2*N9d90< zupk-P6~t>y-XRowrKviEQ?re?(5xLoJ1rmLOA)$wJ+7U`GbAj%`E>|0dDn^8Ilrer zn%fcfOwXrorVb@nE4DY^lZsl+*{CH)x0$eF-O=58X`q&5SUdgGSp)N1idl(nE7p{7B79KrwP_yk3Cv1yjN4hie+;XJ*W1-^*HlW+O28)&kCskk5@*FyX z9p!eUdfkz_#H=4q!PjmMET!Y+%WW zt}?x@e{uL&>2>%jqXmU^EA5Y@$BkAU@ObQ-U{5EmGkhkb{;w&X|*;$FlY9Q>Wxd zxIQ2FqJtmoHZwlpT~iLzd^*$G4Izo7geFPOLeZJq9VIsr3o<6}{*G zVeh>IqCTGf@w-zj+=(EaI{~r8E=8~_wrDH}_TG}%VvjM#iY;~p3u8s>4SVm{H7cU9 z_g=7Ji?RP+v%7B&5KQuU%I|r;pND^z_wDTN?Ck99?C#90yo?)^tGxfF&0T+2bj6^R zrl)`ld=CJ=5&tl!)R3YLN)$2Z3xihtR~k8NzcYPt;m5DqM$)UniJtC& zoyk%fIvMb?I|dC~r5gxe4yP>eMS-WPKv}<$a!(QU&)SN9Ur3N=p0Ddndg^yOZzpu1 z6o?J*X9?7G3dm}IQl6(wt@YKenwMrkZPD!VrjvfZ(LHD<0JuY~m*@0(;{7w5#Fm2I z0!eZnIr)(@wcAhet}}Y|7dd{=(*r)7q$gZ?zEi@3GQ3N+3Y`}u+Q3hY{btyCq88x7faE(`y94$(qUEvljEik0<21lq z{ak8Lp4~7PnnbV`k-xg>zwq1BX`jRW-QRXc{k^jPI-G0f(#nPBOP$Cn2%e_1f3Fjt zHK38wjn++vR8g#&k^=+Mvyf&t4M-9pY64O-tU1dSxmujA@M@*M;l&kj0481#*k>u7 zoMBs+NUgdV%p^Je)#NsZ zT$55dNB-UAtDP+IJ&<9I0<%H+f3=|=+rd}6sPb$iYCSke*D8VkPEno;MtAdV)Zq%I z&-9>F-G#abEQYxMu*6f0w)&jW#_;YMU7ZbFJ5i-05YqN@K+axNWDew@klS_j4QVC5 z6xvQU>{=n*v79JpG^V|54Tmt#DuAKqE`{Y%&1h7zf!aNQB?g+45EK;+8y2Ag36t(I z^eGysPbK%cppjI4r9rqmZI}!BVS8qGr+!p%qML8$~i<(m)Co>U2P)R1_On$O1uN7kaTNO}^B>=q;=Ni7#Z zldAbZF7;=HulVV(Pf&E?NNJobkSI;7 z7bepOr367`&*-~Fww<&-i`|59Nk~N|y`r{k$MOvAFj`Y;ms+*1bOjG9!XF<}^kOWB z+6rKzlRQcmY8MRVOucMnjBPQhybPrTTcyj(YLgL6^OgWkP6XEsRykI%-K0-}bRf+F zUkP@(0M80h60yzbWW!pQ^3LRcPvJQ>4Fkd0UJ4sJzZ48396sI2vS4V33n$+5-)u?Kki8@?S(cZ=bM{6lgzv&a^STp zF+WGB@dY!d;tEaqF}DRg^Mgwr)6)&R1{`M+NNq{!7gE3_bA_C=f!$Ed9>pBb_!M8b zcGfOgjHT`%m6P+0FsSDP8v|&NZ#3fL#SRCWb3qAqR!FgoFbW)9|FNLAn1S+AW zM0PTySWbND=oZu>dKRGcL$X@~fvM_Og_X)EX=&c_QtFCJdz{^VvR4iq2x$W65M6_6 z8TbGyEK8O0g@0{(q@Kq(r(|7u`762FE$Gtsr(Zs{$SsQc@o~l$Dz+A=?4`#gu-LU- zi?fLLe^9V1_PkTqS`kXc1xhw(%<%U$;r(=c9U4o+8)|@4jcm}#*HXi6v3mQ~`0eL! zOHN^RJPZ7WbD@_}_TiNSTCJ+iJ197ELAc&}P0{PIxTQPxso2Pn+QA(10i2#{p259! z`v&YD;;6z#psdElnR?+Xc`vb*Cg@EP*Uw4)UH8WVp4Agc>{^_lp$oWSeTD05y1kW%fI^_^1>oAfB zwXxGSDN>y`4@ND#E!j8g?8#rZu3T&A`uGub#` z2;+a={j#G%JCjvTed_(XD_uj^zp$^B8+y4f#d%bOt&2wn3o1xJJtx|h zq*c4H6ei2S6sj?PblnXWSVpyX10m^$WdlUj(FD}WwX2~Y;wbW4(#L^jr^w8SI?NOW zhH6J<8|WeOk{vqwZ1&m%D_J;>Upt2)QoT}>(3y>YXVbX7=qiqg#EE&MCdb0Qe>Auz zI7E4ftfSokP)8Hyoq*%TtKc!mEgz1Z7z#M{1jWewMmhH3r05&~>;O=t-HxZ5-94^z zMbbv^4sv*6*qp1a?=wFzToe`K{xUPQ+=s1YCL0Oyb*$;Z+*!f5RONmUS~LljgWfkz z0@;dyb3;u#Y4~fq$urvmP9DVQ8q*bq`xEW$(`|HnRHB}-39tKnpbIp@@Nff)wVY{7RyRJCdk9k;p)CI>((vz zxAy5xF1r4kt-smbNiR+@Tb;YuV|F#YIMR|)cT_-@%10q_eW=}0Ahn4GATjTEmXj)K z^6$Zo>px+>2l<(z8)-Ge?WQ9~L4ls+dJHH1dN;k`XIyj~!>bS`(N_1KlW`g*&Q6aBE}dEKaS0=n!9>IC$WhYPUg;0@wS=MyYVO`ISa>{@xUE7 z9`P8=%`}gbS`=x7pabI#pr@QYiYlA{=2gFz%U7ODC0c*gI?p{7(E{|DOT7Sq)9i7a zRHa2HP>r1SDTSPz=UNT8x`@@a-kvtTc(yev&dSJ6mIDAJBFJFI9}7|&Io$7@JvBIS64Dl zN*7p<4gKBLF^|8vh^q!@7R#AdcX0YS|5RIhJyr|x*O~@F7MXkHkt}cDuh-he)xDtk zVerKqA4+QhfM_>)&ag4*DI8R|QrKzyw1hr!&K5fHJCgYn@H=#kdU>U+tP!3RJFI4f zuBb?D-33DI*A2Yf&F28;0E`Y6jQ`4^$#8@X68K}xRDG!%T;59nK+YgC9C93yV;-{4*kWRB&3O&d`%FQacoxSj0kj8I z=vUAsPQ>LG*`0$*5kQ5`f$0xYD_-Cw6B+Jgrpf0Z7S#$^rI-{-_P>8T_HIDU@o1O@ z3^1!rj_09tG^GLO(P70}a5Bt>9nJgm*sw<=Nu_vCu5z#1)R}w~MS(FdEOW^30#><4 z#i-5&NQ!g-l@J?ALjV|fvV>f@ zyV;C?jQ}WJ>$fNVvpo9orV>~H|E#3Hh!e+8TQ`SZv~6gx-1n0`UH2l^@3!&S4N;ZY zqCE4XxXW0L<&B{yB-mR8`=)H6CaaFNEDTt+KxLvEe_}v$ATT2<4p+X5T;G0hW?nh@ z-Wx?-sqz(2I}f!$VlJZKG7l|WWyhGzBLOGyLs)&91c1Ow3gGdY`^6f>_;W>k!07L* z`$pw-Xuto!l_BPHDhEyEp(|W>0|0Q>&wqGxc)S0^R-$e(bgjwZDsT|WJ8pITbe$Dw zVD80kBc|ikE%XFGb2${37ru7#BeoeoLopC71KCZlWaK;0me&xNLs4x4ag z_%es0D39<-%>R>U=yjBzPqUDiHv*1NH)^ii+sUJL@b`dIs?8BP0RT$|It}P_HGsSg zMrS_7?MRhhGerWZ_#N~r2Zi0hr(98;Y3L2;<4~pWTY9RLT2)dmr;l%7ehapBH+%}D zi61cAoP*?KI^T76-n=dqYg;LDlEU64H|Iv@#irw~_bKNk&w@^$GJ2-Jv-Nl4mL&f7@xlko!*K@=Bib{jp~Kp1yceL;SAXakG{ zExrX-xNzIHj3pffOZ8NesgC|P8gK^`{=KYJM$4A&;#n!vdOfgI!WL+)xntV_TEd)r zXhJn8c*ZriudZo%Id&}@Ub|WL_|NR8t>ll9hTMfhU5R!+MFVrd2-z{lN&5%&oPF*X zUz_sv8HNzSv_kJ;z+_*|H4#OZS>AW%q*DWD)G8q8C8V{&pfRm}4n)G}B;(VL9&(}> z_dD{skFTGo%zd$tjJl68jHK=S<19MEUk&MLD5R{<0~|^&rg0CD1BT^9d=14}PmV;u zji$ioc=>#h;>zBZn<1l z(z;daTE=3B28~4zl>mHLl_n;E6i(##0{m0r5twyMHOW5>7x!P~Ik4bDK@TB`lr_%4 zN3asa$`e`z7k&NdC<@^af%Rtk_z0?0Ynt^K5fJLukaECb(X)z64#YZGTb2A|gK4SQ zoI;=2ZZh1iLBV>HGwX$Le!v(?LE%UtPi?ap9@n78Pr(-dnZsL&c|Fze8Z?SzFwC!# zFwAnmsP8k|gfEUpJ7$~KGysX&=^IJa_D(+2zhC2tXl;URAx1JDBqjSal;or|d!Z%= z@N2V{$n?x@#8PZk$Gyp`6%(X^*}{JX5&@$*nrMacXIOx}|3#Ss6>oqGNO*(F7UWm7## znCfjq_r-by)&oANWRIKLyinfP5M)vaDha0TgJ|MwkmeU^GY&RKztFzdwkOQLhRJTa z+`a5JB4-6YE>w$!l-1_H(l79i<_%sLXQ3iqeD@4|*e>&;8k-ncm@4r%w36}nC^a*f*^Re`?1{yj~Uw>ww zE~Nz$Vs>Y+C}K7qr@C*k)X1gJ&GIQw>gQ{|Z!|NZRKj*u1=_d~Mzi0-$R^`e>(4Ms z07UA=(oOG3xv`Fz*VmVmWPF!mu2W{tm2O@FIl0hi>&=LFwjs$w_UN7MY;(Gco>hGk z(lC;Cz6Yh$5+|8PEpg_Ic}J01>KKS9se~7=BXW%nbOhEM(WzYJTlXSjqThiYJRt#b zrGe~E$DXSnw3rsh`iSC4$vU{IDfS5{4WgVMF*eg0%9W_;7PE8Li$T13Qxj?da+r0V zx0xOKa_4W4R1VZfu`X&gPz0N#S!l&atS3^-@?tIPyGBy+p80LV4&9wHBf0t3U>u40(?4ogAEbwyR}py3&oB-H9j$7BX~zG2XfZoU?!T2IpA#BZdWrqUdMdz{`i)8Z$U~$Y4>ds~kvnao< z5gzB?Q*&D|#EcGdVvQahH1FfN7O+$vtR}z`+53#-ikCXUt=s z0Bb+qzD%6AuF;1$CUOjp?I??oAliowDyd**a2Uxtz${l>@;I2C^uU#Jt0G4<_?P3o~$_W}s zD;V;!H#k(BEqOGa2CkWn=dgVsakMw~EyX3DM zPV+42=lP!0(*<=lryLGO_rTu0Ve5bb*wdfE>Q1>M>)){e_-!^0+|Q zwaNx83jkmbfZvvm%6ZlvmcxMH!;6c<_qe&lKO3YVz!X<{QEN9C zrfb+pT&WBFSMvZ*RV7%~hTGLHNKAGZM8`$?Q=}m(j(TVlFX}Y7)->|PY9mBWf z0!r{U%IWm@s=F2+eXMXmiJQHnoB)sn06sjq;7Y}Fg?HdvXock4-cfllIN9ha#>(vY zqg=v*>hv#CeC|`kqk|eHT)4eys1q;`B0pemt^qi{r5LcFUafYkj=52U8>5t-!rJS0iTTwdTzEkvyG^Zb7R7!4_anl>@8% zwD$yuh~vsJ%m-D@(1rVQUCcb8yvhLyulz_|QP%Pl5R6!o$+b(TQ*Y~9AwK>{F@P}t z6_?Q?XJbh)YaW;6fx?BD@iEa;z@bIqz9Yr2hQB3bh#a^z#*5whjoidXD@x%_!%gO8 zfaH~`_s11)&)yuLU-2DAPDw4%D{<$oM7(5oB{Vct75&s;Df|V-BHWwSyMf-ox+Kjl z0j#mHR;4tttk_mbCD|PL=Sl9U4Hl`?uv+s1R*XeIdY0GdTG~&SamUgXvEqs`b~t(Z zWX#SGHHwebSV%!Z(LPobu{J0a4N73E@zcg8_0K8CJ~>A&ma<$*%8R}UNo2xOA!oS@ z+b5{SgO`NyQ6@IGXTD%^X*rsHBkp5a`MH$OI21lUt@1;u8c8eiLqUokP1o~7&|Du) z_K_wxnm@`mS2oA7dORJ2RccpZqsQj23m8l5HBUMf_jcV^=zV%#UDBD1&kNuw=B|zf z%+V|4{rRjJ`F7Qvvg9Qg4p!^DWJ-52&5KNPH&e#=1?gcy2oUABwv)UH;Y&FcDQ~r5 z)D1rdjanw1Q*MmD)MstTZ`sg5N!%4cy39ad_};%lNY7t#kKD^E2v!#8F*?lwPx{R% z2R47D39-FLW~xva4X&mpg^drsX*nSH+#KCy8-e- zIHikN00y*8Q7B1)6Xo(rc`FsY*u8=~zxd7)qX~RqX3l_1I?qJYQ!Y`~u$)U5v;*$?nUoUN6XT$|BslNEJ zjo`M4G`JX+)MKiOBCb8Bv~2JT-?lO?SOG3fq?S{R9(1gj(cW-tB3&(J^fn|-j4BS! z%uepbjYYD1Ptx-_C!VXjslE&TqHw_0d4-jbHDT}yF5$`wzsJW}5_$8UHsl4eI5ku?Z3F)vWQVQHI zoof8~^YwDu)EI(29ch9ex|1%+h0>|SYFc53=!*F%r$2^2J%T3d;z}zpXRI%#R~+`< zYCJ3%tINvRQni517wf+1s=bKP!|JZteTr1QD;GC(>e}(OMNk2ph3D3UlBrQ#E+MQM z$e|DX83kG6)+L1Q|4`$WW}$(_Bm*``)+)3r1)nABbE$C-yrJ+qTy6J>&66sID7?V9 z8Se$;DiBy{s$6piynbG6Pn*S~J~FEME`pd;h$Fk84?{#e@!}hfJxCWD^7d zoYb4j20^n|T9{=ZYUJ7a`{H>GLjwqYtfCx6)|dfq!rN&lA|#UFK-OQ<^LtlQn-qE2LaOEL|q&Ty+_zlx@IaVEjIa| z@-%KT%I)IEaa`$i1*1i_&3CqJ(ogv+CZmHxBlIvGR~g>NgHtm=l;XIGR0xt?C2V)( zlR?z7lCh{^?JOEw$=DEJ?3NRlk+EAD-N%R07~Ago-0>}nbsPoyDXW;nRE(V_gbRh} zt@5H!z6mLvB!r8CjXa!E_YE%eCfFDz4;3>uw)2N}`UO)=p$W=^vZyNPv@i&ECf_Q! z3jd0{5P(J9j)iNsNp!D@F}JyyL0a}t%hc<1&ke%|+Oy-Uc*7!9DL2{=dk^Yb)#wqJ zagle?dJ#*>sl$xAz36^sYb+%d14c%aGnnU6QdLafe-wL3#h?580ZOz8BIbl>MQE% zZyvFBbHU`5ZuvZl55c+6CP47H*qgUaXN_*s=ru#&_6P!d4{}%|h-`Z1mEmrajXtbh z2l*oF+&r?>gA)4y5UhWDZ+|)<*UhOn60HzL=hKLK zSd&BM)0TS1I+o*rWQXmroQZK=esgRHxyPOxST`=_An#D)25BWAJm!R50Zv>ONHx4m z*_wYWZ}O=xitzJ~P)v#~po(GWTzNq7LjTEUT+bQxA8i2y-sWH_Uk5o@sltyL`!+w) zFMk%1BW}%FEuisXKy2FrisL#C0g?~DW)^s#|IPRfY}AEyf)Pu%cCpl*T|jpAQT8s% z=0aJ&RRbQp&lj^wv?4n7r^=cB@b}9%Ui$qga`?hZXSK9A8HHo7t4ITx!LwXSDm#Z`ESw-!e8|z??a$}LniEU27zpLrD=0HTenOUV zv5vuI10_r_xmmn6Na;3lTaJ4(7sRlgjiPq34dmU*=x@oqQBt?w&xPj(o?QJ=KnQ0& zJSfz?6{yl|BaH^6VdW;;fp0Cc*@;b*#GfiE?CNyly=vhWp^@QPgp^@M&2u)(SQ%1K(+IQ}xJq9zZJ|0p0fkgS2~(F$6W|k{Dtf$#-60gDYaw^DRx%&EW6WtO&E^&&u9>D{@X6#SNUSZB)c zt<=2>)c3`Jz^k6&&&thAC~s2;NAA!_p}kHw@2L2dBbMiu*kM8Vsr zWmojBGm3ihl5((R)pnT{{r~}mb%{g%$k>MFQ2=lUfO*&UuxghpM+pFNY9+&18)CN6 zRn(-yS!eW^4{k00Ck47rDVnr+*Gdege1HVek&{28wv%U1EQf2iOPeGA-Yd7%eK+y6 zq8OGNEg-fEzb%3`3$bT;utRd0L%V6;9c$pghMkJas*I)gPD$pVP3sT$9yOWOPqt9Q z@rYLIm7UbH7e-D+TsA!GNeg>n6-ybBKRMF>U$>Yn^p&(%&1maSnpVv}$4O1H-r}Ws z+FkVDZO|*5VOU9UfaEVko|VyT)xx2nukExB)7ntDZl-vwKw*#?UR&!7+M<+xrE>kD zV?W_+;IAJn_y=<0za}93Kc0<<(f2`yppAfFowZz+l2bplyHgwx&?$=t1n3SRhdner zobx*j&D-D@m*tZn|I#P-(QK~$1|V28dpN9^I68574?yr_#RPw|j}itNJD6?u%ROtY zKaX^^dAgvXfWUO+R|dldL8Y_@0PpVdIXAzQ^~~EfqIsd!=h;s&gRmp;2ZS>q)|@Fn z=3bQ>g?yx|hF>XN4dgKDw~yr=v9sTm{>Wj47&%S$(-SV+1rSW1Nu}DC`{q1AKx$cq zjM>tn!^SpvX)d53Q2U?A;ajS;FD?yVefNHMRXfV&JV3j+Y~cgSY^LGFf;Cso%qJkk zsb(;8_&R#Ppi3t@IvrZB%A(eW2go!8m~~MQTj-}33$D)Xs~~UzF!%rk10rZDAh`V+ zS-TZ(U)JlSfDrv&fgHxU*y`nZ!pe`XgdDgB;jL?3+yR=%wO<7UA2aAqys*6W%r^)S zcnJ+|R=NiVXzvgx^*9=}hv%@x?w~wcp7qDHtVQp8Yz8FLABXyG2dP>(m@7XZn1;Ud zI`wKCFt#5cm_gtyRRuZBT>Eto8a$uSu?TX&S!J+Y4LL}$sNK-yARXj7+5(bC`S2ZM zVDS3I&j88cNWnsBA7?}k7?k%jJr7dxp`gQHl;snF;HAa=W<87X1w^UhCE<&#n}8gq za+Zh1-kgl-8jBo$h!-O??;s6CS;NYM6g3pR-FlF2b80$7{=<;wJ4E5bj57@ZhsZJ< zRCt20tKN*?vG_!f(AGSG@H#b4f^}z>Qs{7Ow7Synk(jiIr+D>OEdL|u_}x8So5{tBnkf9{OpIu-*n1@GB5$) z*}Ib_{hHA=vHD{Pg`WV!mbsEZk9({D}Xh@pB8Yh3fI|Yov9n|&X6x`V) z4O5>Sm;2VZHQ`k}8&+p45*T(e4c;H8*<&#Lw(*iZ+l5`4ee>erN`i{QTFWDz5*VTs zAb3L6ai~3U>8Bnz1=kF$ae@3~EUuF<9pcGzESRooJe3}6EbP?>Wq9JGWpR7G$f0MB zw$8R15D<<*VRQ8pvZ3(}nk<~YdImHPaH2p(Uh8HRV<#Ks$KLXc16QJO`El4)8xpAJ zIACyKA6cfd>BBgqhoQ_#+Jzr2M^8#Ay{umDld~tE+AcbYyA}iqJxQkVD5j-~cp{A6 zl2a6nyrA@n$W{kCPDx#j~(^ozt=xy^BBh|MpYk zHKGI`q3YhAqRA6L1O;h$e1@)bi7&NIP7`r#G2cZ-uWWrGsdz|?#~9QJ|E(ZcE2gAG z8cYm3PSF}*7MM1g3QDssi#|kQQOt#YKfFJ4W`h#@Aap?)aULm6J0U(MB-~MDaj+$o53_qG7lQ0EL=OqCyZu()R>o0>BqYMbf&8e8VunZOf3Ia$U zVXSM2IZvSxkXR+j?|pVBZ!zGps*aWOerR&EQN?GdL%MwUuFF z7S?ix00QI--*;ntRJTb1IN4>x3M_QG&qoR;LChAqX&0$`B*t|9MH(FmX}s|wosBfM zw(LP&Y<~CogxPbELMI!&)g2!aTr&n3UR|QN$r$0Rm!%L~G$tl*;gIvz44`k@0W1T9642p7Yo zal1i9r=tJePNG z@9DTb_M~2OAxVqer=HW%r*ik@ppI)WZD`jngCLlAf)T6CRLv4WthqP%Qs`wIj9lg*}}* zve`Wo!6yxggEM>AXbp^+UQ7`S^zZ z;a45z#5X$8npr4*F_Av<&p0IVgI9uhBU!z_jFafZD1Q`ne@Nk_k*e;X*K6m)+Q*af zEHc)OnuATOqQl)sG=45B)jg)rIbip6uat!(k?;6q6Sq)0ug4S(aDLDy0Fft-&VlJ# z`Za3r|I~?wOi3SvwnYns7)66ZNTHg#1V1gR`k}WmdFaJh`K#`v_(dzF2&mPmbyC%x zk_=xzr&aSn25tBkKBv}8&_9K+#zZ@wlka?ExI!Zty2s0?1y*1+d{k$FmCUkW9mD8X zlpsO5A7ojGUF@k>ba@WOQ})g9?hlGtm;#mL%2SM>!YeS_pYk;RLP=fc#Iv7fDG7rK z`T10V*b>s$k#;VEO6W~{79+vic>Lv0#>Ij^_=_3ziMz?4?3NfEY3^cU9xJq85!2Un z!o!r+@bV2=Y)t+JNh)nq5UkZ^C@SX-& z1glnmPf<&a_YHr3P2tO!&2vP3L^An3#XL8<4H*bw&T_c^t;Ih~ZND0e#;o^xv61Lq zNh_%UmzkA8wd>b^kU9JJTz*`+UYBTVMK{DnlCu|Uj+4+$%>vN-%6+Ry=(oA9O)uX^|WKs&glNI>xM$f1jOOEnBw_s9wn z`+<@GQR)aF_|(^HdV9~?1y@B_A?_iEA454*Yk%pr2fFYR6{_7VAN5oz*63O)|3`TM zdc3x`Z|Sp3f3X&>fgH9A%@uUtd)V2CZ=w3IqX`D32MSL>VOGT7zc&`9gji!^^}qmx z@Jge5rp%x8i#rXe`Avg?K!~rK4_V<&29Ib`tG)8N(MkaUcou%7dQf-pkjcN{qwoVlppAWj$D zZYclQb}rio3N>Jy0{f33fPm~Hnf$oxJO_~#6HHFVo$-x0eR2f zASB)R%=H_;Ew<@`l~*)KCA{Iv$-yVH3U&{5psT1QsI!AX$hh`dbIkaj`mou83dS}{ z2XHMGjPSJ6hfk2$q6%Hym1gJx4?rE5t1aY72;`qpCfh%CRdjWm-&h<{HQWUQEpDxo8!a?Fb0=TEBPrYE6vY)*i3OMeiB2ctG%jQl$^ps9$sW(RQLN$LP{M%SFCBP`k6EoMEN2PTV>r z&}XO>v$87Z>usxFmwvJ5xV3B>M;eT><`D{_#;ghT>%F_e#-FNJ;vE5Z!!k!Y20+UR z0P>Wo?DzAWsR>?uU8Mm3aE#gsEOI*;#8Qy=T9YO%diCG~Bn41i<-`r|Iqtx<>(eC` zJU6;IQ3#ivrXa#Dw2LcsED$n6E4x$W6!3N_JG#-ywDVbau{UcLVPk6N0_rogbEfbFqmwsNX`Hh`R~U$|yt*~Bu8r=F)_S0BqceH#LhIK6Q3MdxZ+7<| zf5o)I+J>!*L01$xPj?=>)vZ%|Ic|dIf?TL8%35}u(&;da;l;yB9XU`ngblkj`ngaH z*FFt(fEB{eF1@#N^YIAY0>K^(r<#6;kb_k%{JZKNEe>3?bks2O)4g?}Cn#$!?rP9s z!xjFWWpLM}>zY)x)-HnRBT$$tmEVmHs|c-`5q!ywdh#zT-DvD?OhT1Y8h07kO|aJ0 z_kCBtPb+(1|A=)JhvtZD;4Xz){2~8}hb~p&W^f_QV%82J#^cb<+<@S7yv2JDK5G8+ zd?*-fv&Rd4S$7HsL{JMrFn`pjc;tZL$T~jYQYW|VJtTR{I2{Wu-Et7GFG^-k@Suq( zYk1>EOXIKyl(A|lDGst&c2K=|p?aa-cSwE!L-tMk)u6g=PV|%`ZkPRf1J5VhTcJVq znudH2L#s_IU>NH~#rL8Qo4gF-;=g6U@G%>we@d_xyo?-P=W1qO^LS|7B3_74CC}hL zz3A#8EJPh>&qb&*v3rdcjphgpow7#)!r6vj^$`EMTQY7>Vf0x|C|z2X=88(Es$Wpd zHFs%UUmw|x)2>tVTNW+%up&EMJXNA_h1;G+K!uNOUV~60SG3*y(}crk23reiT&1{4 z>zONJbO=ez?`Na`94G}Zi)e3=XoOb3CVz?|k`juKHAK??yzD>;zhQ!xVu2d>JE$$6 zJV~xF-C{6Kd-pQCyCr>c@Gzuq)sn6R6R)tO229vWhoX6hov2N`9;* z?a!%d=Bjql%EKVwPMQFAO8c^jt7mClC{`*yQaK;6eeOEo8EJ%w zw_E!vyEA#eG-lRxm7u333}XK`;8^JmBVRo^CxUm2o40nkZ!J76_tPy?*BSJK z@C)k|QYKjb@{uCT3Z;k)oIJC>&ylmSipW^OPWsVxAQt!zRkGn!lgE*xGc{N`!;08{ z%L7N2JpOVGT5prHVN8jNnMC{8;05VUI8vPitTf+=MVX@*m()om!7ZpJN^vxkJh!vo zzgzO#!&a=*RjkBNRCl6y)ECqjWm&n{-lFp4JrN7uS<5bRkP7Adgnoth=bF7i=5V{$ zoXK($1D(Ed#!@}TpF#l&xIJ(n~-vgYBE0ZG={6OqF#(DsDK^p7hKL~6DB>Iw!> zG?xttFz8AE;@+G0RXjRWXl^Y#2sy=&6Qv*Iba(s#scZ=`*V%y{F=lO)QaEj?N6c1l zAHE_GtDT>2IU+Ki0>mx!&Kq^)?RT@X!*0U9UqbjR)rDnsd;oQ4SY1iUXun=6^;onGarH|;a1$0k^m9Z9T2Qa@44j}v#R!h zGggR<)TvTBsVce!Wuyw0(nX-we5m!kuGsQ7k81F3kZKo--gvAy22IH?I_g~sHj8TljOcoYMSGjCIKrs7XFI8l0tcgFXSj@apt2klF$w$Q&dU$GACOpMI!4 zM5>lh9fNeGk;D6`N!e@Er9WQ!pPvI_rn zo_5gC6DT+Mj9!7Cw^px=RL34+7r^_I(K{>jyxe)l*K&lcYe=$|-~zo& zt&FKR2FWV&mzUyXok3fOGtgyH)$&=P%U)!CB%cfKxSv7 zSI$>)&LRbjukL~i`M%1Yw)(X7rB)C!(lpe&dTCM3pxbJhOAW2GzSNzy{sw5nfW3L{+LY_2F+3>U_YA~w(DB-G zk3OXko!)w-!c>EP7k**Wuhj4N@zsG}(7D?5{w9bg!&P$_7Wu48IpFITbW@dV@Z-H5 zOYC3sNer1QQXUsvNC<5_JQe2W*9ItFpq?A+1*YG~nFo3*mOIx}*3pI@{6B)6As zPItBDRH;M9P}Y*}VGi0Yt?N=J>NZX_GS-~s;ARnns~)+u^6b#t(2_Asvr&h78JrGh6&2YaS?Y#$&DNibNA2*F6^j$~8 z+AunJAE!rgVf6MsE(5hu5CP@t8p0dUng{4{Ryy*)*b$fNeu*&4Nq1+;&u(gmQffzT zmB!i_2w})E)Uv|C+Zd|`(d)tI_J!Vsx@0ZU9{ zIo%4n&o$+|?XnScQ(Ml}jcErUExQ51h8lM=o!tEM*y(J1gtK0rJZBnH(jy#WNlO(f z{}`5M^O}&|6Fgql^RaQZ_MreQ9?j$q@^aqC^%Ba=)vU%)Q;}xW@(DWgtQkc-0r?Tn z=PGJp$GAVA;3gr#hT1;GhHG|ac~!Q1$n^I&DmQNfTp&i+ay5Sod8D^!Zn=^PF-u{S zB<^%z{VJaNa>f&50lItdDflI-RTq|QL-!ulCV#7Dv~SB}M49Fb&Feu!c5I&2l((jA z<^^`E*pdb(fjD(qO7fj}QELCdz`oT0!S{iH(Av;0M_+7}w^vs;t;qRp{Y5c=R1BKG zRn#+3QN(O?H>_<%A>g9arb|t0O`B=fGvfnG);981r^4yS zHs4Jw!Kb)<=MJRQCAP&L5PURRuFIQWXIu^^0U=KCH4?yi@5yt>u8i+8vQnNGIJmK1 z_JNoAawCOw7H?QOdok%V9v`I`B)lMV}cGal)ZZRj$%%3Kvy^UBq$!ba1Bo|7wzaS?aJ;yGq_^XJ#>THE3cHg`k)Hk9-R zT=gw#!kJ2V^@QdPUYBdOK-9#0h-9w%sSU-x0^~SAvcXx`)9b^pXPXjb?aur*be$nG z9zbop*jx2xv6$wNCgjCW>+-pbC_%>ezi?IpFS-Udbb2-=kMhuU*p~^xJ|HPbF zLKEI(PY#I^^u`#ZoxT0_d`dALso?QGwQz;({7Qs{G>=%MptM%@8RP?=Ad?O=aM^KPfz6XnGQYmA$kTwqZf7ggm_+SJ{UukxsExml^mmLXZOvxz1wXy#z;AROBExl2m7A3s|`hN0;O1twnXb5F& zk|OOPGHI46)8?B>et|CHIUraZ8xdkSnIP)j1h=#yh(+vScgsbf$?M33z6WYlFEf^{ zsLM#j`gRw(tcRKB6F}GlqRPU-(+ZW_R9uXMh|K!23)ve0VfTa73ijL%u$j5>ZU;c9 zh4Eq+Y7Gd3Ogl)Mj%3u-Izu@#nR3H0rC25~l$mlnz<{7_CXD}ot=POe?&D>2hBU6Ty+Rv#C9W*q+vFul9u?<(l~x$;j80* z`le7VbcmG{s5wy2PT83181nQb?<^?hvVqEGG1d8NA9~uB1!jiQoSPnGF?G`nSuJ~i zmNo#hZJLieej+f8&D;D+v)StPpJ{wn1daBjFcYjcVQ9L>9@c#b-D9?O%qC&j>eLH} z!0Oag4=KzJ+qxiy)oC!kEj=Dcd*-js#eMy5>ciYx?Y=tLp+;l_BT3&V;mzzw!C^4b>C%tl^5D0s)GiE+Q=NL1Gda>k&iXTv?0nD&Y*%9nARUXyvW@^n zjD_!P9jUvIi48~vQ5C)9r?45V=mbcaOYue%8yN#KK%=bu?5H5vAe0NRL1-}dM^6KC z0~=xD>aPEl%H_bw!Nzq>Ze$7@S4W%#us#1`8k57shO`0?RSzZQ0Gp)#ExB%mh$5ZV zv<9s>fx4J5HNf=tIkkSMMv{A%xa4`m{C_3kqB3{|#LCU&U>I zhr?)_qje~?$)-3#V}FgW;wh zj>F}V>!kIyBBouN&1aSh!kgy0gQb&gk{?z?%ok;hj9>G3fYDRfCVRjvSrtfSmVDhJ z&5{e4O^Q~@av)u^WI3}HQB#asNw#qol_}{e37+*>?f;EedCR=< z@*=uiNRRIQH;!9rl^b#6DIq_E&)?GnEZ0yG?*(!X-!L}m(Q+8FX$pnX5G=Y0axbv6 zY)kV`-CS@!q(Nk?J7h8VOrR&wE7T-56qrcmze@fzub(JQLhI$5ye_&zz zpauHTm_nvkR*FGc$|Yw-N}qOC<4W9glvIW2u1NyQ7B+=5yIk}pe391Abi zZ;EpnE9B3}VUz!BBlhlJ>wn>eH77EX2BNGXHj-lMB1MppHTVX$LK9aWG4#GLCPA_< z3B)ij1Ofu$D3yoR_Q@3S4J!J7)YD42?V$IZ7MgoH?!9RF{UfvQz7<)X|b zOgCb*{`^%f|Yp%V9v<|$Q^Q-2-+j<)LvY#9#P_2q6ulda> z=U4>OTU*gIE5mA}KX3;P8-uEjf`UyR7Cp(Oq_kIX{HO0@;D;2ToPu45x(1s%{6)1^ zH)EjDxH8~1M_?SeoKN|!+>$NcGvoofQ&iEuHdRAmlddgZrDo7Xc3_rtax>JML3^u+ z^OS!dIr{H4!!qpc|6Y~BoSrr;m9sCnZKtc)%2Jv`HQhpbH*$WzNc06R}hBm(Nw2CAag~_ zfm&b9^Q)z&hFk95B<00~C+CLMNgwj{Ouey;f5o95}GB${}U>tZCRtrzh2|XMHJHh3BfbMCG7;!T2Q^WOjs(9PA>hlpxURyfg^B@ zD%|Vvr0hRteT|}N`6n%y|8-n65u(ijttG96e@@|y#!jnIQ9gNg zDcmi4S4hXj5@V~!mufU21z&0N@js5s;8?0S5cGrj7y{0@S;oZ5J8rXmgFi>CTYwNF zu=-)6BOS&l;#(Yiy^1jiL$Y=w_SIg}yN!z#Vg|~xiP^~OgNL+rUyDF>crJ|%+H_1) zF57*clsK_9HdMR+l@abbJX2s2r5mAg&Mou0wX@`kx3X6FC+R?)k3VF<3lcMoP}}cN z`wnm}AH3ZgYi_A;Q^46;M=UVo7#kz*+IvsS>Hxnx?iJULF<8ZA4*`OmM3-#*Hh7<1 z{4xo_{Z4NbWV=}4-lMD`X+cy6Bv>zfzefr&lGGgtA4QkgIYj@ech*sV24AvPnReK2 zp>ei+`g}#ya@6o>O=%pJHv1p`ZpnBo6-{|wF@3a#CoH9|9r5|!-UG!!|AB_Jo@7KL zjl^2d|Mf0r)IvtTYb|ORDt`Lc5zvUGrVa}ho*>DC`LEZUQ5Q1$U2FMYZqafQN(vh} zE{OK(H+N1IwuroWo_$e`VbQ^AOm~L#Su8E%-*1Urb~gVHxQ5k)n}%naAy8BK zKfraVYbb1+X2yP@dl`}RFZk4=wUC}Lp`@NBZ$s-16#FyC(s=_F?S=H04YZ2Wi5sXb zQrEOWm(@Nab-+(q$p4GL9Ahw|H(IQ_k$iij1!W!gw+ej`C^_z^%O<+p+ms#}`8L6O zEj7*>>HcC+DE>znj%d0FSR&=N=nFc@U+6{uCF^+xSSD}Y4#mPX`0qQ z-OOov!i-odIvDk&ozU8L>2t2rawK;-+raV{ywmz;>^wCFR1Ep|MjTUBY&W%WNv$#e z6MbKsVH$>7&1w4PrR8VPH_2l^Y?W(xMgyV++4;{IK$8pqc}$pgz_b=JAPcL_|KdT(;Dgk2n5mj!_72)K%yd@R*1D9z zv0U|?(s`txrE|iK(XF400}kQE5P}>w_FbF9^Y)kt%ZjO^JWNCBtLMzGP0JTK4NZ5F z{RkWn%Lla0?NOAYh>h*tWoN5e4-TTJa%OR-RwGRALEQi#+_!q1m=WnZD3I&s0A0Si z0V=22ioA(AM)t+?H6jW(b{S)KQY_jq&j18(q3XTaw{b`2_A>#YoWe3wyODUE^~DI2 zm!bMjavBLcEb&`FM)89|fRDRq%}CrFJ=slezv7f+3Dy1;C(n2zuLFM~aQilx7_a-) zR5H+EkCaP^Vc!l29aoU!$6%MkPs_|l4tIFbAN{^-V0SPF_ea_Qi`SW*sKh9fjbY^; z3L0f9Z2JeG+3jWi9_lj6)Bz^TCr6>lVf*OOC=gNF{bm(moX77Y@6p(M&DJ`rh z03J!O6`pSBJqDK39tS9H47zZtq4cJDf3B9Hdc%0E2*Oba!xw!(a?-P;{JZA|1RX~X zFBWJ%_5j(9MI9h(%dzN6#)P_nBPc)wIUSaMIH!Lq*c^rL;WwHw844Vxt6VnY z+J}LJJdZXN%G|=|;<$&XRmJZ=dxVOP14rLJBKr_i>ujw&bILyv5F!BPFVtonrb~z4 z&+l&$4w!jS4H6!1F_MLHz*1MCspq{gEmc7O{jt)C8RG>z=`DkaHZ| z50G0zX?7Zgk0qZb3_DJq6H#^o%JO9E`nr`(=b^u31_T0)@?4mX9G+O)TOE%+IeVTx za#Y}SFjQBRwJZb#dyJhc5&mdYgFCkYq2igXMRa~Z7_N^d`$){fFgke;F)rKDxn);%Sd6T8!0_fa^7 zb|7V<*NAIP1DuP%)efW$Kzt9~A>^OHn+|1Z>B0LG$`{QZ)2*4nxGg5QBkT{z1 zPF{N43Q+_(95p<8<<+il+csKc&8d2Z>P*3av;YKKq2>0N`(VX(Z%->kpEDE=h@c68 z;6t%)Awi~LKh_;>g;=6;Hq6?6?(*FQGp#v$&QLtcnhT(JOtGVlqmG|CJ*wASU@_sIlmWB%D1R_9Z02^v-o#xNKcUkLk?#Rv$aaakOa*`7WtC`rn_FCx zGoj|9?yJswWyxwlKy=~8gm}LvK{xFM4DocpNSZejoWV=N_m{|f8tUtNiONm`(R)*? zthVlf5f~&M!H^Pr=ez&5&k5tC{8VBt%ZB=#+IGldD&T<}o^g=F&1w5I@JDNYBi+RY zKis`cNvK=CMepK;L4Jk8+%NkV!|h!Q^pLVi2;d35DL5bAT?J6=aB3QbuI=kh<2lt| zp|~hilZ~EQAXyw~f*%rZ8tNn>9-@PDOQ1naY4yUqejv3VbXh(p?~*A%CvZxB0Y23e z@xb?n7wY?p9Hs;NpJ+%)xShSaOkYpOdT^mPO`UG?H~)4;@>Ju+8zahOnYIbNR%>=R z-9WjN)*e3$0dBc|JZdJG?gRi>F0UzEz2e@m8WRAZ)R?{)ca|Vy-X-?#bi5Ti6!Dsi z>2U2Th0H`-2$K5@4u`X7J10h}Js5V_QP(K}6>D#o7SG&NI`iyb^>dY6`f3CC&KwgH zk|XVFPxku7*U5Jl{9IO2*evwC>kaBU3soiEpkHUfVJYc`oZ^vHJ|%81ckwB|HtsEm zb?_!V0c6nan{p|?98`1F@)ZN}`{?uGXwJzf-A$D!Zp5Felcvsp03>zh=5 zHd?{Mt&L}!HU!PPC0C92qnAuRAK%N_pr3+YSWQgF1C!S<)ZuxXg6Dvdq?aFcn*(1d z8Q6S-IbF^aI#Wkl5shx%>>cF<{=^wK*y?&T_)^70r=oKqi-l#Ads>4hHiU|rYuaGY z-J?1&7>r&tBnDjOO6{KD@ePcjPIt^kCw%DZS4=2k$?dQtGy5froFa6bfj5)i)9g-J z#7{>_KaY{IA`bsFv8rNRklNtpc7=o>3hg0O^>`vuGp#}=-((n#n`$b*cV}-hk zKpd=8&0jXS%ad&RPg!&Rw4rFUpwCB@bHGDR)YFkpqH(<)Jw#$2plWUJ@;-Y`FQ-4O zWv7wXe3N_NGgbNM%(0!?IF#*Y1z1``cG=%yP~#ih>bJM%Orl;UTSb^9D4z?+?YR@V z)X}xZYb(?Onui9Uu5U+TK`2uJ7gXoirg@V$^tZx6SSHQKEPniiDlP!yRfiiGTWweT zP$p;9*|QRSSQ!Wi0{0yROR|I5^ja;3{doMlzQ|EgKwO^E_yrj4JWnZZ0Y_ek{V5ei`<82f;EDhC(Z}w8 ztk`ozw2Qfdz}io#Ekk?+1gkF<3*U5nv{Zy#SIV5KL-oa9z8dTf$AEW>Ob-;V{*!k^ zrPaK<_MpVIb_nMrJmUC4h^Y2Zcs_Na;@{vkN%>r2y6<@E)4tZJmv3+)n$iE%SN{%^ zpRa8IUG0IG)KylPX6tRJT~DyDcm%xPbm?Whx8L>cN9%lmF0Ix{T{k+pO3WiY62lv! z_?4JxL#bUXew|1IkeJJ&G)uEhvFA&V80fvfn&mOC4*iA!LqWRf$ycf2xK7DLG zk}+#abCU55PPtxZO;ECb5DD`mfAK3zyAG) zkN>>|)_*sl{=o~)|MU4Sy_a4sec#DVY5j_0JI+~ro@33jAqE!XvF|9?ZU~nD;lhU_j~_%+NTH=+Hb0)Z$O>*o4ke` z+$VFVZOXmIWz?GdC-KdPTn#%kY-u}U!`_Sd=JjL9tg`3s@2$J|wSxa;>!yNX$CAz) z_RHthLaH>0@4dIoBnN!+?zwdKGV|xJs9xNn^4DDX*|Bmp^N}L>=Ec3MPlU_hJwN!& zFM}U=L7-7_iw>{#i?5Au?%wiKPoh`0DIC|13LP+c<=Ju_Cpq}}@Yr?l_l1TnK8$ZZ z{<_s?SZDk1zPWUYIs;4}O=CETrDdG7r6WlEd*dY;ZsPFrq<4dIjE)Gws>wL^FJA8L zZHM*uMj+n}-y8HnvEO{NZCjCb^?b_y!soyxd#Wzv}sVOZ`pH=Ps*L|FUMEEAJ*?) zU|W1caS0#L?QFH{9sP}I4+nc8pUdZL6XsfQ`q!tDc1doTza4tCPPe(^AyZ|;sMAMG zn@=CfQLWqNwx#UW6r?$Ab7UL!qMhC57wvK`{{NQ3^#G!n!D7$!#mD$nrk^;*AHmhI u2W$evgniq!j`RC5aUIwP5s=sqQl&b5`APmru7v#%!5#azbDiQ>V*~(_r&Nmo diff --git a/eslint.config.mjs b/eslint.config.mjs index a6a142d197..455e69463b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,11 +1,13 @@ // @ts-check +import { fixupPluginRules, includeIgnoreFile } from "@eslint/compat"; +import eslint from "@eslint/js"; +import eslintConfigPrettier from "eslint-config-prettier"; +import prettier from "eslint-plugin-prettier"; import react from "eslint-plugin-react"; import eslintReactHooks from "eslint-plugin-react-hooks"; -import eslint from "@eslint/js"; +import path from "path"; import tseslint from "typescript-eslint"; -import { fixupPluginRules, includeIgnoreFile } from "@eslint/compat"; import { fileURLToPath } from "url"; -import path from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -15,12 +17,13 @@ export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, includeIgnoreFile(gitignorePath), + eslintConfigPrettier, { plugins: { - // @ts-expect-error Types mismatch for eslint-plugin-react react, // @ts-expect-error https://github.com/facebook/react/pull/28773#issuecomment-2147149016 "react-hooks": fixupPluginRules(eslintReactHooks), + prettier, }, languageOptions: { ecmaVersion: 2020, @@ -40,11 +43,12 @@ export default tseslint.config( }, rules: { - "@typescript-eslint/no-empty-interface": "off", + "prettier/prettier": "error", "react/react-in-jsx-scope": "off", "react/jsx-no-useless-fragment": ["error", { allowExpressions: true }], "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", + "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-unused-vars": [ "error", { diff --git a/lib/lambda/processEmailsHandler.test.ts b/lib/lambda/processEmailsHandler.test.ts index af672db109..91017c9649 100644 --- a/lib/lambda/processEmailsHandler.test.ts +++ b/lib/lambda/processEmailsHandler.test.ts @@ -131,4 +131,4 @@ describe("process emails Handler", () => { await handler(mockEvent, {} as Context, callback); expect(secSPY).toHaveBeenCalledTimes(2); }); -}); \ No newline at end of file +}); diff --git a/lib/lambda/submit/submissionPayloads/app-k.ts b/lib/lambda/submit/submissionPayloads/app-k.ts index 3253d27ee8..ab36f31841 100644 --- a/lib/lambda/submit/submissionPayloads/app-k.ts +++ b/lib/lambda/submit/submissionPayloads/app-k.ts @@ -1,18 +1,12 @@ import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const appK = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["app-k"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["app-k"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -28,10 +22,7 @@ export const appK = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/capitated-initial.ts b/lib/lambda/submit/submissionPayloads/capitated-initial.ts index 4cecba5a28..7debb8cebd 100644 --- a/lib/lambda/submit/submissionPayloads/capitated-initial.ts +++ b/lib/lambda/submit/submissionPayloads/capitated-initial.ts @@ -1,18 +1,12 @@ import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const capitatedInitial = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["capitated-initial"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["capitated-initial"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -28,10 +22,7 @@ export const capitatedInitial = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/capitated-renewal.ts b/lib/lambda/submit/submissionPayloads/capitated-renewal.ts index 6d89027c15..f5aa4bc5dc 100644 --- a/lib/lambda/submit/submissionPayloads/capitated-renewal.ts +++ b/lib/lambda/submit/submissionPayloads/capitated-renewal.ts @@ -1,20 +1,14 @@ // can/should add the additional frontend checks here import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const capitatedRenewal = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["capitated-renewal"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["capitated-renewal"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -30,10 +24,7 @@ export const capitatedRenewal = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/contracting-amendment.ts b/lib/lambda/submit/submissionPayloads/contracting-amendment.ts index f9eb70e633..3167db306d 100644 --- a/lib/lambda/submit/submissionPayloads/contracting-amendment.ts +++ b/lib/lambda/submit/submissionPayloads/contracting-amendment.ts @@ -1,20 +1,14 @@ // can/should add the additional frontend checks here import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const contractingAmendment = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["contracting-amendment"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["contracting-amendment"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -30,10 +24,7 @@ export const contractingAmendment = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/contracting-initial.ts b/lib/lambda/submit/submissionPayloads/contracting-initial.ts index a05a29422b..3c2ef84dce 100644 --- a/lib/lambda/submit/submissionPayloads/contracting-initial.ts +++ b/lib/lambda/submit/submissionPayloads/contracting-initial.ts @@ -1,18 +1,12 @@ import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const contractingInitial = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["contracting-initial"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["contracting-initial"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -28,10 +22,7 @@ export const contractingInitial = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/contracting-renewal.ts b/lib/lambda/submit/submissionPayloads/contracting-renewal.ts index bb1f60cff0..6adb5d2535 100644 --- a/lib/lambda/submit/submissionPayloads/contracting-renewal.ts +++ b/lib/lambda/submit/submissionPayloads/contracting-renewal.ts @@ -1,20 +1,14 @@ // can/should add the additional frontend checks here import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const contractingRenewal = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["contracting-renewal"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["contracting-renewal"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -30,10 +24,7 @@ export const contractingRenewal = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/new-chip-submission.ts b/lib/lambda/submit/submissionPayloads/new-chip-submission.ts index b79cf7bbd0..822f58a695 100644 --- a/lib/lambda/submit/submissionPayloads/new-chip-submission.ts +++ b/lib/lambda/submit/submissionPayloads/new-chip-submission.ts @@ -1,18 +1,12 @@ import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const newChipSubmission = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["new-chip-submission"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["new-chip-submission"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -28,10 +22,7 @@ export const newChipSubmission = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/new-medicaid-submission.ts b/lib/lambda/submit/submissionPayloads/new-medicaid-submission.ts index 91ed571e7d..046916a656 100644 --- a/lib/lambda/submit/submissionPayloads/new-medicaid-submission.ts +++ b/lib/lambda/submit/submissionPayloads/new-medicaid-submission.ts @@ -1,9 +1,5 @@ import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; @@ -28,10 +24,7 @@ export const newMedicaidSubmission = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/temporary-extension.ts b/lib/lambda/submit/submissionPayloads/temporary-extension.ts index 9b8ca9919c..87f3dba1de 100644 --- a/lib/lambda/submit/submissionPayloads/temporary-extension.ts +++ b/lib/lambda/submit/submissionPayloads/temporary-extension.ts @@ -1,20 +1,14 @@ // can/should add the additional frontend checks here import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const temporaryExtension = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["temporary-extension"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["temporary-extension"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; } @@ -34,10 +28,7 @@ export const temporaryExtension = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/toggle-withdraw-rai.ts b/lib/lambda/submit/submissionPayloads/toggle-withdraw-rai.ts index 49b5a82552..b98c5a5e85 100644 --- a/lib/lambda/submit/submissionPayloads/toggle-withdraw-rai.ts +++ b/lib/lambda/submit/submissionPayloads/toggle-withdraw-rai.ts @@ -1,18 +1,12 @@ import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const toggleWithdrawRai = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["toggle-withdraw-rai"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["toggle-withdraw-rai"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; @@ -28,10 +22,7 @@ export const toggleWithdrawRai = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/submit/submissionPayloads/withdraw-rai.ts b/lib/lambda/submit/submissionPayloads/withdraw-rai.ts index 1aee37305f..cf28796321 100644 --- a/lib/lambda/submit/submissionPayloads/withdraw-rai.ts +++ b/lib/lambda/submit/submissionPayloads/withdraw-rai.ts @@ -1,18 +1,12 @@ import { events } from "shared-types/events"; -import { - isAuthorized, - getAuthDetails, - lookupUserAttributes, -} from "../../../libs/api/auth/user"; +import { isAuthorized, getAuthDetails, lookupUserAttributes } from "../../../libs/api/auth/user"; import { type APIGatewayEvent } from "aws-lambda"; import { itemExists } from "libs/api/package"; export const withdrawRai = async (event: APIGatewayEvent) => { if (!event.body) return; - const parsedResult = events["withdraw-rai"].baseSchema.safeParse( - JSON.parse(event.body), - ); + const parsedResult = events["withdraw-rai"].baseSchema.safeParse(JSON.parse(event.body)); if (!parsedResult.success) { throw parsedResult.error; @@ -28,10 +22,7 @@ export const withdrawRai = async (event: APIGatewayEvent) => { } const authDetails = getAuthDetails(event); - const userAttr = await lookupUserAttributes( - authDetails.userId, - authDetails.poolId, - ); + const userAttr = await lookupUserAttributes(authDetails.userId, authDetails.poolId); const submitterEmail = userAttr.email; const submitterName = `${userAttr.given_name} ${userAttr.family_name}`; diff --git a/lib/lambda/update/adminChangeSchemas.ts b/lib/lambda/update/adminChangeSchemas.ts index 274c8b4af0..4404d97749 100644 --- a/lib/lambda/update/adminChangeSchemas.ts +++ b/lib/lambda/update/adminChangeSchemas.ts @@ -47,4 +47,4 @@ export const transformedUpdateIdSchema = updateIdAdminChangeSchema.transform((da packageId: data.id, id: `${data.id}`, timestamp: Date.now(), -})); \ No newline at end of file +})); diff --git a/lib/libs/api/kafka.ts b/lib/libs/api/kafka.ts index 8d92a814f1..1885a291ba 100644 --- a/lib/libs/api/kafka.ts +++ b/lib/libs/api/kafka.ts @@ -19,11 +19,7 @@ export function getProducer() { return kafka.producer(); } -export async function produceMessage( - topic: string, - key: string, - value: string, -) { +export async function produceMessage(topic: string, key: string, value: string) { producer = producer || getProducer(); await producer.connect(); @@ -35,11 +31,7 @@ export async function produceMessage( }; console.log( "About to send the following message to kafka\n" + - JSON.stringify( - { ...message, value: JSON.parse(message.value as string) }, - null, - 2, - ), + JSON.stringify({ ...message, value: JSON.parse(message.value as string) }, null, 2), ); try { await producer.send({ diff --git a/lib/libs/api/statusMemo.ts b/lib/libs/api/statusMemo.ts index ce050dfefe..73f5fcdc1b 100644 --- a/lib/libs/api/statusMemo.ts +++ b/lib/libs/api/statusMemo.ts @@ -1,7 +1,7 @@ export function buildStatusMemoQuery( id: string, msg: string, - operation: "insert" | "update" = "update" + operation: "insert" | "update" = "update", ) { const printable = new Date().toLocaleString("en-US", { timeZone: "America/New_York", diff --git a/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/MedSpaCMS.tsx b/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/MedSpaCMS.tsx index f994c33117..f606bdcf5a 100644 --- a/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/MedSpaCMS.tsx +++ b/lib/libs/email/content/uploadSubsequentDocuments/emailTemplates/MedSpaCMS.tsx @@ -10,7 +10,7 @@ import { BaseEmailTemplate } from "../../email-templates"; export const MedSpaCMSEmail = ({ variables, }: { - variables: Events["UploadSubsequentDocuments"] & CommonEmailVariables + variables: Events["UploadSubsequentDocuments"] & CommonEmailVariables; }) => { return ( attr.Name === "custom:state"); return stateAttribute?.Value?.split(",").includes(state); }).map((user) => { - const attributes = user.Attributes?.reduce((acc, attr) => { - acc[attr.Name as any] = attr.Value; - return acc; - }, {} as Record); + const attributes = user.Attributes?.reduce( + (acc, attr) => { + acc[attr.Name as any] = attr.Value; + return acc; + }, + {} as Record, + ); return { firstName: attributes?.["given_name"], lastName: attributes?.["family_name"], diff --git a/lib/libs/email/index.ts b/lib/libs/email/index.ts index 27f9002b3d..da2eea2fff 100644 --- a/lib/libs/email/index.ts +++ b/lib/libs/email/index.ts @@ -101,7 +101,10 @@ export async function getEmailTemplates( } // I think this needs to be written to handle not finding any matching events and so forth -export async function getLatestMatchingEvent(id: string, actionType: string): Promise { +export async function getLatestMatchingEvent( + id: string, + actionType: string, +): Promise { try { const item = await getPackageChangelog(id); diff --git a/lib/libs/email/mock-data/withdraw-rai.ts b/lib/libs/email/mock-data/withdraw-rai.ts index fe6ee50a33..32b31b3801 100644 --- a/lib/libs/email/mock-data/withdraw-rai.ts +++ b/lib/libs/email/mock-data/withdraw-rai.ts @@ -16,7 +16,6 @@ export const emailTemplateValue: Omit ], label: "CMS Form 179", }, - }, additionalInformation: "This some additional information about the request to withdraw and what makes it important.", diff --git a/lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Capitated.tsx b/lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Capitated.tsx index d2d072419f..ac56fc751b 100644 --- a/lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Capitated.tsx +++ b/lib/libs/email/preview/InitialSubmissions/CMS/Waiver_Capitated.tsx @@ -1,5 +1,5 @@ import { Waiver1915bCMSEmail } from "libs/email/content/newSubmission/emailTemplates"; -import { emailTemplateValue } from "libs/email/mock-data/new-submission" ; +import { emailTemplateValue } from "libs/email/mock-data/new-submission"; export const Waiver1915bCMSCapitatedInitialEmailPreview = () => { return ( diff --git a/lib/libs/env.ts b/lib/libs/env.ts index 9c00fcf01d..f84024c037 100644 --- a/lib/libs/env.ts +++ b/lib/libs/env.ts @@ -1,8 +1,6 @@ export function checkEnvVars(requiredVars: string[]) { const missingVars = requiredVars.filter((v) => !process.env[v]); if (missingVars.length > 0) { - throw new Error( - `Missing required environment variables: ${missingVars.join(", ")}` - ); + throw new Error(`Missing required environment variables: ${missingVars.join(", ")}`); } } diff --git a/lib/libs/handler-lib.ts b/lib/libs/handler-lib.ts index 8abfce1ad4..5eb9dc0035 100644 --- a/lib/libs/handler-lib.ts +++ b/lib/libs/handler-lib.ts @@ -1,14 +1,7 @@ -import type { - APIGatewayEvent, - APIGatewayProxyResult, - Context, -} from "aws-lambda"; +import type { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda"; export const handler = async ( - handler: ( - event?: APIGatewayEvent, - context?: Context, - ) => Promise, + handler: (event?: APIGatewayEvent, context?: Context) => Promise, ) => { const handlerResponse = await handler(); diff --git a/lib/libs/tests/env.test.ts b/lib/libs/tests/env.test.ts index d5bca41466..07d78284a8 100644 --- a/lib/libs/tests/env.test.ts +++ b/lib/libs/tests/env.test.ts @@ -13,12 +13,12 @@ describe("checkEnvVars", () => { test("should throw an error when a required environment variable is missing", () => { process.env.FOO = "bar"; expect(() => checkEnvVars(["FOO", "BAZ"])).toThrowError( - "Missing required environment variables: BAZ" + "Missing required environment variables: BAZ", ); }); test("should throw an error when multiple required environment variables are missing", () => { expect(() => checkEnvVars(["FOO", "BAZ"])).toThrowError( - "Missing required environment variables: FOO, BAZ" + "Missing required environment variables: FOO, BAZ", ); }); }); diff --git a/lib/libs/topics-lib.ts b/lib/libs/topics-lib.ts index 4248a23866..2ab882b3d8 100644 --- a/lib/libs/topics-lib.ts +++ b/lib/libs/topics-lib.ts @@ -7,10 +7,7 @@ interface TopicConfig { // Add other properties as needed } -export async function createTopics( - brokerString: string, - topicsConfig: TopicConfig[], -) { +export async function createTopics(brokerString: string, topicsConfig: TopicConfig[]) { const topics = topicsConfig; const brokers = brokerString.split(","); @@ -25,10 +22,7 @@ export async function createTopics( await admin.connect(); // Fetch topics from MSK and filter out __ internal management topic - const existingTopicList = _.filter( - await admin.listTopics(), - (n) => !n.startsWith("_"), - ); + const existingTopicList = _.filter(await admin.listTopics(), (n) => !n.startsWith("_")); console.log("Existing topics:", JSON.stringify(existingTopicList, null, 2)); @@ -54,8 +48,7 @@ export async function createTopics( topicsMetadata, (topicConfig, topicMetadata) => _.get(topicConfig, "topic") === _.get(topicMetadata, "name") && - _.get(topicConfig, "numPartitions") > - _.get(topicMetadata, "partitions", []).length, + _.get(topicConfig, "numPartitions") > _.get(topicMetadata, "partitions", []).length, ); // Create a collection to update topic partitioning @@ -81,14 +74,8 @@ export async function createTopics( console.log("Topics to Create:", JSON.stringify(topicsToCreate, null, 2)); console.log("Topics to Update:", JSON.stringify(topicsToUpdate, null, 2)); - console.log( - "Partitions to Update:", - JSON.stringify(partitionConfig, null, 2), - ); - console.log( - "Topic configuration options:", - JSON.stringify(configs, null, 2), - ); + console.log("Partitions to Update:", JSON.stringify(partitionConfig, null, 2)); + console.log("Topic configuration options:", JSON.stringify(configs, null, 2)); // Create topics that don't exist in MSK await admin.createTopics({ topics: topicsToCreate }); diff --git a/lib/libs/webforms/ABP1/v202401.ts b/lib/libs/webforms/ABP1/v202401.ts index 1f208e4b7c..cb661fb6a3 100644 --- a/lib/libs/webforms/ABP1/v202401.ts +++ b/lib/libs/webforms/ABP1/v202401.ts @@ -62,8 +62,7 @@ export const v202401: FormSchema = { value: "extend_medicaid_earnings", }, { - label: - "Extended Medicaid Due to Spousal Support Collections", + label: "Extended Medicaid Due to Spousal Support Collections", value: "extend_medicaid_spousal_support_collect", }, { @@ -81,8 +80,7 @@ export const v202401: FormSchema = { { label: "Children with Title IV-E Adoption Assistance, Foster Care or Guardianship Care", - value: - "children_title_IV-E_adoption_assist_foster_guardianship_care", + value: "children_title_IV-E_adoption_assist_foster_guardianship_care", }, { label: "Former Foster Care Children", @@ -97,15 +95,12 @@ export const v202401: FormSchema = { value: "ssi_beneficiaries", }, { - label: - "Aged, Blind and Disabled Individuals in 209(b) States", + label: "Aged, Blind and Disabled Individuals in 209(b) States", value: "aged_blind_disabled_individuals_209b_states", }, { - label: - "Individuals Receiving Mandatory State Supplements", - value: - "individuals_receiving_mandatory_state_supplements", + label: "Individuals Receiving Mandatory State Supplements", + value: "individuals_receiving_mandatory_state_supplements", }, { label: "Individuals Who Are Essential Spouses", @@ -122,20 +117,17 @@ export const v202401: FormSchema = { { label: "Individuals Who Lost Eligibility for SSI/SSP Due to an Increase in OASDI Benefits in 1972", - value: - "lost_eligibility_SSI_SSP_increase_in_OASDI_benefits_1972", + value: "lost_eligibility_SSI_SSP_increase_in_OASDI_benefits_1972", }, { label: "Individuals Eligible for SSI/SSP but for OASDI COLA increases since April, 1977", - value: - "eligible_SSI_SSP_but_for_OASDI_COLA_increases_April_1977", + value: "eligible_SSI_SSP_but_for_OASDI_COLA_increases_April_1977", }, { label: "Disabled Widows and Widowers Ineligible for SSI due to Increase in OASDI", - value: - "disabled_widows_ineligible_SSI_due_to_increase_OASDI", + value: "disabled_widows_ineligible_SSI_due_to_increase_OASDI", }, { label: @@ -168,13 +160,11 @@ export const v202401: FormSchema = { value: "qualifying_individuals", }, { - label: - "Optional Coverage of Parents and Other Caretaker Relatives", + label: "Optional Coverage of Parents and Other Caretaker Relatives", value: "opt_coverage_parents_other_caretaker_relatives", }, { - label: - "Reasonable Classifications of Individuals under Age 21", + label: "Reasonable Classifications of Individuals under Age 21", value: "reasonable_class_under_21", }, { @@ -190,15 +180,13 @@ export const v202401: FormSchema = { value: "opt_targeted_low_income_children", }, { - label: - "Individuals Electing COBRA Continuation Coverage", + label: "Individuals Electing COBRA Continuation Coverage", value: "individuals_electing_COBRA_cont_converage", }, { label: "Certain Individuals Needing Treatment for Breast or Cervical Cancer", - value: - "individuals_need_treatment_for_breasts_cervical_cancer", + value: "individuals_need_treatment_for_breasts_cervical_cancer", }, { label: "Individuals with Tuberculosis", @@ -207,25 +195,21 @@ export const v202401: FormSchema = { { label: "Aged, Blind or Disabled Individuals Eligible for but Not Receiving Cash", - value: - "aged_blind_disabled_eligible_but_not_receiving_cash", + value: "aged_blind_disabled_eligible_but_not_receiving_cash", }, { - label: - "Individuals Eligible for Cash except for Institutionalization", + label: "Individuals Eligible for Cash except for Institutionalization", value: "eligible_cash_except_for_institutionalization", }, { label: "Individuals Receiving Home and Community Based Services under Institutional Rules", - value: - "receiving_home_community_services_under_inst_rule", + value: "receiving_home_community_services_under_inst_rule", }, { label: "Optional State Supplement - 1634 States and SSI Criteria States with 1616 Agreements", - value: - "opt_state_supp_1634_states_SSI_criteria_states_1616_agreements", + value: "opt_state_supp_1634_states_SSI_criteria_states_1616_agreements", }, { label: @@ -263,8 +247,7 @@ export const v202401: FormSchema = { value: "ticket_work_medical_imp_group", }, { - label: - "Family Opportunity Act Children with Disabilities", + label: "Family Opportunity Act Children with Disabilities", value: "family_opportunity_act_children_disabilities", }, { @@ -288,8 +271,7 @@ export const v202401: FormSchema = { value: "med_needy_aged_blind_disabled", }, { - label: - "Medically Needy Blind or Disabled Individuals Eligible in 1973", + label: "Medically Needy Blind or Disabled Individuals Eligible in 1973", value: "med_needy_blind_disabled_eligible_1973", }, ], @@ -321,8 +303,7 @@ export const v202401: FormSchema = { ], }, { - description: - "Is enrollment available for all individuals in these eligibility groups?", + description: "Is enrollment available for all individuals in these eligibility groups?", slots: [ { rhf: "Select", @@ -384,13 +365,11 @@ export const v202401: FormSchema = { props: { options: [ { - label: - "Households with income at or below the standard", + label: "Households with income at or below the standard", value: "income_target_below", }, { - label: - "Households with income above the standard", + label: "Households with income above the standard", value: "income_target_above", }, ], @@ -433,14 +412,12 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^\d*\.?\d+$/, - message: - "Must be a positive percentage", + message: "Must be a positive percentage", }, required: "* Required", }, name: "federal_poverty_level_percentage", - label: - "Enter the federal poverty level percentage", + label: "Enter the federal poverty level percentage", }, ], }, @@ -459,8 +436,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^\d*\.?\d+$/, - message: - "Must be a positive percentage", + message: "Must be a positive percentage", }, required: "* Required", }, @@ -474,16 +450,14 @@ export const v202401: FormSchema = { { rhf: "Input", name: "other_percentage", - label: - "Enter the other percentage", + label: "Enter the other percentage", props: { icon: "%", }, rules: { pattern: { value: /^\d*\.?\d+$/, - message: - "Must be a positive percentage", + message: "Must be a positive percentage", }, required: "* Required", }, @@ -495,8 +469,7 @@ export const v202401: FormSchema = { rules: { required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -536,10 +509,8 @@ export const v202401: FormSchema = { label: "Household Size", name: "household_size", props: { - placeholder: - "enter size", - className: - "w-[300px]", + placeholder: "enter size", + className: "w-[300px]", }, rules: { pattern: { @@ -547,8 +518,7 @@ export const v202401: FormSchema = { message: "Must be a positive integer value", }, - required: - "* Required", + required: "* Required", }, }, { @@ -556,21 +526,17 @@ export const v202401: FormSchema = { name: "standard", label: "Standard ($)", props: { - className: - "w-[200px]", - placeholder: - "enter amount", + className: "w-[200px]", + placeholder: "enter amount", icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be a positive number, maximum of two decimals, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -593,21 +559,18 @@ export const v202401: FormSchema = { slots: [ { rhf: "Input", - label: - "Incremental amount ($)", + label: "Incremental amount ($)", name: "dollar_incremental_amount_statewide_std", props: { icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -633,8 +596,7 @@ export const v202401: FormSchema = { props: { ...DefaultFieldGroupProps, appendText: "Add Region", - removeText: - "Remove Region", + removeText: "Remove Region", }, fields: [ { @@ -642,11 +604,9 @@ export const v202401: FormSchema = { name: "name_of_region", label: "Region Name", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - /^\S(.*\S)?$/, + value: /^\S(.*\S)?$/, message: "Must not have leading or trailing whitespace.", }, @@ -657,11 +617,9 @@ export const v202401: FormSchema = { name: "region_description", label: "Description", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -671,53 +629,42 @@ export const v202401: FormSchema = { rhf: "FieldArray", name: "income_definition_region_statewide_arr", props: { - appendText: - "Add household size", + appendText: "Add household size", }, fields: [ { rhf: "Input", - label: - "Household Size", + label: "Household Size", name: "household_size", props: { - placeholder: - "enter size", - className: - "w-[300px]", + placeholder: "enter size", + className: "w-[300px]", }, rules: { pattern: { - value: - /^[0-9]\d*$/, + value: /^[0-9]\d*$/, message: "Must be a positive integer value", }, - required: - "* Required", + required: "* Required", }, }, { rhf: "Input", name: "standard", - label: - "Standard ($)", + label: "Standard ($)", props: { - className: - "w-[200px]", - placeholder: - "enter amount", + className: "w-[200px]", + placeholder: "enter amount", icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -743,15 +690,13 @@ export const v202401: FormSchema = { icon: "$", }, rules: { - pattern: - { - value: - /^\d*(?:\.\d{1,2})?$/, - message: - "Must be all numbers, no commas. e.g. 1234.56", - }, - required: - "* Required", + pattern: { + value: + /^\d*(?:\.\d{1,2})?$/, + message: + "Must be all numbers, no commas. e.g. 1234.56", + }, + required: "* Required", }, }, ], @@ -768,8 +713,7 @@ export const v202401: FormSchema = { ], }, { - label: - "Standard varies by living arrangement", + label: "Standard varies by living arrangement", value: "living_standard", form: [ @@ -780,23 +724,18 @@ export const v202401: FormSchema = { name: "income_definition_specific_statewide_group_liv_arrange", props: { ...DefaultFieldGroupProps, - appendText: - "Add Living Arrangement", - removeText: - "Remove living arrangement", + appendText: "Add Living Arrangement", + removeText: "Remove living arrangement", }, fields: [ { rhf: "Input", name: "name_of_living_arrangement", - label: - "Name of living arrangement", + label: "Name of living arrangement", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - /^\S(.*\S)?$/, + value: /^\S(.*\S)?$/, message: "Must not have leading or trailing whitespace.", }, @@ -807,11 +746,9 @@ export const v202401: FormSchema = { name: "living_arrangement_description", label: "Description", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -821,53 +758,42 @@ export const v202401: FormSchema = { rhf: "FieldArray", name: "income_definition_specific_statewide_arr", props: { - appendText: - "Add household size", + appendText: "Add household size", }, fields: [ { rhf: "Input", - label: - "Household Size", + label: "Household Size", name: "household_size", props: { - placeholder: - "enter size", - className: - "w-[300px]", + placeholder: "enter size", + className: "w-[300px]", }, rules: { pattern: { - value: - /^[0-9]\d*$/, + value: /^[0-9]\d*$/, message: "Must be a positive integer value", }, - required: - "* Required", + required: "* Required", }, }, { rhf: "Input", name: "standard", - label: - "Standard ($)", + label: "Standard ($)", props: { - className: - "w-[200px]", - placeholder: - "enter amount", + className: "w-[200px]", + placeholder: "enter amount", icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -893,15 +819,13 @@ export const v202401: FormSchema = { icon: "$", }, rules: { - pattern: - { - value: - /^\d*(?:\.\d{1,2})?$/, - message: - "Must be all numbers, no commas. e.g. 1234.56", - }, - required: - "* Required", + pattern: { + value: + /^\d*(?:\.\d{1,2})?$/, + message: + "Must be all numbers, no commas. e.g. 1234.56", + }, + required: "* Required", }, }, ], @@ -918,8 +842,7 @@ export const v202401: FormSchema = { ], }, { - label: - "Standard varies in some other way", + label: "Standard varies in some other way", value: "other_standard", form: [ @@ -930,10 +853,8 @@ export const v202401: FormSchema = { name: "income_definition_specific_statewide_group_other", props: { ...DefaultFieldGroupProps, - appendText: - "Add some other way", - removeText: - "Remove some other way", + appendText: "Add some other way", + removeText: "Remove some other way", }, fields: [ { @@ -941,11 +862,9 @@ export const v202401: FormSchema = { name: "name_of_group", label: "Name", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - /^\S(.*\S)?$/, + value: /^\S(.*\S)?$/, message: "Must not have leading or trailing whitespace.", }, @@ -956,11 +875,9 @@ export const v202401: FormSchema = { name: "group_description", label: "Description", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -970,53 +887,42 @@ export const v202401: FormSchema = { rhf: "FieldArray", name: "income_definition_specific_statewide_arr", props: { - appendText: - "Add household size", + appendText: "Add household size", }, fields: [ { rhf: "Input", - label: - "Household Size", + label: "Household Size", name: "household_size", props: { - placeholder: - "enter size", - className: - "w-[300px]", + placeholder: "enter size", + className: "w-[300px]", }, rules: { pattern: { - value: - /^[0-9]\d*$/, + value: /^[0-9]\d*$/, message: "Must be a positive integer value", }, - required: - "* Required", + required: "* Required", }, }, { rhf: "Input", name: "standard", - label: - "Standard ($)", + label: "Standard ($)", props: { - className: - "w-[200px]", - placeholder: - "enter amount", + className: "w-[200px]", + placeholder: "enter amount", icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -1042,15 +948,13 @@ export const v202401: FormSchema = { icon: "$", }, rules: { - pattern: - { - value: - /^\d*(?:\.\d{1,2})?$/, - message: - "Must be all numbers, no commas. e.g. 1234.56", - }, - required: - "* Required", + pattern: { + value: + /^\d*(?:\.\d{1,2})?$/, + message: + "Must be all numbers, no commas. e.g. 1234.56", + }, + required: "* Required", }, }, ], @@ -1080,8 +984,7 @@ export const v202401: FormSchema = { }, { value: "health", - label: - "Disease, condition, diagnosis, or disorder (check all that apply)", + label: "Disease, condition, diagnosis, or disorder (check all that apply)", slots: [ { rhf: "Checkbox", @@ -1133,8 +1036,7 @@ export const v202401: FormSchema = { { label: "Asthma", value: "asthma" }, { label: "Obesity", value: "obesity" }, { - label: - "Other disease, condition, diagnosis, or disorder", + label: "Other disease, condition, diagnosis, or disorder", value: "other", slots: [ { @@ -1145,8 +1047,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1169,8 +1070,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1242,8 +1142,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1265,8 +1164,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1288,8 +1186,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1311,8 +1208,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, diff --git a/lib/libs/webforms/ABP1/v202402.ts b/lib/libs/webforms/ABP1/v202402.ts index 470f051824..795aff1471 100644 --- a/lib/libs/webforms/ABP1/v202402.ts +++ b/lib/libs/webforms/ABP1/v202402.ts @@ -57,15 +57,13 @@ export const v202402: FormSchema = { value: "adult", }, { - label: - "Aged, blind, and disabled individuals in 209(b) states", + label: "Aged, blind, and disabled individuals in 209(b) states", value: "aged_blind_disabled_individuals_209b_states", }, { label: "Aged, blind, or disabled individuals eligible for but not receiving cash", - value: - "aged_blind_disabled_eligible_but_not_receiving_cash", + value: "aged_blind_disabled_eligible_but_not_receiving_cash", }, { label: "Blind or disabled individuals eligible in 1937", @@ -74,8 +72,7 @@ export const v202402: FormSchema = { { label: "Certain individuals needing treatment for breast or cervical cancer", - value: - "individuals_need_treatment_for_breasts_cervical_cancer", + value: "individuals_need_treatment_for_breasts_cervical_cancer", }, { label: "Children with non-IV-E adoption assistance", @@ -84,8 +81,7 @@ export const v202402: FormSchema = { { label: "Children with Title IV-E adoption assistance, foster care, or guardianship care", - value: - "children_title_IV-E_adoption_assist_foster_guardianship_care", + value: "children_title_IV-E_adoption_assist_foster_guardianship_care", }, { label: "Deemed newborns", @@ -104,17 +100,14 @@ export const v202402: FormSchema = { { label: "Disabled widows and widowers ineligible for SSI due to increase in OASDI", - value: - "disabled_widows_ineligible_SSI_due_to_increase_OASDI", + value: "disabled_widows_ineligible_SSI_due_to_increase_OASDI", }, { - label: - "Extended Medicaid due to spousal support collections", + label: "Extended Medicaid due to spousal support collections", value: "extend_medicaid_spousal_support_collect", }, { - label: - "Family Opportunity Act children with disabilities", + label: "Family Opportunity Act children with disabilities", value: "family_opportunity_act_children_disabilities", }, { @@ -126,31 +119,26 @@ export const v202402: FormSchema = { value: "independent_foster_care_adolescents", }, { - label: - "Individuals eligible for cash except for institutionalization", + label: "Individuals eligible for cash except for institutionalization", value: "eligible_cash_except_for_institutionalization", }, { label: "Individuals eligible for SSI/SSP but for OASDI COLA increases since April 1977", - value: - "eligible_SSI_SSP_but_for_OASDI_COLA_increases_April_1977", + value: "eligible_SSI_SSP_but_for_OASDI_COLA_increases_April_1977", }, { label: "Individuals receiving home and community-based services under institutional rules", - value: - "receiving_home_community_services_under_inst_rule", + value: "receiving_home_community_services_under_inst_rule", }, { label: "Individuals receiving hospice care", value: "hospice_care", }, { - label: - "Individuals receiving mandatory state supplements", - value: - "individuals_receiving_mandatory_state_supplements", + label: "Individuals receiving mandatory state supplements", + value: "individuals_receiving_mandatory_state_supplements", }, { label: "Individuals who are essential spouses", @@ -159,8 +147,7 @@ export const v202402: FormSchema = { { label: "Individuals who lost eligibility for SSI/SSP due to an increase in OASDI benefits in 1972", - value: - "lost_eligibility_SSI_SSP_increase_in_OASDI_benefits_1972", + value: "lost_eligibility_SSI_SSP_increase_in_OASDI_benefits_1972", }, { label: "Individuals with tuberculosis", @@ -171,8 +158,7 @@ export const v202402: FormSchema = { value: "infants_children_under_19", }, { - label: - "Institutionalized individuals continuously eligible since 1973", + label: "Institutionalized individuals continuously eligible since 1973", value: "institutionalized_eligible_1973", }, { @@ -185,10 +171,8 @@ export const v202402: FormSchema = { value: "med_needy_aged_blind_disabled", }, { - label: - "Medically needy blind or disabled individuals eligible in 1973", - value: - "med_needy_aged_blind_disabled_individuals_eligible_in_1973", + label: "Medically needy blind or disabled individuals eligible in 1973", + value: "med_needy_aged_blind_disabled_individuals_eligible_in_1973", }, { label: "Medically needy children age 18 through 20", @@ -207,15 +191,13 @@ export const v202402: FormSchema = { value: "med_needy_pregnant_women", }, { - label: - "Optional coverage of parents and other caretaker relatives", + label: "Optional coverage of parents and other caretaker relatives", value: "opt_coverage_parents_other_caretaker_relatives", }, { label: "Optional state supplement - 1634 states and SSI criteria states with 1616 agreements", - value: - "opt_state_supp_1634_states_SSI_criteria_states_1616_agreements", + value: "opt_state_supp_1634_states_SSI_criteria_states_1616_agreements", }, { label: @@ -244,8 +226,7 @@ export const v202402: FormSchema = { value: "qualified_disabled_children_under_19", }, { - label: - "Reasonable classifications of individuals under age 21", + label: "Reasonable classifications of individuals under age 21", value: "reasonable_class_under_21", }, { @@ -302,8 +283,7 @@ export const v202402: FormSchema = { ], }, { - description: - "Is enrollment available for all individuals in these eligibility groups?", + description: "Is enrollment available for all individuals in these eligibility groups?", slots: [ { rhf: "Select", @@ -364,13 +344,11 @@ export const v202402: FormSchema = { props: { options: [ { - label: - "Households with income at or below the standard", + label: "Households with income at or below the standard", value: "income_target_below", }, { - label: - "Households with income above the standard", + label: "Households with income above the standard", value: "income_target_above", }, ], @@ -415,14 +393,12 @@ export const v202402: FormSchema = { rules: { pattern: { value: /^\d*\.?\d+$/, - message: - "Must be a positive percentage", + message: "Must be a positive percentage", }, required: "* Required", }, name: "fed-poverty-level-percent", - label: - "Percentage of federal poverty level", + label: "Percentage of federal poverty level", labelClassName: "font-bold", }, ], @@ -434,8 +410,7 @@ export const v202402: FormSchema = { { rhf: "Input", name: "ssi-fed-benefit-percentage", - label: - "Percentage of SSI federal benefit", + label: "Percentage of SSI federal benefit", labelClassName: "font-bold", props: { icon: "%", @@ -445,8 +420,7 @@ export const v202402: FormSchema = { rules: { pattern: { value: /^\d*\.?\d+$/, - message: - "Must be a positive percentage", + message: "Must be a positive percentage", }, required: "* Required", }, @@ -470,8 +444,7 @@ export const v202402: FormSchema = { rules: { pattern: { value: /^\d*\.?\d+$/, - message: - "Must be a positive percentage", + message: "Must be a positive percentage", }, required: "* Required", }, @@ -484,8 +457,7 @@ export const v202402: FormSchema = { rules: { required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -520,19 +492,16 @@ export const v202402: FormSchema = { rhf: "FieldArray", name: "income-def-specific-state", props: { - appendText: - "Add standard", + appendText: "Add standard", }, fields: [ { rhf: "Input", label: "Household size", name: "house-size", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { - className: - "w-[150px]", + className: "w-[150px]", }, rules: { pattern: { @@ -540,30 +509,25 @@ export const v202402: FormSchema = { message: "Must be a positive integer value", }, - required: - "* Required", + required: "* Required", }, }, { rhf: "Input", name: "standard", label: "Standard", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { - className: - "w-[200px]", + className: "w-[200px]", icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be a positive number, maximum of two decimals, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -573,8 +537,7 @@ export const v202402: FormSchema = { name: "is-increment-amount", label: "Is there an additional incremental amount?", - labelClassName: - "font-bold", + labelClassName: "font-bold", horizontalLayout: true, props: { @@ -613,8 +576,7 @@ export const v202402: FormSchema = { }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, @@ -637,22 +599,18 @@ export const v202402: FormSchema = { props: { ...DefaultFieldGroupProps, appendText: "Add region", - removeText: - "Remove region", + removeText: "Remove region", }, fields: [ { rhf: "Input", name: "name-of-region", label: "Region name", - labelClassName: - "font-bold", + labelClassName: "font-bold", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - /^\S(.*\S)?$/, + value: /^\S(.*\S)?$/, message: "Must not have leading or trailing whitespace.", }, @@ -662,14 +620,11 @@ export const v202402: FormSchema = { rhf: "Textarea", name: "region-descript", label: "Describe", - labelClassName: - "font-bold", + labelClassName: "font-bold", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -679,52 +634,42 @@ export const v202402: FormSchema = { rhf: "FieldArray", name: "add-house-size", props: { - appendText: - "Add household size", + appendText: "Add household size", }, fields: [ { rhf: "Input", - label: - "Household size", - labelClassName: - "font-bold", + label: "Household size", + labelClassName: "font-bold", name: "house-size", props: { - className: - "w-[150px]", + className: "w-[150px]", }, rules: { pattern: { - value: - /^[0-9]\d*$/, + value: /^[0-9]\d*$/, message: "Must be a positive integer value", }, - required: - "* Required", + required: "* Required", }, }, { rhf: "Input", name: "standard", label: "Standard", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { - className: - "w-[200px]", + className: "w-[200px]", icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -734,8 +679,7 @@ export const v202402: FormSchema = { name: "is-increment-amount", label: "Is there an additional incremental amount?", - labelClassName: - "font-bold", + labelClassName: "font-bold", horizontalLayout: true, props: { @@ -753,23 +697,19 @@ export const v202402: FormSchema = { }, { rhf: "Input", - label: - "Incremental amount", + label: "Incremental amount", name: "increment-amount", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { icon: "$", - className: - "w-[200px]", + className: "w-[200px]", }, dependency: { conditions: [ { name: "is-increment-amount", type: "expectedValue", - expectedValue: - "yes", + expectedValue: "yes", }, ], effect: { @@ -778,13 +718,11 @@ export const v202402: FormSchema = { }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -794,8 +732,7 @@ export const v202402: FormSchema = { ], }, { - label: - "Standard varies by living arrangement", + label: "Standard varies by living arrangement", value: "living_standard", form: [ @@ -806,25 +743,19 @@ export const v202402: FormSchema = { name: "liv-arrange", props: { ...DefaultFieldGroupProps, - appendText: - "Add living arrangement", - removeText: - "Remove living arrangement", + appendText: "Add living arrangement", + removeText: "Remove living arrangement", }, fields: [ { rhf: "Input", name: "name-of-liv-arrange", - label: - "Name of living arrangement", - labelClassName: - "font-bold", + label: "Name of living arrangement", + labelClassName: "font-bold", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - /^\S(.*\S)?$/, + value: /^\S(.*\S)?$/, message: "Must not have leading or trailing whitespace.", }, @@ -834,14 +765,11 @@ export const v202402: FormSchema = { rhf: "Textarea", name: "liv-arrange-descript", label: "Describe", - labelClassName: - "font-bold", + labelClassName: "font-bold", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -851,52 +779,42 @@ export const v202402: FormSchema = { rhf: "FieldArray", name: "add-house-size", props: { - appendText: - "Add household size", + appendText: "Add household size", }, fields: [ { rhf: "Input", - label: - "Household size", - labelClassName: - "font-bold", + label: "Household size", + labelClassName: "font-bold", name: "house-size", props: { - className: - "w-[150px]", + className: "w-[150px]", }, rules: { pattern: { - value: - /^[0-9]\d*$/, + value: /^[0-9]\d*$/, message: "Must be a positive integer value", }, - required: - "* Required", + required: "* Required", }, }, { rhf: "Input", name: "standard", label: "Standard", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { - className: - "w-[200px]", + className: "w-[200px]", icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -906,8 +824,7 @@ export const v202402: FormSchema = { name: "is-increment-amount", label: "Is there an additional incremental amount?", - labelClassName: - "font-bold", + labelClassName: "font-bold", horizontalLayout: true, props: { @@ -926,23 +843,19 @@ export const v202402: FormSchema = { { rhf: "Input", - label: - "Incremental amount", + label: "Incremental amount", name: "increment-amount", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { icon: "$", - className: - "w-[200px]", + className: "w-[200px]", }, dependency: { conditions: [ { name: "is-increment-amount", type: "expectedValue", - expectedValue: - "yes", + expectedValue: "yes", }, ], effect: { @@ -951,13 +864,11 @@ export const v202402: FormSchema = { }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -967,8 +878,7 @@ export const v202402: FormSchema = { ], }, { - label: - "Standard varies in some other way", + label: "Standard varies in some other way", value: "other_standard", form: [ @@ -979,24 +889,19 @@ export const v202402: FormSchema = { name: "add-other-way", props: { ...DefaultFieldGroupProps, - appendText: - "Add other way", - removeText: - "Remove other way", + appendText: "Add other way", + removeText: "Remove other way", }, fields: [ { rhf: "Input", name: "name-of-group", label: "Name", - labelClassName: - "font-bold", + labelClassName: "font-bold", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - /^\S(.*\S)?$/, + value: /^\S(.*\S)?$/, message: "Must not have leading or trailing whitespace.", }, @@ -1006,14 +911,11 @@ export const v202402: FormSchema = { rhf: "Textarea", name: "group-descript", label: "Describe", - labelClassName: - "font-bold", + labelClassName: "font-bold", rules: { - required: - "* Required", + required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -1023,52 +925,42 @@ export const v202402: FormSchema = { rhf: "FieldArray", name: "add-house-size", props: { - appendText: - "Add household size", + appendText: "Add household size", }, fields: [ { rhf: "Input", - label: - "Household size", + label: "Household size", name: "house-size", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { - className: - "w-[150px]", + className: "w-[150px]", }, rules: { pattern: { - value: - /^[0-9]\d*$/, + value: /^[0-9]\d*$/, message: "Must be a positive integer value", }, - required: - "* Required", + required: "* Required", }, }, { rhf: "Input", name: "standard", label: "Standard", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { - className: - "w-[200px]", + className: "w-[200px]", icon: "$", }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -1078,8 +970,7 @@ export const v202402: FormSchema = { name: "is-increment-amount", label: "Is there an additional incremental amount?", - labelClassName: - "font-bold", + labelClassName: "font-bold", horizontalLayout: true, props: { @@ -1097,23 +988,19 @@ export const v202402: FormSchema = { }, { rhf: "Input", - label: - "Incremental amount", + label: "Incremental amount", name: "increment-amount", - labelClassName: - "font-bold", + labelClassName: "font-bold", props: { icon: "$", - className: - "w-[200px]", + className: "w-[200px]", }, dependency: { conditions: [ { name: "is-increment-amount", type: "expectedValue", - expectedValue: - "yes", + expectedValue: "yes", }, ], effect: { @@ -1122,13 +1009,11 @@ export const v202402: FormSchema = { }, rules: { pattern: { - value: - /^\d*(?:\.\d{1,2})?$/, + value: /^\d*(?:\.\d{1,2})?$/, message: "Must be all numbers, no commas. e.g. 1234.56", }, - required: - "* Required", + required: "* Required", }, }, ], @@ -1203,8 +1088,7 @@ export const v202402: FormSchema = { { label: "Asthma", value: "asthma" }, { label: "Obesity", value: "obesity" }, { - label: - "Other disease, condition, diagnosis, or disorder", + label: "Other disease, condition, diagnosis, or disorder", value: "other", slots: [ { @@ -1216,8 +1100,7 @@ export const v202402: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1241,8 +1124,7 @@ export const v202402: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1260,8 +1142,7 @@ export const v202402: FormSchema = { sectionId: "geo-area", form: [ { - description: - "Will this benefit package be available to the entire state/territory?", + description: "Will this benefit package be available to the entire state/territory?", slots: [ { rhf: "Select", @@ -1314,8 +1195,7 @@ export const v202402: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1337,8 +1217,7 @@ export const v202402: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1360,8 +1239,7 @@ export const v202402: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1383,8 +1261,7 @@ export const v202402: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, diff --git a/lib/libs/webforms/ABP10/v202401.ts b/lib/libs/webforms/ABP10/v202401.ts index 6804214e3c..f7c4b066a6 100644 --- a/lib/libs/webforms/ABP10/v202401.ts +++ b/lib/libs/webforms/ABP10/v202401.ts @@ -48,8 +48,7 @@ export const v202401: FormSchema = { label: "Describe the approach", labelClassName: "font-bold", name: "approach-description", - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", rules: { required: "* Required", pattern: { @@ -97,8 +96,7 @@ export const v202401: FormSchema = { { label: "The state or territory assures that all providers of Alternative Benefit Plan benefits shall meet the provider qualification requirements of the base benchmark plan and/or the Medicaid state plan.", - value: - "providers_of_alternative_benefit_plan_meets_provider_qualifications", + value: "providers_of_alternative_benefit_plan_meets_provider_qualifications", }, ], }, diff --git a/lib/libs/webforms/ABP2A/v202401.ts b/lib/libs/webforms/ABP2A/v202401.ts index e941a55fba..02cfe564b4 100644 --- a/lib/libs/webforms/ABP2A/v202401.ts +++ b/lib/libs/webforms/ABP2A/v202401.ts @@ -39,8 +39,7 @@ export const v202401: FormSchema = { { rhf: "Textarea", name: "explain-how-state-territory-aligned", - description: - "Explain how the state has fully aligned its benefits.", + description: "Explain how the state has fully aligned its benefits.", descriptionAbove: true, descriptionClassName: "font-bold text-black", rules: { @@ -97,8 +96,7 @@ export const v202401: FormSchema = { { label: "The state/territory must have a process in place to identify individuals that meet the exemption criteria, and the state/territory must comply with requirements related to providing the option of enrollment in an ABP defined using Section 1937 requirements or an ABP defined as the state/territory's approved Medicaid state plan not subject to Section 1937 requirements.", - value: - "state_territory_must_have_a_process_that_meets_exemption_criteria", + value: "state_territory_must_have_a_process_that_meets_exemption_criteria", }, { styledLabel: [ @@ -143,8 +141,7 @@ export const v202401: FormSchema = { classname: "block py-1", }, ], - value: - "state_territory_assures_it_will_inform_the_individual", + value: "state_territory_assures_it_will_inform_the_individual", }, ], }, @@ -198,8 +195,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -211,8 +207,7 @@ export const v202401: FormSchema = { { rhf: "Upload", name: "provide-copy_upload", - description: - "Provide a copy of the letter, email, or other communication.", + description: "Provide a copy of the letter, email, or other communication.", descriptionAbove: true, descriptionClassName: "font-bold text-black", rules: { @@ -224,8 +219,7 @@ export const v202401: FormSchema = { rhf: "Input", name: "when-to-inform", descriptionAbove: true, - description: - "When did/will the state/territory inform the individuals?", + description: "When did/will the state/territory inform the individuals?", rules: { required: "* Required", pattern: { @@ -280,8 +274,7 @@ export const v202401: FormSchema = { text: "C. Chose to enroll in ABP coverage subject to Section 1937 requirements or defined as the state/territory's approved Medicaid state plan not subject to Section 1937 requirements", }, ], - value: - "state_territory_will_document_exempt_individuals_eligibility", + value: "state_territory_will_document_exempt_individuals_eligibility", }, ], }, @@ -318,8 +311,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -333,8 +325,7 @@ export const v202401: FormSchema = { name: "what-docu-will-be-maintained", descriptionAbove: true, descriptionClassName: "font-bold text-black", - description: - "What documentation will be maintained in the eligibility file?", + description: "What documentation will be maintained in the eligibility file?", formItemClassName: "pb-6 border-b-[1px] border-[#AEB0B5]", rules: { required: "* Required", @@ -348,13 +339,11 @@ export const v202401: FormSchema = { { label: "Signed documentation from the individual consenting to enrollment in the ABP", - value: - "signed_documentation_from_individual_consenting_enrollment_ABP", + value: "signed_documentation_from_individual_consenting_enrollment_ABP", }, { label: "Other", - value: - "what_documentation_will_be_maintained_in_the_eligibility_file_other", + value: "what_documentation_will_be_maintained_in_the_eligibility_file_other", slots: [ { rhf: "Textarea", @@ -365,8 +354,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, diff --git a/lib/libs/webforms/ABP2B/v202401.ts b/lib/libs/webforms/ABP2B/v202401.ts index 068496928b..afa42bd6de 100644 --- a/lib/libs/webforms/ABP2B/v202401.ts +++ b/lib/libs/webforms/ABP2B/v202401.ts @@ -61,8 +61,7 @@ export const v202401: FormSchema = { classname: "block pt-1", }, ], - value: - "effectively_inform_voluntarily_enroll_and_may_disenroll", + value: "effectively_inform_voluntarily_enroll_and_may_disenroll", }, { styledLabel: [ @@ -82,8 +81,7 @@ export const v202401: FormSchema = { classname: "block pt-1", }, ], - value: - "inform_individuals_of_abp_benefits_and_costs_of_different_packages", + value: "inform_individuals_of_abp_benefits_and_costs_of_different_packages", }, ], }, @@ -127,8 +125,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -140,8 +137,7 @@ export const v202401: FormSchema = { { rhf: "Upload", name: "provide-copy", - description: - "Provide a copy of the letter, email, or other communication.", + description: "Provide a copy of the letter, email, or other communication.", descriptionAbove: true, descriptionClassName: "font-bold text-black", rules: { @@ -153,8 +149,7 @@ export const v202401: FormSchema = { rhf: "Input", name: "when-to-inform", descriptionAbove: true, - description: - "When did/will the state/territory inform the individuals?", + description: "When did/will the state/territory inform the individuals?", rules: { required: "* Required", pattern: { @@ -209,8 +204,7 @@ export const v202401: FormSchema = { text: " C. Chose to enroll in ABP coverage subject to Section 1937 requirements or defined as the state/territory's approved Medicaid state plan not subject to Section 1937 requirements", }, ], - value: - "state_territory_will_document_exempt_individuals_eligibility", + value: "state_territory_will_document_exempt_individuals_eligibility", }, ], }, @@ -247,8 +241,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -262,8 +255,7 @@ export const v202401: FormSchema = { name: "what-docu-will-be-maintained", descriptionAbove: true, descriptionClassName: "font-bold text-black", - description: - "What documentation will be maintained in the eligibility file?", + description: "What documentation will be maintained in the eligibility file?", formItemClassName: "pb-6 border-b-[1px] border-[#AEB0B5]", rules: { required: "* Required", @@ -277,13 +269,11 @@ export const v202401: FormSchema = { { label: "Signed documentation from the individual consenting to enrollment in the ABP", - value: - "signed_documentation_from_individual_consenting_enrollment_ABP", + value: "signed_documentation_from_individual_consenting_enrollment_ABP", }, { label: "Other", - value: - "what_documentation_will_be_maintained_in_the_eligibility_file_other", + value: "what_documentation_will_be_maintained_in_the_eligibility_file_other", slots: [ { rhf: "Textarea", @@ -294,8 +284,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, diff --git a/lib/libs/webforms/ABP2C/v202401.ts b/lib/libs/webforms/ABP2C/v202401.ts index c1bd304b2b..48840b5e11 100644 --- a/lib/libs/webforms/ABP2C/v202401.ts +++ b/lib/libs/webforms/ABP2C/v202401.ts @@ -69,8 +69,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -89,8 +88,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -109,8 +107,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -162,8 +159,7 @@ export const v202401: FormSchema = { { name: "how-id-become-exempt", rhf: "Checkbox", - label: - "How will the state/territory identify if an individual becomes exempt?", + label: "How will the state/territory identify if an individual becomes exempt?", labelClassName: "font-bold text-black", rules: { required: "* Required" }, props: { @@ -201,8 +197,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -249,8 +244,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, diff --git a/lib/libs/webforms/ABP3/v202401.ts b/lib/libs/webforms/ABP3/v202401.ts index 09a7c505e9..ed0f380888 100644 --- a/lib/libs/webforms/ABP3/v202401.ts +++ b/lib/libs/webforms/ABP3/v202401.ts @@ -2,8 +2,7 @@ import { FormSchema } from "shared-types"; import { noLeadingTrailingWhitespace } from "shared-utils/regex"; export const v202401: FormSchema = { - header: - "ABP 3: Selection of benchmark or benchmark-equivalent benefit package", + header: "ABP 3: Selection of benchmark or benchmark-equivalent benefit package", formId: "abp3", sections: [ { @@ -11,8 +10,7 @@ export const v202401: FormSchema = { sectionId: "benefit-package", form: [ { - description: - "For the population defined in section 1, the state/territory wants to:", + description: "For the population defined in section 1, the state/territory wants to:", slots: [ { rhf: "Radio", @@ -168,22 +166,19 @@ export const v202401: FormSchema = { rhf: "Radio", name: "approved-state-plan-opts", rules: { - required: - "* Required", + required: "* Required", }, props: { options: [ { label: "Benefits provided in the approved state plan", - value: - "approved_state_plan", + value: "approved_state_plan", }, { label: "All benefits provided in the approved state plan plus additional benefits", - value: - "additional_benefits", + value: "additional_benefits", }, { label: @@ -194,8 +189,7 @@ export const v202401: FormSchema = { { label: "A partial list of benefits provided in the approved state plan", - value: - "partial_list_of_benefits", + value: "partial_list_of_benefits", }, { label: @@ -227,8 +221,7 @@ export const v202401: FormSchema = { rules: { required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -351,8 +344,7 @@ export const v202401: FormSchema = { slots: [ { rhf: "Select", - label: - "Is the base benchmark plan the same as the Section 1937 coverage option?", + label: "Is the base benchmark plan the same as the Section 1937 coverage option?", labelClassName: "font-bold", name: "same-as-sect-1937", rules: { required: "* Required" }, diff --git a/lib/libs/webforms/ABP3_1/v202401.ts b/lib/libs/webforms/ABP3_1/v202401.ts index fb730e2502..28bf45194a 100644 --- a/lib/libs/webforms/ABP3_1/v202401.ts +++ b/lib/libs/webforms/ABP3_1/v202401.ts @@ -2,8 +2,7 @@ import { FormSchema } from "shared-types"; import { noLeadingTrailingWhitespace } from "shared-utils/regex"; export const v202401: FormSchema = { - header: - "ABP 3.1 Selection of benchmark or benchmark-equivalent benefit package", + header: "ABP 3.1 Selection of benchmark or benchmark-equivalent benefit package", formId: "abp3-1", sections: [ { @@ -11,8 +10,7 @@ export const v202401: FormSchema = { sectionId: "benefit-package-details", form: [ { - description: - "For the population defined in Section 1, the state/territory wants to:", + description: "For the population defined in Section 1, the state/territory wants to:", slots: [ { rhf: "Radio", @@ -108,8 +106,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -132,8 +129,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -174,8 +170,7 @@ export const v202401: FormSchema = { { label: "Benefits provided in the approved state plan plus additional benefits", - value: - "additional_benefits", + value: "additional_benefits", }, { label: @@ -186,8 +181,7 @@ export const v202401: FormSchema = { { label: "A partial list of benefits provided in the approved state plan", - value: - "partial_list", + value: "partial_list", }, { label: @@ -198,8 +192,7 @@ export const v202401: FormSchema = { ], }, rules: { - required: - "* Required", + required: "* Required", }, }, ], @@ -226,8 +219,7 @@ export const v202401: FormSchema = { rules: { required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -280,8 +272,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -304,8 +295,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -360,8 +350,7 @@ export const v202401: FormSchema = { { name: "is-ehb-bench-plan-same-section-1937", rhf: "Select", - label: - "Is the EHB-benchmark plan the same as the Section 1937 coverage option?", + label: "Is the EHB-benchmark plan the same as the Section 1937 coverage option?", labelClassName: "font-bold", rules: { required: "* Required", @@ -541,8 +530,7 @@ export const v202401: FormSchema = { value: "largest_three_state_FEHBP_plans", }, { - label: - "The largest insured commercial, non-Medicaid HMO", + label: "The largest insured commercial, non-Medicaid HMO", value: "larged_insured_commercial", }, ], @@ -585,8 +573,7 @@ export const v202401: FormSchema = { value: "largest_three_state_FEHBP_plans", }, { - label: - "The largest insured commercial, non-Medicaid HMO", + label: "The largest insured commercial, non-Medicaid HMO", value: "larged_insured_commercial", }, ], @@ -1063,10 +1050,8 @@ export const v202401: FormSchema = { ], }, { - label: - "Mental health and substance use disorders", - value: - "mental_health_and_substance_use_disorders", + label: "Mental health and substance use disorders", + value: "mental_health_and_substance_use_disorders", slots: [ { rhf: "Select", @@ -1297,10 +1282,8 @@ export const v202401: FormSchema = { ], }, { - label: - "Rehabilitative and habilitative services and devices", - value: - "rehabilitative_and_habilitative_services_and_devices", + label: "Rehabilitative and habilitative services and devices", + value: "rehabilitative_and_habilitative_services_and_devices", slots: [ { rhf: "Select", @@ -1649,10 +1632,8 @@ export const v202401: FormSchema = { ], }, { - label: - "Pediatric services, including oral and vision care", - value: - "pediatric_services_including_oral_and_vision_care", + label: "Pediatric services, including oral and vision care", + value: "pediatric_services_including_oral_and_vision_care", slots: [ { rhf: "Select", diff --git a/lib/libs/webforms/ABP4/v202401.ts b/lib/libs/webforms/ABP4/v202401.ts index f810654b46..feb2fd5e42 100644 --- a/lib/libs/webforms/ABP4/v202401.ts +++ b/lib/libs/webforms/ABP4/v202401.ts @@ -46,8 +46,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "wrapped", props: { - wrapperClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary pb-6", + wrapperClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary pb-6", }, dependency: { conditions: [ @@ -67,8 +66,7 @@ export const v202401: FormSchema = { props: { options: [ { - label: - "See approved Attachment 4.18-F or G for description.", + label: "See approved Attachment 4.18-F or G for description.", value: "true", }, ], @@ -86,8 +84,7 @@ export const v202401: FormSchema = { { rhf: "Textarea", name: "other-info-about-requirements", - label: - "Other information about cost-sharing requirements (optional)", + label: "Other information about cost-sharing requirements (optional)", labelClassName: "font-bold", rules: { pattern: { diff --git a/lib/libs/webforms/ABP5/v202401.ts b/lib/libs/webforms/ABP5/v202401.ts index 5f655fc0e0..08e9e5d399 100644 --- a/lib/libs/webforms/ABP5/v202401.ts +++ b/lib/libs/webforms/ABP5/v202401.ts @@ -151,8 +151,7 @@ function subsectionFormFields({ }, { rhf: "Input", - label: - "Other information about this benefit source, including the name of the source plan", + label: "Other information about this benefit source, including the name of the source plan", labelClassName: "font-bold", name: "source-other-info_input", formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", @@ -176,8 +175,7 @@ function subsectionFormFields({ }, { rhf: "Input", - label: - "Other information about this benefit source, including the name of the source plan", + label: "Other information about this benefit source, including the name of the source plan", labelClassName: "font-bold", name: "secretary-other-info_input", formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", @@ -357,8 +355,7 @@ function subsection({ ? ([ { rhf: "Radio", - label: - "Is there an EHB-benchmark benefit duplicated or substituted?", + label: "Is there an EHB-benchmark benefit duplicated or substituted?", labelClassName: "font-bold", name: "benefit-dupe-or-sub", rules: { required: "* Required" }, @@ -379,8 +376,7 @@ function subsection({ required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -403,8 +399,7 @@ function subsection({ required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -449,8 +444,7 @@ export const v202401: FormSchema = { slots: [ { rhf: "Select", - label: - "Does this description of benefits align with the traditional state plan?", + label: "Does this description of benefits align with the traditional state plan?", labelClassName: "font-bold", name: "benefits-align", rules: { required: "* Required" }, @@ -475,8 +469,7 @@ export const v202401: FormSchema = { slots: [ { rhf: "Select", - label: - "Does the state/territory propose a benchmark-equivalent benefit package?", + label: "Does the state/territory propose a benchmark-equivalent benefit package?", labelClassName: "font-bold", name: "benchmark-equivalent-pkg", rules: { required: "* Required" }, @@ -676,8 +669,7 @@ export const v202401: FormSchema = { }, { rhf: "Input", - label: - "Coverage that exceeds the minimum requirements or other information", + label: "Coverage that exceeds the minimum requirements or other information", labelClassName: "font-bold", name: "other-info", rules: { @@ -690,8 +682,7 @@ export const v202401: FormSchema = { }, { rhf: "Radio", - label: - "Is there an EHB-benchmark benefit duplicated or substituted?", + label: "Is there an EHB-benchmark benefit duplicated or substituted?", labelClassName: "font-bold", name: "benefit-dup-or-sub", rules: { required: "* Required" }, @@ -712,8 +703,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -736,8 +726,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -754,8 +743,7 @@ export const v202401: FormSchema = { ], }, subsection({ - title: - "7. Essential health benefit: Rehabilitative and habilitative services and devices", + title: "7. Essential health benefit: Rehabilitative and habilitative services and devices", sectionName: "rehabilitative-and-habilitative", dependency: initialDependency, headerSlots: [ @@ -789,8 +777,7 @@ export const v202401: FormSchema = { "The state/territory must provide, at a minimum, a broad range of preventive services, including “A” and “B” services recommended by the United States Preventive Services Task Force; vaccines recommended by the Advisory Committee for Immunization Practices (ACIP); preventive care and screening for infants, children, and adults recommended by the Health Resources and Services Administration (HRSA) Bright Futures program; and additional preventive services for women recommended by the Institute of Medicine (IOM).", }), subsection({ - title: - "10. Essential health benefit: Pediatric services including oral and vision care", + title: "10. Essential health benefit: Pediatric services including oral and vision care", sectionName: "pediatric", dependency: initialDependency, benefitProvided: "Medicaid State Plan EPSDT Benefits", @@ -808,8 +795,7 @@ export const v202401: FormSchema = { props: { options: [ { - label: - "11. Other covered benefits that are not essential health benefits", + label: "11. Other covered benefits that are not essential health benefits", optionlabelClassName: "text-2xl font-bold p-4 bg-gray-300 py-4 px-8 w-full leading-9 text-primary", value: "other_covered_benefits_benefit", @@ -854,15 +840,13 @@ export const v202401: FormSchema = { fields: [ { rhf: "Input", - label: - "Base benchmark benefit that was substituted", + label: "Base benchmark benefit that was substituted", labelClassName: "font-bold", rules: { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, name: "benchmark-subbed", @@ -884,8 +868,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, diff --git a/lib/libs/webforms/ABP6/v202401.ts b/lib/libs/webforms/ABP6/v202401.ts index 81c153a4c5..12394a4adf 100644 --- a/lib/libs/webforms/ABP6/v202401.ts +++ b/lib/libs/webforms/ABP6/v202401.ts @@ -55,8 +55,7 @@ export const v202401: FormSchema = { { type: "greaterThanField", fieldName: "abp6_desc-of-ben_agg-actuarial-ben-plan", - message: - "Must be greater than or equal to value entered above.", + message: "Must be greater than or equal to value entered above.", }, ], }, @@ -82,8 +81,7 @@ export const v202401: FormSchema = { }, { value: "acturaial_report", - label: - "The state/territory has included a copy of the actuarial report.", + label: "The state/territory has included a copy of the actuarial report.", slots: [ { name: "actuarial-report", @@ -148,8 +146,7 @@ export const v202401: FormSchema = { }, { value: "standard_utilization_price_factors", - label: - "Using a standardized set of utilization and price factors", + label: "Using a standardized set of utilization and price factors", }, { value: "using_standard_representative_pop", @@ -186,8 +183,7 @@ export const v202401: FormSchema = { "The state/territory assures, as required by Section 1937(b)(2)(A) and 42 CFR 440.335, that benchmark-equivalent coverage shall include coverage for the following categories of services: inpatient and outpatient hospital services, physicians' surgical and medical services, laboratory and x-ray services, prescription drugs, well-baby and well-child care, including age-appropriate immunizations, emergency services, mental health benefits, family planning services and supplies, and other appropriate preventive services as designated by the Secretary.", }, { - value: - "inlcuded_desc_of_ben_and_val_as_percentage_of_equivalent", + value: "inlcuded_desc_of_ben_and_val_as_percentage_of_equivalent", label: "The state/territory has included a description of the benefits included and the actuarial value of the category as a percentage of the actuarial value of the coverage for the benefits included in the benchmark-equivalent benefit plan.", }, @@ -295,8 +291,7 @@ export const v202401: FormSchema = { sectionId: "addtnl-info", form: [ { - description: - "Other information about benchmark-equivalent assurances (optional)", + description: "Other information about benchmark-equivalent assurances (optional)", slots: [ { name: "description", diff --git a/lib/libs/webforms/ABP7/v202401.ts b/lib/libs/webforms/ABP7/v202401.ts index 6b2ee49be5..420ea228b4 100644 --- a/lib/libs/webforms/ABP7/v202401.ts +++ b/lib/libs/webforms/ABP7/v202401.ts @@ -6,8 +6,7 @@ export const v202401: FormSchema = { formId: "abp7", sections: [ { - title: - "Early and Periodic Screening, Diagnostic, and Treatment (EPSDT) assurances", + title: "Early and Periodic Screening, Diagnostic, and Treatment (EPSDT) assurances", sectionId: "epsdt-assurances", form: [ { @@ -18,8 +17,7 @@ export const v202401: FormSchema = { { rhf: "Select", name: "does-abp-include-beneficiaries-under-21", - label: - "Does the Alternative Benefit Plan (ABP) include beneficiaries under age 21?", + label: "Does the Alternative Benefit Plan (ABP) include beneficiaries under age 21?", labelClassName: "font-bold", rules: { required: "* Required" }, props: { @@ -128,10 +126,8 @@ export const v202401: FormSchema = { value: "risk_based_capitation", }, { - label: - "Administrative services contract", - value: - "administrative_services_contract", + label: "Administrative services contract", + value: "administrative_services_contract", }, { label: "Other", @@ -145,8 +141,7 @@ export const v202401: FormSchema = { rules: { required: "* Required", pattern: { - value: - noLeadingTrailingWhitespace, + value: noLeadingTrailingWhitespace, message: "Must not have leading or trailing whitespace.", }, @@ -215,8 +210,7 @@ export const v202401: FormSchema = { { label: "The state/territory assures that it meets the minimum requirements for prescription drug coverage in Section 1937 of the Act and implementing regulations at 42 CFR 440.347. Coverage is at least the greater of one drug in each United States Pharmacopeia (USP) category and class or the same number of prescription drugs in each category and class as the base benchmark.", - value: - "assures_min_requirements_for_perscription_drug_coverage", + value: "assures_min_requirements_for_perscription_drug_coverage", }, { label: @@ -227,14 +221,12 @@ export const v202401: FormSchema = { { label: "The state/territory assures that when it pays for outpatient prescription drugs covered under an ABP, it meets the requirements of Section 1927 of the Act and implementing regulations at 42 CFR 440.345, except for those requirements that are directly contrary to amount, duration, and scope of coverage permitted under Section 1937 of the Act.", - value: - "assures_outpatient_prescription_drugs_coverage_under_abp", + value: "assures_outpatient_prescription_drugs_coverage_under_abp", }, { label: "The state/territory assures that when conducting prior authorization of prescription drugs under an ABP, it complies with prior authorization program requirements in Section 1927(d)(5) of the Act.", - value: - "assures_prior_authorization_of_drugs_under_abp_prior", + value: "assures_prior_authorization_of_drugs_under_abp_prior", }, ], }, @@ -258,8 +250,7 @@ export const v202401: FormSchema = { { label: "The state/territory assures that substituted benefits are actuarially equivalent to the benefits they replaced from the base benchmark plan and that the state/territory has actuarial certification for substituted benefits available for inspection if requested by CMS.", - value: - "assures_substituted_benefits_are_actuarially_equivalent", + value: "assures_substituted_benefits_are_actuarially_equivalent", }, { label: @@ -280,8 +271,7 @@ export const v202401: FormSchema = { { label: "The state/territory assures that it will comply with the mental health and substance use disorder parity requirements of Section 1937(b)(6) of the Act by ensuring that the financial requirements and treatment limitations applicable to mental health or substance use disorder benefits comply with Section 2705(a) of the Public Health Service Act in the same manner as such requirements apply to a group health plan.", - value: - "assures_compliy_with_mental_healthy_and_substance_use_disorder", + value: "assures_compliy_with_mental_healthy_and_substance_use_disorder", }, { label: @@ -296,8 +286,7 @@ export const v202401: FormSchema = { { label: "The state/territory assures, in accordance with 45 CFR 156.115(a)(4) and 45 CFR 147.130, that it will provide as essential health benefits a broad range of preventive services including: “A” and “B” services recommended by the United States Preventive Services Task Force; vaccines recommended by the Advisory Committee for Immunization Practices (ACIP); preventive care and screening for infants, children, and adults recommended by HRSA's Bright Futures program; and additional preventive services for women recommended by the Institute of Medicine (IOM).", - value: - "assures_accordance_with_CFR_it_will_provide_essential_health_benefits", + value: "assures_accordance_with_CFR_it_will_provide_essential_health_benefits", }, ], }, diff --git a/lib/libs/webforms/ABP8/sections/v202401.ts b/lib/libs/webforms/ABP8/sections/v202401.ts index 6785b253b6..573aed3ebb 100644 --- a/lib/libs/webforms/ABP8/sections/v202401.ts +++ b/lib/libs/webforms/ABP8/sections/v202401.ts @@ -52,11 +52,7 @@ export function generateDependency({ // Section generators --------------------------------------------------------- -export function managedCare({ - conditionalInfo, - programLabel, - title, -}: SectionParams): Section { +export function managedCare({ conditionalInfo, programLabel, title }: SectionParams): Section { return { title: title || `${programLabel}`, sectionId: createSectionId(programLabel), @@ -149,8 +145,7 @@ export function managedCare({ ], }, { - label: - "Section 1932(a) mandatory managed care state plan amendment", + label: "Section 1932(a) mandatory managed care state plan amendment", value: "1932a", slots: [ { @@ -205,8 +200,7 @@ export function managedCare({ }, { label: `${ - programLabel === SectionName.HIO || - programLabel === SectionName.MCO + programLabel === SectionName.HIO || programLabel === SectionName.MCO ? "An" : "A" } ${programLabel} consistent with applicable managed care requirements (42 CFR Part 438, 42 CFR Part 440, and Sections 1903(m), 1932, and 1937 of the Social Security Act)`, @@ -222,10 +216,7 @@ export function managedCare({ } // "[Program] procurement or selection" -export function procurementOrSelection({ - conditionalInfo, - programLabel, -}: SectionParams): Section { +export function procurementOrSelection({ conditionalInfo, programLabel }: SectionParams): Section { return { title: `${programLabel} procurement or selection`, sectionId: `${createSectionId(programLabel)}-procurement`, @@ -301,8 +292,7 @@ export function deliverySystemCharactaristics({ rhf: "WrappedGroup", name: "benefit-service-group", props: { - wrapperClassName: - "ml-[0.6rem] pl-4 border-l-4 border-l-primary my-2 space-y-6", + wrapperClassName: "ml-[0.6rem] pl-4 border-l-4 border-l-primary my-2 space-y-6", }, fields: [ { @@ -326,8 +316,7 @@ export function deliverySystemCharactaristics({ required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { @@ -370,13 +359,11 @@ export function deliverySystemCharactaristics({ value: "ffs-provider-contracts", }, { - label: - "Provision of payments to FFS providers on behalf of the state", + label: "Provision of payments to FFS providers on behalf of the state", value: "ffs-provider-payments", }, { - label: - "Provision of enrollee outreach and education activities", + label: "Provision of enrollee outreach and education activities", value: "enrollee-outreach", }, { @@ -394,13 +381,11 @@ export function deliverySystemCharactaristics({ value: "quality-improvement", }, { - label: - "Coordination with behavioral health systems/providers", + label: "Coordination with behavioral health systems/providers", value: "behavioral-health-coordination", }, { - label: - "Coordination with long-term services and support systems/providers", + label: "Coordination with long-term services and support systems/providers", value: "ltss-coordination", }, { @@ -416,8 +401,7 @@ export function deliverySystemCharactaristics({ required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -523,8 +507,7 @@ export function deliverySystemCharactaristics({ ], }, { - label: - "In some other geographic area (must not be smaller than a zip code)", + label: "In some other geographic area (must not be smaller than a zip code)", value: "other-geographic-area", slots: [ { @@ -551,10 +534,7 @@ export function deliverySystemCharactaristics({ } // "[Program] participation exclusions" -export function participationExclusions({ - conditionalInfo, - programLabel, -}: SectionParams): Section { +export function participationExclusions({ conditionalInfo, programLabel }: SectionParams): Section { const sectionId = `${createSectionId(programLabel)}_participation-exclusions`; return { @@ -603,8 +583,7 @@ export function participationExclusions({ value: "less-than-three-months", }, { - label: - "Individuals in a retroactive period of Medicaid eligibility", + label: "Individuals in a retroactive period of Medicaid eligibility", value: "retroactive-period", }, { @@ -620,8 +599,7 @@ export function participationExclusions({ required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { @@ -678,8 +656,7 @@ export function participationRequirements({ required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { @@ -689,8 +666,7 @@ export function participationRequirements({ ], }, { - label: - "Voluntary, using the below method for effectuating enrollment", + label: "Voluntary, using the below method for effectuating enrollment", value: "voluntary", slots: [ { @@ -740,10 +716,7 @@ export function participationRequirements({ } // "Disenrollment" -export function disenrollment({ - conditionalInfo, - programLabel, -}: SectionParams): Section { +export function disenrollment({ conditionalInfo, programLabel }: SectionParams): Section { const sectionId = `${createSectionId(programLabel)}_disenrollment`; return { title: "Disenrollment", @@ -777,8 +750,7 @@ export function disenrollment({ }, { rhf: "Input", - label: - "Length of time the disenrollment limitation will apply (up to 12 months)", + label: "Length of time the disenrollment limitation will apply (up to 12 months)", labelClassName: "font-bold", name: "disenrollment-limit-length", props: { @@ -827,8 +799,7 @@ export function disenrollment({ }, { rhf: "Textarea", - label: - "Additional circumstances of cause for disenrollment (optional)", + label: "Additional circumstances of cause for disenrollment (optional)", labelClassName: "font-bold", name: "additional-disenrollment-cause", props: { @@ -879,8 +850,7 @@ export function disenrollment({ ], }, { - label: - "Enrollees submit disenrollment requests to the state or its agent.", + label: "Enrollees submit disenrollment requests to the state or its agent.", value: "submit-requests", }, { @@ -893,8 +863,7 @@ export function disenrollment({ "The MCO/HIO/PIHP/PAHP/PCCM/PCCM entity may not approve or disapprove requests and must refer all disenrollment requests received to the state.", value: `${createSectionId(programLabel)}-refers-requests`, }, - ...(programLabel === SectionName.PCCM || - programLabel === SectionName.PCCMEntity + ...(programLabel === SectionName.PCCM || programLabel === SectionName.PCCMEntity ? [] : [ { @@ -982,8 +951,7 @@ export function disenrollment({ required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -1019,10 +987,7 @@ export function disenrollment({ } // "Additional information: [program]" -export function additionalInfo({ - conditionalInfo, - programLabel, -}: SectionParams): Section { +export function additionalInfo({ conditionalInfo, programLabel }: SectionParams): Section { return { title: `Additional information: ${programLabel}`, sectionId: `additional-info-${createSectionId(programLabel)}`, @@ -1036,8 +1001,7 @@ export function additionalInfo({ slots: [ { rhf: "Textarea", - label: - "Additional details about this service delivery system (optional)", + label: "Additional details about this service delivery system (optional)", labelClassName: "font-bold", name: "additional-details", props: { @@ -1057,10 +1021,7 @@ export function additionalInfo({ } // "[Program] payments" -export function payments({ - conditionalInfo, - programLabel, -}: SectionParams): Section { +export function payments({ conditionalInfo, programLabel }: SectionParams): Section { const pccmEntityPayment: RHFOption[] = programLabel === SectionName.PCCMEntity ? [ @@ -1111,8 +1072,7 @@ export function payments({ required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, diff --git a/lib/libs/webforms/ABP8/v202401.ts b/lib/libs/webforms/ABP8/v202401.ts index aac203103e..246a272ed1 100644 --- a/lib/libs/webforms/ABP8/v202401.ts +++ b/lib/libs/webforms/ABP8/v202401.ts @@ -78,8 +78,7 @@ export const v202401: FormSchema = { value: "mco", }, { - label: - "Health insuring organization (HIO) (California only)", + label: "Health insuring organization (HIO) (California only)", value: "hio", }, { @@ -206,8 +205,7 @@ export const v202401: FormSchema = { }, { rhf: "Checkbox", - label: - "Which of the following will apply to the managed care program?", + label: "Which of the following will apply to the managed care program?", labelClassName: "font-bold", name: "voluntary-enrollment-options", rules: { @@ -249,8 +247,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { @@ -340,8 +337,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { @@ -365,8 +361,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { diff --git a/lib/libs/webforms/ABP9/v202401.ts b/lib/libs/webforms/ABP9/v202401.ts index 11bfa0f060..fbd93057f1 100644 --- a/lib/libs/webforms/ABP9/v202401.ts +++ b/lib/libs/webforms/ABP9/v202401.ts @@ -36,8 +36,7 @@ export const v202401: FormSchema = { message: "Must not have leading or trailing whitespace.", }, }, - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", label: "Describe the employer-sponsored insurance, including the population covered, amount of premium assistance by population, and employer-sponsored insurance activities, including required contribution, cost-effectiveness test requirements, and benefit information.", labelClassName: "font-bold", @@ -59,8 +58,7 @@ export const v202401: FormSchema = { { rhf: "Select", name: "does-provide-pay-of-premiums", - label: - "Does the state/territory otherwise provide for payment of premiums?", + label: "Does the state/territory otherwise provide for payment of premiums?", labelClassName: "font-bold", rules: { required: "* Required" }, props: { @@ -81,8 +79,7 @@ export const v202401: FormSchema = { message: "Must not have leading or trailing whitespace.", }, }, - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", label: "Describe, including the population covered, amount of premium assistance by population, required contributions, cost-effectiveness test requirements, and benefit information.", labelClassName: "font-bold", diff --git a/lib/libs/webforms/CS11/v202401.ts b/lib/libs/webforms/CS11/v202401.ts index ed16a46d6e..cb8405b041 100644 --- a/lib/libs/webforms/CS11/v202401.ts +++ b/lib/libs/webforms/CS11/v202401.ts @@ -4,8 +4,7 @@ import { noLeadingTrailingWhitespace } from "shared-utils"; export const v202401: FormSchema = { header: "CS 11: Separate CHIP eligibility—Pregnant women who have access to public employee coverage", - subheader: - "Sections 2110(b)(2)(B) and (b)(6) of the Social Security Act (SSA)", + subheader: "Sections 2110(b)(2)(B) and (b)(6) of the Social Security Act (SSA)", formId: "cs11", sections: [ { @@ -103,8 +102,7 @@ export const v202401: FormSchema = { ], }, { - label: - "Hardship criteria as provided in Section 2110(b)(6)(C)", + label: "Hardship criteria as provided in Section 2110(b)(6)(C)", value: "hardship-criteria", slots: [ { @@ -167,8 +165,7 @@ export const v202401: FormSchema = { value: "same", }, { - label: - "Lower than the income standards for targeted low-income pregnant women", + label: "Lower than the income standards for targeted low-income pregnant women", value: "lower", }, ], @@ -307,8 +304,7 @@ export const v202401: FormSchema = { props: { className: "w-[696px]", }, - formItemClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary mb-4", + formItemClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary mb-4", dependency: { conditions: [ { @@ -364,8 +360,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "County", @@ -450,8 +445,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "City", @@ -544,8 +538,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "Geographic Area", @@ -561,8 +554,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "Describe", @@ -584,8 +576,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, @@ -603,8 +594,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, @@ -651,13 +641,11 @@ export const v202401: FormSchema = { props: { options: [ { - label: - "All pregnant women who have access to public employee coverage", + label: "All pregnant women who have access to public employee coverage", value: "all-pregnant-women", }, { - label: - "Certain pregnant women who have access to public employee coverage", + label: "Certain pregnant women who have access to public employee coverage", value: "certain-pregnant-women", slots: [ { @@ -689,8 +677,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -719,8 +706,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, required: "* Required", }, @@ -759,8 +745,7 @@ export const v202401: FormSchema = { props: { options: [ { - label: - "Same as the age criteria used for targeted low-income pregnant women", + label: "Same as the age criteria used for targeted low-income pregnant women", value: "same-as-targeted", }, { @@ -791,8 +776,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^(2[0-9]|[3-9]\d|\d{3,})$/, - message: - "Must be a positive integer value greater than 19", + message: "Must be a positive integer value greater than 19", }, }, props: { @@ -814,8 +798,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, required: "* Required", }, @@ -843,14 +826,12 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, label: "Start of age range", - labelClassName: - "font-bold text-black", + labelClassName: "font-bold text-black", props: { className: "w-[125px]", }, @@ -861,8 +842,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, @@ -870,15 +850,12 @@ export const v202401: FormSchema = { { type: "greaterThanField", strictGreater: true, - fieldName: - "cs11_age-standard_start-age-range", - message: - "Must be greater than start of age range", + fieldName: "cs11_age-standard_start-age-range", + message: "Must be greater than start of age range", }, ], label: "End of age range", - labelClassName: - "font-bold text-black", + labelClassName: "font-bold text-black", props: { className: "w-[125px]", }, @@ -889,8 +866,7 @@ export const v202401: FormSchema = { rhf: "Select", label: "Does the age range for targeted low-income pregnant women overlap with the age range for targeted low-income children?", - labelClassName: - "font-bold text-black mt-4", + labelClassName: "font-bold text-black mt-4", name: "age-range-overlap", rules: { required: "* Required", @@ -920,8 +896,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, required: "* Required", }, diff --git a/lib/libs/webforms/CS12/v202401.ts b/lib/libs/webforms/CS12/v202401.ts index 17e777d0ba..f68c861c26 100644 --- a/lib/libs/webforms/CS12/v202401.ts +++ b/lib/libs/webforms/CS12/v202401.ts @@ -26,11 +26,7 @@ const ageOptions = [ const childStyle = " ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary "; -const ageRangeGroup = ( - nameMod: string, - fullMod: string, - wrapperDep = true, -): RHFSlotProps[] => [ +const ageRangeGroup = (nameMod: string, fullMod: string, wrapperDep = true): RHFSlotProps[] => [ { name: nameMod + "inc-age-standard", descriptionAbove: true, @@ -372,16 +368,11 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, - ...ageRangeGroup( - "county", - "cs12_inc-exception", - false, - ), + ...ageRangeGroup("county", "cs12_inc-exception", false), ], }, ], @@ -412,8 +403,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -429,8 +419,7 @@ export const v202401: FormSchema = { { name: "geo-group", rhf: "FieldArray", - description: - "Enter each geographic area with a unique income standard.", + description: "Enter each geographic area with a unique income standard.", descriptionAbove: true, props: { ...DefaultFieldGroupProps, @@ -448,8 +437,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -462,8 +450,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { diff --git a/lib/libs/webforms/CS15/v202401.ts b/lib/libs/webforms/CS15/v202401.ts index ef54b2fb3b..558bae7c67 100644 --- a/lib/libs/webforms/CS15/v202401.ts +++ b/lib/libs/webforms/CS15/v202401.ts @@ -3,8 +3,7 @@ import { FormSchema } from "shared-types"; export const v202401: FormSchema = { formId: "cs15", header: "CS 15: Separate CHIP MAGI-based income methodologies", - subheader: - "2102(b)(1)(B)(v) of the Social Security Act (SSA) and 42 CFR 457.315", + subheader: "2102(b)(1)(B)(v) of the Social Security Act (SSA) and 42 CFR 457.315", sections: [ { title: "Overview", @@ -212,8 +211,7 @@ export const v202401: FormSchema = { { rhf: "Upload", name: "upload-approval-documentation", - label: - "Upload approval documentation of converted MAGI-equivalent income standards.", + label: "Upload approval documentation of converted MAGI-equivalent income standards.", labelClassName: "text-black font-bold", rules: { required: "* Required" }, formItemClassName: "pb-16", diff --git a/lib/libs/webforms/CS3/v202401.ts b/lib/libs/webforms/CS3/v202401.ts index 11af7fba31..b6d8226dfe 100644 --- a/lib/libs/webforms/CS3/v202401.ts +++ b/lib/libs/webforms/CS3/v202401.ts @@ -41,8 +41,7 @@ export const v202401: FormSchema = { name: "inc-standards", rhf: "WrappedGroup", label: "Age and household income ranges", - description: - "There should be no overlaps in or gaps between ages.", + description: "There should be no overlaps in or gaps between ages.", descriptionAbove: true, labelClassName: "font-bold", fields: [ @@ -50,8 +49,7 @@ export const v202401: FormSchema = { rhf: "FieldArray", name: "age-and-house-inc-range", descriptionClassName: "age-and-house-inc-range", - formItemClassName: - "age-and-house-inc-range [&_select~.slot-form-message]:w-max", + formItemClassName: "age-and-house-inc-range [&_select~.slot-form-message]:w-max", props: { appendText: "Add range", }, diff --git a/lib/libs/webforms/CS7/index.ts b/lib/libs/webforms/CS7/index.ts index 9f925953c2..e5cf77f4fe 100644 --- a/lib/libs/webforms/CS7/index.ts +++ b/lib/libs/webforms/CS7/index.ts @@ -1 +1 @@ -export * from "./v202401"; \ No newline at end of file +export * from "./v202401"; diff --git a/lib/libs/webforms/CS7/v202401.ts b/lib/libs/webforms/CS7/v202401.ts index cbcd62ed6d..78465e3a5a 100644 --- a/lib/libs/webforms/CS7/v202401.ts +++ b/lib/libs/webforms/CS7/v202401.ts @@ -9,8 +9,7 @@ const ageOptions = Array.from({ length: 20 }, (_, i) => ({ export const v202401: FormSchema = { header: "CS 7: Separate CHIP eligibility—Targeted low-income children", - subheader: - "2102(b)(1)(B)(v) of the Social Security Act and 42 CFR 457.310, 457.315, and 457.320", + subheader: "2102(b)(1)(B)(v) of the Social Security Act and 42 CFR 457.310, 457.315, and 457.320", formId: "cs7", sections: [ { @@ -230,8 +229,7 @@ export const v202401: FormSchema = { message: "Must not have leading or trailing whitespace.", }, }, - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", dependency: { conditions: [ { @@ -358,8 +356,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, required: "* Required", }, @@ -395,8 +392,7 @@ export const v202401: FormSchema = { fieldName: "county-field-ranges", fromField: "from-age", toField: "to-age", - message: - "To age must be greater than From age", + message: "To age must be greater than From age", }, ], }, @@ -487,12 +483,10 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, - formItemClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary", dependency: { conditions: [ { @@ -546,8 +540,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, required: "* Required", }, @@ -583,8 +576,7 @@ export const v202401: FormSchema = { fieldName: "city-field-ranges", fromField: "from-age", toField: "to-age", - message: - "To age must be greater than From age", + message: "To age must be greater than From age", }, ], }, @@ -675,12 +667,10 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, - formItemClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary", dependency: { conditions: [ { @@ -734,8 +724,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, required: "* Required", }, @@ -752,8 +741,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -788,8 +776,7 @@ export const v202401: FormSchema = { fieldName: "other-field-ranges", fromField: "from-age", toField: "to-age", - message: - "To age must be greater than From age", + message: "To age must be greater than From age", }, ], }, @@ -880,12 +867,10 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, - formItemClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary", dependency: { conditions: [ { @@ -921,8 +906,7 @@ export const v202401: FormSchema = { slots: [ { rhf: "Select", - label: - "Does the state have a special program for children with disabilities?", + label: "Does the state have a special program for children with disabilities?", labelClassName: "text-black font-bold", name: "does-state-have-special-program", rules: { @@ -938,8 +922,7 @@ export const v202401: FormSchema = { }, { rhf: "Select", - label: - "Is the program available to all eligible targeted low-income children?", + label: "Is the program available to all eligible targeted low-income children?", labelClassName: "text-black font-bold", name: "program-available-to-all-eligible-targeted-low-income-children", rules: { @@ -961,8 +944,7 @@ export const v202401: FormSchema = { rules: { required: "* Required", }, - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", dependency: { conditions: [ @@ -1020,8 +1002,7 @@ export const v202401: FormSchema = { strictGreater: true, fieldName: "cs7_special-program-for-children-with-disabilities_lower-age-limit", - message: - "Upper age limit must be greater than lower age limit", + message: "Upper age limit must be greater than lower age limit", }, ], }, @@ -1032,8 +1013,7 @@ export const v202401: FormSchema = { { label: "The program is limited to targeted low-income children under a certain income level.", - value: - "limited-to-targeted-low-income-children-under-certain-income-level", + value: "limited-to-targeted-low-income-children-under-certain-income-level", slots: [ { rhf: "Input", @@ -1076,8 +1056,7 @@ export const v202401: FormSchema = { }, { rhf: "Textarea", - label: - "Describe the program, including additional benefits offered.", + label: "Describe the program, including additional benefits offered.", labelClassName: "text-black font-bold", name: "describe-the-program", props: { diff --git a/lib/libs/webforms/CS8/v202401.ts b/lib/libs/webforms/CS8/v202401.ts index 8f910b0a40..37e2d2ff0a 100644 --- a/lib/libs/webforms/CS8/v202401.ts +++ b/lib/libs/webforms/CS8/v202401.ts @@ -50,8 +50,7 @@ export const v202401: FormSchema = { subsection: true, form: [ { - description: - "The state provides coverage to pregnant women in the following age ranges:", + description: "The state provides coverage to pregnant women in the following age ranges:", descriptionClassName: "text-base", slots: [ { @@ -103,8 +102,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: @@ -158,8 +156,7 @@ export const v202401: FormSchema = { type: "greaterThanField", strictGreater: true, fieldName: "cs8_age_start-age-range", - message: - "Must be greater than start of age range", + message: "Must be greater than start of age range", }, ], label: "End of age range", @@ -196,8 +193,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "wrapped", props: { - wrapperClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary mb-4", + wrapperClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary mb-4", }, fields: [ { @@ -207,15 +203,13 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "Describe how it’s determined whether the applicant will be provided coverage as a child or as a pregnant woman.", labelClassName: "font-bold", - formItemClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary my-4", + formItemClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary my-4", props: { className: "w-[658px]", }, @@ -368,8 +362,7 @@ export const v202401: FormSchema = { props: { className: "w-[696px]", }, - formItemClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary mb-4", + formItemClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary mb-4", dependency: { conditions: [ { @@ -428,8 +421,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "County", @@ -518,8 +510,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "City", @@ -615,8 +606,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "Geographic Area", @@ -632,8 +622,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, label: "Describe", @@ -655,8 +644,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, @@ -674,8 +662,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, diff --git a/lib/libs/webforms/CS9/v202401.ts b/lib/libs/webforms/CS9/v202401.ts index f19e4ea09f..690ccecf5b 100644 --- a/lib/libs/webforms/CS9/v202401.ts +++ b/lib/libs/webforms/CS9/v202401.ts @@ -2,8 +2,7 @@ import { FormSchema } from "shared-types"; import { noLeadingTrailingWhitespace } from "shared-utils"; export const v202401: FormSchema = { - header: - "CS 9: Separate CHIP eligibility—Coverage from conception to end of pregnancy", + header: "CS 9: Separate CHIP eligibility—Coverage from conception to end of pregnancy", subheader: "Section 2112 of the Social Security Act and 42 CFR 457.10", formId: "cs9", sections: [ @@ -86,8 +85,7 @@ export const v202401: FormSchema = { label: "Describe", labelClassName: "text-black font-bold", name: "age-standard-description", - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", rules: { pattern: { value: noLeadingTrailingWhitespace, @@ -200,8 +198,7 @@ export const v202401: FormSchema = { "Explain, including a description of the overlapping geographic area and the reason for having different income standards.", labelClassName: "text-black font-bold", name: "income-standard-exceptions-description", - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", rules: { pattern: { value: noLeadingTrailingWhitespace, @@ -266,8 +263,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { @@ -282,8 +278,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, @@ -334,8 +329,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { @@ -350,8 +344,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, @@ -403,8 +396,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^\S(.*\S)?$/, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { @@ -419,8 +411,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, required: "* Required", }, @@ -448,8 +439,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, @@ -467,8 +457,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: /^[0-9]\d*$/, - message: - "Must be a positive integer value", + message: "Must be a positive integer value", }, required: "* Required", }, @@ -528,8 +517,7 @@ export const v202401: FormSchema = { props: { options: [ { - label: - "Exempt from requirement of verifying citizenship status", + label: "Exempt from requirement of verifying citizenship status", value: "exempt-from-citizenship-status", }, ], diff --git a/lib/libs/webforms/ER/v202401.ts b/lib/libs/webforms/ER/v202401.ts index b76d3f3a13..beac76d17c 100644 --- a/lib/libs/webforms/ER/v202401.ts +++ b/lib/libs/webforms/ER/v202401.ts @@ -22,8 +22,7 @@ const a1DropdownOptions = [ }, { value: "cfr-435-226", - label: - "Independent foster care adolescents (1902(a)(10)(A)(ii)(XVII)/42 CFR §435.226)", + label: "Independent foster care adolescents (1902(a)(10)(A)(ii)(XVII)/42 CFR §435.226)", }, { value: "cfr-435-229", @@ -47,8 +46,7 @@ const a1DropdownOptions = [ }, { value: "cfr-435-215", - label: - "Individuals with tuberculosis (1902(a)(10)(A)(ii)(XII) and 1902(z)/42 CFR §435.215)", + label: "Individuals with tuberculosis (1902(a)(10)(A)(ii)(XII) and 1902(z)/42 CFR §435.215)", }, { value: "cobra-1902", @@ -77,18 +75,15 @@ const a1DropdownOptions = [ }, { value: "hospice-1905", - label: - "Individuals receiving hospice (1902(a)(10)(A)(ii)(VII) and 1905(o))", + label: "Individuals receiving hospice (1902(a)(10)(A)(ii)(VII) and 1905(o))", }, { value: "cfr-435-225", - label: - "Children under age 19 with a disability (1902(e)(3)/42 CFR §435.225)", + label: "Children under age 19 with a disability (1902(e)(3)/42 CFR §435.225)", }, { value: "age-disability-1902", - label: - "Age and disability-related poverty level (1902(a)(10)(A)(ii)(X) and 1902(m)(1)) ", + label: "Age and disability-related poverty level (1902(a)(10)(A)(ii)(X) and 1902(m)(1)) ", }, { value: "work-1902", label: "Work incentives (1902(a)(10)(A)(ii)(XIII))" }, { @@ -119,8 +114,7 @@ const a1DropdownOptions = [ const a2DroppdownOptionsIncome = [ { value: "65-plus-blind-disability", - label: - "Individuals who are age 65 or older or who have blindness or a disability", + label: "Individuals who are age 65 or older or who have blindness or a disability", }, { value: "qual-med-ben", label: "Qualified medicare beneficiaries " }, { @@ -164,8 +158,7 @@ const a2DroppdownOptionsIncome = [ }, { value: "med-needy-under-21", - label: - "Medically needy reasonable classifications of individuals under age 21", + label: "Medically needy reasonable classifications of individuals under age 21", }, { value: "med-needy-parents", @@ -179,8 +172,7 @@ const a2DroppdownOptionsIncome = [ const a2DroppdownOptionsResource = [ { value: "65-plus-blind-disability", - label: - "Individuals who are age 65 or older or who have blindness or a disability", + label: "Individuals who are age 65 or older or who have blindness or a disability", }, { value: "qual-med-ben", label: "Qualified medicare beneficiaries " }, { @@ -228,8 +220,7 @@ const a2DroppdownOptionsResource = [ }, { value: "med-needy-under-21", - label: - "Medically needy reasonable classifications of individuals under age 21", + label: "Medically needy reasonable classifications of individuals under age 21", }, { value: "med-needy-parents", @@ -294,8 +285,7 @@ const b1DropdownOptions = [ }, { value: "med-needy-under-21", - label: - "Medically needy reasonable classifications of individuals under age 21", + label: "Medically needy reasonable classifications of individuals under age 21", }, { value: "med-needy-parents", @@ -318,13 +308,11 @@ const b2DropdownOptions = [ }, { value: "family-planning", - label: - "Individuals eligible for family planning services (if covered by state)", + label: "Individuals eligible for family planning services (if covered by state)", }, { value: "breat-cervical-cancer", - label: - "Individuals needing treatment for breast or cervical cancer (if covered by state)", + label: "Individuals needing treatment for breast or cervical cancer (if covered by state)", }, ]; @@ -437,8 +425,7 @@ const effectivePeriodSectionChildren = (letter: string): FormGroup[] => { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -483,8 +470,7 @@ export const v202401: FormSchema = { { name: "add-prev-spa", rhf: "Radio", - label: - "Does this SPA add to a previously approved emergency relief SPA in effect?", + label: "Does this SPA add to a previously approved emergency relief SPA in effect?", labelClassName: "font-bold text-black", rules: { required: "* Required", @@ -505,8 +491,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -519,8 +504,7 @@ export const v202401: FormSchema = { { name: "supersede-prev-spa", rhf: "Radio", - label: - "Does this SPA supersede a previously approved emergency relief SPA?", + label: "Does this SPA supersede a previously approved emergency relief SPA?", labelClassName: "font-bold text-black", rules: { required: "* Required", @@ -541,8 +525,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -622,8 +605,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -635,8 +617,7 @@ export const v202401: FormSchema = { { name: "sections-modified", rhf: "Checkbox", - label: - "Sections modified during the period of the public health emergency", + label: "Sections modified during the period of the public health emergency", labelClassName: "font-bold text-black", rules: { required: "* Required", @@ -731,9 +712,7 @@ export const v202401: FormSchema = { subsection: true, title: "A - Eligibility options elected", dependency: { - conditions: [ - { type: "valueExists", name: "ers_a-eligible_options-elected" }, - ], + conditions: [{ type: "valueExists", name: "ers_a-eligible_options-elected" }], effect: { type: "show" }, }, form: [ @@ -847,8 +826,7 @@ export const v202401: FormSchema = { }, { value: "parents-caretakers", - label: - "Medically needy parents and other caretaker relatives", + label: "Medically needy parents and other caretaker relatives", }, { value: "age-blind-disability", @@ -909,8 +887,7 @@ export const v202401: FormSchema = { }, { value: "parents-caretakers", - label: - "Medically needy parents and other caretaker relatives", + label: "Medically needy parents and other caretaker relatives", }, { value: "age-blind-disability", @@ -1130,8 +1107,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, labelClassName: "text-black font-bold", @@ -1178,8 +1154,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, labelClassName: "text-black font-bold", @@ -1355,8 +1330,7 @@ export const v202401: FormSchema = { }, form: [ { - description: - "1. Modify the agency’s approved hospital presumptive eligibility program.", + description: "1. Modify the agency’s approved hospital presumptive eligibility program.", dependency: { conditions: [ { @@ -1426,8 +1400,7 @@ export const v202401: FormSchema = { }, { value: "performance-standards", - label: - "Modify the performance standards for participating hospitals.", + label: "Modify the performance standards for participating hospitals.", slots: [ { rhf: "Textarea", @@ -1488,8 +1461,7 @@ export const v202401: FormSchema = { rhf: "Multiselect", name: "b2-magi-groups", rules: { required: "* Required" }, - label: - "State plan MAGI groups to which presumptive eligibility may be applied", + label: "State plan MAGI groups to which presumptive eligibility may be applied", labelClassName: "font-bold text-black", props: { options: b2DropdownOptions, @@ -1560,8 +1532,7 @@ export const v202401: FormSchema = { ], }, { - description: - "3. Designate additional qualified entities for presumptive eligibility.", + description: "3. Designate additional qualified entities for presumptive eligibility.", dependency: { conditions: [ { @@ -1593,8 +1564,7 @@ export const v202401: FormSchema = { rhf: "Multiselect", name: "b3-magi-groups", rules: { required: "* Required" }, - label: - "State plan MAGI groups to which presumptive eligibility may be applied", + label: "State plan MAGI groups to which presumptive eligibility may be applied", labelClassName: "font-bold text-black", props: { options: b2DropdownOptions, @@ -1714,13 +1684,11 @@ export const v202401: FormSchema = { }, { value: "3-suspend_enrollment_fees", - label: - "3. Suspend enrollment fees, premiums, and similar charges.", + label: "3. Suspend enrollment fees, premiums, and similar charges.", }, { value: "4-reduce_enrollment_fees", - label: - "4. Reduce enrollment fees, premiums, and similar charges.", + label: "4. Reduce enrollment fees, premiums, and similar charges.", }, { value: "5-hardship_waiver", @@ -1739,9 +1707,7 @@ export const v202401: FormSchema = { subsection: true, title: "C - Cost sharing and premiums options elected", dependency: { - conditions: [ - { type: "valueExists", name: "ers_c-costshare_options-elected" }, - ], + conditions: [{ type: "valueExists", name: "ers_c-costshare_options-elected" }], effect: { type: "show" }, }, form: [ @@ -1819,8 +1785,7 @@ export const v202401: FormSchema = { ], }, { - description: - "3. Suspend enrollment fees, premiums, and similar charges.", + description: "3. Suspend enrollment fees, premiums, and similar charges.", dependency: { conditions: [ { @@ -1835,8 +1800,7 @@ export const v202401: FormSchema = { { rhf: "Radio", name: "suspension-beneficiaries", - label: - "The agency suspends enrollment fees, premiums, and similar charges for:", + label: "The agency suspends enrollment fees, premiums, and similar charges for:", labelClassName: "font-bold text-black", rules: { required: "* Required" }, props: { @@ -1867,8 +1831,7 @@ export const v202401: FormSchema = { ], }, { - description: - "4. Reduce enrollment fees, premiums, and similar charges. ", + description: "4. Reduce enrollment fees, premiums, and similar charges. ", dependency: { conditions: [ { @@ -1884,8 +1847,7 @@ export const v202401: FormSchema = { rhf: "Textarea", rules: { required: "* Required" }, name: "how-reduce-fess-premiums-sc", - label: - "How does the agency reduce enrollment fees, premiums, and similar charges?", + label: "How does the agency reduce enrollment fees, premiums, and similar charges?", labelClassName: "text-black font-bold", props: { className: "h-[76px]", @@ -1916,8 +1878,7 @@ export const v202401: FormSchema = { rhf: "Textarea", rules: { required: "* Required" }, name: "unique-hardship-standards", - label: - "What are the standards and/or criteria for determining undue hardship?", + label: "What are the standards and/or criteria for determining undue hardship?", labelClassName: "text-black font-bold", props: { className: "h-[76px]", @@ -1986,8 +1947,7 @@ export const v202401: FormSchema = { }, { value: "3-temp_adj_1915", - label: - "3. Benefits - temporarily adjust the 1915(i) benefit", + label: "3. Benefits - temporarily adjust the 1915(i) benefit", }, { value: "4-compliance_reqs", @@ -2018,8 +1978,7 @@ export const v202401: FormSchema = { }, { value: "7-pharm_adj_supplies", - label: - "7. Pharmacy - adjust days’ supply or quantity limits", + label: "7. Pharmacy - adjust days’ supply or quantity limits", }, { value: "8-pharm_mod_auth", @@ -2027,13 +1986,11 @@ export const v202401: FormSchema = { }, { value: "9-pharm_add_payment", - label: - "9. Pharmacy - add supplement payment to professional dispensing fee", + label: "9. Pharmacy - add supplement payment to professional dispensing fee", }, { value: "10-pharm_establish", - label: - "10. Pharmacy - establish preferred drug list (PDL) exceptions", + label: "10. Pharmacy - establish preferred drug list (PDL) exceptions", }, { value: "11-pharm_waive_sig", @@ -2083,8 +2040,7 @@ export const v202401: FormSchema = { title: "D - Benefits options elected", form: [ { - description: - "1. Benefits—temporarily add optional 1905(a) benefit(s)", + description: "1. Benefits—temporarily add optional 1905(a) benefit(s)", dependency: { conditions: [ { @@ -2239,8 +2195,7 @@ export const v202401: FormSchema = { { rhf: "Textarea", name: "service-scope", - label: - "Service description and/or amount, duration, and scope changes", + label: "Service description and/or amount, duration, and scope changes", labelClassName: "text-black font-bold", props: { className: "h-[76px]" }, rules: { @@ -2268,8 +2223,7 @@ export const v202401: FormSchema = { { rhf: "Textarea", name: "assessment-consent-plan-policies", - label: - "Changes to assessment, consent, and service plan policies", + label: "Changes to assessment, consent, and service plan policies", labelClassName: "text-black font-bold", props: { className: "h-[76px]" }, rules: { @@ -2308,13 +2262,11 @@ export const v202401: FormSchema = { options: [ { value: "suspend-all-services", - label: - "Suspend prior authorization for all covered services.", + label: "Suspend prior authorization for all covered services.", }, { value: "suspend-some-services", - label: - "Suspend prior authorization for certain covered services.", + label: "Suspend prior authorization for certain covered services.", slots: [ { rhf: "Textarea", @@ -2326,8 +2278,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -2353,8 +2304,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -2463,16 +2413,12 @@ export const v202401: FormSchema = { { value: "limits-cat-needy", label: "Limits for categorically needy recipients", - slots: [ - { rhf: "Textarea", name: "limits-cat-needy-desc" }, - ], + slots: [{ rhf: "Textarea", name: "limits-cat-needy-desc" }], }, { value: "limits-med-needy", label: "Limits for medically needy recipients", - slots: [ - { rhf: "Textarea", name: "limits-med-needy-desc" }, - ], + slots: [{ rhf: "Textarea", name: "limits-med-needy-desc" }], }, ], }, @@ -2499,8 +2445,7 @@ export const v202401: FormSchema = { appendText: "Add provider type", divider: true, appendVariant: "default", - fieldArrayClassName: - DefaultFieldGroupProps.fieldArrayClassName, + fieldArrayClassName: DefaultFieldGroupProps.fieldArrayClassName, }, formItemClassName: childStyle, fields: [ @@ -2513,8 +2458,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -2535,8 +2479,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -2550,8 +2493,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -2584,8 +2526,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -2599,8 +2540,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -2701,8 +2641,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2711,8 +2650,7 @@ export const v202401: FormSchema = { }, { value: "service-scope", - label: - "Changes to limits on amount, duration, and scope of service", + label: "Changes to limits on amount, duration, and scope of service", slots: [ { rhf: "Textarea", @@ -2723,8 +2661,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2744,8 +2681,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2765,8 +2701,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2837,8 +2772,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2872,8 +2806,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2906,8 +2839,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2940,8 +2872,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2974,8 +2905,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -2996,8 +2926,7 @@ export const v202401: FormSchema = { options: [ { value: "assured", - label: - "The telehealth will ensure the health and safety of an individual.", + label: "The telehealth will ensure the health and safety of an individual.", slots: [ { rhf: "Textarea", @@ -3008,8 +2937,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, props: { className: "h-[76px]" }, @@ -3024,8 +2952,7 @@ export const v202401: FormSchema = { { rhf: "Textarea", name: "changes-made-to-1915-elig-proc", - label: - "Changes made to the 1915(i) eligibility evaluation process", + label: "Changes made to the 1915(i) eligibility evaluation process", labelClassName: "font-bold text-black", rules: { required: "* Required", @@ -3138,8 +3065,7 @@ export const v202401: FormSchema = { }, { value: "not-applicable", - label: - "Not applicable: The state does not currently have an approved ABP.", + label: "Not applicable: The state does not currently have an approved ABP.", }, ], }, @@ -3164,8 +3090,7 @@ export const v202401: FormSchema = { title: "D - Benefits options elected - telehealth", form: [ { - description: - "6. Telehealth - extend coverage of services provided via telehealth", + description: "6. Telehealth - extend coverage of services provided via telehealth", dependency: { conditions: [ { @@ -3281,8 +3206,7 @@ export const v202401: FormSchema = { ], }, { - description: - "9. Pharmacy - add supplement payment to professional dispensing fee", + description: "9. Pharmacy - add supplement payment to professional dispensing fee", dependency: { conditions: [ { @@ -3301,15 +3225,13 @@ export const v202401: FormSchema = { label: "Payment adjustments made to professional dispensing fee", labelClassName: "font-bold text-black", descriptionAbove: true, - description: - "Agencies must supply documentation to justify the additional fees.", + description: "Agencies must supply documentation to justify the additional fees.", props: { className: "h-[76px]" }, }, ], }, { - description: - "10. Pharmacy - establish preferred drug list (PDL) exceptions", + description: "10. Pharmacy - establish preferred drug list (PDL) exceptions", dependency: { conditions: [ { @@ -3423,8 +3345,7 @@ export const v202401: FormSchema = { }, { value: "5-bed_hold_nf", - label: - "5. Changes to bed hold policies for nursing facilities (NFs)", + label: "5. Changes to bed hold policies for nursing facilities (NFs)", }, { value: "6-bed_hols_icf_iid", @@ -3451,9 +3372,7 @@ export const v202401: FormSchema = { sectionId: "e-options-elected", subsection: true, dependency: { - conditions: [ - { type: "valueExists", name: "ers_e-payments_options-elected" }, - ], + conditions: [{ type: "valueExists", name: "ers_e-payments_options-elected" }], effect: { type: "show" }, }, title: "E - Payments options elected", @@ -3562,8 +3481,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -3723,8 +3641,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -3771,8 +3688,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -3863,8 +3779,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -3899,8 +3814,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -3908,8 +3822,7 @@ export const v202401: FormSchema = { }, { value: "differ-face-to-face", - label: - "Differ from payments for the same services when provided face to face", + label: "Differ from payments for the same services when provided face to face", slots: [ { name: "e3-face-desc", @@ -3923,8 +3836,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -3947,8 +3859,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -4064,8 +3975,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -4093,8 +4003,7 @@ export const v202401: FormSchema = { ], }, { - description: - "5. Changes to bed hold policies for nursing facilities (NFs)", + description: "5. Changes to bed hold policies for nursing facilities (NFs)", dependency: { conditions: [ { @@ -4162,8 +4071,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -4253,8 +4161,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -4345,8 +4252,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -4437,8 +4343,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -4481,8 +4386,7 @@ export const v202401: FormSchema = { }, { value: "2-elect_variance", - label: - "2. Elect a variance to the basic personal needs allowance.", + label: "2. Elect a variance to the basic personal needs allowance.", }, ], }, @@ -4602,8 +4506,7 @@ export const v202401: FormSchema = { ], }, { - description: - "2. Elect a variance to the basic personal needs allowance.", + description: "2. Elect a variance to the basic personal needs allowance.", dependency: { conditions: [ { @@ -4635,20 +4538,17 @@ export const v202401: FormSchema = { }, { sectionId: "g-other-policies", - title: - "G - Other policies and procedures differing from approved Medicaid state plan", + title: "G - Other policies and procedures differing from approved Medicaid state plan", form: [ { slots: [ { rhf: "Textarea", name: "pol-and-procedures", - label: - "Other policies and procedures differing from approved Medicaid state plan", + label: "Other policies and procedures differing from approved Medicaid state plan", labelClassName: "text-black font-bold", descriptionAbove: true, - description: - "This includes legal reference for provision being temporarily amended.", + description: "This includes legal reference for provision being temporarily amended.", }, ], }, diff --git a/lib/libs/webforms/G1/v202401.ts b/lib/libs/webforms/G1/v202401.ts index ae48189fec..b0c745eebd 100644 --- a/lib/libs/webforms/G1/v202401.ts +++ b/lib/libs/webforms/G1/v202401.ts @@ -150,8 +150,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -165,8 +164,7 @@ export const v202401: FormSchema = { ], }, { - title: - "Cost sharing for non-emergency services provided in a hospital emergency department", + title: "Cost sharing for non-emergency services provided in a hospital emergency department", sectionId: "cost-shar-for-non-emergency", form: [ { @@ -337,8 +335,7 @@ export const v202401: FormSchema = { options: [ { value: "true", - label: - "The state identifies which drugs are considered non-preferred.", + label: "The state identifies which drugs are considered non-preferred.", }, ], }, @@ -348,8 +345,7 @@ export const v202401: FormSchema = { name: "assures-timely-process-limit-cost-shar-imposed", rhf: "Checkbox", rules: { required: "* Required" }, - formItemClassName: - "ml-[0.6rem] px-4 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 border-l-4 border-l-primary", dependency: { conditions: [ { @@ -375,8 +371,7 @@ export const v202401: FormSchema = { name: "all-drugs-consider-preferred-drugs", rhf: "Checkbox", rules: { required: "* Required" }, - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", dependency: { conditions: [ { diff --git a/lib/libs/webforms/G2A/v202401.ts b/lib/libs/webforms/G2A/v202401.ts index 823a68b4eb..6b422cc7e0 100644 --- a/lib/libs/webforms/G2A/v202401.ts +++ b/lib/libs/webforms/G2A/v202401.ts @@ -2,8 +2,7 @@ import { FormSchema, DefaultFieldGroupProps } from "shared-types"; import { noLeadingTrailingWhitespace } from "shared-utils/regex"; export const v202401: FormSchema = { - header: - "Premiums and cost sharing G2a: Cost-sharing amounts—Categorically needy individuals", + header: "Premiums and cost sharing G2a: Cost-sharing amounts—Categorically needy individuals", subheader: "1916 | 1916A | 42 CFR 447.52 through 447.54", formId: "g2a", sections: [ @@ -39,8 +38,7 @@ export const v202401: FormSchema = { ], }, { - title: - "Services or items with the same cost-sharing amounts for all incomes", + title: "Services or items with the same cost-sharing amounts for all incomes", subsection: true, sectionId: "services-same-all-income", form: [ @@ -53,8 +51,7 @@ export const v202401: FormSchema = { ...DefaultFieldGroupProps, appendText: "Add service or item", removeText: "Remove", - fieldArrayClassName: - DefaultFieldGroupProps.fieldArrayClassName + "space-y-6", + fieldArrayClassName: DefaultFieldGroupProps.fieldArrayClassName + "space-y-6", }, fields: [ { @@ -167,8 +164,7 @@ export const v202401: FormSchema = { ...DefaultFieldGroupProps, appendText: "Add service or item", removeText: "Remove", - fieldArrayClassName: - DefaultFieldGroupProps.fieldArrayClassName + "space-y-6", + fieldArrayClassName: DefaultFieldGroupProps.fieldArrayClassName + "space-y-6", }, fields: [ { @@ -201,8 +197,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "wrapped", props: { - wrapperClassName: - "space-between flex-row flex w-full gap-5", + wrapperClassName: "space-between flex-row flex w-full gap-5", }, fields: [ { @@ -240,8 +235,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "wrapped", props: { - wrapperClassName: - "space-between flex-row flex w-full gap-5", + wrapperClassName: "space-between flex-row flex w-full gap-5", }, fields: [ { @@ -315,8 +309,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -329,8 +322,7 @@ export const v202401: FormSchema = { ], }, { - title: - "Cost sharing for non-preferred drugs charged to otherwise exempt individuals", + title: "Cost sharing for non-preferred drugs charged to otherwise exempt individuals", subsection: true, sectionId: "cost-share-charge-otherwise-exempt", form: [ @@ -402,8 +394,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "rateWrapper", props: { - wrapperClassName: - "space-between flex-row flex w-full gap-5", + wrapperClassName: "space-between flex-row flex w-full gap-5", }, fields: [ { @@ -562,8 +553,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "rateWrapper", props: { - wrapperClassName: - "space-between flex-row flex w-full gap-5", + wrapperClassName: "space-between flex-row flex w-full gap-5", }, fields: [ { diff --git a/lib/libs/webforms/G2B/v202401.ts b/lib/libs/webforms/G2B/v202401.ts index f86b5665a3..5795ea2956 100644 --- a/lib/libs/webforms/G2B/v202401.ts +++ b/lib/libs/webforms/G2B/v202401.ts @@ -2,8 +2,7 @@ import { FormSchema, DefaultFieldGroupProps } from "shared-types"; import { noLeadingTrailingWhitespace } from "shared-utils/regex"; export const v202401: FormSchema = { - header: - "Premiums and cost sharing G2b: Cost-sharing amounts—Medically needy individuals", + header: "Premiums and cost sharing G2b: Cost-sharing amounts—Medically needy individuals", subheader: "1916 | 1916A | 42 CFR 447.52 through 447.54", formId: "g2b", sections: [ @@ -17,8 +16,7 @@ export const v202401: FormSchema = { name: "state-charge-cost-sharing", rhf: "Select", rules: { required: "* Required" }, - label: - "Does the state charge cost sharing to all medically needy individuals?", + label: "Does the state charge cost sharing to all medically needy individuals?", labelClassName: "font-bold text-[#212121]", props: { className: "w-[125px]", @@ -61,8 +59,7 @@ export const v202401: FormSchema = { }, { - title: - "Services or items with the same cost-sharing amount for all incomes", + title: "Services or items with the same cost-sharing amount for all incomes", sectionId: "services-same-all-incomes", subsection: true, form: [ @@ -220,8 +217,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "wrapped", props: { - wrapperClassName: - "space-between flex-row flex w-full gap-5", + wrapperClassName: "space-between flex-row flex w-full gap-5", }, fields: [ { @@ -259,8 +255,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "wrapped", props: { - wrapperClassName: - "space-between flex-row flex w-full gap-5", + wrapperClassName: "space-between flex-row flex w-full gap-5", }, fields: [ { @@ -334,8 +329,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -349,8 +343,7 @@ export const v202401: FormSchema = { }, { - title: - "Cost sharing for non-preferred drugs charged to otherwise exempt individuals", + title: "Cost sharing for non-preferred drugs charged to otherwise exempt individuals", sectionId: "cost-share-charge-otherwise-exempt", subsection: true, form: [ @@ -422,8 +415,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "rateWrapper", props: { - wrapperClassName: - "space-between flex-row flex w-full gap-5", + wrapperClassName: "space-between flex-row flex w-full gap-5", }, fields: [ { @@ -583,8 +575,7 @@ export const v202401: FormSchema = { rhf: "WrappedGroup", name: "rateWrapper", props: { - wrapperClassName: - "space-between flex-row flex w-full gap-5", + wrapperClassName: "space-between flex-row flex w-full gap-5", }, fields: [ { diff --git a/lib/libs/webforms/G2C/v202401.ts b/lib/libs/webforms/G2C/v202401.ts index 21a830d610..b9f0014279 100644 --- a/lib/libs/webforms/G2C/v202401.ts +++ b/lib/libs/webforms/G2C/v202401.ts @@ -134,8 +134,7 @@ export const v202401: FormSchema = { props: { appendText: "Add service", fieldArrayClassName: - DefaultFieldGroupProps.fieldArrayClassName + - "divider-parent-element", + DefaultFieldGroupProps.fieldArrayClassName + "divider-parent-element", }, fields: [ { @@ -237,8 +236,7 @@ export const v202401: FormSchema = { rules: { pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, diff --git a/lib/libs/webforms/G3/v202401.ts b/lib/libs/webforms/G3/v202401.ts index 2d72cb477b..4725520cad 100644 --- a/lib/libs/webforms/G3/v202401.ts +++ b/lib/libs/webforms/G3/v202401.ts @@ -149,8 +149,7 @@ export const v202401: FormSchema = { subsection: true, form: [ { - description: - "The state may choose to exempt certain groups from cost sharing.", + description: "The state may choose to exempt certain groups from cost sharing.", descriptionClassName: "text-base", slots: [ { @@ -203,8 +202,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -348,8 +346,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -378,8 +375,7 @@ export const v202401: FormSchema = { name: "identify-exempt-from-cost-share", labelClassName: "font-bold text-black", formItemClassName: "border-slate-300 border-t-2 mt-2", - label: - "To identify all other individuals exempt from cost sharing, the state uses:", + label: "To identify all other individuals exempt from cost sharing, the state uses:", rules: { required: "* Required", }, @@ -391,13 +387,11 @@ export const v202401: FormSchema = { }, { value: "eligibility_and_enroll_sys", - label: - "The Eligibility and Enrollment system to flag exempt recipients", + label: "The Eligibility and Enrollment system to flag exempt recipients", }, { value: "medicaid_card_to_indicate", - label: - "The Medicaid card to indicate if a beneficiary is exempt", + label: "The Medicaid card to indicate if a beneficiary is exempt", }, { value: "use_eligi_verif_system", @@ -419,8 +413,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -543,8 +536,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: /^(?:[0-4](?:\.[0-9])?|5(?:\.0)?|\.[0-9])$/, - message: - "Must be between 0% and 5% with max one decimal place", + message: "Must be between 0% and 5% with max one decimal place", }, }, props: { @@ -594,11 +586,9 @@ export const v202401: FormSchema = { { rhf: "Checkbox", name: "how-does-state-track-incurred-prems-and-cost", - label: - "How does the state track each family’s incurred premiums and cost sharing?", + label: "How does the state track each family’s incurred premiums and cost sharing?", labelClassName: "font-bold text-black", - formItemClassName: - "ml-[0.6rem] px-4 mt-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 mt-2 border-l-4 border-l-primary", dependency: { conditions: [ { @@ -634,8 +624,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -658,8 +647,7 @@ export const v202401: FormSchema = { required: "* Required", pattern: { value: noLeadingTrailingWhitespace, - message: - "Must not have leading or trailing whitespace.", + message: "Must not have leading or trailing whitespace.", }, }, }, @@ -675,8 +663,7 @@ export const v202401: FormSchema = { label: "How does the state inform beneficiaries and providers of the beneficiaries' aggregate family limit? How does the state notify beneficiaries and providers when a beneficiary has incurred premiums and cost sharing up to the aggregate family limit and that individual family members are no longer subject to premiums or cost sharing for the remainder of the family's current monthly or quarterly cap period?", labelClassName: "font-bold text-black", - formItemClassName: - "ml-[0.6rem] px-4 mb-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 mb-2 border-l-4 border-l-primary", name: "how-state-bene-agg-fam-limit", dependency: { conditions: [ @@ -703,8 +690,7 @@ export const v202401: FormSchema = { label: "Explain how the state's premium and cost sharing rules do not place beneficiaries at risk of reaching the aggregate family limit.", labelClassName: "font-bold text-black", - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", name: "explain-state-prem-cost-share-dont-place-risk", dependency: { conditions: [ @@ -745,8 +731,7 @@ export const v202401: FormSchema = { rhf: "Textarea", label: "Describe", labelClassName: "font-bold text-black", - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", dependency: { conditions: [ { @@ -817,8 +802,7 @@ export const v202401: FormSchema = { rhf: "Textarea", label: "Describe", labelClassName: "font-bold text-black", - formItemClassName: - "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", + formItemClassName: "ml-[0.6rem] px-4 my-2 border-l-4 border-l-primary", dependency: { conditions: [ { diff --git a/lib/local-aspects/iam-permissions-boundary/index.ts b/lib/local-aspects/iam-permissions-boundary/index.ts index cc47d0c61e..20fc544d9d 100644 --- a/lib/local-aspects/iam-permissions-boundary/index.ts +++ b/lib/local-aspects/iam-permissions-boundary/index.ts @@ -14,27 +14,18 @@ export class IamPermissionsBoundaryAspect implements IAspect { // Check if the node is an instance of the higher-level iam.Role construct if (node instanceof iam.Role) { const roleResource = node.node.defaultChild as iam.CfnRole; - roleResource.addPropertyOverride( - "PermissionsBoundary", - this.permissionsBoundaryArn, - ); + roleResource.addPropertyOverride("PermissionsBoundary", this.permissionsBoundaryArn); } // Check if the node is an instance of a low-level CloudFormation resource (CfnRole) else if (node instanceof iam.CfnRole) { - node.addPropertyOverride( - "PermissionsBoundary", - this.permissionsBoundaryArn, - ); + node.addPropertyOverride("PermissionsBoundary", this.permissionsBoundaryArn); } // For roles created by other constructs such as AutoDeleteObjects which may not be of iam.Role or iam.CfnRole else if ( CfnResource.isCfnResource(node) && (node as CfnResource).cfnResourceType === "AWS::IAM::Role" ) { - (node as iam.CfnRole).addPropertyOverride( - "PermissionsBoundary", - this.permissionsBoundaryArn, - ); + (node as iam.CfnRole).addPropertyOverride("PermissionsBoundary", this.permissionsBoundaryArn); } } } diff --git a/lib/local-constructs/clamav-scanning/index.ts b/lib/local-constructs/clamav-scanning/index.ts index 56e7d60028..b712184087 100644 --- a/lib/local-constructs/clamav-scanning/index.ts +++ b/lib/local-constructs/clamav-scanning/index.ts @@ -10,12 +10,7 @@ import * as kms from "aws-cdk-lib/aws-kms"; import * as lambdaEventSources from "aws-cdk-lib/aws-lambda-event-sources"; import * as destinations from "aws-cdk-lib/aws-lambda-destinations"; import * as cr from "aws-cdk-lib/custom-resources"; -import { - ManagedPolicy, - PolicyDocument, - Role, - ServicePrincipal, -} from "aws-cdk-lib/aws-iam"; +import { ManagedPolicy, PolicyDocument, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; import { LambdaFunction } from "aws-cdk-lib/aws-events-targets"; import { Rule, RuleTargetInput, Schedule } from "aws-cdk-lib/aws-events"; @@ -113,9 +108,7 @@ export class ClamScanScanner extends Construct { this.lambdaRole = new Role(this, "LambdaExecutionRole", { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaBasicExecutionRole", - ), + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), ], inlinePolicies: { LambdaPolicy: new PolicyDocument({ @@ -149,11 +142,7 @@ export class ClamScanScanner extends Construct { resources: ["*"], }), new iam.PolicyStatement({ - actions: [ - "sqs:ReceiveMessage", - "sqs:DeleteMessage", - "sqs:GetQueueAttributes", - ], + actions: ["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"], resources: [notificationQueue.queueArn], }), ], @@ -161,13 +150,9 @@ export class ClamScanScanner extends Construct { }, }); - const clamscanDefsLogGroup = new logs.LogGroup( - this, - `${id}ClamDefsLogGroup`, - { - removalPolicy: cdk.RemovalPolicy.DESTROY, - }, - ); + const clamscanDefsLogGroup = new logs.LogGroup(this, `${id}ClamDefsLogGroup`, { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); const clamDefsLambda = new DockerImageFunction(this, "ServerlessClamDefs", { code: DockerImageCode.fromImageAsset(__dirname, { @@ -185,13 +170,9 @@ export class ClamScanScanner extends Construct { }, }); - const clamscanLambdaLogGroup = new logs.LogGroup( - this, - `${id}ClamscanLambdaLogGroup`, - { - removalPolicy: cdk.RemovalPolicy.DESTROY, - }, - ); + const clamscanLambdaLogGroup = new logs.LogGroup(this, `${id}ClamscanLambdaLogGroup`, { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); const clamscanLambda = new DockerImageFunction(this, "ServerlessClamscan", { code: DockerImageCode.fromImageAsset(__dirname), @@ -209,9 +190,7 @@ export class ClamScanScanner extends Construct { }); // Add the SQS queue as an event source to the Lambda function - clamscanLambda.addEventSource( - new lambdaEventSources.SqsEventSource(notificationQueue), - ); + clamscanLambda.addEventSource(new lambdaEventSources.SqsEventSource(notificationQueue)); const rule = new Rule(this, "ClamscanScheduleRule", { schedule: Schedule.expression("cron(0/2 0-6,8-23 * * ? *)"), @@ -263,9 +242,7 @@ export class ClamScanScanner extends Construct { ]), }, ); - const policy = invokeClamDefsCustomResource.node.findChild( - "CustomResourcePolicy", - ); + const policy = invokeClamDefsCustomResource.node.findChild("CustomResourcePolicy"); invokeClamDefsCustomResource.node.addDependency(policy); invokeClamDefsCustomResourceLogGroup.node.addDependency(policy); } diff --git a/lib/local-constructs/clamav-scanning/src/handlers/scan.test.ts b/lib/local-constructs/clamav-scanning/src/handlers/scan.test.ts index 2545393095..5989f5b630 100644 --- a/lib/local-constructs/clamav-scanning/src/handlers/scan.test.ts +++ b/lib/local-constructs/clamav-scanning/src/handlers/scan.test.ts @@ -85,11 +85,7 @@ test("should handle event and return scan results", async () => { expect(downloadFileFromS3).toHaveBeenCalledWith("test-key", "test-bucket"); expect(checkFileExt).toHaveBeenCalledWith("file-location"); expect(scanLocalFile).toHaveBeenCalledWith("file-location"); - expect(tagWithScanStatus).toHaveBeenCalledWith( - "test-bucket", - "test-key", - STATUS_CLEAN_FILE, - ); + expect(tagWithScanStatus).toHaveBeenCalledWith("test-bucket", "test-key", STATUS_CLEAN_FILE); expect(result).toEqual([STATUS_CLEAN_FILE, STATUS_CLEAN_FILE]); }); diff --git a/lib/local-constructs/clamav-scanning/src/handlers/scan.ts b/lib/local-constructs/clamav-scanning/src/handlers/scan.ts index ba736abadc..6cf0138803 100644 --- a/lib/local-constructs/clamav-scanning/src/handlers/scan.ts +++ b/lib/local-constructs/clamav-scanning/src/handlers/scan.ts @@ -37,13 +37,7 @@ export async function handler(event: any): Promise { s3ObjectKey = extractKeyFromS3Event(sqsMessageBody); s3ObjectBucket = extractBucketFromS3Event(sqsMessageBody); } catch (error) { - logger.error( - `Error extracting data from record: ${JSON.stringify( - record, - null, - 2, - )}` + error, - ); + logger.error(`Error extracting data from record: ${JSON.stringify(record, null, 2)}` + error); results.push(STATUS_ERROR_PROCESSING_FILE); continue; } @@ -57,10 +51,7 @@ export async function handler(event: any): Promise { results.push(virusScanStatus); continue; } - const fileLoc: string = await downloadFileFromS3( - s3ObjectKey, - s3ObjectBucket, - ); + const fileLoc: string = await downloadFileFromS3(s3ObjectKey, s3ObjectBucket); virusScanStatus = await checkFileExt(fileLoc); if (virusScanStatus !== STATUS_CLEAN_FILE) { await tagWithScanStatus(s3ObjectBucket, s3ObjectKey, virusScanStatus); diff --git a/lib/local-constructs/clamav-scanning/src/lib/clamav.ts b/lib/local-constructs/clamav-scanning/src/lib/clamav.ts index bed0f133e5..ab54ca8b15 100644 --- a/lib/local-constructs/clamav-scanning/src/lib/clamav.ts +++ b/lib/local-constructs/clamav-scanning/src/lib/clamav.ts @@ -20,10 +20,7 @@ export const updateAVDefinitonsWithFreshclam = (): boolean => { try { const { stdout, stderr }: SpawnSyncReturns = spawnSync( `${constants.PATH_TO_FRESHCLAM}`, - [ - `--config-file=${constants.FRESHCLAM_CONFIG}`, - `--datadir=${constants.FRESHCLAM_WORK_DIR}`, - ], + [`--config-file=${constants.FRESHCLAM_CONFIG}`, `--datadir=${constants.FRESHCLAM_WORK_DIR}`], ); logger.info("Update message"); logger.info(stdout.toString()); @@ -52,54 +49,41 @@ export const downloadAVDefinitions = async (): Promise => { // List all the files in the bucket logger.info("Downloading Definitions"); - const allFileKeys: string[] = await listBucketFiles( - constants.CLAMAV_BUCKET_NAME, - ); + const allFileKeys: string[] = await listBucketFiles(constants.CLAMAV_BUCKET_NAME); const definitionFileKeys: string[] = allFileKeys .filter((key) => key.startsWith(constants.PATH_TO_AV_DEFINITIONS)) .map((fullPath) => path.basename(fullPath)); // Download each file in the bucket - const downloadPromises: Promise[] = definitionFileKeys.map( - (filenameToDownload) => { - return new Promise((resolve, reject) => { - const destinationFile: string = path.join( - constants.FRESHCLAM_WORK_DIR, - filenameToDownload, - ); - - logger.info( - `Downloading ${filenameToDownload} from S3 to ${destinationFile}`, - ); - - const options = { - Bucket: constants.CLAMAV_BUCKET_NAME, - Key: `${constants.PATH_TO_AV_DEFINITIONS}/${filenameToDownload}`, - }; - - try { - s3Client - .send(new GetObjectCommand(options)) - .then(async ({ Body }) => { - if (!Body || !(Body instanceof Readable)) { - throw new Error("Invalid Body type received from S3"); - } - - await asyncfs.writeFile(destinationFile, Body); - resolve(); - logger.info(`Finished download ${filenameToDownload}`); - }); - } catch (err) { - logger.info( - `Error downloading definition file ${filenameToDownload}`, - ); - logger.error(err); - reject(); - } - }); - }, - ); + const downloadPromises: Promise[] = definitionFileKeys.map((filenameToDownload) => { + return new Promise((resolve, reject) => { + const destinationFile: string = path.join(constants.FRESHCLAM_WORK_DIR, filenameToDownload); + + logger.info(`Downloading ${filenameToDownload} from S3 to ${destinationFile}`); + + const options = { + Bucket: constants.CLAMAV_BUCKET_NAME, + Key: `${constants.PATH_TO_AV_DEFINITIONS}/${filenameToDownload}`, + }; + + try { + s3Client.send(new GetObjectCommand(options)).then(async ({ Body }) => { + if (!Body || !(Body instanceof Readable)) { + throw new Error("Invalid Body type received from S3"); + } + + await asyncfs.writeFile(destinationFile, Body); + resolve(); + logger.info(`Finished download ${filenameToDownload}`); + }); + } catch (err) { + logger.info(`Error downloading definition file ${filenameToDownload}`); + logger.error(err); + reject(); + } + }); + }); return await Promise.all(downloadPromises); }; @@ -111,9 +95,7 @@ export const uploadAVDefinitions = async (): Promise => { // delete all the definitions currently in the bucket. // first list them. logger.info("Uploading Definitions"); - const s3AllFullKeys: string[] = await listBucketFiles( - constants.CLAMAV_BUCKET_NAME, - ); + const s3AllFullKeys: string[] = await listBucketFiles(constants.CLAMAV_BUCKET_NAME); const s3DefinitionFileFullKeys: string[] = s3AllFullKeys.filter((key) => key.startsWith(constants.PATH_TO_AV_DEFINITIONS), ); @@ -134,47 +116,37 @@ export const uploadAVDefinitions = async (): Promise => { logger.info(`Deleted extant definitions: ${s3DefinitionFileFullKeys}`); } catch (err) { - logger.info( - `Error deleting current definition files: ${s3DefinitionFileFullKeys}`, - ); + logger.info(`Error deleting current definition files: ${s3DefinitionFileFullKeys}`); logger.info(err); throw err; } } // list all the files in the work dir for upload - const definitionFiles: string[] = fs.readdirSync( - constants.FRESHCLAM_WORK_DIR, - ); - - const uploadPromises: Promise[] = definitionFiles.map( - (filenameToUpload) => { - return new Promise((resolve, reject) => { - logger.info( - `Uploading updated definitions for file ${filenameToUpload} ---`, - ); - - const options = { - Bucket: constants.CLAMAV_BUCKET_NAME, - Key: `${constants.PATH_TO_AV_DEFINITIONS}/${filenameToUpload}`, - Body: fs.readFileSync( - path.join(constants.FRESHCLAM_WORK_DIR, filenameToUpload), - ), - }; - - try { - s3Client.send(new PutObjectCommand(options)).then(() => { - logger.info(`--- Finished uploading ${filenameToUpload} ---`); - resolve(); - }); - } catch (err) { - logger.info(`--- Error uploading ${filenameToUpload} ---`); - logger.info(err); - reject(); - } - }); - }, - ); + const definitionFiles: string[] = fs.readdirSync(constants.FRESHCLAM_WORK_DIR); + + const uploadPromises: Promise[] = definitionFiles.map((filenameToUpload) => { + return new Promise((resolve, reject) => { + logger.info(`Uploading updated definitions for file ${filenameToUpload} ---`); + + const options = { + Bucket: constants.CLAMAV_BUCKET_NAME, + Key: `${constants.PATH_TO_AV_DEFINITIONS}/${filenameToUpload}`, + Body: fs.readFileSync(path.join(constants.FRESHCLAM_WORK_DIR, filenameToUpload)), + }; + + try { + s3Client.send(new PutObjectCommand(options)).then(() => { + logger.info(`--- Finished uploading ${filenameToUpload} ---`); + resolve(); + }); + } catch (err) { + logger.info(`--- Error uploading ${filenameToUpload} ---`); + logger.info(err); + reject(); + } + }); + }); return await Promise.all(uploadPromises); }; @@ -190,9 +162,7 @@ export const uploadAVDefinitions = async (): Promise => { * * @param pathToFile Path in the filesystem where the file is stored. */ -export const scanLocalFile = async ( - pathToFile: string, -): Promise => { +export const scanLocalFile = async (pathToFile: string): Promise => { try { const avResult: SpawnSyncReturns = spawnSync( "clamdscan", diff --git a/lib/local-constructs/clamav-scanning/src/lib/clamd.ts b/lib/local-constructs/clamav-scanning/src/lib/clamd.ts index ff71aab150..f67c2a1e05 100644 --- a/lib/local-constructs/clamav-scanning/src/lib/clamd.ts +++ b/lib/local-constructs/clamav-scanning/src/lib/clamd.ts @@ -61,11 +61,7 @@ export async function startClamd() { if (timePassed >= MAX_WAIT_TIME) { clearInterval(checkClamdReady); - reject( - new Error( - "clamd did not become fully operational within 30 seconds.", - ), - ); + reject(new Error("clamd did not become fully operational within 30 seconds.")); } }, SLEEP_INTERVAL); }); diff --git a/lib/local-constructs/clamav-scanning/src/lib/constants.ts b/lib/local-constructs/clamav-scanning/src/lib/constants.ts index c51cf7619e..c9ba371179 100644 --- a/lib/local-constructs/clamav-scanning/src/lib/constants.ts +++ b/lib/local-constructs/clamav-scanning/src/lib/constants.ts @@ -20,8 +20,7 @@ import process from "process"; // Various paths and application names on S3 export const ATTACHMENTS_BUCKET: string = process.env.ATTACHMENTS_BUCKET!; export const CLAMAV_BUCKET_NAME: string = process.env.CLAMAV_BUCKET_NAME!; -export const PATH_TO_AV_DEFINITIONS: string = - process.env.PATH_TO_AV_DEFINITIONS!; +export const PATH_TO_AV_DEFINITIONS: string = process.env.PATH_TO_AV_DEFINITIONS!; export const PATH_TO_FRESHCLAM: string = "/bin/freshclam"; export const PATH_TO_CLAMAV: string = "/bin/clamscan"; export const FRESHCLAM_CONFIG: string = "/bin/freshclam.conf"; @@ -29,21 +28,16 @@ export const FRESHCLAM_WORK_DIR: string = "/tmp/"; export const TMP_DOWNLOAD_PATH: string = "/tmp/download/"; // Constants for tagging file after a virus scan. -export const STATUS_CLEAN_FILE: string = - process.env.STATUS_CLEAN_FILE || "CLEAN"; -export const STATUS_INFECTED_FILE: string = - process.env.STATUS_INFECTED_FILE || "INFECTED"; +export const STATUS_CLEAN_FILE: string = process.env.STATUS_CLEAN_FILE || "CLEAN"; +export const STATUS_INFECTED_FILE: string = process.env.STATUS_INFECTED_FILE || "INFECTED"; export const STATUS_ERROR_PROCESSING_FILE: string = process.env.STATUS_ERROR_PROCESSING_FILE || "ERROR"; -export const STATUS_SKIPPED_FILE: string = - process.env.STATUS_SKIPPED_FILE || "SKIPPED"; +export const STATUS_SKIPPED_FILE: string = process.env.STATUS_SKIPPED_FILE || "SKIPPED"; export const STATUS_EXTENSION_MISMATCH_FILE: string = process.env.STATUS_EXTENSION_MISMATCH_FILE || "EXTMISMATCH"; -export const STATUS_UNKNOWN_EXTENSION: string = - process.env.STATUS_UNKNOWN_EXTENSION || "UKNOWNEXT"; +export const STATUS_UNKNOWN_EXTENSION: string = process.env.STATUS_UNKNOWN_EXTENSION || "UKNOWNEXT"; export const STATUS_TOO_BIG: string = process.env.STATUS_TOO_BIG || "TOOBIG"; -export const VIRUS_SCAN_STATUS_KEY: string = - process.env.VIRUS_SCAN_STATUS_KEY || "virusScanStatus"; +export const VIRUS_SCAN_STATUS_KEY: string = process.env.VIRUS_SCAN_STATUS_KEY || "virusScanStatus"; export const VIRUS_SCAN_TIMESTAMP_KEY: string = process.env.VIRUS_SCAN_TIMESTAMP_KEY || "virusScanTimestamp"; export const MAX_FILE_SIZE: string = process.env.MAX_FILE_SIZE || "314572800"; diff --git a/lib/local-constructs/clamav-scanning/src/lib/file-ext.ts b/lib/local-constructs/clamav-scanning/src/lib/file-ext.ts index f86b629e99..22a238fb80 100644 --- a/lib/local-constructs/clamav-scanning/src/lib/file-ext.ts +++ b/lib/local-constructs/clamav-scanning/src/lib/file-ext.ts @@ -37,10 +37,7 @@ export async function checkFileExt(pathToFile: string): Promise { logger.info(`File mimetype from contents: ${mimeTypeFromContents}`); // Check if the mimes are equivalent - const same = areMimeTypesEquivalent( - mimeTypeFromExtension, - mimeTypeFromContents, - ); + const same = areMimeTypesEquivalent(mimeTypeFromExtension, mimeTypeFromContents); // Error out if we can't determine equivalence if (!same) { logger.info( @@ -61,9 +58,7 @@ function isAllowedMime(mime: string): boolean { return FILE_TYPES.some((fileType) => fileType.mime === mime); } -async function getFileTypeFromContents( - filePath: string, -): Promise { +async function getFileTypeFromContents(filePath: string): Promise { try { const fileBuffer = await fs.promises.readFile(filePath); @@ -90,9 +85,7 @@ async function getFileTypeFromContents( } } if (!type?.mime) { - logger.info( - `getFileTypeFromContents: File determined to be mime:${type?.mime}`, - ); + logger.info(`getFileTypeFromContents: File determined to be mime:${type?.mime}`); return false; } logger.info( diff --git a/lib/local-constructs/clamav-scanning/src/lib/s3.ts b/lib/local-constructs/clamav-scanning/src/lib/s3.ts index 2e0addb3db..40d24b8cc1 100644 --- a/lib/local-constructs/clamav-scanning/src/lib/s3.ts +++ b/lib/local-constructs/clamav-scanning/src/lib/s3.ts @@ -16,10 +16,7 @@ import { Readable } from "stream"; const s3Client: S3Client = new S3Client(); -export async function checkFileSize( - key: string, - bucket: string, -): Promise { +export async function checkFileSize(key: string, bucket: string): Promise { try { const res: HeadObjectCommandOutput = await s3Client.send( new HeadObjectCommand({ Key: key, Bucket: bucket }), @@ -29,9 +26,7 @@ export async function checkFileSize( res.ContentLength === null || typeof res.ContentLength !== "number" ) { - logger.info( - `ContentLength is invalid for S3 Object: s3://${bucket}/${key}`, - ); + logger.info(`ContentLength is invalid for S3 Object: s3://${bucket}/${key}`); return constants.STATUS_ERROR_PROCESSING_FILE; } return res.ContentLength > parseInt(constants.MAX_FILE_SIZE) @@ -51,9 +46,7 @@ export async function downloadFileFromS3( fs.mkdirSync(constants.TMP_DOWNLOAD_PATH); } - const localPath: string = `${ - constants.TMP_DOWNLOAD_PATH - }${randomUUID()}--${s3ObjectKey}`; + const localPath: string = `${constants.TMP_DOWNLOAD_PATH}${randomUUID()}--${s3ObjectKey}`; fs.createWriteStream(localPath); logger.info(`Downloading file s3://${s3ObjectBucket}/${s3ObjectKey}`); @@ -109,9 +102,7 @@ export async function tagWithScanStatus( export async function listBucketFiles(bucketName: string): Promise { try { - const listFilesResult = await s3Client.send( - new ListObjectsV2Command({ Bucket: bucketName }), - ); + const listFilesResult = await s3Client.send(new ListObjectsV2Command({ Bucket: bucketName })); if (listFilesResult.Contents) { const keys = listFilesResult.Contents.map((c) => c.Key) as string[]; return keys; diff --git a/lib/local-constructs/cleanup-kafka/index.test.ts b/lib/local-constructs/cleanup-kafka/index.test.ts index 795533d4fa..6441ec326c 100644 --- a/lib/local-constructs/cleanup-kafka/index.test.ts +++ b/lib/local-constructs/cleanup-kafka/index.test.ts @@ -19,9 +19,7 @@ describe("CleanupKafka", () => { availabilityZone: "us-west-2a", }), ]; - const securityGroups = [ - new ec2.SecurityGroup(stack, "SecurityGroup", { vpc }), - ]; + const securityGroups = [new ec2.SecurityGroup(stack, "SecurityGroup", { vpc })]; const brokerString = "mockBrokerString"; const topicPatternsToDelete = ["mockTopicPattern"]; @@ -34,9 +32,7 @@ describe("CleanupKafka", () => { }); it("should create a log group for the Lambda function", () => { - const logGroup = cleanupKafka.node.findChild( - "cleanupKafkaLogGroup", - ) as logs.LogGroup; + const logGroup = cleanupKafka.node.findChild("cleanupKafkaLogGroup") as logs.LogGroup; expect(logGroup).toBeInstanceOf(logs.LogGroup); }); diff --git a/lib/local-constructs/cleanup-kafka/index.ts b/lib/local-constructs/cleanup-kafka/index.ts index 749a68166e..155ef13255 100644 --- a/lib/local-constructs/cleanup-kafka/index.ts +++ b/lib/local-constructs/cleanup-kafka/index.ts @@ -32,13 +32,7 @@ export class CleanupKafka extends Construct { constructor(scope: Construct, id: string, props: CleanupKafkaProps) { super(scope, id); - const { - vpc, - privateSubnets, - securityGroups, - brokerString, - topicPatternsToDelete, - } = props; + const { vpc, privateSubnets, securityGroups, brokerString, topicPatternsToDelete } = props; const logGroup = new LogGroup(this, `cleanupKafkaLogGroup`, { removalPolicy: RemovalPolicy.DESTROY, @@ -53,12 +47,8 @@ export class CleanupKafka extends Construct { role: new Role(this, "CleanupKafkaLambdaExecutionRole", { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaBasicExecutionRole", - ), - ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaVPCAccessExecutionRole", - ), + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaVPCAccessExecutionRole"), ], inlinePolicies: { InvokeLambdaPolicy: new PolicyDocument({ @@ -81,47 +71,39 @@ export class CleanupKafka extends Construct { bundling: commonBundlingOptions, }); - const customResourceLogGroup = new LogGroup( - this, - `cleanupKafkaCustomResourceLogGroup`, - { - removalPolicy: RemovalPolicy.DESTROY, - }, - ); + const customResourceLogGroup = new LogGroup(this, `cleanupKafkaCustomResourceLogGroup`, { + removalPolicy: RemovalPolicy.DESTROY, + }); - const customResource = new AwsCustomResource( - this, - "CleanupKafkaCustomResource", - { - onDelete: { - service: "Lambda", - action: "invoke", - parameters: { - FunctionName: lambda.functionName, - Payload: JSON.stringify({ - RequestType: "Delete", - ResourceProperties: { - brokerString, - topicPatternsToDelete, - }, - }), - }, - physicalResourceId: PhysicalResourceId.of("cleanup-kafka"), - }, - logGroup: customResourceLogGroup, - policy: AwsCustomResourcePolicy.fromStatements([ - new PolicyStatement({ - actions: ["lambda:InvokeFunction"], - resources: [lambda.functionArn], - }), - new PolicyStatement({ - effect: Effect.DENY, - actions: ["logs:CreateLogGroup"], - resources: ["*"], + const customResource = new AwsCustomResource(this, "CleanupKafkaCustomResource", { + onDelete: { + service: "Lambda", + action: "invoke", + parameters: { + FunctionName: lambda.functionName, + Payload: JSON.stringify({ + RequestType: "Delete", + ResourceProperties: { + brokerString, + topicPatternsToDelete, + }, }), - ]), + }, + physicalResourceId: PhysicalResourceId.of("cleanup-kafka"), }, - ); + logGroup: customResourceLogGroup, + policy: AwsCustomResourcePolicy.fromStatements([ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: [lambda.functionArn], + }), + new PolicyStatement({ + effect: Effect.DENY, + actions: ["logs:CreateLogGroup"], + resources: ["*"], + }), + ]), + }); const policy = customResource.node.findChild("CustomResourcePolicy"); customResource.node.addDependency(policy); customResourceLogGroup.node.addDependency(policy); diff --git a/lib/local-constructs/cleanup-kafka/src/cleanupKafka.test.ts b/lib/local-constructs/cleanup-kafka/src/cleanupKafka.test.ts index e04425d2f7..cf0b5f9dd5 100644 --- a/lib/local-constructs/cleanup-kafka/src/cleanupKafka.test.ts +++ b/lib/local-constructs/cleanup-kafka/src/cleanupKafka.test.ts @@ -30,10 +30,7 @@ describe("handler", () => { await handler(event, context); - expect(topics.deleteTopics).toHaveBeenCalledWith( - BrokerString, - TopicPatternsToDelete, - ); + expect(topics.deleteTopics).toHaveBeenCalledWith(BrokerString, TopicPatternsToDelete); expect(topics.deleteTopics).toHaveBeenCalledTimes(1); }); diff --git a/lib/local-constructs/cleanup-kafka/src/cleanupKafka.ts b/lib/local-constructs/cleanup-kafka/src/cleanupKafka.ts index 90a3cd72f4..8c21e516aa 100644 --- a/lib/local-constructs/cleanup-kafka/src/cleanupKafka.ts +++ b/lib/local-constructs/cleanup-kafka/src/cleanupKafka.ts @@ -1,14 +1,11 @@ import { CloudFormationCustomResourceEvent } from "aws-lambda"; import * as topics from "../../../libs/topics-lib"; -export const handler = async function ( - event: CloudFormationCustomResourceEvent, -): Promise { +export const handler = async function (event: CloudFormationCustomResourceEvent): Promise { console.log("Request:", JSON.stringify(event, undefined, 2)); const BrokerString: string = event.ResourceProperties.brokerString; - const TopicPatternsToDelete: string[] = - event.ResourceProperties.topicPatternsToDelete; + const TopicPatternsToDelete: string[] = event.ResourceProperties.topicPatternsToDelete; const requiredPattern = /^--.*--.*--/; // Regular expression to match the required format TopicPatternsToDelete.forEach((pattern) => { @@ -17,9 +14,7 @@ export const handler = async function ( } }); - console.log( - `Attempting a delete for each of the following patterns: ${TopicPatternsToDelete}`, - ); + console.log(`Attempting a delete for each of the following patterns: ${TopicPatternsToDelete}`); const maxRetries = 10; const retryDelay = 10000; //10s @@ -30,15 +25,9 @@ export const handler = async function ( await topics.deleteTopics(BrokerString, TopicPatternsToDelete); success = true; } catch (error) { - console.error( - `Error in deleteTopics operation: ${JSON.stringify(error)}`, - ); + console.error(`Error in deleteTopics operation: ${JSON.stringify(error)}`); retries++; - console.log( - `Retrying in ${ - retryDelay / 1000 - } seconds (Retry ${retries}/${maxRetries})`, - ); + console.log(`Retrying in ${retryDelay / 1000} seconds (Retry ${retries}/${maxRetries})`); await new Promise((resolve) => setTimeout(resolve, retryDelay)); } } diff --git a/lib/local-constructs/cloudwatch-logs-resource-policy/index.ts b/lib/local-constructs/cloudwatch-logs-resource-policy/index.ts index 974e996670..0fd54ae7b3 100644 --- a/lib/local-constructs/cloudwatch-logs-resource-policy/index.ts +++ b/lib/local-constructs/cloudwatch-logs-resource-policy/index.ts @@ -9,40 +9,32 @@ interface CloudWatchLogsResourcePolicyProps { export class CloudWatchLogsResourcePolicy extends Construct { public readonly policy: CfnResourcePolicy; - constructor( - scope: Construct, - id: string, - props: CloudWatchLogsResourcePolicyProps, - ) { + constructor(scope: Construct, id: string, props: CloudWatchLogsResourcePolicyProps) { super(scope, id); const stack = Stack.of(this); - this.policy = new CfnResourcePolicy( - this, - `CentralizedCloudWatchLogsResourcePolicy`, - { - policyName: `${props.project}-centralized-logs-policy-${id}`, - policyDocument: JSON.stringify({ - Version: "2012-10-17", - Statement: [ - { - Effect: "Allow", - Principal: { Service: "delivery.logs.amazonaws.com" }, - Action: ["logs:CreateLogStream", "logs:PutLogEvents"], - Resource: [ - `arn:aws:logs:*:${stack.account}:log-group:aws-waf-logs-*`, - `arn:aws:logs:*:${stack.account}:log-group:/aws/http-api/*`, - `arn:aws:logs:*:${stack.account}:log-group:/aws/vendedlogs/*`, - ], - Condition: { - StringEquals: { "aws:SourceAccount": stack.account }, - ArnLike: { - "aws:SourceArn": `arn:aws:logs:${stack.region}:${stack.account}:*`, - }, + this.policy = new CfnResourcePolicy(this, `CentralizedCloudWatchLogsResourcePolicy`, { + policyName: `${props.project}-centralized-logs-policy-${id}`, + policyDocument: JSON.stringify({ + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { Service: "delivery.logs.amazonaws.com" }, + Action: ["logs:CreateLogStream", "logs:PutLogEvents"], + Resource: [ + `arn:aws:logs:*:${stack.account}:log-group:aws-waf-logs-*`, + `arn:aws:logs:*:${stack.account}:log-group:/aws/http-api/*`, + `arn:aws:logs:*:${stack.account}:log-group:/aws/vendedlogs/*`, + ], + Condition: { + StringEquals: { "aws:SourceAccount": stack.account }, + ArnLike: { + "aws:SourceArn": `arn:aws:logs:${stack.region}:${stack.account}:*`, }, }, - ], - }), - }, - ); + }, + ], + }), + }); } } diff --git a/lib/local-constructs/cloudwatch-to-s3/index.test.ts b/lib/local-constructs/cloudwatch-to-s3/index.test.ts index 736bce440c..256f0244b8 100644 --- a/lib/local-constructs/cloudwatch-to-s3/index.test.ts +++ b/lib/local-constructs/cloudwatch-to-s3/index.test.ts @@ -31,22 +31,15 @@ describe("CloudWatchToS3", () => { }); it("should create IAM roles with appropriate policies", () => { - const firehoseRole = cloudWatchToS3.node.findChild( - "FirehoseRole", - ) as iam.Role; + const firehoseRole = cloudWatchToS3.node.findChild("FirehoseRole") as iam.Role; expect(firehoseRole).toBeInstanceOf(iam.Role); - const policyDocument = - firehoseRole.assumeRolePolicy?.toJSON() as iam.PolicyDocumentProps; - const statement = policyDocument.Statement.find( - (s) => s.Principal && s.Principal.Service, - ); + const policyDocument = firehoseRole.assumeRolePolicy?.toJSON() as iam.PolicyDocumentProps; + const statement = policyDocument.Statement.find((s) => s.Principal && s.Principal.Service); expect(statement).toBeDefined(); expect(statement.Principal.Service).toContain("firehose.amazonaws.com"); - const policy = firehoseRole.node.tryFindChild( - "DefaultPolicy", - ) as iam.Policy; + const policy = firehoseRole.node.tryFindChild("DefaultPolicy") as iam.Policy; const statements = policy.document.statements; expect(statements).toEqual( expect.arrayContaining([ diff --git a/lib/local-constructs/cloudwatch-to-s3/index.ts b/lib/local-constructs/cloudwatch-to-s3/index.ts index 584e0562f9..a66fcf02f1 100644 --- a/lib/local-constructs/cloudwatch-to-s3/index.ts +++ b/lib/local-constructs/cloudwatch-to-s3/index.ts @@ -2,12 +2,7 @@ import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; import { Bucket } from "aws-cdk-lib/aws-s3"; import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose"; -import { - Role, - ServicePrincipal, - PolicyStatement, - PolicyDocument, -} from "aws-cdk-lib/aws-iam"; +import { Role, ServicePrincipal, PolicyStatement, PolicyDocument } from "aws-cdk-lib/aws-iam"; import { LogGroup, CfnSubscriptionFilter } from "aws-cdk-lib/aws-logs"; interface CloudWatchToS3Props { @@ -76,9 +71,7 @@ export class CloudWatchToS3 extends Construct { statements: [ new PolicyStatement({ actions: ["firehose:PutRecord", "firehose:PutRecordBatch"], - resources: [ - cdk.Fn.getAtt(this.deliveryStream.logicalId, "Arn").toString(), - ], + resources: [cdk.Fn.getAtt(this.deliveryStream.logicalId, "Arn").toString()], }), ], }), @@ -89,10 +82,7 @@ export class CloudWatchToS3 extends Construct { new CfnSubscriptionFilter(this, "SubscriptionFilter", { logGroupName: logGroup.logGroupName, filterPattern: filterPattern, - destinationArn: cdk.Fn.getAtt( - this.deliveryStream.logicalId, - "Arn", - ).toString(), + destinationArn: cdk.Fn.getAtt(this.deliveryStream.logicalId, "Arn").toString(), roleArn: subscriptionFilterRole.roleArn, }); } diff --git a/lib/local-constructs/create-topics/index.test.ts b/lib/local-constructs/create-topics/index.test.ts index 6d13b06e78..c72f5e01f7 100644 --- a/lib/local-constructs/create-topics/index.test.ts +++ b/lib/local-constructs/create-topics/index.test.ts @@ -19,9 +19,7 @@ describe("CreateTopics", () => { availabilityZone: "us-west-2a", }), ]; - const securityGroups = [ - new ec2.SecurityGroup(stack, "SecurityGroup", { vpc }), - ]; + const securityGroups = [new ec2.SecurityGroup(stack, "SecurityGroup", { vpc })]; const brokerString = "mockBrokerString"; const topics = [{ topic: "mockTopic1" }, { topic: "mockTopic2" }]; @@ -34,16 +32,12 @@ describe("CreateTopics", () => { }); it("should create a log group for the Lambda function", () => { - const lambdaLogGroup = createTopics.node.findChild( - "CreateTopicsLogGroup", - ) as logs.LogGroup; + const lambdaLogGroup = createTopics.node.findChild("CreateTopicsLogGroup") as logs.LogGroup; expect(lambdaLogGroup).toBeInstanceOf(logs.LogGroup); }); it("should create a Lambda function with appropriate properties", () => { - const lambdaFunction = createTopics.node.findChild( - "CreateTopicsLambda", - ) as lambda.Function; + const lambdaFunction = createTopics.node.findChild("CreateTopicsLambda") as lambda.Function; expect(lambdaFunction).toBeInstanceOf(lambda.Function); expect(lambdaFunction.runtime).toBe(lambda.Runtime.NODEJS_18_X); expect(lambdaFunction.timeout?.toMinutes()).toBe(5); @@ -64,9 +58,7 @@ describe("CreateTopics", () => { }); it("should create a custom resource to invoke the Lambda function", () => { - const customResource = createTopics.node.findChild( - "CustomResource", - ) as cr.AwsCustomResource; + const customResource = createTopics.node.findChild("CustomResource") as cr.AwsCustomResource; expect(customResource).toBeInstanceOf(cr.AwsCustomResource); const customResourceLogGroup = createTopics.node.findChild( diff --git a/lib/local-constructs/create-topics/index.ts b/lib/local-constructs/create-topics/index.ts index e56ac8b273..9b7da9d576 100644 --- a/lib/local-constructs/create-topics/index.ts +++ b/lib/local-constructs/create-topics/index.ts @@ -47,12 +47,8 @@ export class CreateTopics extends Construct { role: new Role(this, "CreateTopicsLambdaExecutionRole", { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaBasicExecutionRole", - ), - ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaVPCAccessExecutionRole", - ), + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaVPCAccessExecutionRole"), ], inlinePolicies: { InvokeLambdaPolicy: new PolicyDocument({ @@ -75,13 +71,9 @@ export class CreateTopics extends Construct { bundling: commonBundlingOptions, }); - const customResourceLogGroup = new LogGroup( - this, - `createTopicsCustomResourceLogGroup`, - { - removalPolicy: RemovalPolicy.DESTROY, - }, - ); + const customResourceLogGroup = new LogGroup(this, `createTopicsCustomResourceLogGroup`, { + removalPolicy: RemovalPolicy.DESTROY, + }); const customResource = new AwsCustomResource(this, "CustomResource", { onCreate: { diff --git a/lib/local-constructs/create-topics/src/createTopics.test.ts b/lib/local-constructs/create-topics/src/createTopics.test.ts index afbd65f816..adc29dcdf7 100644 --- a/lib/local-constructs/create-topics/src/createTopics.test.ts +++ b/lib/local-constructs/create-topics/src/createTopics.test.ts @@ -11,9 +11,7 @@ describe("handler", () => { { topic: "validTopic", numPartitions: 3, replicationFactor: 3 }, { topic: "anotherValidTopic", numPartitions: 1, replicationFactor: 3 }, ]; - const invalidTopicsToCreateNoName = [ - { topic: "", numPartitions: 3, replicationFactor: 3 }, - ]; + const invalidTopicsToCreateNoName = [{ topic: "", numPartitions: 3, replicationFactor: 3 }]; const invalidTopicsToCreateLowReplication = [ { topic: "validTopic", numPartitions: 3, replicationFactor: 2 }, ]; @@ -41,10 +39,7 @@ describe("handler", () => { await handler(event, context); - expect(topics.createTopics).toHaveBeenCalledWith( - brokerString, - validTopicsToCreate, - ); + expect(topics.createTopics).toHaveBeenCalledWith(brokerString, validTopicsToCreate); expect(topics.createTopics).toHaveBeenCalledTimes(1); }); diff --git a/lib/local-constructs/create-topics/src/createTopics.ts b/lib/local-constructs/create-topics/src/createTopics.ts index 967d2c88b9..19fa0fbb6f 100644 --- a/lib/local-constructs/create-topics/src/createTopics.ts +++ b/lib/local-constructs/create-topics/src/createTopics.ts @@ -7,16 +7,12 @@ interface TopicConfig { replicationFactor: number; } -export const handler = async function ( - event: CloudFormationCustomResourceEvent, -) { +export const handler = async function (event: CloudFormationCustomResourceEvent) { console.log("Request:", JSON.stringify(event, undefined, 2)); const resourceProperties = event.ResourceProperties; const topicsToCreate: TopicConfig[] = resourceProperties.topicsToCreate; const brokerString: string = resourceProperties.brokerString; - const topicConfig: TopicConfig[] = topicsToCreate.map(function ( - element: TopicConfig, - ) { + const topicConfig: TopicConfig[] = topicsToCreate.map(function (element: TopicConfig) { const topic: string = element.topic; const replicationFactor: number = element.replicationFactor || 3; const numPartitions: number = element.numPartitions ?? 1; diff --git a/lib/local-constructs/empty-buckets/index.test.ts b/lib/local-constructs/empty-buckets/index.test.ts index d4940cc4fa..ee7a53e76d 100644 --- a/lib/local-constructs/empty-buckets/index.test.ts +++ b/lib/local-constructs/empty-buckets/index.test.ts @@ -21,16 +21,12 @@ describe("EmptyBuckets", () => { }); it("should create a log group for the Lambda function", () => { - const lambdaLogGroup = emptyBuckets.node.findChild( - "LambdaLogGroup", - ) as logs.LogGroup; + const lambdaLogGroup = emptyBuckets.node.findChild("LambdaLogGroup") as logs.LogGroup; expect(lambdaLogGroup).toBeInstanceOf(logs.LogGroup); }); it("should create a Lambda function with appropriate properties", () => { - const lambdaFunction = emptyBuckets.node.findChild( - "Lambda", - ) as lambda.Function; + const lambdaFunction = emptyBuckets.node.findChild("Lambda") as lambda.Function; expect(lambdaFunction).toBeInstanceOf(lambda.Function); expect(lambdaFunction.runtime).toBe(lambda.Runtime.NODEJS_18_X); expect(lambdaFunction.timeout?.toMinutes()).toBe(15); @@ -69,9 +65,7 @@ describe("EmptyBuckets", () => { }); it("should create a custom resource to invoke the Lambda function", () => { - const customResource = emptyBuckets.node.findChild( - "CustomResource", - ) as cr.AwsCustomResource; + const customResource = emptyBuckets.node.findChild("CustomResource") as cr.AwsCustomResource; expect(customResource).toBeInstanceOf(cr.AwsCustomResource); const customResourceLogGroup = emptyBuckets.node.findChild( diff --git a/lib/local-constructs/empty-buckets/index.ts b/lib/local-constructs/empty-buckets/index.ts index ad14f41a62..4082f29f32 100644 --- a/lib/local-constructs/empty-buckets/index.ts +++ b/lib/local-constructs/empty-buckets/index.ts @@ -66,9 +66,7 @@ export class EmptyBuckets extends Construct { const lambdaRole = new Role(this, "LambdaRole", { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaBasicExecutionRole", - ), + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), ], inlinePolicies: { LambdaPolicy: new PolicyDocument({ diff --git a/lib/local-constructs/manage-users/index.test.ts b/lib/local-constructs/manage-users/index.test.ts index b6c8b9779a..c95500255c 100644 --- a/lib/local-constructs/manage-users/index.test.ts +++ b/lib/local-constructs/manage-users/index.test.ts @@ -16,25 +16,15 @@ describe("ManageUsers", () => { const users = [{ username: "user1" }, { username: "user2" }]; const passwordSecretArn = "mockPasswordSecretArn"; // pragma: allowlist secret - const manageUsers = new ManageUsers( - stack, - "ManageUsers", - userPool, - users, - passwordSecretArn, - ); + const manageUsers = new ManageUsers(stack, "ManageUsers", userPool, users, passwordSecretArn); it("should create a log group for the Lambda function", () => { - const lambdaLogGroup = manageUsers.node.findChild( - "LambdaLogGroup", - ) as logs.LogGroup; + const lambdaLogGroup = manageUsers.node.findChild("LambdaLogGroup") as logs.LogGroup; expect(lambdaLogGroup).toBeInstanceOf(logs.LogGroup); }); it("should create a Lambda function with appropriate properties", () => { - const lambdaFunction = manageUsers.node.findChild( - "LambdaFunction", - ) as lambda.Function; + const lambdaFunction = manageUsers.node.findChild("LambdaFunction") as lambda.Function; expect(lambdaFunction).toBeInstanceOf(lambda.Function); expect(lambdaFunction.runtime).toBe(lambda.Runtime.NODEJS_18_X); expect(lambdaFunction.timeout?.toMinutes()).toBe(5); diff --git a/lib/local-constructs/manage-users/index.ts b/lib/local-constructs/manage-users/index.ts index ffe997b27e..043cc98f93 100644 --- a/lib/local-constructs/manage-users/index.ts +++ b/lib/local-constructs/manage-users/index.ts @@ -44,9 +44,7 @@ export class ManageUsers extends Construct { role: new Role(this, "LambdaExecutionRole", { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaBasicExecutionRole", - ), + ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), ], inlinePolicies: { LambdaAssumeRolePolicy: new PolicyDocument({ @@ -68,10 +66,7 @@ export class ManageUsers extends Construct { }), new PolicyStatement({ effect: Effect.ALLOW, - actions: [ - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - ], + actions: ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"], resources: ["*"], }), ], @@ -81,64 +76,56 @@ export class ManageUsers extends Construct { bundling: commonBundlingOptions, }); - const customResourceLogGroup = new LogGroup( - this, - `CustomResourceLogGroup`, - { - removalPolicy: RemovalPolicy.DESTROY, - }, - ); + const customResourceLogGroup = new LogGroup(this, `CustomResourceLogGroup`, { + removalPolicy: RemovalPolicy.DESTROY, + }); - const customResource = new AwsCustomResource( - this, - "CleanupKafkaCustomResource", - { - onCreate: { - service: "Lambda", - action: "invoke", - parameters: { - FunctionName: manageUsers.functionName, - Payload: JSON.stringify({ - RequestType: "Create", - ResourceProperties: { - userPoolId: userPool.userPoolId, - users, - passwordSecretArn: passwordSecretArn, - }, - }), - }, - physicalResourceId: PhysicalResourceId.of("manage-users"), - }, - onUpdate: { - service: "Lambda", - action: "invoke", - parameters: { - FunctionName: manageUsers.functionName, - Payload: JSON.stringify({ - RequestType: "Update", - ResourceProperties: { - userPoolId: userPool.userPoolId, - users, - passwordSecretArn: passwordSecretArn, - }, - }), - }, - physicalResourceId: PhysicalResourceId.of("manage-users"), - }, - logGroup: customResourceLogGroup, - policy: AwsCustomResourcePolicy.fromStatements([ - new PolicyStatement({ - actions: ["lambda:InvokeFunction"], - resources: [manageUsers.functionArn], + const customResource = new AwsCustomResource(this, "CleanupKafkaCustomResource", { + onCreate: { + service: "Lambda", + action: "invoke", + parameters: { + FunctionName: manageUsers.functionName, + Payload: JSON.stringify({ + RequestType: "Create", + ResourceProperties: { + userPoolId: userPool.userPoolId, + users, + passwordSecretArn: passwordSecretArn, + }, }), - new PolicyStatement({ - effect: Effect.DENY, - actions: ["logs:CreateLogGroup"], - resources: ["*"], + }, + physicalResourceId: PhysicalResourceId.of("manage-users"), + }, + onUpdate: { + service: "Lambda", + action: "invoke", + parameters: { + FunctionName: manageUsers.functionName, + Payload: JSON.stringify({ + RequestType: "Update", + ResourceProperties: { + userPoolId: userPool.userPoolId, + users, + passwordSecretArn: passwordSecretArn, + }, }), - ]), + }, + physicalResourceId: PhysicalResourceId.of("manage-users"), }, - ); + logGroup: customResourceLogGroup, + policy: AwsCustomResourcePolicy.fromStatements([ + new PolicyStatement({ + actions: ["lambda:InvokeFunction"], + resources: [manageUsers.functionArn], + }), + new PolicyStatement({ + effect: Effect.DENY, + actions: ["logs:CreateLogGroup"], + resources: ["*"], + }), + ]), + }); const policy = customResource.node.findChild("CustomResourcePolicy"); customResource.node.addDependency(policy); customResourceLogGroup.node.addDependency(policy); diff --git a/lib/local-constructs/waf/index.test.ts b/lib/local-constructs/waf/index.test.ts index b1da08b32e..7ab2767d2c 100644 --- a/lib/local-constructs/waf/index.test.ts +++ b/lib/local-constructs/waf/index.test.ts @@ -17,12 +17,7 @@ describe("WafConstruct", () => { awsBadInputsExcludeRules: ["BadInputsRule1"], }; - const wafConstruct = new WafConstruct( - stack, - "WafConstruct", - props, - "REGIONAL", - ); + const wafConstruct = new WafConstruct(stack, "WafConstruct", props, "REGIONAL"); it("should create a log group with appropriate properties", () => { const logGroup = wafConstruct.logGroup; @@ -46,9 +41,7 @@ describe("WafConstruct", () => { .node.findChild("LoggingConfiguration") as wafv2.CfnLoggingConfiguration; expect(loggingConfiguration).toBeInstanceOf(wafv2.CfnLoggingConfiguration); expect(loggingConfiguration.resourceArn).toBe(wafConstruct.webAcl.attrArn); - expect(loggingConfiguration.logDestinationConfigs).toContain( - wafConstruct.logGroup.logGroupArn, - ); + expect(loggingConfiguration.logDestinationConfigs).toContain(wafConstruct.logGroup.logGroupArn); }); }); @@ -103,9 +96,7 @@ describe("RegionalWaf", () => { .findChild("RegionalWaf") .node.findChild("WebACLAssociation") as wafv2.CfnWebACLAssociation; expect(webAclAssociation).toBeInstanceOf(wafv2.CfnWebACLAssociation); - expect(webAclAssociation.resourceArn).toBe( - apiGateway.deploymentStage.stageArn, - ); + expect(webAclAssociation.resourceArn).toBe(apiGateway.deploymentStage.stageArn); expect(webAclAssociation.webAclArn).toBe(regionalWaf.webAcl.attrArn); }); }); diff --git a/lib/local-constructs/waf/index.ts b/lib/local-constructs/waf/index.ts index 1d4d905a62..2008c60691 100644 --- a/lib/local-constructs/waf/index.ts +++ b/lib/local-constructs/waf/index.ts @@ -1,10 +1,6 @@ import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; -import { - CfnWebACL, - CfnLoggingConfiguration, - CfnWebACLAssociation, -} from "aws-cdk-lib/aws-wafv2"; +import { CfnWebACL, CfnLoggingConfiguration, CfnWebACLAssociation } from "aws-cdk-lib/aws-wafv2"; import { LogGroup } from "aws-cdk-lib/aws-logs"; import { RestApi } from "aws-cdk-lib/aws-apigateway"; @@ -20,12 +16,7 @@ export class WafConstruct extends Construct { public readonly webAcl: CfnWebACL; public readonly logGroup: LogGroup; - constructor( - scope: Construct, - id: string, - props: WafProps, - scopeType: string, - ) { + constructor(scope: Construct, id: string, props: WafProps, scopeType: string) { super(scope, id); const { @@ -150,18 +141,7 @@ export class WafConstruct extends Construct { action: { allow: {} }, statement: { geoMatchStatement: { - countryCodes: [ - "AS", - "FM", - "GU", - "MH", - "MP", - "PR", - "PW", - "UM", - "US", - "VI", - ], + countryCodes: ["AS", "FM", "GU", "MH", "MP", "PR", "PW", "UM", "US", "VI"], }, }, visibilityConfig: { diff --git a/lib/packages/shared-types/attachments.ts b/lib/packages/shared-types/attachments.ts index df13de4038..6c3c375417 100644 --- a/lib/packages/shared-types/attachments.ts +++ b/lib/packages/shared-types/attachments.ts @@ -28,9 +28,12 @@ export const attachmentTitleMap = { supportingDocumentation: "Supporting Documentation", bCapWaiverApplication: "1915(b) Comprehensive (Capitated) Waiver Application Pre-print", bCapCostSpreadsheets: "1915(b) Comprehensive (Capitated) Waiver Cost Effectiveness Spreadsheets", - bCapIndependentAssessment: "1915(b) Comprehensive (Capitated) Waiver Independent Assessment (first two renewals only)", - b4WaiverApplication: "1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print", - b4IndependentAssessment: "1915(b)(4) FFS Selective Contracting (Streamlined) Independent Assessment (first two renewals only)", + bCapIndependentAssessment: + "1915(b) Comprehensive (Capitated) Waiver Independent Assessment (first two renewals only)", + b4WaiverApplication: + "1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print", + b4IndependentAssessment: + "1915(b)(4) FFS Selective Contracting (Streamlined) Independent Assessment (first two renewals only)", appk: "1915(c) Appendix K Amendment Waiver Template", waiverExtensionRequest: "Waiver Extension Request", }; diff --git a/lib/packages/shared-types/authority.ts b/lib/packages/shared-types/authority.ts index 4c5aff79e6..31d749ff8f 100644 --- a/lib/packages/shared-types/authority.ts +++ b/lib/packages/shared-types/authority.ts @@ -6,8 +6,4 @@ export enum Authority { "1915c" = "1915(c)", } -export type AuthorityUnion = - | "Medicaid SPA" - | "CHIP SPA" - | "1915(b)" - | "1915(c)"; +export type AuthorityUnion = "Medicaid SPA" | "CHIP SPA" | "1915(b)" | "1915(c)"; diff --git a/lib/packages/shared-types/events/capitated-amendment.ts b/lib/packages/shared-types/events/capitated-amendment.ts index cf46921d19..2d91bf30d2 100644 --- a/lib/packages/shared-types/events/capitated-amendment.ts +++ b/lib/packages/shared-types/events/capitated-amendment.ts @@ -32,12 +32,7 @@ export const baseSchema = z.object({ files: attachmentArraySchemaOptional(), }), }), - additionalInformation: z - .string() - .max(4000) - .nullable() - .default(null) - .optional(), + additionalInformation: z.string().max(4000).nullable().default(null).optional(), waiverNumber: z .string() .min(1, { message: "Required" }) diff --git a/lib/packages/shared-types/events/contracting-amendment.ts b/lib/packages/shared-types/events/contracting-amendment.ts index 402d38bb07..ef42b89a5a 100644 --- a/lib/packages/shared-types/events/contracting-amendment.ts +++ b/lib/packages/shared-types/events/contracting-amendment.ts @@ -1,8 +1,5 @@ import { z } from "zod"; -import { - attachmentArraySchema, - attachmentArraySchemaOptional, -} from "../attachments"; +import { attachmentArraySchema, attachmentArraySchemaOptional } from "../attachments"; export const baseSchema = z.object({ event: z.literal("contracting-amendment").default("contracting-amendment"), @@ -19,9 +16,7 @@ export const baseSchema = z.object({ b4WaiverApplication: z.object({ label: z .string() - .default( - "1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print", - ), + .default("1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print"), files: attachmentArraySchema(), }), tribalConsultation: z.object({ @@ -33,12 +28,7 @@ export const baseSchema = z.object({ files: attachmentArraySchemaOptional(), }), }), - additionalInformation: z - .string() - .max(4000) - .nullable() - .default(null) - .optional(), + additionalInformation: z.string().max(4000).nullable().default(null).optional(), waiverNumber: z .string() .min(1, { message: "Required" }) diff --git a/lib/packages/shared-types/events/new-chip-submission.ts b/lib/packages/shared-types/events/new-chip-submission.ts index 0b5fd004df..942ea0e7b2 100644 --- a/lib/packages/shared-types/events/new-chip-submission.ts +++ b/lib/packages/shared-types/events/new-chip-submission.ts @@ -1,18 +1,10 @@ import { z } from "zod"; -import { - attachmentArraySchema, - attachmentArraySchemaOptional, -} from "../attachments"; +import { attachmentArraySchema, attachmentArraySchemaOptional } from "../attachments"; export const baseSchema = z.object({ event: z.literal("new-chip-submission").default("new-chip-submission"), - additionalInformation: z - .string() - .max(4000) - .nullable() - .default(null) - .optional(), + additionalInformation: z.string().max(4000).nullable().default(null).optional(), attachments: z.object({ currentStatePlan: z.object({ files: attachmentArraySchema(), diff --git a/lib/packages/shared-types/events/new-medicaid-submission.ts b/lib/packages/shared-types/events/new-medicaid-submission.ts index 5cac80daad..c27e5417ee 100644 --- a/lib/packages/shared-types/events/new-medicaid-submission.ts +++ b/lib/packages/shared-types/events/new-medicaid-submission.ts @@ -1,19 +1,9 @@ import { z } from "zod"; -import { - attachmentArraySchema, - attachmentArraySchemaOptional, -} from "../attachments"; +import { attachmentArraySchema, attachmentArraySchemaOptional } from "../attachments"; export const baseSchema = z.object({ - event: z - .literal("new-medicaid-submission") - .default("new-medicaid-submission"), - additionalInformation: z - .string() - .max(4000) - .nullable() - .default(null) - .optional(), + event: z.literal("new-medicaid-submission").default("new-medicaid-submission"), + additionalInformation: z.string().max(4000).nullable().default(null).optional(), attachments: z.object({ cmsForm179: z.object({ files: attachmentArraySchema({ @@ -32,9 +22,7 @@ export const baseSchema = z.object({ }), tribalEngagement: z.object({ files: attachmentArraySchemaOptional(), - label: z - .string() - .default("Document Demonstrating Good-Faith Tribal Engagement"), + label: z.string().default("Document Demonstrating Good-Faith Tribal Engagement"), }), existingStatePlanPages: z.object({ files: attachmentArraySchemaOptional(), diff --git a/lib/packages/shared-types/events/temporary-extension.ts b/lib/packages/shared-types/events/temporary-extension.ts index 13b3473174..1d5f7eb858 100644 --- a/lib/packages/shared-types/events/temporary-extension.ts +++ b/lib/packages/shared-types/events/temporary-extension.ts @@ -1,8 +1,5 @@ import { z } from "zod"; -import { - attachmentArraySchema, - attachmentArraySchemaOptional, -} from "../attachments"; +import { attachmentArraySchema, attachmentArraySchemaOptional } from "../attachments"; export const baseSchema = z.object({ event: z.literal("temporary-extension").default("temporary-extension"), @@ -21,12 +18,7 @@ export const baseSchema = z.object({ "The Approved Initial or Renewal Waiver Number must be in the format of SS-####.R##.00 or SS-#####.R##.00.", }), authority: z.string(), // z.enum? - additionalInformation: z - .string() - .max(4000) - .nullable() - .default(null) - .optional(), + additionalInformation: z.string().max(4000).nullable().default(null).optional(), attachments: z.object({ waiverExtensionRequest: z.object({ label: z.string().default("Waiver Extension Request"), diff --git a/lib/packages/shared-types/events/toggle-withdraw-rai.ts b/lib/packages/shared-types/events/toggle-withdraw-rai.ts index 7e3c81ca47..c8e4ddd301 100644 --- a/lib/packages/shared-types/events/toggle-withdraw-rai.ts +++ b/lib/packages/shared-types/events/toggle-withdraw-rai.ts @@ -1,6 +1,5 @@ import { z } from "zod"; - export const baseSchema = z.object({ event: z.literal("toggle-withdraw-rai").default("toggle-withdraw-rai"), id: z.string(), diff --git a/lib/packages/shared-types/forms.ts b/lib/packages/shared-types/forms.ts index c0e2e48332..80b6e3a7e7 100644 --- a/lib/packages/shared-types/forms.ts +++ b/lib/packages/shared-types/forms.ts @@ -1,9 +1,4 @@ -import { - Control, - FieldArrayPath, - FieldValues, - RegisterOptions, -} from "react-hook-form"; +import { Control, FieldArrayPath, FieldValues, RegisterOptions } from "react-hook-form"; import { CalendarProps, InputProps, diff --git a/lib/packages/shared-types/inputs.ts b/lib/packages/shared-types/inputs.ts index 0cceeb4c46..0778bdf5ab 100644 --- a/lib/packages/shared-types/inputs.ts +++ b/lib/packages/shared-types/inputs.ts @@ -16,21 +16,16 @@ export type DatePickerProps = { dataTestId?: string; }; -export interface InputProps - extends React.InputHTMLAttributes { +export interface InputProps extends React.InputHTMLAttributes { icon?: string; iconRight?: boolean; } -export type RadioProps = React.ComponentPropsWithoutRef< - typeof RadioGroupPrimitive.Root -> & { +export type RadioProps = React.ComponentPropsWithoutRef & { className?: string; }; -export type SelectProps = React.ComponentPropsWithoutRef< - typeof SelectPrimitive.Root -> & { +export type SelectProps = React.ComponentPropsWithoutRef & { options: { label: string; value: any }[]; className?: string; apiCall?: string; @@ -51,14 +46,11 @@ export type MultiselectProps = { onChange?: (selectedValues: string[]) => void; }; -export type SwitchProps = React.ComponentPropsWithoutRef< - typeof SwitchPrimitives.Root -> & { +export type SwitchProps = React.ComponentPropsWithoutRef & { className?: string; }; -export interface TextareaProps - extends React.TextareaHTMLAttributes { +export interface TextareaProps extends React.TextareaHTMLAttributes { charCount?: "simple" | "limited"; charCountClassName?: string; } diff --git a/lib/packages/shared-types/opensearch/cpocs/index.ts b/lib/packages/shared-types/opensearch/cpocs/index.ts index 2d5f7bc856..a6d1585f16 100644 --- a/lib/packages/shared-types/opensearch/cpocs/index.ts +++ b/lib/packages/shared-types/opensearch/cpocs/index.ts @@ -1,10 +1,4 @@ -import { - Response as Res, - Hit, - Filterable as FIL, - QueryState, - AggQuery, -} from "./../_"; +import { Response as Res, Hit, Filterable as FIL, QueryState, AggQuery } from "./../_"; import { z } from "zod"; import { Officers } from "./transforms"; diff --git a/lib/packages/shared-types/opensearch/subtypes/index.ts b/lib/packages/shared-types/opensearch/subtypes/index.ts index 9457fb9668..a9acfbbb67 100644 --- a/lib/packages/shared-types/opensearch/subtypes/index.ts +++ b/lib/packages/shared-types/opensearch/subtypes/index.ts @@ -1,10 +1,4 @@ -import { - Response as Res, - Hit, - Filterable as FIL, - QueryState, - AggQuery, -} from "./../_"; +import { Response as Res, Hit, Filterable as FIL, QueryState, AggQuery } from "./../_"; import { z } from "zod"; import { Type } from "./transforms"; diff --git a/lib/packages/shared-types/opensearch/types/index.ts b/lib/packages/shared-types/opensearch/types/index.ts index 75ed5dbd00..9727514e42 100644 --- a/lib/packages/shared-types/opensearch/types/index.ts +++ b/lib/packages/shared-types/opensearch/types/index.ts @@ -1,10 +1,4 @@ -import { - Response as Res, - Hit, - Filterable as FIL, - QueryState, - AggQuery, -} from "./../_"; +import { Response as Res, Hit, Filterable as FIL, QueryState, AggQuery } from "./../_"; import { z } from "zod"; import { SPA_Type } from "./transforms"; diff --git a/lib/packages/shared-types/seatool-tables/index.ts b/lib/packages/shared-types/seatool-tables/index.ts index 142785c0de..6a07f7fa7e 100644 --- a/lib/packages/shared-types/seatool-tables/index.ts +++ b/lib/packages/shared-types/seatool-tables/index.ts @@ -1,4 +1,4 @@ export * from "./Type"; export * from "./SPA_Type"; export * from "./State_Plan"; -export * from "./Officers"; \ No newline at end of file +export * from "./Officers"; diff --git a/lib/packages/shared-utils/cloudformation.ts b/lib/packages/shared-utils/cloudformation.ts index dccd63ce6b..236553a83b 100644 --- a/lib/packages/shared-utils/cloudformation.ts +++ b/lib/packages/shared-utils/cloudformation.ts @@ -1,12 +1,6 @@ -import { - CloudFormationClient, - ListExportsCommand, -} from "@aws-sdk/client-cloudformation"; +import { CloudFormationClient, ListExportsCommand } from "@aws-sdk/client-cloudformation"; -export async function getExport( - exportName: string, - region: string = "us-east-1", -): Promise { +export async function getExport(exportName: string, region: string = "us-east-1"): Promise { const client = new CloudFormationClient({ region }); const command = new ListExportsCommand({}); diff --git a/lib/packages/shared-utils/regex.ts b/lib/packages/shared-utils/regex.ts index 6b07a7e3aa..a8e04751d5 100644 --- a/lib/packages/shared-utils/regex.ts +++ b/lib/packages/shared-utils/regex.ts @@ -34,10 +34,7 @@ export const reInsertRegex = (obj: any) => { obj[key].pattern.hasOwnProperty("value") ) { // if its a pattern.value replace the value's value with a regex from the weird array thing - obj[key].pattern.value = new RegExp( - obj[key].pattern.value[1], - obj[key].pattern.value[2], - ); + obj[key].pattern.value = new RegExp(obj[key].pattern.value[1], obj[key].pattern.value[2]); } } } diff --git a/lib/packages/shared-utils/seatool-date-helper.test.ts b/lib/packages/shared-utils/seatool-date-helper.test.ts index 4051e7db59..502475fe20 100644 --- a/lib/packages/shared-utils/seatool-date-helper.test.ts +++ b/lib/packages/shared-utils/seatool-date-helper.test.ts @@ -12,12 +12,7 @@ describe("offsetToUtc", () => { const originalDate = new Date("January 1, 2000 12:00:00"); const timezoneOffset = originalDate.getTimezoneOffset() * 60000; // in milliseconds const expectedDate = new Date(originalDate.getTime() - timezoneOffset); - console.debug( - "originalDate: ", - originalDate, - "expectedDate: ", - expectedDate, - ); + console.debug("originalDate: ", originalDate, "expectedDate: ", expectedDate); expect(offsetToUtc(originalDate)).toEqual(expectedDate); }); }); @@ -27,12 +22,7 @@ describe("offsetFromUtc", () => { const originalDate = new Date("2000-01-01T12:00:00.000Z"); const timezoneOffset = originalDate.getTimezoneOffset() * 60000; // in milliseconds const expectedDate = new Date(originalDate.getTime() + timezoneOffset); - console.debug( - "originalDate: ", - originalDate, - "expectedDate: ", - expectedDate, - ); + console.debug("originalDate: ", originalDate, "expectedDate: ", expectedDate); expect(offsetFromUtc(originalDate)).toEqual(expectedDate); }); }); @@ -42,9 +32,7 @@ describe("seaToolFriendlyTimestamp", () => { const originalDate = new Date("January 1, 2000 12:00:00"); const timezoneOffset = originalDate.getTimezoneOffset() * 60000; // in milliseconds const expectedDate = new Date(originalDate.getTime() - timezoneOffset); - expect(seaToolFriendlyTimestamp(originalDate)).toEqual( - expectedDate.getTime(), - ); + expect(seaToolFriendlyTimestamp(originalDate)).toEqual(expectedDate.getTime()); }); }); diff --git a/lib/packages/shared-utils/seatool-date-helper.ts b/lib/packages/shared-utils/seatool-date-helper.ts index 03423c914e..7bab4356e6 100644 --- a/lib/packages/shared-utils/seatool-date-helper.ts +++ b/lib/packages/shared-utils/seatool-date-helper.ts @@ -26,9 +26,7 @@ export const formatSeatoolDate = (date: string): string => { return moment(date).tz("UTC").format("MM/DD/yyyy"); }; -export const getNextBusinessDayTimestamp = ( - date: Date = new Date(), -): number => { +export const getNextBusinessDayTimestamp = (date: Date = new Date()): number => { const localeStringDate = date.toLocaleString("en-US", { timeZone: "America/New_York", dateStyle: "short", diff --git a/lib/packages/shared-utils/secrets-manager.ts b/lib/packages/shared-utils/secrets-manager.ts index 9dad32b3ae..762e53f441 100644 --- a/lib/packages/shared-utils/secrets-manager.ts +++ b/lib/packages/shared-utils/secrets-manager.ts @@ -4,10 +4,7 @@ import { DescribeSecretCommand, } from "@aws-sdk/client-secrets-manager"; -export async function getSecret( - secretId: string, - region: string = "us-east-1", -): Promise { +export async function getSecret(secretId: string, region: string = "us-east-1"): Promise { const client = new SecretsManagerClient({ region }); try { // Check if the secret is marked for deletion @@ -15,9 +12,7 @@ export async function getSecret( const secretMetadata = await client.send(describeCommand); if (secretMetadata.DeletedDate) { - throw new Error( - `Secret ${secretId} is marked for deletion and will not be used.`, - ); + throw new Error(`Secret ${secretId} is marked for deletion and will not be used.`); } const command = new GetSecretValueCommand({ SecretId: secretId }); diff --git a/lib/stacks/alerts.ts b/lib/stacks/alerts.ts index a520e7eeb6..b51e36ba59 100644 --- a/lib/stacks/alerts.ts +++ b/lib/stacks/alerts.ts @@ -22,11 +22,7 @@ export class Alerts extends cdk.NestedStack { // Create Alerts Topic with AWS-managed KMS Key const alertsTopic = new cdk.aws_sns.Topic(this, "AlertsTopic", { topicName: `Alerts-${project}-${stage}`, - masterKey: cdk.aws_kms.Alias.fromAliasName( - this, - "KmsAlias", - "alias/aws/sns", - ), + masterKey: cdk.aws_kms.Alias.fromAliasName(this, "KmsAlias", "alias/aws/sns"), }); // Output the Alerts Topic ARN diff --git a/lib/stacks/api.ts b/lib/stacks/api.ts index f8cee59b1b..bd1a91cb72 100644 --- a/lib/stacks/api.ts +++ b/lib/stacks/api.ts @@ -4,11 +4,7 @@ import { Construct } from "constructs"; import { join } from "path"; import { DeploymentConfigProperties } from "../config/deployment-config"; import * as LC from "local-constructs"; -import { - BlockPublicAccess, - Bucket, - BucketEncryption, -} from "aws-cdk-lib/aws-s3"; +import { BlockPublicAccess, Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3"; import { AnyPrincipal, Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; import { commonBundlingOptions } from "../config/bundling-config"; @@ -73,9 +69,7 @@ export class Api extends cdk.NestedStack { cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( "service-role/AWSLambdaVPCAccessExecutionRole", ), - cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( - "CloudWatchLogsFullAccess", - ), + cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess"), ], inlinePolicies: { LambdaPolicy: new cdk.aws_iam.PolicyDocument({ @@ -114,10 +108,7 @@ export class Api extends cdk.NestedStack { }), new cdk.aws_iam.PolicyStatement({ effect: cdk.aws_iam.Effect.ALLOW, - actions: [ - "secretsmanager:DescribeSecret", - "secretsmanager:GetSecretValue", - ], + actions: ["secretsmanager:DescribeSecret", "secretsmanager:GetSecretValue"], resources: [ `arn:aws:secretsmanager:${this.region}:${this.account}:secret:${dbInfoSecretName}-*`, ], @@ -293,18 +284,21 @@ export class Api extends cdk.NestedStack { }, ]; - const lambdas = lambdaDefinitions.reduce((acc, lambdaDef) => { - acc[lambdaDef.id] = createNodeJsLambda( - lambdaDef.id, - lambdaDef.entry, - lambdaDef.environment, - vpc, - lambdaSecurityGroup, - privateSubnets, - !props.isDev ? lambdaDef.provisionedConcurrency : 0, - ); - return acc; - }, {} as { [key: string]: NodejsFunction }); + const lambdas = lambdaDefinitions.reduce( + (acc, lambdaDef) => { + acc[lambdaDef.id] = createNodeJsLambda( + lambdaDef.id, + lambdaDef.entry, + lambdaDef.environment, + vpc, + lambdaSecurityGroup, + privateSubnets, + !props.isDev ? lambdaDef.provisionedConcurrency : 0, + ); + return acc; + }, + {} as { [key: string]: NodejsFunction }, + ); // Create IAM role for API Gateway to invoke Lambda functions const apiGatewayRole = new cdk.aws_iam.Role(this, "ApiGatewayRole", { @@ -455,13 +449,10 @@ export class Api extends cdk.NestedStack { const resource = api.root.resourceForPath(path); // Define the integration for the Lambda function - const integration = new cdk.aws_apigateway.LambdaIntegration( - lambdaFunction, - { - proxy: true, - credentialsRole: apiGatewayRole, - }, - ); + const integration = new cdk.aws_apigateway.LambdaIntegration(lambdaFunction, { + proxy: true, + credentialsRole: apiGatewayRole, + }); // Add method for specified HTTP method resource.addMethod(method, integration, { @@ -485,10 +476,7 @@ export class Api extends cdk.NestedStack { }); // Define CloudWatch Alarms - const createCloudWatchAlarm = ( - id: string, - lambdaFunction: cdk.aws_lambda.Function, - ) => { + const createCloudWatchAlarm = (id: string, lambdaFunction: cdk.aws_lambda.Function) => { const alarm = new cdk.aws_cloudwatch.Alarm(this, id, { alarmName: `${project}-${stage}-${id}Alarm`, metric: new cdk.aws_cloudwatch.Metric({ @@ -503,8 +491,7 @@ export class Api extends cdk.NestedStack { threshold: 1, evaluationPeriods: 1, comparisonOperator: - cdk.aws_cloudwatch.ComparisonOperator - .GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + cdk.aws_cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, }); diff --git a/lib/stacks/data.ts b/lib/stacks/data.ts index fd8f03307b..d82c408a2b 100644 --- a/lib/stacks/data.ts +++ b/lib/stacks/data.ts @@ -469,19 +469,22 @@ export class Data extends cdk.NestedStack { sinkTypes: { provisionedConcurrency: 0 }, }; - const lambdaFunctions = Object.entries(functionConfigs).reduce((acc, [name, config]) => { - acc[name] = createLambda({ - id: name, - role: sharedLambdaRole, - useVpc: true, - environment: { - osDomain: `https://${openSearchDomainEndpoint}`, - indexNamespace, - }, - provisionedConcurrency: !props.isDev ? config.provisionedConcurrency : 0, - }); - return acc; - }, {} as { [key: string]: NodejsFunction }); + const lambdaFunctions = Object.entries(functionConfigs).reduce( + (acc, [name, config]) => { + acc[name] = createLambda({ + id: name, + role: sharedLambdaRole, + useVpc: true, + environment: { + osDomain: `https://${openSearchDomainEndpoint}`, + indexNamespace, + }, + provisionedConcurrency: !props.isDev ? config.provisionedConcurrency : 0, + }); + return acc; + }, + {} as { [key: string]: NodejsFunction }, + ); const stateMachineRole = new cdk.aws_iam.Role(this, "StateMachineRole", { assumedBy: new cdk.aws_iam.ServicePrincipal("states.amazonaws.com"), diff --git a/lib/stacks/ui-infra.ts b/lib/stacks/ui-infra.ts index 4e784b880a..b5fb8c073d 100644 --- a/lib/stacks/ui-infra.ts +++ b/lib/stacks/ui-infra.ts @@ -1,10 +1,6 @@ import * as cdk from "aws-cdk-lib"; import { AnyPrincipal, Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; -import { - BlockPublicAccess, - Bucket, - BucketEncryption, -} from "aws-cdk-lib/aws-s3"; +import { BlockPublicAccess, Bucket, BucketEncryption } from "aws-cdk-lib/aws-s3"; import { Construct } from "constructs"; import * as LC from "local-constructs"; @@ -49,8 +45,7 @@ export class UiInfra extends cdk.NestedStack { ) : null; - const sanitizedDomainName = - domainName && domainName.trim() ? domainName.trim() : null; + const sanitizedDomainName = domainName && domainName.trim() ? domainName.trim() : null; // S3 Bucket for hosting static website const bucket = new cdk.aws_s3.Bucket(this, "S3Bucket", { @@ -105,22 +100,16 @@ export class UiInfra extends cdk.NestedStack { loggingBucket.addToResourcePolicy( new cdk.aws_iam.PolicyStatement({ effect: cdk.aws_iam.Effect.ALLOW, - principals: [ - new cdk.aws_iam.ServicePrincipal("cloudfront.amazonaws.com"), - ], + principals: [new cdk.aws_iam.ServicePrincipal("cloudfront.amazonaws.com")], actions: ["s3:PutObject"], resources: [`${loggingBucket.bucketArn}/*`], }), ); // CloudFront Origin Access Identity - const cloudFrontOAI = new cdk.aws_cloudfront.OriginAccessIdentity( - this, - "CloudFrontOAI", - { - comment: "OAI to prevent direct public access to the bucket", - }, - ); + const cloudFrontOAI = new cdk.aws_cloudfront.OriginAccessIdentity(this, "CloudFrontOAI", { + comment: "OAI to prevent direct public access to the bucket", + }); // HSTS Function const hstsFunction = new cdk.aws_cloudfront.Function(this, "HstsFunction", { @@ -141,15 +130,11 @@ export class UiInfra extends cdk.NestedStack { // CloudFront Distribution const viewerCertificate = domainCertificate - ? cdk.aws_cloudfront.ViewerCertificate.fromAcmCertificate( - domainCertificate, - { - aliases: sanitizedDomainName ? [sanitizedDomainName] : [], - securityPolicy: - cdk.aws_cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, - sslMethod: cdk.aws_cloudfront.SSLMethod.SNI, - }, - ) + ? cdk.aws_cloudfront.ViewerCertificate.fromAcmCertificate(domainCertificate, { + aliases: sanitizedDomainName ? [sanitizedDomainName] : [], + securityPolicy: cdk.aws_cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + sslMethod: cdk.aws_cloudfront.SSLMethod.SNI, + }) : cdk.aws_cloudfront.ViewerCertificate.fromCloudFrontDefaultCertificate(); const distribution = new cdk.aws_cloudfront.CloudFrontWebDistribution( @@ -165,15 +150,12 @@ export class UiInfra extends cdk.NestedStack { behaviors: [ { isDefaultBehavior: true, - allowedMethods: - cdk.aws_cloudfront.CloudFrontAllowedMethods.GET_HEAD, - viewerProtocolPolicy: - cdk.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cdk.aws_cloudfront.CloudFrontAllowedMethods.GET_HEAD, + viewerProtocolPolicy: cdk.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, functionAssociations: [ { function: hstsFunction, - eventType: - cdk.aws_cloudfront.FunctionEventType.VIEWER_RESPONSE, + eventType: cdk.aws_cloudfront.FunctionEventType.VIEWER_RESPONSE, }, ], }, diff --git a/lib/stacks/uploads.ts b/lib/stacks/uploads.ts index 1f754d7fce..fedd0c3725 100644 --- a/lib/stacks/uploads.ts +++ b/lib/stacks/uploads.ts @@ -43,9 +43,7 @@ export class Uploads extends cdk.NestedStack { maxAge: 3000, }, ], - removalPolicy: isDev - ? cdk.RemovalPolicy.DESTROY - : cdk.RemovalPolicy.RETAIN, + removalPolicy: isDev ? cdk.RemovalPolicy.DESTROY : cdk.RemovalPolicy.RETAIN, autoDeleteObjects: isDev, }); @@ -54,10 +52,7 @@ export class Uploads extends cdk.NestedStack { effect: cdk.aws_iam.Effect.DENY, principals: [new cdk.aws_iam.AnyPrincipal()], actions: ["s3:*"], - resources: [ - attachmentsBucket.bucketArn, - `${attachmentsBucket.bucketArn}/*`, - ], + resources: [attachmentsBucket.bucketArn, `${attachmentsBucket.bucketArn}/*`], conditions: { Bool: { "aws:SecureTransport": "false" }, }, diff --git a/mocks/handlers/api/user.ts b/mocks/handlers/api/user.ts index 148d725e6f..7047e4f3b1 100644 --- a/mocks/handlers/api/user.ts +++ b/mocks/handlers/api/user.ts @@ -1,9 +1,7 @@ import { CognitoUserAttribute } from "amazon-cognito-identity-js"; import { isCmsUser } from "shared-utils"; import { findUserByUsername, convertUserAttributes } from "../authUtils"; -import type { - TestUserData, -} from "../.."; +import type { TestUserData } from "../.."; // using `any` type here because the function that this is mocking uses any export const mockCurrentAuthenticatedUser = (): TestUserData | any => { diff --git a/mocks/handlers/authUtils.ts b/mocks/handlers/authUtils.ts index 97046d775e..419d2cbf0f 100644 --- a/mocks/handlers/authUtils.ts +++ b/mocks/handlers/authUtils.ts @@ -1,7 +1,5 @@ import { makoReviewer, makoStateSubmitter, userResponses } from "../data/users"; -import type { - TestUserData, -} from "../index.d"; +import type { TestUserData } from "../index.d"; import { CognitoUserAttributes } from "shared-types"; export const setMockUsername = (user?: TestUserData | string | null): void => { diff --git a/mocks/handlers/opensearch/changelog.ts b/mocks/handlers/opensearch/changelog.ts index bdaa09f03d..456c3a71b3 100644 --- a/mocks/handlers/opensearch/changelog.ts +++ b/mocks/handlers/opensearch/changelog.ts @@ -11,7 +11,8 @@ const defaultChangelogSearchHandler = http.post( const must = query?.bool?.must; const mustTerms = must ? getTermKeys(must) : []; - const packageIdValue = getTermValues(must, "packageId.keyword") || getTermValues(must, "packageId"); + const packageIdValue = + getTermValues(must, "packageId.keyword") || getTermValues(must, "packageId"); if (!packageIdValue) { return new HttpResponse("No packageId provided", { status: 400 }); @@ -39,7 +40,11 @@ const defaultChangelogSearchHandler = http.post( "", ) as keyof TestChangelogDocument; if (filterValue) { - changelog = filterItemsByTerm(changelog, filterTerm, filterValue); + changelog = filterItemsByTerm( + changelog, + filterTerm, + filterValue, + ); } }); } diff --git a/package.json b/package.json index 7459fedf94..70678bc566 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "cdk": "cdk", "build:cli": "turbo build:cli", "lint": "eslint", + "format:check": "prettier --check .", + "format:write": "prettier --write .", "e2e": "turbo e2e", "e2e:ui": "turbo e2e:ui", "test-tsc": "tsc --skipLibCheck --noEmit", @@ -64,12 +66,15 @@ "@vitest/ui": "^2.0.5", "aws-cdk": "^2.157.0", "eslint": "^9.10.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.35.2", "eslint-plugin-react-hooks": "^4.6.2", "globals": "^15.9.0", "happy-dom": "^15.7.4", "jest": "^29.7.0", "npm-run-all": "^4.1.5", + "prettier": "3.4.2", "semantic-release": "^21.1.2", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", diff --git a/react-app/components.json b/react-app/components.json deleted file mode 100644 index 2907b0c163..0000000000 --- a/react-app/components.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "tailwind.config.js", - "css": "src/index.css", - "baseColor": "slate", - "cssVariables": true - }, - "aliases": { - "components": "@/components", - "utils": "@/utils" - } -} \ No newline at end of file diff --git a/react-app/index.html b/react-app/index.html index 4e26ef1ec6..3aa7ce9100 100644 --- a/react-app/index.html +++ b/react-app/index.html @@ -1,4 +1,4 @@ - + diff --git a/react-app/src/api/getAttachmentUrl.ts b/react-app/src/api/getAttachmentUrl.ts index daa79283b8..80ed6f1747 100644 --- a/react-app/src/api/getAttachmentUrl.ts +++ b/react-app/src/api/getAttachmentUrl.ts @@ -4,7 +4,7 @@ export const getAttachmentUrl = async ( id: string, bucket: string, key: string, - filename: string + filename: string, ) => { const response = await API.post("os", "/getAttachmentUrl", { body: { diff --git a/react-app/src/api/useGetCPOCs.ts b/react-app/src/api/useGetCPOCs.ts index e654a2a30e..c157a5f69b 100644 --- a/react-app/src/api/useGetCPOCs.ts +++ b/react-app/src/api/useGetCPOCs.ts @@ -15,12 +15,6 @@ export async function fetchCpocData() { } } -export function useGetCPOCs( - queryOptions?: UseQueryOptions, -) { - return useQuery( - ["package-cpocs"], - () => fetchCpocData(), - queryOptions, - ); +export function useGetCPOCs(queryOptions?: UseQueryOptions) { + return useQuery(["package-cpocs"], () => fetchCpocData(), queryOptions); } diff --git a/react-app/src/api/useGetCounties.ts b/react-app/src/api/useGetCounties.ts index b8736005ad..536a271c30 100644 --- a/react-app/src/api/useGetCounties.ts +++ b/react-app/src/api/useGetCounties.ts @@ -24,26 +24,18 @@ const usePopulationData = (stateString: string) => { export const useGetCounties = (): { label: string; value: string }[] => { const { data: userData } = useGetUser(); - const stateCodes = useMemo( - () => getUserStateCodes(userData?.user), - [userData], - ); + const stateCodes = useMemo(() => getUserStateCodes(userData?.user), [userData]); const stateNumericCodesString = useMemo( () => stateCodes - .map( - (code) => - FULL_CENSUS_STATES.find((state) => state.value === code)?.code, - ) + .map((code) => FULL_CENSUS_STATES.find((state) => state.value === code)?.code) .filter((code): code is string => code !== undefined && code !== "00") .join(","), [stateCodes], ); - const { data: populationData = [] } = usePopulationData( - stateNumericCodesString, - ); + const { data: populationData = [] } = usePopulationData(stateNumericCodesString); return ( populationData.map((county) => { diff --git a/react-app/src/api/useGetForm.ts b/react-app/src/api/useGetForm.ts index c8b1bce817..5996638314 100644 --- a/react-app/src/api/useGetForm.ts +++ b/react-app/src/api/useGetForm.ts @@ -3,10 +3,7 @@ import { API } from "aws-amplify"; import { ReactQueryApiError, FormSchema } from "shared-types"; import { reInsertRegex } from "shared-utils"; -export const getForm = async ( - formId: string, - formVersion?: string, -): Promise => { +export const getForm = async (formId: string, formVersion?: string): Promise => { const form = await API.post("os", "/forms", { body: { formId, formVersion }, }); @@ -30,7 +27,5 @@ export const getAllForms = async () => { }; export const useGetAllForms = () => { - return useQuery(["All Webforms"], () => - getAllForms(), - ); + return useQuery(["All Webforms"], () => getAllForms()); }; diff --git a/react-app/src/api/useGetItem.ts b/react-app/src/api/useGetItem.ts index 5b3ee43840..9d8b9f4cc2 100644 --- a/react-app/src/api/useGetItem.ts +++ b/react-app/src/api/useGetItem.ts @@ -1,14 +1,8 @@ -import { - useQuery, - useQueryClient, - UseQueryOptions, -} from "@tanstack/react-query"; +import { useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query"; import { API } from "aws-amplify"; import { opensearch, ReactQueryApiError, SEATOOL_STATUS } from "shared-types"; -export const getItem = async ( - id: string, -): Promise => +export const getItem = async (id: string): Promise => await API.post("os", "/item", { body: { id } }); export const idIsApproved = async (id: string) => { diff --git a/react-app/src/api/useGetPackageActions.ts b/react-app/src/api/useGetPackageActions.ts index 389c59e2d9..8349c9ef52 100644 --- a/react-app/src/api/useGetPackageActions.ts +++ b/react-app/src/api/useGetPackageActions.ts @@ -9,11 +9,11 @@ const getPackageActions = async (id: string): Promise => export const useGetPackageActions = ( id: string, - options?: UseQueryOptions + options?: UseQueryOptions, ) => { return useQuery( ["actions", id], () => getPackageActions(id), - options + options, ); }; diff --git a/react-app/src/api/useGetTypes.ts b/react-app/src/api/useGetTypes.ts index 89a86be05c..a1dbccecec 100644 --- a/react-app/src/api/useGetTypes.ts +++ b/react-app/src/api/useGetTypes.ts @@ -8,10 +8,7 @@ type FetchOptions = { typeIds?: number[] | string[]; }; -export async function fetchData({ - authorityId, - typeIds, -}: FetchOptions): Promise { +export async function fetchData({ authorityId, typeIds }: FetchOptions): Promise { const endpoint = typeIds ? "/getSubTypes" : "/getTypes"; const body = typeIds ? { authorityId, typeIds } : { authorityId }; @@ -39,11 +36,7 @@ export function useGetData( ? ["package-subtypes", authorityId, typeIds] : ["package-types", authorityId]; - return useQuery( - queryKey, - () => fetchData(options), - queryOptions, - ); + return useQuery(queryKey, () => fetchData(options), queryOptions); } export function useGetTypes( @@ -55,11 +48,8 @@ export function useGetTypes( export function useGetSubTypes( authorityId: number | string, - typeIds: number[] | string [], + typeIds: number[] | string[], options?: UseQueryOptions, ) { - return useGetData( - { authorityId, typeIds }, - options, - ); + return useGetData({ authorityId, typeIds }, options); } diff --git a/react-app/src/api/useGetUser.test.ts b/react-app/src/api/useGetUser.test.ts index 81d6392d24..c226d87a6a 100644 --- a/react-app/src/api/useGetUser.test.ts +++ b/react-app/src/api/useGetUser.test.ts @@ -13,9 +13,7 @@ const mockCurrentAuthenticatedUser = vi.fn((options = {}) => { // If you want to simulate an error, you could set a flag on `options` and check it here. if (options.error) { reject( - new Error( - "useGetUser > mockCurrentAuthenticatedUser: Expected error thrown by test.", - ), + new Error("useGetUser > mockCurrentAuthenticatedUser: Expected error thrown by test."), ); } else { resolve({ username: "0000aaaa-0000-00aa-0a0a-aaaaaa000000" }); @@ -31,34 +29,29 @@ const mockUserAttr = ({ options?: { error?: boolean; noRoles?: boolean }; }) => vi.fn(async () => { - return await new Promise>( - (resolve) => { - if (options?.error) - throw Error( - "useGetUser > mockUserAttr: Expected error thrown by test.", - ); - /* This array of attributes is where we make changes to our test - * user for test-related assertions. */ - return resolve([ - { Name: "sub", Value: "0000aaaa-0000-00aa-0a0a-aaaaaa000000" }, - { Name: "email_verified", Value: "true" }, - { Name: "given_name", Value: "George" }, - { Name: "family_name", Value: "Harrison" }, - { - Name: "custom:state", - Value: "VA,OH,SC,CO,GA,MD", - }, - { - Name: "email", - Value: "george@example.com", - }, - !options?.noRoles && { - Name: "custom:cms-roles", - Value: isCms ? "onemac-micro-reviewer" : "onemac-micro-cmsreview", - }, - ] as Array<{ Name: string; Value: string }>); - }, - ); + return await new Promise>((resolve) => { + if (options?.error) throw Error("useGetUser > mockUserAttr: Expected error thrown by test."); + /* This array of attributes is where we make changes to our test + * user for test-related assertions. */ + return resolve([ + { Name: "sub", Value: "0000aaaa-0000-00aa-0a0a-aaaaaa000000" }, + { Name: "email_verified", Value: "true" }, + { Name: "given_name", Value: "George" }, + { Name: "family_name", Value: "Harrison" }, + { + Name: "custom:state", + Value: "VA,OH,SC,CO,GA,MD", + }, + { + Name: "email", + Value: "george@example.com", + }, + !options?.noRoles && { + Name: "custom:cms-roles", + Value: isCms ? "onemac-micro-reviewer" : "onemac-micro-cmsreview", + }, + ] as Array<{ Name: string; Value: string }>); + }); }); describe("getUser", () => { diff --git a/react-app/src/api/useSearch.ts b/react-app/src/api/useSearch.ts index a1140b2a64..a37d451640 100644 --- a/react-app/src/api/useSearch.ts +++ b/react-app/src/api/useSearch.ts @@ -16,10 +16,7 @@ type QueryProps = { aggs?: opensearch.AggQuery[]; }; -export const getOsData = async < - TProps, - TResponse extends opensearch.Response, ->( +export const getOsData = async >( props: QueryProps, ): Promise => { const searchData = await API.post("os", `/search/${props.index}`, { @@ -35,14 +32,10 @@ export const getOsData = async < return searchData; }; -export const getMainExportData = async ( - filters?: opensearch.main.Filterable[], -) => { +export const getMainExportData = async (filters?: opensearch.main.Filterable[]) => { if (!filters) return []; - const recursiveSearch = async ( - startPage: number, - ): Promise => { + const recursiveSearch = async (startPage: number): Promise => { if (startPage * 1000 >= 10000) { return []; } @@ -69,11 +62,7 @@ export const getMainExportData = async ( }; export const useOsSearch = ( - options?: UseMutationOptions< - TResponse, - ReactQueryApiError, - QueryProps - >, + options?: UseMutationOptions>, ) => { //@ts-expect-error return useMutation>( diff --git a/react-app/src/components/Alert/index.tsx b/react-app/src/components/Alert/index.tsx index 5914465426..66f46ef49a 100644 --- a/react-app/src/components/Alert/index.tsx +++ b/react-app/src/components/Alert/index.tsx @@ -9,12 +9,10 @@ const alertVariants = cva( variants: { variant: { default: "bg-background text-foreground", - destructive: - "border-destructive/50 text-destructive [&>svg]:text-destructive", + destructive: "border-destructive/50 text-destructive [&>svg]:text-destructive", infoBlock: "border-l-[6px] border-y-0 border-r-0 border-cyan-500 bg-cyan-300/10 rounded-none", - success: - "border-l-[6px] border-[#2E8540] border-y-0 border-r-0 bg-[#E7F4E4]", + success: "border-l-[6px] border-[#2E8540] border-y-0 border-r-0 bg-[#E7F4E4]", }, }, defaultVariants: { @@ -28,12 +26,7 @@ const Alert = React.forwardRef< HTMLDivElement, React.HTMLAttributes & VariantProps >(({ className, variant, ...props }, ref) => ( -

+
)); Alert.displayName = "Alert"; @@ -51,14 +44,9 @@ AlertTitle.displayName = "AlertTitle"; const AlertDescription = React.forwardRef< HTMLParagraphElement, - React.HTMLAttributes & - VariantProps + React.HTMLAttributes & VariantProps >(({ className, ...props }, ref) => ( -
+
)); AlertDescription.displayName = "AlertDescription"; diff --git a/react-app/src/components/BreadCrumb/BreadCrumb.tsx b/react-app/src/components/BreadCrumb/BreadCrumb.tsx index 89a5fdf8c6..ee1f9d4d2a 100644 --- a/react-app/src/components/BreadCrumb/BreadCrumb.tsx +++ b/react-app/src/components/BreadCrumb/BreadCrumb.tsx @@ -74,11 +74,7 @@ export const BreadCrumbSeperator = () => ; export const BreadCrumbBar = ({ children }: React.PropsWithChildren) => { return ( -
); }; diff --git a/react-app/src/components/Cards/SectionCard.tsx b/react-app/src/components/Cards/SectionCard.tsx index bed04ceaff..80dc138a60 100644 --- a/react-app/src/components/Cards/SectionCard.tsx +++ b/react-app/src/components/Cards/SectionCard.tsx @@ -8,13 +8,7 @@ interface SectionCardProps { id?: string; testId?: string; } -export const SectionCard = ({ - title, - children, - className, - id, - testId, -}: SectionCardProps) => { +export const SectionCard = ({ title, children, className, id, testId }: SectionCardProps) => { return (
{title && ( <> -

{title}

+

+ {title} +


)} -
{children}
+
+ {children} +
); }; diff --git a/react-app/src/components/Chip/Chip.test.tsx b/react-app/src/components/Chip/Chip.test.tsx index 0e1a155325..b1a160ce8c 100644 --- a/react-app/src/components/Chip/Chip.test.tsx +++ b/react-app/src/components/Chip/Chip.test.tsx @@ -9,9 +9,7 @@ describe("Chip", () => { const { container } = render(); - const chipButton = container.getElementsByClassName( - "h-8 py-2 cursor-pointer", - )[0]; + const chipButton = container.getElementsByClassName("h-8 py-2 cursor-pointer")[0]; await userEvent.click(chipButton); diff --git a/react-app/src/components/Chip/index.tsx b/react-app/src/components/Chip/index.tsx index 139053b718..e1729b065f 100644 --- a/react-app/src/components/Chip/index.tsx +++ b/react-app/src/components/Chip/index.tsx @@ -18,7 +18,7 @@ const chipVariants = cva( defaultVariants: { variant: "default", }, - } + }, ); export interface ChipProps extends VariantProps { @@ -37,10 +37,7 @@ const Chip = ({ const noIcon = variant === "noIcon" || variant === "destructive"; return (
diff --git a/react-app/src/components/ConfirmationDialog/ConfirmationDialog.tsx b/react-app/src/components/ConfirmationDialog/ConfirmationDialog.tsx index e2b97d0c6c..d9ca68c30c 100644 --- a/react-app/src/components/ConfirmationDialog/ConfirmationDialog.tsx +++ b/react-app/src/components/ConfirmationDialog/ConfirmationDialog.tsx @@ -56,11 +56,7 @@ export function ConfirmationDialog({ data-testid="dialog-footer" > {acceptButtonVisible && ( - )} diff --git a/react-app/src/components/ConfirmationDialog/userPrompt.tsx b/react-app/src/components/ConfirmationDialog/userPrompt.tsx index 09f1f1d888..45112c3560 100644 --- a/react-app/src/components/ConfirmationDialog/userPrompt.tsx +++ b/react-app/src/components/ConfirmationDialog/userPrompt.tsx @@ -31,9 +31,7 @@ export const userPrompt = (newUserPrompt: UserPrompt) => { }; export const UserPrompt = () => { - const [activeUserPrompt, setActiveUserPrompt] = useState( - null, - ); + const [activeUserPrompt, setActiveUserPrompt] = useState(null); const [isOpen, setIsOpen] = useState(false); useEffect(() => { diff --git a/react-app/src/components/Container/Accordion/index.tsx b/react-app/src/components/Container/Accordion/index.tsx index 9c8074dc64..c7d9a4b440 100644 --- a/react-app/src/components/Container/Accordion/index.tsx +++ b/react-app/src/components/Container/Accordion/index.tsx @@ -11,11 +11,7 @@ const AccordionItem = React.forwardRef< React.ElementRef, AccordionItemProps >(({ className, ...props }, ref) => ( - + )); AccordionItem.displayName = "AccordionItem"; @@ -32,7 +28,7 @@ const AccordionTrigger = React.forwardRef< ref={ref} className={cn( "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", - className + className, )} {...props} > diff --git a/react-app/src/components/DetailsSection/DetailsSection.test.tsx b/react-app/src/components/DetailsSection/DetailsSection.test.tsx index a23c06f080..612633385a 100644 --- a/react-app/src/components/DetailsSection/DetailsSection.test.tsx +++ b/react-app/src/components/DetailsSection/DetailsSection.test.tsx @@ -6,11 +6,7 @@ import { DetailsSection } from "./index"; describe("DetailsSection", () => { it("renders with description", () => { render( - +

test child

, ); diff --git a/react-app/src/components/Dialog/index.tsx b/react-app/src/components/Dialog/index.tsx index 9951a5a19c..9a38456eed 100644 --- a/react-app/src/components/Dialog/index.tsx +++ b/react-app/src/components/Dialog/index.tsx @@ -61,13 +61,7 @@ const DialogHeader = ({ }: React.HTMLAttributes & { className?: string; }) => ( -
+
); DialogHeader.displayName = "DialogHeader"; @@ -78,10 +72,7 @@ const DialogFooter = ({ className?: string; }) => (
); @@ -95,10 +86,7 @@ const DialogTitle = React.forwardRef< >(({ className, ...props }, ref) => ( )); diff --git a/react-app/src/components/Form/content/ContentWrappers.tsx b/react-app/src/components/Form/content/ContentWrappers.tsx index b4f0743cb3..2ff96bb15f 100644 --- a/react-app/src/components/Form/content/ContentWrappers.tsx +++ b/react-app/src/components/Form/content/ContentWrappers.tsx @@ -57,7 +57,11 @@ export const ActionFormDescription = ({ }; export const ActionFormHeading = ({ title }: { title: string }) => { - return

{title}

; + return ( +

+ {title} +

+ ); }; export const PreSubmitNotice = ({ diff --git a/react-app/src/components/Inputs/button.test.tsx b/react-app/src/components/Inputs/button.test.tsx index 8dfb4d8db5..67f9caa522 100644 --- a/react-app/src/components/Inputs/button.test.tsx +++ b/react-app/src/components/Inputs/button.test.tsx @@ -1,36 +1,39 @@ -import { render, screen } from '@testing-library/react'; -import { describe, it, expect } from 'vitest'; -import { Button } from './button'; +import { render, screen } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import { Button } from "./button"; -describe('Button Component', () => { - it('renders children correctly', () => { +describe("Button Component", () => { + it("renders children correctly", () => { render(); - expect(screen.getByText('Click Me')).toBeInTheDocument(); + expect(screen.getByText("Click Me")).toBeInTheDocument(); }); - it('applies default variant and size classes', () => { + it("applies default variant and size classes", () => { render(); - const button = screen.getByText('Click Me'); - expect(button).toHaveClass('bg-primary text-slate-50'); + const button = screen.getByText("Click Me"); + expect(button).toHaveClass("bg-primary text-slate-50"); }); - it('applies the correct variant and size classes when props are set', () => { - render(); - const button = screen.getByText('Delete'); - expect(button).toHaveClass('bg-destructive'); - expect(button).toHaveClass('h-11 px-8'); + it("applies the correct variant and size classes when props are set", () => { + render( + , + ); + const button = screen.getByText("Delete"); + expect(button).toHaveClass("bg-destructive"); + expect(button).toHaveClass("h-11 px-8"); }); - it('shows a loading spinner when loading prop is true', () => { + it("shows a loading spinner when loading prop is true", () => { const { container } = render(); - const spinner = container.querySelector('.animate-spin'); + const spinner = container.querySelector(".animate-spin"); expect(spinner).toBeInTheDocument(); }); - it('disables the button when the disabled prop is true', () => { + it("disables the button when the disabled prop is true", () => { render(); - const button = screen.getByText('Disabled'); + const button = screen.getByText("Disabled"); expect(button).toBeDisabled(); }); }); - diff --git a/react-app/src/components/Inputs/button.tsx b/react-app/src/components/Inputs/button.tsx index c86960409b..d05bfc4b1a 100644 --- a/react-app/src/components/Inputs/button.tsx +++ b/react-app/src/components/Inputs/button.tsx @@ -11,12 +11,9 @@ const buttonVariants = cva( variants: { variant: { default: "bg-primary text-slate-50 hover:bg-primary-dark", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-primary text-primary font-bold hover:bg-primary/10", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: "border border-primary text-primary font-bold hover:bg-primary/10", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, @@ -42,17 +39,10 @@ export interface ButtonProps } const Button = React.forwardRef( - ( - { className, variant, size, loading, children, asChild = false, ...props }, - ref, - ) => { + ({ className, variant, size, loading, children, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button"; return ( - + <> {loading && } {children} diff --git a/react-app/src/components/Inputs/calendar.tsx b/react-app/src/components/Inputs/calendar.tsx index a0b3de5a48..c71e36b971 100644 --- a/react-app/src/components/Inputs/calendar.tsx +++ b/react-app/src/components/Inputs/calendar.tsx @@ -3,12 +3,7 @@ import { DayPicker } from "react-day-picker"; import { cn } from "@/utils"; import { buttonVariants } from "./button"; -function Calendar({ - className, - classNames, - showOutsideDays = true, - ...props -}: CalendarProps) { +function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) { return ( {caption} - props.onPageChange(Number(v.currentTarget.value) - 1) - } + onChange={(v) => props.onPageChange(Number(v.currentTarget.value) - 1)} className="absolute w-auto h-auto opacity-0 cursor-pointer" aria-labelledby="morePagesButton" data-testid="morePagesButton" diff --git a/react-app/src/components/Popover/index.tsx b/react-app/src/components/Popover/index.tsx index 31497ebe24..6354fbd54c 100644 --- a/react-app/src/components/Popover/index.tsx +++ b/react-app/src/components/Popover/index.tsx @@ -24,7 +24,7 @@ const PopoverContent = React.forwardRef< sideOffset={sideOffset} className={cn( "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", - className + className, )} {...props} /> diff --git a/react-app/src/components/RHF/Field.tsx b/react-app/src/components/RHF/Field.tsx index a89cc1f828..4cc16153f4 100644 --- a/react-app/src/components/RHF/Field.tsx +++ b/react-app/src/components/RHF/Field.tsx @@ -32,22 +32,13 @@ export const Field = ({
{(SLOT.label || SLOT.styledLabel) && ( - + )} -
+
{SLOT.fields?.map((F) => { return ( - + ); })}
@@ -61,9 +52,7 @@ export const Field = ({ key={adjustedSlotName} // @ts-expect-error control={control} - rules={ - ruleGenerator(SLOT.rules, SLOT.addtnlRules) as CustomRegisterOptions - } + rules={ruleGenerator(SLOT.rules, SLOT.addtnlRules) as CustomRegisterOptions} name={adjustedSlotName as never} render={RHFSlot({ ...SLOT, diff --git a/react-app/src/components/RHF/FieldArray.tsx b/react-app/src/components/RHF/FieldArray.tsx index 07bc75aa8d..67fb5e77c5 100644 --- a/react-app/src/components/RHF/FieldArray.tsx +++ b/react-app/src/components/RHF/FieldArray.tsx @@ -7,9 +7,7 @@ import { slotInitializer } from "./utils"; import { Field } from "./Field"; import { cn } from "@/utils"; -export const RHFFieldArray = ( - props: FieldArrayProps, -) => { +export const RHFFieldArray = (props: FieldArrayProps) => { const fieldArr = useFieldArray({ control: props.control, name: props.name, @@ -31,19 +29,9 @@ export const RHFFieldArray = (
{fieldArr.fields.map((FLD, index) => { return ( -
+
{props.fields.map((SLOT, i) => { - return ( - - ); + return ; })} {/* FieldArray Removal */} {index >= 1 && !props.removeText && ( @@ -66,19 +54,12 @@ export const RHFFieldArray = ( {props.removeText ?? "Remove Group"} )} - {props.divider && ( -
- )} + {props.divider &&
}
); })} {props.lastDivider && ( -
+
)}
+
)); Table.displayName = "Table"; @@ -34,11 +30,7 @@ const TableBody = React.forwardRef< className?: string; } >(({ className, ...props }, ref) => ( - + )); TableBody.displayName = "TableBody"; @@ -98,16 +90,8 @@ const TableHead = React.forwardRef< icon ) : ( <> - {desc && ( - - )} - {!desc && ( - - )} + {desc && } + {!desc && } )} @@ -138,21 +122,8 @@ const TableCaption = React.forwardRef< className?: string; } >(({ className, ...props }, ref) => ( -
+ )); TableCaption.displayName = "TableCaption"; -export { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -}; +export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }; diff --git a/react-app/src/components/Tooltip/index.tsx b/react-app/src/components/Tooltip/index.tsx index b9c51859b9..01475bd6ab 100644 --- a/react-app/src/components/Tooltip/index.tsx +++ b/react-app/src/components/Tooltip/index.tsx @@ -1,12 +1,12 @@ import * as React from "react"; import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { cn } from "@/utils"; -const TooltipProvider = TooltipPrimitive.Provider - -const Tooltip = TooltipPrimitive.Root - -const TooltipTrigger = TooltipPrimitive.Trigger - +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + const TooltipContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -16,11 +16,11 @@ const TooltipContent = React.forwardRef< sideOffset={sideOffset} className={cn( "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", - className + className, )} {...props} /> -)) -TooltipContent.displayName = TooltipPrimitive.Content.displayName - -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } \ No newline at end of file +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/react-app/src/features/dashboard/Lists/spas/consts.tsx b/react-app/src/features/dashboard/Lists/spas/consts.tsx index c31e6b726e..7061542575 100644 --- a/react-app/src/features/dashboard/Lists/spas/consts.tsx +++ b/react-app/src/features/dashboard/Lists/spas/consts.tsx @@ -1,10 +1,10 @@ -import { removeUnderscoresAndCapitalize } from "@/utils"; -import { OsTableColumn } from "@/components"; -import { CMS_READ_ONLY_ROLES, SEATOOL_STATUS, UserRoles } from "shared-types"; import { useGetUser } from "@/api"; -import { CellDetailsLink, renderCellActions, renderCellDate } from "../renderCells"; +import { OsTableColumn } from "@/components"; import { BLANK_VALUE } from "@/consts"; +import { removeUnderscoresAndCapitalize } from "@/utils"; +import { CMS_READ_ONLY_ROLES, SEATOOL_STATUS, UserRoles } from "shared-types"; import { formatSeatoolDate } from "shared-utils"; +import { CellDetailsLink, renderCellActions, renderCellDate } from "../renderCells"; export const useSpaTableColumns = (): OsTableColumn[] => { const { data: props } = useGetUser(); diff --git a/react-app/src/features/dashboard/Lists/waivers/consts.tsx b/react-app/src/features/dashboard/Lists/waivers/consts.tsx index 02ebdc1437..7b7aa604dd 100644 --- a/react-app/src/features/dashboard/Lists/waivers/consts.tsx +++ b/react-app/src/features/dashboard/Lists/waivers/consts.tsx @@ -1,10 +1,10 @@ -import { removeUnderscoresAndCapitalize, LABELS } from "@/utils"; +import { useGetUser } from "@/api"; import { OsTableColumn } from "@/components"; import { BLANK_VALUE } from "@/consts"; +import { LABELS, removeUnderscoresAndCapitalize } from "@/utils"; import { CMS_READ_ONLY_ROLES, SEATOOL_STATUS, UserRoles } from "shared-types"; -import { useGetUser } from "@/api"; -import { CellDetailsLink, renderCellActions, renderCellDate } from "../renderCells"; import { formatSeatoolDate } from "shared-utils"; +import { CellDetailsLink, renderCellActions, renderCellDate } from "../renderCells"; export const useWaiverTableColumns = (): OsTableColumn[] => { const { data: props } = useGetUser(); diff --git a/react-app/src/features/forms/index.ts b/react-app/src/features/forms/index.ts index b3ae65ecb8..06b660f945 100644 --- a/react-app/src/features/forms/index.ts +++ b/react-app/src/features/forms/index.ts @@ -3,4 +3,4 @@ export * as CapitatedWaivers from "./waiver/capitated"; export * as ContractingWaivers from "./waiver/contracting"; export * from "./waiver/app-k"; export * from "./waiver/temporary-extension"; -export * from "./post-submission/withdraw-rai"; \ No newline at end of file +export * from "./post-submission/withdraw-rai"; diff --git a/react-app/src/features/guides/index.tsx b/react-app/src/features/guides/index.tsx index 38bddaf858..2236a01336 100644 --- a/react-app/src/features/guides/index.tsx +++ b/react-app/src/features/guides/index.tsx @@ -122,8 +122,7 @@ const abp_forms: Guide[] = [ }, { title: "Alternative Benefit Plan State Training Webinar", - linkTitle: - "Alternative Benefit Plan State Training Webinar (2013-08-13 .wmv)", + linkTitle: "Alternative Benefit Plan State Training Webinar (2013-08-13 .wmv)", href: "https://www.medicaid.gov/media/162926", targetBlank: true, }, diff --git a/react-app/src/features/package/hooks.tsx b/react-app/src/features/package/hooks.tsx index 9039853522..764f6821fe 100644 --- a/react-app/src/features/package/hooks.tsx +++ b/react-app/src/features/package/hooks.tsx @@ -7,9 +7,7 @@ export type DetailsSidebarLink = { displayName: string; }; -export const useDetailsSidebarLinks = ( - dataId: string, -): DetailsSidebarLink[] => { +export const useDetailsSidebarLinks = (dataId: string): DetailsSidebarLink[] => { const { data } = useGetItem(dataId); const [sideBarLinks, setSideBarLinks] = useState([]); diff --git a/react-app/src/features/package/index.tsx b/react-app/src/features/package/index.tsx index b90747133f..a4e30538f5 100644 --- a/react-app/src/features/package/index.tsx +++ b/react-app/src/features/package/index.tsx @@ -56,7 +56,9 @@ type LoaderData = { authority: Authority; }; -export const packageDetailsLoader = async ({ params }: LoaderFunctionArgs): Promise => { +export const packageDetailsLoader = async ({ + params, +}: LoaderFunctionArgs): Promise => { const { id, authority } = params; if (id === undefined || authority === undefined) { return redirect("/dashboard"); diff --git a/react-app/src/features/selection-flow/options.tsx b/react-app/src/features/selection-flow/options.tsx index 3d37b7ee9f..1096958c33 100644 --- a/react-app/src/features/selection-flow/options.tsx +++ b/react-app/src/features/selection-flow/options.tsx @@ -1,9 +1,5 @@ import { OptionData } from "@/features/selection-flow/plan-types"; -import { - ORIGIN, - SPA_SUBMISSION_ORIGIN, - WAIVER_SUBMISSION_ORIGIN, -} from "@/utils"; +import { ORIGIN, SPA_SUBMISSION_ORIGIN, WAIVER_SUBMISSION_ORIGIN } from "@/utils"; export const AUTHORITY_OPTIONS: OptionData[] = [ { @@ -13,8 +9,7 @@ export const AUTHORITY_OPTIONS: OptionData[] = [ }, { title: "Waiver Action", - description: - "Submit Waivers, Amendments, Renewals, and Temporary Extensions", + description: "Submit Waivers, Amendments, Renewals, and Temporary Extensions", to: "/new-submission/waiver", }, ]; @@ -94,21 +89,16 @@ export const WAIVER_OPTIONS: OptionData[] = [ export const B_WAIVER_OPTIONS: OptionData[] = [ { title: "1915(b)(4) FFS Selective Contracting Waivers", - description: - "Submit 1915(b)(4) FFS Selective Contracting Waivers, Amendments, and Renewals", + description: "Submit 1915(b)(4) FFS Selective Contracting Waivers, Amendments, and Renewals", to: "/new-submission/waiver/b/b4", }, { title: "1915(b) Comprehensive (Capitated) Waiver Authority", description: ( <> - Submit 1915(b) Comprehensive (Capitated) Waivers, Amendments and - Renewals
+ Submit 1915(b) Comprehensive (Capitated) Waivers, Amendments and Renewals
- - Not applicable for 1915(b)(4) FFS Selective Contracting Waiver - actions - + Not applicable for 1915(b)(4) FFS Selective Contracting Waiver actions ), @@ -119,8 +109,7 @@ export const B_WAIVER_OPTIONS: OptionData[] = [ export const B4_WAIVER_OPTIONS: OptionData[] = [ { title: "1915(b)(4) FFS Selective Contracting New Initial Waiver", - description: - "Create a new 1915(b)(4) FFS Selective Contracting Initial Waiver", + description: "Create a new 1915(b)(4) FFS Selective Contracting Initial Waiver", to: { pathname: "/new-submission/waiver/b/b4/initial/create", search: new URLSearchParams({ @@ -130,8 +119,7 @@ export const B4_WAIVER_OPTIONS: OptionData[] = [ }, { title: "1915(b)(4) FFS Selective Contracting Renewal Waiver", - description: - "Renew an existing 1915(b)(4) FFS Selective Contracting Waiver", + description: "Renew an existing 1915(b)(4) FFS Selective Contracting Waiver", to: { pathname: "/new-submission/waiver/b/b4/renewal/create", search: new URLSearchParams({ @@ -141,8 +129,7 @@ export const B4_WAIVER_OPTIONS: OptionData[] = [ }, { title: "1915(b)(4) FFS Selective Contracting Waiver Amendment", - description: - "Amend an existing 1915(b)(4) FFS Selective Contracting Waiver", + description: "Amend an existing 1915(b)(4) FFS Selective Contracting Waiver", to: { pathname: "/new-submission/waiver/b/b4/amendment/create", search: new URLSearchParams({ @@ -154,8 +141,7 @@ export const B4_WAIVER_OPTIONS: OptionData[] = [ export const BCAP_WAIVER_OPTIONS: OptionData[] = [ { title: "1915(b) Comprehensive (Capitated) New Initial Waiver", - description: - "Create a new 1915(b) Comprehensive (Capitated) Initial Waiver", + description: "Create a new 1915(b) Comprehensive (Capitated) Initial Waiver", to: { pathname: "/new-submission/waiver/b/capitated/initial/create", search: new URLSearchParams({ diff --git a/react-app/src/formSchemas/temporary-extension.ts b/react-app/src/formSchemas/temporary-extension.ts index 85fbc1787a..e129f90c8e 100644 --- a/react-app/src/formSchemas/temporary-extension.ts +++ b/react-app/src/formSchemas/temporary-extension.ts @@ -15,9 +15,7 @@ export const formSchema = events["temporary-extension"].baseSchema .object({ validAuthority: z .object({ - waiverNumber: events[ - "temporary-extension" - ].baseSchema.shape.waiverNumber + waiverNumber: events["temporary-extension"].baseSchema.shape.waiverNumber .refine(async (value) => await itemExists(value), { message: "According to our records, this Approved Initial or Renewal Waiver Number does not yet exist. Please check the Approved Initial or Renewal Waiver Number and try entering it again.", @@ -33,9 +31,7 @@ export const formSchema = events["temporary-extension"].baseSchema try { const originalWaiverData = await getItem(data.waiverNumber); - return !( - originalWaiverData._source.authority !== data.authority - ); + return !(originalWaiverData._source.authority !== data.authority); } catch { return z.never; } diff --git a/react-app/src/formSchemas/withdraw-package.ts b/react-app/src/formSchemas/withdraw-package.ts index dbf32c1501..bc13fe5644 100644 --- a/react-app/src/formSchemas/withdraw-package.ts +++ b/react-app/src/formSchemas/withdraw-package.ts @@ -8,8 +8,7 @@ export const formSchema = events["withdraw-package"].baseSchema .superRefine((data, ctx) => { if ( !data.attachments.supportingDocumentation?.files.length && - (data.additionalInformation === undefined || - data.additionalInformation === "") + (data.additionalInformation === undefined || data.additionalInformation === "") ) { ctx.addIssue({ message: "An Attachment or Additional Information is required.", diff --git a/react-app/src/hooks/UseDebounce.test.tsx b/react-app/src/hooks/UseDebounce.test.tsx index 9f53858fa8..74910eb276 100644 --- a/react-app/src/hooks/UseDebounce.test.tsx +++ b/react-app/src/hooks/UseDebounce.test.tsx @@ -16,10 +16,9 @@ describe("UseDebounce", () => { }); it("returns updated value after the specified delay", () => { - const { result, rerender } = renderHook( - ({ value, delay }) => useDebounce(value, delay), - { initialProps: { value: "start value", delay: 500 } }, - ); + const { result, rerender } = renderHook(({ value, delay }) => useDebounce(value, delay), { + initialProps: { value: "start value", delay: 500 }, + }); expect(result.current).toBe("start value"); rerender({ value: "new value", delay: 500 }); diff --git a/react-app/src/hooks/useCountdown/index.test.ts b/react-app/src/hooks/useCountdown/index.test.ts index eeed6eb828..d657889dd0 100644 --- a/react-app/src/hooks/useCountdown/index.test.ts +++ b/react-app/src/hooks/useCountdown/index.test.ts @@ -1,13 +1,5 @@ import { act, renderHook } from "@testing-library/react"; -import { - beforeEach, - describe, - test, - vi, - expect, - beforeAll, - afterAll, -} from "vitest"; +import { beforeEach, describe, test, vi, expect, beforeAll, afterAll } from "vitest"; import { useCountdown } from "."; import { cleanup } from "@testing-library/react"; diff --git a/react-app/src/hooks/useCountdown/index.ts b/react-app/src/hooks/useCountdown/index.ts index bdb02f6e7c..26fce65a67 100644 --- a/react-app/src/hooks/useCountdown/index.ts +++ b/react-app/src/hooks/useCountdown/index.ts @@ -8,9 +8,7 @@ type CountdownControllers = { resetCountdown: () => void; }; -export const useCountdown = ( - minutesToCountDown: number, -): [number, CountdownControllers] => { +export const useCountdown = (minutesToCountDown: number): [number, CountdownControllers] => { const [count, setCount] = useState(minutesToCountDown); const [isCountdownRunning, setIsCountdownRunning] = useState(false); diff --git a/react-app/src/hooks/useIdle/index.test.ts b/react-app/src/hooks/useIdle/index.test.ts index b175ccab62..57f275e8df 100644 --- a/react-app/src/hooks/useIdle/index.test.ts +++ b/react-app/src/hooks/useIdle/index.test.ts @@ -66,17 +66,13 @@ describe("useIdle", () => { }); test("Returns correct initial value from initialState argument", () => { - const { result } = renderHook(() => - useIdle(IDLE_TIME, { initialState: false }), - ); + const { result } = renderHook(() => useIdle(IDLE_TIME, { initialState: false })); expect(result.current).toBe(false); }); test("Returns correct value when timeout has elapsed", () => { - const { result } = renderHook(() => - useIdle(IDLE_TIME, { initialState: false }), - ); + const { result } = renderHook(() => useIdle(IDLE_TIME, { initialState: false })); act(() => { vi.advanceTimersByTime(IDLE_TIME); diff --git a/react-app/src/hooks/useIdle/index.ts b/react-app/src/hooks/useIdle/index.ts index 5831ea3003..903aa73aa0 100644 --- a/react-app/src/hooks/useIdle/index.ts +++ b/react-app/src/hooks/useIdle/index.ts @@ -48,9 +48,7 @@ export function useIdle( events.forEach((event) => document.addEventListener(event, handleEvents)); return () => { - events.forEach((event) => - document.removeEventListener(event, handleEvents), - ); + events.forEach((event) => document.removeEventListener(event, handleEvents)); }; }, [timeout]); diff --git a/react-app/src/index.css b/react-app/src/index.css index 1793e8ba32..2690fa3582 100644 --- a/react-app/src/index.css +++ b/react-app/src/index.css @@ -81,7 +81,9 @@ body { @apply bg-background text-foreground; font-family: "Open Sans", system-ui, sans-serif; - font-feature-settings: "rlig" 1, "calt" 1; + font-feature-settings: + "rlig" 1, + "calt" 1; } } diff --git a/react-app/src/utils/Poller/DataPoller.ts b/react-app/src/utils/Poller/DataPoller.ts index 3622cde367..e2d01638f9 100644 --- a/react-app/src/utils/Poller/DataPoller.ts +++ b/react-app/src/utils/Poller/DataPoller.ts @@ -39,8 +39,7 @@ export class DataPoller { } } } catch (error) { - const message = - error instanceof Error ? (error as Error).message : error; + const message = error instanceof Error ? (error as Error).message : error; errorMessage = `Error fetching data: ${message}`; } } else { diff --git a/react-app/src/utils/Poller/documentPoller.ts b/react-app/src/utils/Poller/documentPoller.ts index f86af71971..0d236eed19 100644 --- a/react-app/src/utils/Poller/documentPoller.ts +++ b/react-app/src/utils/Poller/documentPoller.ts @@ -6,10 +6,7 @@ export type CheckDocumentFunction = ( check: ReturnType & { recordExists: boolean }, ) => boolean; -export const documentPoller = ( - id: string, - documentChecker: CheckDocumentFunction, -) => +export const documentPoller = (id: string, documentChecker: CheckDocumentFunction) => new DataPoller({ interval: 1000, pollAttempts: 20, diff --git a/react-app/src/utils/createContextProvider.ts b/react-app/src/utils/createContextProvider.ts index 23f44e9577..00619ff078 100644 --- a/react-app/src/utils/createContextProvider.ts +++ b/react-app/src/utils/createContextProvider.ts @@ -24,7 +24,7 @@ type CreateContextReturn = [React.Provider, () => T, React.Context]; * @param options create context options */ export function createContextProvider( - options: CreateContextOptions + options: CreateContextOptions, ): CreateContextReturn { const { errorMessage = "useContext: `context` is undefined. Seems you forgot to wrap component within the Provider", @@ -48,9 +48,5 @@ export function createContextProvider( return context as T; } - return [ - Context.Provider, - useContext, - Context, - ] as CreateContextReturn; + return [Context.Provider, useContext, Context] as CreateContextReturn; } diff --git a/react-app/src/utils/crumbs.test.ts b/react-app/src/utils/crumbs.test.ts index 8c1de3ed7b..63649ddccc 100644 --- a/react-app/src/utils/crumbs.test.ts +++ b/react-app/src/utils/crumbs.test.ts @@ -1,15 +1,15 @@ -import { describe, it, expect} from 'vitest'; +import { describe, it, expect } from "vitest"; import { getDashboardTabForAuthority, detailsAndActionsCrumbs, dashboardCrumb, detailsCrumb, actionCrumb, -} from './crumbs'; -import { Action } from 'shared-types/actions'; +} from "./crumbs"; +import { Action } from "shared-types/actions"; -describe('getDashboardTabForAuthority', () => { - //test for authority +describe("getDashboardTabForAuthority", () => { + //test for authority it('should return "spas" for "CHIP SPA"', () => { const result = getDashboardTabForAuthority("CHIP SPA" as any); expect(result).toBe("spas"); @@ -30,25 +30,24 @@ describe('getDashboardTabForAuthority', () => { expect(result).toBe("waivers"); }); - it('should throw an error for an invalid authority', () => { - expect(() => getDashboardTabForAuthority("Invalid Authority" as any)).toThrow("Invalid authority"); + it("should throw an error for an invalid authority", () => { + expect(() => getDashboardTabForAuthority("Invalid Authority" as any)).toThrow( + "Invalid authority", + ); }); }); -describe('detailsAndActionsCrumbs', () => { +describe("detailsAndActionsCrumbs", () => { const id = "12345"; const authority = "CHIP SPA" as any; - it('should return default breadcrumbs without actionType', () => { - const expectedBreadcrumbs = [ - dashboardCrumb(authority), - detailsCrumb(id, authority), - ]; + it("should return default breadcrumbs without actionType", () => { + const expectedBreadcrumbs = [dashboardCrumb(authority), detailsCrumb(id, authority)]; const result = detailsAndActionsCrumbs({ id, authority }); expect(result).toEqual(expectedBreadcrumbs); }); - it('should return breadcrumbs including action crumb when actionType is provided', () => { + it("should return breadcrumbs including action crumb when actionType is provided", () => { const actionType = Action.RESPOND_TO_RAI; const expectedBreadcrumbs = [ dashboardCrumb(authority), @@ -60,8 +59,8 @@ describe('detailsAndActionsCrumbs', () => { }); }); -describe('dashboardCrumb', () => { - it('should return correct breadcrumb with authority', () => { +describe("dashboardCrumb", () => { + it("should return correct breadcrumb with authority", () => { const result = dashboardCrumb("CHIP SPA" as any); const expected = { displayText: "Dashboard", @@ -72,7 +71,7 @@ describe('dashboardCrumb', () => { expect(result).toEqual(expected); }); - it('should return correct breadcrumb without authority', () => { + it("should return correct breadcrumb without authority", () => { const result = dashboardCrumb(); const expected = { displayText: "Dashboard", @@ -84,8 +83,8 @@ describe('dashboardCrumb', () => { }); }); -describe('detailsCrumb', () => { - it('should return the correct details breadcrumb', () => { +describe("detailsCrumb", () => { + it("should return the correct details breadcrumb", () => { const id = "12345"; const authority = "CHIP SPA" as any; const result = detailsCrumb(id, authority); @@ -98,8 +97,8 @@ describe('detailsCrumb', () => { }); }); -describe('actionCrumb', () => { - it('should return the correct action breadcrumb', () => { +describe("actionCrumb", () => { + it("should return the correct action breadcrumb", () => { const actionType = Action.RESPOND_TO_RAI; const id = "12345"; const result = actionCrumb(actionType, id); diff --git a/react-app/src/utils/crumbs.ts b/react-app/src/utils/crumbs.ts index de778650a0..bdbb8aa328 100644 --- a/react-app/src/utils/crumbs.ts +++ b/react-app/src/utils/crumbs.ts @@ -9,9 +9,7 @@ type DetailsAndActionsBreadCrumbsArgs = { actionType?: Action; }; -export const getDashboardTabForAuthority = ( - authority: Authority, -): "spas" | "waivers" => { +export const getDashboardTabForAuthority = (authority: Authority): "spas" | "waivers" => { switch (authority) { case "CHIP SPA" as Authority: case "Medicaid SPA" as Authority: @@ -29,29 +27,19 @@ export const detailsAndActionsCrumbs = ({ authority, actionType, }: DetailsAndActionsBreadCrumbsArgs): BreadCrumbConfig[] => { - const defaultBreadCrumbs = [ - dashboardCrumb(authority), - detailsCrumb(id, authority), - ]; + const defaultBreadCrumbs = [dashboardCrumb(authority), detailsCrumb(id, authority)]; - return actionType - ? [...defaultBreadCrumbs, actionCrumb(actionType, id)] - : defaultBreadCrumbs; + return actionType ? [...defaultBreadCrumbs, actionCrumb(actionType, id)] : defaultBreadCrumbs; }; export const dashboardCrumb = (authority?: Authority): BreadCrumbConfig => ({ displayText: "Dashboard", order: 1, default: true, - to: authority - ? `/dashboard?tab=${getDashboardTabForAuthority(authority)}` - : "/dashboard", + to: authority ? `/dashboard?tab=${getDashboardTabForAuthority(authority)}` : "/dashboard", }); -export const detailsCrumb = ( - id: string, - authority: Authority, -): BreadCrumbConfig => ({ +export const detailsCrumb = (id: string, authority: Authority): BreadCrumbConfig => ({ displayText: id, order: 2, to: `/details/${authority}/${id}`, diff --git a/react-app/src/utils/formOrigin.test.ts b/react-app/src/utils/formOrigin.test.ts index 59d6f8f7dd..aca52a300d 100644 --- a/react-app/src/utils/formOrigin.test.ts +++ b/react-app/src/utils/formOrigin.test.ts @@ -1,12 +1,12 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { getFormOrigin } from './formOrigin'; -import { Authority } from 'shared-types/authority'; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { getFormOrigin } from "./formOrigin"; +import { Authority } from "shared-types/authority"; -vi.mock('./crumbs', () => ({ - getDashboardTabForAuthority: vi.fn(() => 'spas'), // Mock return value +vi.mock("./crumbs", () => ({ + getDashboardTabForAuthority: vi.fn(() => "spas"), // Mock return value })); -describe('getFormOrigin', () => { +describe("getFormOrigin", () => { let originalLocation: Location; beforeEach(() => { @@ -14,27 +14,27 @@ describe('getFormOrigin', () => { const mockLocation = { ...originalLocation, - search: '', + search: "", assign: vi.fn(), reload: vi.fn(), replace: vi.fn(), }; - Object.defineProperty(window, 'location', { + Object.defineProperty(window, "location", { value: mockLocation, writable: true, }); }); afterEach(() => { - Object.defineProperty(window, 'location', { + Object.defineProperty(window, "location", { value: originalLocation, writable: true, }); vi.clearAllMocks(); // Clear mocks after each test }); - it('should return the correct pathname and search for dashboard origin', () => { + it("should return the correct pathname and search for dashboard origin", () => { window.location.search = `?origin=dashboard`; const authority = "chip spa" as Authority; // Use string assertion @@ -43,34 +43,34 @@ describe('getFormOrigin', () => { expect(result).toEqual({ pathname: `/dashboard`, - search: new URLSearchParams({ tab: 'spas' }).toString(), + search: new URLSearchParams({ tab: "spas" }).toString(), }); - // Other tests remain unchanged -}); - it('should return the correct pathname for spa submission origin', () => { + // Other tests remain unchanged + }); + it("should return the correct pathname for spa submission origin", () => { window.location.search = `?origin=spas`; const result = getFormOrigin(); expect(result).toEqual({ pathname: `/dashboard`, - search: new URLSearchParams({ tab: 'spas' }).toString(), + search: new URLSearchParams({ tab: "spas" }).toString(), }); }); - it('should return the correct pathname for waiver submission origin', () => { + it("should return the correct pathname for waiver submission origin", () => { window.location.search = `?origin=waivers`; const result = getFormOrigin(); expect(result).toEqual({ pathname: `/dashboard`, - search: new URLSearchParams({ tab: 'waivers' }).toString(), + search: new URLSearchParams({ tab: "waivers" }).toString(), }); }); - it('should return the default pathname for unknown origin', () => { - window.location.search = ''; + it("should return the default pathname for unknown origin", () => { + window.location.search = ""; const result = getFormOrigin(); @@ -78,4 +78,4 @@ describe('getFormOrigin', () => { pathname: `/dashboard`, }); }); -}); \ No newline at end of file +}); diff --git a/react-app/src/utils/formOrigin.ts b/react-app/src/utils/formOrigin.ts index cac985fa25..a3cf69de40 100644 --- a/react-app/src/utils/formOrigin.ts +++ b/react-app/src/utils/formOrigin.ts @@ -28,14 +28,11 @@ type GetFormOrigin = (args?: GetFormOriginArgs) => { * Instead, call within functions */ export const getFormOrigin: GetFormOrigin = ({ id, authority } = {}) => { - const origin = - new URLSearchParams(window.location.search).get(ORIGIN) ?? DASHBOARD_ORIGIN; + const origin = new URLSearchParams(window.location.search).get(ORIGIN) ?? DASHBOARD_ORIGIN; if (origin === DETAILS_ORIGIN && id && authority) { return { - pathname: `/${origin}/${encodeURIComponent( - authority, - )}/${encodeURIComponent(id)}`, + pathname: `/${origin}/${encodeURIComponent(authority)}/${encodeURIComponent(id)}`, }; } diff --git a/react-app/src/utils/location.ts b/react-app/src/utils/location.ts index bc7d7f578f..eaff8b9aab 100644 --- a/react-app/src/utils/location.ts +++ b/react-app/src/utils/location.ts @@ -1,5 +1,4 @@ export const isProd = - window.location.hostname === "mako.cms.gov" || - window.location.hostname === "onemac.cms.gov"; + window.location.hostname === "mako.cms.gov" || window.location.hostname === "onemac.cms.gov"; export const isFaqPage = window.location.pathname.startsWith("/faq"); diff --git a/react-app/src/utils/stateName.test.ts b/react-app/src/utils/stateName.test.ts index 63774431a2..d98db50bba 100644 --- a/react-app/src/utils/stateName.test.ts +++ b/react-app/src/utils/stateName.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect} from "vitest"; -import { convertStateAbbrToFullName } from "./stateNames"; +import { describe, it, expect } from "vitest"; +import { convertStateAbbrToFullName } from "./stateNames"; describe("convertStateAbbrToFullName", () => { it("should return the full state name for a valid abbreviation", () => { @@ -16,4 +16,4 @@ describe("convertStateAbbrToFullName", () => { it("should handle empty input gracefully", () => { expect(convertStateAbbrToFullName("")).toBe(""); }); -}); \ No newline at end of file +}); diff --git a/react-app/src/utils/stateNames.ts b/react-app/src/utils/stateNames.ts index 16662b68ca..255348864d 100644 --- a/react-app/src/utils/stateNames.ts +++ b/react-app/src/utils/stateNames.ts @@ -1,8 +1,7 @@ import { STATES } from "@/hooks"; type State = keyof typeof STATES; -const isStringAState = (supposedState: string): supposedState is State => - supposedState in STATES; +const isStringAState = (supposedState: string): supposedState is State => supposedState in STATES; export const convertStateAbbrToFullName = (input: string): string => { if (isStringAState(input)) { diff --git a/react-app/src/utils/test-helpers/uploadFiles.ts b/react-app/src/utils/test-helpers/uploadFiles.ts index c82d8930d6..fe6ee5da3f 100644 --- a/react-app/src/utils/test-helpers/uploadFiles.ts +++ b/react-app/src/utils/test-helpers/uploadFiles.ts @@ -11,10 +11,10 @@ type ExtractAttachmentKeys = : never : never : TSchema extends z.ZodObject // Handle direct ZodObject case - ? Shape["attachments"] extends z.ZodObject // Ensure attachments is a ZodObject - ? keyof AttachmentsShape // Extract the keys from attachments' shape - : never - : never; + ? Shape["attachments"] extends z.ZodObject // Ensure attachments is a ZodObject + ? keyof AttachmentsShape // Extract the keys from attachments' shape + : never + : never; export const uploadFiles = () => { type AttachmentKey = ExtractAttachmentKeys; @@ -24,10 +24,7 @@ export const uploadFiles = () => { type: "image/png", }); - await userEvent.upload( - screen.getByTestId(`${attachmentKey}-upload`), - EXAMPLE_FILE, - ); + await userEvent.upload(screen.getByTestId(`${attachmentKey}-upload`), EXAMPLE_FILE); return screen.getByTestId(`${attachmentKey}-label`); }; diff --git a/react-app/src/utils/textHelpers.test.ts b/react-app/src/utils/textHelpers.test.ts index a2f15efb6d..838790f3b9 100644 --- a/react-app/src/utils/textHelpers.test.ts +++ b/react-app/src/utils/textHelpers.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect } from 'vitest'; -import { removeUnderscoresAndCapitalize, convertCamelCaseToWords } from './textHelpers'; +import { describe, it, expect } from "vitest"; +import { removeUnderscoresAndCapitalize, convertCamelCaseToWords } from "./textHelpers"; -describe('removeUnderscoresAndCapitalize', () => { - it('should replace underscores with spaces and capitalize each word', () => { +describe("removeUnderscoresAndCapitalize", () => { + it("should replace underscores with spaces and capitalize each word", () => { expect(removeUnderscoresAndCapitalize("hello_world")).toBe("Hello World"); expect(removeUnderscoresAndCapitalize("remove_this_string")).toBe("Remove This String"); }); @@ -12,28 +12,28 @@ describe('removeUnderscoresAndCapitalize', () => { expect(removeUnderscoresAndCapitalize("cat_and_dogs")).toBe("Cat And Dog"); }); - it('should handle empty strings', () => { + it("should handle empty strings", () => { expect(removeUnderscoresAndCapitalize("")).toBe(""); }); - it('should handle strings without underscores', () => { + it("should handle strings without underscores", () => { expect(removeUnderscoresAndCapitalize("hello")).toBe("Hello"); expect(removeUnderscoresAndCapitalize("test")).toBe("Test"); }); }); -describe('convertCamelCaseToWords', () => { - it('should convert camel case to words', () => { +describe("convertCamelCaseToWords", () => { + it("should convert camel case to words", () => { expect(convertCamelCaseToWords("helloWorld")).toBe("Hello World"); expect(convertCamelCaseToWords("convertCamelCaseToWords")).toBe("Convert Camel Case To Words"); }); - it('should handle leading uppercase letters', () => { + it("should handle leading uppercase letters", () => { expect(convertCamelCaseToWords("CamelCase")).toBe("Camel Case"); expect(convertCamelCaseToWords("HelloWorld")).toBe("Hello World"); }); - it('should handle no camel case', () => { + it("should handle no camel case", () => { expect(convertCamelCaseToWords("justwords")).toBe("Justwords"); }); }); diff --git a/react-app/src/utils/textHelpers.ts b/react-app/src/utils/textHelpers.ts index dd42e0c953..920248ccd7 100644 --- a/react-app/src/utils/textHelpers.ts +++ b/react-app/src/utils/textHelpers.ts @@ -1,8 +1,6 @@ import { Authority } from "shared-types/authority"; -export function removeUnderscoresAndCapitalize( - str: Authority | string, -): string { +export function removeUnderscoresAndCapitalize(str: Authority | string): string { // Replace underscores with spaces const withoutUnderscores = str.replace(/_/g, " "); diff --git a/react-app/src/utils/user.test.ts b/react-app/src/utils/user.test.ts index 5dafd8867c..321951146d 100644 --- a/react-app/src/utils/user.test.ts +++ b/react-app/src/utils/user.test.ts @@ -60,9 +60,7 @@ describe("getUserStateCodes", () => { it("should return the state codes for a state user", () => { vi.mocked(isCmsUser).mockReturnValue(false); vi.mocked(isStateUser).mockReturnValue(true); - const result = getUserStateCodes( - stateSubmitterUser as CognitoUserAttributes, - ); + const result = getUserStateCodes(stateSubmitterUser as CognitoUserAttributes); expect(result).toEqual(["CA"]); }); }); @@ -84,17 +82,13 @@ describe("isAuthorizedState", () => { }); it("should throw an error if no cognito attributes are found", async () => { - const consoleErrorSpy = vi - .spyOn(console, "error") - .mockImplementation(() => {}); + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); vi.mocked(getUser).mockResolvedValue({ user: null }); const result = await isAuthorizedState("CA-1234.R00.00"); expect(result).toBe(false); - expect(consoleErrorSpy).toHaveBeenCalledWith( - new Error("No cognito attributes found."), - ); + expect(consoleErrorSpy).toHaveBeenCalledWith(new Error("No cognito attributes found.")); consoleErrorSpy.mockRestore(); }); }); diff --git a/react-app/src/utils/user.ts b/react-app/src/utils/user.ts index 82e205cb4f..185c7f9d65 100644 --- a/react-app/src/utils/user.ts +++ b/react-app/src/utils/user.ts @@ -3,23 +3,16 @@ import { type CognitoUserAttributes } from "shared-types/user"; import { isCmsUser, isStateUser } from "shared-utils"; import { getUser } from "@/api"; -export const getUserStateCodes = ( - user: CognitoUserAttributes | null | undefined, -): StateCode[] => { +export const getUserStateCodes = (user: CognitoUserAttributes | null | undefined): StateCode[] => { // We always need a user, and state users always need a custom:state value - if (!user || (isStateUser(user) && user["custom:state"] === undefined)) - return []; - return isCmsUser(user) - ? [...STATE_CODES] - : (user["custom:state"]!.split(",") as StateCode[]); + if (!user || (isStateUser(user) && user["custom:state"] === undefined)) return []; + return isCmsUser(user) ? [...STATE_CODES] : (user["custom:state"]!.split(",") as StateCode[]); }; export const isAuthorizedState = async (id: string) => { try { const user = await getUser(); if (!user.user) throw Error("No cognito attributes found."); - return getUserStateCodes(user.user).includes( - id.substring(0, 2) as StateCode, - ); + return getUserStateCodes(user.user).includes(id.substring(0, 2) as StateCode); } catch (e) { console.error(e); return false; diff --git a/react-app/src/utils/zod.test.ts b/react-app/src/utils/zod.test.ts index c74b2b58c0..0c26371510 100644 --- a/react-app/src/utils/zod.test.ts +++ b/react-app/src/utils/zod.test.ts @@ -325,9 +325,8 @@ describe("zAmendmentOriginalWaiverNumberSchema", () => { }); it("fails if the waiver number is not approved", async () => { - const result = await zAmendmentOriginalWaiverNumberSchema.safeParseAsync( - EXISTING_ITEM_PENDING_ID, - ); + const result = + await zAmendmentOriginalWaiverNumberSchema.safeParseAsync(EXISTING_ITEM_PENDING_ID); expect(result.success).toBe(false); expect(result.error.errors[0].message).toBe( "According to our records, this 1915(b) Waiver Number is not approved. You must supply an approved 1915(b) Initial or Renewal Waiver Number.", @@ -378,9 +377,8 @@ describe("zRenewalOriginalWaiverNumberSchema", () => { }); it("fails if the waiver number is not approved", async () => { - const result = await zRenewalOriginalWaiverNumberSchema.safeParseAsync( - EXISTING_ITEM_PENDING_ID, - ); + const result = + await zRenewalOriginalWaiverNumberSchema.safeParseAsync(EXISTING_ITEM_PENDING_ID); expect(result.success).toBe(false); expect(result.error.errors[0].message).toBe( "According to our records, this 1915(b) Waiver Number is not approved. You must supply an approved 1915(b) Initial or Renewal Waiver Number.", @@ -475,9 +473,8 @@ describe("zExtensionOriginalWaiverNumberSchema", () => { }); it("fails if the waiver number is not approved", async () => { - const result = await zExtensionOriginalWaiverNumberSchema.safeParseAsync( - EXISTING_ITEM_PENDING_ID, - ); + const result = + await zExtensionOriginalWaiverNumberSchema.safeParseAsync(EXISTING_ITEM_PENDING_ID); expect(result.success).toBe(false); expect(result.error.errors[0].message).toBe( "According to our records, this Approved Initial or Renewal Waiver Number is not approved. You must supply an approved Initial or Renewal Waiver Number.", diff --git a/react-app/src/utils/zod.ts b/react-app/src/utils/zod.ts index b3eee5d8c3..d27bee56e8 100644 --- a/react-app/src/utils/zod.ts +++ b/react-app/src/utils/zod.ts @@ -32,11 +32,9 @@ export const zAttachmentRequired = ({ max?: number; message?: string; }) => - z - .array(z.instanceof(File)) - .refine((value) => value.length >= min && value.length <= max, { - message: message, - }); + z.array(z.instanceof(File)).refine((value) => value.length >= min && value.length <= max, { + message: message, + }); export const zAdditionalInfoOptional = z .string() diff --git a/react-app/src/zodIdValidator.ts b/react-app/src/zodIdValidator.ts index 51334fb3bd..9a83577a7b 100644 --- a/react-app/src/zodIdValidator.ts +++ b/react-app/src/zodIdValidator.ts @@ -1,9 +1,6 @@ import { z, SuperRefinement } from "zod"; -export const validId = ( - idRegex: RegExp, - message: string, -): SuperRefinement => { +export const validId = (idRegex: RegExp, message: string): SuperRefinement => { const correctFormatSchema = z.string().regex(idRegex, { message, }); diff --git a/test/e2e/pages/faq.page.ts b/test/e2e/pages/faq.page.ts index d37862647d..2e9eac87f5 100644 --- a/test/e2e/pages/faq.page.ts +++ b/test/e2e/pages/faq.page.ts @@ -46,7 +46,7 @@ export class FAQPage { readonly withdrawWaiverRai: Locator; readonly withdrawPackageWaiver: Locator; - constructor(page:Page) { + constructor(page: Page) { this.page = page; this.header = page.getByTestId("sub-nav-header"); @@ -94,6 +94,6 @@ export class FAQPage { // HREFs of PDFs this.pdfs = { statePlans: page.locator('a[href*="state-plan-macpro"]'), - } + }; } -} \ No newline at end of file +} diff --git a/test/e2e/pages/home.page.ts b/test/e2e/pages/home.page.ts index 6df99930bb..34da5ffce3 100644 --- a/test/e2e/pages/home.page.ts +++ b/test/e2e/pages/home.page.ts @@ -1,4 +1,4 @@ -import { type Locator, type Page } from '@playwright/test'; +import { type Locator, type Page } from "@playwright/test"; export class HomePage { readonly page: Page; @@ -11,16 +11,16 @@ export class HomePage { // the desktop object is used to group like selectors this.desktop = { - usaBanner: page.getByTestId('usa-statement-d'), - usaBannerBtn: page.getByTestId('usa-expand-btn-d'), - homeLink: page.getByTestId('Home-d'), - faqLink: page.getByTestId('FAQ-d'), - signInBtn: page.getByTestId('sign-in-button-d'), - registerBtn: page.getByTestId('register-button-d'), - newBetterBtn: page.getByTestId('new-better-btn') + usaBanner: page.getByTestId("usa-statement-d"), + usaBannerBtn: page.getByTestId("usa-expand-btn-d"), + homeLink: page.getByTestId("Home-d"), + faqLink: page.getByTestId("FAQ-d"), + signInBtn: page.getByTestId("sign-in-button-d"), + registerBtn: page.getByTestId("register-button-d"), + newBetterBtn: page.getByTestId("new-better-btn"), }; - this.officialUsage = page.getByTestId('official-usage'); - this.secureUsage = page.getByTestId('secure-usage'); + this.officialUsage = page.getByTestId("official-usage"); + this.secureUsage = page.getByTestId("secure-usage"); } -} \ No newline at end of file +} diff --git a/test/e2e/pages/index.ts b/test/e2e/pages/index.ts index 5f5e4f0396..856b77b399 100644 --- a/test/e2e/pages/index.ts +++ b/test/e2e/pages/index.ts @@ -1,2 +1,2 @@ export * from "./loginPage"; -export * from "./home.page"; \ No newline at end of file +export * from "./home.page"; diff --git a/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts index e92a94de57..e0c389d382 100644 --- a/test/e2e/playwright.config.ts +++ b/test/e2e/playwright.config.ts @@ -9,9 +9,7 @@ const baseURL = process.env.STAGE_NAME ( await new SSMClient({ region: "us-east-1" }).send( new GetParameterCommand({ - Name: `/${process.env.PROJECT}/${ - process.env.STAGE_NAME || "main" - }/deployment-output`, + Name: `/${process.env.PROJECT}/${process.env.STAGE_NAME || "main"}/deployment-output`, }), ) ).Parameter!.Value!, diff --git a/test/e2e/selectors/components/AddIssueForm/index.ts b/test/e2e/selectors/components/AddIssueForm/index.ts index e7feba41f7..de3960d992 100644 --- a/test/e2e/selectors/components/AddIssueForm/index.ts +++ b/test/e2e/selectors/components/AddIssueForm/index.ts @@ -8,11 +8,11 @@ export class AddIssueFormSelectors { } get addButton() { - return this.page.locator("_react=Button[buttonText=\"Add\"]"); + return this.page.locator('_react=Button[buttonText="Add"]'); } get submitButton() { - return this.page.locator("_react=Button[buttonText=\"Submit\"]"); + return this.page.locator('_react=Button[buttonText="Submit"]'); } get titleInput() { diff --git a/test/e2e/selectors/navigation/index.ts b/test/e2e/selectors/navigation/index.ts index cb98c48358..2cfe8b193a 100644 --- a/test/e2e/selectors/navigation/index.ts +++ b/test/e2e/selectors/navigation/index.ts @@ -8,12 +8,10 @@ export class NavSelectors { } get issuesDropDown() { - return this.page.locator( - "_react=NavSection[section.buttonText = \"Issues\"]" - ); + return this.page.locator('_react=NavSection[section.buttonText = "Issues"]'); } get allIssuesLink() { - return this.page.locator("_react=Link[text = \"All Issues\"]"); + return this.page.locator('_react=Link[text = "All Issues"]'); } } diff --git a/test/e2e/tests/FAQs/faq.spec.ts b/test/e2e/tests/FAQs/faq.spec.ts index 73cf597de9..b50877a83d 100644 --- a/test/e2e/tests/FAQs/faq.spec.ts +++ b/test/e2e/tests/FAQs/faq.spec.ts @@ -1,66 +1,74 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; -import { FAQPage } from 'pages/faq.page'; +import { FAQPage } from "pages/faq.page"; let faqPage: FAQPage; -test.describe('FAQ page', {tag: ["@e2e", "@smoke", "@faq"]}, () => { +test.describe("FAQ page", { tag: ["@e2e", "@smoke", "@faq"] }, () => { test.beforeEach(async ({ page }) => { faqPage = new FAQPage(page); - await page.goto('/faq'); + await page.goto("/faq"); }); - test.describe("UI validation", {tag: ["@CI"]}, () => { + test.describe("UI validation", { tag: ["@CI"] }, () => { test.describe("header", () => { - test("displays header", async() => { + test("displays header", async () => { await expect(faqPage.header).toBeVisible(); await expect(faqPage.header).toHaveText("Frequently Asked Questions"); }); }); - + test.describe("General section", () => { - test("displays system for state submission FAQ", async() => { + test("displays system for state submission FAQ", async () => { await expect(faqPage.crossWalk).toBeVisible(); - await expect(faqPage.crossWalk).toHaveText("Which system should I use for my state’s submission?"); - + await expect(faqPage.crossWalk).toHaveText( + "Which system should I use for my state’s submission?", + ); + await expect(faqPage.pdfs.statePlans).not.toBeVisible(); }); - test("displays browser type FAQ", async() => { + test("displays browser type FAQ", async () => { await expect(faqPage.browsers).toBeVisible(); await expect(faqPage.browsers).toHaveText("What browsers can I use to access the system?"); - + await expect(faqPage.browsers.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays confirmation email FAQ", async() => { + test("displays confirmation email FAQ", async () => { await expect(faqPage.confirmEmail).toBeVisible(); - await expect(faqPage.confirmEmail).toHaveText("What should we do if we don’t receive a confirmation email?"); - - await expect(faqPage.confirmEmail.locator("div:nth-child(1)")).not.toBeVisible() + await expect(faqPage.confirmEmail).toHaveText( + "What should we do if we don’t receive a confirmation email?", + ); + + await expect(faqPage.confirmEmail.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays official submission FAQ", async() => { + test("displays official submission FAQ", async () => { await expect(faqPage.official).toBeVisible(); - await expect(faqPage.official).toHaveText("Is this considered the official state submission?"); + await expect(faqPage.official).toHaveText( + "Is this considered the official state submission?", + ); await expect(faqPage.official.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays OneMac user roles FAQ", async() => { + test("displays OneMac user roles FAQ", async () => { await expect(faqPage.onemacRoles).toBeVisible(); await expect(faqPage.onemacRoles).toHaveText("What are the OneMAC user roles?"); await expect(faqPage.onemacRoles.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays file format FAQ", async() => { + test("displays file format FAQ", async () => { await expect(faqPage.fileFormats).toBeVisible(); - await expect(faqPage.fileFormats).toHaveText("What are the kinds of file formats I can upload into OneMAC"); + await expect(faqPage.fileFormats).toHaveText( + "What are the kinds of file formats I can upload into OneMAC", + ); await expect(faqPage.fileFormats.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays onboarding materials FAQ", async() => { + test("displays onboarding materials FAQ", async () => { await expect(faqPage.onboardingMaterials).toBeVisible(); await expect(faqPage.onboardingMaterials).toHaveText("Onboarding Materials"); @@ -69,223 +77,285 @@ test.describe('FAQ page', {tag: ["@e2e", "@smoke", "@faq"]}, () => { }); test.describe("State Plan Amendments (SPAs)", () => { - test("displays format used to enter a SPA ID FAQ", async() => { + test("displays format used to enter a SPA ID FAQ", async () => { await expect(faqPage.spaIdFormat).toBeVisible(); await expect(faqPage.spaIdFormat).toHaveText("What format is used to enter a SPA ID?"); - + await expect(faqPage.spaIdFormat.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays Medicaid SPA attachments FAQ", async() => { + test("displays Medicaid SPA attachments FAQ", async () => { await expect(faqPage.medicaidSpaAttachments).toBeVisible(); - await expect(faqPage.medicaidSpaAttachments).toHaveText("What are the attachments for a Medicaid SPA?"); - + await expect(faqPage.medicaidSpaAttachments).toHaveText( + "What are the attachments for a Medicaid SPA?", + ); + await expect(faqPage.medicaidSpaAttachments.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays attachments response to Medicaid RAI FAQ", async() => { + test("displays attachments response to Medicaid RAI FAQ", async () => { await expect(faqPage.medicaidSpaRai).toBeVisible(); - await expect(faqPage.medicaidSpaRai).toHaveText("What are the attachments for a Medicaid response to Request for Additional Information (RAI)?"); - + await expect(faqPage.medicaidSpaRai).toHaveText( + "What are the attachments for a Medicaid response to Request for Additional Information (RAI)?", + ); + await expect(faqPage.medicaidSpaRai.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays CHIP SPA attachments FAQ", async() => { + test("displays CHIP SPA attachments FAQ", async () => { await expect(faqPage.chipSpaAttachments).toBeVisible(); - await expect(faqPage.chipSpaAttachments).toHaveText("What are the attachments for a CHIP SPA?"); - + await expect(faqPage.chipSpaAttachments).toHaveText( + "What are the attachments for a CHIP SPA?", + ); + await expect(faqPage.chipSpaAttachments.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays attachments response to CHIP RAI FAQ", async() => { + test("displays attachments response to CHIP RAI FAQ", async () => { await expect(faqPage.chipSpaRai).toBeVisible(); - await expect(faqPage.chipSpaRai).toHaveText("What are the attachments for a CHIP SPA response to Request for Additional Information (RAI)?"); - + await expect(faqPage.chipSpaRai).toHaveText( + "What are the attachments for a CHIP SPA response to Request for Additional Information (RAI)?", + ); + await expect(faqPage.chipSpaRai.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays PHE FAQ", async() => { + test("displays PHE FAQ", async () => { await expect(faqPage.publicHealthEmergency).toBeVisible(); - await expect(faqPage.publicHealthEmergency).toHaveText("Can I submit SPAs relating to the Public Health Emergency (PHE) in OneMAC?"); - + await expect(faqPage.publicHealthEmergency).toHaveText( + "Can I submit SPAs relating to the Public Health Emergency (PHE) in OneMAC?", + ); + await expect(faqPage.publicHealthEmergency.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays withdraw formal RAI for Medicaid SPA FAQ", async() => { + test("displays withdraw formal RAI for Medicaid SPA FAQ", async () => { await expect(faqPage.withdrawSpaRai).toBeVisible(); - await expect(faqPage.withdrawSpaRai).toHaveText("How do I Withdraw a Formal RAI Response for a Medicaid SPA?"); - + await expect(faqPage.withdrawSpaRai).toHaveText( + "How do I Withdraw a Formal RAI Response for a Medicaid SPA?", + ); + await expect(faqPage.withdrawSpaRai.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays withdraw package for Medicaid SPA FAQ", async() => { + test("displays withdraw package for Medicaid SPA FAQ", async () => { await expect(faqPage.withdrawPackageSpa).toBeVisible(); - await expect(faqPage.withdrawPackageSpa).toHaveText("How do I Withdraw a Package for a Medicaid SPA?"); - + await expect(faqPage.withdrawPackageSpa).toHaveText( + "How do I Withdraw a Package for a Medicaid SPA?", + ); + await expect(faqPage.withdrawPackageSpa.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays withdraw formal RAI for CHIP SPA FAQ", async() => { + test("displays withdraw formal RAI for CHIP SPA FAQ", async () => { await expect(faqPage.withdrawChipSpaRai).toBeVisible(); - await expect(faqPage.withdrawChipSpaRai).toHaveText("How do I Withdraw a Formal RAI Response for a CHIP SPA?"); - + await expect(faqPage.withdrawChipSpaRai).toHaveText( + "How do I Withdraw a Formal RAI Response for a CHIP SPA?", + ); + await expect(faqPage.withdrawChipSpaRai.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays withdraw package for CHIP SPA FAQ", async() => { + test("displays withdraw package for CHIP SPA FAQ", async () => { await expect(faqPage.withdrawPackageChipSpa).toBeVisible(); - await expect(faqPage.withdrawPackageChipSpa).toHaveText("How do I Withdraw a Package for a CHIP SPA?"); - + await expect(faqPage.withdrawPackageChipSpa).toHaveText( + "How do I Withdraw a Package for a CHIP SPA?", + ); + await expect(faqPage.withdrawPackageChipSpa.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays download ABP SPA templates FAQ", async() => { + test("displays download ABP SPA templates FAQ", async () => { await expect(faqPage.abpSpaTemplates).toBeVisible(); - await expect(faqPage.abpSpaTemplates).toHaveText("Where can I download Medicaid Alternative Benefit Plan (ABP) SPA templates?"); - + await expect(faqPage.abpSpaTemplates).toHaveText( + "Where can I download Medicaid Alternative Benefit Plan (ABP) SPA templates?", + ); + await expect(faqPage.abpSpaTemplates.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays download ABP SPA implementation guides FAQ", async() => { + test("displays download ABP SPA implementation guides FAQ", async () => { await expect(faqPage.abpSpaGuides).toBeVisible(); - await expect(faqPage.abpSpaGuides).toHaveText("Where can I download Medicaid Alternative Benefit Plan (ABP) SPA implementation guides?"); - + await expect(faqPage.abpSpaGuides).toHaveText( + "Where can I download Medicaid Alternative Benefit Plan (ABP) SPA implementation guides?", + ); + await expect(faqPage.abpSpaGuides.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays download MPC SPA templates FAQ", async() => { + test("displays download MPC SPA templates FAQ", async () => { await expect(faqPage.mpcSpaTemplates).toBeVisible(); - await expect(faqPage.mpcSpaTemplates).toHaveText("Where can I download Medicaid Premiums and Cost Sharing (MPC) SPA templates?"); - + await expect(faqPage.mpcSpaTemplates).toHaveText( + "Where can I download Medicaid Premiums and Cost Sharing (MPC) SPA templates?", + ); + await expect(faqPage.mpcSpaTemplates.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays download MPC SPA implementation guides FAQ", async() => { + test("displays download MPC SPA implementation guides FAQ", async () => { await expect(faqPage.mpcSpaGuides).toBeVisible(); - await expect(faqPage.mpcSpaGuides).toHaveText("Where can I download Medicaid Premiums and Cost Sharing (MPC) SPA implementation guides?"); - + await expect(faqPage.mpcSpaGuides).toHaveText( + "Where can I download Medicaid Premiums and Cost Sharing (MPC) SPA implementation guides?", + ); + await expect(faqPage.mpcSpaGuides.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays download CHIP eligibility SPA templates FAQ", async() => { + test("displays download CHIP eligibility SPA templates FAQ", async () => { await expect(faqPage.chipSpaTemplates).toBeVisible(); - await expect(faqPage.chipSpaTemplates).toHaveText("Where can I download CHIP eligibility SPA templates?"); - + await expect(faqPage.chipSpaTemplates).toHaveText( + "Where can I download CHIP eligibility SPA templates?", + ); + await expect(faqPage.chipSpaTemplates.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays download CHIP eligibility SPA implementation guides FAQ", async() => { + test("displays download CHIP eligibility SPA implementation guides FAQ", async () => { await expect(faqPage.chipSpaGuides).toBeVisible(); - await expect(faqPage.chipSpaGuides).toHaveText("Where can I download CHIP eligibility SPA implementation guides?"); - + await expect(faqPage.chipSpaGuides).toHaveText( + "Where can I download CHIP eligibility SPA implementation guides?", + ); + await expect(faqPage.chipSpaGuides.locator("div:nth-child(1)")).not.toBeVisible(); }); }); test.describe("Waivers Section", () => { - test("displays 1915(b) initial waiver number FAQ", async() => { + test("displays 1915(b) initial waiver number FAQ", async () => { await expect(faqPage.waiverIdFormat).toBeVisible(); - await expect(faqPage.waiverIdFormat).toHaveText("What format is used to enter a 1915(b) Initial Waiver number?"); - + await expect(faqPage.waiverIdFormat).toHaveText( + "What format is used to enter a 1915(b) Initial Waiver number?", + ); + await expect(faqPage.waiverIdFormat.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays 1915(b) waiver renewal number FAQ", async() => { + test("displays 1915(b) waiver renewal number FAQ", async () => { await expect(faqPage.waiverRenewalIdFormat).toBeVisible(); - await expect(faqPage.waiverRenewalIdFormat).toHaveText("What format is used to enter a 1915(b) Waiver Renewal number?"); + await expect(faqPage.waiverRenewalIdFormat).toHaveText( + "What format is used to enter a 1915(b) Waiver Renewal number?", + ); await expect(faqPage.waiverRenewalIdFormat.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays 1915(b) waiver amendment number FAQ", async() => { + test("displays 1915(b) waiver amendment number FAQ", async () => { await expect(faqPage.waiverAmendmentIdFormat).toBeVisible(); - await expect(faqPage.waiverAmendmentIdFormat).toHaveText("What format is used to enter a 1915(b) Waiver Amendment number?"); + await expect(faqPage.waiverAmendmentIdFormat).toHaveText( + "What format is used to enter a 1915(b) Waiver Amendment number?", + ); await expect(faqPage.waiverAmendmentIdFormat.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays contact help for 1915(b) waiver FAQ", async() => { + test("displays contact help for 1915(b) waiver FAQ", async () => { await expect(faqPage.waiverIdHelp).toBeVisible(); - await expect(faqPage.waiverIdHelp).toHaveText("Who can I contact to help me figure out the correct 1915(b) Waiver Number?"); + await expect(faqPage.waiverIdHelp).toHaveText( + "Who can I contact to help me figure out the correct 1915(b) Waiver Number?", + ); await expect(faqPage.waiverIdHelp.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays format 1915(c) waiver number FAQ", async() => { + test("displays format 1915(c) waiver number FAQ", async () => { await expect(faqPage.waiverCId).toBeVisible(); - await expect(faqPage.waiverCId).toHaveText("What format is used to enter a 1915(c) waiver number?"); + await expect(faqPage.waiverCId).toHaveText( + "What format is used to enter a 1915(c) waiver number?", + ); await expect(faqPage.waiverCId.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays attachments are needed for 1915(b) waiver action FAQ", async() => { + test("displays attachments are needed for 1915(b) waiver action FAQ", async () => { await expect(faqPage.waiverBAttachments).toBeVisible(); - await expect(faqPage.waiverBAttachments).toHaveText("What attachments are needed to submit a 1915(b) waiver action?"); + await expect(faqPage.waiverBAttachments).toHaveText( + "What attachments are needed to submit a 1915(b) waiver action?", + ); await expect(faqPage.waiverBAttachments.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays attachments 1915b and c App K RAI response FAQ", async() => { + test("displays attachments 1915b and c App K RAI response FAQ", async () => { await expect(faqPage.waiverBRaiAttachments).toBeVisible(); - await expect(faqPage.waiverBRaiAttachments).toHaveText("What are the attachments for a 1915(b) Waiver and 1915(c) Appendix K response to Request for Additional Information (RAI)?"); + await expect(faqPage.waiverBRaiAttachments).toHaveText( + "What are the attachments for a 1915(b) Waiver and 1915(c) Appendix K response to Request for Additional Information (RAI)?", + ); await expect(faqPage.waiverBRaiAttachments.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays temporary extension format FAQ", async() => { + test("displays temporary extension format FAQ", async () => { await expect(faqPage.waiverExtensionIdFormat).toBeVisible(); - await expect(faqPage.waiverExtensionIdFormat).toHaveText("What format is used to enter a 1915(b) or 1915(c) Temporary Extension number?"); + await expect(faqPage.waiverExtensionIdFormat).toHaveText( + "What format is used to enter a 1915(b) or 1915(c) Temporary Extension number?", + ); await expect(faqPage.waiverExtensionIdFormat.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays status of my temporary extension FAQ", async() => { + test("displays status of my temporary extension FAQ", async () => { await expect(faqPage.waiverExtensionStatus).toBeVisible(); - await expect(faqPage.waiverExtensionStatus).toHaveText("Why does the status of my Temporary Extension Request continue to show as 'Submitted'?"); + await expect(faqPage.waiverExtensionStatus).toHaveText( + "Why does the status of my Temporary Extension Request continue to show as 'Submitted'?", + ); await expect(faqPage.waiverExtensionStatus.locator("div:nth-child(1)")).not.toBeVisible(); }); // remove skip when selector is updated in application - test.skip("displays attachments for 1915(b) waiver FAQ", async() => { + test.skip("displays attachments for 1915(b) waiver FAQ", async () => { await expect(faqPage.tempExtensionBAttachments).toBeVisible(); - await expect(faqPage.tempExtensionBAttachments).toHaveText("What are the attachments for a 1915(b) Waiver - Request for Temporary Extension?"); + await expect(faqPage.tempExtensionBAttachments).toHaveText( + "What are the attachments for a 1915(b) Waiver - Request for Temporary Extension?", + ); - await expect(faqPage.tempExtensionBAttachments.locator("div:nth-child(1)")).not.toBeVisible(); + await expect( + faqPage.tempExtensionBAttachments.locator("div:nth-child(1)"), + ).not.toBeVisible(); }); // remove skip when selector is updated in application - test.skip("displays attachments for 1915(c) waiver FAQ", async() => { + test.skip("displays attachments for 1915(c) waiver FAQ", async () => { await expect(faqPage.tempExtensionCAttachments).toBeVisible(); - await expect(faqPage.tempExtensionCAttachments).toHaveText("What are the attachments for a 1915(c) Waiver - Request for Temporary Extension?"); + await expect(faqPage.tempExtensionCAttachments).toHaveText( + "What are the attachments for a 1915(c) Waiver - Request for Temporary Extension?", + ); - await expect(faqPage.tempExtensionCAttachments.locator("div:nth-child(1)")).not.toBeVisible(); + await expect( + faqPage.tempExtensionCAttachments.locator("div:nth-child(1)"), + ).not.toBeVisible(); }); - test("displays submit App K attachments FAQ", async() => { + test("displays submit App K attachments FAQ", async () => { await expect(faqPage.appK).toBeVisible(); await expect(faqPage.appK).toHaveText("Can I submit Appendix K amendments in OneMAC?"); await expect(faqPage.appK.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays attachments for 1915(c) Appendix K waiver FAQ", async() => { + test("displays attachments for 1915(c) Appendix K waiver FAQ", async () => { await expect(faqPage.appKAttachments).toBeVisible(); - await expect(faqPage.appKAttachments).toHaveText("What are the attachments for a 1915(c) Appendix K Waiver?"); + await expect(faqPage.appKAttachments).toHaveText( + "What are the attachments for a 1915(c) Appendix K Waiver?", + ); await expect(faqPage.appKAttachments.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays withdraw Formal RAI Response for Medicaid Waiver FAQ", async() => { + test("displays withdraw Formal RAI Response for Medicaid Waiver FAQ", async () => { await expect(faqPage.withdrawWaiverRai).toBeVisible(); - await expect(faqPage.withdrawWaiverRai).toHaveText("How do I Withdraw a Formal RAI Response for a Medicaid Waiver?"); + await expect(faqPage.withdrawWaiverRai).toHaveText( + "How do I Withdraw a Formal RAI Response for a Medicaid Waiver?", + ); await expect(faqPage.withdrawWaiverRai.locator("div:nth-child(1)")).not.toBeVisible(); }); - test("displays withdraw Package for Waiver FAQ", async() => { + test("displays withdraw Package for Waiver FAQ", async () => { await expect(faqPage.withdrawPackageWaiver).toBeVisible(); - await expect(faqPage.withdrawPackageWaiver).toHaveText("How do I Withdraw a Package for a Waiver?"); + await expect(faqPage.withdrawPackageWaiver).toHaveText( + "How do I Withdraw a Package for a Waiver?", + ); await expect(faqPage.withdrawPackageWaiver.locator("div:nth-child(1)")).not.toBeVisible(); }); @@ -294,42 +364,42 @@ test.describe('FAQ page', {tag: ["@e2e", "@smoke", "@faq"]}, () => { test.describe("Interaction validation", () => { test.describe("General Section", () => { - test("should display crosswalk system FAQ response", async() => { + test("should display crosswalk system FAQ response", async () => { await faqPage.crossWalk.click(); await expect(faqPage.pdfs.statePlans).toBeVisible(); }); - - test("should display browser FAQ response", async() => { + + test("should display browser FAQ response", async () => { await faqPage.browsers.click(); await expect(faqPage.browsers.locator("div:nth-child(1)")).toBeVisible(); - // await expect(faqPage.browsers.locator("div:nth-child(1)")).toHaveText(""); Something TODO + // await expect(faqPage.browsers.locator("div:nth-child(1)")).toHaveText(""); Something TODO }); - - test("should display confirmation email FAQ response", async() => { + + test("should display confirmation email FAQ response", async () => { await faqPage.confirmEmail.click(); await expect(faqPage.confirmEmail.locator("div:nth-child(1)")).toBeVisible(); - // await expect(faqPage.confirmEmail.locator("div:nth-child(1)")).toHaveText(""); // Something TODO + // await expect(faqPage.confirmEmail.locator("div:nth-child(1)")).toHaveText(""); // Something TODO }); - - test("should display official submission FAQ response", async() => { + + test("should display official submission FAQ response", async () => { await faqPage.official.click(); await expect(faqPage.official.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.official.locator("div:nth-child(1)")).toHaveText(""); TODO }); - - test("should diplay OneMac User role FAQ response", async() => { + + test("should diplay OneMac User role FAQ response", async () => { await faqPage.onemacRoles.click(); await expect(faqPage.onemacRoles.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.onemacRoles.locator("div:nth-child(1)")).toHaveText(""); TODO }); - - test("should display file formats FAQ response", async() => { + + test("should display file formats FAQ response", async () => { await faqPage.fileFormats.click(); await expect(faqPage.fileFormats.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.fileFormats.locator("div:nth-child(1)")).toHaveText(""); TODO }); - - test("should display the list on onboarding materials FAQ response", async() => { + + test("should display the list on onboarding materials FAQ response", async () => { await faqPage.onboardingMaterials.click(); await expect(faqPage.onboardingMaterials.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.onboardingMaterials.locator("div:nth-child(1)")).toHaveText(""); TODO @@ -338,178 +408,180 @@ test.describe('FAQ page', {tag: ["@e2e", "@smoke", "@faq"]}, () => { }); test.describe("State Plan Amendments (SPAs) Section", () => { - test("should display format used to enter a SPA ID FAQ response", async() => { + test("should display format used to enter a SPA ID FAQ response", async () => { await faqPage.spaIdFormat.click(); await expect(faqPage.spaIdFormat.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display Medicaid SPA attachments FAQ response", async() => { + test("should display Medicaid SPA attachments FAQ response", async () => { await faqPage.medicaidSpaAttachments.click(); await expect(faqPage.medicaidSpaAttachments.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display attachments response to Medicaid RAI FAQ response", async() => { + test("should display attachments response to Medicaid RAI FAQ response", async () => { await faqPage.medicaidSpaRai.click(); await expect(faqPage.medicaidSpaRai.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display CHIP SPA attachments FAQ response", async() => { + test("should display CHIP SPA attachments FAQ response", async () => { await faqPage.chipSpaAttachments.click(); await expect(faqPage.chipSpaAttachments.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display attachments response to CHIP RAI FAQ response", async() => { + test("should display attachments response to CHIP RAI FAQ response", async () => { await faqPage.chipSpaRai.click(); await expect(faqPage.chipSpaRai.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display PHE FAQ response", async() => { + test("should display PHE FAQ response", async () => { await faqPage.publicHealthEmergency.click(); await expect(faqPage.publicHealthEmergency.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display withdraw formal RAI for Medicaid SPA FAQ response", async() => { + test("should display withdraw formal RAI for Medicaid SPA FAQ response", async () => { await faqPage.withdrawSpaRai.click(); await expect(faqPage.withdrawSpaRai.locator('div[data-state="open"]')).toBeVisible(); }); - test("should display withdraw package for Medicaid SPA FAQ response", async() => { + test("should display withdraw package for Medicaid SPA FAQ response", async () => { await faqPage.withdrawPackageSpa.click(); await expect(faqPage.withdrawPackageSpa.locator('div[data-state="open"]')).toBeVisible(); }); - test("should display withdraw formal RAI for CHIP SPA FAQ response", async() => { + test("should display withdraw formal RAI for CHIP SPA FAQ response", async () => { await faqPage.withdrawChipSpaRai.click(); await expect(faqPage.withdrawChipSpaRai.locator('div[data-state="open"]')).toBeVisible(); }); - test("should display withdraw package for CHIP SPA FAQ response", async() => { + test("should display withdraw package for CHIP SPA FAQ response", async () => { await faqPage.withdrawPackageChipSpa.click(); - await expect(faqPage.withdrawPackageChipSpa.locator('div[data-state="open"]')).toBeVisible(); + await expect( + faqPage.withdrawPackageChipSpa.locator('div[data-state="open"]'), + ).toBeVisible(); }); - test("should display download ABP SPA templates FAQ response", async() => { + test("should display download ABP SPA templates FAQ response", async () => { await faqPage.abpSpaTemplates.click(); await expect(faqPage.abpSpaTemplates.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display download ABP SPA implementation guides FAQ response", async() => { + test("should display download ABP SPA implementation guides FAQ response", async () => { await faqPage.abpSpaGuides.click(); await expect(faqPage.abpSpaGuides.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display download MPC SPA templates FAQ response", async() => { + test("should display download MPC SPA templates FAQ response", async () => { await faqPage.mpcSpaTemplates.click(); await expect(faqPage.mpcSpaTemplates.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display download MPC SPA implementation guides FAQ response", async() => { + test("should display download MPC SPA implementation guides FAQ response", async () => { await faqPage.mpcSpaGuides.click(); await expect(faqPage.mpcSpaGuides.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display download CHIP eligibility SPA templates FAQ response", async() => { + test("should display download CHIP eligibility SPA templates FAQ response", async () => { await faqPage.chipSpaTemplates.click(); await expect(faqPage.chipSpaTemplates.locator("div:nth-child(1)")).toBeVisible(); }); - test("should display download CHIP eligibility SPA implementation guides FAQ response", async() => { + test("should display download CHIP eligibility SPA implementation guides FAQ response", async () => { await faqPage.chipSpaGuides.click(); await expect(faqPage.chipSpaGuides.locator("div:nth-child(1)")).toBeVisible(); }); }); test.describe("Waivers Section", () => { - test("displays 1915(b) initial waiver number FAQ response", async() => { + test("displays 1915(b) initial waiver number FAQ response", async () => { await faqPage.waiverIdFormat.click(); await expect(faqPage.waiverIdFormat.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.waiverIdFormat.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays 1915(b) waiver renewal number FAQ response", async() => { + test("displays 1915(b) waiver renewal number FAQ response", async () => { await faqPage.waiverRenewalIdFormat.click(); await expect(faqPage.waiverRenewalIdFormat.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.waiverRenewalIdFormat.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays 1915(b) waiver amendment number FAQ response", async() => { + test("displays 1915(b) waiver amendment number FAQ response", async () => { await faqPage.waiverAmendmentIdFormat.click(); await expect(faqPage.waiverAmendmentIdFormat.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.waiverAmendmentIdFormat.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays contact help for 1915(b) waiver FAQ response", async() => { + test("displays contact help for 1915(b) waiver FAQ response", async () => { await faqPage.waiverIdHelp.click(); await expect(faqPage.waiverIdHelp.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.waiverIdHelp.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays format 1915(c) waiver number FAQ response", async() => { + test("displays format 1915(c) waiver number FAQ response", async () => { await faqPage.waiverCId.click(); await expect(faqPage.waiverCId.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.waiverCId.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays attachments are needed for 1915(b) waiver action FAQ response", async() => { + test("displays attachments are needed for 1915(b) waiver action FAQ response", async () => { await faqPage.waiverBAttachments.click(); await expect(faqPage.waiverBAttachments.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.waiverBAttachments.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays attachments 1915b and c App K RAI response FAQ response", async() => { + test("displays attachments 1915b and c App K RAI response FAQ response", async () => { await faqPage.waiverBRaiAttachments.click(); await expect(faqPage.waiverBRaiAttachments.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.waiverBRaiAttachments.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays temporary extension format FAQ response", async() => { + test("displays temporary extension format FAQ response", async () => { await faqPage.waiverExtensionIdFormat.click(); await expect(faqPage.waiverExtensionIdFormat.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.waiverExtensionIdFormat.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays status of my temporary extension FAQ response", async() => { + test("displays status of my temporary extension FAQ response", async () => { await faqPage.waiverExtensionStatus.click(); await expect(faqPage.waiverExtensionStatus.locator("div:nth-child(1)")).toBeVisible(); }); // remove skip when selector is updated in application - test.skip("displays attachments for 1915(b) waiver FAQ response", async() => { + test.skip("displays attachments for 1915(b) waiver FAQ response", async () => { await faqPage.tempExtensionBAttachments.click(); await expect(faqPage.tempExtensionBAttachments.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.tempExtensionBAttachments.locator("div:nth-child(1)")).toHaveText(""); TODO }); // remove skip when selector us updated in application - test.skip("displays attachments for 1915(c) waiver FAQ response", async() => { + test.skip("displays attachments for 1915(c) waiver FAQ response", async () => { await faqPage.tempExtensionCAttachments.click(); await expect(faqPage.tempExtensionCAttachments.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.tempExtensionCAttachments.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays submit App K attachments FAQ response", async() => { + test("displays submit App K attachments FAQ response", async () => { await faqPage.appK.click(); await expect(faqPage.appK.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.appK.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays attachments for 1915(c) Appendix K waiver FAQ response", async() => { + test("displays attachments for 1915(c) Appendix K waiver FAQ response", async () => { await faqPage.appKAttachments.click(); await expect(faqPage.appKAttachments.locator("div:nth-child(1)")).toBeVisible(); // await expect(faqPage.appKAttachments.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays withdraw Formal RAI Response for Medicaid Waiver FAQ response", async() => { + test("displays withdraw Formal RAI Response for Medicaid Waiver FAQ response", async () => { await faqPage.withdrawWaiverRai.click(); await expect(faqPage.withdrawWaiverRai.locator('div[data-state="open"]')).toBeVisible(); // await expect(faqPage.withdrawWaiverRai.locator("div:nth-child(1)")).toHaveText(""); TODO }); - test("displays withdraw Package for Waiver FAQ response", async() => { + test("displays withdraw Package for Waiver FAQ response", async () => { await faqPage.withdrawPackageWaiver.click(); await expect(faqPage.withdrawPackageWaiver.locator('div[data-state="open"]')).toBeVisible(); // await expect(faqPage.withdrawPackageWaiver.locator("div:nth-child(1)")).toHaveText(""); TODO }); }); }); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/a11y/index.spec.ts b/test/e2e/tests/a11y/index.spec.ts index d61044fe01..ef0139f634 100644 --- a/test/e2e/tests/a11y/index.spec.ts +++ b/test/e2e/tests/a11y/index.spec.ts @@ -4,7 +4,7 @@ import * as routes from "../fixtures/routes"; const STATIC_ROUTES = routes.STATIC; -test.describe("test a11y on static routes", {tag: ["@CI", "@a11y"] }, () => { +test.describe("test a11y on static routes", { tag: ["@CI", "@a11y"] }, () => { for (const route of STATIC_ROUTES) { test(`${route} should not have any automatically detectable accessibility issues`, async ({ page, @@ -12,10 +12,7 @@ test.describe("test a11y on static routes", {tag: ["@CI", "@a11y"] }, () => { await page.goto(route); await page.waitForTimeout(500); const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); - console.log( - `${route} violations: `, - accessibilityScanResults.violations.length, - ); + console.log(`${route} violations: `, accessibilityScanResults.violations.length); expect(accessibilityScanResults.violations).toEqual([]); }); } @@ -24,7 +21,7 @@ test.describe("test a11y on static routes", {tag: ["@CI", "@a11y"] }, () => { const WEBFORM_ROUTES = routes.WEBFORM; // Add to CI when prior to going to Prod -test.describe("test a11y on webform routes", {tag: ["@CI", "@a11y"] }, () => { +test.describe("test a11y on webform routes", { tag: ["@CI", "@a11y"] }, () => { for (const route of WEBFORM_ROUTES) { test(`${route} should not have any automatically detectable accessibility issues`, async ({ page, @@ -32,10 +29,7 @@ test.describe("test a11y on webform routes", {tag: ["@CI", "@a11y"] }, () => { await page.goto(route); await page.waitForTimeout(2000); const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); - console.log( - `${route} violations: `, - accessibilityScanResults.violations.length, - ); + console.log(`${route} violations: `, accessibilityScanResults.violations.length); expect(accessibilityScanResults.violations).toEqual([]); }); } diff --git a/test/e2e/tests/fixtures/routes.ts b/test/e2e/tests/fixtures/routes.ts index a88d3a2f56..517e66a248 100644 --- a/test/e2e/tests/fixtures/routes.ts +++ b/test/e2e/tests/fixtures/routes.ts @@ -47,4 +47,4 @@ export const WEBFORM = [ "/webform/abp2a/202401", "/webform/abp1/202401", "/webform/abp1/202402", -]; \ No newline at end of file +]; diff --git a/test/e2e/tests/home/disableRAIresponsewithdraw.spec.ts b/test/e2e/tests/home/disableRAIresponsewithdraw.spec.ts index 89b1944aaa..56f6dbd7d1 100644 --- a/test/e2e/tests/home/disableRAIresponsewithdraw.spec.ts +++ b/test/e2e/tests/home/disableRAIresponsewithdraw.spec.ts @@ -4,4 +4,4 @@ test.describe.skip("Disable RAI Response withdraw", async () => { // comment this out until we need it // test.beforeAll(); test("Perform disable RAI response withdraw for a Medicaid SPA", () => {}); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/home/enableRAIresponsewithdraw.spec.ts b/test/e2e/tests/home/enableRAIresponsewithdraw.spec.ts index 3df570e27e..7c0dbe8c7b 100644 --- a/test/e2e/tests/home/enableRAIresponsewithdraw.spec.ts +++ b/test/e2e/tests/home/enableRAIresponsewithdraw.spec.ts @@ -4,4 +4,4 @@ test.describe.skip("Enable RAI Response withdraw", async () => { // comment this out until we need it // test.beforeAll(); test("Perform enable RAI response withdraw for a Medicaid SPA", () => {}); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/home/index.spec.ts b/test/e2e/tests/home/index.spec.ts index 88da67ac00..a81fb8075c 100644 --- a/test/e2e/tests/home/index.spec.ts +++ b/test/e2e/tests/home/index.spec.ts @@ -1,35 +1,30 @@ import { test, expect } from "@playwright/test"; -test.describe('home page', {tag: '@e2e'}, () => { +test.describe("home page", { tag: "@e2e" }, () => { test.beforeEach(async ({ page }) => { await page.goto("/"); }); - test("has title", async ({ page }) => { + test("has title", async ({ page }) => { await expect(page).toHaveTitle("OneMAC"); }); - test('should display a menu', async({ page }) => { - await expect(page.getByTestId('sign-in-button-d')).not.toBeVisible(); + test("should display a menu", async ({ page }) => { + await expect(page.getByTestId("sign-in-button-d")).not.toBeVisible(); }); - + test("see frequently asked questions header when in faq page", async ({ page }) => { const popup = page.waitForEvent("popup"); await page.getByRole("link", { name: "FAQ", exact: true }).click(); const foundFaqHeading = await popup; - await foundFaqHeading - .getByRole("heading", { name: "Frequently Asked Questions" }) - .isVisible(); + await foundFaqHeading.getByRole("heading", { name: "Frequently Asked Questions" }).isVisible(); expect(foundFaqHeading).toBeTruthy(); }); - + test("see dashboard link when log in", async ({ page }) => { await page.getByRole("link", { name: "Dashboard" }).click(); - - const dashboardLinkVisible = await page - .getByRole("link", { name: "Dashboard" }) - .isVisible(); + + const dashboardLinkVisible = await page.getByRole("link", { name: "Dashboard" }).isVisible(); expect(dashboardLinkVisible).toBeTruthy(); - }); + }); }); - diff --git a/test/e2e/tests/home/noauth.spec.ts b/test/e2e/tests/home/noauth.spec.ts index f36877494b..8c498674b1 100644 --- a/test/e2e/tests/home/noauth.spec.ts +++ b/test/e2e/tests/home/noauth.spec.ts @@ -1,20 +1,22 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from "@playwright/test"; -import { HomePage } from 'pages/home.page'; +import { HomePage } from "pages/home.page"; let homePage; -test.describe('home page - no auth', {tag: ['@e2e', '@smoke']}, () => { +test.describe("home page - no auth", { tag: ["@e2e", "@smoke"] }, () => { test.use({ storageState: { cookies: [], origins: [] } }); test.beforeEach(async ({ page }) => { homePage = new HomePage(page); - await page.goto('/'); + await page.goto("/"); }); - test.describe('UI validations', () => { - test('should have a USA banner', async () => { + test.describe("UI validations", () => { + test("should have a USA banner", async () => { await expect(homePage.desktop.usaBanner).toBeVisible(); - await expect(homePage.desktop.usaBanner).toHaveText('An official website of the United States government'); + await expect(homePage.desktop.usaBanner).toHaveText( + "An official website of the United States government", + ); await expect(homePage.desktop.usaBannerBtn).toBeVisible(); await expect(homePage.desktop.usaBannerBtn).toHaveText("Here's how you know"); @@ -22,37 +24,41 @@ test.describe('home page - no auth', {tag: ['@e2e', '@smoke']}, () => { await expect(homePage.officialUsage).not.toBeVisible(); await expect(homePage.secureUsage).not.toBeVisible(); }); - - test('should have a navigation banner', async () => { + + test("should have a navigation banner", async () => { await expect(homePage.desktop.homeLink).toBeVisible(); - await expect(homePage.desktop.homeLink).toHaveText('Home'); - + await expect(homePage.desktop.homeLink).toHaveText("Home"); + await expect(homePage.desktop.faqLink).toBeVisible(); - await expect(homePage.desktop.faqLink).toHaveText('FAQ'); - + await expect(homePage.desktop.faqLink).toHaveText("FAQ"); + await expect(homePage.desktop.signInBtn).toBeVisible(); - await expect(homePage.desktop.signInBtn).toHaveText('Sign In'); - + await expect(homePage.desktop.signInBtn).toHaveText("Sign In"); + await expect(homePage.desktop.registerBtn).toBeVisible(); - await expect(homePage.desktop.registerBtn).toHaveText('Register'); + await expect(homePage.desktop.registerBtn).toHaveText("Register"); }); }); - test.describe('Workflow validations', () => { - test.describe('USA Banner Interactions', () => { + test.describe("Workflow validations", () => { + test.describe("USA Banner Interactions", () => { test.beforeEach(async () => { await homePage.desktop.usaBannerBtn.click(); }); - test('should display USA statement', async() => { + test("should display USA statement", async () => { await expect(homePage.officialUsage).toBeVisible(); - await expect(homePage.officialUsage).toHaveText('Official websites use .govA.gov website belongs to an official government organization in the United States.'); - + await expect(homePage.officialUsage).toHaveText( + "Official websites use .govA.gov website belongs to an official government organization in the United States.", + ); + await expect(homePage.secureUsage).toBeVisible(); - await expect(homePage.secureUsage).toHaveText("Secure .gov websites use HTTPSA lock (LockA locked padlock) or https:// means you've safely connected to the .gov website. Share sensitive information only on official, secure websites."); + await expect(homePage.secureUsage).toHaveText( + "Secure .gov websites use HTTPSA lock (LockA locked padlock) or https:// means you've safely connected to the .gov website. Share sensitive information only on official, secure websites.", + ); }); - - test('should collapse the USA statement', async() => { + + test("should collapse the USA statement", async () => { await homePage.desktop.usaBannerBtn.click(); await expect(homePage.officialUsage).not.toBeVisible(); @@ -60,16 +66,16 @@ test.describe('home page - no auth', {tag: ['@e2e', '@smoke']}, () => { }); }); - test.describe('FAQs', () => { - test('navigastes to the FAQ page', async({ page }) => { + test.describe("FAQs", () => { + test("navigastes to the FAQ page", async ({ page }) => { await homePage.desktop.faqLink.click(); - const pagePromise = page.waitForEvent('popup'); + const pagePromise = page.waitForEvent("popup"); const newTab = await pagePromise; await newTab.waitForLoadState(); - await expect(newTab.locator('#crosswalk-system')).toBeVisible(); + await expect(newTab.locator("#crosswalk-system")).toBeVisible(); }); }); }); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/home/respondtorai.spec.ts b/test/e2e/tests/home/respondtorai.spec.ts index 8276a23d28..a76db92317 100644 --- a/test/e2e/tests/home/respondtorai.spec.ts +++ b/test/e2e/tests/home/respondtorai.spec.ts @@ -4,4 +4,4 @@ test.describe.skip("Respond to RAI", async () => { // comment this out until we need it // test.beforeAll(); test("Submit a Respond to RAI for a Medicaid SPA", () => {}); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/home/subsequentdocuments.spec.ts b/test/e2e/tests/home/subsequentdocuments.spec.ts index 636120734b..c1b2512675 100644 --- a/test/e2e/tests/home/subsequentdocuments.spec.ts +++ b/test/e2e/tests/home/subsequentdocuments.spec.ts @@ -4,4 +4,4 @@ test.describe.skip("Subsequent Documents", async () => { // comment this out until we need it // test.beforeAll(); test("Submit a subsequent documents for a Medicaid SPA", () => {}); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/home/withdrawRAIresponse.spec.ts b/test/e2e/tests/home/withdrawRAIresponse.spec.ts index a5d4efedb9..6f900ee26c 100644 --- a/test/e2e/tests/home/withdrawRAIresponse.spec.ts +++ b/test/e2e/tests/home/withdrawRAIresponse.spec.ts @@ -4,4 +4,4 @@ test.describe.skip("Withdraw RAI Response", async () => { // comment this out until we need it // test.beforeAll(); test("Perform withdraw RAI response for a Medicaid SPA", () => {}); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/home/withdrawpackage.spec.ts b/test/e2e/tests/home/withdrawpackage.spec.ts index ac5aba7deb..39eb0f5323 100644 --- a/test/e2e/tests/home/withdrawpackage.spec.ts +++ b/test/e2e/tests/home/withdrawpackage.spec.ts @@ -4,4 +4,4 @@ test.describe.skip("Withdraw package", async () => { // comment this out until we need it // test.beforeAll(); test("Perform withdraw package action for a Medicaid SPA", () => {}); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/perf/index.spec.ts b/test/e2e/tests/perf/index.spec.ts index 51071f7115..bbe08d6db5 100644 --- a/test/e2e/tests/perf/index.spec.ts +++ b/test/e2e/tests/perf/index.spec.ts @@ -5,9 +5,11 @@ const STATIC_ROUTES = routes.STATIC; test.describe("test performance on static routes", { tag: ["@perf"] }, () => { for (const route of STATIC_ROUTES) { - test(`Time to First Byte for ${route}`, { tag: ["@ttfb"] }, async({ page }) => { + test(`Time to First Byte for ${route}`, { tag: ["@ttfb"] }, async ({ page }) => { await page.goto(route); - const ttfb = await page.evaluate(() => performance.timing.responseStart - performance.timing.requestStart) + const ttfb = await page.evaluate( + () => performance.timing.responseStart - performance.timing.requestStart, + ); console.log(`TTFB for ${route}: ${ttfb} ms`); }); @@ -18,7 +20,7 @@ test.describe("test performance on static routes", { tag: ["@perf"] }, () => { new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); resolve(entries[entries.length - 1]); - }).observe({ type: 'largest-contentful-paint', buffered: true }); + }).observe({ type: "largest-contentful-paint", buffered: true }); }); }); @@ -32,11 +34,11 @@ test.describe("test performance on static routes", { tag: ["@perf"] }, () => { new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); resolve(entries[0]); - }).observe({ type: 'paint', buffered: true }); + }).observe({ type: "paint", buffered: true }); }); }); console.log(`First Contentful Paint for ${route} is: ${fcp.startTime} ms`); }); } -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/sub-doc/medicaidSPADetail.spec.ts b/test/e2e/tests/sub-doc/medicaidSPADetail.spec.ts index 59ac0caba2..ad5b258968 100644 --- a/test/e2e/tests/sub-doc/medicaidSPADetail.spec.ts +++ b/test/e2e/tests/sub-doc/medicaidSPADetail.spec.ts @@ -3,7 +3,7 @@ import { test, expect } from "@playwright/test"; test.describe("Medicaid SPA - Sub Doc", () => { test.beforeEach(async ({ page }) => { await page.goto("/"); - await page.getByTestId('Dashboard-d').click(); + await page.getByTestId("Dashboard-d").click(); await page.locator('a[href*="details/Medicaid%20SPA/CO-22-2020"]').click(); }); @@ -14,10 +14,18 @@ test.describe("Medicaid SPA - Sub Doc", () => { test.describe("form actions", () => { test("should display the details page", async ({ page }) => { // elements before to be validated - - await expect(page.locator('a[href*="upload-subsequent-documents/Medicaid SPA/CO-22-2020?origin=details"]')).toBeVisible(); - await expect(page.locator('a[href*="upload-subsequent-documents/Medicaid SPA/CO-22-2020?origin=details"]')).toContainText('Upload Subsequent Document'); - + + await expect( + page.locator( + 'a[href*="upload-subsequent-documents/Medicaid SPA/CO-22-2020?origin=details"]', + ), + ).toBeVisible(); + await expect( + page.locator( + 'a[href*="upload-subsequent-documents/Medicaid SPA/CO-22-2020?origin=details"]', + ), + ).toContainText("Upload Subsequent Document"); + // elements after to be validated }); }); @@ -25,15 +33,19 @@ test.describe("Medicaid SPA - Sub Doc", () => { test.describe.skip("package activity", () => {}); }); - test.describe('Naviation - Validation', () => { - test('navigate to withdraw package page', () => { + test.describe("Naviation - Validation", () => { + test("navigate to withdraw package page", () => { // see below }); - test('navigate to sub doc page', async ({ page }) => { - await page.locator('a[href*="/actions/upload-subsequent-documents/Medicaid SPA/CO-22-2020?origin=details"]').click(); + test("navigate to sub doc page", async ({ page }) => { + await page + .locator( + 'a[href*="/actions/upload-subsequent-documents/Medicaid SPA/CO-22-2020?origin=details"]', + ) + .click(); await expect(page.getByTestId("detail-section")).toBeVisible(); }); }); -}); \ No newline at end of file +}); diff --git a/test/e2e/tests/sub-doc/medicaidSubDocDetail.spec.ts b/test/e2e/tests/sub-doc/medicaidSubDocDetail.spec.ts index 621e83cb60..710767a0de 100644 --- a/test/e2e/tests/sub-doc/medicaidSubDocDetail.spec.ts +++ b/test/e2e/tests/sub-doc/medicaidSubDocDetail.spec.ts @@ -1,44 +1,57 @@ -import {test, expect} from "@playwright/test"; - +import { test, expect } from "@playwright/test"; test.beforeEach(async ({ page }) => { await page.goto("/"); await page.getByTestId("Dashboard-d").click(); await page.locator('a[href*="details/Medicaid%20SPA/CO-22-2020"]').click(); - await page.locator('a[href*="/actions/upload-subsequent-documents/Medicaid SPA/CO-22-2020?origin=details"]').click(); + await page + .locator( + 'a[href*="/actions/upload-subsequent-documents/Medicaid SPA/CO-22-2020?origin=details"]', + ) + .click(); }); test.describe("UI - Validation", () => { test.describe("Breadcrumbs", () => { - test("should displays breadcrumb elements", async({ page }) => { + test("should displays breadcrumb elements", async ({ page }) => { // need a better selector await expect(page.locator("#root > div > main > div:nth-child(2) > nav")).toBeVisible(); - await expect(page.locator("#root > div > main > div:nth-child(2) > nav")).toHaveText("DashboardCO-22-2020New Subsequent Documentation"); + await expect(page.locator("#root > div > main > div:nth-child(2) > nav")).toHaveText( + "DashboardCO-22-2020New Subsequent Documentation", + ); }); }); test.describe("Form Elements", () => { - test("Detail Section", async({ page }) => { + test("Detail Section", async ({ page }) => { await expect(page.getByTestId("detail-section")).toBeVisible(); await expect(page.getByTestId("detail-section-title")).toBeVisible(); - await expect(page.getByTestId("detail-section-title")).toHaveText("Medicaid SPA Subsequent Documents Details"); + await expect(page.getByTestId("detail-section-title")).toHaveText( + "Medicaid SPA Subsequent Documents Details", + ); await expect(page.getByTestId("detail-section-child")).toBeVisible(); // await expect(page.getByTestId("detail-section-child")).toHaveText(); needs more detailed selectors to validate text }); - test("Document Section", async({ page }) => { + test("Document Section", async ({ page }) => { await expect(page.getByTestId("attachment-section")).toBeVisible(); await expect(page.getByTestId("attachment-section-title")).toBeVisible(); - await expect(page.getByTestId("attachment-section-title")).toHaveText("Subsequent Medicaid SPA Documents *"); + await expect(page.getByTestId("attachment-section-title")).toHaveText( + "Subsequent Medicaid SPA Documents *", + ); await expect(page.getByTestId("attachment-section-child")).toBeVisible(); await expect(page.getByTestId("attachments-instructions")).toBeVisible(); - await expect(page.getByTestId("attachments-instructions")).toHaveText("Maximum file size of 80 MB per attachment. You can add multiple files per attachment type. Read the description for each of the attachment types on the FAQ Page."); + await expect(page.getByTestId("attachments-instructions")).toHaveText( + "Maximum file size of 80 MB per attachment. You can add multiple files per attachment type. Read the description for each of the attachment types on the FAQ Page.", + ); await expect(page.getByTestId("accepted-files")).toBeVisible(); - await expect(page.getByTestId("accepted-files")).toHaveText("We accept the following file formats: .doc, .docx, .pdf, .jpg, .xlsx, and more. See the full list."); + await expect(page.getByTestId("accepted-files")).toHaveText( + "We accept the following file formats: .doc, .docx, .pdf, .jpg, .xlsx, and more. See the full list.", + ); await expect(page.getByTestId("cmsForm179-label")).toBeVisible(); await expect(page.getByTestId("cmsForm179-label")).toHaveText("CMS Form 179"); @@ -46,15 +59,17 @@ test.describe("UI - Validation", () => { // TODO: Extend to all labels }); - test("Reason Section", async({ page }) => { + test("Reason Section", async ({ page }) => { await expect(page.getByTestId("additional-info")).toBeVisible(); await expect(page.getByTestId("additional-info-title")).toBeVisible(); - await expect(page.getByTestId("additional-info-title")).toHaveText("Reason for subsequent documents *"); + await expect(page.getByTestId("additional-info-title")).toHaveText( + "Reason for subsequent documents *", + ); await expect(page.getByTestId("additional-info-child")).toBeVisible(); }); }); }); -test.describe.skip("Navigation", () => {}); \ No newline at end of file +test.describe.skip("Navigation", () => {}); diff --git a/test/e2e/utils/auth.setup.ts b/test/e2e/utils/auth.setup.ts index ee653f28e8..f6fe93c653 100644 --- a/test/e2e/utils/auth.setup.ts +++ b/test/e2e/utils/auth.setup.ts @@ -2,10 +2,7 @@ import { test as setup } from "@playwright/test"; import { testUsers } from "./users"; import { LoginPage } from "../pages"; import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; -import { - SecretsManagerClient, - GetSecretValueCommand, -} from "@aws-sdk/client-secrets-manager"; +import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager"; const stage = process.env.STAGE_NAME || "main"; const deploymentConfig = JSON.parse( diff --git a/test/e2e/utils/setup.ts b/test/e2e/utils/setup.ts index 6d87ffec84..769341bf11 100644 --- a/test/e2e/utils/setup.ts +++ b/test/e2e/utils/setup.ts @@ -3,10 +3,7 @@ import { test as setup } from "@playwright/test"; import { testUsers } from "./users"; import { LoginPage } from "../pages"; import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm"; -import { - SecretsManagerClient, - GetSecretValueCommand, -} from "@aws-sdk/client-secrets-manager"; +import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager"; import { fromEnv } from "@aws-sdk/credential-providers"; const stage = process.env.STAGE_NAME || "brain"; From 1001df7b3bb7b0e1f11346e202b8004c3737cafe Mon Sep 17 00:00:00 2001 From: James Dinh Date: Wed, 15 Jan 2025 12:00:27 -0800 Subject: [PATCH 13/17] fix(email-verbiage): Additional QE feedback for initial email templates --- lib/libs/email/content/newSubmission/index.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/libs/email/content/newSubmission/index.tsx b/lib/libs/email/content/newSubmission/index.tsx index b915675978..a7a967f2f3 100644 --- a/lib/libs/email/content/newSubmission/index.tsx +++ b/lib/libs/email/content/newSubmission/index.tsx @@ -1,5 +1,4 @@ import { Events, Authority, EmailAddresses, CommonEmailVariables } from "shared-types"; -import { formatActionType } from "shared-utils"; import { AuthoritiesWithUserTypesTemplate } from "../.."; import { MedSpaCMSEmail, @@ -78,9 +77,7 @@ export const newSubmission: AuthoritiesWithUserTypesTemplate = { ) => { return { to: [`${variables.submitterName} <${variables.submitterEmail}>`], - subject: `Your ${formatActionType(variables.actionType)} ${ - variables.id - } has been submitted to CMS`, + subject: `Your ${variables.authority} ${variables.id} has been submitted to CMS`, body: await render(), }; }, From 002ee3fd2ed58e367e3765a69de407cbaeafb910 Mon Sep 17 00:00:00 2001 From: asharonbaltazar <58940073+asharonbaltazar@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:25:25 -0500 Subject: [PATCH 14/17] fix: hide timeout modal description (#1026) --- react-app/src/components/TimeoutModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-app/src/components/TimeoutModal/index.tsx b/react-app/src/components/TimeoutModal/index.tsx index 21359d7b99..6be317f055 100644 --- a/react-app/src/components/TimeoutModal/index.tsx +++ b/react-app/src/components/TimeoutModal/index.tsx @@ -62,7 +62,7 @@ export const TimeoutModal = () => { return ( - Session expiring soon + Session expiring soon Session expiring soon From e4d0bd9774f3ecc5ecebda5a7cdfe94fb98dab72 Mon Sep 17 00:00:00 2001 From: Tiffany Forkner Date: Wed, 15 Jan 2025 16:38:47 -0500 Subject: [PATCH 15/17] adding tests to insight and legacyInsight (#1030) * added tests for sinkInsight and sinkLegacyInsight --- lib/lambda/getPackageActions.test.ts | 13 + lib/lambda/processEmails.ts | 5 + lib/lambda/sinkCpocs.test.ts | 68 ++++++ lib/lambda/sinkInsights.test.ts | 136 +++++++++++ lib/lambda/sinkInsights.ts | 8 +- lib/lambda/sinkLegacyInsights.test.ts | 210 ++++++++++++++++ lib/lambda/sinkLegacyInsights.ts | 6 +- lib/lambda/sinkMainProcessors.test.ts | 231 ++++++++++++++++++ .../submissionPayloads/respond-to-rai.ts | 2 +- lib/lambda/update/updatePackage.test.ts | 7 +- lib/libs/api/package/itemExists.ts | 2 +- lib/libs/handler-lib.ts | 18 -- lib/libs/opensearch-lib.test.ts | 38 +++ lib/libs/opensearch-lib.ts | 33 +-- mocks/handlers/opensearch/index.ts | 2 + mocks/handlers/opensearch/indices.ts | 6 + mocks/handlers/opensearch/main.ts | 5 + vitest.config.ts | 1 + 18 files changed, 739 insertions(+), 52 deletions(-) create mode 100644 lib/lambda/sinkInsights.test.ts create mode 100644 lib/lambda/sinkLegacyInsights.test.ts create mode 100644 lib/libs/opensearch-lib.test.ts diff --git a/lib/lambda/getPackageActions.test.ts b/lib/lambda/getPackageActions.test.ts index 3d77dbdfce..373d58b1be 100644 --- a/lib/lambda/getPackageActions.test.ts +++ b/lib/lambda/getPackageActions.test.ts @@ -19,6 +19,19 @@ describe("getPackageActions Handler", () => { expect(res.statusCode).toEqual(400); }); + it("should return 500 if event body is invalid", async () => { + const event = { + body: {}, + requestContext: getRequestContext(), + } as APIGatewayEvent; + + const res = await handler(event); + + expect(res).toBeTruthy(); + expect(res.statusCode).toEqual(500); + expect(res.body).toEqual(JSON.stringify({ message: "Internal server error" })); + }); + it("should return 401 if not authorized to view resources from the state", async () => { const event = { body: JSON.stringify({ id: HI_TEST_ITEM_ID }), diff --git a/lib/lambda/processEmails.ts b/lib/lambda/processEmails.ts index 0d62cd5e05..29a86325a7 100644 --- a/lib/lambda/processEmails.ts +++ b/lib/lambda/processEmails.ts @@ -175,6 +175,11 @@ export async function processAndSendEmails(record: any, id: string, config: Proc const sec = await getSecret(config.emailAddressLookupSecretName); const item = await os.getItem(config.osDomain, getNamespace("main"), id); + if (!item?.found || !item?._source) { + console.log(`The package was not found for id: ${id}. Doing nothing.`); + return; + } + const cpocEmail = [...getCpocEmail(item)]; const srtEmails = [...getSrtEmails(item)]; diff --git a/lib/lambda/sinkCpocs.test.ts b/lib/lambda/sinkCpocs.test.ts index f03453de09..147ef783c6 100644 --- a/lib/lambda/sinkCpocs.test.ts +++ b/lib/lambda/sinkCpocs.test.ts @@ -9,8 +9,12 @@ import { createKafkaRecord, OPENSEARCH_DOMAIN, OPENSEARCH_INDEX_NAMESPACE, + rateLimitBulkUpdateDataHandler, + errorBulkUpdateDataHandler, } from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; import cpocs, { MUHAMMAD_BASHAR_ID } from "mocks/data/cpocs"; +import { Client } from "@opensearch-project/opensearch"; const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}cpocs`; const TOPIC = "--mako--branch-name--aws.seatool.debezium.cdc.SEA.dbo.Officers"; @@ -221,4 +225,68 @@ describe("test sync cpoc", () => { }, ]); }); + + it("should succeed after receiving a rate limit exceeded error", async () => { + const osBulkSpy = vi.spyOn(Client.prototype, "bulk"); + mockedServer.use(rateLimitBulkUpdateDataHandler); + + await handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + value: convertObjToBase64({ + payload: { + after: { + Officer_ID: MUHAMMAD_BASHAR_ID, + First_Name: MUHAMMAD_BASHAR._source?.firstName, + Last_Name: MUHAMMAD_BASHAR._source?.lastName, + Email: MUHAMMAD_BASHAR._source?.email, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + ...MUHAMMAD_BASHAR._source, + }, + ]); + expect(osBulkSpy).toHaveBeenCalledTimes(2); + }); + + it("should succeed after receiving a rate limit exceeded error", async () => { + mockedServer.use(errorBulkUpdateDataHandler); + + await expect(() => + handler( + createKafkaEvent({ + [`${TOPIC}-xyz`]: [ + createKafkaRecord({ + topic: `${TOPIC}-xyz`, + key: MUHAMMAD_BASHAR_KEY, + value: convertObjToBase64({ + payload: { + after: { + Officer_ID: MUHAMMAD_BASHAR_ID, + First_Name: MUHAMMAD_BASHAR._source?.firstName, + Last_Name: MUHAMMAD_BASHAR._source?.lastName, + Email: MUHAMMAD_BASHAR._source?.email, + }, + }, + }), + }), + ], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("Response Error"); + }); }); diff --git a/lib/lambda/sinkInsights.test.ts b/lib/lambda/sinkInsights.test.ts new file mode 100644 index 0000000000..fa2eac3969 --- /dev/null +++ b/lib/lambda/sinkInsights.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { handler } from "./sinkInsights"; +import { Context } from "aws-lambda"; +import * as os from "libs/opensearch-lib"; +import * as sink from "../libs/sink-lib"; +import { + convertObjToBase64, + createKafkaEvent, + createKafkaRecord, + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, +} from "mocks"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}insights`; +const TEST_INSIGHT_ID = "42"; +const TEST_INSIGHT_KEY = Buffer.from(TEST_INSIGHT_ID).toString("base64"); +const TOPIC = `--mako--branch-name--aws.seatool.ksql.onemac.three.agg.State_Plan-${TEST_INSIGHT_ID}`; + +describe("test sync types", () => { + const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); + const logErrorSpy = vi.spyOn(sink, "logError"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should throw an error if the topic is undefined", async () => { + await expect(() => + handler( + createKafkaEvent({ + undefined: [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (undefined) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should throw an error if the topic is invalid", async () => { + await expect(() => + handler( + createKafkaEvent({ + "invalid-topic": [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (invalid-topic) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should skip if the key is invalid", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + // @ts-expect-error + key: undefined, + value: convertObjToBase64({ + test: "value", + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should skip if the record has no value", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + // @ts-expect-error needs to be undefined for test + value: undefined, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should handle a valid record", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + value: convertObjToBase64({ + test: "value", + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: TEST_INSIGHT_ID, + test: "value", + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/lambda/sinkInsights.ts b/lib/lambda/sinkInsights.ts index 9b3e0c27eb..54726c64a5 100644 --- a/lib/lambda/sinkInsights.ts +++ b/lib/lambda/sinkInsights.ts @@ -9,12 +9,12 @@ export const handler: Handler = async (event) => { for (const topicPartition of Object.keys(event.records)) { const topic = getTopic(topicPartition); switch (topic) { - case undefined: - logError({ type: ErrorType.BADTOPIC }); - throw new Error(); case "aws.seatool.ksql.onemac.three.agg.State_Plan": await ksql(event.records[topicPartition], topicPartition); break; + default: + logError({ type: ErrorType.BADTOPIC }); + throw new Error(`topic (${topicPartition}) is invalid`); } } } catch (error) { @@ -30,7 +30,7 @@ const ksql = async (kafkaRecords: KafkaRecord[], topicPartition: string) => { try { if (!value) continue; - const id: string = JSON.parse(decodeBase64WithUtf8(key)); + const id: string = decodeBase64WithUtf8(key); const record = JSON.parse(decodeBase64WithUtf8(value)); docs.push({ ...record, id }); } catch (error) { diff --git a/lib/lambda/sinkLegacyInsights.test.ts b/lib/lambda/sinkLegacyInsights.test.ts new file mode 100644 index 0000000000..ce223ee340 --- /dev/null +++ b/lib/lambda/sinkLegacyInsights.test.ts @@ -0,0 +1,210 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { handler } from "./sinkLegacyInsights"; +import { Context } from "aws-lambda"; +import * as os from "libs/opensearch-lib"; +import * as sink from "../libs/sink-lib"; +import { + convertObjToBase64, + createKafkaEvent, + createKafkaRecord, + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, +} from "mocks"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}legacyinsights`; +const TEST_INSIGHT_ID = "42"; +const TEST_INSIGHT_KEY = Buffer.from(TEST_INSIGHT_ID).toString("base64"); +const TOPIC = `--mako--branch-name--aws.onemac.migration.cdc-${TEST_INSIGHT_ID}`; + +describe("test sync types", () => { + const bulkUpdateDataSpy = vi.spyOn(os, "bulkUpdateData"); + const logErrorSpy = vi.spyOn(sink, "logError"); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should throw an error if the topic is undefined", async () => { + await expect(() => + handler( + createKafkaEvent({ + undefined: [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (undefined) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should throw an error if the topic is invalid", async () => { + await expect(() => + handler( + createKafkaEvent({ + "invalid-topic": [], + }), + {} as Context, + vi.fn(), + ), + ).rejects.toThrowError("topic (invalid-topic) is invalid"); + + expect(logErrorSpy).toHaveBeenCalledWith({ type: sink.ErrorType.BADTOPIC }); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.UNKNOWN, + }), + ); + }); + + it("should skip if the key is invalid", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + // @ts-expect-error + key: undefined, + value: convertObjToBase64({ + test: "value", + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: sink.ErrorType.BADPARSE, + }), + ); + }); + + it("should delete the record if the value is undefined", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + // @ts-expect-error needs to be undefined for test + value: undefined, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: TEST_INSIGHT_ID, + hardDeletedFromLegacy: true, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should skip if the record does not have sk field", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + value: convertObjToBase64({ + test: "value", + }), + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, []); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should handle a valid record for package change", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + value: convertObjToBase64({ + test: "value", + sk: "Package", + }), + offset: 0, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: TEST_INSIGHT_ID, + test: "value", + sk: "Package", + approvedEffectiveDate: null, + changedDate: null, + finalDispositionDate: null, + proposedDate: null, + proposedEffectiveDate: null, + statusDate: null, + submissionDate: null, + hardDeletedFromLegacy: null, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); + + it("should handle a valid record for offset change", async () => { + await handler( + createKafkaEvent({ + [TOPIC]: [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_INSIGHT_KEY, + value: convertObjToBase64({ + test: "value", + sk: "Offset", + }), + offset: 3, + }), + ], + }), + {} as Context, + vi.fn(), + ); + + expect(bulkUpdateDataSpy).toHaveBeenCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + id: "3", + test: "value", + sk: "Offset", + approvedEffectiveDate: null, + changedDate: null, + finalDispositionDate: null, + proposedDate: null, + proposedEffectiveDate: null, + statusDate: null, + submissionDate: null, + hardDeletedFromLegacy: null, + }, + ]); + expect(logErrorSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/lib/lambda/sinkLegacyInsights.ts b/lib/lambda/sinkLegacyInsights.ts index 023238f32a..913908b997 100644 --- a/lib/lambda/sinkLegacyInsights.ts +++ b/lib/lambda/sinkLegacyInsights.ts @@ -9,12 +9,12 @@ export const handler: Handler = async (event) => { for (const topicPartition of Object.keys(event.records)) { const topic = getTopic(topicPartition); switch (topic) { - case undefined: - logError({ type: ErrorType.BADTOPIC }); - throw new Error(); case "aws.onemac.migration.cdc": await onemac(event.records[topicPartition], topicPartition); break; + default: + logError({ type: ErrorType.BADTOPIC }); + throw new Error(`topic (${topicPartition}) is invalid`); } } } catch (error) { diff --git a/lib/lambda/sinkMainProcessors.test.ts b/lib/lambda/sinkMainProcessors.test.ts index c4b05ab5a9..4b219a3efa 100644 --- a/lib/lambda/sinkMainProcessors.test.ts +++ b/lib/lambda/sinkMainProcessors.test.ts @@ -14,9 +14,12 @@ import { OPENSEARCH_INDEX_NAMESPACE, TEST_ITEM_ID, EXISTING_ITEM_TEMPORARY_EXTENSION_ID, + NOT_FOUND_ITEM_ID, convertObjToBase64, createKafkaRecord, + errorMainMultiDocumentHandler, } from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; import { appkBase, capitatedInitial, @@ -615,6 +618,119 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { ]); }); + it("outputs kafka records into mako records if mako record is not found", async () => { + await insertNewSeatoolRecordsFromKafkaIntoMako( + [ + createKafkaRecord({ + topic: TOPIC, + key: Buffer.from(NOT_FOUND_ITEM_ID).toString("base64"), + value: convertObjToBase64({ + id: NOT_FOUND_ITEM_ID, + ACTION_OFFICERS: [ + { + FIRST_NAME: "John", + LAST_NAME: "Doe", + EMAIL: "john.doe@medicaid.gov", + OFFICER_ID: 12345, + DEPARTMENT: "State Plan Review", + PHONE: "202-555-1234", + }, + { + FIRST_NAME: "Emily", + LAST_NAME: "Rodriguez", + EMAIL: "emily.rodriguez@medicaid.gov", + OFFICER_ID: 12346, + DEPARTMENT: "Compliance Division", + PHONE: "202-555-5678", + }, + ], + LEAD_ANALYST: [ + { + FIRST_NAME: "Michael", + LAST_NAME: "Chen", + EMAIL: "michael.chen@cms.hhs.gov", + OFFICER_ID: 67890, + DEPARTMENT: "Medicaid Innovation Center", + PHONE: "202-555-9012", + }, + ], + STATE_PLAN: { + PLAN_TYPE: 123, + SPW_STATUS_ID: 4, + APPROVED_EFFECTIVE_DATE: TIMESTAMP, + CHANGED_DATE: EARLIER_TIMESTAMP, + SUMMARY_MEMO: "Sample summary", + TITLE_NAME: "Sample Title", + STATUS_DATE: EARLIER_TIMESTAMP, + SUBMISSION_DATE: TIMESTAMP, + LEAD_ANALYST_ID: 67890, + ACTUAL_EFFECTIVE_DATE: null, + PROPOSED_DATE: null, + STATE_CODE: "10", + }, + RAI: [], + ACTIONTYPES: [{ ACTION_NAME: "Initial Review", ACTION_ID: 1, PLAN_TYPE_ID: 123 }], + STATE_PLAN_SERVICETYPES: [{ SPA_TYPE_ID: 1, SPA_TYPE_NAME: "Type A" }], + STATE_PLAN_SERVICE_SUBTYPES: [{ TYPE_ID: 1, TYPE_NAME: "SubType X" }], + }), + }), + ], + TOPIC, + ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + actionType: "Initial Review", + approvedEffectiveDate: ISO_DATETIME, + authority: "1915(c)", + changed_date: EARLIER_TIMESTAMP, + cmsStatus: "Approved", + description: "Sample summary", + finalDispositionDate: EARLIER_ISO_DATETIME, + id: NOT_FOUND_ITEM_ID, + initialIntakeNeeded: false, + leadAnalystEmail: "michael.chen@cms.hhs.gov", + leadAnalystName: "Michael Chen", + leadAnalystOfficerId: 67890, + locked: false, + proposedDate: null, + raiReceivedDate: null, + raiRequestedDate: null, + raiWithdrawEnabled: false, + raiWithdrawnDate: null, + reviewTeam: [ + { + email: "john.doe@medicaid.gov", + name: "John Doe", + }, + { + email: "emily.rodriguez@medicaid.gov", + name: "Emily Rodriguez", + }, + ], + seatoolStatus: "Approved", + secondClock: false, + state: "10", + stateStatus: "Approved", + statusDate: EARLIER_ISO_DATETIME, + subTypes: [ + { + TYPE_ID: 1, + TYPE_NAME: "SubType X", + }, + ], + subject: "Sample Title", + submissionDate: ISO_DATETIME, + types: [ + { + SPA_TYPE_ID: 1, + SPA_TYPE_NAME: "Type A", + }, + ], + }, + ]); + }); + it("outputs kafka records into mako records without changedDates", async () => { await insertNewSeatoolRecordsFromKafkaIntoMako( [ @@ -728,6 +844,121 @@ describe("insertNewSeatoolRecordsFromKafkaIntoMako", () => { ]); }); + it("handles errors in getting mako timestamps", async () => { + mockedServer.use(errorMainMultiDocumentHandler); + + await insertNewSeatoolRecordsFromKafkaIntoMako( + [ + createKafkaRecord({ + topic: TOPIC, + key: TEST_ITEM_KEY, + value: convertObjToBase64({ + id: TEST_ITEM_ID, + ACTION_OFFICERS: [ + { + FIRST_NAME: "John", + LAST_NAME: "Doe", + EMAIL: "john.doe@medicaid.gov", + OFFICER_ID: 12345, + DEPARTMENT: "State Plan Review", + PHONE: "202-555-1234", + }, + { + FIRST_NAME: "Emily", + LAST_NAME: "Rodriguez", + EMAIL: "emily.rodriguez@medicaid.gov", + OFFICER_ID: 12346, + DEPARTMENT: "Compliance Division", + PHONE: "202-555-5678", + }, + ], + LEAD_ANALYST: [ + { + FIRST_NAME: "Michael", + LAST_NAME: "Chen", + EMAIL: "michael.chen@cms.hhs.gov", + OFFICER_ID: 67890, + DEPARTMENT: "Medicaid Innovation Center", + PHONE: "202-555-9012", + }, + ], + STATE_PLAN: { + PLAN_TYPE: 123, + SPW_STATUS_ID: 4, + APPROVED_EFFECTIVE_DATE: TIMESTAMP, + CHANGED_DATE: EARLIER_TIMESTAMP, + SUMMARY_MEMO: "Sample summary", + TITLE_NAME: "Sample Title", + STATUS_DATE: EARLIER_TIMESTAMP, + SUBMISSION_DATE: TIMESTAMP, + LEAD_ANALYST_ID: 67890, + ACTUAL_EFFECTIVE_DATE: null, + PROPOSED_DATE: null, + STATE_CODE: "10", + }, + RAI: [], + ACTIONTYPES: [{ ACTION_NAME: "Initial Review", ACTION_ID: 1, PLAN_TYPE_ID: 123 }], + STATE_PLAN_SERVICETYPES: [{ SPA_TYPE_ID: 1, SPA_TYPE_NAME: "Type A" }], + STATE_PLAN_SERVICE_SUBTYPES: [{ TYPE_ID: 1, TYPE_NAME: "SubType X" }], + }), + }), + ], + TOPIC, + ); + + expect(bulkUpdateDataSpy).toBeCalledWith(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, [ + { + actionType: "Initial Review", + approvedEffectiveDate: ISO_DATETIME, + authority: "1915(c)", + changed_date: EARLIER_TIMESTAMP, + cmsStatus: "Approved", + description: "Sample summary", + finalDispositionDate: EARLIER_ISO_DATETIME, + id: TEST_ITEM_ID, + initialIntakeNeeded: false, + leadAnalystEmail: "michael.chen@cms.hhs.gov", + leadAnalystName: "Michael Chen", + leadAnalystOfficerId: 67890, + locked: false, + proposedDate: null, + raiReceivedDate: null, + raiRequestedDate: null, + raiWithdrawEnabled: false, + raiWithdrawnDate: null, + reviewTeam: [ + { + email: "john.doe@medicaid.gov", + name: "John Doe", + }, + { + email: "emily.rodriguez@medicaid.gov", + name: "Emily Rodriguez", + }, + ], + seatoolStatus: "Approved", + secondClock: false, + state: "10", + stateStatus: "Approved", + statusDate: EARLIER_ISO_DATETIME, + subTypes: [ + { + TYPE_ID: 1, + TYPE_NAME: "SubType X", + }, + ], + subject: "Sample Title", + submissionDate: ISO_DATETIME, + types: [ + { + SPA_TYPE_ID: 1, + SPA_TYPE_NAME: "Type A", + }, + ], + }, + ]); + }); + it("skips newer mako records", async () => { await insertNewSeatoolRecordsFromKafkaIntoMako( [ diff --git a/lib/lambda/submit/submissionPayloads/respond-to-rai.ts b/lib/lambda/submit/submissionPayloads/respond-to-rai.ts index 0d1827e539..3fd5ca4a4a 100644 --- a/lib/lambda/submit/submissionPayloads/respond-to-rai.ts +++ b/lib/lambda/submit/submissionPayloads/respond-to-rai.ts @@ -31,7 +31,7 @@ export const respondToRai = async (event: APIGatewayEvent) => { const transformedData = events["respond-to-rai"].schema.parse({ ...parsedResult.data, - authority: item?._source.authority, + authority: item?._source?.authority, submitterName, submitterEmail, timestamp: Date.now(), diff --git a/lib/lambda/update/updatePackage.test.ts b/lib/lambda/update/updatePackage.test.ts index 89e4514a6d..df208f9aea 100644 --- a/lib/lambda/update/updatePackage.test.ts +++ b/lib/lambda/update/updatePackage.test.ts @@ -212,10 +212,13 @@ describe("handler", () => { } as APIGatewayEvent; const result = await handler(noActionevent); - const expectedResult = "Cannot read properties of undefined (reading 'baseSchema')"; + expect(result?.statusCode).toStrictEqual(500); - expect(result?.body.message).toStrictEqual(expectedResult); + expect(result?.body).toStrictEqual({ + message: "Cannot read properties of undefined (reading 'baseSchema')", + }); }); + it("should fail to update a package - no topic name ", async () => { process.env.topicName = ""; const noActionevent = { diff --git a/lib/libs/api/package/itemExists.ts b/lib/libs/api/package/itemExists.ts index c5172f161b..0610657f7d 100644 --- a/lib/libs/api/package/itemExists.ts +++ b/lib/libs/api/package/itemExists.ts @@ -14,7 +14,7 @@ export async function itemExists(params: { : getNamespace("main"); const packageResult = await os.getItem(domain, index, params.id); - return !!packageResult?._source; + return packageResult?._source !== undefined && packageResult?._source !== null; } catch (error) { console.error(error); return false; diff --git a/lib/libs/handler-lib.ts b/lib/libs/handler-lib.ts index 5eb9dc0035..7a13363ca3 100644 --- a/lib/libs/handler-lib.ts +++ b/lib/libs/handler-lib.ts @@ -1,21 +1,3 @@ -import type { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda"; - -export const handler = async ( - handler: (event?: APIGatewayEvent, context?: Context) => Promise, -) => { - const handlerResponse = await handler(); - - const response: APIGatewayProxyResult = { - headers: { - "Access-Control-Allow-Headers": "Content-Type", - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "OPTIONS,POST,GET,PUT,DELETE", - }, - ...handlerResponse, - }; - return () => response; -}; - export function response( currentResponse: { statusCode?: number; diff --git a/lib/libs/opensearch-lib.test.ts b/lib/libs/opensearch-lib.test.ts new file mode 100644 index 0000000000..791caccbcb --- /dev/null +++ b/lib/libs/opensearch-lib.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect, vi } from "vitest"; +import { updateFieldMapping, decodeUtf8 } from "./opensearch-lib"; +import { + OPENSEARCH_DOMAIN, + OPENSEARCH_INDEX_NAMESPACE, + errorUpdateFieldMappingHandler, +} from "mocks"; +import { mockedServiceServer as mockedServer } from "mocks/server"; + +const OPENSEARCH_INDEX = `${OPENSEARCH_INDEX_NAMESPACE}main`; + +describe("opensearch-lib tests", () => { + describe("updateFieldMapping tests", () => { + it("should handle a server error when updating a field mapping", async () => { + mockedServer.use(errorUpdateFieldMappingHandler); + + await expect(() => + updateFieldMapping(OPENSEARCH_DOMAIN, OPENSEARCH_INDEX, { + throw: "error", + }), + ).rejects.toThrowError("Response Error"); + }); + }); + + describe("decodeUtf8 tests", () => { + it("should handle decoding an invalid string", () => { + vi.stubGlobal("decodeURIComponent", () => { + throw new Error("Bad format"); + }); + + const value = "test%20value%%"; + const decoded = decodeUtf8(value); + expect(decoded).toEqual(value); + + vi.unstubAllGlobals(); + }); + }); +}); diff --git a/lib/libs/opensearch-lib.ts b/lib/libs/opensearch-lib.ts index 521b17d969..397172bb33 100644 --- a/lib/libs/opensearch-lib.ts +++ b/lib/libs/opensearch-lib.ts @@ -180,20 +180,13 @@ export async function getItem( index: opensearch.Index, id: string, ): Promise { - try { - client = client || (await getClient(host)); - const response = await client.get({ id, index }); - return decodeUtf8(response).body; - } catch (error) { - if ( - (error instanceof OpensearchErrors.ResponseError && error.statusCode === 404) || - error.meta?.statusCode === 404 - ) { - console.log("Error (404) retrieving in OpenSearch:", error); - return undefined; - } - throw error; + client = client || (await getClient(host)); + const response = await client.get({ id, index }); + const item = decodeUtf8(response).body; + if (item.found === false || !item._source) { + return undefined; } + return item; } export async function getItems(ids: string[]): Promise { @@ -210,18 +203,12 @@ export async function getItems(ids: string[]): Promise { }); return response.body.docs.reduce((acc, doc) => { - if (doc.found && doc._source) { - try { - return acc.concat(doc._source); - } catch (e) { - console.error(`Failed to parse JSON for document with ID ${doc._id}:`, e); - return acc; - } + if (doc && doc.found && doc._source) { + return acc.concat(doc._source); } else { console.error(`Document with ID ${doc._id} not found.`); + return acc; } - - return acc; }, []); } catch (e) { console.log({ e }); @@ -264,7 +251,7 @@ export async function updateFieldMapping( } } -function decodeUtf8(data: any): any { +export function decodeUtf8(data: any): any { if (typeof data === "string") { try { return decodeURIComponent(escape(data)); diff --git a/mocks/handlers/opensearch/index.ts b/mocks/handlers/opensearch/index.ts index 5d0c78199b..48e8c921eb 100644 --- a/mocks/handlers/opensearch/index.ts +++ b/mocks/handlers/opensearch/index.ts @@ -21,8 +21,10 @@ export { errorCreateIndexHandler, errorUpdateFieldMappingHandler, errorBulkUpdateDataHandler, + rateLimitBulkUpdateDataHandler, errorDeleteIndexHandler, } from "./indices"; +export { errorMainMultiDocumentHandler } from "./main"; export { errorSecurityRolesMappingHandler } from "./security"; export { errorSubtypeSearchHandler } from "./subtypes"; export { errorTypeSearchHandler } from "./types"; diff --git a/mocks/handlers/opensearch/indices.ts b/mocks/handlers/opensearch/indices.ts index 6a40734f8b..ea306e18de 100644 --- a/mocks/handlers/opensearch/indices.ts +++ b/mocks/handlers/opensearch/indices.ts @@ -30,6 +30,12 @@ const defaultBulkUpdateDataHandler = http.post( () => new HttpResponse(null, { status: 200 }), ); +export const rateLimitBulkUpdateDataHandler = http.post( + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/_bulk", + () => new HttpResponse("Rate limit exceeded", { status: 429 }), + { once: true }, +); + export const errorBulkUpdateDataHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/_bulk", () => new HttpResponse("Internal server error", { status: 500 }), diff --git a/mocks/handlers/opensearch/main.ts b/mocks/handlers/opensearch/main.ts index fdcd6efb7f..eae507d289 100644 --- a/mocks/handlers/opensearch/main.ts +++ b/mocks/handlers/opensearch/main.ts @@ -42,6 +42,11 @@ const defaultMainMultiDocumentHandler = http.post( }, ); +export const errorMainMultiDocumentHandler = http.post( + "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_mget", + () => new HttpResponse("Internal server error", { status: 500 }), +); + const defaultMainSearchHandler = http.post( "https://vpc-opensearchdomain-mock-domain.us-east-1.es.amazonaws.com/test-namespace-main/_search", async ({ request }) => { diff --git a/vitest.config.ts b/vitest.config.ts index b1518d5c67..18ce87308e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ "build_run", ".cdk", "docs/**", + "**/vitest.setup.ts", "lib/libs/webforms/**", "lib/libs/email/mock-data/**", "react-app/src/features/webforms/**", From 4f03c81ca2a227e3fed187db79efc15ea97ce446 Mon Sep 17 00:00:00 2001 From: tiffanyvu Date: Thu, 16 Jan 2025 06:23:29 -0800 Subject: [PATCH 16/17] fix(login): Redirect after login based on user roles 2 (#1028) * check in dashboard loader func * rm space * extract --- react-app/src/features/dashboard/index.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/react-app/src/features/dashboard/index.tsx b/react-app/src/features/dashboard/index.tsx index 14bdd64e42..e7bc5c73cf 100644 --- a/react-app/src/features/dashboard/index.tsx +++ b/react-app/src/features/dashboard/index.tsx @@ -3,6 +3,7 @@ import { Plus as PlusIcon } from "lucide-react"; import { getUser, useGetUser } from "@/api"; import { WaiversList } from "./Lists/waivers"; import { SpasList } from "./Lists/spas"; +import { UserRoles } from "shared-types"; import { OsProvider, type OsTab, @@ -40,7 +41,14 @@ export const Dashboard = () => { const { data: userObj } = useGetUser(); const osData = useOsData(); - if (userObj === undefined) { + const isAbleToAccessDashboard = () => { + return ( + userObj.user["custom:cms-roles"] && + Object.values(UserRoles).some((role) => userObj.user["custom:cms-roles"].includes(role)) + ); + }; + + if (userObj === undefined || !isAbleToAccessDashboard()) { return ; } From 61b45730a31801bbba763e8dc29dfa449d2a23e9 Mon Sep 17 00:00:00 2001 From: Thomas Walker Date: Thu, 16 Jan 2025 11:53:27 -0500 Subject: [PATCH 17/17] feat(bug) update artifact update (#1032) --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5d0925e70f..67238eba1a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -149,7 +149,7 @@ jobs: role-duration-seconds: 10800 - name: Run e2e tests run: run e2e - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report @@ -220,7 +220,7 @@ jobs: join_by() { local IFS="$1"; shift; echo "$*"; } echo "["$(join_by "," "${resourceData[@]}")"]" > "resources/aws-resources.json" - name: Archive stage resources - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: aws-resources-${{ startsWith(github.ref_name, 'snyk-') && 'snyk' || github.ref_name }} path: resources/aws-resources.json