Skip to content

Commit

Permalink
Merge branch 'master' into galapagos
Browse files Browse the repository at this point in the history
  • Loading branch information
jdinh8124 authored Jan 19, 2024
2 parents 2b3af2f + f7cbba5 commit d5e8b39
Show file tree
Hide file tree
Showing 27 changed files with 740 additions and 512 deletions.
3 changes: 1 addition & 2 deletions src/packages/shared-utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
export * from "./user-helper";
export * from "./s3-url-parser";
export { isStateUser } from "./is-state-user";
export * from "./rai-helper";
export * from "./regex";
export * from "./package-actions/getAvailableActions";
export * from "./packageCheck";
export * from "./seatool-date-helper"
export * from "./seatool-date-helper";
12 changes: 0 additions & 12 deletions src/packages/shared-utils/is-state-user.ts

This file was deleted.

17 changes: 10 additions & 7 deletions src/packages/shared-utils/package-actions/rules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Action, ActionRule, SEATOOL_STATUS, finalDispositionStatuses } from "../../shared-types";
import { isCmsUser, isStateUser, isCmsReadonlyUser, isCmsWriteUser } from "../user-helper";
import { PackageCheck } from "../packageCheck";
import {
Action,
ActionRule,
SEATOOL_STATUS,
finalDispositionStatuses,
} from "../../shared-types";
import { isStateUser, isCmsWriteUser } from "../user-helper";

const arIssueRai: ActionRule = {
action: Action.ISSUE_RAI,
Expand All @@ -24,7 +28,7 @@ const arEnableWithdrawRaiResponse: ActionRule = {
checker.isNotWithdrawn &&
checker.hasRaiResponse &&
!checker.hasEnabledRaiWithdraw &&
isCmsWriteUser(user)
isCmsWriteUser(user),
};

const arDisableWithdrawRaiResponse: ActionRule = {
Expand All @@ -33,7 +37,7 @@ const arDisableWithdrawRaiResponse: ActionRule = {
checker.isNotWithdrawn &&
checker.hasRaiResponse &&
checker.hasEnabledRaiWithdraw &&
isCmsWriteUser(user)
isCmsWriteUser(user),
};

const arWithdrawRaiResponse: ActionRule = {
Expand All @@ -47,8 +51,7 @@ const arWithdrawRaiResponse: ActionRule = {
const arWithdrawPackage: ActionRule = {
action: Action.WITHDRAW_PACKAGE,
check: (checker, user) =>
!checker.hasStatus(finalDispositionStatuses)
&& isStateUser(user),
!checker.hasStatus(finalDispositionStatuses) && isStateUser(user),
};

export default [
Expand Down
29 changes: 19 additions & 10 deletions src/packages/shared-utils/user-helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { CMS_ROLES, CMS_WRITE_ROLES, CMS_READ_ONLY_ROLES, CognitoUserAttributes, STATE_ROLES } from "../shared-types";
import {
CMS_ROLES,
CMS_WRITE_ROLES,
CMS_READ_ONLY_ROLES,
CognitoUserAttributes,
STATE_ROLES,
} from "../shared-types";

export const isCmsUser = (user: CognitoUserAttributes) => {
const userRoles = user["custom:cms-roles"];
Expand Down Expand Up @@ -31,16 +37,19 @@ export const isCmsReadonlyUser = (user: CognitoUserAttributes) => {
}
}
return false;
}
};

export const isStateUser = (user: CognitoUserAttributes) => {
const userRoles = user["custom:cms-roles"];
export const isStateUser = (user: CognitoUserAttributes | null) => {
if (!user) {
return false;
} else {
const userRoles = user["custom:cms-roles"];

for (const role of STATE_ROLES) {
if (userRoles.includes(role)) {
return true;
for (const role of STATE_ROLES) {
if (userRoles.includes(role)) {
return true;
}
}
return false;
}
return false;

}
};
69 changes: 27 additions & 42 deletions src/services/api/handlers/getAttachmentUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

import * as os from "./../../../libs/opensearch-lib";
import { getStateFilter } from "../libs/auth/user";
import { opensearch } from "shared-types";
import { getPackage, getPackageChangelog } from "../libs/package";
if (!process.env.osDomain) {
throw "ERROR: osDomain env variable is required,";
}
Expand All @@ -29,52 +28,37 @@ export const handler = async (event: APIGatewayEvent) => {
try {
const body = JSON.parse(event.body);

let query: any = {};
query = {
query: {
bool: {
must: [
{
ids: {
values: [body.id],
},
},
],
},
},
};
const stateFilter = await getStateFilter(event);
if (stateFilter) {
query.query.bool.must.push(stateFilter);
}

const results = (await os.search(
process.env.osDomain,
"main",
query
)) as opensearch.Response<opensearch.main.Document>;

if (!results) {
const mainResult = await getPackage(body.id);
if (!mainResult) {
return response({
statusCode: 404,
body: { message: "No record found for the given id" },
});
}

const allAttachments = [
...(results.hits.hits[0]._source.attachments || []),
...Object.values(results.hits.hits[0]._source.rais).flatMap((entry) => [
...(entry.request?.attachments || []),
...(entry.response?.attachments || []),
...(entry.withdraw?.attachments || []),
]),
];

if (
!allAttachments.some((e) => {
return e.bucket === body.bucket && e.key === body.key;
})
) {
const stateFilter = await getStateFilter(event);
if (stateFilter) {
const stateAccessAllowed = stateFilter?.terms.state.includes(
mainResult?._source?.state?.toLocaleLowerCase() || ""
);

if (!stateAccessAllowed) {
return response({
statusCode: 404,
body: { message: "state access not permitted for the given id" },
});
}
}

// add state
// Do we want to check
const changelogs = await getPackageChangelog(body.id);
const attachmentExists = changelogs.hits.hits.some((CL) => {
return CL._source.attachments?.some(
(ATT) => ATT.bucket === body.bucket && ATT.key === body.key
);
});
if (!attachmentExists) {
return response({
statusCode: 500,
body: {
Expand Down Expand Up @@ -136,6 +120,7 @@ async function getClient(bucket: string) {
}
}

//TODO: add check for resource before signing URL
async function generatePresignedUrl(
bucket: string,
key: string,
Expand Down
4 changes: 4 additions & 0 deletions src/services/api/libs/auth/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const isAuthorizedToGetPackageActions = async (
);
};

// originally intended for /_search
export const getStateFilter = async (event: APIGatewayEvent) => {
// Retrieve authentication details of the user
const authDetails = getAuthDetails(event);
Expand All @@ -137,11 +138,14 @@ export const getStateFilter = async (event: APIGatewayEvent) => {
if (userAttributes["custom:state"]) {
const filter = {
terms: {
//NOTE: this could instead be
// "state.keyword": userAttributes["custom:state"],
state: userAttributes["custom:state"]
.split(",")
.map((state) => state.toLocaleLowerCase()),
},
};

return filter;
} else {
throw "State user detected, but no associated states. Cannot continue";
Expand Down
11 changes: 9 additions & 2 deletions src/services/api/libs/package/changelog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import * as os from "../../../../libs/opensearch-lib";
import { opensearch } from "shared-types";

export const getPackageChangelog = async (packageId: string) => {
export const getPackageChangelog = async (
packageId: string,
filter: any[] = []
) => {
if (!process.env.osDomain) {
throw new Error("process.env.osDomain must be defined");
}
Expand All @@ -10,6 +13,10 @@ export const getPackageChangelog = async (packageId: string) => {
from: 0,
size: 200,
sort: [{ timestamp: "desc" }],
query: { bool: { must: [{ term: { "packageId.keyword": packageId } }] } },
query: {
bool: {
must: [{ term: { "packageId.keyword": packageId } }].concat(filter),
},
},
})) as opensearch.changelog.Response;
};
14 changes: 12 additions & 2 deletions src/services/ui/src/components/Inputs/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";
import { Loader2 } from "lucide-react";

const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
Expand Down Expand Up @@ -38,17 +39,26 @@ export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
(
{ className, variant, size, loading, children, asChild = false, ...props },
ref
) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
>
<>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{children}
</>
</Comp>
);
}
);
Expand Down
16 changes: 0 additions & 16 deletions src/services/ui/src/hooks/useActionFormController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,17 @@ import { submit } from "@/api/submissionService";
import { buildActionUrl } from "@/lib";
import { PlanType } from "shared-types";

type DataConditionError = {
message: string;
};

export const useActionSubmitHandler = <D extends FieldValues>({
authority,
addDataConditions,
}: {
formHookReturn: UseFormReturn<D>;
authority: PlanType;
/** Reserved for things zod cannot check. */
addDataConditions?: ((data: D) => DataConditionError | null)[];
}): SubmitHandler<D> => {
const { id, type } = useParams("/action/:id/:type");
const { data: user } = useGetUser();
const { setSuccessModalOpen, setErrorModalOpen } = useModalContext();
return async (data) => {
try {
if (addDataConditions?.length) {
const errors = addDataConditions
.map((fn) => fn(data))
.filter((err) => err) // Filter out nulls
.map((err) => err?.message);
if (errors.length)
throw Error(`Additional conditions were not met: ${errors}`);
}
// TODO: Type update for submit generic
await submit<D & { id: string }>({
data: { ...data, id: id! },
endpoint: buildActionUrl(type!),
Expand Down
35 changes: 18 additions & 17 deletions src/services/ui/src/pages/actions/IssueRai.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as I from "@/components/Inputs";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { opensearch, PlanType } from "shared-types";
import { ActionFormTemplate } from "@/pages/actions/template";
import { useActionSubmitHandler } from "@/hooks/useActionFormController";
import { ActionFormIntro } from "@/pages/actions/common";
import { FormSetup } from "@/pages/actions/setups";

const preSubmitMessage =
"Once you submit this form, a confirmation email is sent to you and to the State.";
export const RaiIssue = ({
item,
schema,
Expand All @@ -26,25 +26,26 @@ export const RaiIssue = ({
item={item}
formController={form}
submitHandler={handleSubmit}
intro={
<ActionFormIntro title={"Formal RAI Details"}>
<I.RequiredIndicator /> Indicates a required field
<p className="font-light mb-6 max-w-4xl">
Issuance of a Formal RAI in OneMAC will create a Formal RAI email
sent to the State. This will also create a section in the package
details summary for you and the State to have record. Please attach
the Formal RAI Letter along with any additional information or
comments in the provided text box. Once you submit this form, a
confirmation email is sent to you and to the State.{" "}
<strong className="bold">
If you leave this page, you will lose your progress on this form.
</strong>
</p>
</ActionFormIntro>
title={"Formal RAI Details"}
description={
<p className="font-light mb-6 max-w-4xl">
Issuance of a Formal RAI in OneMAC will create a Formal RAI email sent
to the State. This will also create a section in the package details
summary for you and the State to have record. Please attach the Formal
RAI Letter along with any additional information or comments in the
provided text box. {preSubmitMessage}
<strong className="bold">
If you leave this page, you will lose your progress on this form.
</strong>
</p>
}
preSubmitMessage={preSubmitMessage}
attachments={attachments}
attachmentFaqLink={"/faq/#medicaid-spa-rai-attachments"}
requireAddlInfo
addlInfoInstructions={
<p>Add anything else that you would like to share with the State.</p>
}
/>
);
};
Loading

0 comments on commit d5e8b39

Please sign in to comment.