From eab3af5f26caa4bc764170d7d31aca6dd33decd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 9 Dec 2025 18:01:40 +0100 Subject: [PATCH] Handle document list state + API payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- apps/console/package.json | 1 + ...CenterAccessGraphUpdateMutation.graphql.ts | 13 +- .../TrustCenterAccessEditDialog.tsx | 242 +++++------------- .../TrustCenterAccessItem.tsx | 2 +- .../TrustCenterAccessTab.tsx | 2 +- .../TrustCenterDocumentAccessList.tsx | 125 +++++++++ apps/console/vite.config.ts | 3 + package-lock.json | 19 ++ packages/coredata/package.json | 21 ++ .../coredata/src}/TrustCenterAccess.ts | 0 .../src}/TrustCenterDocumentAccess.ts | 11 +- packages/coredata/src/index.ts | 2 + packages/coredata/tsconfig.json | 5 + packages/helpers/package.json | 3 + packages/helpers/src/index.ts | 5 + .../helpers/src/trustCenterDocumentAccess.ts | 92 +++++++ packages/ui/src/Atoms/Button/Button.tsx | 4 +- packages/ui/src/theme.css | 5 + pkg/probo/trust_center_access_service.go | 35 +-- pkg/server/api/console/v1/schema.graphql | 11 +- pkg/server/api/console/v1/schema/schema.go | 95 +++++-- pkg/server/api/console/v1/types/types.go | 17 +- pkg/server/api/console/v1/v1_resolver.go | 33 ++- 23 files changed, 507 insertions(+), 239 deletions(-) create mode 100644 apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterDocumentAccessList.tsx create mode 100644 packages/coredata/package.json rename {apps/console/src/coredata => packages/coredata/src}/TrustCenterAccess.ts (100%) rename {apps/console/src/coredata => packages/coredata/src}/TrustCenterDocumentAccess.ts (55%) create mode 100644 packages/coredata/src/index.ts create mode 100644 packages/coredata/tsconfig.json create mode 100644 packages/helpers/src/trustCenterDocumentAccess.ts diff --git a/apps/console/package.json b/apps/console/package.json index 8f0b5d456..e19d06a7e 100644 --- a/apps/console/package.json +++ b/apps/console/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@hookform/resolvers": "^5.0.1", + "@probo/coredata": "^1.0.0", "@probo/helpers": "^1.0.0", "@probo/hooks": "1.0.0", "@probo/i18n": "1.0.0", diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts index bd6b41e4d..769c27fa8 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<25b79c235754d47097b2ca28f1f3e9a0>> + * @generated SignedSource<<4d6afeeeed479773202c8516eeb63620>> * @lightSyntaxTransform * @nogrep */ @@ -9,13 +9,18 @@ // @ts-nocheck import { ConcreteRequest } from 'relay-runtime'; +export type TrustCenterDocumentAccessStatus = "GRANTED" | "REJECTED" | "REQUESTED" | "REVOKED"; export type UpdateTrustCenterAccessInput = { active?: boolean | null | undefined; - documentIds?: ReadonlyArray | null | undefined; + documents?: ReadonlyArray | null | undefined; id: string; name?: string | null | undefined; - reportIds?: ReadonlyArray | null | undefined; - trustCenterFileIds?: ReadonlyArray | null | undefined; + reports?: ReadonlyArray | null | undefined; + trustCenterFiles?: ReadonlyArray | null | undefined; +}; +export type TrustCenterDocumentAccessInput = { + id: string; + status: TrustCenterDocumentAccessStatus; }; export type TrustCenterAccessGraphUpdateMutation$variables = { input: UpdateTrustCenterAccessInput; diff --git a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessEditDialog.tsx b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessEditDialog.tsx index 6b9e88b95..538303048 100644 --- a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessEditDialog.tsx +++ b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessEditDialog.tsx @@ -1,73 +1,15 @@ -import { - Badge, - Button, - Checkbox, - Dialog, - DialogContent, - DialogFooter, - Field, - Spinner, - Table, - Tbody, - Td, - Th, - Thead, - Tr, -} from "@probo/ui"; +import type { TrustCenterAccess, TrustCenterDocumentAccessStatus } from "@probo/coredata"; +import { getTrustCenterDocumentAccessInfo, type TrustCenterDocumentAccessInfo } from "@probo/helpers"; +import { Button, Checkbox, Dialog, DialogContent, DialogFooter, Field, Spinner } from "@probo/ui"; import { usePreloadedQuery, type PreloadedQuery, useQueryLoader } from "react-relay"; import type { TrustCenterAccessGraphLoadDocumentAccessesQuery } from "/hooks/graph/__generated__/TrustCenterAccessGraphLoadDocumentAccessesQuery.graphql"; -import type { TrustCenterDocumentAccess } from "/coredata/TrustCenterDocumentAccess"; import { loadTrustCenterAccessDocumentAccessesQuery, updateTrustCenterAccessMutation } from "/hooks/graph/TrustCenterAccessGraph"; import { useTranslate } from "@probo/i18n"; import z from "zod"; import { useFormWithSchema } from "/hooks/useFormWithSchema"; -import type { TrustCenterAccess } from "/coredata/TrustCenterAccess"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; -import { Suspense, useEffect } from "react"; - -function getDocumentAccessInfo( - docAccess: TrustCenterDocumentAccess, - __: (key: string) => string -) { - if (docAccess.document) { - return { - variant: "info" as const, - name: docAccess.document?.title, - type: __("Document"), - category: docAccess.document?.documentType, - id: docAccess.document?.id, - requested: docAccess.requested, - active: docAccess.active, - status: docAccess.status, - }; - } - if (docAccess.report) { - return { - variant: "success" as const, - name: docAccess.report?.filename, - type: __("Report"), - category: docAccess.report?.audit?.framework?.name, - id: docAccess.report?.id, - requested: docAccess.requested, - active: docAccess.active, - status: docAccess.status, - }; - } - if (docAccess.trustCenterFile) { - return { - variant: "highlight" as const, - name: docAccess.trustCenterFile?.name, - type: __("File"), - category: docAccess.trustCenterFile?.category, - id: docAccess.trustCenterFile?.id, - requested: docAccess.requested, - active: docAccess.active, - status: docAccess.status, - }; - } - - throw new Error("Unknown trust center access document type"); -} +import { Suspense, useCallback, useEffect, useState } from "react"; +import { TrustCenterDocumentAccessList } from "./TrustCenterDocumentAccessList"; interface TrustCenterAccessEditDialogProps { access: TrustCenterAccess; @@ -121,7 +63,30 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps) loadTrustCenterAccessDocumentAccessesQuery, queryRef, ) - const documentAccesses: TrustCenterDocumentAccess[] = data.node.availableDocumentAccesses?.edges.map(edge => edge.node) ?? []; + + const initialDocumentAccesses = (data.node.availableDocumentAccesses?.edges.map(edge => edge.node) ?? []).map(da => getTrustCenterDocumentAccessInfo(da, __)) + const initialStatusByID = initialDocumentAccesses.reduce>((acc, docAccess) => { + acc[docAccess.id] = docAccess.status; + return acc + }, {}) + const [documentAccesses, setDocumentAccesses] = useState(initialDocumentAccesses); + + const handleUpdateDocumentAccessStatus = useCallback((documentAccess: TrustCenterDocumentAccessInfo, status: TrustCenterDocumentAccessStatus) => { + setDocumentAccesses((prev) => { + const nextDocumentAccesses = [...prev]; + const docAccessIndex = nextDocumentAccesses.findIndex(element => element.id === documentAccess.id) + const previousDocAccess = nextDocumentAccesses[docAccessIndex]; + nextDocumentAccesses.splice(docAccessIndex, 1, { ...previousDocAccess, status }); + + return nextDocumentAccesses; + }) + }, []) + const handleGrantAllDocumentAccess = useCallback(() => { + setDocumentAccesses((prev) => prev.map(element => ({...element, status: "GRANTED"}))) + }, []) + const handleRejectOrRevokeAllDocumentAccess = useCallback(() => { + setDocumentAccesses((prev) => prev.map(element => ({...element, status: initialStatusByID[element.id] === "GRANTED" ? "REVOKED" : "REJECTED"}))) + }, [initialStatusByID]) const editSchema = z.object({ name: z.string().min(1, __("Name is required")).min(2, __("Name must be at least 2 characters long")), @@ -137,20 +102,23 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps) }); const handleSubmit = editForm.handleSubmit(async (data) => { - const { documentIds, reportIds, trustCenterFileIds } = documentAccesses.reduce( - (acc, docAccess) => { - // TODO status update - if (docAccess.document?.id) { - acc.documentIds.push(docAccess.document.id); - } else if (docAccess.report?.id) { - acc.reportIds.push(docAccess.report.id); - } else if (docAccess.trustCenterFile?.id) { - acc.trustCenterFileIds.push(docAccess.trustCenterFile.id); - } - return acc; - }, - { documentIds: [] as string[], reportIds: [] as string[], trustCenterFileIds: [] as string[] } - ); + const documents: {id: string, status: TrustCenterDocumentAccessStatus}[] = [] + const reports: {id: string, status: TrustCenterDocumentAccessStatus}[] = [] + const trustCenterFiles: {id: string, status: TrustCenterDocumentAccessStatus}[] = []; + + for (const docAccess of documentAccesses) { + switch (docAccess.type) { + case "document": + documents.push({id: docAccess.id, status: docAccess.status}); + break; + case "report": + reports.push({id: docAccess.id, status: docAccess.status}); + break; + case "file": + trustCenterFiles.push({id: docAccess.id, status: docAccess.status}); + break; + } + } await updateTrustCenterAccess({ variables: { @@ -158,14 +126,12 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps) id: access.id, name: data.name.trim(), active: data.active, - documentIds, - reportIds, - trustCenterFileIds, + documents, + reports, + trustCenterFiles, }, }, - onSuccess: () => { - onSubmit(); - }, + onSuccess: onSubmit, }); }); @@ -201,7 +167,13 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps) - + @@ -213,103 +185,3 @@ export function TrustCenterAccessEditForm(props: TrustCenterAccessEditFormProps) ); } - -function TrustCenterDocumentAccessList(props: { - documentAccesses: TrustCenterDocumentAccess[]; -}) { - const { documentAccesses } = props; - - const { __ } = useTranslate(); - const formattedDocumentAccesses: NonNullable>[] = documentAccesses - ?.map((docAccess) => getDocumentAccessInfo(docAccess, __)) ?? []; - - const showGrantCTA = formattedDocumentAccesses.some(da => da.status !== "GRANTED"); - const showRejectCTA = formattedDocumentAccesses.some(da => da.status !== "REJECTED" && da.status !== "REVOKED"); - - return ( -
-
-

- {__("Document Access Permissions")} -

- {showGrantCTA && - - } - {showRejectCTA && - - } -
- - {formattedDocumentAccesses.length > 0 ? ( -
- - - - - - - - - - - - {formattedDocumentAccesses.map((info) => { - const { variant, name, type, category, id, status } = info; - - return ( - - - - - - - - ); - })} - -
{__("Name")}{__("Type")}{__("Category")} - {__("Access")} -
-
- {name} -
-
- - {type} - - -
- {category || "-"} -
-
- - {status} - - -
- {/* TODO DROPDOWN */} -
-
-
- ) : ( -
- {__("No documents available")} -
- )} -
- ) -} diff --git a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessItem.tsx b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessItem.tsx index 31ef47a43..a0aae566a 100644 --- a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessItem.tsx +++ b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessItem.tsx @@ -1,5 +1,5 @@ +import type { TrustCenterAccess } from "@probo/coredata"; import { Button, IconCheckmark1, IconCrossLargeX, IconPencil, IconTrashCan, Td, Tr } from "@probo/ui"; -import type { TrustCenterAccess } from "/coredata/TrustCenterAccess"; import { formatDate } from "@probo/helpers"; import { use, useCallback, useState } from "react"; import { PermissionsContext } from "/providers/PermissionsContext"; diff --git a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessTab.tsx b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessTab.tsx index 3e166b293..5285e2818 100644 --- a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessTab.tsx +++ b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterAccessTab.tsx @@ -26,8 +26,8 @@ import { import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import { PermissionsContext } from "/providers/PermissionsContext"; -import type { TrustCenterAccess } from "/coredata/TrustCenterAccess"; import { TrustCenterAccessItem } from "./TrustCenterAccessItem"; +import type { TrustCenterAccess } from "@probo/coredata"; type ContextType = { organization: { diff --git a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterDocumentAccessList.tsx b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterDocumentAccessList.tsx new file mode 100644 index 000000000..f8a92cc86 --- /dev/null +++ b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab/TrustCenterDocumentAccessList.tsx @@ -0,0 +1,125 @@ +import type { TrustCenterDocumentAccessStatus } from "@probo/coredata"; +import { getTrustCenterDocumentAccessStatusBadgeVariant, type TrustCenterDocumentAccessInfo } from "@probo/helpers"; +import { useTranslate } from "@probo/i18n"; +import { Badge, Button, Table, Tbody, Td, Th, Thead, Tr } from "@probo/ui"; + +interface TrustCenterDocumentAccessListProps { + documentAccesses: TrustCenterDocumentAccessInfo[]; + initialStatusByID: Record; + onGrantAll: () => void; + onRejectOrRevokeAll: () => void; + onUpdateStatus: (docAccess: TrustCenterDocumentAccessInfo, status: TrustCenterDocumentAccessStatus) => void; +} + +export function TrustCenterDocumentAccessList(props: TrustCenterDocumentAccessListProps) { + const { documentAccesses, initialStatusByID, onGrantAll, onRejectOrRevokeAll, onUpdateStatus } = props; + + const { __ } = useTranslate(); + + const showGrantCTA = documentAccesses.some(da => da.status !== "GRANTED"); + const showRejectCTA = documentAccesses.some(da => da.status !== "REJECTED" && da.status !== "REVOKED"); + + return ( +
+
+

+ {__("Document Access Permissions")} +

+
+ {showGrantCTA && + + } + {showRejectCTA && + + } +
+
+ + {documentAccesses.length > 0 ? ( +
+ + + + + + + + + + + + {documentAccesses.map((docAccess) => { + return ( + + + + + + + + ); + })} + +
{__("Name")}{__("Type")}{__("Category")} + {__("Access")} +
+
+ {docAccess.name} +
+
+ + {docAccess.type} + + +
+ {docAccess.category || "-"} +
+
+ + {docAccess.status} + + + {docAccess.status !== "GRANTED" && + + } + {docAccess.status !== "REJECTED" && docAccess.status !== "REVOKED" && + + } +
+
+ ) : ( +
+ {__("No documents available")} +
+ )} +
+ ) +} diff --git a/apps/console/vite.config.ts b/apps/console/vite.config.ts index 2ad87c224..17c9bce0f 100644 --- a/apps/console/vite.config.ts +++ b/apps/console/vite.config.ts @@ -28,6 +28,9 @@ export default defineConfig({ "/components": fileURLToPath( new URL("./src/components", import.meta.url), ), + "/coredata": fileURLToPath( + new URL("./src/coredata", import.meta.url), + ), "/hooks": fileURLToPath(new URL("./src/hooks", import.meta.url)), "/layouts": fileURLToPath(new URL("./src/layouts", import.meta.url)), "/pages": fileURLToPath(new URL("./src/pages", import.meta.url)), diff --git a/package-lock.json b/package-lock.json index 5fcd15047..b59b1cd59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "version": "0.0.0", "dependencies": { "@hookform/resolvers": "^5.0.1", + "@probo/coredata": "^1.0.0", "@probo/helpers": "^1.0.0", "@probo/hooks": "1.0.0", "@probo/i18n": "1.0.0", @@ -2606,6 +2607,10 @@ "resolved": "apps/console", "link": true }, + "node_modules/@probo/coredata": { + "resolved": "packages/coredata", + "link": true + }, "node_modules/@probo/emails": { "resolved": "packages/emails", "link": true @@ -19295,6 +19300,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "packages/coredata": { + "name": "@probo/coredata", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@probo/prettier": "1.0.0", + "prettier": "^3.5.3", + "typescript": "^5.8.3", + "vitest": "^3.1.3" + } + }, "packages/emails": { "name": "@probo/emails", "version": "0.1.0", @@ -19330,6 +19346,9 @@ "name": "@probo/helpers", "version": "1.0.0", "license": "ISC", + "dependencies": { + "@probo/coredata": "^1.0.0" + }, "devDependencies": { "@probo/prettier": "1.0.0", "prettier": "^3.5.3", diff --git a/packages/coredata/package.json b/packages/coredata/package.json new file mode 100644 index 000000000..5ddad4e16 --- /dev/null +++ b/packages/coredata/package.json @@ -0,0 +1,21 @@ +{ + "name": "@probo/coredata", + "version": "1.0.0", + "description": "", + "main": "./src/index.ts", + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "format": "prettier --write ./src" + }, + "prettier": "@probo/prettier", + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@probo/prettier": "1.0.0", + "prettier": "^3.5.3", + "typescript": "^5.8.3", + "vitest": "^3.1.3" + } +} diff --git a/apps/console/src/coredata/TrustCenterAccess.ts b/packages/coredata/src/TrustCenterAccess.ts similarity index 100% rename from apps/console/src/coredata/TrustCenterAccess.ts rename to packages/coredata/src/TrustCenterAccess.ts diff --git a/apps/console/src/coredata/TrustCenterDocumentAccess.ts b/packages/coredata/src/TrustCenterDocumentAccess.ts similarity index 55% rename from apps/console/src/coredata/TrustCenterDocumentAccess.ts rename to packages/coredata/src/TrustCenterDocumentAccess.ts index d12c25be5..9a42be58d 100644 --- a/apps/console/src/coredata/TrustCenterDocumentAccess.ts +++ b/packages/coredata/src/TrustCenterDocumentAccess.ts @@ -1,6 +1,15 @@ +export const trustCenterDocumentAccessStatus = { + REQUESTED: "REQUESTED", + GRANTED: "GRANTED", + REJECTED: "REJECTED", + REVOKED: "REVOKED", +} as const; + +export type TrustCenterDocumentAccessStatus = (typeof trustCenterDocumentAccessStatus)[keyof typeof trustCenterDocumentAccessStatus]; + export type TrustCenterDocumentAccess = { active: boolean; - status: string; + status: TrustCenterDocumentAccessStatus; requested: boolean; document?: { id: string; diff --git a/packages/coredata/src/index.ts b/packages/coredata/src/index.ts new file mode 100644 index 000000000..e217421a5 --- /dev/null +++ b/packages/coredata/src/index.ts @@ -0,0 +1,2 @@ +export type { TrustCenterAccess } from "./TrustCenterAccess"; +export type { TrustCenterDocumentAccess, TrustCenterDocumentAccessStatus } from "./TrustCenterDocumentAccess"; diff --git a/packages/coredata/tsconfig.json b/packages/coredata/tsconfig.json new file mode 100644 index 000000000..a172e9faf --- /dev/null +++ b/packages/coredata/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": ["ES2021", "dom"], + } +} diff --git a/packages/helpers/package.json b/packages/helpers/package.json index 420f5a72b..ef53cc3a4 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -17,5 +17,8 @@ "prettier": "^3.5.3", "typescript": "^5.8.3", "vitest": "^3.1.3" + }, + "dependencies": { + "@probo/coredata": "^1.0.0" } } diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts index b1398827e..8351ee272 100644 --- a/packages/helpers/src/index.ts +++ b/packages/helpers/src/index.ts @@ -66,3 +66,8 @@ export { formatDatetime, formatDate } from "./date"; export { getLogoUrl, getTrustCenterUrl } from "./trustCenter"; export { formatError, type GraphQLError } from "./error"; export { Role, getAssignableRoles } from "./roles"; +export { + getTrustCenterDocumentAccessInfo, + getTrustCenterDocumentAccessStatusBadgeVariant, + type TrustCenterDocumentAccessInfo, +} from "./trustCenterDocumentAccess"; diff --git a/packages/helpers/src/trustCenterDocumentAccess.ts b/packages/helpers/src/trustCenterDocumentAccess.ts new file mode 100644 index 000000000..105373a9e --- /dev/null +++ b/packages/helpers/src/trustCenterDocumentAccess.ts @@ -0,0 +1,92 @@ +import type { TrustCenterDocumentAccess, TrustCenterDocumentAccessStatus } from "@probo/coredata"; + +export function getTrustCenterDocumentAccessStatusBadgeVariant(status: TrustCenterDocumentAccessStatus) { + switch (status) { + case "REQUESTED": + return "warning" as const; + case "GRANTED": + return "success" as const; + case "REJECTED": + case "REVOKED": + return "danger" as const; + } +} + +export type TrustCenterDocumentAccessInfo = { + variant: "info", + name: string, + type: "document", + typeLabel: string, + category: string; + id: string; + requested: boolean; + active: boolean; + status: TrustCenterDocumentAccessStatus; +} | { + variant: "success", + name: string, + type: "report", + typeLabel: string, + category: string; + id: string; + requested: boolean; + active: boolean; + status: TrustCenterDocumentAccessStatus; +} | { + variant: "highlight", + name: string, + type: "file", + typeLabel: string, + category: string; + id: string; + requested: boolean; + active: boolean; + status: TrustCenterDocumentAccessStatus; +} + +export function getTrustCenterDocumentAccessInfo( + docAccess: TrustCenterDocumentAccess, + __: (key: string) => string +): TrustCenterDocumentAccessInfo { + if (docAccess.document) { + return { + variant: "info" as const, + name: docAccess.document.title, + type: "document", + typeLabel: __("Document"), + category: docAccess.document.documentType, + id: docAccess.document.id, + requested: docAccess.requested, + active: docAccess.active, + status: docAccess.status, + }; + } + if (docAccess.report) { + return { + variant: "success" as const, + name: docAccess.report.filename, + type: "report", + typeLabel: __("Report"), + category: docAccess.report.audit?.framework.name ?? "", + id: docAccess.report.id, + requested: docAccess.requested, + active: docAccess.active, + status: docAccess.status, + }; + } + if (docAccess.trustCenterFile) { + return { + variant: "highlight" as const, + name: docAccess.trustCenterFile.name, + type: "file", + typeLabel: __("File"), + category: docAccess.trustCenterFile.category, + id: docAccess.trustCenterFile.id, + requested: docAccess.requested, + active: docAccess.active, + status: docAccess.status, + }; + } + + throw new Error("Unknown trust center access document type"); +} diff --git a/packages/ui/src/Atoms/Button/Button.tsx b/packages/ui/src/Atoms/Button/Button.tsx index 702aabc07..58c4c183b 100644 --- a/packages/ui/src/Atoms/Button/Button.tsx +++ b/packages/ui/src/Atoms/Button/Button.tsx @@ -21,6 +21,7 @@ export const button = tv({ quaternary: "bg-highlight text-txt-primary hover:bg-highlight-hover active:bg-highlight-pressed", danger: "bg-danger-plain text-txt-invert hover:bg-danger-hover shadow-base hover:shadow-hover active:bg-danger-pressed border border-border-danger", + success: "bg-success-plain text-txt-invert hover:text-invert hover:bg-success-hover shadow-base hover:shadow-hover active:bg-success-pressed border border-border-success", }, disabled: { true: "opacity-60 cursor-default", @@ -47,7 +48,8 @@ type Props = PropsWithChildren< | "secondary" | "tertiary" | "quaternary" - | "danger"; + | "danger" + | "success"; to?: string; asChild?: boolean; } & VariantProps diff --git a/packages/ui/src/theme.css b/packages/ui/src/theme.css index a9e90b21c..a5305c2bf 100644 --- a/packages/ui/src/theme.css +++ b/packages/ui/src/theme.css @@ -61,6 +61,7 @@ --color-subtle-hover: #0027000d; --color-highlight-hover: #022a0213; --color-accent-hover: #1e2c1c; + --color-success-hover: #1e2c1c; --color-active-hover: #d7f2b0; --color-danger-hover: #cf393f; @@ -72,6 +73,7 @@ --color-subtle-pressed: #022a0213; --color-highlight-pressed: #02210219; --color-accent-pressed: #293a25; + --color-success-pressed: #293a25; --color-active-pressed: #c9e894; --color-danger-pressed: #c6373c; @@ -197,6 +199,7 @@ --color-highlight: rgba(214, 251, 196, 0.06); --color-active: rgba(239, 254, 226, 0.08); --color-accent: #c4f042; + --color-success-plain: #c4f042; --color-danger: rgba(254, 58, 61, 0.13); --color-danger-plain: rgba(254, 58, 61, 0.13); --color-danger-dark: rgba(255, 89, 95, 0.94); @@ -225,6 +228,7 @@ --color-subtle-hover: rgba(214, 251, 196, 0.06); --color-highlight-hover: rgba(233, 254, 223, 0.1); --color-accent-hover: rgba(183, 255, 50, 0.82); + --color-success-hover: rgba(183, 255, 50, 0.82); --color-active-hover: rgba(233, 254, 223, 0.1); --color-danger-hover: rgba(254, 58, 61, 0.7); @@ -236,6 +240,7 @@ --color-subtle-pressed: rgba(239, 254, 226, 0.08); --color-highlight-pressed: rgba(236, 254, 229, 0.13); --color-accent-pressed: rgba(209, 255, 70, 0.94); + --color-success-pressed: rgba(209, 255, 70, 0.94); --color-active-pressed: rgba(236, 254, 229, 0.13); --color-danger-pressed: rgba(254, 58, 61, 0.5); } diff --git a/pkg/probo/trust_center_access_service.go b/pkg/probo/trust_center_access_service.go index a35c49dab..aecf4c35d 100644 --- a/pkg/probo/trust_center_access_service.go +++ b/pkg/probo/trust_center_access_service.go @@ -40,13 +40,18 @@ type ( Name string } + UpdateTrustCenterDocumentAccessRequest struct { + ID gid.GID + Status coredata.TrustCenterDocumentAccessStatus + } + UpdateTrustCenterAccessRequest struct { - ID gid.GID - Name *string - Active *bool - DocumentIDs []gid.GID - ReportIDs []gid.GID - TrustCenterFileIDs []gid.GID + ID gid.GID + Name *string + Active *bool + DocumentAccesses []UpdateTrustCenterDocumentAccessRequest + ReportAccesses []UpdateTrustCenterDocumentAccessRequest + TrustCenterFileAccesses []UpdateTrustCenterDocumentAccessRequest } TrustCenterAccessData struct { @@ -70,15 +75,15 @@ func (utcar *UpdateTrustCenterAccessRequest) Validate() error { v.Check(utcar.ID, "id", validator.Required(), validator.GID(coredata.TrustCenterAccessEntityType)) v.Check(utcar.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength)) - v.CheckEach(utcar.DocumentIDs, "document_ids", func(index int, item any) { - v.Check(item, fmt.Sprintf("document_ids[%d]", index), validator.Required(), validator.GID(coredata.DocumentEntityType)) - }) - v.CheckEach(utcar.ReportIDs, "report_ids", func(index int, item any) { - v.Check(item, fmt.Sprintf("report_ids[%d]", index), validator.Required(), validator.GID(coredata.ReportEntityType)) - }) - v.CheckEach(utcar.TrustCenterFileIDs, "trust_center_file_ids", func(index int, item any) { - v.Check(item, fmt.Sprintf("trust_center_file_ids[%d]", index), validator.Required(), validator.GID(coredata.TrustCenterFileEntityType)) - }) + for i, docAccess := range utcar.DocumentAccesses { + v.Check(docAccess, fmt.Sprintf("documentAccesses[%d].ID", i), validator.Required(), validator.GID(coredata.DocumentEntityType)) + } + for i, reportAccess := range utcar.ReportAccesses { + v.Check(reportAccess, fmt.Sprintf("reportAccesses[%d].ID", i), validator.Required(), validator.GID(coredata.ReportEntityType)) + } + for i, reportAccess := range utcar.TrustCenterFileAccesses { + v.Check(reportAccess, fmt.Sprintf("trustCenterFileAccesses[%d].ID", i), validator.Required(), validator.GID(coredata.TrustCenterFileEntityType)) + } return v.Error() } diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 98cc96125..c5ffcd07f 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -3189,13 +3189,18 @@ input CreateTrustCenterAccessInput { active: Boolean! } +input TrustCenterDocumentAccessInput { + id: ID! + status: TrustCenterDocumentAccessStatus! +} + input UpdateTrustCenterAccessInput { id: ID! name: String active: Boolean - documentIds: [ID!] - reportIds: [ID!] - trustCenterFileIds: [ID!] + documents: [TrustCenterDocumentAccessInput!] + reports: [TrustCenterDocumentAccessInput!] + trustCenterFiles: [TrustCenterDocumentAccessInput!] } input DeleteTrustCenterAccessInput { diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index d068a786d..964faafb2 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -9414,6 +9414,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputSnapshotOrder, ec.unmarshalInputTaskOrder, ec.unmarshalInputTrustCenterAccessOrder, + ec.unmarshalInputTrustCenterDocumentAccessInput, ec.unmarshalInputTrustCenterDocumentAccessOrder, ec.unmarshalInputTrustCenterFileOrder, ec.unmarshalInputTrustCenterReferenceOrder, @@ -12749,13 +12750,18 @@ input CreateTrustCenterAccessInput { active: Boolean! } +input TrustCenterDocumentAccessInput { + id: ID! + status: TrustCenterDocumentAccessStatus! +} + input UpdateTrustCenterAccessInput { id: ID! name: String active: Boolean - documentIds: [ID!] - reportIds: [ID!] - trustCenterFileIds: [ID!] + documents: [TrustCenterDocumentAccessInput!] + reports: [TrustCenterDocumentAccessInput!] + trustCenterFiles: [TrustCenterDocumentAccessInput!] } input DeleteTrustCenterAccessInput { @@ -61747,6 +61753,40 @@ func (ec *executionContext) unmarshalInputTrustCenterAccessOrder(ctx context.Con return it, nil } +func (ec *executionContext) unmarshalInputTrustCenterDocumentAccessInput(ctx context.Context, obj any) (types.TrustCenterDocumentAccessInput, error) { + var it types.TrustCenterDocumentAccessInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id", "status"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.ID = data + case "status": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("status")) + data, err := ec.unmarshalNTrustCenterDocumentAccessStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐTrustCenterDocumentAccessStatus(ctx, v) + if err != nil { + return it, err + } + it.Status = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputTrustCenterDocumentAccessOrder(ctx context.Context, obj any) (types.OrderBy[coredata.TrustCenterDocumentAccessOrderField], error) { var it types.OrderBy[coredata.TrustCenterDocumentAccessOrderField] asMap := map[string]any{} @@ -63291,7 +63331,7 @@ func (ec *executionContext) unmarshalInputUpdateTrustCenterAccessInput(ctx conte asMap[k] = v } - fieldsInOrder := [...]string{"id", "name", "active", "documentIds", "reportIds", "trustCenterFileIds"} + fieldsInOrder := [...]string{"id", "name", "active", "documents", "reports", "trustCenterFiles"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -63319,27 +63359,27 @@ func (ec *executionContext) unmarshalInputUpdateTrustCenterAccessInput(ctx conte return it, err } it.Active = data - case "documentIds": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("documentIds")) - data, err := ec.unmarshalOID2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, v) + case "documents": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("documents")) + data, err := ec.unmarshalOTrustCenterDocumentAccessInput2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐTrustCenterDocumentAccessInputᚄ(ctx, v) if err != nil { return it, err } - it.DocumentIds = data - case "reportIds": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reportIds")) - data, err := ec.unmarshalOID2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, v) + it.Documents = data + case "reports": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reports")) + data, err := ec.unmarshalOTrustCenterDocumentAccessInput2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐTrustCenterDocumentAccessInputᚄ(ctx, v) if err != nil { return it, err } - it.ReportIds = data - case "trustCenterFileIds": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("trustCenterFileIds")) - data, err := ec.unmarshalOID2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, v) + it.Reports = data + case "trustCenterFiles": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("trustCenterFiles")) + data, err := ec.unmarshalOTrustCenterDocumentAccessInput2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐTrustCenterDocumentAccessInputᚄ(ctx, v) if err != nil { return it, err } - it.TrustCenterFileIds = data + it.TrustCenterFiles = data } } @@ -89828,6 +89868,11 @@ func (ec *executionContext) marshalNTrustCenterDocumentAccessEdge2ᚖgoᚗprobo return ec._TrustCenterDocumentAccessEdge(ctx, sel, v) } +func (ec *executionContext) unmarshalNTrustCenterDocumentAccessInput2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐTrustCenterDocumentAccessInput(ctx context.Context, v any) (*types.TrustCenterDocumentAccessInput, error) { + res, err := ec.unmarshalInputTrustCenterDocumentAccessInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNTrustCenterDocumentAccessOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐTrustCenterDocumentAccessOrderField(ctx context.Context, v any) (coredata.TrustCenterDocumentAccessOrderField, error) { tmp, err := graphql.UnmarshalString(v) res := unmarshalNTrustCenterDocumentAccessOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐTrustCenterDocumentAccessOrderField[tmp] @@ -93382,6 +93427,24 @@ func (ec *executionContext) unmarshalOTrustCenterAccessOrder2ᚖgoᚗproboᚗinc return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOTrustCenterDocumentAccessInput2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐTrustCenterDocumentAccessInputᚄ(ctx context.Context, v any) ([]*types.TrustCenterDocumentAccessInput, error) { + if v == nil { + return nil, nil + } + var vSlice []any + vSlice = graphql.CoerceList(v) + var err error + res := make([]*types.TrustCenterDocumentAccessInput, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNTrustCenterDocumentAccessInput2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐTrustCenterDocumentAccessInput(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + func (ec *executionContext) unmarshalOTrustCenterDocumentAccessOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐOrderBy(ctx context.Context, v any) (*types.OrderBy[coredata.TrustCenterDocumentAccessOrderField], error) { if v == nil { return nil, nil diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index 59e71c378..18feb593c 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -1807,6 +1807,11 @@ type TrustCenterDocumentAccessEdge struct { Node *TrustCenterDocumentAccess `json:"node"` } +type TrustCenterDocumentAccessInput struct { + ID gid.GID `json:"id"` + Status coredata.TrustCenterDocumentAccessStatus `json:"status"` +} + type TrustCenterEdge struct { Cursor page.CursorKey `json:"cursor"` Node *TrustCenter `json:"node"` @@ -2140,12 +2145,12 @@ type UpdateTaskPayload struct { } type UpdateTrustCenterAccessInput struct { - ID gid.GID `json:"id"` - Name *string `json:"name,omitempty"` - Active *bool `json:"active,omitempty"` - DocumentIds []gid.GID `json:"documentIds,omitempty"` - ReportIds []gid.GID `json:"reportIds,omitempty"` - TrustCenterFileIds []gid.GID `json:"trustCenterFileIds,omitempty"` + ID gid.GID `json:"id"` + Name *string `json:"name,omitempty"` + Active *bool `json:"active,omitempty"` + Documents []*TrustCenterDocumentAccessInput `json:"documents,omitempty"` + Reports []*TrustCenterDocumentAccessInput `json:"reports,omitempty"` + TrustCenterFiles []*TrustCenterDocumentAccessInput `json:"trustCenterFiles,omitempty"` } type UpdateTrustCenterAccessPayload struct { diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 2ba60db71..e4b75f147 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -1661,13 +1661,34 @@ func (r *mutationResolver) UpdateTrustCenterAccess(ctx context.Context, input ty prb := r.ProboService(ctx, input.ID.TenantID()) + var documentAccesses []probo.UpdateTrustCenterDocumentAccessRequest + var reportAccesses []probo.UpdateTrustCenterDocumentAccessRequest + var fileAccesses []probo.UpdateTrustCenterDocumentAccessRequest + for _, documentAccess := range documentAccesses { + documentAccesses = append(documentAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ + ID: documentAccess.ID, + Status: documentAccess.Status, + }) + } + for _, reportAccess := range reportAccesses { + reportAccesses = append(reportAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ + ID: reportAccess.ID, + Status: reportAccess.Status, + }) + } + for _, fileAccess := range fileAccesses { + fileAccesses = append(fileAccesses, probo.UpdateTrustCenterDocumentAccessRequest{ + ID: fileAccess.ID, + Status: fileAccess.Status, + }) + } access, err := prb.TrustCenterAccesses.Update(ctx, &probo.UpdateTrustCenterAccessRequest{ - ID: input.ID, - Name: input.Name, - Active: input.Active, - DocumentIDs: input.DocumentIds, - ReportIDs: input.ReportIds, - TrustCenterFileIDs: input.TrustCenterFileIds, + ID: input.ID, + Name: input.Name, + Active: input.Active, + DocumentAccesses: documentAccesses, + ReportAccesses: reportAccesses, + TrustCenterFileAccesses: fileAccesses, }) if err != nil { panic(fmt.Errorf("cannot update trust center access: %w", err))