Skip to content

Commit

Permalink
Merge pull request #317 from Enterprise-CMCS/bruce
Browse files Browse the repository at this point in the history
feat(OY2-26082): Package actions FE
  • Loading branch information
pkim-gswell authored Jan 19, 2024
2 parents 93e07a2 + aec99aa commit f2a859d
Show file tree
Hide file tree
Showing 10 changed files with 553 additions and 355 deletions.
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
71 changes: 0 additions & 71 deletions src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx

This file was deleted.

89 changes: 82 additions & 7 deletions src/services/ui/src/pages/detail/admin-changes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,97 @@
import { Accordion, DetailsSection } from "@/components";
import { FC, useMemo } from "react";
import { opensearch } from "shared-types";
import { FC } from "react";
import { AdminChange } from "./AdminChanges";
import { format } from "date-fns";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
DetailsSection,
} from "@/components";
import { BLANK_VALUE } from "@/consts";

export const ACTIONS_ADMIN = ["disable-rai-withdraw", "enable-rai-withdraw"];
export const AC_WithdrawEnabled: FC<opensearch.changelog.Document> = (
props
) => {
return (
<div className="flex flex-col gap-2">
<p className="font-bold">Change made</p>
<p>
{props.submitterName} has enabled package action to submit formal RAI
response
</p>
</div>
);
};

export const AC_WithdrawDisabled: FC<opensearch.changelog.Document> = (
props
) => {
return (
<div className="flex flex-col gap-2">
<p className="font-bold">Change made</p>
<p>
{props.submitterName} has disabled package action to submit formal RAI
response
</p>
</div>
);
};

export const AC_Update: FC<opensearch.changelog.Document> = () => {
return <p>Coming Soon</p>;
};

export const AdminChange: FC<opensearch.changelog.Document> = (props) => {
const [label, Content] = useMemo(() => {
switch (props.actionType) {
case "disable-rai-withdraw":
return ["Disable formal RAI response withdraw", AC_WithdrawDisabled];
case "enable-rai-withdraw":
return ["Enable formal RAI response withdraw", AC_WithdrawEnabled];
case "update":
return ["SPA ID update", AC_Update];
default:
return [BLANK_VALUE, AC_Update];
}
}, [props.actionType]);

return (
<AccordionItem key={props.id} value={props.id}>
<AccordionTrigger className="bg-gray-100 px-3">
<p className="flex flex-row gap-2 text-gray-600">
<strong>{label as string}</strong>
{" - "}
{format(new Date(props.timestamp), "eee, MMM d, yyyy hh:mm:ss a")}
</p>
</AccordionTrigger>
<AccordionContent className="p-4">
<Content {...props} />
</AccordionContent>
</AccordionItem>
);
};

export const AdminChanges: FC<opensearch.main.Document> = (props) => {
const data = props.changelog?.filter((CL) =>
ACTIONS_ADMIN.includes(CL._source.actionType)
["disable-rai-withdraw", "enable-rai-withdraw"].includes(
CL._source.actionType
)
);

if (!data?.length) return <></>;

return (
<DetailsSection
id="admin-updates"
title={`Administrative Package Changes (${data?.length})`}
description="Administrative changes reflect updates to specific data fields. If you have additional questions, please contact the assigned CPOC."
>
{!data?.length && <p className="text-gray-500">-- no logs --</p>}
<Accordion type="multiple" className="flex flex-col mt-6 gap-2">
<Accordion
type="multiple"
defaultValue={[data?.[0]._source.id as string]}
className="flex flex-col mt-6 gap-2"
>
{data?.map((CL) => (
<AdminChange {...CL._source} key={CL._source.id} />
))}
Expand Down
3 changes: 1 addition & 2 deletions src/services/ui/src/pages/detail/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
AdditionalInfo,
Alert,
CardWithTopBorder,
ConfirmationModal,
Expand All @@ -12,7 +11,7 @@ import { useGetUser } from "@/api/useGetUser";
import { Action, opensearch, UserRoles } from "shared-types";
import { PackageCheck } from "shared-utils";
import { useQuery } from "@/hooks";
import { getAttachmentUrl, useGetItem } from "@/api";
import { useGetItem } from "@/api";
import { BreadCrumbs } from "@/components/BreadCrumb";
import { mapActionLabel } from "@/utils";
import { useLocation } from "react-router-dom";
Expand Down
Loading

0 comments on commit f2a859d

Please sign in to comment.