From 1fc40a0d6498ccc95da012fd4944297388c60299 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Tue, 2 Jan 2024 10:42:44 -0700 Subject: [PATCH 01/66] feat(OY2-26538): init --- src/packages/shared-types/opensearch.ts | 1 - src/packages/shared-types/opensearch/base.ts | 108 +++++++++++++++ .../shared-types/opensearch/changelog.ts | 130 ++++++++++++++++++ src/packages/shared-types/opensearch/main.ts | 28 ++++ src/services/api/handlers/search.ts | 12 +- src/services/api/serverless.yml | 2 +- src/services/data/handlers/index.ts | 6 +- src/services/data/handlers/reindex.ts | 2 + src/services/data/handlers/sink.ts | 32 +++-- src/services/ui/src/api/useSearch.ts | 4 +- .../components/Opensearch/Provider/index.tsx | 4 +- .../components/Opensearch/useOpensearch.ts | 4 +- 12 files changed, 307 insertions(+), 26 deletions(-) create mode 100644 src/packages/shared-types/opensearch/base.ts create mode 100644 src/packages/shared-types/opensearch/changelog.ts create mode 100644 src/packages/shared-types/opensearch/main.ts diff --git a/src/packages/shared-types/opensearch.ts b/src/packages/shared-types/opensearch.ts index 8e704c6b53..569a915275 100644 --- a/src/packages/shared-types/opensearch.ts +++ b/src/packages/shared-types/opensearch.ts @@ -48,7 +48,6 @@ export type OsMainSourceItem = OnemacTransform & WithdrawPackageTransform & ToggleWithdrawRaiEnabledTransform; export type OsMainSearchResponse = OsResponse; -export type SearchData = OsHits; export type ItemResult = OsHit & { found: boolean; }; diff --git a/src/packages/shared-types/opensearch/base.ts b/src/packages/shared-types/opensearch/base.ts new file mode 100644 index 0000000000..7232ec8479 --- /dev/null +++ b/src/packages/shared-types/opensearch/base.ts @@ -0,0 +1,108 @@ +import { + SeaToolTransform, + OnemacTransform, + OnemacLegacyTransform, + RaiIssueTransform, + RaiResponseTransform, + RaiWithdrawTransform, + WithdrawPackageTransform, + ToggleWithdrawRaiEnabledTransform, +} from ".."; + +export type OsHit = { + _index: string; + _id: string; + _score: number; + _source: T; + sort: Array; +}; +export type OsHits = { + hits: OsHit[]; + max_score: number; + total: { value: number; relation: "eq" }; +}; + +export type OsResponse = { + _shards: { + total: number; + failed: number; + successful: number; + skipped: number; + }; + hits: OsHits; + total: { + value: number; + }; + max_score: number | null; + took: number; + timed_out: boolean; + aggregations?: OsAggResult; +}; + +export type OsFilterType = + | "term" + | "terms" + | "match" + | "range" + | "search" + | "global_search" + | "exists"; + +export type OsRangeValue = { gte?: string; lte?: string }; +export type OsFilterValue = string | string[] | number | boolean | OsRangeValue; +export type OsMainSourceItem = OnemacTransform & + OnemacLegacyTransform & + SeaToolTransform & + RaiIssueTransform & + RaiResponseTransform & + RaiWithdrawTransform & + WithdrawPackageTransform & + ToggleWithdrawRaiEnabledTransform; +export type OsMainSearchResponse = OsResponse; +export type ItemResult = OsHit & { + found: boolean; +}; +export type OsField = + | keyof OsMainSourceItem + | `${keyof OsMainSourceItem}.keyword`; + +export type OsFilterable = { + type: OsFilterType; + label?: string; + component?: string; + field: OsField; + value: OsFilterValue; + prefix: "must" | "must_not" | "should" | "filter"; +}; + +export type OsQueryState = { + sort: { field: OsField; order: "asc" | "desc" }; + pagination: { number: number; size: number }; + filters: OsFilterable[]; + search?: string; +}; + +export type OsAggQuery = { + name: string; + type: OsFilterType; + field: OsField; + size: number; +}; + +export type OsAggBucket = { key: string; doc_count: number }; + +export type OsAggResult = Record< + string, + { + doc_count_error_upper_bound: number; + sum_other_doc_count: number; + buckets: OsAggBucket[]; + } +>; + +export type OsExportHeaderOptions = { + transform: (data: TData) => string; + name: string; +}; + +export type OsIndex = "main" | "seatool" | "changelog"; diff --git a/src/packages/shared-types/opensearch/changelog.ts b/src/packages/shared-types/opensearch/changelog.ts new file mode 100644 index 0000000000..533c3fa382 --- /dev/null +++ b/src/packages/shared-types/opensearch/changelog.ts @@ -0,0 +1,130 @@ +export type IndexDocumentChangelog = { + GSI1pk?: string; + GSI1sk?: string; + GSI2pk?: string; + GSI2sk?: string; + Latest?: number; + actionType?: string; + additionalInformation?: string; + adminChanges: { + changeType?: string; + changeMade?: string; + changeReason?: string; + changeTimestamp: string; + }[]; + approvedEffectiveDate?: string; + attachments: { + bucket?: string; + contentType?: string; + filename?: string; + key?: string; + s3key?: string; + title?: string; + uploadDate?: string; + url?: string; + }[]; + authority: string; + changedByEmail: string; + changedByName: string; + clockEndTimestamp: number; + componentId: string; + componentType: string; + convertTimestamp: number; + cpocEmail?: string; + cpocName?: string; + currentStatus?: string; + dataFrom?: string; + date: number; + description: string; + division: string; + doneByEmail: string; + doneByName: string; + email: string; + eventTimestamp: number; + finalDispositionDate: string; + fullName: string; + group: string; + id: string; + lastActivityTimestamp: number; + lastEventTimestamp: number; + lastModifiedEmail: string; + lastModifiedName: string; + latestRaiResponseTimestamp: number; + notes: string; + origin: string; + originallyFrom: string; + packageId: string; + parentId: string; + parentType: string; + pk: string; + proposedEffectiveDate: string; + rairesponses: { + additionalInformation: string; + attachments?: { + contentType?: string; + keyword?: string; + s3Key?: string; + title?: string; + url?: string; + }[]; + currentStatus: string; + eventTimestamp: number; + submissionTimestamp: number; + }[]; + raiWithdrawEnabled: boolean; + reason: string; + requestedDate: number; + responseDate: number; + reverseChron: { + keyword: string; + additionalInformation: string; + attachments?: { + contentType?: string; + keyword?: string; + s3Key?: string; + title?: string; + url?: string; + }[]; + currentStatus: string; + eventTimestamp: string; + timestamp: string; + type: string; + }[]; + reviewTeam: string; + reviewTeamEmailList: string; + role: string; + sk: string; + state: string; + status: string; + streamUpdateDate: number; + subStatus: string; + subject: string; + submissionTimestamp: number; + submitterEmail: string; + submitterName: string; + temporaryExtensionType: string; + territory: string; + timestamp: string; + title: string; + transmittalNumberWarningMessage: string; + waiverAuthority: string; + withdrawalRequests: { + properties: { + additionalInformation: { + type: "text"; + fields: { + keyword: { + type: "keyword"; + ignore_above: 256; + }; + }; + }; + submissionTimestamp: { + type: "long"; + }; + }; + }; + withdrawnDate: { + type: "long"; + }; +}; diff --git a/src/packages/shared-types/opensearch/main.ts b/src/packages/shared-types/opensearch/main.ts new file mode 100644 index 0000000000..2b7fc444ed --- /dev/null +++ b/src/packages/shared-types/opensearch/main.ts @@ -0,0 +1,28 @@ +import { + SeaToolTransform, + OnemacTransform, + OnemacLegacyTransform, + RaiIssueTransform, + RaiResponseTransform, + RaiWithdrawTransform, + WithdrawPackageTransform, + ToggleWithdrawRaiEnabledTransform, +} from ".."; + +import { OsResponse, OsHit } from "./base"; + +export type IndexDocumentMain = OnemacTransform & + OnemacLegacyTransform & + SeaToolTransform & + RaiIssueTransform & + RaiResponseTransform & + RaiWithdrawTransform & + WithdrawPackageTransform & + ToggleWithdrawRaiEnabledTransform; +export type OsMainSearchResponse = OsResponse; +export type ItemResult = OsHit & { + found: boolean; +}; +export type OsField = + | keyof IndexDocumentMain + | `${keyof IndexDocumentMain}.keyword`; diff --git a/src/services/api/handlers/search.ts b/src/services/api/handlers/search.ts index 813b3bd97e..cceec30006 100644 --- a/src/services/api/handlers/search.ts +++ b/src/services/api/handlers/search.ts @@ -8,6 +8,12 @@ if (!process.env.osDomain) { // Handler function to search index export const getSearchData = async (event: APIGatewayEvent) => { + if (!event.pathParameters || !event.pathParameters.index) { + return response({ + statusCode: 400, + body: { message: "Index path parameter required" }, + }); + } try { let query: any = {}; if (event.body) { @@ -35,7 +41,11 @@ export const getSearchData = async (event: APIGatewayEvent) => { }); } - const results = await os.search(process.env.osDomain, "main", query); + const results = await os.search( + process.env.osDomain, + event.pathParameters.index as any, + query + ); return response({ statusCode: 200, body: results, diff --git a/src/services/api/serverless.yml b/src/services/api/serverless.yml index 38b30a7db1..4862087167 100644 --- a/src/services/api/serverless.yml +++ b/src/services/api/serverless.yml @@ -107,7 +107,7 @@ functions: osDomain: ${param:osDomain} events: - http: - path: /search + path: /search/{index} method: post cors: true authorizer: aws_iam diff --git a/src/services/data/handlers/index.ts b/src/services/data/handlers/index.ts index c63596f2ba..6714abfb86 100644 --- a/src/services/data/handlers/index.ts +++ b/src/services/data/handlers/index.ts @@ -53,12 +53,12 @@ async function manageIndex() { try { await manageIndexResource({ index: "main", + // TODO: remove after rai transform update: { rais: { type: "object", enabled: false } }, }); - await manageIndexResource({ - index: "changelog", - }); + await manageIndexResource({ index: "changelog" }); + await manageIndexResource({ index: "seatool" }); } catch (error) { console.log(error); throw "ERROR: Error occured during index management."; diff --git a/src/services/data/handlers/reindex.ts b/src/services/data/handlers/reindex.ts index 419c0bb2f9..c79b4e04aa 100644 --- a/src/services/data/handlers/reindex.ts +++ b/src/services/data/handlers/reindex.ts @@ -133,6 +133,8 @@ export const deleteIndex: Handler = async () => { throw "process.env.osDomain cannot be undefined"; } await os.deleteIndex(process.env.osDomain, "main"); + await os.deleteIndex(process.env.osDomain, "changelog"); + await os.deleteIndex(process.env.osDomain, "seatool"); } catch (error: any) { if (error.meta.body.error.type == "index_not_found_exception") { console.log("Index does not exist."); diff --git a/src/services/data/handlers/sink.ts b/src/services/data/handlers/sink.ts index 4992ae377e..ed2b5a6bf1 100644 --- a/src/services/data/handlers/sink.ts +++ b/src/services/data/handlers/sink.ts @@ -146,17 +146,22 @@ export const onemacDataTransform = (props: { key: string; value?: string }) => { const record = { id, ...JSON.parse(decode(props.value)) }; // is Legacy - if (record?.origin !== "micro") { - if (record?.sk !== "Package") return null; - if (!record.submitterName) return null; - if (record.submitterName === "-- --") return null; + const isLegacy = record?.origin !== "micro"; + if (isLegacy) { + const notPackageView = record?.sk !== "Package"; + if (notPackageView) return null; + + const notOriginatingFromOnemacLegacy = + !record.submitterName || record.submitterName === "-- --"; + if (notOriginatingFromOnemacLegacy) return null; const result = transformOnemacLegacy(id).safeParse(record); return result.success ? result.data : null; } - // is new create - if (!record?.actionType) { + // NOTE: Make official decision on initial type by MVP - timebomb + const isNewRecord = !record?.actionType; + if (isNewRecord) { const result = transformOnemac(id).safeParse(record); return result.success ? result.data : null; } @@ -164,32 +169,31 @@ export const onemacDataTransform = (props: { key: string; value?: string }) => { // --------- Package-Actions ---------// // TODO: remove transform package-action below - //ENABLE_RAI_WITHDRAW if (record.actionType === Action.ENABLE_RAI_WITHDRAW) { const result = transformToggleWithdrawRaiEnabled(id).safeParse(record); return result.success ? result.data : null; } - //DISABLE_RAI_WITHDRAW + if (record.actionType === Action.DISABLE_RAI_WITHDRAW) { const result = transformToggleWithdrawRaiEnabled(id).safeParse(record); return result.success ? result.data : null; } - //ISSUE_RAI + if (record.actionType === Action.ISSUE_RAI) { const result = transformRaiIssue(id).safeParse(record); return result.success ? result.data : null; } - //RESPOND_TO_RAI + if (record.actionType === Action.RESPOND_TO_RAI) { const result = transformRaiResponse(id).safeParse(record); return result.success ? result.data : null; } - //WITHDRAW_RAI + if (record.actionType === Action.WITHDRAW_RAI) { const result = transformRaiWithdraw(id).safeParse(record); return result.success ? result.data : null; } - //WITHDRAW_PACKAGE + if (record.actionType === Action.WITHDRAW_PACKAGE) { const result = transformWithdrawPackage(id).safeParse(record); return result.success ? result.data : null; @@ -219,11 +223,11 @@ export const onemac_main = async (event: Event) => { export const onemac_changelog = async (event: Event) => { const data = Object.values(event.records).reduce((ACC, RECORDS) => { RECORDS.forEach((REC) => { - // omit delete + // omit delete event if (!REC.value) return; const record = JSON.parse(decode(REC.value)); - // omit legacy + // omit legacy record if (record?.origin !== "micro") return; // include package actions diff --git a/src/services/ui/src/api/useSearch.ts b/src/services/ui/src/api/useSearch.ts index 5e5f9504b9..7e54d639f7 100644 --- a/src/services/ui/src/api/useSearch.ts +++ b/src/services/ui/src/api/useSearch.ts @@ -25,7 +25,7 @@ type QueryProps = { export const getSearchData = async ( props: QueryProps ): Promise => { - const searchData = await API.post("os", "/search", { + const searchData = await API.post("os", "/search/main", { body: { ...filterQueryBuilder(props.filters), ...paginationQueryBuilder(props.pagination), @@ -48,7 +48,7 @@ export const getAllSearchData = async (filters?: OsFilterable[]) => { return []; } - const searchData = await API.post("os", "/search", { + const searchData = await API.post("os", "/search/main", { body: { ...filterQueryBuilder(filters), ...paginationQueryBuilder({ number: startPage, size: 1000 }), diff --git a/src/services/ui/src/components/Opensearch/Provider/index.tsx b/src/services/ui/src/components/Opensearch/Provider/index.tsx index b89793547c..b8e9936cf5 100644 --- a/src/services/ui/src/components/Opensearch/Provider/index.tsx +++ b/src/services/ui/src/components/Opensearch/Provider/index.tsx @@ -1,9 +1,9 @@ import { ReactNode } from "react"; import { createContextProvider } from "@/utils"; -import { ReactQueryApiError, SearchData } from "shared-types"; +import { ReactQueryApiError, OsMainSearchResponse } from "shared-types"; type ContextState = { - data: SearchData | undefined; + data: OsMainSearchResponse["hits"] | undefined; isLoading: boolean; error: ReactQueryApiError | null; }; diff --git a/src/services/ui/src/components/Opensearch/useOpensearch.ts b/src/services/ui/src/components/Opensearch/useOpensearch.ts index 15c59d6efb..8774fddf97 100644 --- a/src/services/ui/src/components/Opensearch/useOpensearch.ts +++ b/src/services/ui/src/components/Opensearch/useOpensearch.ts @@ -1,7 +1,7 @@ import { getSearchData, useOsSearch } from "@/api"; import { useLzUrl } from "@/hooks/useParams"; import { useEffect, useState } from "react"; -import { OsQueryState, SearchData, UserRoles } from "shared-types"; +import { OsQueryState, OsMainSearchResponse, UserRoles } from "shared-types"; import { createSearchFilterable } from "./utils"; import { useQuery } from "@tanstack/react-query"; import { useGetUser } from "@/api/useGetUser"; @@ -39,7 +39,7 @@ Comments */ export const useOsData = () => { const params = useOsUrl(); - const [data, setData] = useState(); + const [data, setData] = useState(); const { mutateAsync, isLoading, error } = useOsSearch(); const onRequest = async (query: OsQueryState, options?: any) => { try { From 48c9c23d030b37f6cb83f20416808f975466139f Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Wed, 3 Jan 2024 06:41:31 -0700 Subject: [PATCH 02/66] feat(OY2-26538): init --- src/packages/shared-types/opensearch.ts | 109 ------------- .../shared-types/opensearch/{base.ts => _.ts} | 40 +---- .../shared-types/opensearch/changelog.ts | 143 +++--------------- src/packages/shared-types/opensearch/index.ts | 3 + src/packages/shared-types/opensearch/main.ts | 17 ++- src/services/api/handlers/getAttachmentUrl.ts | 4 +- src/services/api/libs/package/getPackage.ts | 18 ++- src/services/ui/src/api/useGetItem.ts | 8 +- src/services/ui/src/api/useSearch.ts | 58 ++++--- .../ui/src/components/ExportButton/index.tsx | 2 +- .../components/Opensearch/changelog/index.ts | 0 .../Opensearch/changelog/useOsChangelog.ts | 46 ++++++ .../ui/src/components/Opensearch/index.ts | 6 +- .../{ => main}/Filtering/FilterChips.tsx | 19 ++- .../{ => main}/Filtering/FilterDrawer.tsx | 11 +- .../{ => main}/Filtering/FilterProvider.tsx | 0 .../Filtering/FilterableCheckbox.tsx | 0 .../Filtering/FilterableDateRange.tsx | 0 .../{ => main}/Filtering/FilterableSelect.tsx | 0 .../Opensearch/{ => main}/Filtering/consts.ts | 12 +- .../Opensearch/{ => main}/Filtering/index.tsx | 4 +- .../{ => main}/Filtering/useFilterDrawer.ts | 8 +- .../Opensearch/{ => main}/Provider/index.tsx | 4 +- .../{ => main}/Settings/Visibility.tsx | 0 .../Opensearch/{ => main}/Settings/index.ts | 0 .../Opensearch/{ => main}/Table/index.tsx | 6 +- .../Opensearch/{ => main}/Table/types.ts | 6 +- .../src/components/Opensearch/main/index.ts | 5 + .../src/components/Opensearch/main/types.ts | 14 ++ .../Opensearch/{ => main}/useOpensearch.ts | 21 ++- .../ui/src/components/Opensearch/types.ts | 13 -- .../ui/src/components/Opensearch/utils.ts | 30 +--- .../actions/ToggleRaiResponseWithdraw.tsx | 4 +- .../ui/src/pages/actions/WithdrawPackage.tsx | 5 +- src/services/ui/src/pages/actions/common.tsx | 4 +- .../dashboard/Lists/renderCells/index.tsx | 10 +- .../src/pages/dashboard/Lists/spas/consts.tsx | 2 +- .../src/pages/dashboard/Lists/spas/index.tsx | 4 +- .../pages/dashboard/Lists/waivers/consts.tsx | 2 +- .../pages/dashboard/Lists/waivers/index.tsx | 2 +- src/services/ui/src/pages/dashboard/index.tsx | 2 +- src/services/ui/src/pages/detail/index.tsx | 8 +- 42 files changed, 255 insertions(+), 395 deletions(-) delete mode 100644 src/packages/shared-types/opensearch.ts rename src/packages/shared-types/opensearch/{base.ts => _.ts} (61%) create mode 100644 src/packages/shared-types/opensearch/index.ts create mode 100644 src/services/ui/src/components/Opensearch/changelog/index.ts create mode 100644 src/services/ui/src/components/Opensearch/changelog/useOsChangelog.ts rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/FilterChips.tsx (85%) rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/FilterDrawer.tsx (91%) rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/FilterProvider.tsx (100%) rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/FilterableCheckbox.tsx (100%) rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/FilterableDateRange.tsx (100%) rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/FilterableSelect.tsx (100%) rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/consts.ts (96%) rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/index.tsx (91%) rename src/services/ui/src/components/Opensearch/{ => main}/Filtering/useFilterDrawer.ts (93%) rename src/services/ui/src/components/Opensearch/{ => main}/Provider/index.tsx (79%) rename src/services/ui/src/components/Opensearch/{ => main}/Settings/Visibility.tsx (100%) rename src/services/ui/src/components/Opensearch/{ => main}/Settings/index.ts (100%) rename src/services/ui/src/components/Opensearch/{ => main}/Table/index.tsx (95%) rename src/services/ui/src/components/Opensearch/{ => main}/Table/types.ts (52%) create mode 100644 src/services/ui/src/components/Opensearch/main/index.ts create mode 100644 src/services/ui/src/components/Opensearch/main/types.ts rename src/services/ui/src/components/Opensearch/{ => main}/useOpensearch.ts (86%) delete mode 100644 src/services/ui/src/components/Opensearch/types.ts diff --git a/src/packages/shared-types/opensearch.ts b/src/packages/shared-types/opensearch.ts deleted file mode 100644 index 569a915275..0000000000 --- a/src/packages/shared-types/opensearch.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - SeaToolTransform, - OnemacTransform, - OnemacLegacyTransform, - RaiIssueTransform, - RaiResponseTransform, - RaiWithdrawTransform, - WithdrawPackageTransform, - ToggleWithdrawRaiEnabledTransform, -} from "./"; - -export type OsHit = { - _index: string; - _id: string; - _score: number; - _source: T; - sort: Array; -}; -export type OsHits = { - hits: OsHit[]; - max_score: number; - total: { value: number; relation: "eq" }; -}; - -export type OsResponse = { - _shards: { - total: number; - failed: number; - successful: number; - skipped: number; - }; - hits: OsHits; - total: { - value: number; - }; - max_score: number | null; - took: number; - timed_out: boolean; - aggregations?: OsAggResult; -}; - -export type OsMainSourceItem = OnemacTransform & - OnemacLegacyTransform & - SeaToolTransform & - RaiIssueTransform & - RaiResponseTransform & - RaiWithdrawTransform & - WithdrawPackageTransform & - ToggleWithdrawRaiEnabledTransform; -export type OsMainSearchResponse = OsResponse; -export type ItemResult = OsHit & { - found: boolean; -}; - -export type OsFilterType = - | "term" - | "terms" - | "match" - | "range" - | "search" - | "global_search" - | "exists"; - -export type OsRangeValue = { gte?: string; lte?: string }; -export type OsFilterValue = string | string[] | number | boolean | OsRangeValue; -export type OsField = - | keyof OsMainSourceItem - | `${keyof OsMainSourceItem}.keyword`; - -export type OsFilterable = { - type: OsFilterType; - label?: string; - component?: string; - field: OsField; - value: OsFilterValue; - prefix: "must" | "must_not" | "should" | "filter"; -}; - -export type OsQueryState = { - sort: { field: OsField; order: "asc" | "desc" }; - pagination: { number: number; size: number }; - filters: OsFilterable[]; - search?: string; -}; - -export type OsAggQuery = { - name: string; - type: OsFilterType; - field: OsField; - size: number; -}; - -export type OsAggBucket = { key: string; doc_count: number }; - -export type OsAggResult = Record< - string, - { - doc_count_error_upper_bound: number; - sum_other_doc_count: number; - buckets: OsAggBucket[]; - } ->; - -export type OsExportHeaderOptions = { - transform: (data: TData) => string; - name: string; -}; - -export type OsIndex = "main" | "seatool" | "changelog"; diff --git a/src/packages/shared-types/opensearch/base.ts b/src/packages/shared-types/opensearch/_.ts similarity index 61% rename from src/packages/shared-types/opensearch/base.ts rename to src/packages/shared-types/opensearch/_.ts index 7232ec8479..00520f4881 100644 --- a/src/packages/shared-types/opensearch/base.ts +++ b/src/packages/shared-types/opensearch/_.ts @@ -1,14 +1,3 @@ -import { - SeaToolTransform, - OnemacTransform, - OnemacLegacyTransform, - RaiIssueTransform, - RaiResponseTransform, - RaiWithdrawTransform, - WithdrawPackageTransform, - ToggleWithdrawRaiEnabledTransform, -} from ".."; - export type OsHit = { _index: string; _id: string; @@ -50,42 +39,27 @@ export type OsFilterType = export type OsRangeValue = { gte?: string; lte?: string }; export type OsFilterValue = string | string[] | number | boolean | OsRangeValue; -export type OsMainSourceItem = OnemacTransform & - OnemacLegacyTransform & - SeaToolTransform & - RaiIssueTransform & - RaiResponseTransform & - RaiWithdrawTransform & - WithdrawPackageTransform & - ToggleWithdrawRaiEnabledTransform; -export type OsMainSearchResponse = OsResponse; -export type ItemResult = OsHit & { - found: boolean; -}; -export type OsField = - | keyof OsMainSourceItem - | `${keyof OsMainSourceItem}.keyword`; -export type OsFilterable = { +export type OsFilterable<_FIELD> = { type: OsFilterType; label?: string; component?: string; - field: OsField; + field: _FIELD; value: OsFilterValue; prefix: "must" | "must_not" | "should" | "filter"; }; -export type OsQueryState = { - sort: { field: OsField; order: "asc" | "desc" }; +export type OsQueryState<_FIELD> = { + sort: { field: _FIELD; order: "asc" | "desc" }; pagination: { number: number; size: number }; - filters: OsFilterable[]; + filters: OsFilterable<_FIELD>[]; search?: string; }; -export type OsAggQuery = { +export type OsAggQuery<_FIELD> = { name: string; type: OsFilterType; - field: OsField; + field: _FIELD; size: number; }; diff --git a/src/packages/shared-types/opensearch/changelog.ts b/src/packages/shared-types/opensearch/changelog.ts index 533c3fa382..048834769c 100644 --- a/src/packages/shared-types/opensearch/changelog.ts +++ b/src/packages/shared-types/opensearch/changelog.ts @@ -1,130 +1,37 @@ -export type IndexDocumentChangelog = { - GSI1pk?: string; - GSI1sk?: string; - GSI2pk?: string; - GSI2sk?: string; - Latest?: number; - actionType?: string; - additionalInformation?: string; - adminChanges: { - changeType?: string; - changeMade?: string; - changeReason?: string; - changeTimestamp: string; - }[]; - approvedEffectiveDate?: string; - attachments: { - bucket?: string; - contentType?: string; - filename?: string; - key?: string; - s3key?: string; - title?: string; - uploadDate?: string; - url?: string; +import { OsResponse, OsHit, OsFilterable, OsQueryState, OsAggQuery } from "./_"; + +export type ChangelogDocument = { + actionType: string; + additionalInformation: string; + attachments?: { + bucket: string; + filename: string; + key: string; + title: string; + uploadDate: number; }[]; authority: string; - changedByEmail: string; - changedByName: string; - clockEndTimestamp: number; - componentId: string; - componentType: string; - convertTimestamp: number; - cpocEmail?: string; - cpocName?: string; - currentStatus?: string; - dataFrom?: string; - date: number; - description: string; - division: string; - doneByEmail: string; - doneByName: string; - email: string; - eventTimestamp: number; - finalDispositionDate: string; - fullName: string; - group: string; id: string; - lastActivityTimestamp: number; - lastEventTimestamp: number; - lastModifiedEmail: string; - lastModifiedName: string; - latestRaiResponseTimestamp: number; - notes: string; origin: string; - originallyFrom: string; packageId: string; - parentId: string; - parentType: string; - pk: string; - proposedEffectiveDate: string; - rairesponses: { - additionalInformation: string; - attachments?: { - contentType?: string; - keyword?: string; - s3Key?: string; - title?: string; - url?: string; - }[]; - currentStatus: string; - eventTimestamp: number; - submissionTimestamp: number; - }[]; + proposedEffectiveDate: number; raiWithdrawEnabled: boolean; - reason: string; + rais: any; requestedDate: number; responseDate: number; - reverseChron: { - keyword: string; - additionalInformation: string; - attachments?: { - contentType?: string; - keyword?: string; - s3Key?: string; - title?: string; - url?: string; - }[]; - currentStatus: string; - eventTimestamp: string; - timestamp: string; - type: string; - }[]; - reviewTeam: string; - reviewTeamEmailList: string; - role: string; - sk: string; state: string; - status: string; - streamUpdateDate: number; - subStatus: string; - subject: string; - submissionTimestamp: number; submitterEmail: string; submitterName: string; - temporaryExtensionType: string; - territory: string; - timestamp: string; - title: string; - transmittalNumberWarningMessage: string; - waiverAuthority: string; - withdrawalRequests: { - properties: { - additionalInformation: { - type: "text"; - fields: { - keyword: { - type: "keyword"; - ignore_above: 256; - }; - }; - }; - submissionTimestamp: { - type: "long"; - }; - }; - }; - withdrawnDate: { - type: "long"; - }; + withdrawnDate: number; +}; + +export type ChangelogResponse = OsResponse; +export type ChangelogItemResult = OsHit & { + found: boolean; }; +export type ChangelogField = + | keyof ChangelogDocument + | `${keyof ChangelogDocument}.keyword`; +export type ChangelogFilterable = OsFilterable; +export type ChangelogState = OsQueryState; +export type ChangelogAggs = OsAggQuery; diff --git a/src/packages/shared-types/opensearch/index.ts b/src/packages/shared-types/opensearch/index.ts new file mode 100644 index 0000000000..5c23af482d --- /dev/null +++ b/src/packages/shared-types/opensearch/index.ts @@ -0,0 +1,3 @@ +export * from "./changelog"; +export * from "./main"; +export * from "./_"; diff --git a/src/packages/shared-types/opensearch/main.ts b/src/packages/shared-types/opensearch/main.ts index 2b7fc444ed..c8cf703354 100644 --- a/src/packages/shared-types/opensearch/main.ts +++ b/src/packages/shared-types/opensearch/main.ts @@ -9,9 +9,9 @@ import { ToggleWithdrawRaiEnabledTransform, } from ".."; -import { OsResponse, OsHit } from "./base"; +import { OsResponse, OsHit, OsFilterable, OsQueryState, OsAggQuery } from "./_"; -export type IndexDocumentMain = OnemacTransform & +export type MainDocument = OnemacTransform & OnemacLegacyTransform & SeaToolTransform & RaiIssueTransform & @@ -19,10 +19,13 @@ export type IndexDocumentMain = OnemacTransform & RaiWithdrawTransform & WithdrawPackageTransform & ToggleWithdrawRaiEnabledTransform; -export type OsMainSearchResponse = OsResponse; -export type ItemResult = OsHit & { + +export type MainResponse = OsResponse; +export type MainItemResult = OsHit & { found: boolean; }; -export type OsField = - | keyof IndexDocumentMain - | `${keyof IndexDocumentMain}.keyword`; + +export type MainField = keyof MainDocument | `${keyof MainDocument}.keyword`; +export type MainFilterable = OsFilterable; +export type MainState = OsQueryState; +export type MainAggs = OsAggQuery; diff --git a/src/services/api/handlers/getAttachmentUrl.ts b/src/services/api/handlers/getAttachmentUrl.ts index 45cd592dae..629c445b0f 100644 --- a/src/services/api/handlers/getAttachmentUrl.ts +++ b/src/services/api/handlers/getAttachmentUrl.ts @@ -6,7 +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"; +import { MainDocument, OsResponse } from "shared-types"; if (!process.env.osDomain) { throw "ERROR: osDomain env variable is required,"; } @@ -52,7 +52,7 @@ export const handler = async (event: APIGatewayEvent) => { process.env.osDomain, "main", query - )) as OsResponse; + )) as OsResponse; if (!results) { return response({ diff --git a/src/services/api/libs/package/getPackage.ts b/src/services/api/libs/package/getPackage.ts index 887000a4f7..f400182434 100644 --- a/src/services/api/libs/package/getPackage.ts +++ b/src/services/api/libs/package/getPackage.ts @@ -1,9 +1,23 @@ import * as os from "../../../../libs/opensearch-lib"; -import { ItemResult } from "shared-types"; +import { ChangelogResponse, MainItemResult } from "shared-types"; export const getPackage = async (id: string) => { if (!process.env.osDomain) { throw new Error("process.env.osDomain must be defined"); } - return (await os.getItem(process.env.osDomain, "main", id)) as ItemResult; + const main = (await os.getItem( + process.env.osDomain, + "main", + id + )) as MainItemResult; + const changelog = (await os.search(process.env.osDomain, "changelog", { + from: 0, + size: 200, + query: { bool: { must: [{ term: { "packageId.keyword": id } }] } }, + })) as ChangelogResponse; + + return { + ...main, + _source: { ...main._source, changelog: changelog.hits.hits }, + }; }; diff --git a/src/services/ui/src/api/useGetItem.ts b/src/services/ui/src/api/useGetItem.ts index 12371aeae5..f3c20d10ac 100644 --- a/src/services/ui/src/api/useGetItem.ts +++ b/src/services/ui/src/api/useGetItem.ts @@ -1,8 +1,8 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import { API } from "aws-amplify"; -import { ItemResult, ReactQueryApiError } from "shared-types"; +import { MainItemResult, ReactQueryApiError } from "shared-types"; -export const getItem = async (id: string): Promise => +export const getItem = async (id: string): Promise => await API.post("os", "/item", { body: { id } }); export const idIsUnique = async (id: string) => { @@ -16,9 +16,9 @@ export const idIsUnique = async (id: string) => { export const useGetItem = ( id: string, - options?: UseQueryOptions + options?: UseQueryOptions ) => { - return useQuery( + return useQuery( ["record", id], () => getItem(id), options diff --git a/src/services/ui/src/api/useSearch.ts b/src/services/ui/src/api/useSearch.ts index 7e54d639f7..a29afbf314 100644 --- a/src/services/ui/src/api/useSearch.ts +++ b/src/services/ui/src/api/useSearch.ts @@ -7,25 +7,29 @@ import { import { useMutation, UseMutationOptions } from "@tanstack/react-query"; import { API } from "aws-amplify"; import type { - OsQueryState, ReactQueryApiError, - OsFilterable, OsAggQuery, - OsMainSearchResponse, - OsMainSourceItem, + MainFilterable, + MainDocument, + OsIndex, + ChangelogResponse, + OsQueryState, + ChangelogField, + OsResponse, } from "shared-types"; -type QueryProps = { - filters: OsQueryState["filters"]; - sort?: OsQueryState["sort"]; - pagination: OsQueryState["pagination"]; - aggs?: OsAggQuery[]; +type QueryProps = { + index: OsIndex; + filters: OsQueryState["filters"]; + sort?: OsQueryState["sort"]; + pagination: OsQueryState["pagination"]; + aggs?: OsAggQuery[]; }; -export const getSearchData = async ( - props: QueryProps -): Promise => { - const searchData = await API.post("os", "/search/main", { +export const getOsData = async >( + props: QueryProps +): Promise => { + const searchData = await API.post("os", `/search/${props.index}`, { body: { ...filterQueryBuilder(props.filters), ...paginationQueryBuilder(props.pagination), @@ -38,12 +42,12 @@ export const getSearchData = async ( return searchData; }; -export const getAllSearchData = async (filters?: OsFilterable[]) => { +export const getMainExportData = async (filters?: MainFilterable[]) => { if (!filters) return []; const recursiveSearch = async ( startPage: number - ): Promise => { + ): Promise => { if (startPage * 1000 >= 10000) { return []; } @@ -69,15 +73,29 @@ export const getAllSearchData = async (filters?: OsFilterable[]) => { return await recursiveSearch(0); }; -export const useOsSearch = ( +export const useOsSearch = ( options?: UseMutationOptions< - OsMainSearchResponse, + TResponse, ReactQueryApiError, - QueryProps + QueryProps > ) => { - return useMutation( - (props) => getSearchData(props), + return useMutation>( + (props) => getOsData(props), options ); }; + +export const useChangelogSearch = ( + options?: UseMutationOptions< + ChangelogResponse, + ReactQueryApiError, + QueryProps + > +) => { + return useMutation< + ChangelogResponse, + ReactQueryApiError, + QueryProps + >((props) => getOsData(props), options); +}; diff --git a/src/services/ui/src/components/ExportButton/index.tsx b/src/services/ui/src/components/ExportButton/index.tsx index 1a0a30b497..32a6e30fb0 100644 --- a/src/services/ui/src/components/ExportButton/index.tsx +++ b/src/services/ui/src/components/ExportButton/index.tsx @@ -4,7 +4,7 @@ import { Download, Loader } from "lucide-react"; import { useState } from "react"; import { motion } from "framer-motion"; import { format } from "date-fns"; -import { useOsUrl } from "../Opensearch"; +import { useOsUrl } from "@/components/Opensearch/main"; import { OsExportHeaderOptions } from "shared-types"; type Props> = { diff --git a/src/services/ui/src/components/Opensearch/changelog/index.ts b/src/services/ui/src/components/Opensearch/changelog/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/services/ui/src/components/Opensearch/changelog/useOsChangelog.ts b/src/services/ui/src/components/Opensearch/changelog/useOsChangelog.ts new file mode 100644 index 0000000000..0fd6f7435f --- /dev/null +++ b/src/services/ui/src/components/Opensearch/changelog/useOsChangelog.ts @@ -0,0 +1,46 @@ +import { getOsData, useOsSearch } from "@/api"; +import { useLzUrl } from "@/hooks/useParams"; +import { useEffect, useState } from "react"; +import { + ChangelogField, + ChangelogResponse, + // UserRoles, + ChangelogState, +} from "shared-types"; +import { createSearchFilterable } from "../utils"; +// import { useQuery } from "@tanstack/react-query"; +// import { useGetUser } from "@/api/useGetUser"; + +export const useOsChangelog = () => { + const [data, setData] = useState(); + const { mutateAsync, isLoading, error } = useOsSearch< + ChangelogField, + ChangelogResponse + >({}); + + useEffect(() => { + (async () => { + try { + await mutateAsync( + { + index: "changelog", + pagination: { number: 0, size: 200 }, + filters: [ + { + field: "packageId.keyword", + value: "", + prefix: "must", + type: "term", + }, + ], + }, + { onSuccess: (res) => setData(res.hits) } + ); + } catch (error) { + console.error("Error occurred during search:", error); + } + })(); + }, []); + + return { data, isLoading, error }; +}; diff --git a/src/services/ui/src/components/Opensearch/index.ts b/src/services/ui/src/components/Opensearch/index.ts index aecfc92713..178cd64f81 100644 --- a/src/services/ui/src/components/Opensearch/index.ts +++ b/src/services/ui/src/components/Opensearch/index.ts @@ -1,5 +1 @@ -export * from "./useOpensearch"; -export * from "./types"; -export * from "./Table"; -export * from "./Filtering"; -export * from "./Provider"; +export * from "./utils"; diff --git a/src/services/ui/src/components/Opensearch/Filtering/FilterChips.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterChips.tsx similarity index 85% rename from src/services/ui/src/components/Opensearch/Filtering/FilterChips.tsx rename to src/services/ui/src/components/Opensearch/main/Filtering/FilterChips.tsx index c24f768079..fcfcd25d82 100644 --- a/src/services/ui/src/components/Opensearch/Filtering/FilterChips.tsx +++ b/src/services/ui/src/components/Opensearch/main/Filtering/FilterChips.tsx @@ -1,17 +1,17 @@ import { type FC, useCallback, Fragment } from "react"; import { Chip } from "@/components/Chip"; -import { useOsUrl } from "@/components/Opensearch"; -import { OsFilterable, OsRangeValue } from "shared-types"; +import { useOsUrl } from "@/components/Opensearch/main"; +import { MainFilterable, OsRangeValue } from "shared-types"; import { useFilterDrawerContext } from "./FilterProvider"; -import { checkMultiFilter, resetFilters } from "../utils"; +import { checkMultiFilter } from "@/components/Opensearch"; import { useLabelMapping } from "@/hooks"; interface RenderProp { - filter: OsFilterable; + filter: MainFilterable; index: number; openDrawer: () => void; - clearFilter: (filter: OsFilterable, valIndex?: number) => void; + clearFilter: (filter: MainFilterable, valIndex?: number) => void; } // simple date range chips @@ -76,7 +76,7 @@ export const FilterChips: FC = () => { const openDrawer = useCallback(() => setDrawerState(true), [setDrawerState]); const twoOrMoreFiltersApplied = checkMultiFilter(url.state.filters, 2); - const clearFilter = (filter: OsFilterable, valIndex?: number) => { + const clearFilter = (filter: MainFilterable, valIndex?: number) => { url.onSet((s) => { let filters = s.filters; const filterIndex = filters.findIndex((f) => f.field === filter.field); @@ -98,7 +98,12 @@ export const FilterChips: FC = () => { }); }; - const handleChipClick = () => resetFilters(url.onSet); + const handleChipClick = () => + url.onSet((s) => ({ + ...s, + filters: [], + pagination: { ...s.pagination, number: 0 }, + })); return (
diff --git a/src/services/ui/src/components/Opensearch/Filtering/FilterDrawer.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterDrawer.tsx similarity index 91% rename from src/services/ui/src/components/Opensearch/Filtering/FilterDrawer.tsx rename to src/services/ui/src/components/Opensearch/main/Filtering/FilterDrawer.tsx index 59afaf7f08..85cd860ec7 100644 --- a/src/services/ui/src/components/Opensearch/Filtering/FilterDrawer.tsx +++ b/src/services/ui/src/components/Opensearch/main/Filtering/FilterDrawer.tsx @@ -19,15 +19,20 @@ import { FilterableDateRange } from "./FilterableDateRange"; import { FilterableCheckbox } from "./FilterableCheckbox"; import { useFilterDrawer } from "./useFilterDrawer"; import { Button } from "@/components/Inputs"; -import { checkMultiFilter, resetFilters } from "../utils"; -import { useOsUrl } from "../useOpensearch"; +import { checkMultiFilter } from "@/components/Opensearch"; +import { useOsUrl } from "@/components/Opensearch/main"; export const OsFilterDrawer = () => { const hook = useFilterDrawer(); const url = useOsUrl(); const filtersApplied = checkMultiFilter(url.state.filters, 1); - const handleFilterReset = () => resetFilters(url.onSet); + const handleFilterReset = () => + url.onSet((s) => ({ + ...s, + filters: [], + pagination: { ...s.pagination, number: 0 }, + })); return ( diff --git a/src/services/ui/src/components/Opensearch/Filtering/FilterProvider.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterProvider.tsx similarity index 100% rename from src/services/ui/src/components/Opensearch/Filtering/FilterProvider.tsx rename to src/services/ui/src/components/Opensearch/main/Filtering/FilterProvider.tsx diff --git a/src/services/ui/src/components/Opensearch/Filtering/FilterableCheckbox.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterableCheckbox.tsx similarity index 100% rename from src/services/ui/src/components/Opensearch/Filtering/FilterableCheckbox.tsx rename to src/services/ui/src/components/Opensearch/main/Filtering/FilterableCheckbox.tsx diff --git a/src/services/ui/src/components/Opensearch/Filtering/FilterableDateRange.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterableDateRange.tsx similarity index 100% rename from src/services/ui/src/components/Opensearch/Filtering/FilterableDateRange.tsx rename to src/services/ui/src/components/Opensearch/main/Filtering/FilterableDateRange.tsx diff --git a/src/services/ui/src/components/Opensearch/Filtering/FilterableSelect.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterableSelect.tsx similarity index 100% rename from src/services/ui/src/components/Opensearch/Filtering/FilterableSelect.tsx rename to src/services/ui/src/components/Opensearch/main/Filtering/FilterableSelect.tsx diff --git a/src/services/ui/src/components/Opensearch/Filtering/consts.ts b/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts similarity index 96% rename from src/services/ui/src/components/Opensearch/Filtering/consts.ts rename to src/services/ui/src/components/Opensearch/main/Filtering/consts.ts index 385dd0d217..a637d2a41d 100644 --- a/src/services/ui/src/components/Opensearch/Filtering/consts.ts +++ b/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts @@ -1,8 +1,8 @@ import { + MainFilterable, OsExportHeaderOptions, - OsField, - OsFilterable, - OsMainSourceItem, + MainField, + MainDocument, } from "shared-types"; import { OsFilterComponentType, OsTab } from "../types"; import { UserRoles } from "shared-types"; @@ -15,7 +15,7 @@ type DrawerFilterableGroup = { component: OsFilterComponentType; }; type FilterGroup = Partial< - Record + Record >; const SPA_FILTER_GROUP = (isCms: boolean): FilterGroup => { @@ -159,10 +159,10 @@ export const FILTER_GROUPS = (user?: any, tab?: OsTab): FilterGroup => { export const EXPORT_GROUPS = ( tab: OsTab, user?: any -): OsExportHeaderOptions[] => { +): OsExportHeaderOptions[] => { const idFieldName = tab === "spas" ? "SPA ID" : tab === "waivers" ? "Waiver Number" : ""; - const actionField: OsExportHeaderOptions[] = + const actionField: OsExportHeaderOptions[] = tab === "waivers" ? [ { diff --git a/src/services/ui/src/components/Opensearch/Filtering/index.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/index.tsx similarity index 91% rename from src/services/ui/src/components/Opensearch/Filtering/index.tsx rename to src/services/ui/src/components/Opensearch/main/Filtering/index.tsx index c0a88b43cb..6d6c71d7d8 100644 --- a/src/services/ui/src/components/Opensearch/Filtering/index.tsx +++ b/src/services/ui/src/components/Opensearch/main/Filtering/index.tsx @@ -4,7 +4,7 @@ import { DEFAULT_FILTERS, useOsUrl } from "../useOpensearch"; import { ExportButton } from "@/components/ExportButton"; import { useOsContext } from "../Provider"; import { OsFilterDrawer } from "./FilterDrawer"; -import { getAllSearchData } from "@/api"; +import { getMainExportData } from "@/api"; import { useGetUser } from "@/api/useGetUser"; import { EXPORT_GROUPS } from "./consts"; @@ -35,7 +35,7 @@ export const OsFiltering: FC<{ />
getAllSearchData([...url.state.filters, ...filters])} + data={() => getMainExportData([...url.state.filters, ...filters])} headers={EXPORT_GROUPS(url.state.tab, user)} /> diff --git a/src/services/ui/src/components/Opensearch/Filtering/useFilterDrawer.ts b/src/services/ui/src/components/Opensearch/main/Filtering/useFilterDrawer.ts similarity index 93% rename from src/services/ui/src/components/Opensearch/Filtering/useFilterDrawer.ts rename to src/services/ui/src/components/Opensearch/main/Filtering/useFilterDrawer.ts index 296d4bea1b..a57f03b6d6 100644 --- a/src/services/ui/src/components/Opensearch/Filtering/useFilterDrawer.ts +++ b/src/services/ui/src/components/Opensearch/main/Filtering/useFilterDrawer.ts @@ -1,8 +1,8 @@ import { useState, useEffect, useMemo } from "react"; -import type { OsField } from "../types"; + import * as Consts from "./consts"; import { useOsAggregate, useOsUrl } from "../useOpensearch"; -import { OsFilterValue, OsRangeValue } from "shared-types"; +import { MainField, OsFilterValue, OsRangeValue } from "shared-types"; import { useLabelMapping } from "@/hooks"; import { useFilterDrawerContext } from "./FilterProvider"; import { useGetUser } from "@/api/useGetUser"; @@ -18,7 +18,7 @@ export const useFilterDrawer = () => { const labelMap = useLabelMapping(); const _aggs = useOsAggregate(); - const onFilterChange = (field: OsField) => { + const onFilterChange = (field: MainField) => { return (value: OsFilterValue) => { setFilters((state) => { const updateState = { ...state, [field]: { ...state[field], value } }; @@ -91,7 +91,7 @@ export const useFilterDrawer = () => { value: BUCK.key, })), }; - }, {} as Record); + }, {} as Record); }, [_aggs]); return { diff --git a/src/services/ui/src/components/Opensearch/Provider/index.tsx b/src/services/ui/src/components/Opensearch/main/Provider/index.tsx similarity index 79% rename from src/services/ui/src/components/Opensearch/Provider/index.tsx rename to src/services/ui/src/components/Opensearch/main/Provider/index.tsx index b8e9936cf5..1e0a4c7351 100644 --- a/src/services/ui/src/components/Opensearch/Provider/index.tsx +++ b/src/services/ui/src/components/Opensearch/main/Provider/index.tsx @@ -1,9 +1,9 @@ import { ReactNode } from "react"; import { createContextProvider } from "@/utils"; -import { ReactQueryApiError, OsMainSearchResponse } from "shared-types"; +import { ReactQueryApiError, MainResponse } from "shared-types"; type ContextState = { - data: OsMainSearchResponse["hits"] | undefined; + data: MainResponse["hits"] | undefined; isLoading: boolean; error: ReactQueryApiError | null; }; diff --git a/src/services/ui/src/components/Opensearch/Settings/Visibility.tsx b/src/services/ui/src/components/Opensearch/main/Settings/Visibility.tsx similarity index 100% rename from src/services/ui/src/components/Opensearch/Settings/Visibility.tsx rename to src/services/ui/src/components/Opensearch/main/Settings/Visibility.tsx diff --git a/src/services/ui/src/components/Opensearch/Settings/index.ts b/src/services/ui/src/components/Opensearch/main/Settings/index.ts similarity index 100% rename from src/services/ui/src/components/Opensearch/Settings/index.ts rename to src/services/ui/src/components/Opensearch/main/Settings/index.ts diff --git a/src/services/ui/src/components/Opensearch/Table/index.tsx b/src/services/ui/src/components/Opensearch/main/Table/index.tsx similarity index 95% rename from src/services/ui/src/components/Opensearch/Table/index.tsx rename to src/services/ui/src/components/Opensearch/main/Table/index.tsx index 1b614d5668..4060d40eb9 100644 --- a/src/services/ui/src/components/Opensearch/Table/index.tsx +++ b/src/services/ui/src/components/Opensearch/main/Table/index.tsx @@ -3,10 +3,10 @@ import { LoadingSpinner } from "@/components/LoadingSpinner"; import { FC, useState } from "react"; import { OsTableColumn } from "./types"; import { useOsContext } from "../Provider"; -import { useOsUrl } from "../useOpensearch"; +import { useOsUrl } from "@/components/Opensearch/main"; import { VisibilityPopover } from "../Settings"; import { BLANK_VALUE } from "@/consts"; -import { OsField } from "shared-types"; +import { MainField } from "shared-types"; export const OsTable: FC<{ columns: OsTableColumn[]; @@ -59,7 +59,7 @@ export const OsTable: FC<{ url.onSet((s) => ({ ...s, sort: { - field: TH.field as OsField, + field: TH.field as MainField, order: s.sort.order === "desc" ? "asc" : "desc", }, })); diff --git a/src/services/ui/src/components/Opensearch/Table/types.ts b/src/services/ui/src/components/Opensearch/main/Table/types.ts similarity index 52% rename from src/services/ui/src/components/Opensearch/Table/types.ts rename to src/services/ui/src/components/Opensearch/main/Table/types.ts index c365e3b020..89ea0184c4 100644 --- a/src/services/ui/src/components/Opensearch/Table/types.ts +++ b/src/services/ui/src/components/Opensearch/main/Table/types.ts @@ -1,12 +1,12 @@ -import type { OsField, OsHit, OsMainSourceItem } from "shared-types"; +import type { MainField, MainDocument } from "shared-types"; import type { ReactNode } from "react"; export type OsTableColumn = { - field?: OsField; + field?: MainField; label: string; visible?: boolean; locked?: boolean; isSystem?: boolean; props?: any; - cell: (data: OsHit["_source"]) => ReactNode; + cell: (data: MainDocument) => ReactNode; }; diff --git a/src/services/ui/src/components/Opensearch/main/index.ts b/src/services/ui/src/components/Opensearch/main/index.ts new file mode 100644 index 0000000000..aecfc92713 --- /dev/null +++ b/src/services/ui/src/components/Opensearch/main/index.ts @@ -0,0 +1,5 @@ +export * from "./useOpensearch"; +export * from "./types"; +export * from "./Table"; +export * from "./Filtering"; +export * from "./Provider"; diff --git a/src/services/ui/src/components/Opensearch/main/types.ts b/src/services/ui/src/components/Opensearch/main/types.ts new file mode 100644 index 0000000000..3b48411f79 --- /dev/null +++ b/src/services/ui/src/components/Opensearch/main/types.ts @@ -0,0 +1,14 @@ +import type { MainFilterable, MainField } from "shared-types"; + +export type OsDrawerFilterable = MainFilterable & { open: boolean }; +export type OsFilterComponentType = "multiSelect" | "multiCheck" | "dateRange"; +export type OsFilterGroup = { + label: string; + field: MainField; + type: OsFilterComponentType; +}; + +export type OsTab = "waivers" | "spas"; + +export { MainField }; +export * from "./Table/types"; diff --git a/src/services/ui/src/components/Opensearch/useOpensearch.ts b/src/services/ui/src/components/Opensearch/main/useOpensearch.ts similarity index 86% rename from src/services/ui/src/components/Opensearch/useOpensearch.ts rename to src/services/ui/src/components/Opensearch/main/useOpensearch.ts index 8774fddf97..e37ab54841 100644 --- a/src/services/ui/src/components/Opensearch/useOpensearch.ts +++ b/src/services/ui/src/components/Opensearch/main/useOpensearch.ts @@ -1,8 +1,8 @@ -import { getSearchData, useOsSearch } from "@/api"; +import { getOsData, useOsSearch } from "@/api"; import { useLzUrl } from "@/hooks/useParams"; import { useEffect, useState } from "react"; -import { OsQueryState, OsMainSearchResponse, UserRoles } from "shared-types"; -import { createSearchFilterable } from "./utils"; +import { MainResponse, UserRoles, MainState, MainField } from "shared-types"; +import { createSearchFilterable } from "../utils"; import { useQuery } from "@tanstack/react-query"; import { useGetUser } from "@/api/useGetUser"; import { OsTab } from "./types"; @@ -39,12 +39,16 @@ Comments */ export const useOsData = () => { const params = useOsUrl(); - const [data, setData] = useState(); - const { mutateAsync, isLoading, error } = useOsSearch(); - const onRequest = async (query: OsQueryState, options?: any) => { + const [data, setData] = useState(); + const { mutateAsync, isLoading, error } = useOsSearch< + MainField, + MainResponse + >(); + const onRequest = async (query: MainState, options?: any) => { try { await mutateAsync( { + index: "main", pagination: query.pagination, ...(!query.search && { sort: query.sort }), filters: [ @@ -71,7 +75,8 @@ export const useOsAggregate = () => { refetchOnWindowFocus: false, queryKey: [state.tab], queryFn: (props) => { - return getSearchData({ + return getOsData({ + index: "main", aggs: [ { field: "state.keyword", @@ -119,7 +124,7 @@ export const useOsAggregate = () => { }); return aggs.data?.aggregations; }; -export type OsUrlState = OsQueryState & { tab: OsTab }; +export type OsUrlState = MainState & { tab: OsTab }; export const useOsUrl = () => { return useLzUrl({ key: "os", diff --git a/src/services/ui/src/components/Opensearch/types.ts b/src/services/ui/src/components/Opensearch/types.ts deleted file mode 100644 index 4541d7170b..0000000000 --- a/src/services/ui/src/components/Opensearch/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { OsField, OsFilterable } from "shared-types"; - -export type OsDrawerFilterable = OsFilterable & { open: boolean }; -export type OsFilterComponentType = "multiSelect" | "multiCheck" | "dateRange"; -export type OsFilterGroup = { - label: string; - field: OsField; - type: OsFilterComponentType; -}; - -export type OsTab = "waivers" | "spas"; - -export { OsField }; diff --git a/src/services/ui/src/components/Opensearch/utils.ts b/src/services/ui/src/components/Opensearch/utils.ts index d2b3f21f7d..f396dd2eee 100644 --- a/src/services/ui/src/components/Opensearch/utils.ts +++ b/src/services/ui/src/components/Opensearch/utils.ts @@ -1,9 +1,8 @@ import { OsAggQuery, OsFilterable, OsQueryState } from "shared-types"; -import { OsUrlState } from "./useOpensearch"; const filterMapQueryReducer = ( - state: Record, - filter: OsFilterable + state: Record["prefix"], any[]>, + filter: OsFilterable ) => { if (!filter.value) return state; @@ -40,7 +39,7 @@ const filterMapQueryReducer = ( return state; }; -export const filterQueryBuilder = (filters: OsFilterable[]) => { +export const filterQueryBuilder = (filters: OsFilterable[]) => { if (!filters?.length) return {}; return { @@ -56,7 +55,7 @@ export const filterQueryBuilder = (filters: OsFilterable[]) => { }; export const paginationQueryBuilder = ( - pagination: OsQueryState["pagination"] + pagination: OsQueryState["pagination"] ) => { const from = (() => { if (!pagination.number) return 0; @@ -69,11 +68,11 @@ export const paginationQueryBuilder = ( }; }; -export const sortQueryBuilder = (sort: OsQueryState["sort"]) => { +export const sortQueryBuilder = (sort: OsQueryState["sort"]) => { return { sort: [{ [sort.field]: sort.order }] }; }; -export const aggQueryBuilder = (aggs: OsAggQuery[]) => { +export const aggQueryBuilder = (aggs: OsAggQuery[]) => { return { aggs: aggs.reduce((STATE, AGG) => { STATE[AGG.name] = { @@ -96,24 +95,11 @@ export const createSearchFilterable = (value?: string) => { field: "", value, prefix: "must", - } as unknown as OsFilterable, + } as unknown as OsFilterable, ]; }; -export const resetFilters = ( - onSet: ( - arg: (arg: OsUrlState) => OsUrlState, - shouldIsolate?: boolean | undefined - ) => void -) => { - onSet((s) => ({ - ...s, - filters: [], - pagination: { ...s.pagination, number: 0 }, - })); -}; - -export const checkMultiFilter = (filters: OsFilterable[], val: number) => { +export const checkMultiFilter = (filters: OsFilterable[], val: number) => { return ( filters.length >= val || filters.some( diff --git a/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx b/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx index d2704f47b4..a3d14f58a2 100644 --- a/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx +++ b/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx @@ -1,6 +1,6 @@ import { Navigate, useNavigate, useParams } from "@/components/Routing"; import { Alert, LoadingSpinner } from "@/components"; -import { Action, PlanType, ItemResult } from "shared-types"; +import { Action, PlanType, MainItemResult } from "shared-types"; import { Button } from "@/components/Inputs"; import { useEffect, useMemo, useState } from "react"; import { PackageActionForm } from "@/pages/actions/PackageActionForm"; @@ -10,7 +10,7 @@ import { buildActionUrl } from "@/lib"; import { useGetUser } from "@/api/useGetUser"; import { ActionFormIntro, PackageInfo } from "@/pages/actions/common"; -const ToggleRaiResponseWithdrawForm = ({ item }: { item?: ItemResult }) => { +const ToggleRaiResponseWithdrawForm = ({ item }: { item?: MainItemResult }) => { const navigate = useNavigate(); const { id, type } = useParams("/action/:id/:type"); const { data: user } = useGetUser(); diff --git a/src/services/ui/src/pages/actions/WithdrawPackage.tsx b/src/services/ui/src/pages/actions/WithdrawPackage.tsx index b9dee69b55..d9a4b5eb07 100644 --- a/src/services/ui/src/pages/actions/WithdrawPackage.tsx +++ b/src/services/ui/src/pages/actions/WithdrawPackage.tsx @@ -1,8 +1,9 @@ +/* eslint-disable react/prop-types */ import { Navigate, useNavigate, useParams } from "@/components/Routing"; import { Button } from "@/components/Inputs"; import { ConfirmationModal } from "@/components/Modal/ConfirmationModal"; import { useState } from "react"; -import { PlanType, ItemResult } from "shared-types"; +import { PlanType, MainItemResult } from "shared-types"; import { PackageActionForm } from "./PackageActionForm"; import { ActionFormIntro, PackageInfo } from "./common"; import { z } from "zod"; @@ -35,7 +36,7 @@ const attachments: AttachmentRecipe[] = [ } as const, ]; -const WithdrawPackageForm: React.FC = ({ item }: { item?: ItemResult }) => { +const WithdrawPackageForm: React.FC<{ item?: MainItemResult }> = ({ item }) => { const [successModalIsOpen, setSuccessModalIsOpen] = useState(false); const [errorModalIsOpen, setErrorModalIsOpen] = useState(false); const [cancelModalIsOpen, setCancelModalIsOpen] = useState(false); diff --git a/src/services/ui/src/pages/actions/common.tsx b/src/services/ui/src/pages/actions/common.tsx index d21e2bf3f8..c3f91c4273 100644 --- a/src/services/ui/src/pages/actions/common.tsx +++ b/src/services/ui/src/pages/actions/common.tsx @@ -1,6 +1,6 @@ import { removeUnderscoresAndCapitalize } from "@/utils"; import { PropsWithChildren } from "react"; -import { ItemResult } from "shared-types"; +import { MainItemResult } from "shared-types"; // Keeps aria stuff and classes condensed const SectionTemplate = ({ @@ -18,7 +18,7 @@ const SectionTemplate = ({
); -export const PackageInfo = ({ item }: { item: ItemResult }) => ( +export const PackageInfo = ({ item }: { item: MainItemResult }) => (
- function Cell(data: OsMainSourceItem) { +export const renderCellDate = (key: keyof MainDocument) => + function Cell(data: MainDocument) { if (!data[key]) return null; return format(new Date(data[key] as string), "MM/dd/yyyy"); }; export const renderCellIdLink = (pathResolver: (id: string) => string) => - function Cell(data: OsMainSourceItem) { + function Cell(data: MainDocument) { if (!data.authority) return <>; const path = pathResolver(encodeURIComponent(data.id)); return ( @@ -25,7 +25,7 @@ export const renderCellIdLink = (pathResolver: (id: string) => string) => }; export const renderCellActions = (user: CognitoUserAttributes | null) => - function Cell(data: OsMainSourceItem) { + function Cell(data: MainDocument) { if (!user) return <>; const actions = getAvailableActions(user, data); return ( diff --git a/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx b/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx index 9d078a34a5..c6ec0dc601 100644 --- a/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx @@ -1,6 +1,6 @@ import { format } from "date-fns"; import { removeUnderscoresAndCapitalize } from "@/utils"; -import { OsTableColumn } from "@/components/Opensearch/Table/types"; +import { OsTableColumn } from "@/components/Opensearch/main"; import { CMS_READ_ONLY_ROLES, UserRoles } from "shared-types"; import { useGetUser } from "@/api/useGetUser"; import { diff --git a/src/services/ui/src/pages/dashboard/Lists/spas/index.tsx b/src/services/ui/src/pages/dashboard/Lists/spas/index.tsx index f42c767a53..7abab8c31c 100644 --- a/src/services/ui/src/pages/dashboard/Lists/spas/index.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/spas/index.tsx @@ -1,4 +1,4 @@ -import { ErrorAlert, LoadingSpinner } from "@/components"; +import { ErrorAlert } from "@/components"; import { Pagination } from "@/components/Pagination"; import { @@ -6,7 +6,7 @@ import { OsFiltering, useOsContext, useOsUrl, -} from "@/components/Opensearch"; +} from "@/components/Opensearch/main"; import { useSpaTableColumns } from "./consts"; export const SpasList = () => { diff --git a/src/services/ui/src/pages/dashboard/Lists/waivers/consts.tsx b/src/services/ui/src/pages/dashboard/Lists/waivers/consts.tsx index bd8db15630..b227c20a08 100644 --- a/src/services/ui/src/pages/dashboard/Lists/waivers/consts.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/waivers/consts.tsx @@ -1,5 +1,5 @@ import { removeUnderscoresAndCapitalize } from "@/utils"; -import { OsTableColumn } from "@/components/Opensearch/Table/types"; +import { OsTableColumn } from "@/components/Opensearch/main"; import { LABELS } from "@/lib"; import { BLANK_VALUE } from "@/consts"; import { CMS_READ_ONLY_ROLES, UserRoles } from "shared-types"; diff --git a/src/services/ui/src/pages/dashboard/Lists/waivers/index.tsx b/src/services/ui/src/pages/dashboard/Lists/waivers/index.tsx index b4a37254e2..37baa4bf99 100644 --- a/src/services/ui/src/pages/dashboard/Lists/waivers/index.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/waivers/index.tsx @@ -5,7 +5,7 @@ import { OsFiltering, useOsContext, useOsUrl, -} from "@/components/Opensearch"; +} from "@/components/Opensearch/main"; import { useWaiverTableColumns } from "./consts"; export const WaiversList = () => { diff --git a/src/services/ui/src/pages/dashboard/index.tsx b/src/services/ui/src/pages/dashboard/index.tsx index c04d75d5d7..b102a01652 100644 --- a/src/services/ui/src/pages/dashboard/index.tsx +++ b/src/services/ui/src/pages/dashboard/index.tsx @@ -10,7 +10,7 @@ import { useOsData, FilterChips, FilterDrawerProvider, -} from "@/components/Opensearch"; +} from "@/components/Opensearch/main"; import { Button } from "@/components/Inputs"; import { useUserContext } from "@/components/Context/userContext"; import { useMemo } from "react"; diff --git a/src/services/ui/src/pages/detail/index.tsx b/src/services/ui/src/pages/detail/index.tsx index 7baa4d605f..54e2a31410 100644 --- a/src/services/ui/src/pages/detail/index.tsx +++ b/src/services/ui/src/pages/detail/index.tsx @@ -14,8 +14,8 @@ import { useGetUser } from "@/api/useGetUser"; import { Action, ActionAvailabilityCheck, - ItemResult, - OsMainSourceItem, + MainItemResult, + MainDocument, PlanType, PlanTypeCheck, UserRoles, @@ -46,7 +46,7 @@ const DetailCardWrapper = ({
); -const StatusCard = (data: OsMainSourceItem) => { +const StatusCard = (data: MainDocument) => { const transformedStatuses = getStatus(data.seatoolStatus); const checker = ActionAvailabilityCheck(data); const { data: user } = useGetUser(); @@ -160,7 +160,7 @@ const PackageActionsCard = ({ id }: { id: string }) => { ); }; -export const DetailsContent = ({ data }: { data?: ItemResult }) => { +export const DetailsContent = ({ data }: { data?: MainItemResult }) => { const { state } = useLocation(); if (!data?._source) return ; return ( From 67c3ac98a6a20c04945598f4d1ff6c1ccb2750ab Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Thu, 4 Jan 2024 16:30:31 -0700 Subject: [PATCH 03/66] feat(OY2-26538): package actions init --- src/libs/opensearch-lib.ts | 14 +- src/packages/shared-types/actions.ts | 4 +- src/packages/shared-types/index.ts | 2 +- src/packages/shared-types/opensearch/_.ts | 42 ++-- .../shared-types/opensearch/changelog.ts | 25 ++- src/packages/shared-types/opensearch/index.ts | 4 +- src/packages/shared-types/opensearch/main.ts | 25 ++- .../package-actions/getAvailableActions.ts | 4 +- src/services/api/handlers/getAttachmentUrl.ts | 4 +- src/services/api/libs/package/getPackage.ts | 10 +- src/services/data/handlers/index.ts | 4 +- src/services/ui/src/api/useGetItem.ts | 10 +- src/services/ui/src/api/useSearch.ts | 43 ++-- .../src/components/AdditionalInfo/index.tsx | 4 +- .../src/components/AttachmentsList/index.tsx | 4 +- .../src/components/DetailsSection/index.tsx | 4 +- .../ui/src/components/ExportButton/index.tsx | 4 +- .../components/Opensearch/changelog/index.ts | 1 + .../Opensearch/changelog/useOsChangelog.ts | 46 ---- .../Opensearch/main/Filtering/FilterChips.tsx | 13 +- .../main/Filtering/FilterDrawer.tsx | 6 +- .../main/Filtering/FilterableDateRange.tsx | 11 +- .../Opensearch/main/Filtering/consts.ts | 16 +- .../main/Filtering/useFilterDrawer.ts | 12 +- .../Opensearch/main/Provider/index.tsx | 4 +- .../Opensearch/main/Table/index.tsx | 4 +- .../components/Opensearch/main/Table/types.ts | 6 +- .../src/components/Opensearch/main/types.ts | 7 +- .../Opensearch/main/useOpensearch.ts | 12 +- .../ui/src/components/Opensearch/utils.ts | 21 +- .../ui/src/components/RaiList/index.tsx | 4 +- .../actions/ToggleRaiResponseWithdraw.tsx | 8 +- .../ui/src/pages/actions/WithdrawPackage.tsx | 6 +- src/services/ui/src/pages/actions/common.tsx | 4 +- .../dashboard/Lists/renderCells/index.tsx | 10 +- .../detail/admin-changes/AdminChanges.tsx | 65 ++++++ .../src/pages/detail/admin-changes/index.tsx | 21 ++ src/services/ui/src/pages/detail/index.tsx | 41 ++-- .../package-activity/PackageActivity.tsx | 202 ++++++++++++++++++ .../pages/detail/package-activity/index.tsx | 28 +++ .../ui/src/pages/detail/setup/spa.tsx | 8 +- 41 files changed, 530 insertions(+), 233 deletions(-) delete mode 100644 src/services/ui/src/components/Opensearch/changelog/useOsChangelog.ts create mode 100644 src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx create mode 100644 src/services/ui/src/pages/detail/admin-changes/index.tsx create mode 100644 src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx create mode 100644 src/services/ui/src/pages/detail/package-activity/index.tsx diff --git a/src/libs/opensearch-lib.ts b/src/libs/opensearch-lib.ts index 34a3e03005..6d194adf9d 100644 --- a/src/libs/opensearch-lib.ts +++ b/src/libs/opensearch-lib.ts @@ -7,7 +7,7 @@ import * as aws4 from "aws4"; import axios from "axios"; import { aws4Interceptor } from "aws4-axios"; import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts"; -import { OsIndex } from "shared-types"; +import { opensearch } from "shared-types"; let client: Client; export async function getClient(host: string) { @@ -40,7 +40,7 @@ export async function updateData(host: string, indexObject: any) { export async function bulkUpdateData( host: string, - index: OsIndex, + index: opensearch.Index, arrayOfDocuments: any ) { client = client || (await getClient(host)); @@ -60,7 +60,7 @@ export async function bulkUpdateData( console.log(response); } -export async function deleteIndex(host: string, index: OsIndex) { +export async function deleteIndex(host: string, index: opensearch.Index) { client = client || (await getClient(host)); var response = await client.indices.delete({ index }); } @@ -111,7 +111,7 @@ export async function mapRole( } } -export async function search(host: string, index: OsIndex, query: any) { +export async function search(host: string, index: opensearch.Index, query: any) { client = client || (await getClient(host)); try { const response = await client.search({ @@ -124,7 +124,7 @@ export async function search(host: string, index: OsIndex, query: any) { } } -export async function getItem(host: string, index: OsIndex, id: string) { +export async function getItem(host: string, index: opensearch.Index, id: string) { client = client || (await getClient(host)); try { const response = await client.get({ id, index }); @@ -135,7 +135,7 @@ export async function getItem(host: string, index: OsIndex, id: string) { } // check it exists - then create -export async function createIndex(host: string, index: OsIndex) { +export async function createIndex(host: string, index: opensearch.Index) { client = client || (await getClient(host)); try { const exists = await client.indices.exists({ index }); @@ -150,7 +150,7 @@ export async function createIndex(host: string, index: OsIndex) { export async function updateFieldMapping( host: string, - index: OsIndex, + index: opensearch.Index, properties: object ) { client = client || (await getClient(host)); diff --git a/src/packages/shared-types/actions.ts b/src/packages/shared-types/actions.ts index 6b51464c8f..91fa93f3f4 100644 --- a/src/packages/shared-types/actions.ts +++ b/src/packages/shared-types/actions.ts @@ -1,4 +1,4 @@ -import { OsMainSourceItem } from "./opensearch"; +import { main } from "./opensearch"; import { CognitoUserAttributes } from "./user"; import { getLatestRai } from "shared-utils"; import { SEATOOL_STATUS } from "./statusHelper"; @@ -29,7 +29,7 @@ export const ActionAvailabilityCheck = ({ rais, raiWithdrawEnabled, planType, -}: OsMainSourceItem) => { +}: main.Document) => { const latestRai = getLatestRai(rais); return { /** Is in any of our pending statuses, sans Pending-RAI **/ diff --git a/src/packages/shared-types/index.ts b/src/packages/shared-types/index.ts index df749505fd..f8af01976a 100644 --- a/src/packages/shared-types/index.ts +++ b/src/packages/shared-types/index.ts @@ -4,7 +4,7 @@ export * from "./errors"; export * from "./seatool"; export * from "./onemac"; export * from "./onemacLegacy"; -export * from "./opensearch"; +export * as opensearch from "./opensearch"; export * from "./uploads"; export * from "./actions"; export * from "./attachments"; diff --git a/src/packages/shared-types/opensearch/_.ts b/src/packages/shared-types/opensearch/_.ts index 00520f4881..f16e3ce65c 100644 --- a/src/packages/shared-types/opensearch/_.ts +++ b/src/packages/shared-types/opensearch/_.ts @@ -1,34 +1,34 @@ -export type OsHit = { +export type Hit = { _index: string; _id: string; _score: number; _source: T; sort: Array; }; -export type OsHits = { - hits: OsHit[]; +export type Hits = { + hits: Hit[]; max_score: number; total: { value: number; relation: "eq" }; }; -export type OsResponse = { +export type Response = { _shards: { total: number; failed: number; successful: number; skipped: number; }; - hits: OsHits; + hits: Hits; total: { value: number; }; max_score: number | null; took: number; timed_out: boolean; - aggregations?: OsAggResult; + aggregations?: AggResult; }; -export type OsFilterType = +export type FilterType = | "term" | "terms" | "match" @@ -37,46 +37,46 @@ export type OsFilterType = | "global_search" | "exists"; -export type OsRangeValue = { gte?: string; lte?: string }; -export type OsFilterValue = string | string[] | number | boolean | OsRangeValue; +export type RangeValue = { gte?: string; lte?: string }; +export type FilterValue = string | string[] | number | boolean | RangeValue; -export type OsFilterable<_FIELD> = { - type: OsFilterType; +export type Filterable<_FIELD> = { + type: FilterType; label?: string; component?: string; field: _FIELD; - value: OsFilterValue; + value: FilterValue; prefix: "must" | "must_not" | "should" | "filter"; }; -export type OsQueryState<_FIELD> = { +export type QueryState<_FIELD> = { sort: { field: _FIELD; order: "asc" | "desc" }; pagination: { number: number; size: number }; - filters: OsFilterable<_FIELD>[]; + filters: Filterable<_FIELD>[]; search?: string; }; -export type OsAggQuery<_FIELD> = { +export type AggQuery<_FIELD> = { name: string; - type: OsFilterType; + type: FilterType; field: _FIELD; size: number; }; -export type OsAggBucket = { key: string; doc_count: number }; +export type AggBucket = { key: string; doc_count: number }; -export type OsAggResult = Record< +export type AggResult = Record< string, { doc_count_error_upper_bound: number; sum_other_doc_count: number; - buckets: OsAggBucket[]; + buckets: AggBucket[]; } >; -export type OsExportHeaderOptions = { +export type ExportHeaderOptions = { transform: (data: TData) => string; name: string; }; -export type OsIndex = "main" | "seatool" | "changelog"; +export type Index = "main" | "seatool" | "changelog"; diff --git a/src/packages/shared-types/opensearch/changelog.ts b/src/packages/shared-types/opensearch/changelog.ts index 048834769c..2694862be0 100644 --- a/src/packages/shared-types/opensearch/changelog.ts +++ b/src/packages/shared-types/opensearch/changelog.ts @@ -1,6 +1,12 @@ -import { OsResponse, OsHit, OsFilterable, OsQueryState, OsAggQuery } from "./_"; +import { + Response as Res, + Hit, + Filterable as FIL, + QueryState, + AggQuery, +} from "./_"; -export type ChangelogDocument = { +export type Document = { actionType: string; additionalInformation: string; attachments?: { @@ -25,13 +31,12 @@ export type ChangelogDocument = { withdrawnDate: number; }; -export type ChangelogResponse = OsResponse; -export type ChangelogItemResult = OsHit & { +export type Response = Res; +export type ItemResult = Hit & { found: boolean; }; -export type ChangelogField = - | keyof ChangelogDocument - | `${keyof ChangelogDocument}.keyword`; -export type ChangelogFilterable = OsFilterable; -export type ChangelogState = OsQueryState; -export type ChangelogAggs = OsAggQuery; + +export type Field = keyof Document | `${keyof Document}.keyword`; +export type Filterable = FIL; +export type State = QueryState; +export type Aggs = AggQuery; diff --git a/src/packages/shared-types/opensearch/index.ts b/src/packages/shared-types/opensearch/index.ts index 5c23af482d..36c6bf521b 100644 --- a/src/packages/shared-types/opensearch/index.ts +++ b/src/packages/shared-types/opensearch/index.ts @@ -1,3 +1,3 @@ -export * from "./changelog"; -export * from "./main"; +export * as changelog from "./changelog"; +export * as main from "./main"; export * from "./_"; diff --git a/src/packages/shared-types/opensearch/main.ts b/src/packages/shared-types/opensearch/main.ts index c8cf703354..79c38048b0 100644 --- a/src/packages/shared-types/opensearch/main.ts +++ b/src/packages/shared-types/opensearch/main.ts @@ -9,23 +9,30 @@ import { ToggleWithdrawRaiEnabledTransform, } from ".."; -import { OsResponse, OsHit, OsFilterable, OsQueryState, OsAggQuery } from "./_"; +import { + Response as Res, + Hit, + Filterable as FIL, + QueryState, + AggQuery, +} from "./_"; +import { ItemResult as Changelog } from "./changelog"; -export type MainDocument = OnemacTransform & +export type Document = OnemacTransform & OnemacLegacyTransform & SeaToolTransform & RaiIssueTransform & RaiResponseTransform & RaiWithdrawTransform & WithdrawPackageTransform & - ToggleWithdrawRaiEnabledTransform; + ToggleWithdrawRaiEnabledTransform & { changelog?: Changelog[] }; -export type MainResponse = OsResponse; -export type MainItemResult = OsHit & { +export type Response = Res; +export type ItemResult = Hit & { found: boolean; }; -export type MainField = keyof MainDocument | `${keyof MainDocument}.keyword`; -export type MainFilterable = OsFilterable; -export type MainState = OsQueryState; -export type MainAggs = OsAggQuery; +export type Field = keyof Document | `${keyof Document}.keyword`; +export type Filterable = FIL; +export type State = QueryState; +export type Aggs = AggQuery; diff --git a/src/packages/shared-utils/package-actions/getAvailableActions.ts b/src/packages/shared-utils/package-actions/getAvailableActions.ts index b5f3ee96de..a02bab0a49 100644 --- a/src/packages/shared-utils/package-actions/getAvailableActions.ts +++ b/src/packages/shared-utils/package-actions/getAvailableActions.ts @@ -1,15 +1,15 @@ import { ActionAvailabilityCheck, CognitoUserAttributes, - OsMainSourceItem, PlanTypeCheck, PlanType, + opensearch } from "../../shared-types"; import rules from "./rules"; export const getAvailableActions = ( user: CognitoUserAttributes, - result: OsMainSourceItem + result: opensearch.main.Document ) => { const actionChecker = ActionAvailabilityCheck(result); return PlanTypeCheck(result.planType).is([PlanType.MED_SPA]) diff --git a/src/services/api/handlers/getAttachmentUrl.ts b/src/services/api/handlers/getAttachmentUrl.ts index 629c445b0f..df73631731 100644 --- a/src/services/api/handlers/getAttachmentUrl.ts +++ b/src/services/api/handlers/getAttachmentUrl.ts @@ -6,7 +6,7 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import * as os from "./../../../libs/opensearch-lib"; import { getStateFilter } from "../libs/auth/user"; -import { MainDocument, OsResponse } from "shared-types"; +import { opensearch } from "shared-types"; if (!process.env.osDomain) { throw "ERROR: osDomain env variable is required,"; } @@ -52,7 +52,7 @@ export const handler = async (event: APIGatewayEvent) => { process.env.osDomain, "main", query - )) as OsResponse; + )) as opensearch.Response; if (!results) { return response({ diff --git a/src/services/api/libs/package/getPackage.ts b/src/services/api/libs/package/getPackage.ts index f400182434..c7aa71e839 100644 --- a/src/services/api/libs/package/getPackage.ts +++ b/src/services/api/libs/package/getPackage.ts @@ -1,5 +1,5 @@ import * as os from "../../../../libs/opensearch-lib"; -import { ChangelogResponse, MainItemResult } from "shared-types"; +import { opensearch } from "shared-types"; export const getPackage = async (id: string) => { if (!process.env.osDomain) { @@ -9,15 +9,17 @@ export const getPackage = async (id: string) => { process.env.osDomain, "main", id - )) as MainItemResult; + )) as opensearch.main.ItemResult; const changelog = (await os.search(process.env.osDomain, "changelog", { from: 0, size: 200, + // NOTE: get the required timestamp sort field + sort: [{}], query: { bool: { must: [{ term: { "packageId.keyword": id } }] } }, - })) as ChangelogResponse; + })) as opensearch.changelog.Response; return { ...main, _source: { ...main._source, changelog: changelog.hits.hits }, - }; + } as opensearch.main.ItemResult; }; diff --git a/src/services/data/handlers/index.ts b/src/services/data/handlers/index.ts index 6714abfb86..e2804e89d3 100644 --- a/src/services/data/handlers/index.ts +++ b/src/services/data/handlers/index.ts @@ -2,7 +2,7 @@ import { Handler } from "aws-lambda"; import { send, SUCCESS, FAILED } from "cfn-response-async"; type ResponseStatus = typeof SUCCESS | typeof FAILED; import * as os from "./../../../libs/opensearch-lib"; -import { OsIndex } from "shared-types"; +import { opensearch } from "shared-types"; export const customResourceWrapper: Handler = async (event, context) => { console.log("request:", JSON.stringify(event, undefined, 2)); @@ -26,7 +26,7 @@ export const handler: Handler = async () => { }; const manageIndexResource = async (resource: { - index: OsIndex; + index: opensearch.Index; update?: object; }) => { if (!process.env.osDomain) { diff --git a/src/services/ui/src/api/useGetItem.ts b/src/services/ui/src/api/useGetItem.ts index f3c20d10ac..0d65819f93 100644 --- a/src/services/ui/src/api/useGetItem.ts +++ b/src/services/ui/src/api/useGetItem.ts @@ -1,8 +1,10 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import { API } from "aws-amplify"; -import { MainItemResult, ReactQueryApiError } from "shared-types"; +import { opensearch, ReactQueryApiError } from "shared-types"; -export const getItem = async (id: string): Promise => +export const getItem = async ( + id: string +): Promise => await API.post("os", "/item", { body: { id } }); export const idIsUnique = async (id: string) => { @@ -16,9 +18,9 @@ export const idIsUnique = async (id: string) => { export const useGetItem = ( id: string, - options?: UseQueryOptions + options?: UseQueryOptions ) => { - return useQuery( + return useQuery( ["record", id], () => getItem(id), options diff --git a/src/services/ui/src/api/useSearch.ts b/src/services/ui/src/api/useSearch.ts index a29afbf314..992268d490 100644 --- a/src/services/ui/src/api/useSearch.ts +++ b/src/services/ui/src/api/useSearch.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { aggQueryBuilder, filterQueryBuilder, @@ -6,27 +7,20 @@ import { } from "@/components/Opensearch/utils"; import { useMutation, UseMutationOptions } from "@tanstack/react-query"; import { API } from "aws-amplify"; -import type { - ReactQueryApiError, - OsAggQuery, - MainFilterable, - MainDocument, - OsIndex, - ChangelogResponse, - OsQueryState, - ChangelogField, - OsResponse, -} from "shared-types"; +import type { ReactQueryApiError, opensearch } from "shared-types"; type QueryProps = { - index: OsIndex; - filters: OsQueryState["filters"]; - sort?: OsQueryState["sort"]; - pagination: OsQueryState["pagination"]; - aggs?: OsAggQuery[]; + index: opensearch.Index; + filters: opensearch.QueryState["filters"]; + sort?: opensearch.QueryState["sort"]; + pagination: opensearch.QueryState["pagination"]; + aggs?: opensearch.AggQuery[]; }; -export const getOsData = async >( +export const getOsData = async < + TProps, + TResponse extends opensearch.Response +>( props: QueryProps ): Promise => { const searchData = await API.post("os", `/search/${props.index}`, { @@ -42,12 +36,14 @@ export const getOsData = async >( return searchData; }; -export const getMainExportData = async (filters?: MainFilterable[]) => { +export const getMainExportData = async ( + filters?: opensearch.main.Filterable[] +) => { if (!filters) return []; const recursiveSearch = async ( startPage: number - ): Promise => { + ): Promise => { if (startPage * 1000 >= 10000) { return []; } @@ -80,6 +76,7 @@ export const useOsSearch = ( QueryProps > ) => { + //@ts-ignore return useMutation>( (props) => getOsData(props), options @@ -88,14 +85,14 @@ export const useOsSearch = ( export const useChangelogSearch = ( options?: UseMutationOptions< - ChangelogResponse, + opensearch.changelog.Response, ReactQueryApiError, - QueryProps + QueryProps > ) => { return useMutation< - ChangelogResponse, + opensearch.changelog.Response, ReactQueryApiError, - QueryProps + QueryProps >((props) => getOsData(props), options); }; diff --git a/src/services/ui/src/components/AdditionalInfo/index.tsx b/src/services/ui/src/components/AdditionalInfo/index.tsx index 5c7600a9f2..e27edc3871 100644 --- a/src/services/ui/src/components/AdditionalInfo/index.tsx +++ b/src/services/ui/src/components/AdditionalInfo/index.tsx @@ -1,9 +1,9 @@ -import { OsMainSourceItem } from "shared-types"; +import { opensearch } from "shared-types"; export const AdditionalInfo = ({ additionalInformation, }: { - additionalInformation: OsMainSourceItem["additionalInformation"]; + additionalInformation: opensearch.main.Document["additionalInformation"]; }) => { return (
diff --git a/src/services/ui/src/components/AttachmentsList/index.tsx b/src/services/ui/src/components/AttachmentsList/index.tsx index ffd2d9c837..b5afd69640 100644 --- a/src/services/ui/src/components/AttachmentsList/index.tsx +++ b/src/services/ui/src/components/AttachmentsList/index.tsx @@ -4,7 +4,7 @@ import { BLANK_VALUE } from "@/consts"; import { DownloadIcon } from "lucide-react"; import JSZip from "jszip"; import { saveAs } from "file-saver"; -import { OsMainSourceItem } from "shared-types"; +import { opensearch } from "shared-types"; import { useState } from "react"; import { Button } from "../Inputs/button"; import { @@ -122,7 +122,7 @@ export const Attachmentslist = (data: AttachmentList) => { }; async function downloadAll( - attachments: OsMainSourceItem["attachments"], + attachments: opensearch.main.Document["attachments"], id: string ) { if (!attachments) return null; diff --git a/src/services/ui/src/components/DetailsSection/index.tsx b/src/services/ui/src/components/DetailsSection/index.tsx index c3e1ccf9f1..f99bc2a01a 100644 --- a/src/services/ui/src/components/DetailsSection/index.tsx +++ b/src/services/ui/src/components/DetailsSection/index.tsx @@ -12,8 +12,10 @@ export const DetailsSection: React.FC = ({ id, }: DetailsSectionProps) => { return ( -
+

{title}

+
+ {description &&

{description}

} {children} diff --git a/src/services/ui/src/components/ExportButton/index.tsx b/src/services/ui/src/components/ExportButton/index.tsx index 32a6e30fb0..9465a2aeaa 100644 --- a/src/services/ui/src/components/ExportButton/index.tsx +++ b/src/services/ui/src/components/ExportButton/index.tsx @@ -5,11 +5,11 @@ import { useState } from "react"; import { motion } from "framer-motion"; import { format } from "date-fns"; import { useOsUrl } from "@/components/Opensearch/main"; -import { OsExportHeaderOptions } from "shared-types"; +import { opensearch } from "shared-types"; type Props> = { data: TData[] | (() => Promise); - headers: OsExportHeaderOptions[]; + headers: opensearch.ExportHeaderOptions[]; // | Record> }; diff --git a/src/services/ui/src/components/Opensearch/changelog/index.ts b/src/services/ui/src/components/Opensearch/changelog/index.ts index e69de29bb2..c50c39f0c2 100644 --- a/src/services/ui/src/components/Opensearch/changelog/index.ts +++ b/src/services/ui/src/components/Opensearch/changelog/index.ts @@ -0,0 +1 @@ +export const _ = "filler "; diff --git a/src/services/ui/src/components/Opensearch/changelog/useOsChangelog.ts b/src/services/ui/src/components/Opensearch/changelog/useOsChangelog.ts deleted file mode 100644 index 0fd6f7435f..0000000000 --- a/src/services/ui/src/components/Opensearch/changelog/useOsChangelog.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { getOsData, useOsSearch } from "@/api"; -import { useLzUrl } from "@/hooks/useParams"; -import { useEffect, useState } from "react"; -import { - ChangelogField, - ChangelogResponse, - // UserRoles, - ChangelogState, -} from "shared-types"; -import { createSearchFilterable } from "../utils"; -// import { useQuery } from "@tanstack/react-query"; -// import { useGetUser } from "@/api/useGetUser"; - -export const useOsChangelog = () => { - const [data, setData] = useState(); - const { mutateAsync, isLoading, error } = useOsSearch< - ChangelogField, - ChangelogResponse - >({}); - - useEffect(() => { - (async () => { - try { - await mutateAsync( - { - index: "changelog", - pagination: { number: 0, size: 200 }, - filters: [ - { - field: "packageId.keyword", - value: "", - prefix: "must", - type: "term", - }, - ], - }, - { onSuccess: (res) => setData(res.hits) } - ); - } catch (error) { - console.error("Error occurred during search:", error); - } - })(); - }, []); - - return { data, isLoading, error }; -}; diff --git a/src/services/ui/src/components/Opensearch/main/Filtering/FilterChips.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterChips.tsx index fcfcd25d82..a49871565a 100644 --- a/src/services/ui/src/components/Opensearch/main/Filtering/FilterChips.tsx +++ b/src/services/ui/src/components/Opensearch/main/Filtering/FilterChips.tsx @@ -2,16 +2,16 @@ import { type FC, useCallback, Fragment } from "react"; import { Chip } from "@/components/Chip"; import { useOsUrl } from "@/components/Opensearch/main"; -import { MainFilterable, OsRangeValue } from "shared-types"; +import { opensearch } from "shared-types"; import { useFilterDrawerContext } from "./FilterProvider"; import { checkMultiFilter } from "@/components/Opensearch"; import { useLabelMapping } from "@/hooks"; interface RenderProp { - filter: MainFilterable; + filter: opensearch.main.Filterable; index: number; openDrawer: () => void; - clearFilter: (filter: MainFilterable, valIndex?: number) => void; + clearFilter: (filter: opensearch.main.Filterable, valIndex?: number) => void; } // simple date range chips @@ -21,7 +21,7 @@ const DateChip: FC = ({ openDrawer, clearFilter, }) => { - const value = filter.value as OsRangeValue; + const value = filter.value as opensearch.RangeValue; return ( { const openDrawer = useCallback(() => setDrawerState(true), [setDrawerState]); const twoOrMoreFiltersApplied = checkMultiFilter(url.state.filters, 2); - const clearFilter = (filter: MainFilterable, valIndex?: number) => { + const clearFilter = ( + filter: opensearch.main.Filterable, + valIndex?: number + ) => { url.onSet((s) => { let filters = s.filters; const filterIndex = filters.findIndex((f) => f.field === filter.field); diff --git a/src/services/ui/src/components/Opensearch/main/Filtering/FilterDrawer.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterDrawer.tsx index 85cd860ec7..d87efa6ac6 100644 --- a/src/services/ui/src/components/Opensearch/main/Filtering/FilterDrawer.tsx +++ b/src/services/ui/src/components/Opensearch/main/Filtering/FilterDrawer.tsx @@ -1,5 +1,5 @@ import { FilterIcon } from "lucide-react"; -import { OsRangeValue } from "shared-types"; +import { opensearch } from "shared-types"; import { Sheet, @@ -83,7 +83,9 @@ export const OsFilterDrawer = () => { )} {PK.component === "dateRange" && ( )} diff --git a/src/services/ui/src/components/Opensearch/main/Filtering/FilterableDateRange.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/FilterableDateRange.tsx index 7d624208f6..9aa3e61775 100644 --- a/src/services/ui/src/components/Opensearch/main/Filtering/FilterableDateRange.tsx +++ b/src/services/ui/src/components/Opensearch/main/Filtering/FilterableDateRange.tsx @@ -18,14 +18,14 @@ import { DateRange } from "react-day-picker"; import { cn } from "@/lib/utils"; import { Button, Calendar, Input } from "@/components/Inputs"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/Popover"; -import { OsRangeValue } from "shared-types"; +import { opensearch } from "shared-types"; type Props = Omit< React.HTMLAttributes, "onChange" | "value" | "onSelect" > & { - value: OsRangeValue; - onChange: (val: OsRangeValue) => void; + value: opensearch.RangeValue; + onChange: (val: opensearch.RangeValue) => void; className?: string; }; @@ -113,7 +113,10 @@ export function FilterableDateRange({ value, onChange, ...props }: Props) { } }; - const getDateRange = (startDate: Date, endDate: Date): OsRangeValue => { + const getDateRange = ( + startDate: Date, + endDate: Date + ): opensearch.RangeValue => { return { gte: startDate.toISOString(), lte: endDate.toISOString(), diff --git a/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts b/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts index a637d2a41d..b257f2ad63 100644 --- a/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts +++ b/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts @@ -1,9 +1,4 @@ -import { - MainFilterable, - OsExportHeaderOptions, - MainField, - MainDocument, -} from "shared-types"; +import { opensearch } from "shared-types"; import { OsFilterComponentType, OsTab } from "../types"; import { UserRoles } from "shared-types"; import { BLANK_VALUE } from "@/consts"; @@ -15,7 +10,10 @@ type DrawerFilterableGroup = { component: OsFilterComponentType; }; type FilterGroup = Partial< - Record + Record< + opensearch.main.Field, + opensearch.main.Filterable & DrawerFilterableGroup + > >; const SPA_FILTER_GROUP = (isCms: boolean): FilterGroup => { @@ -159,10 +157,10 @@ export const FILTER_GROUPS = (user?: any, tab?: OsTab): FilterGroup => { export const EXPORT_GROUPS = ( tab: OsTab, user?: any -): OsExportHeaderOptions[] => { +): opensearch.ExportHeaderOptions[] => { const idFieldName = tab === "spas" ? "SPA ID" : tab === "waivers" ? "Waiver Number" : ""; - const actionField: OsExportHeaderOptions[] = + const actionField: opensearch.ExportHeaderOptions[] = tab === "waivers" ? [ { diff --git a/src/services/ui/src/components/Opensearch/main/Filtering/useFilterDrawer.ts b/src/services/ui/src/components/Opensearch/main/Filtering/useFilterDrawer.ts index a57f03b6d6..1bd98cc9ba 100644 --- a/src/services/ui/src/components/Opensearch/main/Filtering/useFilterDrawer.ts +++ b/src/services/ui/src/components/Opensearch/main/Filtering/useFilterDrawer.ts @@ -2,7 +2,7 @@ import { useState, useEffect, useMemo } from "react"; import * as Consts from "./consts"; import { useOsAggregate, useOsUrl } from "../useOpensearch"; -import { MainField, OsFilterValue, OsRangeValue } from "shared-types"; +import { opensearch } from "shared-types"; import { useLabelMapping } from "@/hooks"; import { useFilterDrawerContext } from "./FilterProvider"; import { useGetUser } from "@/api/useGetUser"; @@ -18,8 +18,8 @@ export const useFilterDrawer = () => { const labelMap = useLabelMapping(); const _aggs = useOsAggregate(); - const onFilterChange = (field: MainField) => { - return (value: OsFilterValue) => { + const onFilterChange = (field: opensearch.main.Field) => { + return (value: opensearch.FilterValue) => { setFilters((state) => { const updateState = { ...state, [field]: { ...state[field], value } }; const updateFilters = Object.values(updateState).filter((FIL) => { @@ -29,7 +29,7 @@ export const useFilterDrawer = () => { } if (FIL.type === "range") { - const value = FIL.value as OsRangeValue; + const value = FIL.value as opensearch.RangeValue; return !!value?.gte && !!value?.lte; } @@ -66,7 +66,7 @@ export const useFilterDrawer = () => { return updateFilter.value; } if (VAL.type === "terms") return [] as string[]; - return { gte: undefined, lte: undefined } as OsRangeValue; + return { gte: undefined, lte: undefined } as opensearch.RangeValue; })(); STATE[KEY] = { ...VAL, value }; @@ -91,7 +91,7 @@ export const useFilterDrawer = () => { value: BUCK.key, })), }; - }, {} as Record); + }, {} as Record); }, [_aggs]); return { diff --git a/src/services/ui/src/components/Opensearch/main/Provider/index.tsx b/src/services/ui/src/components/Opensearch/main/Provider/index.tsx index 1e0a4c7351..750b00d57d 100644 --- a/src/services/ui/src/components/Opensearch/main/Provider/index.tsx +++ b/src/services/ui/src/components/Opensearch/main/Provider/index.tsx @@ -1,9 +1,9 @@ import { ReactNode } from "react"; import { createContextProvider } from "@/utils"; -import { ReactQueryApiError, MainResponse } from "shared-types"; +import { ReactQueryApiError, opensearch } from "shared-types"; type ContextState = { - data: MainResponse["hits"] | undefined; + data: opensearch.main.Response["hits"] | undefined; isLoading: boolean; error: ReactQueryApiError | null; }; diff --git a/src/services/ui/src/components/Opensearch/main/Table/index.tsx b/src/services/ui/src/components/Opensearch/main/Table/index.tsx index 4060d40eb9..9032ce8e20 100644 --- a/src/services/ui/src/components/Opensearch/main/Table/index.tsx +++ b/src/services/ui/src/components/Opensearch/main/Table/index.tsx @@ -6,7 +6,7 @@ import { useOsContext } from "../Provider"; import { useOsUrl } from "@/components/Opensearch/main"; import { VisibilityPopover } from "../Settings"; import { BLANK_VALUE } from "@/consts"; -import { MainField } from "shared-types"; +import { opensearch } from "shared-types"; export const OsTable: FC<{ columns: OsTableColumn[]; @@ -59,7 +59,7 @@ export const OsTable: FC<{ url.onSet((s) => ({ ...s, sort: { - field: TH.field as MainField, + field: TH.field as opensearch.main.Field, order: s.sort.order === "desc" ? "asc" : "desc", }, })); diff --git a/src/services/ui/src/components/Opensearch/main/Table/types.ts b/src/services/ui/src/components/Opensearch/main/Table/types.ts index 89ea0184c4..6d34056de0 100644 --- a/src/services/ui/src/components/Opensearch/main/Table/types.ts +++ b/src/services/ui/src/components/Opensearch/main/Table/types.ts @@ -1,12 +1,12 @@ -import type { MainField, MainDocument } from "shared-types"; +import type { opensearch } from "shared-types"; import type { ReactNode } from "react"; export type OsTableColumn = { - field?: MainField; + field?: opensearch.main.Field; label: string; visible?: boolean; locked?: boolean; isSystem?: boolean; props?: any; - cell: (data: MainDocument) => ReactNode; + cell: (data: opensearch.main.Document) => ReactNode; }; diff --git a/src/services/ui/src/components/Opensearch/main/types.ts b/src/services/ui/src/components/Opensearch/main/types.ts index 3b48411f79..84e9981808 100644 --- a/src/services/ui/src/components/Opensearch/main/types.ts +++ b/src/services/ui/src/components/Opensearch/main/types.ts @@ -1,14 +1,13 @@ -import type { MainFilterable, MainField } from "shared-types"; +import type { opensearch } from "shared-types"; -export type OsDrawerFilterable = MainFilterable & { open: boolean }; +export type OsDrawerFilterable = opensearch.main.Filterable & { open: boolean }; export type OsFilterComponentType = "multiSelect" | "multiCheck" | "dateRange"; export type OsFilterGroup = { label: string; - field: MainField; + field: opensearch.main.Field; type: OsFilterComponentType; }; export type OsTab = "waivers" | "spas"; -export { MainField }; export * from "./Table/types"; diff --git a/src/services/ui/src/components/Opensearch/main/useOpensearch.ts b/src/services/ui/src/components/Opensearch/main/useOpensearch.ts index e37ab54841..feaf02bd1e 100644 --- a/src/services/ui/src/components/Opensearch/main/useOpensearch.ts +++ b/src/services/ui/src/components/Opensearch/main/useOpensearch.ts @@ -1,7 +1,7 @@ import { getOsData, useOsSearch } from "@/api"; import { useLzUrl } from "@/hooks/useParams"; import { useEffect, useState } from "react"; -import { MainResponse, UserRoles, MainState, MainField } from "shared-types"; +import { UserRoles, opensearch } from "shared-types"; import { createSearchFilterable } from "../utils"; import { useQuery } from "@tanstack/react-query"; import { useGetUser } from "@/api/useGetUser"; @@ -39,12 +39,12 @@ Comments */ export const useOsData = () => { const params = useOsUrl(); - const [data, setData] = useState(); + const [data, setData] = useState(); const { mutateAsync, isLoading, error } = useOsSearch< - MainField, - MainResponse + opensearch.main.Field, + opensearch.main.Response >(); - const onRequest = async (query: MainState, options?: any) => { + const onRequest = async (query: opensearch.main.State, options?: any) => { try { await mutateAsync( { @@ -124,7 +124,7 @@ export const useOsAggregate = () => { }); return aggs.data?.aggregations; }; -export type OsUrlState = MainState & { tab: OsTab }; +export type OsUrlState = opensearch.main.State & { tab: OsTab }; export const useOsUrl = () => { return useLzUrl({ key: "os", diff --git a/src/services/ui/src/components/Opensearch/utils.ts b/src/services/ui/src/components/Opensearch/utils.ts index f396dd2eee..3bff8d8377 100644 --- a/src/services/ui/src/components/Opensearch/utils.ts +++ b/src/services/ui/src/components/Opensearch/utils.ts @@ -1,8 +1,8 @@ -import { OsAggQuery, OsFilterable, OsQueryState } from "shared-types"; +import { opensearch } from "shared-types"; const filterMapQueryReducer = ( - state: Record["prefix"], any[]>, - filter: OsFilterable + state: Record["prefix"], any[]>, + filter: opensearch.Filterable ) => { if (!filter.value) return state; @@ -39,7 +39,7 @@ const filterMapQueryReducer = ( return state; }; -export const filterQueryBuilder = (filters: OsFilterable[]) => { +export const filterQueryBuilder = (filters: opensearch.Filterable[]) => { if (!filters?.length) return {}; return { @@ -55,7 +55,7 @@ export const filterQueryBuilder = (filters: OsFilterable[]) => { }; export const paginationQueryBuilder = ( - pagination: OsQueryState["pagination"] + pagination: opensearch.QueryState["pagination"] ) => { const from = (() => { if (!pagination.number) return 0; @@ -68,11 +68,11 @@ export const paginationQueryBuilder = ( }; }; -export const sortQueryBuilder = (sort: OsQueryState["sort"]) => { +export const sortQueryBuilder = (sort: opensearch.QueryState["sort"]) => { return { sort: [{ [sort.field]: sort.order }] }; }; -export const aggQueryBuilder = (aggs: OsAggQuery[]) => { +export const aggQueryBuilder = (aggs: opensearch.AggQuery[]) => { return { aggs: aggs.reduce((STATE, AGG) => { STATE[AGG.name] = { @@ -95,11 +95,14 @@ export const createSearchFilterable = (value?: string) => { field: "", value, prefix: "must", - } as unknown as OsFilterable, + } as unknown as opensearch.Filterable, ]; }; -export const checkMultiFilter = (filters: OsFilterable[], val: number) => { +export const checkMultiFilter = ( + filters: opensearch.Filterable[], + val: number +) => { return ( filters.length >= val || filters.some( diff --git a/src/services/ui/src/components/RaiList/index.tsx b/src/services/ui/src/components/RaiList/index.tsx index 6da50ba497..7144a672da 100644 --- a/src/services/ui/src/components/RaiList/index.tsx +++ b/src/services/ui/src/components/RaiList/index.tsx @@ -1,4 +1,4 @@ -import { OsMainSourceItem } from "shared-types"; +import { opensearch } from "shared-types"; import { DetailsSection } from "../DetailsSection"; import { format } from "date-fns"; import { @@ -10,7 +10,7 @@ import { } from "@/components"; import { BLANK_VALUE } from "@/consts"; -export const RaiList = (data: OsMainSourceItem) => { +export const RaiList = (data: opensearch.main.Document) => { if (!data.rais) return null; return ( diff --git a/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx b/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx index a3d14f58a2..23ae470680 100644 --- a/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx +++ b/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx @@ -1,6 +1,6 @@ import { Navigate, useNavigate, useParams } from "@/components/Routing"; import { Alert, LoadingSpinner } from "@/components"; -import { Action, PlanType, MainItemResult } from "shared-types"; +import { Action, PlanType, opensearch } from "shared-types"; import { Button } from "@/components/Inputs"; import { useEffect, useMemo, useState } from "react"; import { PackageActionForm } from "@/pages/actions/PackageActionForm"; @@ -10,7 +10,11 @@ import { buildActionUrl } from "@/lib"; import { useGetUser } from "@/api/useGetUser"; import { ActionFormIntro, PackageInfo } from "@/pages/actions/common"; -const ToggleRaiResponseWithdrawForm = ({ item }: { item?: MainItemResult }) => { +const ToggleRaiResponseWithdrawForm = ({ + item, +}: { + item?: opensearch.main.ItemResult; +}) => { const navigate = useNavigate(); const { id, type } = useParams("/action/:id/:type"); const { data: user } = useGetUser(); diff --git a/src/services/ui/src/pages/actions/WithdrawPackage.tsx b/src/services/ui/src/pages/actions/WithdrawPackage.tsx index d9a4b5eb07..89a810e8c7 100644 --- a/src/services/ui/src/pages/actions/WithdrawPackage.tsx +++ b/src/services/ui/src/pages/actions/WithdrawPackage.tsx @@ -3,7 +3,7 @@ import { Navigate, useNavigate, useParams } from "@/components/Routing"; import { Button } from "@/components/Inputs"; import { ConfirmationModal } from "@/components/Modal/ConfirmationModal"; import { useState } from "react"; -import { PlanType, MainItemResult } from "shared-types"; +import { PlanType, opensearch } from "shared-types"; import { PackageActionForm } from "./PackageActionForm"; import { ActionFormIntro, PackageInfo } from "./common"; import { z } from "zod"; @@ -36,7 +36,9 @@ const attachments: AttachmentRecipe[] = [ } as const, ]; -const WithdrawPackageForm: React.FC<{ item?: MainItemResult }> = ({ item }) => { +const WithdrawPackageForm: React.FC<{ item?: opensearch.main.ItemResult }> = ({ + item, +}) => { const [successModalIsOpen, setSuccessModalIsOpen] = useState(false); const [errorModalIsOpen, setErrorModalIsOpen] = useState(false); const [cancelModalIsOpen, setCancelModalIsOpen] = useState(false); diff --git a/src/services/ui/src/pages/actions/common.tsx b/src/services/ui/src/pages/actions/common.tsx index c3f91c4273..0fb14afc11 100644 --- a/src/services/ui/src/pages/actions/common.tsx +++ b/src/services/ui/src/pages/actions/common.tsx @@ -1,6 +1,6 @@ import { removeUnderscoresAndCapitalize } from "@/utils"; import { PropsWithChildren } from "react"; -import { MainItemResult } from "shared-types"; +import { opensearch } from "shared-types"; // Keeps aria stuff and classes condensed const SectionTemplate = ({ @@ -18,7 +18,7 @@ const SectionTemplate = ({
); -export const PackageInfo = ({ item }: { item: MainItemResult }) => ( +export const PackageInfo = ({ item }: { item: opensearch.main.ItemResult }) => (
- function Cell(data: MainDocument) { +export const renderCellDate = (key: keyof opensearch.main.Document) => + function Cell(data: opensearch.main.Document) { if (!data[key]) return null; return format(new Date(data[key] as string), "MM/dd/yyyy"); }; export const renderCellIdLink = (pathResolver: (id: string) => string) => - function Cell(data: MainDocument) { + function Cell(data: opensearch.main.Document) { if (!data.authority) return <>; const path = pathResolver(encodeURIComponent(data.id)); return ( @@ -25,7 +25,7 @@ export const renderCellIdLink = (pathResolver: (id: string) => string) => }; export const renderCellActions = (user: CognitoUserAttributes | null) => - function Cell(data: MainDocument) { + function Cell(data: opensearch.main.Document) { if (!user) return <>; const actions = getAvailableActions(user, data); return ( diff --git a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx new file mode 100644 index 0000000000..2e8aca7d2e --- /dev/null +++ b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx @@ -0,0 +1,65 @@ +import { + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components"; +import { opensearch } from "shared-types"; +import { FC, useMemo } from "react"; +import { BLANK_VALUE } from "@/consts"; + +export const AC_WithdrawEnabled: FC = ( + props +) => { + return ( +

OneMac admin has enabled package action to submit formal RAI response

+ ); +}; + +export const AC_WithdrawDisabled: FC = ( + props +) => { + return ( +

+ OneMac admin has disabled package action to submit formal RAI response +

+ ); +}; + +export const AC_Update: FC = () => { + return

Coming Soon

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

+ {label as string} + {" - "} + {/* WHAT Date */} + {new Date(props.responseDate).toDateString()} +

+
+ + + +
+ ); +}; diff --git a/src/services/ui/src/pages/detail/admin-changes/index.tsx b/src/services/ui/src/pages/detail/admin-changes/index.tsx new file mode 100644 index 0000000000..41c41a2303 --- /dev/null +++ b/src/services/ui/src/pages/detail/admin-changes/index.tsx @@ -0,0 +1,21 @@ +import { Accordion, DetailsSection } from "@/components"; +import { opensearch } from "shared-types"; +import { FC } from "react"; +import { AdminChange } from "./AdminChanges"; + +export const ACTIONS_ADMIN = ["disable-rai-withdraw", "enable-rai-withdraw"]; + +export const AdminChanges: FC = (props) => { + const data = props.changelog?.filter((CL) => + ACTIONS_ADMIN.includes(CL._source.actionType) + ); + return ( + + + {data?.map((CL) => ( + + ))} + + + ); +}; diff --git a/src/services/ui/src/pages/detail/index.tsx b/src/services/ui/src/pages/detail/index.tsx index 54e2a31410..3c7dc21633 100644 --- a/src/services/ui/src/pages/detail/index.tsx +++ b/src/services/ui/src/pages/detail/index.tsx @@ -1,27 +1,22 @@ import { AdditionalInfo, Alert, - Attachmentslist, CardWithTopBorder, ConfirmationModal, DetailItemsGrid, DetailsSection, ErrorAlert, LoadingSpinner, - RaiList, } from "@/components"; import { useGetUser } from "@/api/useGetUser"; import { Action, ActionAvailabilityCheck, - MainItemResult, - MainDocument, - PlanType, - PlanTypeCheck, + opensearch, UserRoles, } from "shared-types"; import { useQuery } from "@/hooks"; -import { useGetItem } from "@/api"; +import { getAttachmentUrl, useGetItem } from "@/api"; import { BreadCrumbs } from "@/components/BreadCrumb"; import { mapActionLabel } from "@/utils"; import { useLocation } from "react-router-dom"; @@ -32,6 +27,8 @@ import { API } from "aws-amplify"; import { getStatus } from "shared-types/statusHelper"; import { spaDetails, submissionDetails } from "@/pages/detail/setup/spa"; import { Link } from "@/components/Routing"; +import { PackageActivities } from "./package-activity"; +import { AdminChanges } from "./admin-changes"; const DetailCardWrapper = ({ title, @@ -46,7 +43,7 @@ const DetailCardWrapper = ({
); -const StatusCard = (data: MainDocument) => { +const StatusCard = (data: opensearch.main.Document) => { const transformedStatuses = getStatus(data.seatoolStatus); const checker = ActionAvailabilityCheck(data); const { data: user } = useGetUser(); @@ -160,7 +157,11 @@ const PackageActionsCard = ({ id }: { id: string }) => { ); }; -export const DetailsContent = ({ data }: { data?: MainItemResult }) => { +export const DetailsContent = ({ + data, +}: { + data?: opensearch.main.ItemResult; +}) => { const { state } = useLocation(); if (!data?._source) return ; return ( @@ -200,20 +201,14 @@ export const DetailsContent = ({ data }: { data?: MainItemResult }) => { -

{"Package Details"}

- - - {/* Below is used for spacing. Keep it simple */} -
- - - - - - - +
+ + + + + + +
); diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx new file mode 100644 index 0000000000..ae344fac2e --- /dev/null +++ b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx @@ -0,0 +1,202 @@ +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + DetailsSection, +} from "@/components"; +import { opensearch } from "shared-types"; +import { getAttachmentUrl } from "@/api"; +import { FC, useMemo } from "react"; +import { Button } from "@/components/Inputs"; +import * as Table from "@/components/Table"; +import { BLANK_VALUE } from "@/consts"; + +export const PA_InitialSubmission: FC = ( + props +) => { + return ( + + + + Document Type + Attached File + + + + {props.attachments?.map((ATC) => { + return ( + + {ATC.title} + + + + + ); + })} + + + ); +}; + +export const PA_ResponseSubmitted: FC = ( + props +) => { + return ( +
+
+

Attached File

+ {props.attachments?.map((ATC) => ( + + ))} +
+
+

Additional Information

+

{props.additionalInformation || "-- --"}

+
+
+ ); +}; + +export const PA_ResponseWithdrawn: FC = ( + props +) => { + return ( +
+
+

Attached File

+ {props.attachments?.map((ATC) => ( + + ))} +
+
+

Additional Information

+

{props.additionalInformation || "-- --"}

+
+
+ ); +}; + +export const PA_RaiIssued: FC = (props) => { + return ( +
+
+

Attached File

+ {props.attachments?.map((ATC) => ( + + ))} +
+
+

Additional Information

+

{props.additionalInformation || "-- --"}

+
+
+ ); +}; + +export const PA_WithdrawEnabled: FC = ( + props +) => { + return <>TBD; +}; + +export const PA_WithdrawDisabled: FC = ( + props +) => { + return <>TBD; +}; + +const usePackageActivity = (doc: opensearch.changelog.Document) => { + return useMemo(() => { + switch (doc.actionType) { + case "new-submission": + return ["Initial package submitted", PA_InitialSubmission]; + case "withdraw-rai": + return ["RAI response withdrawn", PA_ResponseWithdrawn]; + case "withdraw-package": + return ["RAI package withdrawn", PA_ResponseWithdrawn]; + case "issue-rai": + return ["RAI issued", PA_RaiIssued]; + case "respond-to-rai": + return ["RAI response submitted", PA_ResponseSubmitted]; + default: + return [BLANK_VALUE, PA_ResponseSubmitted]; + // case "disable-rai-withdraw": + // return ["RAI withdraw disabled", PA_WithdrawDisabled]; + // case "enable-rai-withdraw": + // return ["RAI withdraw enabled", PA_WithdrawEnabled]; + } + }, [doc.actionType]); +}; + +export const PackageActivity: FC = (props) => { + const [label, Content] = usePackageActivity(props); + + return ( + + +

+ {label as string} + {" - "} + {/* WHAT Date */} + {new Date(props.responseDate).toDateString()} +

+
+ + + +
+ ); +}; diff --git a/src/services/ui/src/pages/detail/package-activity/index.tsx b/src/services/ui/src/pages/detail/package-activity/index.tsx new file mode 100644 index 0000000000..3b0e6c626f --- /dev/null +++ b/src/services/ui/src/pages/detail/package-activity/index.tsx @@ -0,0 +1,28 @@ +import { Accordion, DetailsSection } from "@/components"; +import { opensearch } from "shared-types"; +import { FC } from "react"; +import { PackageActivity } from "./PackageActivity"; + +export const ACTIONS_PA = [ + "new-submission", + "withdraw-rai", + "withdraw-package", + "issue-rai", + "respond-to-rai", +]; + +export const PackageActivities: FC = (props) => { + const data = props.changelog?.filter((CL) => + ACTIONS_PA.includes(CL._source.actionType) + ); + + return ( + + + {data?.map((CL) => ( + + ))} + + + ); +}; diff --git a/src/services/ui/src/pages/detail/setup/spa.tsx b/src/services/ui/src/pages/detail/setup/spa.tsx index caf6c9cfa6..860064c4aa 100644 --- a/src/services/ui/src/pages/detail/setup/spa.tsx +++ b/src/services/ui/src/pages/detail/setup/spa.tsx @@ -3,7 +3,7 @@ import { isCmsUser } from "shared-utils"; import { LABELS } from "@/lib"; import { BLANK_VALUE } from "@/consts"; import { format } from "date-fns"; -import { OsMainSourceItem } from "shared-types"; +import { opensearch } from "shared-types"; import { ReactNode } from "react"; import { OneMacUser } from "@/api/useGetUser"; import { ReviewTeamList } from "@/components/PackageDetails/ReviewTeamList"; @@ -13,7 +13,9 @@ export type DetailSectionItem = { value: ReactNode; canView: (u: OneMacUser | undefined) => boolean; }; -export const spaDetails = (data: OsMainSourceItem): DetailSectionItem[] => [ +export const spaDetails = ( + data: opensearch.main.Document +): DetailSectionItem[] => [ { label: "Submission ID", value: data.id, @@ -69,7 +71,7 @@ export const spaDetails = (data: OsMainSourceItem): DetailSectionItem[] => [ ]; export const submissionDetails = ( - data: OsMainSourceItem + data: opensearch.main.Document ): DetailSectionItem[] => [ { label: "Submitted By", From 45d1705beb9e39a188c0945f4171cfccddf89e2a Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Fri, 5 Jan 2024 06:32:36 -0700 Subject: [PATCH 04/66] feat(OY2-26538): package actions init --- src/packages/shared-utils/packageCheck.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/shared-utils/packageCheck.ts b/src/packages/shared-utils/packageCheck.ts index 9afcbf44c7..8ea974f06b 100644 --- a/src/packages/shared-utils/packageCheck.ts +++ b/src/packages/shared-utils/packageCheck.ts @@ -1,4 +1,4 @@ -import { OsMainSourceItem, PlanType, SEATOOL_STATUS } from "../shared-types"; +import { opensearch, PlanType, SEATOOL_STATUS } from "../shared-types"; import { getLatestRai } from "./rai-helper"; const secondClockStatuses = [ @@ -24,7 +24,7 @@ export const PackageCheck = ({ rais, raiWithdrawEnabled, planType, -}: OsMainSourceItem) => { +}: opensearch.main.Document) => { const latestRai = getLatestRai(rais); const planChecks = { isSpa: checkPlan(planType, [PlanType.MED_SPA, PlanType.CHIP_SPA]), From 228972ff9312347674e5287592fa4b0ca651cf25 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Mon, 8 Jan 2024 09:45:01 -0700 Subject: [PATCH 05/66] feat(OY2-26538): package actions init --- src/packages/shared-types/opensearch/changelog.ts | 1 + src/services/data/handlers/sink.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/packages/shared-types/opensearch/changelog.ts b/src/packages/shared-types/opensearch/changelog.ts index 2694862be0..97bd59cf27 100644 --- a/src/packages/shared-types/opensearch/changelog.ts +++ b/src/packages/shared-types/opensearch/changelog.ts @@ -16,6 +16,7 @@ export type Document = { title: string; uploadDate: number; }[]; + timestamp: string; authority: string; id: string; origin: string; diff --git a/src/services/data/handlers/sink.ts b/src/services/data/handlers/sink.ts index ed2b5a6bf1..aca0548aad 100644 --- a/src/services/data/handlers/sink.ts +++ b/src/services/data/handlers/sink.ts @@ -235,6 +235,7 @@ export const onemac_changelog = async (event: Event) => { ACC.push({ ...record, ...(!record?.actionType && { actionType: "new-submission" }), // new-submission custom actionType + timestamp: REC.timestamp, id: `${packageId}-${REC.offset}`, packageId, }); From 64f37c063122071878d89467e9801959c75917b2 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Mon, 8 Jan 2024 16:07:40 -0700 Subject: [PATCH 06/66] feat(OY2-26538): package actions init --- src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx | 2 +- .../ui/src/pages/detail/package-activity/PackageActivity.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx index 2e8aca7d2e..c2bdd27078 100644 --- a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx +++ b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx @@ -54,7 +54,7 @@ export const AdminChange: FC = (props) => { {label as string} {" - "} {/* WHAT Date */} - {new Date(props.responseDate).toDateString()} + {new Date(props.timestamp).toDateString()}

diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx index ae344fac2e..6d34409a0c 100644 --- a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx +++ b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx @@ -191,7 +191,7 @@ export const PackageActivity: FC = (props) => { {label as string} {" - "} {/* WHAT Date */} - {new Date(props.responseDate).toDateString()} + {new Date(props.timestamp).toDateString()}

From 049626d4a0d4834fdc2c8e5ac980efcc74f7b9b5 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Mon, 8 Jan 2024 16:31:50 -0700 Subject: [PATCH 07/66] feat(OY2-26538): package actions init --- src/services/api/libs/package/getPackage.ts | 2 +- .../detail/admin-changes/AdminChanges.tsx | 8 ++------ .../src/pages/detail/admin-changes/index.tsx | 6 +++++- .../package-activity/PackageActivity.tsx | 18 ------------------ .../pages/detail/package-activity/index.tsx | 6 +++++- 5 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/services/api/libs/package/getPackage.ts b/src/services/api/libs/package/getPackage.ts index c7aa71e839..1118f42783 100644 --- a/src/services/api/libs/package/getPackage.ts +++ b/src/services/api/libs/package/getPackage.ts @@ -14,7 +14,7 @@ export const getPackage = async (id: string) => { from: 0, size: 200, // NOTE: get the required timestamp sort field - sort: [{}], + sort: [{ timestamp: "desc" }], query: { bool: { must: [{ term: { "packageId.keyword": id } }] } }, })) as opensearch.changelog.Response; diff --git a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx index c2bdd27078..118e941247 100644 --- a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx +++ b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx @@ -7,17 +7,13 @@ import { opensearch } from "shared-types"; import { FC, useMemo } from "react"; import { BLANK_VALUE } from "@/consts"; -export const AC_WithdrawEnabled: FC = ( - props -) => { +export const AC_WithdrawEnabled: FC = () => { return (

OneMac admin has enabled package action to submit formal RAI response

); }; -export const AC_WithdrawDisabled: FC = ( - props -) => { +export const AC_WithdrawDisabled: FC = () => { return (

OneMac admin has disabled package action to submit formal RAI response diff --git a/src/services/ui/src/pages/detail/admin-changes/index.tsx b/src/services/ui/src/pages/detail/admin-changes/index.tsx index 41c41a2303..3ad582505f 100644 --- a/src/services/ui/src/pages/detail/admin-changes/index.tsx +++ b/src/services/ui/src/pages/detail/admin-changes/index.tsx @@ -10,7 +10,11 @@ export const AdminChanges: FC = (props) => { ACTIONS_ADMIN.includes(CL._source.actionType) ); return ( - + + {!data?.length &&

-- no logs --

} {data?.map((CL) => ( diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx index 6d34409a0c..1223db4788 100644 --- a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx +++ b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx @@ -1,9 +1,7 @@ import { - Accordion, AccordionContent, AccordionItem, AccordionTrigger, - DetailsSection, } from "@/components"; import { opensearch } from "shared-types"; import { getAttachmentUrl } from "@/api"; @@ -146,18 +144,6 @@ export const PA_RaiIssued: FC = (props) => { ); }; -export const PA_WithdrawEnabled: FC = ( - props -) => { - return <>TBD; -}; - -export const PA_WithdrawDisabled: FC = ( - props -) => { - return <>TBD; -}; - const usePackageActivity = (doc: opensearch.changelog.Document) => { return useMemo(() => { switch (doc.actionType) { @@ -173,10 +159,6 @@ const usePackageActivity = (doc: opensearch.changelog.Document) => { return ["RAI response submitted", PA_ResponseSubmitted]; default: return [BLANK_VALUE, PA_ResponseSubmitted]; - // case "disable-rai-withdraw": - // return ["RAI withdraw disabled", PA_WithdrawDisabled]; - // case "enable-rai-withdraw": - // return ["RAI withdraw enabled", PA_WithdrawEnabled]; } }, [doc.actionType]); }; diff --git a/src/services/ui/src/pages/detail/package-activity/index.tsx b/src/services/ui/src/pages/detail/package-activity/index.tsx index 3b0e6c626f..2103a859a9 100644 --- a/src/services/ui/src/pages/detail/package-activity/index.tsx +++ b/src/services/ui/src/pages/detail/package-activity/index.tsx @@ -17,7 +17,11 @@ export const PackageActivities: FC = (props) => { ); return ( - + + {!data?.length &&

-- no logs --

} {data?.map((CL) => ( From d1bc5181fa63d4c8e619b2b04b6bf81e3283d787 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Mon, 8 Jan 2024 16:58:25 -0700 Subject: [PATCH 08/66] feat(OY2-26538): package actions dl --- .../src/components/DetailsSection/index.tsx | 2 +- .../detail/admin-changes/AdminChanges.tsx | 6 +- .../package-activity/PackageActivity.tsx | 88 +++++++++++-------- .../pages/detail/package-activity/index.tsx | 13 ++- 4 files changed, 69 insertions(+), 40 deletions(-) diff --git a/src/services/ui/src/components/DetailsSection/index.tsx b/src/services/ui/src/components/DetailsSection/index.tsx index f99bc2a01a..c3f22d0a88 100644 --- a/src/services/ui/src/components/DetailsSection/index.tsx +++ b/src/services/ui/src/components/DetailsSection/index.tsx @@ -1,6 +1,6 @@ interface DetailsSectionProps { children: React.ReactNode; - title: string; + title: React.ReactNode; id: string; description?: string; } diff --git a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx index 118e941247..9b9a635f19 100644 --- a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx +++ b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx @@ -6,6 +6,7 @@ import { import { opensearch } from "shared-types"; import { FC, useMemo } from "react"; import { BLANK_VALUE } from "@/consts"; +import { format } from "date-fns"; export const AC_WithdrawEnabled: FC = () => { return ( @@ -46,11 +47,10 @@ export const AdminChange: FC = (props) => { return ( -

+

{label as string} {" - "} - {/* WHAT Date */} - {new Date(props.timestamp).toDateString()} + {format(new Date(props.timestamp), "eee, MMM d, yyyy hh:mm:ss a OOO")}

diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx index 1223db4788..e13036ead4 100644 --- a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx +++ b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx @@ -9,44 +9,63 @@ import { FC, useMemo } from "react"; import { Button } from "@/components/Inputs"; import * as Table from "@/components/Table"; import { BLANK_VALUE } from "@/consts"; +import { format } from "date-fns"; export const PA_InitialSubmission: FC = ( props ) => { return ( - - - - Document Type - Attached File - - - - {props.attachments?.map((ATC) => { - return ( - - {ATC.title} - - - +
+ <> + + + + + Document Type + + Attached File - ); - })} - - + + + {props.attachments?.map((ATC) => { + return ( + + {ATC.title} + + + + + ); + })} + + + + +
+

Additional Information

+

{props.additionalInformation || "-- --"}

+
+
); }; @@ -169,11 +188,10 @@ export const PackageActivity: FC = (props) => { return ( -

+

{label as string} {" - "} - {/* WHAT Date */} - {new Date(props.timestamp).toDateString()} + {format(new Date(props.timestamp), "eee, MMM d, yyyy hh:mm:ss a OOO")}

diff --git a/src/services/ui/src/pages/detail/package-activity/index.tsx b/src/services/ui/src/pages/detail/package-activity/index.tsx index 2103a859a9..1d96d4c2c6 100644 --- a/src/services/ui/src/pages/detail/package-activity/index.tsx +++ b/src/services/ui/src/pages/detail/package-activity/index.tsx @@ -2,6 +2,7 @@ import { Accordion, DetailsSection } from "@/components"; import { opensearch } from "shared-types"; import { FC } from "react"; import { PackageActivity } from "./PackageActivity"; +import { Button } from "@/components/Inputs"; export const ACTIONS_PA = [ "new-submission", @@ -16,10 +17,20 @@ export const PackageActivities: FC = (props) => { ACTIONS_PA.includes(CL._source.actionType) ); + // TODO: OY2-26538 + const onDownloadAll = () => null; + return ( + {`Package Activity (${data?.length})`} + + + } > {!data?.length &&

-- no logs --

} From ac5e2b9137fea8514538e68f6c47b8c2bd7cdfc9 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Mon, 8 Jan 2024 17:15:35 -0700 Subject: [PATCH 09/66] feat(OY2-26538): package actions dl --- .../pages/detail/admin-changes/AdminChanges.tsx | 16 ++++++++++++---- .../ui/src/pages/detail/admin-changes/index.tsx | 5 +++-- .../detail/package-activity/PackageActivity.tsx | 11 +++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx index 9b9a635f19..46ba4755ab 100644 --- a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx +++ b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx @@ -8,16 +8,24 @@ import { FC, useMemo } from "react"; import { BLANK_VALUE } from "@/consts"; import { format } from "date-fns"; -export const AC_WithdrawEnabled: FC = () => { +export const AC_WithdrawEnabled: FC = ( + props +) => { return ( -

OneMac admin has enabled package action to submit formal RAI response

+

+ {props.submitterName} has enabled package action to submit formal RAI + response +

); }; -export const AC_WithdrawDisabled: FC = () => { +export const AC_WithdrawDisabled: FC = ( + props +) => { return (

- OneMac admin has disabled package action to submit formal RAI response + {props.submitterName} has disabled package action to submit formal RAI + response

); }; diff --git a/src/services/ui/src/pages/detail/admin-changes/index.tsx b/src/services/ui/src/pages/detail/admin-changes/index.tsx index 3ad582505f..5311a86c0a 100644 --- a/src/services/ui/src/pages/detail/admin-changes/index.tsx +++ b/src/services/ui/src/pages/detail/admin-changes/index.tsx @@ -12,10 +12,11 @@ export const AdminChanges: FC = (props) => { return ( {!data?.length &&

-- no logs --

} - + {data?.map((CL) => ( ))} diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx index e13036ead4..23b0d052d5 100644 --- a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx +++ b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx @@ -63,7 +63,7 @@ export const PA_InitialSubmission: FC = (

Additional Information

-

{props.additionalInformation || "-- --"}

+

{props.additionalInformation || "No information submitted"}

); @@ -76,6 +76,7 @@ export const PA_ResponseSubmitted: FC = (

Attached File

+ {!props.attachments?.length &&

No information submitted

} {props.attachments?.map((ATC) => (

Additional Information

-

{props.additionalInformation || "-- --"}

+

{props.additionalInformation || "No information submitted"}

); @@ -108,6 +109,7 @@ export const PA_ResponseWithdrawn: FC = (

Attached File

+ {!props.attachments?.length &&

No information submitted

} {props.attachments?.map((ATC) => (

Additional Information

-

{props.additionalInformation || "-- --"}

+

{props.additionalInformation || "No information submitted"}

); @@ -138,6 +140,7 @@ export const PA_RaiIssued: FC = (props) => {

Attached File

+ {!props.attachments?.length &&

No information submitted

} {props.attachments?.map((ATC) => (

Additional Information

-

{props.additionalInformation || "-- --"}

+

{props.additionalInformation || "No information submitted"}

); From 1efde1ac3ab60130d6006d32bfe80e1a45bbf691 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Tue, 9 Jan 2024 14:48:20 -0700 Subject: [PATCH 10/66] feat(OY2-26538): package actions cleanup --- src/services/api/handlers/item.ts | 26 ++++++++++--------- src/services/api/libs/package/changelog.ts | 15 +++++++++++ src/services/api/libs/package/getPackage.ts | 14 +--------- src/services/api/libs/package/index.ts | 2 ++ .../detail/admin-changes/AdminChanges.tsx | 4 ++- 5 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 src/services/api/libs/package/changelog.ts create mode 100644 src/services/api/libs/package/index.ts diff --git a/src/services/api/handlers/item.ts b/src/services/api/handlers/item.ts index 55ca1543c7..de71ae87ab 100644 --- a/src/services/api/handlers/item.ts +++ b/src/services/api/handlers/item.ts @@ -1,8 +1,7 @@ import { response } from "../libs/handler"; import { APIGatewayEvent } from "aws-lambda"; import { getStateFilter } from "../libs/auth/user"; -import { getPackage } from "../libs/package/getPackage"; - +import { getPackage, getPackageChangelog } from "../libs/package"; if (!process.env.osDomain) { throw "ERROR: osDomain env variable is required,"; } @@ -17,13 +16,13 @@ export const getItemData = async (event: APIGatewayEvent) => { try { const body = JSON.parse(event.body); const stateFilter = await getStateFilter(event); - const result = await getPackage(body.id); - + const packageResult = await getPackage(body.id); + const changelog = await getPackageChangelog(body.id); if ( stateFilter && - (!result._source.state || + (!packageResult._source.state || !stateFilter.terms.state.includes( - result._source.state.toLocaleLowerCase() + packageResult._source.state.toLocaleLowerCase() )) ) { return response({ @@ -32,17 +31,20 @@ export const getItemData = async (event: APIGatewayEvent) => { }); } - if (!result.found) { + if (!packageResult.found) { return response({ statusCode: 404, body: { message: "No record found for the given id" }, }); - } else { - return response({ - statusCode: 200, - body: result, - }); } + + return response({ + statusCode: 200, + body: { + ...packageResult, + _source: { ...packageResult._source, changelog: changelog.hits.hits }, + }, + }); } catch (error) { console.error({ error }); return response({ diff --git a/src/services/api/libs/package/changelog.ts b/src/services/api/libs/package/changelog.ts new file mode 100644 index 0000000000..578ffaaf59 --- /dev/null +++ b/src/services/api/libs/package/changelog.ts @@ -0,0 +1,15 @@ +import * as os from "../../../../libs/opensearch-lib"; +import { opensearch } from "shared-types"; + +export const getPackageChangelog = async (packageId: string) => { + if (!process.env.osDomain) { + throw new Error("process.env.osDomain must be defined"); + } + + return (await os.search(process.env.osDomain, "changelog", { + from: 0, + size: 200, + sort: [{ timestamp: "desc" }], + query: { bool: { must: [{ term: { "packageId.keyword": packageId } }] } }, + })) as opensearch.changelog.Response; +}; diff --git a/src/services/api/libs/package/getPackage.ts b/src/services/api/libs/package/getPackage.ts index 1118f42783..92c824667f 100644 --- a/src/services/api/libs/package/getPackage.ts +++ b/src/services/api/libs/package/getPackage.ts @@ -5,21 +5,9 @@ export const getPackage = async (id: string) => { if (!process.env.osDomain) { throw new Error("process.env.osDomain must be defined"); } - const main = (await os.getItem( + return (await os.getItem( process.env.osDomain, "main", id )) as opensearch.main.ItemResult; - const changelog = (await os.search(process.env.osDomain, "changelog", { - from: 0, - size: 200, - // NOTE: get the required timestamp sort field - sort: [{ timestamp: "desc" }], - query: { bool: { must: [{ term: { "packageId.keyword": id } }] } }, - })) as opensearch.changelog.Response; - - return { - ...main, - _source: { ...main._source, changelog: changelog.hits.hits }, - } as opensearch.main.ItemResult; }; diff --git a/src/services/api/libs/package/index.ts b/src/services/api/libs/package/index.ts new file mode 100644 index 0000000000..a82b6b308d --- /dev/null +++ b/src/services/api/libs/package/index.ts @@ -0,0 +1,2 @@ +export * from "./changelog"; +export * from "./getPackage"; diff --git a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx index 46ba4755ab..a855c0d2ac 100644 --- a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx +++ b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx @@ -34,7 +34,9 @@ export const AC_Update: FC = () => { return

Coming Soon

; }; -const useAdminChange = (doc: opensearch.changelog.Document) => { +const useAdminChange = ( + doc: opensearch.changelog.Document +): [string, FC] => { return useMemo(() => { switch (doc.actionType) { case "disable-rai-withdraw": From 5127c73b0b5e0e5b217474f1e6a2125ba5639bee Mon Sep 17 00:00:00 2001 From: Walesango2 <110047743+Walesango2@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:09:38 -0500 Subject: [PATCH 11/66] "fix(bugs): disable actions for help desk, drop 2nd clock, add withdraw package when RAI is issued" (#298) * fix(bug fix): withdraw package and status view for users * fix(add withdraw package): add withdraw package option when status is PAI issued * fix(dissable actions): disable actions for helpdesk * fix(attachement header): Add attachement header * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued * fix(add withdraw package): Add withdraw package to when RAI is issued --------- Co-authored-by: Adewale Sangobiyi Co-authored-by: Adewale Sangobiyi Co-authored-by: Adewale Sangobiyi --- src/packages/shared-types/seatool.ts | 8 ++------ src/packages/shared-types/statusHelper.ts | 6 ++++++ .../package-actions/getAvailableActions.ts | 2 +- src/packages/shared-utils/package-actions/rules.ts | 14 +++++++------- src/services/api/handlers/getPackageActions.ts | 1 + .../ui/src/pages/actions/WithdrawPackage.tsx | 5 +++++ src/services/ui/src/pages/detail/index.tsx | 6 ++++-- 7 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/packages/shared-types/seatool.ts b/src/packages/shared-types/seatool.ts index c7ef4833bb..860141ffb9 100644 --- a/src/packages/shared-types/seatool.ts +++ b/src/packages/shared-types/seatool.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { SEATOOL_STATUS, getStatus } from "./statusHelper"; +import { SEATOOL_STATUS, getStatus, finalDispositionStatuses } from "./statusHelper"; import { PlanType } from "./planType"; type AuthorityType = "SPA" | "WAIVER" | "MEDICAID" | "CHIP"; @@ -146,11 +146,7 @@ const compileSrtList = ( officers?.length ? officers.map((o) => `${o.FIRST_NAME} ${o.LAST_NAME}`) : []; const getFinalDispositionDate = (status: string, record: SeaToolSink) => { - const finalDispositionStatuses = [ - SEATOOL_STATUS.APPROVED, - SEATOOL_STATUS.DISAPPROVED, - SEATOOL_STATUS.WITHDRAWN, - ]; + return status && finalDispositionStatuses.includes(status) ? getDateStringOrNullFromEpoc(record.STATE_PLAN.STATUS_DATE) : null; diff --git a/src/packages/shared-types/statusHelper.ts b/src/packages/shared-types/statusHelper.ts index 7357847771..0696565260 100644 --- a/src/packages/shared-types/statusHelper.ts +++ b/src/packages/shared-types/statusHelper.ts @@ -38,6 +38,12 @@ const statusToDisplayToCmsUser = { [SEATOOL_STATUS.PENDING_OFF_THE_CLOCK]: "Pending - Off the Clock", }; +export const finalDispositionStatuses = [ + SEATOOL_STATUS.APPROVED, + SEATOOL_STATUS.DISAPPROVED, + SEATOOL_STATUS.WITHDRAWN, +]; + export const getStatus = (seatoolStatus?: string | null) => { const stateStatus = statusToDisplayToStateUser[seatoolStatus ?? "Unknown"]; const cmsStatus = statusToDisplayToCmsUser[seatoolStatus ?? "Unknown"]; diff --git a/src/packages/shared-utils/package-actions/getAvailableActions.ts b/src/packages/shared-utils/package-actions/getAvailableActions.ts index 20f433b592..2cd1d41acc 100644 --- a/src/packages/shared-utils/package-actions/getAvailableActions.ts +++ b/src/packages/shared-utils/package-actions/getAvailableActions.ts @@ -14,4 +14,4 @@ export const getAvailableActions = ( return checks.planTypeIs([PlanType.MED_SPA]) ? rules.filter((r) => r.check(checks, user)).map((r) => r.action) : []; -}; +}; \ No newline at end of file diff --git a/src/packages/shared-utils/package-actions/rules.ts b/src/packages/shared-utils/package-actions/rules.ts index 95631ec472..4a7790cdb9 100644 --- a/src/packages/shared-utils/package-actions/rules.ts +++ b/src/packages/shared-utils/package-actions/rules.ts @@ -1,5 +1,5 @@ -import { Action, ActionRule, SEATOOL_STATUS } from "../../shared-types"; -import { isCmsUser, isStateUser } from "../user-helper"; +import { Action, ActionRule, SEATOOL_STATUS, finalDispositionStatuses } from "../../shared-types"; +import { isCmsUser, isStateUser, isCmsReadonlyUser, isCmsWriteUser } from "../user-helper"; import { PackageCheck } from "../packageCheck"; const arIssueRai: ActionRule = { @@ -7,7 +7,7 @@ const arIssueRai: ActionRule = { check: (checker, user) => checker.isInActivePendingStatus && (!checker.hasLatestRai || checker.hasRequestedRai) && - isCmsUser(user), + isCmsWriteUser(user), }; const arRespondToRai: ActionRule = { @@ -24,7 +24,7 @@ const arEnableWithdrawRaiResponse: ActionRule = { checker.isNotWithdrawn && checker.hasRaiResponse && !checker.hasEnabledRaiWithdraw && - isCmsUser(user), + isCmsWriteUser(user) }; const arDisableWithdrawRaiResponse: ActionRule = { @@ -33,7 +33,7 @@ const arDisableWithdrawRaiResponse: ActionRule = { checker.isNotWithdrawn && checker.hasRaiResponse && checker.hasEnabledRaiWithdraw && - isCmsUser(user), + isCmsWriteUser(user) }; const arWithdrawRaiResponse: ActionRule = { @@ -44,11 +44,11 @@ const arWithdrawRaiResponse: ActionRule = { checker.hasEnabledRaiWithdraw && isStateUser(user), }; - const arWithdrawPackage: ActionRule = { action: Action.WITHDRAW_PACKAGE, check: (checker, user) => - checker.isInActivePendingStatus && isStateUser(user), + !checker.hasStatus(finalDispositionStatuses) + && isStateUser(user), }; export default [ diff --git a/src/services/api/handlers/getPackageActions.ts b/src/services/api/handlers/getPackageActions.ts index 3e14f1de5d..5927fd1a56 100644 --- a/src/services/api/handlers/getPackageActions.ts +++ b/src/services/api/handlers/getPackageActions.ts @@ -41,6 +41,7 @@ export const getPackageActions = async (event: APIGatewayEvent) => { authDetails.userId, authDetails.poolId ); + return response({ statusCode: 200, body: { diff --git a/src/services/ui/src/pages/actions/WithdrawPackage.tsx b/src/services/ui/src/pages/actions/WithdrawPackage.tsx index b9dee69b55..69c71a6906 100644 --- a/src/services/ui/src/pages/actions/WithdrawPackage.tsx +++ b/src/services/ui/src/pages/actions/WithdrawPackage.tsx @@ -94,6 +94,11 @@ const WithdrawPackageForm: React.FC = ({ item }: { item?: ItemResult }) => {

+

Attachments

+

+ Upload your supporting documentation for withdrawal or explain your + need for withdrawal in the Additional Information section. +

{/* Change faqLink once we know the anchor */} diff --git a/src/services/ui/src/pages/detail/index.tsx b/src/services/ui/src/pages/detail/index.tsx index 6be16ff7b5..26bd9e2bc5 100644 --- a/src/services/ui/src/pages/detail/index.tsx +++ b/src/services/ui/src/pages/detail/index.tsx @@ -54,12 +54,14 @@ const StatusCard = (data: OsMainSourceItem) => { : transformedStatuses.stateStatus} {checker.hasEnabledRaiWithdraw && ( - + {"Withdraw Formal RAI Response - Enabled"} )} {user?.isCms && checker.isInSecondClock && ( - 2nd Clock + + 2nd Clock + )} From 2bdcc7c115e549d8491528d7c3b6a7b1ceae6fc9 Mon Sep 17 00:00:00 2001 From: Kevin Haube Date: Wed, 10 Jan 2024 10:34:00 -0500 Subject: [PATCH 12/66] feat(actions): Restructure ActionFormIndex and utilize ModalContext in forms (#299) * Update Index * Update Modals * Update WithdrawPackage action form * Update ToggleRaiWithdraw action form * Update IssueRai action form * Update WithdrawRai action form * Update RespondToRai action form * Remove PackageActionForm * Update breadcrumbs generator name --- .../ui/src/pages/actions/IssueRai.tsx | 369 +++++++---------- .../src/pages/actions/PackageActionForm.tsx | 68 ---- .../ui/src/pages/actions/RespondToRai.tsx | 372 +++++++----------- .../actions/ToggleRaiResponseWithdraw.tsx | 55 +-- .../ui/src/pages/actions/WithdrawPackage.tsx | 242 ++++-------- .../ui/src/pages/actions/WithdrawRai.tsx | 118 +----- .../src/pages/actions/actions-breadcrumbs.ts | 2 +- src/services/ui/src/pages/actions/index.tsx | 75 +++- src/services/ui/src/pages/detail/index.tsx | 4 +- src/services/ui/src/pages/form/modals.tsx | 39 ++ 10 files changed, 498 insertions(+), 846 deletions(-) delete mode 100644 src/services/ui/src/pages/actions/PackageActionForm.tsx diff --git a/src/services/ui/src/pages/actions/IssueRai.tsx b/src/services/ui/src/pages/actions/IssueRai.tsx index abb84490a6..71d4522abf 100644 --- a/src/services/ui/src/pages/actions/IssueRai.tsx +++ b/src/services/ui/src/pages/actions/IssueRai.tsx @@ -1,18 +1,15 @@ -import { useParams, Link, useNavigate } from "@/components/Routing"; +import { useParams, Link } from "@/components/Routing"; import * as I from "@/components/Inputs"; -import { useState } from "react"; import { type SubmitHandler, useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import { SimplePageContainer, Alert, LoadingSpinner } from "@/components"; -import { ConfirmationModal } from "@/components/Modal/ConfirmationModal"; +import { Alert, LoadingSpinner } from "@/components"; import { FAQ_TARGET } from "@/routes"; -import { PlanType } from "shared-types"; +import { ItemResult, PlanType } from "shared-types"; import { useGetUser } from "@/api/useGetUser"; -import { useGetItem } from "@/api"; import { submit } from "@/api/submissionService"; import { buildActionUrl } from "@/lib"; -import { PackageActionForm } from "@/pages/actions/PackageActionForm"; +import { useModalContext } from "@/pages/form/modals"; const formSchema = z.object({ additionalInformation: z.string().max(4000), @@ -56,14 +53,11 @@ const FormDescriptionText = () => { ); }; -export const RaiIssueForm = () => { +export const RaiIssue = ({ item }: { item: ItemResult }) => { const { id, type } = useParams("/action/:id/:type"); - const { data: item } = useGetItem(id!); const authority = item?._source.authority as PlanType; - const [successModalIsOpen, setSuccessModalIsOpen] = useState(false); - const [errorModalIsOpen, setErrorModalIsOpen] = useState(false); - const [cancelModalIsOpen, setCancelModalIsOpen] = useState(false); - const navigate = useNavigate(); + const { setCancelModalOpen, setSuccessModalOpen, setErrorModalOpen } = + useModalContext(); const form = useForm({ resolver: zodResolver(formSchema), }); @@ -79,236 +73,155 @@ export const RaiIssueForm = () => { user, authority, }); - setSuccessModalIsOpen(true); + setSuccessModalOpen(true); } catch (err) { console.log(err); - setErrorModalIsOpen(true); + setErrorModalOpen(true); } }; return ( - - - -
-

Formal RAI Details

-

- Indicates a required field -

- -
- {/*-------------------------------------------------------- */} -
-

- Package Details -

-
- - - {id} - -
-
- - - {item?._source.planType} - -
-
- {/*-------------------------------------------------------- */} -
-

Attachments

-

- Maximum file size of 80 MB per attachment.{" "} - You can add multiple files per attachment type.{" "} - Read the description for each of the attachment types on the{" "} - { - - FAQ Page - - } - . -

-
-

- We accept the following file formats:{" "} - .docx, .jpg, .png, .pdf, .xlsx,{" "} - and a few others. See the full list on the{" "} - { - - FAQ Page - - } - . -

-
-

- - At least one attachment is required. -

-
- {attachmentList.map(({ name, label, required }) => ( - ( - - - {label} - {required ? : ""} - - - - - )} - /> - ))} - {/*-------------------------------------------------------- */} + + +
+

Formal RAI Details

+

+ Indicates a required field +

+ +
+ {/*-------------------------------------------------------- */} +
+

+ Package Details +

+
+ + + {id} + +
+
+ + + {item?._source.planType} + +
+
+ {/*-------------------------------------------------------- */} +
+

Attachments

+

+ Maximum file size of 80 MB per attachment.{" "} + You can add multiple files per attachment type.{" "} + Read the description for each of the attachment types on the{" "} + { + + FAQ Page + + } + . +

+
+

+ We accept the following file formats:{" "} + .docx, .jpg, .png, .pdf, .xlsx,{" "} + and a few others. See the full list on the{" "} + { + + FAQ Page + + } + . +

+
+

+ + At least one attachment is required. +

+
+ {attachmentList.map(({ name, label, required }) => ( ( -

- Additional Information* -

- - Add anything else you would like to share with the state. + + {label} + {required ? : ""} - - 4,000 characters allowed + +
)} /> - {/*-------------------------------------------------------- */} - - {Object.keys(form.formState.errors).length !== 0 ? ( - - Missing or malformed information. Please see errors above. - - ) : null} - {form.formState.isSubmitting && ( -
- -
+ ))} + {/*-------------------------------------------------------- */} + ( + +

+ Additional Information* +

+ + Add anything else you would like to share with the state. + + + 4,000 characters allowed +
)} -
- - Submit - - setCancelModalIsOpen(true)} - className="px-12" - > - Cancel - - { - setSuccessModalIsOpen(false); - // navigate(`/details?id=${id}`); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setSuccessModalIsOpen(false)} - title="The Formal RAI has been issued." - body={ -

- The Formal RAI has been issued successfully. You and the State - will receive an email confirmation. -

- } - cancelButtonVisible={false} - acceptButtonText="Exit to Package Details" - /> - { - setErrorModalIsOpen(false); - // navigate(`/details?id=${id}`); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setErrorModalIsOpen(false)} - title="Submission Error" - body={ -

- An error occurred during issue. -
- You may close this window and try again, however, this likely - requires support. -
-
- Please contact the{" "} - - helpdesk - {" "} - . You may include the following in your support request:{" "} -
-
-

    -
  • SPA ID: {id}
  • -
  • Timestamp: {Date.now()}
  • -
-

- } - cancelButtonVisible={true} - cancelButtonText="Return to Form" - acceptButtonText="Exit to Package Details" - /> - { - setCancelModalIsOpen(false); - // navigate(`/details?id=${id}`); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setCancelModalIsOpen(false)} - cancelButtonText="Return to Form" - acceptButtonText="Yes" - title="Are you sure you want to cancel?" - body={ -

- If you leave this page you will lose your progress on this - form -

- } - /> + /> + {/*-------------------------------------------------------- */} + + {Object.keys(form.formState.errors).length !== 0 ? ( + + Missing or malformed information. Please see errors above. + + ) : null} + {form.formState.isSubmitting && ( +
+
- - - + )} +
+ + Submit + + setCancelModalOpen(true)} + className="px-12" + > + Cancel + +
+ + ); }; - -export const RaiIssue = () => ( - - - -); diff --git a/src/services/ui/src/pages/actions/PackageActionForm.tsx b/src/services/ui/src/pages/actions/PackageActionForm.tsx deleted file mode 100644 index 8c27e9f757..0000000000 --- a/src/services/ui/src/pages/actions/PackageActionForm.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Navigate, useParams } from "@/components/Routing"; -import { useGetItem, useGetPackageActions } from "@/api"; -import { - Alert, - BreadCrumbs, - LoadingSpinner, - SimplePageContainer, -} from "@/components"; -import { DETAILS_AND_ACTIONS_CRUMBS } from "@/pages/actions/actions-breadcrumbs"; -import React, { - JSXElementConstructor, - PropsWithChildren, - ReactElement, -} from "react"; - -type CloneableChild = ReactElement>; - -export const PackageActionForm = ({ children }: PropsWithChildren) => { - const { id, type } = useParams("/action/:id/:type"); - const { - data: item, - isLoading: itemIsLoading, - error: itemError, - } = useGetItem(id!); - const { - data: actions, - isLoading: actionsAreLoading, - error: actionsError, - } = useGetPackageActions(id!); - - if (!id || !type) return ; - if (itemIsLoading || actionsAreLoading) return ; - return ( - - - {itemError && ( - - ERROR getting item: - {itemError.response.data.message} - - )} - {actionsError && ( - - ERROR getting available actions: - {actionsError.response.data.message} - - )} - {!actionsError && !actions?.actions.includes(type) && ( - - ERROR, invalid action: - You cannot perform {type} on this package. - - )} - {!actionsError && !itemError && actions.actions.includes(type) - ? React.Children.map( - children as CloneableChild[], - (child: CloneableChild) => - React.cloneElement(child, { - // Child has to be configured to take these - item, - }) - ) - : null} - - ); -}; diff --git a/src/services/ui/src/pages/actions/RespondToRai.tsx b/src/services/ui/src/pages/actions/RespondToRai.tsx index f154cf9c93..0c3bcf563b 100644 --- a/src/services/ui/src/pages/actions/RespondToRai.tsx +++ b/src/services/ui/src/pages/actions/RespondToRai.tsx @@ -1,18 +1,15 @@ import * as I from "@/components/Inputs"; -import { useState } from "react"; import { type SubmitHandler, useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import { SimplePageContainer, Alert, LoadingSpinner } from "@/components"; -import { ConfirmationModal } from "@/components/Modal/ConfirmationModal"; +import { Alert, LoadingSpinner } from "@/components"; import { FAQ_TARGET } from "@/routes"; -import { Link, useNavigate, useParams } from "@/components/Routing"; -import { Action, PlanType } from "shared-types"; +import { Link, useParams } from "@/components/Routing"; +import { ItemResult, PlanType } from "shared-types"; import { useGetUser } from "@/api/useGetUser"; import { submit } from "@/api/submissionService"; -import { useGetItem } from "@/api"; import { buildActionUrl } from "@/lib"; -import { PackageActionForm } from "@/pages/actions/PackageActionForm"; +import { useModalContext } from "@/pages/form/modals"; const formSchema = z.object({ additionalInformation: z.string().max(4000).optional(), @@ -54,14 +51,11 @@ const FormDescriptionText = () => { ); }; -export const RespondToRaiForm = () => { +export const RespondToRai = ({ item }: { item: ItemResult }) => { const { id, type } = useParams("/action/:id/:type"); - const { data: item } = useGetItem(id!); const authority = item?._source.authority as PlanType; - const [successModalIsOpen, setSuccessModalIsOpen] = useState(false); - const [errorModalIsOpen, setErrorModalIsOpen] = useState(false); - const [cancelModalIsOpen, setCancelModalIsOpen] = useState(false); - const navigate = useNavigate(); + const { setCancelModalOpen, setErrorModalOpen, setSuccessModalOpen } = + useModalContext(); const form = useForm({ resolver: zodResolver(formSchema), }); @@ -77,237 +71,157 @@ export const RespondToRaiForm = () => { user, authority, }); - setSuccessModalIsOpen(true); + setCancelModalOpen(true); } catch (err) { console.log(err); - setErrorModalIsOpen(true); + setErrorModalOpen(true); } }; return ( - - -
-
-

- Medicaid SPA Formal RAI Details -

-

- Indicates a required field -

- -
- {/*-------------------------------------------------------- */} -
-

- Package Details -

-
- - - {id} - -
-
- - - {item?._source.planType} - -
-
- {/*-------------------------------------------------------- */} -
-

Attachments

-

- Maximum file size of 80 MB per attachment.{" "} - You can add multiple files per attachment type.{" "} - Read the description for each of the attachment types on the{" "} - { - - FAQ Page - - } - . -

-
-

- We accept the following file formats:{" "} - .docx, .jpg, .png, .pdf, .xlsx,{" "} - and a few others. See the full list on the{" "} - { - - FAQ Page - - } - . -

-
-

- - At least one attachment is required. -

-
- {attachmentList.map(({ name, label, required }) => ( - ( - - - {label} - {required ? : ""} - - - - - )} - /> - ))} - {/*-------------------------------------------------------- */} + + +
+

+ Medicaid SPA Formal RAI Details +

+

+ Indicates a required field +

+ +
+ {/*-------------------------------------------------------- */} +
+

+ Package Details +

+
+ + + {id} + +
+
+ + + {item?._source.planType} + +
+
+ {/*-------------------------------------------------------- */} +
+

Attachments

+

+ Maximum file size of 80 MB per attachment.{" "} + You can add multiple files per attachment type.{" "} + Read the description for each of the attachment types on the{" "} + { + + FAQ Page + + } + . +

+
+

+ We accept the following file formats:{" "} + .docx, .jpg, .png, .pdf, .xlsx,{" "} + and a few others. See the full list on the{" "} + { + + FAQ Page + + } + . +

+
+

+ + At least one attachment is required. +

+
+ {attachmentList.map(({ name, label, required }) => ( ( -

- Additional Information -

- - Add anything else that you would like to share with CMS. + + {label} + {required ? : ""} - - 4,000 characters allowed + +
)} /> - {/*-------------------------------------------------------- */} - - {Object.keys(form.formState.errors).length !== 0 ? ( - - Missing or malformed information. Please see errors above. - - ) : null} - {form.formState.isSubmitting && ( -
- -
+ ))} + {/*-------------------------------------------------------- */} + ( + +

+ Additional Information +

+ + Add anything else that you would like to share with CMS. + + + 4,000 characters allowed +
)} -
- - Submit - - setCancelModalIsOpen(true)} - className="px-12" - > - Cancel - - { - setSuccessModalIsOpen(false); - // navigate(`/details?id=${id}`); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setSuccessModalIsOpen(false)} - title="Submission Successful" - body={ -

- Please be aware that it may take up to a minute for your - submission to show in the Dashboard. -

- } - cancelButtonVisible={false} - acceptButtonText="Exit to Package Details" - /> - { - setErrorModalIsOpen(false); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setErrorModalIsOpen(false)} - title="Submission Error" - body={ -

- An error occurred during issue. -
- You may close this window and try again, however, this likely - requires support. -
-
- Please contact the{" "} - - helpdesk - {" "} - . You may include the following in your support request:{" "} -
-
-

    -
  • SPA ID: {id}
  • -
  • Timestamp: {Date.now()}
  • -
-

- } - cancelButtonVisible={true} - cancelButtonText="Return to Form" - acceptButtonText="Exit to Package Details" - /> - { - setCancelModalIsOpen(false); - // navigate(`/details?id=${id}`); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setCancelModalIsOpen(false)} - cancelButtonText="Return to Form" - acceptButtonText="Yes" - title="Are you sure you want to cancel?" - body={ -

- If you leave this page you will lose your progress on this - form -

- } - /> + /> + {/*-------------------------------------------------------- */} + + {Object.keys(form.formState.errors).length !== 0 ? ( + + Missing or malformed information. Please see errors above. + + ) : null} + {form.formState.isSubmitting && ( +
+
- - - + )} +
+ + Submit + + setCancelModalOpen(true)} + className="px-12" + > + Cancel + +
+ + ); }; - -export const RespondToRai = () => ( - - - -); diff --git a/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx b/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx index d2704f47b4..315d3d8db2 100644 --- a/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx +++ b/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx @@ -1,23 +1,19 @@ -import { Navigate, useNavigate, useParams } from "@/components/Routing"; +import { Navigate, useParams } from "@/components/Routing"; import { Alert, LoadingSpinner } from "@/components"; import { Action, PlanType, ItemResult } from "shared-types"; import { Button } from "@/components/Inputs"; -import { useEffect, useMemo, useState } from "react"; -import { PackageActionForm } from "@/pages/actions/PackageActionForm"; -import { ConfirmationModal } from "@/components/Modal/ConfirmationModal"; +import { useEffect, useMemo } from "react"; import { useSubmissionService } from "@/api/submissionService"; import { buildActionUrl } from "@/lib"; import { useGetUser } from "@/api/useGetUser"; import { ActionFormIntro, PackageInfo } from "@/pages/actions/common"; +import { useModalContext } from "@/pages/form/modals"; -const ToggleRaiResponseWithdrawForm = ({ item }: { item?: ItemResult }) => { - const navigate = useNavigate(); +export const ToggleRaiResponseWithdraw = ({ item }: { item?: ItemResult }) => { const { id, type } = useParams("/action/:id/:type"); const { data: user } = useGetUser(); const authority = item?._source.authority as PlanType; - const [successModalOpen, setSuccessModalOpen] = useState(false); - const [cancelModalOpen, setCancelModalOpen] = useState(false); - + const { setCancelModalOpen, setSuccessModalOpen } = useModalContext(); const { mutate, isLoading, isSuccess, error } = useSubmissionService<{ id: string; }>({ @@ -26,6 +22,7 @@ const ToggleRaiResponseWithdrawForm = ({ item }: { item?: ItemResult }) => { user, authority, }); + const ACTION_WORD = useMemo( () => (type === Action.ENABLE_RAI_WITHDRAW ? "Enable" : "Disable"), [type] @@ -65,46 +62,6 @@ const ToggleRaiResponseWithdrawForm = ({ item }: { item?: ItemResult }) => { Cancel
- {/* Success Modal */} - { - setSuccessModalOpen(false); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setSuccessModalOpen(false)} // Should be made optional - cancelButtonVisible={false} // Should be made optional - title={`Formal RAI Response Withdraw Successfully ${ACTION_WORD}d`} - body={ -

- Please be aware that it may take up to a minute for changes to show - up on the Dashboard and Details pages. -

- } - acceptButtonText="Go to Package Details" - /> - - {/* Cancel Modal */} - { - setCancelModalOpen(false); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setCancelModalOpen(false)} - cancelButtonText="Return to Form" - acceptButtonText="Leave Page" - title="Are you sure you want to cancel?" - body={ -

If you leave this page you will lose your progress on this form

- } - /> ); }; - -export const ToggleRaiResponseWithdraw = () => ( - - - -); diff --git a/src/services/ui/src/pages/actions/WithdrawPackage.tsx b/src/services/ui/src/pages/actions/WithdrawPackage.tsx index 69c71a6906..4304fa23c0 100644 --- a/src/services/ui/src/pages/actions/WithdrawPackage.tsx +++ b/src/services/ui/src/pages/actions/WithdrawPackage.tsx @@ -1,9 +1,7 @@ -import { Navigate, useNavigate, useParams } from "@/components/Routing"; +import { Navigate, useParams } from "@/components/Routing"; import { Button } from "@/components/Inputs"; -import { ConfirmationModal } from "@/components/Modal/ConfirmationModal"; import { useState } from "react"; import { PlanType, ItemResult } from "shared-types"; -import { PackageActionForm } from "./PackageActionForm"; import { ActionFormIntro, PackageInfo } from "./common"; import { z } from "zod"; import { SubmitHandler, useForm } from "react-hook-form"; @@ -14,6 +12,7 @@ import { AttachmentRecipe, buildActionUrl } from "@/lib"; import { useGetUser } from "@/api/useGetUser"; import { submit } from "@/api/submissionService"; import { AttachmentsSizeTypesDesc } from "@/pages/form/content"; +import { useModalContext } from "@/pages/form/modals"; // Temporary, will be refactored to an extendable schema with Brian/Mike's back-end // work. @@ -35,12 +34,14 @@ const attachments: AttachmentRecipe[] = [ } as const, ]; -const WithdrawPackageForm: React.FC = ({ item }: { item?: ItemResult }) => { - const [successModalIsOpen, setSuccessModalIsOpen] = useState(false); - const [errorModalIsOpen, setErrorModalIsOpen] = useState(false); - const [cancelModalIsOpen, setCancelModalIsOpen] = useState(false); - const navigate = useNavigate(); +export const WithdrawPackage = ({ item }: { item?: ItemResult }) => { const { id, type } = useParams("/action/:id/:type"); + const { + cancelModalOpen, + setCancelModalOpen, + setSuccessModalOpen, + setErrorModalOpen, + } = useModalContext(); const { data: user } = useGetUser(); const authority = item?._source.authority as PlanType; const form = useForm({ @@ -52,7 +53,7 @@ const WithdrawPackageForm: React.FC = ({ item }: { item?: ItemResult }) => { data ) => { try { - if (!cancelModalIsOpen) { + if (!cancelModalOpen) { if ( !data.attachments.supportingDocumentation && !data.additionalInformation @@ -70,12 +71,12 @@ const WithdrawPackageForm: React.FC = ({ item }: { item?: ItemResult }) => { user, authority, }); - setSuccessModalIsOpen(true); + setSuccessModalOpen(true); } } } catch (err) { console.log(err); - setErrorModalIsOpen(true); + setErrorModalOpen(true); } }; @@ -84,167 +85,88 @@ const WithdrawPackageForm: React.FC = ({ item }: { item?: ItemResult }) => { <> {form.formState.isSubmitting && }
-
- -

- Complete this form to withdraw a package. Once complete, you will - not be able to resubmit this package. CMS will be notified and - will use this content to review your request. If CMS needs any - additional information, they will follow up by email. -

-
- -

Attachments

-

- Upload your supporting documentation for withdrawal or explain your - need for withdrawal in the Additional Information section. + +

+ Complete this form to withdraw a package. Once complete, you will + not be able to resubmit this package. CMS will be notified and will + use this content to review your request. If CMS needs any additional + information, they will follow up by email.

- -
- {/* Change faqLink once we know the anchor */} - - {attachments.map(({ name, label, required }) => ( - ( - - - {label} - {required ? : ""} - - - - - )} - /> - ))} + + +

Attachments

+

+ Upload your supporting documentation for withdrawal or explain your + need for withdrawal in the Additional Information section. +

+ + + {/* Change faqLink once we know the anchor */} + + {attachments.map(({ name, label, required }) => ( ( -

- Additional Information -

- - Explain your need for withdrawal or upload supporting - documentation. -
-

- - Once you submit this form, a confirmation email is - sent to you and to CMS. CMS will use this content to - review your package. If CMS needs any additional - information, they will follow up by email. - {" "} -

-
+ + {label} + {required ? : ""} - - - 4,000 characters allowed - + +
)} /> - {errorMessage && ( -
{errorMessage}
+ ))} + ( + +

+ Additional Information +

+ + Explain your need for withdrawal or upload supporting + documentation. +
+

+ + Once you submit this form, a confirmation email is sent + to you and to CMS. CMS will use this content to review + your package. If CMS needs any additional information, + they will follow up by email. + {" "} +

+
+
+ + + 4,000 characters allowed + +
)} -
- - -
- -
-
- {/* Success Modal */} - { - setSuccessModalIsOpen(false); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setSuccessModalIsOpen(false)} // Should be made optional - title="Withdraw Successful" - body={ -

- Please be aware that it may take up to a minute for your status to - change on the Dashboard and Details pages. -

- } - cancelButtonVisible={false} - acceptButtonText="Go to Package Details" - /> - {/* Error Modal */} - { - setErrorModalIsOpen(false); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setErrorModalIsOpen(false)} - title="Submission Error" - body={ -

- An error occurred during issue. -
- You may close this window and try again, however, this likely - requires support. -
-
- Please contact the{" "} - + {errorMessage && ( +

+ )} +
+ + +
+ +
); }; - -export const WithdrawPackage = () => ( - - - -); diff --git a/src/services/ui/src/pages/actions/WithdrawRai.tsx b/src/services/ui/src/pages/actions/WithdrawRai.tsx index 84b05dcef5..743e254a9f 100644 --- a/src/services/ui/src/pages/actions/WithdrawRai.tsx +++ b/src/services/ui/src/pages/actions/WithdrawRai.tsx @@ -1,20 +1,15 @@ -import { - ConfirmationModal, - LoadingSpinner, - SimplePageContainer, -} from "@/components"; +import { ConfirmationModal, LoadingSpinner } from "@/components"; import * as I from "@/components/Inputs"; import { zodResolver } from "@hookform/resolvers/zod"; import { SubmitHandler, useForm } from "react-hook-form"; import { z } from "zod"; -import { Link, useNavigate, useParams } from "@/components/Routing"; -import { PlanType } from "shared-types"; -import { useGetItem } from "@/api/useGetItem"; +import { Link, useParams } from "@/components/Routing"; +import { ItemResult, PlanType } from "shared-types"; import { useGetUser } from "@/api/useGetUser"; -import { PackageActionForm } from "./PackageActionForm"; import { submit } from "@/api/submissionService"; import { useState } from "react"; import { FAQ_TARGET } from "@/routes"; +import { useModalContext } from "@/pages/form/modals"; const formSchema = z.object({ additionalInformation: z.string().max(4000), @@ -33,22 +28,16 @@ const attachmentList = [ }, ] as const; -export const WithdrawRaiForm = () => { - const form = useForm({ - resolver: zodResolver(formSchema), - }); - const [successModalIsOpen, setSuccessModalIsOpen] = useState(false); - const [areYouSureModalIsOpen, setAreYouSureModalIsOpen] = useState(false); - const [errorModalIsOpen, setErrorModalIsOpen] = useState(false); - const [cancelModalIsOpen, setCancelModalIsOpen] = useState(false); - +export const WithdrawRai = ({ item }: { item: ItemResult }) => { const { id } = useParams("/action/:id/:type"); - - const navigate = useNavigate(); - - const { data: item } = useGetItem(id!); + const { setCancelModalOpen, setSuccessModalOpen, setErrorModalOpen } = + useModalContext(); + const [areYouSureModalIsOpen, setAreYouSureModalIsOpen] = useState(false); const authority = item?._source.authority as PlanType; const user = useGetUser(); + const form = useForm({ + resolver: zodResolver(formSchema), + }); const handleSubmit: SubmitHandler = async (data) => { try { @@ -59,18 +48,17 @@ export const WithdrawRaiForm = () => { user: user.data, authority: authority, }); - - setSuccessModalIsOpen(true); + setSuccessModalOpen(true); } catch (err: unknown) { if (err) { console.log("There was an error", err); - setErrorModalIsOpen(true); + setErrorModalOpen(true); } } }; return ( - + <>
{ setCancelModalIsOpen(true)} + onClick={() => setCancelModalOpen(true)} className="px-12" > Cancel @@ -231,80 +219,6 @@ export const WithdrawRaiForm = () => { acceptButtonText="Yes, withdraw response" cancelButtonText="Cancel" /> - - { - setSuccessModalIsOpen(false); - // navigate(`/details?id=${id}`); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setSuccessModalIsOpen(false)} - title="Withdraw Formal RAI Response request has been submitted." - body={ -

- Your Formal RAI Response has been withdrawn successfully. If CMS - needs any additional information, they will follow up by email. -

- } - cancelButtonVisible={false} - acceptButtonText="Exit to Package Details" - /> - { - setErrorModalIsOpen(false); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setErrorModalIsOpen(false)} - title="Submission Error" - body={ -

- An error occurred during Formal RAI Response Withdraw. -
- You may close this window and try again, however, this likely - requires support. -
-
- Please contact the{" "} - - helpdesk - {" "} - . You may include the following in your support request:
-
-

    -
  • SPA ID: {id}
  • -
  • Timestamp: {Date.now()}
  • -
-

- } - cancelButtonVisible={true} - cancelButtonText="Return to Form" - acceptButtonText="Exit to Package Details" - /> - { - setCancelModalIsOpen(false); - navigate({ path: "/details", query: { id } }); - }} - onCancel={() => setCancelModalIsOpen(false)} - cancelButtonText="Return to Form" - acceptButtonText="Yes" - title="Are you sure you want to cancel?" - body={ -

If you leave this page you will lose your progress on this form

- } - /> - + ); }; - -export const WithdrawRai = () => ( - - - -); diff --git a/src/services/ui/src/pages/actions/actions-breadcrumbs.ts b/src/services/ui/src/pages/actions/actions-breadcrumbs.ts index 547b4775fc..1ac8c7c8b3 100644 --- a/src/services/ui/src/pages/actions/actions-breadcrumbs.ts +++ b/src/services/ui/src/pages/actions/actions-breadcrumbs.ts @@ -2,7 +2,7 @@ import { Action } from "shared-types"; import { BreadCrumbConfig } from "@/components"; import { actionCrumb, dashboardCrumb, detailsCrumb } from "@/utils/crumbs"; -export const DETAILS_AND_ACTIONS_CRUMBS = ({ +export const detailsAndActionsCrumbs = ({ id, action, }: { diff --git a/src/services/ui/src/pages/actions/index.tsx b/src/services/ui/src/pages/actions/index.tsx index d235a65df2..5ff6059280 100644 --- a/src/services/ui/src/pages/actions/index.tsx +++ b/src/services/ui/src/pages/actions/index.tsx @@ -5,22 +5,83 @@ import { WithdrawPackage } from "@/pages/actions/WithdrawPackage"; import { RespondToRai } from "@/pages/actions/RespondToRai"; import { Action } from "shared-types"; import { WithdrawRai } from "./WithdrawRai"; +import { useGetItem, useGetPackageActions } from "@/api"; +import { + Alert, + BreadCrumbs, + LoadingSpinner, + SimplePageContainer, +} from "@/components"; +import { ModalProvider } from "@/pages/form/modals"; +import { detailsAndActionsCrumbs } from "@/pages/actions/actions-breadcrumbs"; -export const ActionFormIndex = () => { - const { type } = useParams("/action/:id/:type"); +const ActionFormSwitch = () => { + const { id, type } = useParams("/action/:id/:type"); + const { + data: item, + isLoading: itemIsLoading, + error: itemError, + } = useGetItem(id!); + const { + data: actions, + isLoading: actionsAreLoading, + error: actionsError, + } = useGetPackageActions(id!); + // Safety bolt-on to limit non-null assertions needed + if (!id || !type) return ; + + // Non-form renders + if (itemIsLoading || actionsAreLoading) return ; + if (itemError) + return ( + + ERROR getting item: + {itemError.response.data.message} + + ); + if (actionsError) + return ( + + ERROR getting available actions: + {actionsError.response.data.message} + + ); + if (!actionsError && !actions?.actions.includes(type)) + return ( + + ERROR, invalid action: + You cannot perform {type} on this package. + + ); + + // Form renders switch (type) { case Action.WITHDRAW_PACKAGE: - return ; + return ; case Action.ENABLE_RAI_WITHDRAW: case Action.DISABLE_RAI_WITHDRAW: - return ; + return ; case Action.ISSUE_RAI: - return ; + return ; case Action.WITHDRAW_RAI: - return ; + return ; case Action.RESPOND_TO_RAI: - return ; + return ; default: return ; } }; + +export const ActionFormIndex = () => { + const { id, type } = useParams("/action/:id/:type"); + return ( + + + + + + + ); +}; diff --git a/src/services/ui/src/pages/detail/index.tsx b/src/services/ui/src/pages/detail/index.tsx index 26bd9e2bc5..772f5ccfa7 100644 --- a/src/services/ui/src/pages/detail/index.tsx +++ b/src/services/ui/src/pages/detail/index.tsx @@ -20,7 +20,7 @@ import { mapActionLabel } from "@/utils"; import { useLocation } from "react-router-dom"; import { useGetPackageActions } from "@/api/useGetPackageActions"; import { PropsWithChildren, useState } from "react"; -import { DETAILS_AND_ACTIONS_CRUMBS } from "@/pages/actions/actions-breadcrumbs"; +import { detailsAndActionsCrumbs } from "@/pages/actions/actions-breadcrumbs"; import { API } from "aws-amplify"; import { getStatus } from "shared-types/statusHelper"; import { spaDetails, submissionDetails } from "@/pages/detail/setup/spa"; @@ -229,7 +229,7 @@ export const Details = () => { return ( <>
- +
diff --git a/src/services/ui/src/pages/form/modals.tsx b/src/services/ui/src/pages/form/modals.tsx index cf42631c4a..6da7e9d1be 100644 --- a/src/services/ui/src/pages/form/modals.tsx +++ b/src/services/ui/src/pages/form/modals.tsx @@ -52,14 +52,49 @@ const Cancel = ({ open, setOpen }: ModalProps) => { ); }; +const Error = ({ open, setOpen }: ModalProps) => { + return ( + { + setOpen(false); + }} + onCancel={() => setOpen(false)} + title="Submission Error" + body={ +

+ An error occurred during issue. +
+ You may close this window and try again, however, this likely requires + support. +
+
+ Please contact the{" "} + + helpdesk + +

+ } + cancelButtonVisible={false} + acceptButtonText="Exit to Package Details" + /> + ); +}; + const useFormModalControllers = () => { const [cancelModalOpen, setCancelModalOpen] = useState(false); const [successModalOpen, setSuccessModalOpen] = useState(false); + const [errorModalOpen, setErrorModalOpen] = useState(false); return { cancelModalOpen, setCancelModalOpen, successModalOpen, setSuccessModalOpen, + errorModalOpen, + setErrorModalOpen, }; }; @@ -83,6 +118,10 @@ export const ModalProvider = ({ children }: PropsWithChildren) => { open={context.cancelModalOpen} setOpen={context.setCancelModalOpen} /> + ); }; From b0b261c45a9dd2999987e7e0fa4fb42361f50dd1 Mon Sep 17 00:00:00 2001 From: "Gavin St. Ours" Date: Wed, 10 Jan 2024 11:10:05 -0500 Subject: [PATCH 13/66] feat(ABP 3) Adds ABP 3 form to OneMAC (Mako) (#292) * Set up shell for ABP 3 * Add first two sections of ABP 3 * Finish first pass of form * Add conditional logic to first section * Add a break between webforms * Fix copy issues * Update form title label to reflect Figma * Fix typos in PRA disclosure statement * Remove unnecessary label --------- Co-authored-by: Benjamin Paige --- src/services/api/layers/ABP3/v1.ts | 359 ++++++++++++++++++ .../ui/src/components/RHF/Document.tsx | 2 +- .../ui/src/components/Webform/footer.tsx | 8 +- .../ui/src/components/Webform/index.tsx | 9 +- 4 files changed, 372 insertions(+), 6 deletions(-) create mode 100644 src/services/api/layers/ABP3/v1.ts diff --git a/src/services/api/layers/ABP3/v1.ts b/src/services/api/layers/ABP3/v1.ts new file mode 100644 index 0000000000..1b846b4a63 --- /dev/null +++ b/src/services/api/layers/ABP3/v1.ts @@ -0,0 +1,359 @@ +import { FormSchema } from "shared-types"; + +const ABP3: FormSchema = { + header: + "ABP 3: Selection of benchmark benefit package or benchmark-equivalent benefit package", + sections: [ + { + title: "Benefit package details", + form: [ + { + description: "Select one of the following", + slots: [ + { + rhf: "Radio", + name: "benefit_package_details", + rules: { required: "* Required" }, + props: { + options: [ + { + label: + "The state/territory is amending one existing benefit package for the population defined in section 1.", + value: "benchmark_amending", + }, + { + label: + "The state/territory is creating a single new benefit package for the population defined in section 1.", + value: "benchmark_creating", + }, + ], + }, + }, + { + rhf: "Input", + name: "benefit_package_name", + label: "Benefit package name", + rules: { required: "* Required" }, + dependency: { + conditions: [ + { + name: "benefit_package_details", + type: "expectedValue", + expectedValue: "benchmark_creating", + }, + ], + effect: { type: "show" }, + }, + }, + ], + }, + ], + }, + { + title: "Selection of Section 1937 coverage option", + form: [ + { + description: + "The state/territory selects as its Section 1937 coverage option the following type of benchmark benefit package or benchmark-equivalent benefit package under this Alternative Benefit Plan:", + slots: [ + { + rhf: "Radio", + name: "section_1937_coverage_option", + rules: { required: "* Required" }, + props: { + options: [ + { + label: "Benchmark benefit package", + value: "benchmark_benefit_package", + form: [ + { + description: + "The state/territory will provide the following benchmark benefit package:", + slots: [ + { + rhf: "Radio", + name: "Benchmark_benefit_packag_options", + rules: { required: "* Required" }, + props: { + options: [ + { + label: + "The standard Blue Cross Blue Shield preferred provider option offered through the Federal Employee Health Benefit Program (FEHBP)", + value: "blue_cross_blue_shield", + }, + { + label: + "State employee coverage that is offered and generally available to state employees (state employee coverage)", + value: "state_employee_coverage", + form: [ + { + slots: [ + { + rhf: "Input", + label: "Plan name", + name: "state_employee_coverage_plan_name", + rules: { required: "* Required" }, + }, + ], + }, + ], + }, + { + label: + "A commercial HMO with the largest insured commercial, non-Medicaid enrollment in the state/territory (commercial HMO)", + value: "commercial_hmo", + form: [ + { + slots: [ + { + rhf: "Input", + label: "Plan name", + name: "commercial_hmo_plan_name", + rules: { required: "* Required" }, + }, + ], + }, + ], + }, + { + label: "Secretary-approved coverage", + value: "secretary_approved_coverage", + form: [ + { + slots: [ + { + rhf: "Radio", + name: "secretary_approved_coverage_options", + rules: { required: "* Required" }, + props: { + options: [ + { + label: + "The state/territory offers benefits based on the approved state plan.", + value: "approved_state_plan", + form: [ + { + slots: [ + { + rhf: "Radio", + name: "approved_state_plan_options", + rules: { + required: + "* Required", + }, + props: { + options: [ + { + label: + "The state/territory offers the benefits provided in the approved state plan.", + value: + "approved_state_plan", + }, + { + label: + "Benefits include all those provided in the approved state plan plus additional benefits.", + value: + "additional_benefits", + }, + { + label: + "Benefits are the same as provided in the approved state plan but in a different amount, duration, and/or scope.", + value: + "different_amount_duration_scope", + }, + { + label: + "The state/territory offers only a partial list of benefits provided in the approved state plan.", + value: + "partial_list_of_benefits", + }, + { + label: + "The state/territory offers a partial list of benefits provided in the approved state plan plus additional benefits.", + value: + "partial_list_of_benefits_plus_additional_benefits", + }, + ], + }, + }, + ], + }, + ], + }, + { + label: + "The state/territory offers an array of benefits from the Section 1937 coverage option and/or base benchmark plan benefit packages, the approved state plan, or a combination of these benefit packages.", + value: "array_of_benefits", + }, + ], + }, + }, + { + rhf: "Textarea", + name: "benefits_and_limitations", + rules: { required: "* Required" }, + label: + "Briefly identify the benefits, the source of benefits, and any limitations.", + }, + ], + }, + ], + }, + ], + }, + }, + ], + }, + ], + }, + { + label: "Benchmark-equivalent benefit package", + value: "benchmark_equivalent_benefit_package", + form: [ + { + description: + "The state/territory will provide the following benchmark-equivalent benefit package:", + slots: [ + { + rhf: "Radio", + name: "benchmark_equivalent", + rules: { required: "* Required" }, + props: { + options: [ + { + label: + "The standard Blue Cross Blue Shield preferred provider option offered through the Federal Employee Health Benefit Program (FEHBP)", + value: "blue_cross_blue_shield", + }, + { + label: + "State employee coverage that is offered and generally available to state employees (state employee coverage)", + value: "state_employee_coverage", + form: [ + { + slots: [ + { + rhf: "Input", + name: "state_employee_coverage_plan_name", + label: "Plan name", + rules: { required: "* Required" }, + }, + ], + }, + ], + }, + { + label: + "A commercial HMO with the largest insured commercial, non-Medicaid enrollment in the state/territory (commercial HMO)", + value: "commercial_hmo", + form: [ + { + slots: [ + { + rhf: "Input", + name: "commercial_hmo_plan_name", + label: "Plan name", + rules: { required: "* Required" }, + }, + ], + }, + ], + }, + { + label: "Secretary-approved coverage", + value: "secretary_approved_coverage", + }, + ], + }, + }, + ], + }, + ], + }, + ], + }, + }, + ], + }, + ], + }, + { + title: "Selection of base benchmark plan", + form: [ + { + description: + "The state/territory must select a base benchmark plan as the basis for providing essential health benefits in its benchmark or benchmark-equivalent package.", + slots: [ + { + rhf: "Select", + label: + "Is the base benchmark plan the same as the Section 1937 coverage option?", + name: "base_benchmark_plan_same_as_section_1937", + rules: { required: "* Required" }, + props: { + className: "w-[150px]", + options: [ + { label: "Yes", value: "yes" }, + { label: "No", value: "no" }, + ], + }, + }, + { + rhf: "Radio", + label: + "Indicate which benchmark plan described at 45 CFR 156.100(a) the state/territory will use as its base benchmark plan.", + name: "base_benchmark_plan", + rules: { required: "* Required" }, + props: { + options: [ + { + label: + "The largest plan by enrollment of the three largest small group insurance products in the state's small group market", + value: "largest_plan_by_enrollment", + }, + { + label: + "Any of the largest three state employee health benefit plans by enrollment", + value: "any_of_largest_three_state", + }, + { + label: + "Any of the largest three national FEHBP plan options open to federal employees in all geographies by enrollment", + value: "any_of_largest_three_national_fehbp_plan_options", + }, + { + label: "The largest insured commercial non-Medicaid HMO", + value: "largest_insured_commercial_hmo", + }, + ], + }, + }, + { + rhf: "Input", + label: "Plan name", + name: "base_benchmark_plan_name", + rules: { required: "* Required" }, + }, + ], + }, + ], + }, + { + title: "Additional information", + form: [ + { + description: + "Other information related to selection of the Section 1937 coverage option and the base benchmark plan (optional)", + slots: [ + { + rhf: "Textarea", + name: "additional_information", + }, + ], + }, + ], + }, + ], +}; + +export const form = ABP3; diff --git a/src/services/ui/src/components/RHF/Document.tsx b/src/services/ui/src/components/RHF/Document.tsx index 8f9d05460a..237b54e0a0 100644 --- a/src/services/ui/src/components/RHF/Document.tsx +++ b/src/services/ui/src/components/RHF/Document.tsx @@ -13,7 +13,7 @@ export const RHFDocument = (props: {
- + {props.document.header}
diff --git a/src/services/ui/src/components/Webform/footer.tsx b/src/services/ui/src/components/Webform/footer.tsx index 5940b8af41..663dc5bf04 100644 --- a/src/services/ui/src/components/Webform/footer.tsx +++ b/src/services/ui/src/components/Webform/footer.tsx @@ -7,19 +7,19 @@ export const Footer = () => { "All State Medicaid agencies administering or supervising the \ administration of 1915(c) home and community-based services (HCBS) \ waivers are required to submit an annual Form CMS-372(S) Report for each \ - approved waiver. Section 1915(c)(2)(E) of the SocialSecurity Act \ + approved waiver. Section 1915(c)(2)(E) of the Social Security Act \ requires states to annually provide CMS with information on the waiver's \ impact on the type, amount and cost of services provided under the state \ - plan in addition to the health and welgare of recipients. Under the \ + plan in addition to the health and welfare of recipients. Under the \ Privacy Act of 1974 any personally identifying information obatined will \ be kept private to the extent of the law." }

{ - "Accordint to the Paperwork Reduction Act of 1995, no persons are required to respond \ + "According to the Paperwork Reduction Act of 1995, no persons are required to respond \ to a collection of information unless it displays a valid OMB control number. The valid OMB \ - control number for this information colleciton is 0938-0272. The time required to complete \ + control number for this information collection is 0938-0272. The time required to complete \ this information collection is estimated to average 44 hours per response, including the time to \ review instructions, search existing data resources, gather the data needed, and complete and \ review the information collection. If you have comments concerning the accuracy of the time \ diff --git a/src/services/ui/src/components/Webform/index.tsx b/src/services/ui/src/components/Webform/index.tsx index 0f13721f99..63d24e808e 100644 --- a/src/services/ui/src/components/Webform/index.tsx +++ b/src/services/ui/src/components/Webform/index.tsx @@ -20,7 +20,14 @@ export const Webforms = () => { path="/webform/:id/:version" params={{ id: "abp1", version: 1 }} > - ABP1 + ABP 1 + +
+ + ABP 3

From 43e0cafcb6f46b135d8b1bc289efc96146a48e26 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Wed, 10 Jan 2024 09:15:11 -0700 Subject: [PATCH 14/66] Merge branch 'master' of https://github.com/Enterprise-CMCS/macpro-mako into bruce --- src/services/ui/src/pages/actions/IssueRai.tsx | 4 ++-- src/services/ui/src/pages/actions/RespondToRai.tsx | 8 ++++++-- .../ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx | 6 ++++-- src/services/ui/src/pages/actions/WithdrawPackage.tsx | 4 +++- src/services/ui/src/pages/actions/WithdrawRai.tsx | 4 ++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/services/ui/src/pages/actions/IssueRai.tsx b/src/services/ui/src/pages/actions/IssueRai.tsx index 71d4522abf..8a35d9ba2b 100644 --- a/src/services/ui/src/pages/actions/IssueRai.tsx +++ b/src/services/ui/src/pages/actions/IssueRai.tsx @@ -5,7 +5,7 @@ import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { Alert, LoadingSpinner } from "@/components"; import { FAQ_TARGET } from "@/routes"; -import { ItemResult, PlanType } from "shared-types"; +import { opensearch, PlanType } from "shared-types"; import { useGetUser } from "@/api/useGetUser"; import { submit } from "@/api/submissionService"; import { buildActionUrl } from "@/lib"; @@ -53,7 +53,7 @@ const FormDescriptionText = () => { ); }; -export const RaiIssue = ({ item }: { item: ItemResult }) => { +export const RaiIssue = ({ item }: { item: opensearch.main.ItemResult }) => { const { id, type } = useParams("/action/:id/:type"); const authority = item?._source.authority as PlanType; const { setCancelModalOpen, setSuccessModalOpen, setErrorModalOpen } = diff --git a/src/services/ui/src/pages/actions/RespondToRai.tsx b/src/services/ui/src/pages/actions/RespondToRai.tsx index 0c3bcf563b..5b3bbbca88 100644 --- a/src/services/ui/src/pages/actions/RespondToRai.tsx +++ b/src/services/ui/src/pages/actions/RespondToRai.tsx @@ -5,7 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Alert, LoadingSpinner } from "@/components"; import { FAQ_TARGET } from "@/routes"; import { Link, useParams } from "@/components/Routing"; -import { ItemResult, PlanType } from "shared-types"; +import { opensearch, PlanType } from "shared-types"; import { useGetUser } from "@/api/useGetUser"; import { submit } from "@/api/submissionService"; import { buildActionUrl } from "@/lib"; @@ -51,7 +51,11 @@ const FormDescriptionText = () => { ); }; -export const RespondToRai = ({ item }: { item: ItemResult }) => { +export const RespondToRai = ({ + item, +}: { + item: opensearch.main.ItemResult; +}) => { const { id, type } = useParams("/action/:id/:type"); const authority = item?._source.authority as PlanType; const { setCancelModalOpen, setErrorModalOpen, setSuccessModalOpen } = diff --git a/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx b/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx index 1f44442626..16252cfdac 100644 --- a/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx +++ b/src/services/ui/src/pages/actions/ToggleRaiResponseWithdraw.tsx @@ -9,9 +9,11 @@ import { useGetUser } from "@/api/useGetUser"; import { ActionFormIntro, PackageInfo } from "@/pages/actions/common"; import { useModalContext } from "@/pages/form/modals"; -export const ToggleRaiResponseWithdraw: FC<{ +export const ToggleRaiResponseWithdraw = ({ + item, +}: { item?: opensearch.main.ItemResult; -}> = ({ item }) => { +}) => { const { id, type } = useParams("/action/:id/:type"); const { data: user } = useGetUser(); const authority = item?._source.authority as PlanType; diff --git a/src/services/ui/src/pages/actions/WithdrawPackage.tsx b/src/services/ui/src/pages/actions/WithdrawPackage.tsx index 3f62536050..9f4302afde 100644 --- a/src/services/ui/src/pages/actions/WithdrawPackage.tsx +++ b/src/services/ui/src/pages/actions/WithdrawPackage.tsx @@ -34,8 +34,10 @@ const attachments: AttachmentRecipe[] = [ } as const, ]; -export const WithdrawPackage: FC<{ item: opensearch.main.ItemResult }> = ({ +export const WithdrawPackage = ({ item, +}: { + item: opensearch.main.ItemResult; }) => { const { id, type } = useParams("/action/:id/:type"); const { diff --git a/src/services/ui/src/pages/actions/WithdrawRai.tsx b/src/services/ui/src/pages/actions/WithdrawRai.tsx index 743e254a9f..5f776f51e1 100644 --- a/src/services/ui/src/pages/actions/WithdrawRai.tsx +++ b/src/services/ui/src/pages/actions/WithdrawRai.tsx @@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { SubmitHandler, useForm } from "react-hook-form"; import { z } from "zod"; import { Link, useParams } from "@/components/Routing"; -import { ItemResult, PlanType } from "shared-types"; +import { opensearch, PlanType } from "shared-types"; import { useGetUser } from "@/api/useGetUser"; import { submit } from "@/api/submissionService"; import { useState } from "react"; @@ -28,7 +28,7 @@ const attachmentList = [ }, ] as const; -export const WithdrawRai = ({ item }: { item: ItemResult }) => { +export const WithdrawRai = ({ item }: { item: opensearch.main.ItemResult }) => { const { id } = useParams("/action/:id/:type"); const { setCancelModalOpen, setSuccessModalOpen, setErrorModalOpen } = useModalContext(); From ce60a567343ce0dad7f1bc9a627a5425f07cb752 Mon Sep 17 00:00:00 2001 From: Mike Dial <48921055+mdial89f@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:57:01 -0500 Subject: [PATCH 15/66] fix(respon to rai modal): Typo causing bad modal behavior.. fixed (#314) --- src/services/ui/src/pages/actions/RespondToRai.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ui/src/pages/actions/RespondToRai.tsx b/src/services/ui/src/pages/actions/RespondToRai.tsx index 5b3bbbca88..3a2eedae0b 100644 --- a/src/services/ui/src/pages/actions/RespondToRai.tsx +++ b/src/services/ui/src/pages/actions/RespondToRai.tsx @@ -75,7 +75,7 @@ export const RespondToRai = ({ user, authority, }); - setCancelModalOpen(true); + setSuccessModalOpen(true); } catch (err) { console.log(err); setErrorModalOpen(true); From 39513aaf2d45d8d7fe6b2b4b20983a48200be5c7 Mon Sep 17 00:00:00 2001 From: Mike Dial <48921055+mdial89f@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:40:36 -0500 Subject: [PATCH 16/66] fix(timestamps): Align our dates and times to how seatool operates (#304) * hold * all of these are discrete days midnight relative to utc... weird * Correct submission timestamps to be in line with seatool * change helper return type * ts on backend * use primitive * Update dashboard * change import * fix compile issue * Remove line that was meant to be deleted * Update display of rai dates, although this is going to be removed shortly * Update seatoolFriendlyTimestamp to be more useful * Account for export functionality * Remove unused --- src/packages/shared-utils/index.ts | 1 + .../shared-utils/seatool-date-helper.ts | 21 +++++++++++++++ src/services/api/handlers/packageActions.ts | 27 +++++++++++++------ src/services/api/handlers/submit.ts | 18 +++++-------- src/services/api/layers/tsconfig.json | 3 ++- src/services/ui/package.json | 1 + src/services/ui/src/api/submissionService.ts | 10 +++---- .../Opensearch/main/Filtering/consts.ts | 6 ++--- .../ui/src/components/RaiList/index.tsx | 6 ++--- .../dashboard/Lists/renderCells/index.tsx | 4 +-- .../src/pages/dashboard/Lists/spas/consts.tsx | 4 +-- .../ui/src/pages/detail/setup/spa.tsx | 14 +++++----- yarn.lock | 12 +++++++++ 13 files changed, 82 insertions(+), 45 deletions(-) create mode 100644 src/packages/shared-utils/seatool-date-helper.ts diff --git a/src/packages/shared-utils/index.ts b/src/packages/shared-utils/index.ts index fcc7b63a22..04d4ee65b9 100644 --- a/src/packages/shared-utils/index.ts +++ b/src/packages/shared-utils/index.ts @@ -5,3 +5,4 @@ export * from "./rai-helper"; export * from "./regex"; export * from "./package-actions/getAvailableActions"; export * from "./packageCheck"; +export * from "./seatool-date-helper" \ No newline at end of file diff --git a/src/packages/shared-utils/seatool-date-helper.ts b/src/packages/shared-utils/seatool-date-helper.ts new file mode 100644 index 0000000000..dd7ea2c255 --- /dev/null +++ b/src/packages/shared-utils/seatool-date-helper.ts @@ -0,0 +1,21 @@ +import moment from "moment-timezone"; + +// This manually accounts for the offset between the client's timezone and UTC. +export const offsetForUtc = (date: Date): Date => { + return new Date(date.getTime() - (date.getTimezoneOffset() * 60000)); +} + +// This creates a Date for midnight today, then accounts for timezone offset. +export const seaToolFriendlyTimestamp = (date?: Date): number => { + // If you don't pass a date, we assume you want today the timestamp for today, midnight, utc. + if(!date) { + date = new Date(); + date.setHours(0,0,0,0); + } + return offsetForUtc(date).getTime(); +}; + +// This takes an epoch string and converts it to a standard format for display +export const formatSeatoolDate = (date: string): string => { + return moment(date).tz("UTC").format("MM/DD/yyyy") +} \ No newline at end of file diff --git a/src/services/api/handlers/packageActions.ts b/src/services/api/handlers/packageActions.ts index 935f7aaa3a..c115bbe69d 100644 --- a/src/services/api/handlers/packageActions.ts +++ b/src/services/api/handlers/packageActions.ts @@ -29,6 +29,7 @@ import { produceMessage } from "../libs/kafka"; import { response } from "../libs/handler"; import { SEATOOL_STATUS } from "shared-types/statusHelper"; import { getLatestRai } from "shared-utils"; +import { seaToolFriendlyTimestamp } from "shared-utils"; const TOPIC_NAME = process.env.topicName as string; @@ -36,13 +37,14 @@ export async function issueRai(body: RaiIssue) { console.log("CMS issuing a new RAI"); const pool = await sql.connect(config); const transaction = new sql.Transaction(pool); + const today = seaToolFriendlyTimestamp(); try { await transaction.begin(); // Issue RAI const query1 = ` Insert into SEA.dbo.RAI (ID_Number, RAI_Requested_Date) values ('${body.id}' - ,dateadd(s, convert(int, left(${body.requestedDate}, 10)), cast('19700101' as datetime))) + ,dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime))) `; const result1 = await transaction.request().query(query1); console.log(result1); @@ -57,7 +59,7 @@ export async function issueRai(body: RaiIssue) { console.log(result2); // write to kafka here - const result = raiIssueSchema.safeParse(body); + const result = raiIssueSchema.safeParse({ ...body, requestedDate: today }); if (result.success === false) { console.log( "RAI Validation Error. The following record failed to parse: ", @@ -69,7 +71,10 @@ export async function issueRai(body: RaiIssue) { await produceMessage( TOPIC_NAME, body.id, - JSON.stringify({ ...result.data, actionType: Action.ISSUE_RAI }) + JSON.stringify({ + ...result.data, + actionType: Action.ISSUE_RAI, + }) ); } @@ -88,9 +93,11 @@ export async function issueRai(body: RaiIssue) { export async function withdrawRai(body: RaiWithdraw, rais: any) { const activeKey = getLatestRai(rais)?.key; + const today = seaToolFriendlyTimestamp(); const result = raiWithdrawSchema.safeParse({ ...body, requestedDate: activeKey, + withdrawnDate: today, }); console.log("Withdraw body is", body); @@ -100,13 +107,12 @@ export async function withdrawRai(body: RaiWithdraw, rais: any) { console.log("LATEST RAI KEY: " + activeKey); const pool = await sql.connect(config); const transaction = new sql.Transaction(pool); - try { await transaction.begin(); // Issue RAI const query1 = ` UPDATE SEA.dbo.RAI - SET RAI_WITHDRAWN_DATE = DATEADD(s, CONVERT(int, LEFT('${result.data.withdrawnDate}', 10)), CAST('19700101' AS DATETIME)) + SET RAI_WITHDRAWN_DATE = DATEADD(s, CONVERT(int, LEFT('${today}', 10)), CAST('19700101' AS DATETIME)) WHERE ID_Number = '${result.data.id}' AND RAI_REQUESTED_DATE = DATEADD(s, CONVERT(int, LEFT('${activeKey}', 10)), CAST('19700101' AS DATETIME)) `; const result1 = await transaction.request().query(query1); @@ -125,7 +131,10 @@ export async function withdrawRai(body: RaiWithdraw, rais: any) { await produceMessage( TOPIC_NAME, result.data.id, - JSON.stringify({ ...result.data, actionType: Action.WITHDRAW_RAI }) + JSON.stringify({ + ...result.data, + actionType: Action.WITHDRAW_RAI, + }) ); // Commit transaction @@ -158,12 +167,13 @@ export async function respondToRai(body: RaiResponse, rais: any) { const pool = await sql.connect(config); const transaction = new sql.Transaction(pool); console.log(body); + const today = seaToolFriendlyTimestamp(); try { await transaction.begin(); // Issue RAI const query1 = ` UPDATE SEA.dbo.RAI - SET RAI_RECEIVED_DATE = DATEADD(s, CONVERT(int, LEFT('${body.responseDate}', 10)), CAST('19700101' AS DATETIME)) + SET RAI_RECEIVED_DATE = DATEADD(s, CONVERT(int, LEFT('${today}', 10)), CAST('19700101' AS DATETIME)) WHERE ID_Number = '${body.id}' AND RAI_REQUESTED_DATE = DATEADD(s, CONVERT(int, LEFT('${activeKey}', 10)), CAST('19700101' AS DATETIME)) `; const result1 = await transaction.request().query(query1); @@ -181,6 +191,7 @@ export async function respondToRai(body: RaiResponse, rais: any) { // // write to kafka here const result = raiResponseSchema.safeParse({ ...body, + responseDate: today, requestedDate: activeKey, }); if (result.success === false) { @@ -197,6 +208,7 @@ export async function respondToRai(body: RaiResponse, rais: any) { body.id, JSON.stringify({ ...result.data, + responseDate: today, actionType: Action.RESPOND_TO_RAI, }) ); @@ -213,7 +225,6 @@ export async function respondToRai(body: RaiResponse, rais: any) { // Close pool await pool.close(); } - } export async function withdrawPackage(body: WithdrawPackage) { diff --git a/src/services/api/handlers/submit.ts b/src/services/api/handlers/submit.ts index e26281a38a..100c133ae2 100644 --- a/src/services/api/handlers/submit.ts +++ b/src/services/api/handlers/submit.ts @@ -17,6 +17,7 @@ const config = { import { Kafka, Message } from "kafkajs"; import { PlanType, onemacSchema, transformOnemac } from "shared-types"; +import { seaToolFriendlyTimestamp } from "shared-utils"; const kafka = new Kafka({ clientId: "submit", @@ -60,23 +61,18 @@ export const submit = async (event: APIGatewayEvent) => { }); } + const today = seaToolFriendlyTimestamp(); const pool = await sql.connect(config); console.log(body); const query = ` Insert into SEA.dbo.State_Plan (ID_Number, State_Code, Region_ID, Plan_Type, Submission_Date, Status_Date, Proposed_Date, SPW_Status_ID, Budget_Neutrality_Established_Flag) values ('${body.id}' ,'${body.state}' - ,(Select Region_ID from SEA.dbo.States where State_Code = '${ - body.state - }') - ,(Select Plan_Type_ID from SEA.dbo.Plan_Types where Plan_Type_Name = '${ - body.authority - }') - ,dateadd(s, convert(int, left(${Date.now()}, 10)), cast('19700101' as datetime)) - ,dateadd(s, convert(int, left(${Date.now()}, 10)), cast('19700101' as datetime)) - ,dateadd(s, convert(int, left(${ - body.proposedEffectiveDate - }, 10)), cast('19700101' as datetime)) + ,(Select Region_ID from SEA.dbo.States where State_Code = '${body.state}') + ,(Select Plan_Type_ID from SEA.dbo.Plan_Types where Plan_Type_Name = '${body.authority}') + ,dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime)) + ,dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime)) + ,dateadd(s, convert(int, left(${body.proposedEffectiveDate}, 10)), cast('19700101' as datetime)) ,(Select SPW_Status_ID from SEA.dbo.SPW_Status where SPW_Status_DESC = 'Pending') ,0) `; diff --git a/src/services/api/layers/tsconfig.json b/src/services/api/layers/tsconfig.json index bbda01c81f..38a9974ea0 100644 --- a/src/services/api/layers/tsconfig.json +++ b/src/services/api/layers/tsconfig.json @@ -3,7 +3,8 @@ "target": "ES2016", "moduleResolution": "node", "module": "commonjs", - "skipLibCheck": true + "skipLibCheck": true, + "esModuleInterop": true }, "include": ["./**/*.ts"], "exclude": ["node_modules"] diff --git a/src/services/ui/package.json b/src/services/ui/package.json index b581ebc2a7..d64cf2c68f 100644 --- a/src/services/ui/package.json +++ b/src/services/ui/package.json @@ -55,6 +55,7 @@ "jszip": "^3.10.1", "lucide-react": "^0.291.0", "lz-string": "^1.5.0", + "moment-timezone": "^0.5.44", "react": "^18.2.0", "react-day-picker": "^8.8.1", "react-dom": "^18.2.0", diff --git a/src/services/ui/src/api/submissionService.ts b/src/services/ui/src/api/submissionService.ts index 331896e7b8..24ddf33654 100644 --- a/src/services/ui/src/api/submissionService.ts +++ b/src/services/ui/src/api/submissionService.ts @@ -9,6 +9,7 @@ import { import { buildActionUrl, SubmissionServiceEndpoint } from "@/lib"; import { OneMacUser } from "@/api/useGetUser"; import { useMutation, UseMutationOptions } from "@tanstack/react-query"; +import { seaToolFriendlyTimestamp } from "shared-utils"; type SubmissionServiceParameters = { data: T; @@ -65,8 +66,6 @@ const buildSubmissionPayload = >( submitterName: `${user?.user?.given_name} ${user?.user?.family_name}` ?? "N/A", }; - const seaToolFriendlyTimestamp = - Math.floor(new Date().getTime() / 1000) * 1000; // Truncating to match seatool switch (endpoint) { case "/submit": @@ -75,7 +74,9 @@ const buildSubmissionPayload = >( origin: "micro", ...data, ...userDetails, - proposedEffectiveDate: (data.proposedEffectiveDate as Date).getTime(), + proposedEffectiveDate: seaToolFriendlyTimestamp( + data.proposedEffectiveDate as Date + ), attachments: attachments ? buildAttachmentObject(attachments) : null, state: (data.id as string).split("-")[0], }; @@ -86,7 +87,6 @@ const buildSubmissionPayload = >( ...data, ...userDetails, attachments: attachments ? buildAttachmentObject(attachments) : null, - withdrawnDate: seaToolFriendlyTimestamp, }; case buildActionUrl(Action.ISSUE_RAI): return { @@ -94,7 +94,6 @@ const buildSubmissionPayload = >( origin: "micro", ...data, ...userDetails, - requestedDate: seaToolFriendlyTimestamp, attachments: attachments ? buildAttachmentObject(attachments) : null, }; case buildActionUrl(Action.RESPOND_TO_RAI): @@ -103,7 +102,6 @@ const buildSubmissionPayload = >( origin: "micro", ...data, ...userDetails, - responseDate: seaToolFriendlyTimestamp, attachments: attachments ? buildAttachmentObject(attachments) : null, }; case buildActionUrl(Action.WITHDRAW_PACKAGE): diff --git a/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts b/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts index 25a53009d6..d41e45a220 100644 --- a/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts +++ b/src/services/ui/src/components/Opensearch/main/Filtering/consts.ts @@ -3,7 +3,7 @@ import { OsFilterComponentType, OsTab } from "../types"; import { UserRoles } from "shared-types"; import { BLANK_VALUE } from "@/consts"; import { LABELS } from "@/lib/labels"; -import { format } from "date-fns"; +import { formatSeatoolDate } from "shared-utils"; type DrawerFilterableGroup = { label: string; @@ -229,14 +229,14 @@ export const EXPORT_GROUPS = ( name: "Initial Submission", transform: (data) => data?.submissionDate - ? format(new Date(data.submissionDate), "MM/dd/yyyy") + ? formatSeatoolDate(data.submissionDate) : BLANK_VALUE, }, { name: "Formal RAI Response", transform: (data) => { return data.raiReceivedDate && !data.raiWithdrawnDate - ? format(new Date(data.raiReceivedDate), "MM/dd/yyyy") + ? formatSeatoolDate(data.raiReceivedDate) : BLANK_VALUE; }, }, diff --git a/src/services/ui/src/components/RaiList/index.tsx b/src/services/ui/src/components/RaiList/index.tsx index 7144a672da..d8c9786fa6 100644 --- a/src/services/ui/src/components/RaiList/index.tsx +++ b/src/services/ui/src/components/RaiList/index.tsx @@ -9,6 +9,7 @@ import { Attachmentslist, } from "@/components"; import { BLANK_VALUE } from "@/consts"; +import { formatSeatoolDate } from "shared-utils"; export const RaiList = (data: opensearch.main.Document) => { if (!data.rais) return null; @@ -217,10 +218,7 @@ function getLatestStatus(rai: any) { // Check if latestDate is a valid number before formatting if (!isNaN(latestDate) && isFinite(latestDate)) { - return `${retString} ${format( - new Date(latestDate), - "EEE, MMM d yyyy, h:mm a" - )}`; + return `${retString} ${formatSeatoolDate(new Date(latestDate).toString())}`; } else { return "Invalid date"; } diff --git a/src/services/ui/src/pages/dashboard/Lists/renderCells/index.tsx b/src/services/ui/src/pages/dashboard/Lists/renderCells/index.tsx index 68eddf381d..1ec6229fa4 100644 --- a/src/services/ui/src/pages/dashboard/Lists/renderCells/index.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/renderCells/index.tsx @@ -5,12 +5,12 @@ import { getAvailableActions } from "shared-utils"; import { Link } from "react-router-dom"; import { cn } from "@/lib"; import { mapActionLabel } from "@/utils"; -import { format } from "date-fns"; +import { formatSeatoolDate } from "shared-utils"; export const renderCellDate = (key: keyof opensearch.main.Document) => function Cell(data: opensearch.main.Document) { if (!data[key]) return null; - return format(new Date(data[key] as string), "MM/dd/yyyy"); + return formatSeatoolDate(data[key] as string); }; export const renderCellIdLink = (pathResolver: (id: string) => string) => diff --git a/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx b/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx index 735e612ebf..4a78df95c2 100644 --- a/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx +++ b/src/services/ui/src/pages/dashboard/Lists/spas/consts.tsx @@ -1,4 +1,3 @@ -import { format } from "date-fns"; import { removeUnderscoresAndCapitalize } from "@/utils"; import { OsTableColumn } from "@/components/Opensearch/main"; import { CMS_READ_ONLY_ROLES, UserRoles } from "shared-types"; @@ -9,6 +8,7 @@ import { renderCellIdLink, } from "../renderCells"; import { BLANK_VALUE } from "@/consts"; +import { formatSeatoolDate } from "shared-utils"; export const useSpaTableColumns = (): OsTableColumn[] => { const { data: props } = useGetUser(); @@ -70,7 +70,7 @@ export const useSpaTableColumns = (): OsTableColumn[] => { label: "Formal RAI Response", cell: (data) => { if (!data.raiReceivedDate || data.raiWithdrawnDate) return null; - return format(new Date(data.raiReceivedDate), "MM/dd/yyyy"); + return formatSeatoolDate(data.raiReceivedDate); }, }, { diff --git a/src/services/ui/src/pages/detail/setup/spa.tsx b/src/services/ui/src/pages/detail/setup/spa.tsx index f84cfbdca3..31a823643c 100644 --- a/src/services/ui/src/pages/detail/setup/spa.tsx +++ b/src/services/ui/src/pages/detail/setup/spa.tsx @@ -2,11 +2,11 @@ import { removeUnderscoresAndCapitalize } from "@/utils"; import { isCmsUser } from "shared-utils"; import { LABELS } from "@/lib"; import { BLANK_VALUE } from "@/consts"; -import { format } from "date-fns"; import { opensearch } from "shared-types"; import { ReactNode } from "react"; import { OneMacUser } from "@/api/useGetUser"; import { ReviewTeamList } from "@/components/PackageDetails/ReviewTeamList"; +import { formatSeatoolDate } from "shared-utils"; export type DetailSectionItem = { label: string; @@ -36,35 +36,33 @@ export const spaDetails = ( { label: "Initial Submission Date", value: data.submissionDate - ? format(new Date(data.submissionDate), "MM/dd/yyyy h:mm:ss a") + ? formatSeatoolDate(data.submissionDate) : BLANK_VALUE, canView: () => true, }, { label: "Proposed Effective Date", value: data.proposedDate - ? format(new Date(data.proposedDate), "MM/dd/yyyy") + ? formatSeatoolDate(data.proposedDate) : BLANK_VALUE, canView: () => true, }, { label: "Approved Effective Date", value: data.approvedEffectiveDate - ? format(new Date(data.approvedEffectiveDate), "MM/dd/yyyy h:mm:ss a") + ? formatSeatoolDate(data.approvedEffectiveDate) : BLANK_VALUE, canView: () => true, }, { label: "Status Date", - value: data.statusDate - ? format(new Date(data.statusDate), "MM/dd/yyyy h:mm:ss a") - : BLANK_VALUE, + value: data.statusDate ? formatSeatoolDate(data.statusDate) : BLANK_VALUE, canView: (u) => (!u || !u.user ? false : isCmsUser(u.user)), }, { label: "Final Disposition Date", value: data.finalDispositionDate - ? format(new Date(data.finalDispositionDate), "MM/dd/yyyy h:mm:ss a") + ? formatSeatoolDate(data.finalDispositionDate) : BLANK_VALUE, canView: () => true, }, diff --git a/yarn.lock b/yarn.lock index b80d897785..55673ea084 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11641,6 +11641,18 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== +moment-timezone@^0.5.44: + version "0.5.44" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.44.tgz#a64a4e47b68a43deeab5ae4eb4f82da77cdf595f" + integrity sha512-nv3YpzI/8lkQn0U6RkLd+f0W/zy/JnoR5/EyPz/dNkPTBjA2jNLCVxaiQ8QpeLymhSZvX0wCL5s27NQWdOPwAw== + dependencies: + moment "^2.29.4" + +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + mrmime@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" From 5c3dd4361242dde67bbb337e4a34451609079029 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Thu, 11 Jan 2024 11:29:50 -0700 Subject: [PATCH 17/66] feat(OY2-26082): package actions multi-file download --- .../package-activity/PackageActivity.tsx | 66 +++++++++---------- .../src/pages/detail/package-activity/hook.ts | 63 ++++++++++++++++++ .../pages/detail/package-activity/index.tsx | 25 +++++-- 3 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 src/services/ui/src/pages/detail/package-activity/hook.ts diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx index 23b0d052d5..1b9541dc0e 100644 --- a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx +++ b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx @@ -4,16 +4,19 @@ import { AccordionTrigger, } from "@/components"; import { opensearch } from "shared-types"; -import { getAttachmentUrl } from "@/api"; import { FC, useMemo } from "react"; import { Button } from "@/components/Inputs"; import * as Table from "@/components/Table"; import { BLANK_VALUE } from "@/consts"; import { format } from "date-fns"; +import { useAttachmentService } from "./hook"; +import { Loader2 } from "lucide-react"; export const PA_InitialSubmission: FC = ( props ) => { + const hook = useAttachmentService(props); + return (
<> @@ -36,14 +39,12 @@ export const PA_InitialSubmission: FC = ( className="ml-[-15px]" variant="link" onClick={() => { - getAttachmentUrl( - props.packageId, - ATC.bucket, - ATC.key, - ATC.filename - ).then(window.open); + hook.onUrl(ATC).then(window.open); }} > + {hook.loading && ( + + )} {ATC.filename} @@ -52,12 +53,17 @@ export const PA_InitialSubmission: FC = ( })} + @@ -72,26 +78,24 @@ export const PA_InitialSubmission: FC = ( export const PA_ResponseSubmitted: FC = ( props ) => { + const hook = useAttachmentService(props); + return (

Attached File

{!props.attachments?.length &&

No information submitted

} {props.attachments?.map((ATC) => ( - + ))}
@@ -105,26 +109,24 @@ export const PA_ResponseSubmitted: FC = ( export const PA_ResponseWithdrawn: FC = ( props ) => { + const hook = useAttachmentService(props); + return (

Attached File

{!props.attachments?.length &&

No information submitted

} {props.attachments?.map((ATC) => ( - + ))}
@@ -136,26 +138,24 @@ export const PA_ResponseWithdrawn: FC = ( }; export const PA_RaiIssued: FC = (props) => { + const hook = useAttachmentService(props); + return (

Attached File

{!props.attachments?.length &&

No information submitted

} {props.attachments?.map((ATC) => ( - + ))}
diff --git a/src/services/ui/src/pages/detail/package-activity/hook.ts b/src/services/ui/src/pages/detail/package-activity/hook.ts new file mode 100644 index 0000000000..dcf31e42e1 --- /dev/null +++ b/src/services/ui/src/pages/detail/package-activity/hook.ts @@ -0,0 +1,63 @@ +import { getAttachmentUrl } from "@/api"; +import JSZip from "jszip"; +import { saveAs } from "file-saver"; +import { useState } from "react"; +import { opensearch } from "shared-types"; + +type Attachments = NonNullable; + +export const useAttachmentService = ( + props: Pick +) => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + const onUrl = async (ATT: Attachments[number]) => { + setLoading(true); + try { + return await getAttachmentUrl( + props.packageId, + ATT.bucket, + ATT.key, + ATT.filename + ); + } catch (e) { + if (e instanceof Error) { + setError(e.message); + } else { + setError("You failed"); + } + } finally { + setLoading(false); + } + }; + + const onZip = (attachments: Attachments) => { + setLoading(true); + const zip = new JSZip(); + + const remoteZips = attachments.map(async (ATT) => { + const url = await onUrl(ATT); + if (!url) return; + const response = await fetch(url); + const data = await response.blob(); + zip.file(ATT.filename, data); + return data; + }); + + Promise.all(remoteZips) + .then(() => { + zip.generateAsync({ type: "blob" }).then((content) => { + saveAs(content, `package-actions-${new Date().toISOString()}.zip`); + }); + }) + .catch(() => { + console.error("Failed to fetch files"); + }) + .finally(() => { + setLoading(false); + }); + }; + + return { loading, error, onUrl, onZip }; +}; diff --git a/src/services/ui/src/pages/detail/package-activity/index.tsx b/src/services/ui/src/pages/detail/package-activity/index.tsx index 1d96d4c2c6..5e1d38ea53 100644 --- a/src/services/ui/src/pages/detail/package-activity/index.tsx +++ b/src/services/ui/src/pages/detail/package-activity/index.tsx @@ -1,8 +1,10 @@ import { Accordion, DetailsSection } from "@/components"; import { opensearch } from "shared-types"; -import { FC } from "react"; +import { FC, useMemo } from "react"; import { PackageActivity } from "./PackageActivity"; import { Button } from "@/components/Inputs"; +import { useAttachmentService } from "./hook"; +import { Loader2 } from "lucide-react"; export const ACTIONS_PA = [ "new-submission", @@ -13,12 +15,26 @@ export const ACTIONS_PA = [ ]; export const PackageActivities: FC = (props) => { - const data = props.changelog?.filter((CL) => - ACTIONS_PA.includes(CL._source.actionType) + const hook = useAttachmentService({ packageId: props.id }); + const data = useMemo( + () => + props.changelog?.filter((CL) => + ACTIONS_PA.includes(CL._source.actionType) + ), + [props.changelog] ); // TODO: OY2-26538 - const onDownloadAll = () => null; + const onDownloadAll = () => { + const attachments = props.changelog?.reduce((ACC, ATT) => { + if (!ATT._source.attachments) return ACC; + return ACC.concat(ATT._source.attachments); + }, [] as any); + + if (!attachments.length) return; + + hook.onZip(attachments); + }; return ( = (props) => {
{`Package Activity (${data?.length})`}
From 43199b8fcb2651bdaebf3a49e5a984f4e2c36950 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Thu, 11 Jan 2024 18:49:01 -0700 Subject: [PATCH 18/66] feat(OY2-26082): package actions multi-file download --- .../src/pages/detail/package-activity/PackageActivity.tsx | 6 ------ src/services/ui/src/pages/detail/package-activity/hook.ts | 4 ++-- src/services/ui/src/pages/detail/package-activity/index.tsx | 1 - 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx index 1b9541dc0e..7a09ddd4a4 100644 --- a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx +++ b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx @@ -42,9 +42,6 @@ export const PA_InitialSubmission: FC = ( hook.onUrl(ATC).then(window.open); }} > - {hook.loading && ( - - )} {ATC.filename} @@ -93,7 +90,6 @@ export const PA_ResponseSubmitted: FC = ( hook.onUrl(ATC).then(window.open); }} > - {hook.loading && } {ATC.filename} ))} @@ -124,7 +120,6 @@ export const PA_ResponseWithdrawn: FC = ( hook.onUrl(ATC).then(window.open); }} > - {hook.loading && } {ATC.filename} ))} @@ -153,7 +148,6 @@ export const PA_RaiIssued: FC = (props) => { hook.onUrl(ATC).then(window.open); }} > - {hook.loading && } {ATC.filename} ))} diff --git a/src/services/ui/src/pages/detail/package-activity/hook.ts b/src/services/ui/src/pages/detail/package-activity/hook.ts index dcf31e42e1..f723804361 100644 --- a/src/services/ui/src/pages/detail/package-activity/hook.ts +++ b/src/services/ui/src/pages/detail/package-activity/hook.ts @@ -36,12 +36,12 @@ export const useAttachmentService = ( setLoading(true); const zip = new JSZip(); - const remoteZips = attachments.map(async (ATT) => { + const remoteZips = attachments.map(async (ATT, index) => { const url = await onUrl(ATT); if (!url) return; const response = await fetch(url); const data = await response.blob(); - zip.file(ATT.filename, data); + zip.file(`${ATT.filename}_${index + 1}`, data); return data; }); diff --git a/src/services/ui/src/pages/detail/package-activity/index.tsx b/src/services/ui/src/pages/detail/package-activity/index.tsx index 5e1d38ea53..b0fdab62db 100644 --- a/src/services/ui/src/pages/detail/package-activity/index.tsx +++ b/src/services/ui/src/pages/detail/package-activity/index.tsx @@ -24,7 +24,6 @@ export const PackageActivities: FC = (props) => { [props.changelog] ); - // TODO: OY2-26538 const onDownloadAll = () => { const attachments = props.changelog?.reduce((ACC, ATT) => { if (!ATT._source.attachments) return ACC; From 18d566bcad9390edb2e930f37a72e35b08fa75d2 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Thu, 11 Jan 2024 18:49:09 -0700 Subject: [PATCH 19/66] feat(OY2-26082): package actions multi-file download --- src/services/ui/src/pages/detail/package-activity/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/ui/src/pages/detail/package-activity/index.tsx b/src/services/ui/src/pages/detail/package-activity/index.tsx index b0fdab62db..26b7884846 100644 --- a/src/services/ui/src/pages/detail/package-activity/index.tsx +++ b/src/services/ui/src/pages/detail/package-activity/index.tsx @@ -25,14 +25,14 @@ export const PackageActivities: FC = (props) => { ); const onDownloadAll = () => { - const attachments = props.changelog?.reduce((ACC, ATT) => { + const attachmentsAggregate = props.changelog?.reduce((ACC, ATT) => { if (!ATT._source.attachments) return ACC; return ACC.concat(ATT._source.attachments); }, [] as any); - if (!attachments.length) return; + if (!attachmentsAggregate.length) return; - hook.onZip(attachments); + hook.onZip(attachmentsAggregate); }; return ( From 2cccf432d5b59d26e56389b2c935daeb9326a55e Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Thu, 11 Jan 2024 19:15:47 -0700 Subject: [PATCH 20/66] feat(OY2-26082): package actions multi-file download --- .../ui/src/pages/detail/package-activity/hook.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/services/ui/src/pages/detail/package-activity/hook.ts b/src/services/ui/src/pages/detail/package-activity/hook.ts index f723804361..17f244bcf1 100644 --- a/src/services/ui/src/pages/detail/package-activity/hook.ts +++ b/src/services/ui/src/pages/detail/package-activity/hook.ts @@ -22,11 +22,8 @@ export const useAttachmentService = ( ATT.filename ); } catch (e) { - if (e instanceof Error) { - setError(e.message); - } else { - setError("You failed"); - } + const err = e instanceof Error ? e.message : "Failed download"; + setError(err); } finally { setLoading(false); } @@ -51,8 +48,9 @@ export const useAttachmentService = ( saveAs(content, `package-actions-${new Date().toISOString()}.zip`); }); }) - .catch(() => { - console.error("Failed to fetch files"); + .catch((e) => { + const err = e instanceof Error ? e.message : "Failed download"; + setError(err); }) .finally(() => { setLoading(false); From f60d490821ba6b5d76782f59dc613c2ad45e0b3b Mon Sep 17 00:00:00 2001 From: Mike Dial <48921055+mdial89f@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:59:59 -0500 Subject: [PATCH 21/66] fix(status date): Set Status_Date when updating a seatool record's status (#319) * fix(status date): Set Status_Date anytime we change status in seatool * Fix sql syntax --- src/services/api/handlers/packageActions.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/services/api/handlers/packageActions.ts b/src/services/api/handlers/packageActions.ts index c115bbe69d..6eaa708f7c 100644 --- a/src/services/api/handlers/packageActions.ts +++ b/src/services/api/handlers/packageActions.ts @@ -52,8 +52,10 @@ export async function issueRai(body: RaiIssue) { // Update Status const query2 = ` UPDATE SEA.dbo.State_Plan - SET SPW_Status_ID = (Select SPW_Status_ID from SEA.dbo.SPW_Status where SPW_Status_DESC = '${SEATOOL_STATUS.PENDING_RAI}') - WHERE ID_Number = '${body.id}' + SET + SPW_Status_ID = (SELECT SPW_Status_ID FROM SEA.dbo.SPW_Status WHERE SPW_Status_DESC = '${SEATOOL_STATUS.PENDING_RAI}'), + Status_Date = dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime)) + WHERE ID_Number = '${body.id}' `; const result2 = await transaction.request().query(query2); console.log(result2); @@ -121,7 +123,9 @@ export async function withdrawRai(body: RaiWithdraw, rais: any) { // Update Status const query2 = ` UPDATE SEA.dbo.State_Plan - SET SPW_Status_ID = (Select SPW_Status_ID from SEA.dbo.SPW_Status where SPW_Status_DESC = '${SEATOOL_STATUS.PENDING}') + SET + SPW_Status_ID = (SELECT SPW_Status_ID FROM SEA.dbo.SPW_Status WHERE SPW_Status_DESC = '${SEATOOL_STATUS.PENDING}'), + Status_Date = dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime)) WHERE ID_Number = '${result.data.id}' `; const result2 = await transaction.request().query(query2); @@ -182,7 +186,9 @@ export async function respondToRai(body: RaiResponse, rais: any) { // Update Status const query2 = ` UPDATE SEA.dbo.State_Plan - SET SPW_Status_ID = (Select SPW_Status_ID from SEA.dbo.SPW_Status where SPW_Status_DESC = '${SEATOOL_STATUS.PENDING}') + SET + SPW_Status_ID = (SELECT SPW_Status_ID FROM SEA.dbo.SPW_Status WHERE SPW_Status_DESC = '${SEATOOL_STATUS.PENDING}'), + Status_Date = dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime)) WHERE ID_Number = '${body.id}' `; const result2 = await transaction.request().query(query2); @@ -244,11 +250,14 @@ export async function withdrawPackage(body: WithdrawPackage) { }); } // Begin query (data is confirmed) + const today = seaToolFriendlyTimestamp(); const pool = await sql.connect(config); const transaction = new sql.Transaction(pool); const query = ` UPDATE SEA.dbo.State_Plan - SET SPW_Status_ID = (Select SPW_Status_ID from SEA.dbo.SPW_Status where SPW_Status_DESC = '${SEATOOL_STATUS.WITHDRAWN}') + SET + SPW_Status_ID = (SELECT SPW_Status_ID FROM SEA.dbo.SPW_Status WHERE SPW_Status_DESC = '${SEATOOL_STATUS.WITHDRAWN}'), + Status_Date = dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime)) WHERE ID_Number = '${body.id}' `; From 10c84bfb8e4add85a5989719092a382514c685d2 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Fri, 12 Jan 2024 09:00:57 -0700 Subject: [PATCH 22/66] feat(OY2-26082): package actions multi-file download --- src/services/ui/src/pages/detail/package-activity/hook.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/ui/src/pages/detail/package-activity/hook.ts b/src/services/ui/src/pages/detail/package-activity/hook.ts index 17f244bcf1..ff138298c4 100644 --- a/src/services/ui/src/pages/detail/package-activity/hook.ts +++ b/src/services/ui/src/pages/detail/package-activity/hook.ts @@ -38,7 +38,8 @@ export const useAttachmentService = ( if (!url) return; const response = await fetch(url); const data = await response.blob(); - zip.file(`${ATT.filename}_${index + 1}`, data); + const [prefix, extension] = ATT.filename.split("."); + zip.file(`${prefix}_${index + 1}.${extension}`, data); return data; }); From 92bc1252447cfee24b8f8ee8dd0c885ed1a8e156 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Fri, 12 Jan 2024 09:30:46 -0700 Subject: [PATCH 23/66] feat(OY2-26082): package actions multi-file download --- .../package-activity/PackageActivity.tsx | 9 ++-- .../src/pages/detail/package-activity/hook.ts | 41 +++++-------------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx index 7a09ddd4a4..fb74757ed2 100644 --- a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx +++ b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx @@ -85,7 +85,8 @@ export const PA_ResponseSubmitted: FC = ( {props.attachments?.map((ATC) => (
} > - {!data?.length &&

-- no logs --

} - - {data?.map((CL) => ( + {!hook.data?.length &&

-- no logs --

} + + {hook.data?.map((CL) => ( ))} From 820eccef19dade2db8a7a2da02e12799caa85d69 Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Fri, 12 Jan 2024 10:59:20 -0700 Subject: [PATCH 25/66] feat(OY2-26082): package actions multi-file download --- .../ui/src/pages/detail/package-activity/hook.ts | 2 +- .../ui/src/pages/detail/package-activity/index.tsx | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/services/ui/src/pages/detail/package-activity/hook.ts b/src/services/ui/src/pages/detail/package-activity/hook.ts index 83fde82781..5e59eb3d49 100644 --- a/src/services/ui/src/pages/detail/package-activity/hook.ts +++ b/src/services/ui/src/pages/detail/package-activity/hook.ts @@ -74,7 +74,7 @@ export const usePackageActivities = (props: opensearch.main.Document) => { return { data, - accordianDefault: [data?.[0]._source.id as string], + accordianDefault: [data?.[0]?._source?.id as string], onDownloadAll, ...service, }; diff --git a/src/services/ui/src/pages/detail/package-activity/index.tsx b/src/services/ui/src/pages/detail/package-activity/index.tsx index 065c040558..7b7011ad96 100644 --- a/src/services/ui/src/pages/detail/package-activity/index.tsx +++ b/src/services/ui/src/pages/detail/package-activity/index.tsx @@ -11,14 +11,18 @@ export const PackageActivities: FC = (props) => { return ( {`Package Activity (${hook.data?.length})`} - + {!!hook.data?.length && ( + + )}
} > From 70f16e536336f5ef96bca6994ce69795e00dbcfc Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Mon, 15 Jan 2024 19:59:54 -0700 Subject: [PATCH 26/66] feat(OY2-26082): package actions multi-file download --- .../ui/src/components/Inputs/button.tsx | 14 +- .../detail/admin-changes/AdminChanges.tsx | 71 ------ .../src/pages/detail/admin-changes/index.tsx | 73 +++++- src/services/ui/src/pages/detail/index.tsx | 3 +- .../package-activity/PackageActivity.tsx | 202 ----------------- .../src/pages/detail/package-activity/hook.ts | 40 ++-- .../pages/detail/package-activity/index.tsx | 211 +++++++++++++++++- 7 files changed, 303 insertions(+), 311 deletions(-) delete mode 100644 src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx delete mode 100644 src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx diff --git a/src/services/ui/src/components/Inputs/button.tsx b/src/services/ui/src/components/Inputs/button.tsx index e69a796fff..4fe2ca9d72 100644 --- a/src/services/ui/src/components/Inputs/button.tsx +++ b/src/services/ui/src/components/Inputs/button.tsx @@ -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", @@ -38,17 +39,26 @@ export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; + loading?: boolean; } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { + ( + { className, variant, size, loading, children, asChild = false, ...props }, + ref + ) => { const Comp = asChild ? Slot : "button"; return ( + > + <> + {loading && } + {children} + + ); } ); diff --git a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx b/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx deleted file mode 100644 index 0c57118b08..0000000000 --- a/src/services/ui/src/pages/detail/admin-changes/AdminChanges.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components"; -import { opensearch } from "shared-types"; -import { FC, useMemo } from "react"; -import { BLANK_VALUE } from "@/consts"; -import { format } from "date-fns"; - -export const AC_WithdrawEnabled: FC = ( - props -) => { - return ( -

- {props.submitterName} has enabled package action to submit formal RAI - response -

- ); -}; - -export const AC_WithdrawDisabled: FC = ( - props -) => { - return ( -

- {props.submitterName} has disabled package action to submit formal RAI - response -

- ); -}; - -export const AC_Update: FC = () => { - return

Coming Soon

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

- {label as string} - {" - "} - {format(new Date(props.timestamp), "eee, MMM d, yyyy hh:mm:ss a")} -

-
- - - -
- ); -}; diff --git a/src/services/ui/src/pages/detail/admin-changes/index.tsx b/src/services/ui/src/pages/detail/admin-changes/index.tsx index c9433b8f82..db05797160 100644 --- a/src/services/ui/src/pages/detail/admin-changes/index.tsx +++ b/src/services/ui/src/pages/detail/admin-changes/index.tsx @@ -1,13 +1,76 @@ -import { Accordion, DetailsSection } from "@/components"; +import { format } from "date-fns"; +import { FC, useMemo } from "react"; import { opensearch } from "shared-types"; -import { FC } from "react"; -import { AdminChange } from "./AdminChanges"; +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 = ( + props +) => { + return ( +

+ {props.submitterName} has enabled package action to submit formal RAI + response +

+ ); +}; + +export const AC_WithdrawDisabled: FC = ( + props +) => { + return ( +

+ {props.submitterName} has disabled package action to submit formal RAI + response +

+ ); +}; + +export const AC_Update: FC = () => { + return

Coming Soon

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

+ {label as string} + {" - "} + {format(new Date(props.timestamp), "eee, MMM d, yyyy hh:mm:ss a")} +

+
+ + + +
+ ); +}; export const AdminChanges: FC = (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 <>; diff --git a/src/services/ui/src/pages/detail/index.tsx b/src/services/ui/src/pages/detail/index.tsx index affc597d72..2cf8a0f94a 100644 --- a/src/services/ui/src/pages/detail/index.tsx +++ b/src/services/ui/src/pages/detail/index.tsx @@ -1,5 +1,4 @@ import { - AdditionalInfo, Alert, CardWithTopBorder, ConfirmationModal, @@ -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"; diff --git a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx b/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx deleted file mode 100644 index 5ccec78723..0000000000 --- a/src/services/ui/src/pages/detail/package-activity/PackageActivity.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import { - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components"; -import { opensearch } from "shared-types"; -import { FC, useMemo } from "react"; -import { Button } from "@/components/Inputs"; -import * as Table from "@/components/Table"; -import { BLANK_VALUE } from "@/consts"; -import { format } from "date-fns"; -import { useAttachmentService } from "./hook"; -import { Loader2 } from "lucide-react"; - -export const PA_InitialSubmission: FC = ( - props -) => { - const hook = useAttachmentService(props); - - return ( -
- <> - - - - - Document Type - - Attached File - - - - {props.attachments?.map((ATC) => { - return ( - - {ATC.title} - - - - - ); - })} - - - - - -
-

Additional Information

-

{props.additionalInformation || "No information submitted"}

-
-
- ); -}; - -export const PA_ResponseSubmitted: FC = ( - props -) => { - const hook = useAttachmentService(props); - - return ( -
-
-

Attached File

- {!props.attachments?.length &&

No information submitted

} - {props.attachments?.map((ATC) => ( - - ))} -
-
-

Additional Information

-

{props.additionalInformation || "No information submitted"}

-
-
- ); -}; - -export const PA_ResponseWithdrawn: FC = ( - props -) => { - const hook = useAttachmentService(props); - - return ( -
-
-

Attached File

- {!props.attachments?.length &&

No information submitted

} - {props.attachments?.map((ATC) => ( - - ))} -
-
-

Additional Information

-

{props.additionalInformation || "No information submitted"}

-
-
- ); -}; - -export const PA_RaiIssued: FC = (props) => { - const hook = useAttachmentService(props); - - return ( -
-
-

Attached File

- {!props.attachments?.length &&

No information submitted

} - {props.attachments?.map((ATC) => ( - - ))} -
-
-

Additional Information

-

{props.additionalInformation || "No information submitted"}

-
-
- ); -}; - -const usePackageActivity = (doc: opensearch.changelog.Document) => { - return useMemo(() => { - switch (doc.actionType) { - case "new-submission": - return ["Initial package submitted", PA_InitialSubmission]; - case "withdraw-rai": - return ["RAI response withdrawn", PA_ResponseWithdrawn]; - case "withdraw-package": - return ["RAI package withdrawn", PA_ResponseWithdrawn]; - case "issue-rai": - return ["RAI issued", PA_RaiIssued]; - case "respond-to-rai": - return ["RAI response submitted", PA_ResponseSubmitted]; - default: - return [BLANK_VALUE, PA_ResponseSubmitted]; - } - }, [doc.actionType]); -}; - -export const PackageActivity: FC = (props) => { - const [label, Content] = usePackageActivity(props); - - return ( - - -

- {label as string} - {" - "} - {format(new Date(props.timestamp), "eee, MMM d, yyyy hh:mm:ss a")} -

-
- - - -
- ); -}; diff --git a/src/services/ui/src/pages/detail/package-activity/hook.ts b/src/services/ui/src/pages/detail/package-activity/hook.ts index 5e59eb3d49..ec0a7071a7 100644 --- a/src/services/ui/src/pages/detail/package-activity/hook.ts +++ b/src/services/ui/src/pages/detail/package-activity/hook.ts @@ -4,7 +4,6 @@ import { saveAs } from "file-saver"; import { opensearch } from "shared-types"; import { useMutation } from "@tanstack/react-query"; -import { useMemo } from "react"; type Attachments = NonNullable; @@ -22,17 +21,23 @@ export const useAttachmentService = ( const remoteZips = attachments.map(async (ATT, index) => { const url = await mutateAsync(ATT); if (!url) return; - const response = await fetch(url); - const data = await response.blob(); - const [prefix, extension] = ATT.filename.split("."); - zip.file(`${prefix}(${index + 1}).${extension}`, data); + const data = await fetch(url).then((res) => res.blob()); + + // append index for uniqueness (fileone.md -> fileone(1).md) + const filename = (() => { + const pieces = ATT.filename.split("."); + const ext = pieces.pop(); + return `${pieces.join(".")}(${index + 1}).${ext}`; + })(); + + zip.file(filename, data); return data; }); Promise.allSettled(remoteZips) .then(() => { zip.generateAsync({ type: "blob" }).then((content) => { - saveAs(content, `package-actions-${new Date().toISOString()}.zip`); + saveAs(content, `PackageActivity - ${new Date().toDateString()}.zip`); }); }) .catch((e) => { @@ -43,25 +48,20 @@ export const useAttachmentService = ( return { loading: isLoading, error, onUrl: mutateAsync, onZip }; }; -export const ACTIONS_PA = [ - "new-submission", - "withdraw-rai", - "withdraw-package", - "issue-rai", - "respond-to-rai", -]; - export const usePackageActivities = (props: opensearch.main.Document) => { const service = useAttachmentService({ packageId: props.id }); - const data = useMemo( - () => - props.changelog?.filter((CL) => - ACTIONS_PA.includes(CL._source.actionType) - ), - [props.changelog] + const data = props.changelog?.filter((CL) => + [ + "new-submission", + "withdraw-rai", + "withdraw-package", + "issue-rai", + "respond-to-rai", + ].includes(CL._source.actionType) ); const onDownloadAll = () => { + // gathering all attachments across each changelog const attachmentsAggregate = props.changelog?.reduce((ACC, ATT) => { if (!ATT._source.attachments) return ACC; return ACC.concat(ATT._source.attachments); diff --git a/src/services/ui/src/pages/detail/package-activity/index.tsx b/src/services/ui/src/pages/detail/package-activity/index.tsx index 7b7011ad96..3d5b92d164 100644 --- a/src/services/ui/src/pages/detail/package-activity/index.tsx +++ b/src/services/ui/src/pages/detail/package-activity/index.tsx @@ -1,25 +1,218 @@ import { Accordion, DetailsSection } from "@/components"; import { opensearch } from "shared-types"; -import { FC } from "react"; -import { PackageActivity } from "./PackageActivity"; +import { FC, useMemo } from "react"; import { Button } from "@/components/Inputs"; -import { Loader2 } from "lucide-react"; -import { usePackageActivities } from "./hook"; +import { + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components"; +import * as Table from "@/components/Table"; +import { BLANK_VALUE } from "@/consts"; +import { format } from "date-fns"; +import { usePackageActivities, useAttachmentService } from "./hook"; -export const PackageActivities: FC = (props) => { +export const PA_InitialSubmission: FC = ( + props +) => { + const hook = useAttachmentService(props); + + return ( +
+ + + + + Document Type + + Attached File + + + + {props.attachments?.map((ATC) => { + return ( + + {ATC.title} + + + + + ); + })} + + + + + +
+

Additional Information

+

{props.additionalInformation || "No information submitted"}

+
+
+ ); +}; + +export const PA_ResponseSubmitted: FC = ( + props +) => { + const hook = useAttachmentService(props); + + return ( +
+
+

Attached File

+ {!props.attachments?.length &&

No information submitted

} + {props.attachments?.map((ATC) => ( + + ))} +
+
+

Additional Information

+

{props.additionalInformation || "No information submitted"}

+
+
+ ); +}; + +export const PA_ResponseWithdrawn: FC = ( + props +) => { + const hook = useAttachmentService(props); + + return ( +
+
+

Attached File

+ {!props.attachments?.length &&

No information submitted

} + {props.attachments?.map((ATC) => ( + + ))} +
+
+

Additional Information

+

{props.additionalInformation || "No information submitted"}

+
+
+ ); +}; + +export const PA_RaiIssued: FC = (props) => { + const hook = useAttachmentService(props); + + return ( +
+
+

Attached File

+ {!props.attachments?.length &&

No information submitted

} + {props.attachments?.map((ATC) => ( + + ))} +
+
+

Additional Information

+

{props.additionalInformation || "No information submitted"}

+
+
+ ); +}; + +// Control Map +export const PackageActivity: FC = (props) => { + const [LABEL, CONTENT] = useMemo(() => { + switch (props.actionType) { + case "new-submission": + return ["Initial package submitted", PA_InitialSubmission]; + case "withdraw-rai": + return ["RAI response withdrawn", PA_ResponseWithdrawn]; + case "withdraw-package": + return ["RAI package withdrawn", PA_ResponseWithdrawn]; + case "issue-rai": + return ["RAI issued", PA_RaiIssued]; + case "respond-to-rai": + return ["RAI response submitted", PA_ResponseSubmitted]; + default: + return [BLANK_VALUE, PA_ResponseSubmitted]; + } + }, [props.actionType]); + + return ( + + +

+ {LABEL as string} + {" - "} + {format(new Date(props.timestamp), "eee, MMM d, yyyy hh:mm:ss a")} +

+
+ + + +
+ ); +}; + +export const PackageActivities = (props: opensearch.main.Document) => { const hook = usePackageActivities(props); return ( {`Package Activity (${hook.data?.length})`} {!!hook.data?.length && ( - )} From 63e004afb14cf8187b4947f10d6610061cc7f176 Mon Sep 17 00:00:00 2001 From: Kevin Haube Date: Tue, 16 Jan 2024 09:46:29 -0500 Subject: [PATCH 27/66] feat(actions): Actions available for CHIP SPA (#315) * Extract useActionFormController for common use * Extract render slots * fix spacing in withdraw package * fix spacing in withdraw package * fix additional conditions in submit handler * extract template and implement with WithdrawPackage * implement template with IssueRai * implement template with RespondToRai * (Save Point w/ bug): Fitting the bespoke Rai withdraw confirm modal into template pattern * (Save Point w/ bug): Fitting the bespoke Rai withdraw confirm modal into template pattern * Open actions to CHIP SPA * Extract setups and pipe from index * Tinker with setup getter * Remove enum usage for strings for ongoing union types refactor * Cleanup * Remove rogue modal * Cleanup * Fix Additional Info not showing required * Testing fixes * Fix addl info instructions: WithdrawPackage * Fix addl info instructions: RespondToRai --- .../package-actions/getAvailableActions.ts | 6 +- .../ui/src/hooks/useActionFormController.ts | 48 ++++ src/services/ui/src/lib/common-types.ts | 3 +- .../ui/src/pages/actions/IssueRai.tsx | 255 +++--------------- .../ui/src/pages/actions/RespondToRai.tsx | 254 +++-------------- .../ui/src/pages/actions/WithdrawPackage.tsx | 198 ++++---------- .../ui/src/pages/actions/WithdrawRai.tsx | 246 +++-------------- src/services/ui/src/pages/actions/index.tsx | 77 ++++-- .../ui/src/pages/actions/renderSlots.tsx | 72 +++++ src/services/ui/src/pages/actions/setups/_.ts | 7 + .../ui/src/pages/actions/setups/index.ts | 5 + .../src/pages/actions/setups/setupIssueRai.ts | 27 ++ .../pages/actions/setups/setupRespondToRai.ts | 74 +++++ .../actions/setups/setupWithdrawPackage.ts | 38 +++ .../pages/actions/setups/setupWithdrawRai.ts | 17 ++ .../ui/src/pages/actions/template.tsx | 93 +++++++ src/services/ui/src/pages/form/zod.ts | 5 + 17 files changed, 610 insertions(+), 815 deletions(-) create mode 100644 src/services/ui/src/hooks/useActionFormController.ts create mode 100644 src/services/ui/src/pages/actions/renderSlots.tsx create mode 100644 src/services/ui/src/pages/actions/setups/_.ts create mode 100644 src/services/ui/src/pages/actions/setups/index.ts create mode 100644 src/services/ui/src/pages/actions/setups/setupIssueRai.ts create mode 100644 src/services/ui/src/pages/actions/setups/setupRespondToRai.ts create mode 100644 src/services/ui/src/pages/actions/setups/setupWithdrawPackage.ts create mode 100644 src/services/ui/src/pages/actions/setups/setupWithdrawRai.ts create mode 100644 src/services/ui/src/pages/actions/template.tsx diff --git a/src/packages/shared-utils/package-actions/getAvailableActions.ts b/src/packages/shared-utils/package-actions/getAvailableActions.ts index c31844708d..0780420e47 100644 --- a/src/packages/shared-utils/package-actions/getAvailableActions.ts +++ b/src/packages/shared-utils/package-actions/getAvailableActions.ts @@ -1,7 +1,7 @@ import { CognitoUserAttributes, PlanType, - opensearch + opensearch, } from "../../shared-types"; import rules from "./rules"; import { PackageCheck } from "../packageCheck"; @@ -11,7 +11,7 @@ export const getAvailableActions = ( result: opensearch.main.Document ) => { const checks = PackageCheck(result); - return checks.planTypeIs([PlanType.MED_SPA]) + return checks.isSpa ? rules.filter((r) => r.check(checks, user)).map((r) => r.action) : []; -}; \ No newline at end of file +}; diff --git a/src/services/ui/src/hooks/useActionFormController.ts b/src/services/ui/src/hooks/useActionFormController.ts new file mode 100644 index 0000000000..849ecefe12 --- /dev/null +++ b/src/services/ui/src/hooks/useActionFormController.ts @@ -0,0 +1,48 @@ +import { FieldValues, SubmitHandler, UseFormReturn } from "react-hook-form"; +import { useParams } from "@/components/Routing"; +import { useGetUser } from "@/api/useGetUser"; +import { useModalContext } from "@/pages/form/modals"; +import { submit } from "@/api/submissionService"; +import { buildActionUrl } from "@/lib"; +import { PlanType } from "shared-types"; + +type DataConditionError = { + message: string; +}; + +export const useActionSubmitHandler = ({ + authority, + addDataConditions, +}: { + formHookReturn: UseFormReturn; + authority: PlanType; + /** Reserved for things zod cannot check. */ + addDataConditions?: ((data: D) => DataConditionError | null)[]; +}): SubmitHandler => { + 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({ + data: { ...data, id: id! }, + endpoint: buildActionUrl(type!), + user, + authority: authority, + }); + setSuccessModalOpen(true); + } catch (e) { + console.error(e); + setErrorModalOpen(true); + } + }; +}; diff --git a/src/services/ui/src/lib/common-types.ts b/src/services/ui/src/lib/common-types.ts index 7e149a4ace..9ac2c27d46 100644 --- a/src/services/ui/src/lib/common-types.ts +++ b/src/services/ui/src/lib/common-types.ts @@ -8,9 +8,8 @@ export type SubmissionServiceEndpoint = export const buildActionUrl = (action: Action): PackageActionEndpoint => `/action/${action}`; -type UploadKey> = keyof S["attachments"]; export type AttachmentRecipe> = { - readonly name: UploadKey; + readonly name: keyof S["attachments"] | string; readonly label: string; readonly required: boolean; }; diff --git a/src/services/ui/src/pages/actions/IssueRai.tsx b/src/services/ui/src/pages/actions/IssueRai.tsx index 8a35d9ba2b..d78d6157cb 100644 --- a/src/services/ui/src/pages/actions/IssueRai.tsx +++ b/src/services/ui/src/pages/actions/IssueRai.tsx @@ -1,227 +1,50 @@ -import { useParams, Link } from "@/components/Routing"; import * as I from "@/components/Inputs"; -import { type SubmitHandler, useForm } from "react-hook-form"; +import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Alert, LoadingSpinner } from "@/components"; -import { FAQ_TARGET } from "@/routes"; import { opensearch, PlanType } from "shared-types"; -import { useGetUser } from "@/api/useGetUser"; -import { submit } from "@/api/submissionService"; -import { buildActionUrl } from "@/lib"; -import { useModalContext } from "@/pages/form/modals"; +import { ActionFormTemplate } from "@/pages/actions/template"; +import { useActionSubmitHandler } from "@/hooks/useActionFormController"; +import { ActionFormIntro } from "@/pages/actions/common"; +import { FormSetup } from "@/pages/actions/setups"; -const formSchema = z.object({ - additionalInformation: z.string().max(4000), - attachments: z.object({ - formalRaiLetter: z - .array(z.instanceof(File)) - .refine((value) => value.length > 0, { - message: "Required", - }), - other: z.array(z.instanceof(File)).optional(), - }), -}); -export type RaiIssueFormSchema = z.infer; -//@ -const attachmentList = [ - { - name: "formalRaiLetter", - label: "Formal RAI Letter", - required: true, - }, - { - name: "other", - label: "Other", - required: false, - }, -] as const; - -const FormDescriptionText = () => { - return ( -

- 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.{" "} - - If you leave this page, you will lose your progress on this form. - -

- ); -}; - -export const RaiIssue = ({ item }: { item: opensearch.main.ItemResult }) => { - const { id, type } = useParams("/action/:id/:type"); - const authority = item?._source.authority as PlanType; - const { setCancelModalOpen, setSuccessModalOpen, setErrorModalOpen } = - useModalContext(); - const form = useForm({ - resolver: zodResolver(formSchema), +export const RaiIssue = ({ + item, + schema, + attachments, +}: FormSetup & { item: opensearch.main.ItemResult }) => { + const form = useForm>({ + resolver: zodResolver(schema), + }); + const handleSubmit = useActionSubmitHandler>({ + formHookReturn: form, + authority: item?._source.authority as PlanType, }); - const { data: user } = useGetUser(); - const handleSubmit: SubmitHandler = async (data) => { - try { - await submit({ - data: { - id: id!, // Declared here because it's not part of the form data - ...data, - }, - endpoint: buildActionUrl(type!), - user, - authority, - }); - setSuccessModalOpen(true); - } catch (err) { - console.log(err); - setErrorModalOpen(true); - } - }; return ( - - -
-

Formal RAI Details

-

- Indicates a required field -

- -
- {/*-------------------------------------------------------- */} -
-

- Package Details -

-
- - - {id} - -
-
- - - {item?._source.planType} - -
-
- {/*-------------------------------------------------------- */} -
-

Attachments

-

- Maximum file size of 80 MB per attachment.{" "} - You can add multiple files per attachment type.{" "} - Read the description for each of the attachment types on the{" "} - { - - FAQ Page - - } - . -

-
-

- We accept the following file formats:{" "} - .docx, .jpg, .png, .pdf, .xlsx,{" "} - and a few others. See the full list on the{" "} - { - - FAQ Page - - } - . -

-
-

- - At least one attachment is required. + > + item={item} + formController={form} + submitHandler={handleSubmit} + intro={ + + Indicates a required field +

+ 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.{" "} + + If you leave this page, you will lose your progress on this form. +

-
- {attachmentList.map(({ name, label, required }) => ( - ( - - - {label} - {required ? : ""} - - - - - )} - /> - ))} - {/*-------------------------------------------------------- */} - ( - -

- Additional Information* -

- - Add anything else you would like to share with the state. - - - 4,000 characters allowed -
- )} - /> - {/*-------------------------------------------------------- */} - - {Object.keys(form.formState.errors).length !== 0 ? ( - - Missing or malformed information. Please see errors above. - - ) : null} - {form.formState.isSubmitting && ( -
- -
- )} -
- - Submit - - setCancelModalOpen(true)} - className="px-12" - > - Cancel - -
- -
+ + } + attachments={attachments} + attachmentFaqLink={"/faq/#medicaid-spa-rai-attachments"} + requireAddlInfo + /> ); }; diff --git a/src/services/ui/src/pages/actions/RespondToRai.tsx b/src/services/ui/src/pages/actions/RespondToRai.tsx index 3a2eedae0b..d1d2bb25de 100644 --- a/src/services/ui/src/pages/actions/RespondToRai.tsx +++ b/src/services/ui/src/pages/actions/RespondToRai.tsx @@ -1,231 +1,51 @@ import * as I from "@/components/Inputs"; -import { type SubmitHandler, useForm } from "react-hook-form"; -import { z } from "zod"; +import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Alert, LoadingSpinner } from "@/components"; -import { FAQ_TARGET } from "@/routes"; -import { Link, useParams } from "@/components/Routing"; import { opensearch, PlanType } from "shared-types"; -import { useGetUser } from "@/api/useGetUser"; -import { submit } from "@/api/submissionService"; -import { buildActionUrl } from "@/lib"; -import { useModalContext } from "@/pages/form/modals"; - -const formSchema = z.object({ - additionalInformation: z.string().max(4000).optional(), - attachments: z.object({ - raiResponseLetter: z - .array(z.instanceof(File)) - .refine((value) => value.length > 0, { - message: "Required", - }), - other: z.array(z.instanceof(File)).optional(), - }), -}); -export type RespondToRaiFormSchema = z.infer; - -const attachmentList = [ - { - name: "raiResponseLetter", - label: "RAI Response Letter", - required: true, - }, - { - name: "other", - label: "Other", - required: false, - }, -] as const; - -const FormDescriptionText = () => { - return ( -

- Once you submit this form, a confirmation email is sent to you and to CMS. - CMS will use this content to review your package, and you will not be able - to edit this form. If CMS needs any additional information, they will - follow up by email.{" "} - - If you leave this page, you will lose your progress on this form. - -

- ); -}; +import { useActionSubmitHandler } from "@/hooks/useActionFormController"; +import { ActionFormIntro } from "@/pages/actions/common"; +import { ActionFormTemplate } from "@/pages/actions/template"; +import { FormSetup } from "@/pages/actions/setups"; export const RespondToRai = ({ item, -}: { + schema, + attachments, +}: FormSetup & { item: opensearch.main.ItemResult; }) => { - const { id, type } = useParams("/action/:id/:type"); - const authority = item?._source.authority as PlanType; - const { setCancelModalOpen, setErrorModalOpen, setSuccessModalOpen } = - useModalContext(); - const form = useForm({ - resolver: zodResolver(formSchema), + const form = useForm({ + resolver: zodResolver(schema), + }); + const handleSubmit = useActionSubmitHandler({ + formHookReturn: form, + authority: item?._source.authority as PlanType, }); - const { data: user } = useGetUser(); - const handleSubmit: SubmitHandler = async (data) => { - try { - await submit({ - data: { - id: id!, - ...data, - }, - endpoint: buildActionUrl(type!), - user, - authority, - }); - setSuccessModalOpen(true); - } catch (err) { - console.log(err); - setErrorModalOpen(true); - } - }; return ( - -
-
-

- Medicaid SPA Formal RAI Details -

-

- Indicates a required field -

- -
- {/*-------------------------------------------------------- */} -
-

- Package Details -

-
- - - {id} - -
-
- - - {item?._source.planType} - -
-
- {/*-------------------------------------------------------- */} -
-

Attachments

-

- Maximum file size of 80 MB per attachment.{" "} - You can add multiple files per attachment type.{" "} - Read the description for each of the attachment types on the{" "} - { - - FAQ Page - - } - . -

-
-

- We accept the following file formats:{" "} - .docx, .jpg, .png, .pdf, .xlsx,{" "} - and a few others. See the full list on the{" "} - { - - FAQ Page - - } - . -

-
-

- - At least one attachment is required. + + Indicates a required field +

+ Once you submit this form, a confirmation email is sent to you and + to CMS. CMS will use this content to review your package, and you + will not be able to edit this form. If CMS needs any additional + information, they will follow up by email.{" "} + + If you leave this page, you will lose your progress on this form. +

-
- {attachmentList.map(({ name, label, required }) => ( - ( - - - {label} - {required ? : ""} - - - - - )} - /> - ))} - {/*-------------------------------------------------------- */} - ( - -

- Additional Information -

- - Add anything else that you would like to share with CMS. - - - 4,000 characters allowed -
- )} - /> - {/*-------------------------------------------------------- */} - - {Object.keys(form.formState.errors).length !== 0 ? ( - - Missing or malformed information. Please see errors above. - - ) : null} - {form.formState.isSubmitting && ( -
- -
- )} -
- - Submit - - setCancelModalOpen(true)} - className="px-12" - > - Cancel - -
- -
+ + } + attachments={attachments} + attachmentFaqLink={"/faq/#medicaid-spa-rai-attachments"} + addlInfoInstructions={ +

Add anything else that you would like to share with CMS.

+ } + /> ); }; diff --git a/src/services/ui/src/pages/actions/WithdrawPackage.tsx b/src/services/ui/src/pages/actions/WithdrawPackage.tsx index 9f4302afde..612301db86 100644 --- a/src/services/ui/src/pages/actions/WithdrawPackage.tsx +++ b/src/services/ui/src/pages/actions/WithdrawPackage.tsx @@ -1,95 +1,50 @@ -import { Navigate, useParams } from "@/components/Routing"; -import { Button } from "@/components/Inputs"; -import { FC, useState } from "react"; +import { Navigate } from "@/components/Routing"; import { PlanType, opensearch } from "shared-types"; -import { ActionFormIntro, PackageInfo } from "./common"; +import { ActionFormIntro } from "./common"; import { z } from "zod"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import * as I from "@/components/Inputs"; -import { LoadingSpinner } from "@/components"; -import { AttachmentRecipe, buildActionUrl } from "@/lib"; -import { useGetUser } from "@/api/useGetUser"; -import { submit } from "@/api/submissionService"; -import { AttachmentsSizeTypesDesc } from "@/pages/form/content"; -import { useModalContext } from "@/pages/form/modals"; - -// Temporary, will be refactored to an extendable schema with Brian/Mike's back-end -// work. -const withdrawPackageFormSchema = z.object({ - additionalInformation: z - .string() - .max(4000, "This field may only be up to 4000 characters.") - .optional(), - attachments: z.object({ - supportingDocumentation: z.array(z.instanceof(File)).optional(), - }), -}); -type WithdrawPackageFormSchema = z.infer; -const attachments: AttachmentRecipe[] = [ - { - name: "supportingDocumentation", - label: "Supporting Documentation", - required: false, - } as const, -]; +import { useActionSubmitHandler } from "@/hooks/useActionFormController"; +import { ActionFormTemplate } from "@/pages/actions/template"; +import { FormSetup } from "@/pages/actions/setups"; export const WithdrawPackage = ({ item, -}: { + schema, + attachments, +}: FormSetup & { item: opensearch.main.ItemResult; }) => { - const { id, type } = useParams("/action/:id/:type"); - const { - cancelModalOpen, - setCancelModalOpen, - setSuccessModalOpen, - setErrorModalOpen, - } = useModalContext(); - const { data: user } = useGetUser(); - const authority = item?._source.authority as PlanType; - const form = useForm({ - resolver: zodResolver(withdrawPackageFormSchema), + const form = useForm>({ + resolver: zodResolver(schema), }); - - const [errorMessage, setErrorMessage] = useState(null); - const handleSubmit: SubmitHandler = async ( - data - ) => { - try { - if (!cancelModalOpen) { + const handleSubmit = useActionSubmitHandler({ + formHookReturn: form, + authority: item?._source.authority as PlanType, + addDataConditions: [ + (data) => { if ( !data.attachments.supportingDocumentation && !data.additionalInformation ) { - setErrorMessage( - "An Attachment or Additional Information is required." - ); + return { + message: "An Attachment or Additional Information is required.", + }; } else { - await submit({ - data: { - ...data, - id: id!, // Declared here because it's not part of the form data. - }, - endpoint: buildActionUrl(type!), - user, - authority, - }); - setSuccessModalOpen(true); + return null; } - } - } catch (err) { - console.log(err); - setErrorModalOpen(true); - } - }; + }, + ], + }); - if (!item) return ; // Prevents optional chains below + if (!item) return ; // Prevents optionals below return ( - <> - {form.formState.isSubmitting && } -
- + > + item={item} + formController={form} + submitHandler={handleSubmit} + intro={ +

Complete this form to withdraw a package. Once complete, you will not be able to resubmit this package. CMS will be notified and will @@ -97,80 +52,27 @@ export const WithdrawPackage = ({ information, they will follow up by email.

- -

Attachments

+ } + attachments={attachments} + attachmentFaqLink={"/faq"} + attachmentInstructions={

- Upload your supporting documentation for withdrawal or explain your - need for withdrawal in the Additional Information section. + Official withdrawal letters are required and must be on state + letterhead signed by the State Medicaid Director or CHIP Director. +

+ } + addlInfoInstructions={ +

+ Explain your need for withdrawal, or upload supporting documentation. +
+ + Once you submit this form, a confirmation email is sent to you and + to CMS. CMS will use this content to review your package. If CMS + needs any additional information, they will follow up by email + + .

- -
- {/* Change faqLink once we know the anchor */} - - {attachments.map(({ name, label, required }) => ( - ( - - - {label} - {required ? : ""} - - - - - )} - /> - ))} - ( - -

- Additional Information -

- - Explain your need for withdrawal or upload supporting - documentation. -
-

- - Once you submit this form, a confirmation email is sent - to you and to CMS. CMS will use this content to review - your package. If CMS needs any additional information, - they will follow up by email. - {" "} -

-
-
- - - 4,000 characters allowed - -
- )} - /> - {errorMessage && ( -
{errorMessage}
- )} -
- - -
- -
-
- + } + /> ); }; diff --git a/src/services/ui/src/pages/actions/WithdrawRai.tsx b/src/services/ui/src/pages/actions/WithdrawRai.tsx index 5f776f51e1..80cc313087 100644 --- a/src/services/ui/src/pages/actions/WithdrawRai.tsx +++ b/src/services/ui/src/pages/actions/WithdrawRai.tsx @@ -1,224 +1,44 @@ -import { ConfirmationModal, LoadingSpinner } from "@/components"; import * as I from "@/components/Inputs"; import { zodResolver } from "@hookform/resolvers/zod"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { useForm } from "react-hook-form"; import { z } from "zod"; -import { Link, useParams } from "@/components/Routing"; import { opensearch, PlanType } from "shared-types"; -import { useGetUser } from "@/api/useGetUser"; -import { submit } from "@/api/submissionService"; -import { useState } from "react"; -import { FAQ_TARGET } from "@/routes"; -import { useModalContext } from "@/pages/form/modals"; +import { ActionFormTemplate } from "@/pages/actions/template"; +import { useActionSubmitHandler } from "@/hooks/useActionFormController"; +import { ActionFormIntro } from "@/pages/actions/common"; +import { FormSetup } from "@/pages/actions/setups"; -const formSchema = z.object({ - additionalInformation: z.string().max(4000), - attachments: z.object({ - supportingDocumentation: z.array(z.instanceof(File)).nullish(), - }), -}); - -type FormSchema = z.infer; - -const attachmentList = [ - { - name: "supportingDocumentation", - label: "Supporting Documentation", - required: false, - }, -] as const; - -export const WithdrawRai = ({ item }: { item: opensearch.main.ItemResult }) => { - const { id } = useParams("/action/:id/:type"); - const { setCancelModalOpen, setSuccessModalOpen, setErrorModalOpen } = - useModalContext(); - const [areYouSureModalIsOpen, setAreYouSureModalIsOpen] = useState(false); - const authority = item?._source.authority as PlanType; - const user = useGetUser(); - const form = useForm({ - resolver: zodResolver(formSchema), +export const WithdrawRai = ({ + item, + schema, + attachments, +}: FormSetup & { item: opensearch.main.ItemResult }) => { + // const [areYouSureModalOpen, setAreYouSureModalOpen] = useState(false); + const form = useForm>({ + resolver: zodResolver(schema), + }); + const handleSubmit = useActionSubmitHandler>({ + formHookReturn: form, + authority: item?._source.authority as PlanType, }); - - const handleSubmit: SubmitHandler = async (data) => { - try { - console.log(data); - await submit({ - data: { ...data, id }, - endpoint: "/action/withdraw-rai", - user: user.data, - authority: authority, - }); - setSuccessModalOpen(true); - } catch (err: unknown) { - if (err) { - console.log("There was an error", err); - setErrorModalOpen(true); - } - } - }; return ( - <> - -
-
-

- Withdraw Formal RAI Response Details -

-

- Indicates a required field -

-

- Complete this form to withdraw the Formal RAI response. Once - complete, you and CMS will receive an email confirmation. -

-
-
-

- Package Details -

-
- - - {id} - -
-
- - - {item?._source.planType} - -
-
-

Attachments

-

- Maximum file size of 80 MB per attachment.{" "} - You can add multiple files per attachment type.{" "} - Read the description for each of the attachment types on the{" "} - { - - FAQ Page - - } - . -

-

- We accept the following file formats:{" "} - .docx, .jpg, .png, .pdf, .xlsx,{" "} - and a few others. See the full list on the{" "} - { - - FAQ Page - - } - . + > + item={item} + formController={form} + submitHandler={handleSubmit} + intro={ + + Indicates a required field +

+ Complete this form to withdraw the Formal RAI response. Once + complete, you and CMS will receive an email confirmation.

- {attachmentList.map(({ name, label, required }) => ( - ( - - - {label} - {required ? : ""} - - - - - )} - /> - ))} - { - return ( - -

- Additional Information - -

- - Explain your need for withdrawal. - - - - 4,000 characters allowed - -
- ); - }} - /> - {form.formState.isSubmitting && ( -
- -
- )} -
- { - await form.trigger(); - - if (form.formState.isValid) { - setAreYouSureModalIsOpen(true); - } - }} - className="px-12" - > - Submit - - setCancelModalOpen(true)} - className="px-12" - > - Cancel - -
- -
- {/* Are you sure modal */} - { - setAreYouSureModalIsOpen(false); - form.handleSubmit(handleSubmit)(); - }} - onCancel={() => { - setAreYouSureModalIsOpen(false); - }} - title="Withdraw Formal RAI Response?" - body={`You are about to withdraw the Formal RAI Response for ${id}. CMS will be notified.`} - acceptButtonText="Yes, withdraw response" - cancelButtonText="Cancel" - /> - + + } + attachments={attachments} + attachmentFaqLink={"/faq/#medicaid-spa-rai-attachments"} + requireAddlInfo + /> ); }; diff --git a/src/services/ui/src/pages/actions/index.tsx b/src/services/ui/src/pages/actions/index.tsx index 5ff6059280..2218c07cb7 100644 --- a/src/services/ui/src/pages/actions/index.tsx +++ b/src/services/ui/src/pages/actions/index.tsx @@ -3,7 +3,7 @@ import { ToggleRaiResponseWithdraw } from "@/pages/actions/ToggleRaiResponseWith import { RaiIssue } from "@/pages/actions/IssueRai"; import { WithdrawPackage } from "@/pages/actions/WithdrawPackage"; import { RespondToRai } from "@/pages/actions/RespondToRai"; -import { Action } from "shared-types"; +import { opensearch, Action } from "shared-types"; import { WithdrawRai } from "./WithdrawRai"; import { useGetItem, useGetPackageActions } from "@/api"; import { @@ -14,6 +14,39 @@ import { } from "@/components"; import { ModalProvider } from "@/pages/form/modals"; import { detailsAndActionsCrumbs } from "@/pages/actions/actions-breadcrumbs"; +import { + chipRespondToRaiSetup, + chipWithdrawPackageSetup, + defaultIssueRaiSetup, + defaultWithdrawRaiSetup, + FormSetup, + medicaidRespondToRaiSetup, + medicaidWithdrawPackageSetup, +} from "@/pages/actions/setups"; + +type SetupOptions = "CHIP SPA" | "Medicaid SPA"; +const getFormSetup = (opt: SetupOptions, type: Action): FormSetup | null => { + switch (type) { + case "issue-rai": + return defaultIssueRaiSetup; + case "respond-to-rai": + return { + "Medicaid SPA": medicaidRespondToRaiSetup, + "CHIP SPA": chipRespondToRaiSetup, + }[opt]; + case "withdraw-rai": + return defaultWithdrawRaiSetup; + case "withdraw-package": + return { + "Medicaid SPA": medicaidWithdrawPackageSetup, + "CHIP SPA": chipWithdrawPackageSetup, + }[opt]; + case "enable-rai-withdraw": + case "disable-rai-withdraw": + default: + return null; + } +}; const ActionFormSwitch = () => { const { id, type } = useParams("/action/:id/:type"); @@ -54,21 +87,33 @@ const ActionFormSwitch = () => { ); - // Form renders - switch (type) { - case Action.WITHDRAW_PACKAGE: - return ; - case Action.ENABLE_RAI_WITHDRAW: - case Action.DISABLE_RAI_WITHDRAW: - return ; - case Action.ISSUE_RAI: - return ; - case Action.WITHDRAW_RAI: - return ; - case Action.RESPOND_TO_RAI: - return ; - default: - return ; + // Enable/Disable Rai Withdraw is an "Admin Action", and breaks + // the mold for a "form" in that it does not utilize any form + // tooling or elements. A later refactor could be considered. + if ( + (type as Action) === "enable-rai-withdraw" || + (type as Action) === "disable-rai-withdraw" + ) { + return ; + } else { + const setup = getFormSetup( + item!._source.planType as string as SetupOptions, + type + ); + if (!setup) return ; + // Form renders + switch (type as Action) { + case "issue-rai": + return ; + case "respond-to-rai": + return ; + case "withdraw-rai": + return ; + case "withdraw-package": + return ; + default: + return ; + } } }; diff --git a/src/services/ui/src/pages/actions/renderSlots.tsx b/src/services/ui/src/pages/actions/renderSlots.tsx new file mode 100644 index 0000000000..824112c51e --- /dev/null +++ b/src/services/ui/src/pages/actions/renderSlots.tsx @@ -0,0 +1,72 @@ +import { + FormDescription, + FormItem, + FormLabel, + RequiredIndicator, + Textarea, + Upload, +} from "@/components/Inputs"; +import { + ControllerProps, + ControllerRenderProps, + FieldPath, + FieldValues, +} from "react-hook-form"; +import { ReactElement, ReactNode } from "react"; + +export const SlotAttachments = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + label, + message, + ...props +}: { + label: ReactNode; + message: ReactNode; + className?: string; +}): ControllerProps["render"] => + function Render({ + field, + }: { + field: ControllerRenderProps; + }) { + return ( + + {label} + + {message} + + ); + }; + +export const SlotAdditionalInfo = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + label, + description, + required, + ...props +}: { + description: string; + label?: ReactElement; + required?: boolean; + className?: string; +}): ControllerProps["render"] => + function Render({ + field, + }: { + field: ControllerRenderProps; + }) { + return ( + +

+ Additional Information {required && } +

+ {label} +