Skip to content

Commit

Permalink
feat(lamdba): add admin function for NOSO 2 (#1060)
Browse files Browse the repository at this point in the history
* rewriting noso admin to be a function for an empty package

* we adding some tests up in this b

* making asharon fixes

* changed back the string check for event.body

* putting back the .and zod thing

* needed to adjust logic so that if the package is in SeaTool we still do send a message to kafka so it shows on our dashboard

* updated tests to reflect changes for checking for seaTool packages

* :( i have to remove this extra `s`

* setting the item origin to SEATool on line 78 was redundant

* removed .and on zod, and created a new zod schema for sinkMain

* i spelled like all the properties wrong

* in changing the sinkmain schema i also have to change the changelog schema

* change the admin change title

* forgt the status on the extended schema

* added mockEvent prop]

* fixed the ui part

* fixed test and other errors

* removed unused code, adde3d example json comment

* embarassing left andie console logs
  • Loading branch information
andieswift authored Jan 29, 2025
1 parent 896c544 commit a1f4e7f
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 5 deletions.
5 changes: 4 additions & 1 deletion lib/lambda/sinkChangelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
transformUpdateValuesSchema,
transformDeleteSchema,
transformedUpdateIdSchema,
transformSubmitValuesSchema,
} from "./update/adminChangeSchemas";
import { getPackageChangelog } from "libs/api/package";

Expand Down Expand Up @@ -67,7 +68,9 @@ const processAndIndex = async ({
// query all changelog entries for this ID and create copies of all entries with new ID
if (record.isAdminChange) {
const schema = transformDeleteSchema(offset).or(
transformUpdateValuesSchema(offset).or(transformedUpdateIdSchema),
transformUpdateValuesSchema(offset)
.or(transformedUpdateIdSchema)
.or(transformSubmitValuesSchema),
);

const result = schema.safeParse(record);
Expand Down
4 changes: 3 additions & 1 deletion lib/lambda/sinkMainProcessors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {
deleteAdminChangeSchema,
updateValuesAdminChangeSchema,
updateIdAdminChangeSchema,
extendSubmitNOSOAdminSchema,
} from "./update/adminChangeSchemas";

const removeDoubleQuotesSurroundingString = (str: string) => str.replace(/^"|"$/g, "");
const adminRecordSchema = deleteAdminChangeSchema
.or(updateValuesAdminChangeSchema)
.or(updateIdAdminChangeSchema);
.or(updateIdAdminChangeSchema)
.or(extendSubmitNOSOAdminSchema);

type OneMacRecord = {
id: string;
Expand Down
34 changes: 34 additions & 0 deletions lib/lambda/update/adminChangeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,37 @@ export const transformedUpdateIdSchema = updateIdAdminChangeSchema.transform((da
id: `${data.id}`,
timestamp: Date.now(),
}));

export const submitNOSOAdminSchema = z.object({
id: z.string(),
authority: z.string(),
status: z.string(),
submitterEmail: z.string(),
submitterName: z.string(),
adminChangeType: z.literal("NOSO"),
mockEvent: z.string(),
changeMade: z.string(),
changeReason: z.string(),
});

export const extendSubmitNOSOAdminSchema = submitNOSOAdminSchema.extend({
packageId: z.string(),
origin: z.string(),
makoChangedDate: z.number(),
changedDate: z.number(),
statusDate: z.number(),
isAdminChange: z.boolean(),
state: z.string(),
event: z.string(),
stateStatus: z.string(),
cmsStatus: z.string(),
});

export const transformSubmitValuesSchema = extendSubmitNOSOAdminSchema.transform((data) => ({
...data,
adminChangeType: "NOSO",
event: "NOSO",
id: data.id,
packageId: data.id,
timestamp: Date.now(),
}));
114 changes: 114 additions & 0 deletions lib/lambda/update/submitNOSO.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { handler } from "./submitNOSO";
import { APIGatewayEvent } from "node_modules/shared-types";

import { NOT_EXISTING_ITEM_ID, TEST_ITEM_ID } from "mocks";

vi.mock("libs/handler-lib", () => ({
response: vi.fn((data) => data),
}));

describe("handler", () => {
beforeEach(() => {
vi.clearAllMocks();
process.env.topicName = "test-topic";
});

it("should return 400 if event body is missing", async () => {
const event = {} as APIGatewayEvent;
const result = await handler(event);
const expectedResult = { statusCode: 400, body: { message: "Event body required" } };

expect(result).toStrictEqual(expectedResult);
});

it("should return 400 if package ID is not found", async () => {
const noActionevent = {
body: JSON.stringify({ packageId: "123", changeReason: "Nunya", authority: "test" }),
} as APIGatewayEvent;

const resultPackage = await handler(noActionevent);

expect(resultPackage?.statusCode).toBe(400);
});
it("should return 400 if admingChangeType is not found", async () => {
const noApackageEvent = {
body: JSON.stringify({ action: "123", changeReason: "Nunya" }),
} as APIGatewayEvent;

const resultAction = await handler(noApackageEvent);

expect(resultAction?.statusCode).toBe(400);
});
it("should return 400 if existing item is entered", async () => {
const noActionevent = {
body: JSON.stringify({
id: TEST_ITEM_ID,
adminChangeType: "NOSO",
authority: "SPA",
submitterEmail: "[email protected]",
submitterName: "Name",
status: "submitted",
changeMade: "change",
mockEvent: "mock-event",
changeReason: "reason",
}),
} as APIGatewayEvent;

const result = await handler(noActionevent);

const expectedResult = {
statusCode: 400,
body: { message: `Package with id: ${TEST_ITEM_ID} already exists.` },
};
expect(result).toStrictEqual(expectedResult);
});

it("should submit a new item", async () => {
const validItem = {
body: JSON.stringify({
id: NOT_EXISTING_ITEM_ID,
authority: "Medicaid SPA",
status: "submitted",
submitterEmail: "[email protected]",
submitterName: "Name",
adminChangeType: "NOSO",
changeMade: "change",
mockEvent: "mock-event",
changeReason: "reason",
}),
} as APIGatewayEvent;

const result = await handler(validItem);

const expectedResult = {
statusCode: 200,
body: { message: `${NOT_EXISTING_ITEM_ID} has been submitted.` },
};
expect(result).toStrictEqual(expectedResult);
});

it("should fail to create a package ID with no topic name", async () => {
process.env.topicName = "";
const validItem = {
body: JSON.stringify({
id: NOT_EXISTING_ITEM_ID,
authority: "Medicaid SPA",
status: "submitted",
submitterEmail: "[email protected]",
submitterName: "Name",
adminChangeType: "NOSO",
mockEvent: "mock-event",
changeMade: "change",
changeReason: "reason",
}),
} as APIGatewayEvent;

const result = await handler(validItem);
const expectedResult = {
statusCode: 500,
body: { message: "Topic name is not defined" },
};
expect(result).toStrictEqual(expectedResult);
});
});
115 changes: 115 additions & 0 deletions lib/lambda/update/submitNOSO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { response } from "libs/handler-lib";
import { APIGatewayEvent } from "aws-lambda";
import { produceMessage } from "libs/api/kafka";
import { getPackage } from "libs/api/package";
import { ItemResult } from "shared-types/opensearch/main";
import { submitNOSOAdminSchema } from "./adminChangeSchemas";
import { z } from "zod";

import { getStatus } from "shared-types";

/*
EXAMPLE EVENT JSON:
{
"body": {
"id": "CO-34304.R00.01",
"authority": "1915(c)",
"status": "Submitted",
"submitterEmail": "george@example.com",
"submitterName": "George Harrison",
"adminChangeType": "NOSO",
"mockEvent": "app-k", //needed for future actions
"changeMade": "CO-34304.R00.01 added to OneMAC.Package not originally submitted in OneMAC. Contact your CPOC to verify the initial submission documents.",
"changeReason": "Per request from CMS, this package was added to OneMAC."
}
}
*/

interface submitMessageType {
id: string;
authority: string;
status: string;
submitterEmail: string;
submitterName: string;
adminChangeType: string;
stateStatus: string;
cmsStatus: string;
}

const sendSubmitMessage = async (item: submitMessageType) => {
const topicName = process.env.topicName as string;
if (!topicName) {
throw new Error("Topic name is not defined");
}

const currentTime = Date.now();

await produceMessage(
topicName,
item.id,
JSON.stringify({
...item,
packageId: item.id,
origin: "SEATool",
isAdminChange: true,
adminChangeType: "NOSO",
description: null,
event: "NOSO",
state: item.id.substring(0, 2),
makoChangedDate: currentTime,
changedDate: currentTime,
statusDate: currentTime,
}),
);

return response({
statusCode: 200,
body: { message: `${item.id} has been submitted.` },
});
};

export const handler = async (event: APIGatewayEvent) => {
if (!event.body) {
return response({
statusCode: 400,
body: { message: "Event body required" },
});
}

try {
const item = submitNOSOAdminSchema.parse(
typeof event.body === "string" ? JSON.parse(event.body) : event.body,
);

const { stateStatus, cmsStatus } = getStatus(item.status);
// check if it already exsists
const currentPackage: ItemResult | undefined = await getPackage(item.id);

if (currentPackage && currentPackage.found == true) {
// if it exists and has origin OneMAC we shouldn't override it
if (currentPackage._source.origin === "OneMAC") {
return response({
statusCode: 400,
body: { message: `Package with id: ${item.id} already exists.` },
});
}
}

return await sendSubmitMessage({ ...item, stateStatus, cmsStatus });
} catch (err) {
console.error("Error has occured submitting package:", err);
if (err instanceof z.ZodError) {
return response({
statusCode: 400,
body: { message: err.errors },
});
}

return response({
statusCode: 500,
body: { message: err.message || "Internal Server Error" },
});
}
};
3 changes: 2 additions & 1 deletion lib/packages/shared-types/opensearch/changelog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ export type Document = Omit<AppkDocument, "event"> &
| "withdraw-rai"
| "update-values"
| "update-id"
| "delete";
| "delete"
| "NOSO";
};

export type Response = Res<Document>;
Expand Down
1 change: 1 addition & 0 deletions lib/packages/shared-types/opensearch/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export type Document = AppkDocument &
adminChangeType?: string;
changeMade?: string;
idToBeUpdated?: string;
mockEvent?: string;
};

export type Response = Res<Document>;
Expand Down
11 changes: 11 additions & 0 deletions lib/stacks/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,17 @@ export class Api extends cdk.NestedStack {
indexNamespace,
},
},
{
id: "submitNOSO",
entry: join(__dirname, "../lambda/update/submitNOSO.ts"),
environment: {
dbInfoSecretName,
topicName,
brokerString,
osDomain: `https://${openSearchDomainEndpoint}`,
indexNamespace,
},
},
{
id: "getSystemNotifs",
entry: join(__dirname, "../lambda/getSystemNotifs.ts"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,13 @@ export const UploadSubsequentDocuments = () => {
return <Navigate to="/dashboard" />;
}

const originalSubmissionEvent = (submission._source.changelog ?? []).reduce<string | null>(
let originalSubmissionEvent = (submission._source.changelog ?? []).reduce<string | null>(
(acc, { _source }) => (_source?.event ? _source?.event : acc),
null,
);
if (originalSubmissionEvent === "NOSO") {
originalSubmissionEvent = submission._source.mockEvent;
}

const schema: SchemaWithEnforcableProps | undefined = formSchemas[originalSubmissionEvent];

Expand Down
4 changes: 3 additions & 1 deletion react-app/src/features/package/admin-changes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@ export const AdminChange: FC<opensearch.changelog.Document> = (props) => {
}
return ["Disable Formal RAI Response Withdraw", AC_WithdrawDisabled];
}
case "NOSO":
return [props.changeType || "Package Added", AC_LegacyAdminChange];
case "legacy-admin-change":
return [props.changeType || "Manual Update", AC_LegacyAdminChange];
default:
return [BLANK_VALUE, AC_Update];
}
}, [props.actionType, props.changeType]);
}, [props.event, props.changeType, props.raiWithdrawEnabled]);

return (
<AccordionItem key={props.id} value={props.id}>
Expand Down

0 comments on commit a1f4e7f

Please sign in to comment.