Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(medicaid submission): Medicaid Form Submission #181

Merged
merged 71 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 70 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
e186550
initial structure
13bfrancis Oct 25, 2023
47d8fcb
More layout
13bfrancis Oct 26, 2023
80f0f0f
setup react-hook-form and zod and finish layout
13bfrancis Oct 27, 2023
cb14479
fix label issue and some fancy typescript to make it typesafe (tuples)
13bfrancis Oct 27, 2023
c97eab8
add open sans font
13bfrancis Oct 27, 2023
442f453
mostly working state
13bfrancis Oct 30, 2023
bbb2e8a
fix uploads
13bfrancis Oct 30, 2023
c531d27
attachment stuff
mdial89f Oct 31, 2023
3525753
make mike happy
13bfrancis Oct 31, 2023
979961b
yut
mdial89f Oct 31, 2023
61ac1bf
yut
mdial89f Oct 31, 2023
be1871a
make submission things happen for dashboard
13bfrancis Oct 31, 2023
4846020
Merge branch 'forms' of https://github.com/Enterprise-CMCS/mako into …
13bfrancis Oct 31, 2023
4c8ed5b
yut
mdial89f Oct 31, 2023
3b00db7
working
mdial89f Oct 31, 2023
eaf2adf
routing
mdial89f Oct 31, 2023
e2e5b10
crumbs
mdial89f Oct 31, 2023
1b0b72f
date thing
mdial89f Oct 31, 2023
0b60272
button fix thing
mdial89f Oct 31, 2023
708779f
buttons and such
mdial89f Oct 31, 2023
e14090a
remove typo
mdial89f Oct 31, 2023
76ce986
rm
mdial89f Oct 31, 2023
31ce0a6
rm axios
mdial89f Oct 31, 2023
7320c89
free brittney
mdial89f Oct 31, 2023
471a55a
asdf
mdial89f Nov 1, 2023
c68160d
meh.. populate with the state
mdial89f Nov 1, 2023
65823b0
we should be able to clean this up
mdial89f Nov 1, 2023
c4b13d4
Pass title in the payload
mdial89f Nov 1, 2023
c4c2c29
error banner at bottom
mdial89f Nov 1, 2023
27ac23a
basic loading spinner
mdial89f Nov 1, 2023
0849594
modal by fire
mdial89f Nov 1, 2023
5b79944
blue hyperlinks
mdial89f Nov 1, 2023
b2ff70c
modals for success/failure
mdial89f Nov 1, 2023
ca9d1f4
language
mdial89f Nov 1, 2023
86319a0
rm dropdown
mdial89f Nov 1, 2023
c84e9d8
danke ben
mdial89f Nov 1, 2023
f234c01
Fix other peoples issues
benjaminpaige Nov 1, 2023
3cdd398
title fix. padma 1
mdial89f Nov 1, 2023
7ba4409
syntax padma 2
mdial89f Nov 1, 2023
f62cbb3
open in new tab, but idk how to link to a speicifc faq question... pa…
mdial89f Nov 1, 2023
d781958
correct FAQ... padma 5
mdial89f Nov 1, 2023
6e01141
spa ce bewteen sentences... padma 10
mdial89f Nov 1, 2023
30f3774
add faq page as new tab link, but still dont know how to link to spec…
mdial89f Nov 1, 2023
4a330b7
add text... padma 12
mdial89f Nov 1, 2023
8b33d93
make required attachment required... padma 13
mdial89f Nov 1, 2023
2e408cd
drop timestamp.. padma 15
mdial89f Nov 1, 2023
cbea2c6
add missing word... padma 18
mdial89f Nov 1, 2023
a59c83b
multi file, removable file, capped at 80... padma 20, 25
mdial89f Nov 2, 2023
1b44cb7
language
mdial89f Nov 2, 2023
4021cbc
add timestamp to zip
mdial89f Nov 2, 2023
eecfff0
cancel modal
mdial89f Nov 2, 2023
5896b29
FAQ fixes, thanks wale/kevin. padma 4.2 and 11.2
mdial89f Nov 2, 2023
b8fa0fb
fixes
mdial89f Nov 2, 2023
c5c2dce
fix regex
mdial89f Nov 2, 2023
37b7922
fixes
mdial89f Nov 2, 2023
ebae5d0
uc
mdial89f Nov 3, 2023
d6ffd41
remove help text
mdial89f Nov 3, 2023
6ea03b2
modal updates
mdial89f Nov 3, 2023
4f1559e
allow past dates
mdial89f Nov 3, 2023
ddecb72
limit file types
mdial89f Nov 6, 2023
54597a7
remove unused
mdial89f Nov 6, 2023
0249abc
Auto upcase the ID field text
mdial89f Nov 6, 2023
220b907
Check user has access to state
mdial89f Nov 6, 2023
28f7d60
Check that the SPA ID does not already exist
mdial89f Nov 6, 2023
cfe68ad
format with slashes
mdial89f Nov 6, 2023
1686cea
Destructure per kevin
mdial89f Nov 6, 2023
99c6753
Abstract per kevin
mdial89f Nov 6, 2023
de18f05
Merge branch 'master' into forms
mdial89f Nov 6, 2023
41cb7dd
Fix 29
mdial89f Nov 6, 2023
c3d1752
fix file size
mdial89f Nov 6, 2023
5fd0e77
change name
mdial89f Nov 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/packages/shared-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from "./errors";
export * from "./seatool";
export * from "./onemac";
export * from "./opensearch";
export * from "./uploads";
export * from "./actions";
42 changes: 32 additions & 10 deletions src/packages/shared-types/onemac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { z } from "zod";
import { s3ParseUrl } from "shared-utils/s3-url-parser";

const onemacAttachmentSchema = z.object({
s3Key: z.string(),
s3Key: z.string().nullish(),
filename: z.string(),
title: z.string(),
contentType: z.string(),
url: z.string().url(),
contentType: z.string().nullish(),
url: z.string().url().nullish(),
bucket: z.string().nullish(),
key: z.string().nullish(),
uploadDate: z.number().nullish(),
});

export const onemacSchema = z.object({
Expand All @@ -30,13 +33,30 @@ export const transformOnemac = (id: string) => {
id,
attachments:
data.attachments?.map((attachment) => {
const uploadDate = parseInt(attachment.s3Key.split("/")[0]);
const parsedUrl = s3ParseUrl(attachment.url);
if (!parsedUrl) return null;
const { bucket, key } = parsedUrl;
// this is a legacy onemac attachment
let bucket = "";
let key = "";
let uploadDate = 0;
if ("bucket" in attachment) {
bucket = attachment.bucket as string;
}
if ("key" in attachment) {
key = attachment.key as string;
}
if ("uploadDate" in attachment) {
uploadDate = attachment.uploadDate as number;
}
if (bucket == "") {
const parsedUrl = s3ParseUrl(attachment.url || "");
if (!parsedUrl) return null;
bucket = parsedUrl.bucket;
key = parsedUrl.key;
uploadDate = parseInt(attachment.s3Key?.split("/")[0] || "0");
}

return {
...attachment,
title: attachment.title,
filename: attachment.filename,
uploadDate,
bucket,
key,
Expand All @@ -49,8 +69,10 @@ export const transformOnemac = (id: string) => {
submissionTimestamp: response.submissionTimestamp,
attachments:
response.attachments?.map((attachment) => {
const uploadDate = parseInt(attachment.s3Key.split("/")[0]);
const parsedUrl = s3ParseUrl(attachment.url);
const uploadDate = parseInt(
attachment.s3Key?.split("/")[0] || "0"
);
const parsedUrl = s3ParseUrl(attachment.url || "");
if (!parsedUrl) return null;
const { bucket, key } = parsedUrl;

Expand Down
86 changes: 86 additions & 0 deletions src/packages/shared-types/uploads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
export type FileTypeInfo = {
extension: string;
description: string;
mime: string;
};

export const FILE_TYPES: FileTypeInfo[] = [
{ extension: ".bmp", description: "Bitmap Image File", mime: "image/bmp" },
{
extension: ".csv",
description: "Comma-separated Values",
mime: "text/csv",
},
{
extension: ".doc",
description: "MS Word Document",
mime: "application/msword",
},
{
extension: ".docx",
description: "MS Word Document (xml)",
mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
},
{
extension: ".gif",
description: "Graphics Interchange Format",
mime: "image/gif",
},
{
extension: ".jpeg",
description: "Joint Photographic Experts Group",
mime: "image/jpeg",
},
{
extension: ".odp",
description: "OpenDocument Presentation (OpenOffice)",
mime: "application/vnd.oasis.opendocument.presentation",
},
{
extension: ".ods",
description: "OpenDocument Spreadsheet (OpenOffice)",
mime: "application/vnd.oasis.opendocument.spreadsheet",
},
{
extension: ".odt",
description: "OpenDocument Text (OpenOffice)",
mime: "application/vnd.oasis.opendocument.text",
},
{
extension: ".png",
description: "Portable Network Graphic",
mime: "image/png",
},
{
extension: ".pdf",
description: "Portable Document Format",
mime: "application/pdf",
},
{
extension: ".ppt",
description: "MS Powerpoint File",
mime: "application/vnd.ms-powerpoint",
},
{
extension: ".pptx",
description: "MS Powerpoint File (xml)",
mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
},
{
extension: ".rtf",
description: "Rich Text Format",
mime: "application/rtf",
},
{ extension: ".tif", description: "Tagged Image Format", mime: "image/tiff" },
{ extension: ".txt", description: "Text File Format", mime: "text/plain" },
{
extension: ".xls",
description: "MS Excel File",
mime: "application/vnd.ms-excel",
},
{
extension: ".xlsx",
description: "MS Excel File (xml)",
mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
},
];
15 changes: 13 additions & 2 deletions src/services/api/handlers/getAttachmentUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
try {
const body = JSON.parse(event.body);

let query: any = {};

Check warning on line 19 in src/services/api/handlers/getAttachmentUrl.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
query = {
query: {
bool: {
Expand Down Expand Up @@ -69,7 +69,12 @@
}

// Now we can generate the presigned url
const url = await generateLegacyPresignedS3Url(body.bucket, body.key, 60);
const url = await generatePresignedUrl(
body.bucket,
body.key,
body.filename,
60
);

return response<unknown>({
statusCode: 200,
Expand Down Expand Up @@ -112,14 +117,20 @@
}
}

async function generateLegacyPresignedS3Url(bucket, key, expirationInSeconds) {
async function generatePresignedUrl(
bucket,
key,
filename,
expirationInSeconds
) {
// Get an S3 client
const client = await getClient(bucket);

// Create a command to get the object (you can adjust this according to your use case)
const getObjectCommand = new GetObjectCommand({
Bucket: bucket,
Key: key,
ResponseContentDisposition: `filename ="${filename}"`,
});

// Generate a presigned URL
Expand Down
87 changes: 81 additions & 6 deletions src/services/api/handlers/submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ const config = {
database: "SEA",
};

import { Kafka, KafkaMessage } from "kafkajs";
import { OneMacSink, transformOnemac } from "shared-types";

const kafka = new Kafka({
clientId: "submit",
brokers: process.env.brokerString.split(","),
retry: {
initialRetryTime: 300,
retries: 8,
},
ssl: {
rejectUnauthorized: false,
},
});

const producer = kafka.producer();

export const submit = async (event: APIGatewayEvent) => {
try {
const body = JSON.parse(event.body);
Expand All @@ -27,10 +44,20 @@ export const submit = async (event: APIGatewayEvent) => {
});
}

const pool = await sql.connect(config);
if (body.authority !== "medicaid spa") {
return response({
statusCode: 400,
body: {
message:
"The Mako Submissions API only supports Medicaid SPA at this time",
},
});
}

const pool = await sql.connect(config);
console.log(body);
const query = `
Insert into SEA.dbo.State_Plan (ID_Number, State_Code, Region_ID, Plan_Type, Submission_Date, Status_Date, SPW_Status_ID, Budget_Neutrality_Established_Flag)
Insert into SEA.dbo.State_Plan (ID_Number, State_Code, Region_ID, Plan_Type, Submission_Date, Status_Date, Proposed_Date, SPW_Status_ID, Budget_Neutrality_Established_Flag)
values ('${body.id}'
,'${body.state}'
,(Select Region_ID from SEA.dbo.States where State_Code = '${
Expand All @@ -41,6 +68,9 @@ export const submit = async (event: APIGatewayEvent) => {
}')
,dateadd(s, convert(int, left(${Date.now()}, 10)), cast('19700101' as datetime))
,dateadd(s, convert(int, left(${Date.now()}, 10)), cast('19700101' as datetime))
,dateadd(s, convert(int, left(${
body.proposedEffectiveDate
}, 10)), cast('19700101' as datetime))
,(Select SPW_Status_ID from SEA.dbo.SPW_Status where SPW_Status_DESC = 'Pending')
,0)
`;
Expand All @@ -50,10 +80,29 @@ export const submit = async (event: APIGatewayEvent) => {

await pool.close();

return response({
statusCode: 200,
body: { message: "success" },
});
const message: OneMacSink = body;
const makoBody = transformOnemac(body.id).safeParse(message);
if (makoBody.success === false) {
// handle
console.log(
"MAKO Validation Error. The following record failed to parse: ",
JSON.stringify(message),
"Because of the following Reason(s): ",
makoBody.error.message
);
} else {
console.log(message);
await produceMessage(
process.env.topicName,
body.id,
JSON.stringify(message)
);

return response({
statusCode: 200,
body: { message: "success" },
});
}
} catch (error) {
console.error({ error });
return response({
Expand All @@ -63,4 +112,30 @@ export const submit = async (event: APIGatewayEvent) => {
}
};

async function produceMessage(topic, key, value) {
console.log("about to connect");
await producer.connect();
console.log("connected");

const message: KafkaMessage = {
key: key,
value: value,
partition: 0,
headers: { source: "mako" },
mdial89f marked this conversation as resolved.
Show resolved Hide resolved
};
console.log(message);

try {
await producer.send({
topic,
messages: [message],
});
console.log("Message sent successfully");
} catch (error) {
console.error("Error sending message:", error);
} finally {
await producer.disconnect();
}
}

export const handler = submit;
9 changes: 5 additions & 4 deletions src/services/data/handlers/sink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
const seaToolRecords: (SeaToolTransform | SeaToolRecordsToDelete)[] = [];
const docObject: Record<string, SeaToolTransform | SeaToolRecordsToDelete> =
{};
const rawArr: any[] = [];

Check warning on line 24 in src/services/data/handlers/sink.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

for (const recordKey of Object.keys(event.records)) {
for (const seatoolRecord of event.records[recordKey] as {
Expand Down Expand Up @@ -106,10 +106,11 @@
const id: string = decode(key);
const record = { id, ...JSON.parse(decode(value)) };
if (
record &&
record.sk === "Package" &&
record.submitterName &&
record.submitterName !== "-- --" // these records did not originate from onemac, thus we ignore them
record && // testing if we have a record
(record.origin === "micro" || // testing if this is a micro record
(record.sk === "Package" && // testing if this is a legacy onemac package record
record.submitterName &&
record.submitterName !== "-- --"))
) {
const result = transformOnemac(id).safeParse(record);
if (result.success === false) {
Expand Down
6 changes: 4 additions & 2 deletions src/services/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
"@aws-amplify/auth": "^5.4.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/open-sans": "^5.0.17",
"@heroicons/react": "^2.0.17",
"@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^3.3.1",
"@hookform/resolvers": "^3.3.2",
"@mui/lab": "^5.0.0-alpha.136",
"@mui/material": "^5.14.1",
"@mui/styled-engine": "^5.13.2",
Expand Down Expand Up @@ -50,11 +51,12 @@
"file-saver": "^2.0.5",
"framer-motion": "^10.16.1",
"jszip": "^3.10.1",
"lucide-react": "^0.268.0",
"lucide-react": "^0.291.0",
"lz-string": "^1.5.0",
"react": "^18.2.0",
"react-day-picker": "^8.8.1",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.46.2",
"react-loader-spinner": "^5.3.4",
"react-router-dom": "^6.10.0",
Expand Down
4 changes: 3 additions & 1 deletion src/services/ui/src/api/getAttachmentUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { API } from "aws-amplify";
export const getAttachmentUrl = async (
id: string,
bucket: string,
key: string
key: string,
filename: string
) => {
const response = await API.post("os", "/getAttachmentUrl", {
body: {
id,
bucket,
key,
filename,
},
});
return response.url as string;
Expand Down
Loading
Loading