Skip to content

Commit

Permalink
feat(rai-responses): Added support for rai responses (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminpaige authored Aug 30, 2023
1 parent 19891b3 commit 01f2e1c
Show file tree
Hide file tree
Showing 16 changed files with 323 additions and 90 deletions.
41 changes: 35 additions & 6 deletions src/packages/shared-types/onemac.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { z } from "zod";
import { s3ParseUrl } from "shared-utils/s3-url-parser";

const onemacAttachmentSchema = z.object({
s3Key: z.string(),
filename: z.string(),
title: z.string(),
contentType: z.string(),
url: z.string().url(),
});

export const onemacSchema = z.object({
additionalInformation: z.string().nullable().default(null),
submitterName: z.string(),
submitterEmail: z.string(),
attachments: z
attachments: z.array(onemacAttachmentSchema).nullish(),
raiResponses: z
.array(
z.object({
s3Key: z.string(),
filename: z.string(),
title: z.string(),
contentType: z.string(),
url: z.string().url(),
additionalInformation: z.string().nullable().default(null),
submissionTimestamp: z.number(),
attachments: z.array(onemacAttachmentSchema),
})
)
.nullish(),
Expand All @@ -35,9 +42,31 @@ export const transformOnemac = (id: string) => {
key,
};
}) ?? null,
raiResponses:
data.raiResponses?.map((response) => {
return {
additionalInformation: response.additionalInformation,
submissionTimestamp: response.submissionTimestamp,
attachments:
response.attachments?.map((attachment) => {
const uploadDate = parseInt(attachment.s3Key.split("/")[0]);
const parsedUrl = s3ParseUrl(attachment.url);
if (!parsedUrl) return null;
const { bucket, key } = parsedUrl;

return {
...attachment,
uploadDate,
bucket,
key,
};
}) ?? null,
};
}) ?? null,
additionalInformation: data.additionalInformation,
submitterEmail: data.submitterEmail,
submitterName: data.submitterName,
origin: "OneMAC",
}));
};

Expand Down
8 changes: 4 additions & 4 deletions src/packages/shared-types/seatool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const authorityLookup = (val: number | null): null | string => {
};

function getLeadAnalyst(eventData: SeaToolSink) {
let leadAnalystOfficerId = null;
let leadAnalystName = null;
let leadAnalystOfficerId: null | number = null;
let leadAnalystName: null | string = null;

if (
eventData.LEAD_ANALYST &&
Expand All @@ -40,8 +40,8 @@ function getLeadAnalyst(eventData: SeaToolSink) {
}

const getRaiDate = (data: SeaToolSink) => {
let raiReceivedDate = null;
let raiRequestedDate = null;
let raiReceivedDate: null | string = null;
let raiRequestedDate: null | string = null;

const raiDate =
data.RAI?.sort((a, b) => {
Expand Down
14 changes: 12 additions & 2 deletions src/services/api/handlers/getAttachmentUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

import * as os from "./../../../libs/opensearch-lib";
import { getStateFilter } from "../libs/auth/user";
import { OsMainSourceItem, OsResponse } from "shared-types";
if (!process.env.osDomain) {
throw "ERROR: osDomain env variable is required,";
}
Expand Down Expand Up @@ -34,7 +35,11 @@ export const handler = async (event: APIGatewayEvent) => {
query.query.bool.must.push(stateFilter);
}

const results = await os.search(process.env.osDomain, "main", query);
const results: OsResponse<OsMainSourceItem> = await os.search(
process.env.osDomain,
"main",
query
);

if (!results) {
return response({
Expand All @@ -43,8 +48,13 @@ export const handler = async (event: APIGatewayEvent) => {
});
}

const allAttachments = [
...results.hits[0]._source.attachments,
...results.hits[0]._source.raiResponses.map((R) => R.attachments).flat(),
];

if (
!results.hits[0]._source.attachments.some((e) => {
!allAttachments.some((e) => {
return e.bucket === body.bucket && e.key === body.key;
})
) {
Expand Down
4 changes: 3 additions & 1 deletion src/services/data/handlers/sink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const onemac: Handler = async (event) => {
record &&
record.sk === "Package" &&
record.submitterName &&
record.submitterName !== "-- --" // "-- --" indicates it did not originate from onemac
record.submitterName !== "-- --" // these records did not originate from onemac, thus we ignore them
) {
const result = transformOnemac(id).safeParse(record);
if (result.success === false) {
Expand All @@ -131,6 +131,8 @@ export const onemac: Handler = async (event) => {
attachments: undefined,
submitterEmail: undefined,
submitterName: undefined,
origin: undefined,
raiResponses: undefined,
};

docObject[id] = oneMacTombstone;
Expand Down
1 change: 1 addition & 0 deletions src/services/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@mui/styles": "^5.14.0",
"@mui/system": "^5.14.1",
"@mui/x-data-grid": "^6.10.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-slot": "^1.0.2",
Expand Down
70 changes: 70 additions & 0 deletions src/services/ui/src/components/Accordian/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";

import { cn } from "@/lib/utils";

const Accordion = AccordionPrimitive.Root;

type AccordionItemProps = {
className?: string; // Add className to prop type definition
} & React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>;

const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
AccordionItemProps
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";

type AccordionTriggerProps = {
className?: string; // Add className to prop type definition
} & React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>;

const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
AccordionTriggerProps
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;

type AccordionContentProps = {
className?: string; // Add className to prop type definition
} & React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>;

const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
AccordionContentProps
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className={cn(
"overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
className
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
141 changes: 77 additions & 64 deletions src/services/ui/src/components/AttachmentsList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,25 @@ import { format } from "date-fns";
import JSZip from "jszip";
import { saveAs } from "file-saver";
import { OsMainSourceItem } from "shared-types";
import { useState } from "react";

const handleDownloadAll = async (data: OsMainSourceItem) => {
type AttachmentList = {
id: string;
attachments:
| ({
uploadDate: number;
bucket: string;
key: string;
s3Key: string;
filename: string;
title: string;
contentType: string;
url: string;
} | null)[]
| null;
};

const handleDownloadAll = async (data: AttachmentList) => {
if (data.attachments && data.attachments.length > 0) {
const validAttachments = data.attachments.filter(
(attachment): attachment is NonNullable<typeof attachment> =>
Expand All @@ -28,7 +45,8 @@ const handleDownloadAll = async (data: OsMainSourceItem) => {
}
};

export const Attachmentslist = (data: OsMainSourceItem) => {
export const Attachmentslist = (data: AttachmentList) => {
const [loading, setLoading] = useState(false);
return (
<div>
<Table borderless className="w-full">
Expand All @@ -40,63 +58,64 @@ export const Attachmentslist = (data: OsMainSourceItem) => {
</tr>
</thead>
<tbody>
{data.attachments?.map((attachment) => {
if (!attachment) return null;
return (
<tr key={attachment.key}>
<TH rowHeader>
<p className="text-sm font-bold">{attachment.title}</p>
</TH>
<TD>
<div className="text-sm">
<button
className="text-blue-600"
onClick={async () => {
const url = await getAttachmentUrl(
data.id,
attachment.bucket,
attachment.key
);
console.log(url);
window.open(url);
}}
>
{attachment.filename}
</button>
{/* originally wanted the size as well, but that data is missing*/}
<p>
(
{attachment.contentType
? attachment.contentType.split("/").slice(-1)
: "Unknown"}
)
</p>
</div>
</TD>
<TD>
<div className="text-slate-500 text-sm">
{attachment.uploadDate ? (
<>
<p>{format(attachment.uploadDate, "MM/dd/yyyy")}</p>
<p>{format(attachment.uploadDate, "h:mm a")}</p>
</>
) : (
<p>Unknown</p>
)}
</div>
</TD>
</tr>
);
})}
{data.attachments ? (
data.attachments.map((attachment) => {
if (!attachment) return null;
return (
<tr key={attachment.key}>
<TH rowHeader>
<p className="text-sm font-bold">{attachment.title}</p>
</TH>
<TD>
<div className="text-sm">
<button
className="text-blue-600"
onClick={async () => {
const url = await getAttachmentUrl(
data.id,
attachment.bucket,
attachment.key
);
console.log(url);
window.open(url);
}}
>
{attachment.filename}
</button>
</div>
</TD>
<TD>
<div className="text-slate-500 text-sm">
{attachment.uploadDate ? (
<>
<p>{format(attachment.uploadDate, "MM/dd/yyyy")}</p>
<p>{format(attachment.uploadDate, "h:mm a")}</p>
</>
) : (
<p>Unknown</p>
)}
</div>
</TD>
</tr>
);
})
) : (
<p className="text-sm font-bold p-4">No Attachments To Show</p>
)}
</tbody>
</Table>
<div className="flex justify-end">
{data.attachments && (
<Button
buttonText="Download All"
buttonText={loading ? "Downloading" : "Download All"}
buttonVariation="secondary"
disabled={loading}
iconName="file_download"
onClick={() => handleDownloadAll(data)}
onClick={async () => {
setLoading(true);
await handleDownloadAll(data);
setLoading(false);
}}
target="_self"
type="button"
style={{ padding: "4px" }}
Expand All @@ -107,21 +126,15 @@ export const Attachmentslist = (data: OsMainSourceItem) => {
);
};

type Attachments = {
url: string;
uploadDate: number;
bucket: string;
key: string;
s3Key: string;
filename: string;
title: string;
contentType: string;
}[];

async function downloadAll(attachments: Attachments, id: string) {
async function downloadAll(
attachments: OsMainSourceItem["attachments"],
id: string
) {
if (!attachments) return null;
const downloadList = (await Promise.all(
attachments
.map(async (attachment) => {
if (!attachment) return null;
try {
const resp = await fetch(attachment.url);
if (!resp.ok) throw resp;
Expand Down
Loading

0 comments on commit 01f2e1c

Please sign in to comment.