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

feat(OY2-26082): Package actions FE #317

Merged
merged 14 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading