From c7cabf23deca27e3048804c17c66fc3594d482cc Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Wed, 22 Oct 2025 16:23:30 +0200 Subject: [PATCH] Add document classification Signed-off-by: Bryan Frimin --- .gitattributes | 2 +- .../form/DocumentClassificationOptions.tsx | 20 ++ .../src/hooks/forms/useDocumentForm.tsx | 2 + .../DocumentGraphListQuery.graphql.ts | 13 +- .../DocumentGraphNodeQuery.graphql.ts | 103 ++++--- ...nterDocumentGraphUpdateMutation.graphql.ts | 4 +- .../documents/DocumentDetailPage.tsx | 70 ++++- .../organizations/documents/DocumentsPage.tsx | 14 +- ...umentDetailPageDocumentFragment.graphql.ts | 82 +++-- ...cumentDetailPagePublishMutation.graphql.ts | 13 +- .../DocumentDetailPageRowFragment.graphql.ts | 13 +- ...ocumentDetailPageUpdateMutation.graphql.ts | 18 +- .../DocumentsListQuery.graphql.ts | 13 +- .../DocumentsPageRowFragment.graphql.ts | 13 +- .../dialogs/CreateDocumentDialog.tsx | 15 + .../CreateDocumentDialogMutation.graphql.ts | 15 +- .../PublishDocumentsDialogMutation.graphql.ts | 13 +- packages/helpers/src/documents.ts | 20 ++ packages/helpers/src/index.ts | 7 +- .../Badge/DocumentClassificationBadge.tsx | 33 ++ packages/ui/src/index.ts | 1 + pkg/coredata/document.go | 31 +- pkg/coredata/document_classification.go | 49 +++ pkg/coredata/document_version.go | 33 +- pkg/coredata/migrations/20251018T081657Z.sql | 12 + pkg/probo/document_service.go | 82 +++-- pkg/server/api/console/v1/schema.graphql | 46 ++- pkg/server/api/console/v1/schema/schema.go | 286 ++++++++++++++++-- pkg/server/api/console/v1/types/document.go | 1 + .../api/console/v1/types/document_version.go | 19 +- pkg/server/api/console/v1/types/types.go | 64 ++-- pkg/server/api/console/v1/v1_resolver.go | 13 +- 32 files changed, 891 insertions(+), 229 deletions(-) create mode 100644 apps/console/src/components/form/DocumentClassificationOptions.tsx create mode 100644 packages/ui/src/Molecules/Badge/DocumentClassificationBadge.tsx create mode 100644 pkg/coredata/document_classification.go create mode 100644 pkg/coredata/migrations/20251018T081657Z.sql diff --git a/.gitattributes b/.gitattributes index e89696568..ccc5c1871 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -pkg/api/console/v1/v1_resolver.go linguist-generated=false +pkg/server/api/console/v1/v1_resolver.go linguist-generated=false diff --git a/apps/console/src/components/form/DocumentClassificationOptions.tsx b/apps/console/src/components/form/DocumentClassificationOptions.tsx new file mode 100644 index 000000000..c6741b77b --- /dev/null +++ b/apps/console/src/components/form/DocumentClassificationOptions.tsx @@ -0,0 +1,20 @@ +import { useTranslate } from "@probo/i18n"; +import { Option } from "@probo/ui"; +import { + documentClassifications, + getDocumentClassificationLabel, +} from "@probo/helpers"; + +export function DocumentClassificationOptions() { + const { __ } = useTranslate(); + + return ( + <> + {documentClassifications.map((classification) => ( + + ))} + + ); +} diff --git a/apps/console/src/hooks/forms/useDocumentForm.tsx b/apps/console/src/hooks/forms/useDocumentForm.tsx index fd51a831e..82e7ab2e6 100644 --- a/apps/console/src/hooks/forms/useDocumentForm.tsx +++ b/apps/console/src/hooks/forms/useDocumentForm.tsx @@ -6,12 +6,14 @@ export const documentSchema = z.object({ content: z.string(), ownerId: z.string(), documentType: z.enum(["OTHER", "ISMS", "POLICY", "PROCEDURE"]), + classification: z.enum(["PUBLIC", "INTERNAL", "CONFIDENTIAL", "SECRET"]), }); export const useDocumentForm = () => { return useFormWithSchema(documentSchema, { defaultValues: { documentType: "POLICY", + classification: "INTERNAL", }, }); }; diff --git a/apps/console/src/hooks/graph/__generated__/DocumentGraphListQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/DocumentGraphListQuery.graphql.ts index 2c959f177..cc3a074b8 100644 --- a/apps/console/src/hooks/graph/__generated__/DocumentGraphListQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/DocumentGraphListQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<80c5f44dbb42b6501cd63d626d52518c>> + * @generated SignedSource<<42c3f270cfade27e94b4d0895a0641f5>> * @lightSyntaxTransform * @nogrep */ @@ -161,6 +161,13 @@ return { "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null + }, { "alias": null, "args": null, @@ -377,12 +384,12 @@ return { ] }, "params": { - "cacheID": "e3e170d0a40aa1d0d4d07d9d414704ce", + "cacheID": "74570a2b122efea617c7e2559fb51053", "id": null, "metadata": {}, "name": "DocumentGraphListQuery", "operationKind": "query", - "text": "query DocumentGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...DocumentsPageListFragment\n }\n}\n\nfragment DocumentsPageListFragment on Organization {\n documents(first: 50, orderBy: {field: TITLE, direction: ASC}) {\n edges {\n node {\n id\n ...DocumentsPageRowFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" + "text": "query DocumentGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...DocumentsPageListFragment\n }\n}\n\nfragment DocumentsPageListFragment on Organization {\n documents(first: 50, orderBy: {field: TITLE, direction: ASC}) {\n edges {\n node {\n id\n ...DocumentsPageRowFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n classification\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/hooks/graph/__generated__/DocumentGraphNodeQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/DocumentGraphNodeQuery.graphql.ts index d2e0045af..d1e899dae 100644 --- a/apps/console/src/hooks/graph/__generated__/DocumentGraphNodeQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/DocumentGraphNodeQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -53,48 +53,68 @@ v3 = { "storageKey": null }, v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null +}, +v5 = { "alias": null, "args": null, "kind": "ScalarField", "name": "fullName", "storageKey": null }, -v5 = [ +v6 = { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v3/*: any*/), + (v5/*: any*/) + ], + "storageKey": null +}, +v7 = [ { "kind": "Literal", "name": "first", "value": 20 } ], -v6 = { +v8 = { "alias": null, "args": null, "kind": "ScalarField", "name": "name", "storageKey": null }, -v7 = { +v9 = { "alias": null, "args": null, "kind": "ScalarField", "name": "cursor", "storageKey": null }, -v8 = { +v10 = { "alias": null, "args": null, "kind": "ScalarField", "name": "endCursor", "storageKey": null }, -v9 = { +v11 = { "alias": null, "args": null, "kind": "ScalarField", "name": "hasNextPage", "storageKey": null }, -v10 = { +v12 = { "kind": "ClientExtension", "selections": [ { @@ -106,14 +126,14 @@ v10 = { } ] }, -v11 = [ +v13 = [ { "kind": "Literal", "name": "first", "value": 1000 } ], -v12 = { +v14 = { "alias": null, "args": null, "concreteType": "PageInfo", @@ -121,8 +141,8 @@ v12 = { "name": "pageInfo", "plural": false, "selections": [ - (v8/*: any*/), - (v9/*: any*/) + (v10/*: any*/), + (v11/*: any*/) ], "storageKey": null }; @@ -193,22 +213,11 @@ return { "name": "documentType", "storageKey": null }, + (v4/*: any*/), + (v6/*: any*/), { "alias": null, - "args": null, - "concreteType": "People", - "kind": "LinkedField", - "name": "owner", - "plural": false, - "selections": [ - (v3/*: any*/), - (v4/*: any*/) - ], - "storageKey": null - }, - { - "alias": null, - "args": (v5/*: any*/), + "args": (v7/*: any*/), "concreteType": "ControlConnection", "kind": "LinkedField", "name": "controls", @@ -231,7 +240,7 @@ return { "plural": false, "selections": [ (v3/*: any*/), - (v6/*: any*/), + (v8/*: any*/), { "alias": null, "args": null, @@ -248,7 +257,7 @@ return { "plural": false, "selections": [ (v3/*: any*/), - (v6/*: any*/) + (v8/*: any*/) ], "storageKey": null }, @@ -256,7 +265,7 @@ return { ], "storageKey": null }, - (v7/*: any*/) + (v9/*: any*/) ], "storageKey": null }, @@ -268,8 +277,8 @@ return { "name": "pageInfo", "plural": false, "selections": [ - (v8/*: any*/), - (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), { "alias": null, "args": null, @@ -287,13 +296,13 @@ return { ], "storageKey": null }, - (v10/*: any*/) + (v12/*: any*/) ], "storageKey": "controls(first:20)" }, { "alias": null, - "args": (v5/*: any*/), + "args": (v7/*: any*/), "filters": [ "orderBy", "filter" @@ -329,7 +338,7 @@ return { }, { "alias": null, - "args": (v5/*: any*/), + "args": (v7/*: any*/), "concreteType": "DocumentVersionConnection", "kind": "LinkedField", "name": "versions", @@ -387,9 +396,11 @@ return { "name": "updatedAt", "storageKey": null }, + (v4/*: any*/), + (v6/*: any*/), { "alias": null, - "args": (v11/*: any*/), + "args": (v13/*: any*/), "concreteType": "DocumentVersionSignatureConnection", "kind": "LinkedField", "name": "signatures", @@ -428,7 +439,7 @@ return { "plural": false, "selections": [ (v3/*: any*/), - (v4/*: any*/), + (v5/*: any*/), { "alias": null, "args": null, @@ -457,18 +468,18 @@ return { ], "storageKey": null }, - (v7/*: any*/) + (v9/*: any*/) ], "storageKey": null }, - (v12/*: any*/), - (v10/*: any*/) + (v14/*: any*/), + (v12/*: any*/) ], "storageKey": "signatures(first:1000)" }, { "alias": null, - "args": (v11/*: any*/), + "args": (v13/*: any*/), "filters": [ "filter" ], @@ -479,7 +490,7 @@ return { }, { "alias": null, - "args": (v11/*: any*/), + "args": (v13/*: any*/), "filters": [], "handle": "connection", "key": "DocumentDetailPage_signatures", @@ -490,18 +501,18 @@ return { ], "storageKey": null }, - (v7/*: any*/) + (v9/*: any*/) ], "storageKey": null }, - (v12/*: any*/), - (v10/*: any*/) + (v14/*: any*/), + (v12/*: any*/) ], "storageKey": "versions(first:20)" }, { "alias": null, - "args": (v5/*: any*/), + "args": (v7/*: any*/), "filters": null, "handle": "connection", "key": "DocumentDetailPage_versions", @@ -518,12 +529,12 @@ return { ] }, "params": { - "cacheID": "4dd1a6c2a9117deb789984766f9c3b36", + "cacheID": "72db9b5c4f2a4cd076ebf4e45f0eb662", "id": null, "metadata": {}, "name": "DocumentGraphNodeQuery", "operationKind": "query", - "text": "query DocumentGraphNodeQuery(\n $documentId: ID!\n) {\n node(id: $documentId) {\n __typename\n ... on Document {\n ...DocumentDetailPageDocumentFragment\n }\n id\n }\n}\n\nfragment DocumentControlsTabFragment on Document {\n id\n controls(first: 20) {\n edges {\n node {\n id\n ...LinkedControlsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment DocumentDetailPageDocumentFragment on Document {\n id\n title\n documentType\n owner {\n id\n fullName\n }\n ...DocumentControlsTabFragment\n controlsInfo: controls(first: 0) {\n totalCount\n }\n versions(first: 20) {\n edges {\n node {\n id\n content\n status\n publishedAt\n version\n updatedAt\n ...DocumentSignaturesTab_version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n signedBy {\n id\n }\n ...DocumentSignaturesTab_signature\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment DocumentSignaturesTab_signature on DocumentVersionSignature {\n id\n state\n signedAt\n requestedAt\n signedBy {\n fullName\n primaryEmailAddress\n id\n }\n}\n\nfragment DocumentSignaturesTab_version on DocumentVersion {\n id\n status\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n signedBy {\n id\n fullName\n primaryEmailAddress\n }\n ...DocumentSignaturesTab_signature\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment LinkedControlsCardFragment on Control {\n id\n name\n sectionTitle\n framework {\n id\n name\n }\n}\n" + "text": "query DocumentGraphNodeQuery(\n $documentId: ID!\n) {\n node(id: $documentId) {\n __typename\n ... on Document {\n ...DocumentDetailPageDocumentFragment\n }\n id\n }\n}\n\nfragment DocumentControlsTabFragment on Document {\n id\n controls(first: 20) {\n edges {\n node {\n id\n ...LinkedControlsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment DocumentDetailPageDocumentFragment on Document {\n id\n title\n documentType\n classification\n owner {\n id\n fullName\n }\n ...DocumentControlsTabFragment\n controlsInfo: controls(first: 0) {\n totalCount\n }\n versions(first: 20) {\n edges {\n node {\n id\n content\n status\n publishedAt\n version\n updatedAt\n classification\n owner {\n id\n fullName\n }\n ...DocumentSignaturesTab_version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n signedBy {\n id\n }\n ...DocumentSignaturesTab_signature\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment DocumentSignaturesTab_signature on DocumentVersionSignature {\n id\n state\n signedAt\n requestedAt\n signedBy {\n fullName\n primaryEmailAddress\n id\n }\n}\n\nfragment DocumentSignaturesTab_version on DocumentVersion {\n id\n status\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n signedBy {\n id\n fullName\n primaryEmailAddress\n }\n ...DocumentSignaturesTab_signature\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment LinkedControlsCardFragment on Control {\n id\n name\n sectionTitle\n framework {\n id\n name\n }\n}\n" } }; })(); diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterDocumentGraphUpdateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterDocumentGraphUpdateMutation.graphql.ts index 2cd199bcc..4b456b7b0 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterDocumentGraphUpdateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterDocumentGraphUpdateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<18b931f119e8ef68f016dfcdf76366cc>> + * @generated SignedSource<<383c2d6a9d25911bb5df8c148a8a3e51>> * @lightSyntaxTransform * @nogrep */ @@ -10,9 +10,11 @@ import { ConcreteRequest } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; +export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "SECRET"; export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type TrustCenterVisibility = "NONE" | "PRIVATE" | "PUBLIC"; export type UpdateDocumentInput = { + classification?: DocumentClassification | null | undefined; content?: string | null | undefined; documentType?: DocumentType | null | undefined; id: string; diff --git a/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx b/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx index 468a79510..3e5966367 100644 --- a/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx +++ b/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx @@ -49,8 +49,10 @@ import { useOrganizationId } from "/hooks/useOrganizationId"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import { getDocumentTypeLabel, + getDocumentClassificationLabel, sprintf, documentTypes, + documentClassifications, formatDate, } from "@probo/helpers"; import { @@ -71,6 +73,7 @@ import clsx from "clsx"; import { PeopleSelectField } from "/components/form/PeopleSelectField"; import { ControlledField } from "/components/form/ControlledField"; import { DocumentTypeOptions } from "/components/form/DocumentTypeOptions"; +import { DocumentClassificationOptions } from "/components/form/DocumentClassificationOptions"; import { z } from "zod"; import { useFormWithSchema } from "/hooks/useFormWithSchema"; @@ -83,6 +86,7 @@ const documentFragment = graphql` id title documentType + classification owner { id fullName @@ -101,6 +105,11 @@ const documentFragment = graphql` publishedAt version updatedAt + classification + owner { + id + fullName + } ...DocumentSignaturesTab_version signatures(first: 1000) @connection(key: "DocumentDetailPage_signatures", filters: []) { @@ -128,6 +137,7 @@ graphql` title description documentType + classification updatedAt owner { id @@ -182,6 +192,7 @@ const updateDocumentMutation = graphql` id title documentType + classification owner { id fullName @@ -195,6 +206,7 @@ const documentUpdateSchema = z.object({ title: z.string().min(1, "Title is required").max(255), ownerId: z.string().min(1, "Owner is required"), documentType: z.enum(documentTypes), + classification: z.enum(documentClassifications), }); const UserEmailQuery = graphql` @@ -221,6 +233,7 @@ export default function DocumentDetailPage(props: Props) { const [isEditingTitle, setIsEditingTitle] = useState(false); const [isEditingOwner, setIsEditingOwner] = useState(false); const [isEditingType, setIsEditingType] = useState(false); + const [isEditingClassification, setIsEditingClassification] = useState(false); const versions = document.versions.edges.map((edge) => edge.node); const currentVersion = document.versions.edges.find((v) => v.node.id === versionId)?.node ?? @@ -267,8 +280,9 @@ export default function DocumentDetailPage(props: Props) { { defaultValues: { title: document.title, - ownerId: document.owner?.id || "", + ownerId: currentVersion.owner?.id || "", documentType: document.documentType, + classification: currentVersion.classification, }, } ); @@ -325,6 +339,28 @@ export default function DocumentDetailPage(props: Props) { }); }; + const handleUpdateClassification = (data: { + classification: (typeof documentClassifications)[number]; + }) => { + updateDocument({ + variables: { + input: { + id: document.id, + classification: data.classification, + }, + }, + onSuccess: () => { + setIsEditingClassification(false); + loadQuery( + props.queryRef.environment, + documentNodeQuery, + props.queryRef.variables, + { fetchPolicy: "network-only" } + ); + }, + }); + }; + const handlePublish = () => { publishDocumentVersion({ variables: { @@ -610,8 +646,8 @@ export default function DocumentDetailPage(props: Props) { ) : ( setIsEditingOwner(true)}> - - {document.owner?.fullName} + + {currentVersion.owner?.fullName} )} @@ -642,6 +678,34 @@ export default function DocumentDetailPage(props: Props) { )} + + {isEditingClassification ? ( + { + setIsEditingClassification(false); + reset(); + }} + disabled={isUpdatingDocument} + > + + + + + ) : ( + setIsEditingClassification(true)} + > +
+ {getDocumentClassificationLabel(__, currentVersion.classification)} +
+
+ )} +
{__("Type")} + {__("Classification")} {__("Owner")} {__("Last update")} {__("Signatures")} @@ -230,7 +236,7 @@ export default function DocumentsPage(props: Props) { ) : ( - +
{sprintf(__("%s documents selected"), selection.length)} - @@ -330,6 +336,7 @@ const rowFragment = graphql` title description documentType + classification updatedAt owner { id @@ -424,6 +431,9 @@ function DocumentRow({ {getDocumentTypeLabel(__, document.documentType)} + + {getDocumentClassificationLabel(__, document.classification)} +
diff --git a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageDocumentFragment.graphql.ts b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageDocumentFragment.graphql.ts index 3d0b3e246..cbd24bb36 100644 --- a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageDocumentFragment.graphql.ts +++ b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageDocumentFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<3ba79250c6ba07fa44aca6eff28b3d9d>> * @lightSyntaxTransform * @nogrep */ @@ -9,11 +9,13 @@ // @ts-nocheck import { ReaderFragment } from 'relay-runtime'; +export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "SECRET"; export type DocumentStatus = "DRAFT" | "PUBLISHED"; export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type DocumentVersionSignatureState = "REQUESTED" | "SIGNED"; import { FragmentRefs } from "relay-runtime"; export type DocumentDetailPageDocumentFragment$data = { + readonly classification: DocumentClassification; readonly controlsInfo: { readonly totalCount: number; }; @@ -28,8 +30,13 @@ export type DocumentDetailPageDocumentFragment$data = { readonly __id: string; readonly edges: ReadonlyArray<{ readonly node: { + readonly classification: DocumentClassification; readonly content: string; readonly id: string; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; readonly publishedAt: any | null | undefined; readonly signatures: { readonly __id: string; @@ -71,17 +78,43 @@ v1 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "__typename", + "name": "classification", "storageKey": null }, v2 = { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v4 = { "alias": null, "args": null, "kind": "ScalarField", "name": "cursor", "storageKey": null }, -v3 = { +v5 = { "alias": null, "args": null, "concreteType": "PageInfo", @@ -106,7 +139,7 @@ v3 = { ], "storageKey": null }, -v4 = { +v6 = { "kind": "ClientExtension", "selections": [ { @@ -156,25 +189,8 @@ return { "name": "documentType", "storageKey": null }, - { - "alias": null, - "args": null, - "concreteType": "People", - "kind": "LinkedField", - "name": "owner", - "plural": false, - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fullName", - "storageKey": null - } - ], - "storageKey": null - }, + (v1/*: any*/), + (v2/*: any*/), { "args": null, "kind": "FragmentSpread", @@ -264,6 +280,8 @@ return { "name": "updatedAt", "storageKey": null }, + (v1/*: any*/), + (v2/*: any*/), { "args": null, "kind": "FragmentSpread", @@ -318,29 +336,29 @@ return { "kind": "FragmentSpread", "name": "DocumentSignaturesTab_signature" }, - (v1/*: any*/) + (v3/*: any*/) ], "storageKey": null }, - (v2/*: any*/) + (v4/*: any*/) ], "storageKey": null }, - (v3/*: any*/), - (v4/*: any*/) + (v5/*: any*/), + (v6/*: any*/) ], "storageKey": null }, - (v1/*: any*/) + (v3/*: any*/) ], "storageKey": null }, - (v2/*: any*/) + (v4/*: any*/) ], "storageKey": null }, - (v3/*: any*/), - (v4/*: any*/) + (v5/*: any*/), + (v6/*: any*/) ], "storageKey": null } @@ -350,6 +368,6 @@ return { }; })(); -(node as any).hash = "f1a7ec789a05b74cade1d3024119a844"; +(node as any).hash = "e1418d5efeade64db32e9939132c03de"; export default node; diff --git a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPagePublishMutation.graphql.ts b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPagePublishMutation.graphql.ts index efd18d894..af7395d6b 100644 --- a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPagePublishMutation.graphql.ts +++ b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPagePublishMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<80564e817a10279d3bcbed4ad0ce8e27>> * @lightSyntaxTransform * @nogrep */ @@ -135,6 +135,13 @@ return { "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null + }, { "alias": null, "args": null, @@ -264,12 +271,12 @@ return { ] }, "params": { - "cacheID": "e7734d4aece1b73c9bd85eddfbda2548", + "cacheID": "abf83afc77c45fa28de8d3c711633ec3", "id": null, "metadata": {}, "name": "DocumentDetailPagePublishMutation", "operationKind": "mutation", - "text": "mutation DocumentDetailPagePublishMutation(\n $input: PublishDocumentVersionInput!\n) {\n publishDocumentVersion(input: $input) {\n document {\n id\n ...DocumentDetailPageRowFragment\n }\n }\n}\n\nfragment DocumentDetailPageRowFragment on Document {\n id\n title\n description\n documentType\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" + "text": "mutation DocumentDetailPagePublishMutation(\n $input: PublishDocumentVersionInput!\n) {\n publishDocumentVersion(input: $input) {\n document {\n id\n ...DocumentDetailPageRowFragment\n }\n }\n}\n\nfragment DocumentDetailPageRowFragment on Document {\n id\n title\n description\n documentType\n classification\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageRowFragment.graphql.ts b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageRowFragment.graphql.ts index 104d0abee..9659b1d3d 100644 --- a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageRowFragment.graphql.ts +++ b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageRowFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<07388b39e4109439464531d46a890841>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -9,11 +9,13 @@ // @ts-nocheck import { ReaderFragment } from 'relay-runtime'; +export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "SECRET"; export type DocumentStatus = "DRAFT" | "PUBLISHED"; export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type DocumentVersionSignatureState = "REQUESTED" | "SIGNED"; import { FragmentRefs } from "relay-runtime"; export type DocumentDetailPageRowFragment$data = { + readonly classification: DocumentClassification; readonly description: string; readonly documentType: DocumentType; readonly id: string; @@ -82,6 +84,13 @@ return { "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null + }, { "alias": null, "args": null, @@ -208,6 +217,6 @@ return { }; })(); -(node as any).hash = "2144e456e031960d8ceefd334741831e"; +(node as any).hash = "405e3b543fa517a3754d74b4f8c03d46"; export default node; diff --git a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageUpdateMutation.graphql.ts b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageUpdateMutation.graphql.ts index 3eadcd45a..b64d514d4 100644 --- a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageUpdateMutation.graphql.ts +++ b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageUpdateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<575a6c27726fb3e5457989b0d5a4edc2>> + * @generated SignedSource<<7c1bfb7a8cc7cc0f426348c57a17f7c9>> * @lightSyntaxTransform * @nogrep */ @@ -9,9 +9,11 @@ // @ts-nocheck import { ConcreteRequest } from 'relay-runtime'; +export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "SECRET"; export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type TrustCenterVisibility = "NONE" | "PRIVATE" | "PUBLIC"; export type UpdateDocumentInput = { + classification?: DocumentClassification | null | undefined; content?: string | null | undefined; documentType?: DocumentType | null | undefined; id: string; @@ -25,6 +27,7 @@ export type DocumentDetailPageUpdateMutation$variables = { export type DocumentDetailPageUpdateMutation$data = { readonly updateDocument: { readonly document: { + readonly classification: DocumentClassification; readonly documentType: DocumentType; readonly id: string; readonly owner: { @@ -93,6 +96,13 @@ v2 = [ "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null + }, { "alias": null, "args": null, @@ -137,16 +147,16 @@ return { "selections": (v2/*: any*/) }, "params": { - "cacheID": "23268b13b11a1a56164dcc4cb96e24b3", + "cacheID": "865056a65394e413822f798f31351341", "id": null, "metadata": {}, "name": "DocumentDetailPageUpdateMutation", "operationKind": "mutation", - "text": "mutation DocumentDetailPageUpdateMutation(\n $input: UpdateDocumentInput!\n) {\n updateDocument(input: $input) {\n document {\n id\n title\n documentType\n owner {\n id\n fullName\n }\n }\n }\n}\n" + "text": "mutation DocumentDetailPageUpdateMutation(\n $input: UpdateDocumentInput!\n) {\n updateDocument(input: $input) {\n document {\n id\n title\n documentType\n classification\n owner {\n id\n fullName\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "507a2bd40b923d2064fdd864f16f29aa"; +(node as any).hash = "bd6acddfc137a6bdce42628d05dc9ff6"; export default node; diff --git a/apps/console/src/pages/organizations/documents/__generated__/DocumentsListQuery.graphql.ts b/apps/console/src/pages/organizations/documents/__generated__/DocumentsListQuery.graphql.ts index e40d28b78..eadd8c8a9 100644 --- a/apps/console/src/pages/organizations/documents/__generated__/DocumentsListQuery.graphql.ts +++ b/apps/console/src/pages/organizations/documents/__generated__/DocumentsListQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<739800753f29986463be3d9e4316a44a>> + * @generated SignedSource<<83dad18be7a9249bb99048ba14b6de41>> * @lightSyntaxTransform * @nogrep */ @@ -236,6 +236,13 @@ return { "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null + }, { "alias": null, "args": null, @@ -452,12 +459,12 @@ return { ] }, "params": { - "cacheID": "ce2facebbd111ac1b3398933a5a06a05", + "cacheID": "2f5cd9919b919f4e9d80354cb26d3e9d", "id": null, "metadata": {}, "name": "DocumentsListQuery", "operationKind": "query", - "text": "query DocumentsListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: DocumentOrder = {field: TITLE, direction: ASC}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...DocumentsPageListFragment_16fISc\n id\n }\n}\n\nfragment DocumentsPageListFragment_16fISc on Organization {\n documents(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n ...DocumentsPageRowFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" + "text": "query DocumentsListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: DocumentOrder = {field: TITLE, direction: ASC}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...DocumentsPageListFragment_16fISc\n id\n }\n}\n\nfragment DocumentsPageListFragment_16fISc on Organization {\n documents(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n ...DocumentsPageRowFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n classification\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/pages/organizations/documents/__generated__/DocumentsPageRowFragment.graphql.ts b/apps/console/src/pages/organizations/documents/__generated__/DocumentsPageRowFragment.graphql.ts index aec2f6fd9..8e6094f5f 100644 --- a/apps/console/src/pages/organizations/documents/__generated__/DocumentsPageRowFragment.graphql.ts +++ b/apps/console/src/pages/organizations/documents/__generated__/DocumentsPageRowFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<96486371decaf730cf0a559125086bd8>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -9,11 +9,13 @@ // @ts-nocheck import { ReaderFragment } from 'relay-runtime'; +export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "SECRET"; export type DocumentStatus = "DRAFT" | "PUBLISHED"; export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type DocumentVersionSignatureState = "REQUESTED" | "SIGNED"; import { FragmentRefs } from "relay-runtime"; export type DocumentsPageRowFragment$data = { + readonly classification: DocumentClassification; readonly description: string; readonly documentType: DocumentType; readonly id: string; @@ -83,6 +85,13 @@ return { "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null + }, { "alias": null, "args": null, @@ -216,6 +225,6 @@ return { }; })(); -(node as any).hash = "4151906ef10da152d053c46af6b9f890"; +(node as any).hash = "cd6d6bebffb9ed17a251aeef4b86f753"; export default node; diff --git a/apps/console/src/pages/organizations/documents/dialogs/CreateDocumentDialog.tsx b/apps/console/src/pages/organizations/documents/dialogs/CreateDocumentDialog.tsx index 9532524cd..460c95acf 100644 --- a/apps/console/src/pages/organizations/documents/dialogs/CreateDocumentDialog.tsx +++ b/apps/console/src/pages/organizations/documents/dialogs/CreateDocumentDialog.tsx @@ -21,6 +21,7 @@ import { PeopleSelectField } from "/components/form/PeopleSelectField"; import type { CreateDocumentDialogMutation } from "./__generated__/CreateDocumentDialogMutation.graphql"; import { ControlledField } from "/components/form/ControlledField"; import { DocumentTypeOptions } from "/components/form/DocumentTypeOptions"; +import { DocumentClassificationOptions } from "/components/form/DocumentClassificationOptions"; type Props = { trigger?: ReactNode; @@ -125,6 +126,20 @@ export function CreateDocumentDialog({ trigger, connection }: Props) { + + + + + + > + * @generated SignedSource<<8f842e1d6a4c314a9bfba43097ac2795>> * @lightSyntaxTransform * @nogrep */ @@ -10,9 +10,11 @@ import { ConcreteRequest } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; +export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "SECRET"; export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type TrustCenterVisibility = "NONE" | "PRIVATE" | "PUBLIC"; export type CreateDocumentInput = { + classification: DocumentClassification; content: string; documentType: DocumentType; organizationId: string; @@ -172,6 +174,13 @@ return { "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null + }, { "alias": null, "args": null, @@ -327,12 +336,12 @@ return { ] }, "params": { - "cacheID": "0a5f7934a498b696a268943176bdf910", + "cacheID": "319502472cd9600df506f084a7648571", "id": null, "metadata": {}, "name": "CreateDocumentDialogMutation", "operationKind": "mutation", - "text": "mutation CreateDocumentDialogMutation(\n $input: CreateDocumentInput!\n) {\n createDocument(input: $input) {\n documentEdge {\n node {\n id\n ...DocumentsPageRowFragment\n }\n }\n }\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" + "text": "mutation CreateDocumentDialogMutation(\n $input: CreateDocumentInput!\n) {\n createDocument(input: $input) {\n documentEdge {\n node {\n id\n ...DocumentsPageRowFragment\n }\n }\n }\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n classification\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/pages/organizations/documents/dialogs/__generated__/PublishDocumentsDialogMutation.graphql.ts b/apps/console/src/pages/organizations/documents/dialogs/__generated__/PublishDocumentsDialogMutation.graphql.ts index 7ec56ed6a..795fe3c15 100644 --- a/apps/console/src/pages/organizations/documents/dialogs/__generated__/PublishDocumentsDialogMutation.graphql.ts +++ b/apps/console/src/pages/organizations/documents/dialogs/__generated__/PublishDocumentsDialogMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<18ebf050c9c9ec64b126385ad8b8dd7d>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -156,6 +156,13 @@ return { "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "classification", + "storageKey": null + }, { "alias": null, "args": null, @@ -295,12 +302,12 @@ return { ] }, "params": { - "cacheID": "5c79c67dcab25c8ead714ff999b0e7cf", + "cacheID": "d3e85ba061ad3937ad4a3c6727eb76fa", "id": null, "metadata": {}, "name": "PublishDocumentsDialogMutation", "operationKind": "mutation", - "text": "mutation PublishDocumentsDialogMutation(\n $input: BulkPublishDocumentVersionsInput!\n) {\n bulkPublishDocumentVersions(input: $input) {\n documentEdges {\n node {\n id\n ...DocumentsPageRowFragment\n }\n }\n }\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" + "text": "mutation PublishDocumentsDialogMutation(\n $input: BulkPublishDocumentVersionsInput!\n) {\n bulkPublishDocumentVersions(input: $input) {\n documentEdges {\n node {\n id\n ...DocumentsPageRowFragment\n }\n }\n }\n}\n\nfragment DocumentsPageRowFragment on Document {\n id\n title\n description\n documentType\n classification\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n version\n signatures(first: 1000) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/packages/helpers/src/documents.ts b/packages/helpers/src/documents.ts index 5a983d6d5..b997e3f0d 100644 --- a/packages/helpers/src/documents.ts +++ b/packages/helpers/src/documents.ts @@ -14,3 +14,23 @@ export function getDocumentTypeLabel(__: Translator, type: string) { return __("Procedure"); } } + +export const documentClassifications = [ + "PUBLIC", + "INTERNAL", + "CONFIDENTIAL", + "SECRET", +] as const; + +export function getDocumentClassificationLabel(__: Translator, classification: string) { + switch (classification) { + case "PUBLIC": + return __("Public"); + case "INTERNAL": + return __("Internal"); + case "CONFIDENTIAL": + return __("Confidential"); + case "SECRET": + return __("Secret"); + } +} diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts index 05c448ebf..8296aebf3 100644 --- a/packages/helpers/src/index.ts +++ b/packages/helpers/src/index.ts @@ -20,7 +20,12 @@ export { type CountryCode, } from "./countries"; export { availableFrameworks } from "./frameworks"; -export { getDocumentTypeLabel, documentTypes } from "./documents"; +export { + getDocumentTypeLabel, + documentTypes, + getDocumentClassificationLabel, + documentClassifications, +} from "./documents"; export { getAssetTypeVariant } from "./assets"; export { getSnapshotTypeLabel, diff --git a/packages/ui/src/Molecules/Badge/DocumentClassificationBadge.tsx b/packages/ui/src/Molecules/Badge/DocumentClassificationBadge.tsx new file mode 100644 index 000000000..96dac36a9 --- /dev/null +++ b/packages/ui/src/Molecules/Badge/DocumentClassificationBadge.tsx @@ -0,0 +1,33 @@ +import { Badge } from "../../Atoms/Badge/Badge"; +import { getDocumentClassificationLabel } from "@probo/helpers"; +import { useTranslate } from "@probo/i18n"; + +type Props = { + classification: string; +}; + +export function DocumentClassificationBadge({ classification }: Props) { + const { __ } = useTranslate(); + + // Choose badge variant based on classification level + const variant = (() => { + switch (classification) { + case "PUBLIC": + return "success" as const; + case "INTERNAL": + return "neutral" as const; + case "CONFIDENTIAL": + return "warning" as const; + case "SECRET": + return "danger" as const; + default: + return "neutral" as const; + } + })(); + + return ( + + {getDocumentClassificationLabel(__, classification)} + + ); +} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index f672dbc8a..e02edf545 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -66,6 +66,7 @@ export { MeasureBadge } from "./Molecules/Badge/MeasureBadge"; export { MeasureImplementation } from "./Molecules/MeasureImplementation/MeasureImplementation"; export { FrameworkSelector } from "./Molecules/FrameworkSelector/FrameworkSelector"; export { DocumentTypeBadge } from "./Molecules/Badge/DocumentTypeBadge"; +export { DocumentClassificationBadge } from "./Molecules/Badge/DocumentClassificationBadge"; export { SentitivityOptions } from "./Molecules/Select/SentitivityOptions"; export { ImpactOptions } from "./Molecules/Select/ImpactOptions"; export { DurationPicker } from "./Molecules/DurationPicker/DurationPicker"; diff --git a/pkg/coredata/document.go b/pkg/coredata/document.go index 84337be00..c8ff33431 100644 --- a/pkg/coredata/document.go +++ b/pkg/coredata/document.go @@ -28,15 +28,16 @@ import ( type ( Document struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - OwnerID gid.GID `db:"owner_id"` - Title string `db:"title"` - DocumentType DocumentType `db:"document_type"` - CurrentPublishedVersion *int `db:"current_published_version"` - TrustCenterVisibility TrustCenterVisibility `db:"trust_center_visibility"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + OwnerID gid.GID `db:"owner_id"` + Title string `db:"title"` + DocumentType DocumentType `db:"document_type"` + Classification DocumentClassification `db:"classification"` + CurrentPublishedVersion *int `db:"current_published_version"` + TrustCenterVisibility TrustCenterVisibility `db:"trust_center_visibility"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } Documents []*Document @@ -68,6 +69,7 @@ SELECT owner_id, title, document_type, + classification, current_published_version, trust_center_visibility, created_at, @@ -150,6 +152,7 @@ SELECT owner_id, title, document_type, + classification, current_published_version, trust_center_visibility, created_at, @@ -200,6 +203,7 @@ SELECT owner_id, title, document_type, + classification, current_published_version, trust_center_visibility, created_at, @@ -249,6 +253,7 @@ INSERT INTO owner_id, title, document_type, + classification, current_published_version, trust_center_visibility, created_at, @@ -261,6 +266,7 @@ VALUES ( @owner_id, @title, @document_type, + @classification, @current_published_version, @trust_center_visibility, @created_at, @@ -275,6 +281,7 @@ VALUES ( "owner_id": p.OwnerID, "title": p.Title, "document_type": p.DocumentType, + "classification": p.Classification, "current_published_version": p.CurrentPublishedVersion, "trust_center_visibility": p.TrustCenterVisibility, "created_at": p.CreatedAt, @@ -334,6 +341,7 @@ SET current_published_version = @current_published_version, owner_id = @owner_id, document_type = @document_type, + classification = @classification, trust_center_visibility = @trust_center_visibility, updated_at = @updated_at WHERE @@ -350,6 +358,7 @@ WHERE "current_published_version": p.CurrentPublishedVersion, "owner_id": p.OwnerID, "document_type": p.DocumentType, + "classification": p.Classification, "trust_center_visibility": p.TrustCenterVisibility, } maps.Copy(args, scope.SQLArguments()) @@ -427,6 +436,7 @@ WITH plcs AS ( p.owner_id, p.title, p.document_type, + p.classification, p.current_published_version, p.trust_center_visibility, p.created_at, @@ -445,6 +455,7 @@ SELECT owner_id, title, document_type, + classification, current_published_version, trust_center_visibility, created_at, @@ -543,6 +554,7 @@ WITH plcs AS ( p.owner_id, p.title, p.document_type, + p.classification, p.current_published_version, p.trust_center_visibility, p.created_at, @@ -562,6 +574,7 @@ SELECT owner_id, title, document_type, + classification, current_published_version, trust_center_visibility, created_at, diff --git a/pkg/coredata/document_classification.go b/pkg/coredata/document_classification.go new file mode 100644 index 000000000..027c3a1db --- /dev/null +++ b/pkg/coredata/document_classification.go @@ -0,0 +1,49 @@ +package coredata + +import ( + "database/sql/driver" + "fmt" +) + +type DocumentClassification string + +const ( + DocumentClassificationPublic DocumentClassification = "PUBLIC" + DocumentClassificationInternal DocumentClassification = "INTERNAL" + DocumentClassificationConfidential DocumentClassification = "CONFIDENTIAL" + DocumentClassificationSecret DocumentClassification = "SECRET" +) + +func (dc DocumentClassification) String() string { + switch dc { + case DocumentClassificationPublic: + return "PUBLIC" + case DocumentClassificationInternal: + return "INTERNAL" + case DocumentClassificationConfidential: + return "CONFIDENTIAL" + case DocumentClassificationSecret: + return "SECRET" + } + panic(fmt.Errorf("invalid DocumentClassification value: %s", string(dc))) +} + +// Scan implements the sql.Scanner interface for database deserialization. +func (dc *DocumentClassification) Scan(value interface{}) error { + if value == nil { + return nil + } + + sv, ok := value.(string) + if !ok { + return fmt.Errorf("failed to scan DocumentClassification: %v", value) + } + + *dc = DocumentClassification(sv) + return nil +} + +// Value implements the driver.Valuer interface for database serialization. +func (dc DocumentClassification) Value() (driver.Value, error) { + return string(dc), nil +} diff --git a/pkg/coredata/document_version.go b/pkg/coredata/document_version.go index 93fd2a1d2..148ccb095 100644 --- a/pkg/coredata/document_version.go +++ b/pkg/coredata/document_version.go @@ -28,17 +28,18 @@ import ( type ( DocumentVersion struct { - ID gid.GID `db:"id"` - DocumentID gid.GID `db:"document_id"` - Title string `db:"title"` - OwnerID gid.GID `db:"owner_id"` - VersionNumber int `db:"version_number"` - Content string `db:"content"` - Changelog string `db:"changelog"` - Status DocumentStatus `db:"status"` - PublishedAt *time.Time `db:"published_at"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID gid.GID `db:"id"` + DocumentID gid.GID `db:"document_id"` + Title string `db:"title"` + OwnerID gid.GID `db:"owner_id"` + VersionNumber int `db:"version_number"` + Classification DocumentClassification `db:"classification"` + Content string `db:"content"` + Changelog string `db:"changelog"` + Status DocumentStatus `db:"status"` + PublishedAt *time.Time `db:"published_at"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } DocumentVersions []*DocumentVersion @@ -58,6 +59,7 @@ SELECT title, owner_id, version_number, + classification, content, changelog, status, @@ -116,6 +118,7 @@ SELECT title, owner_id, version_number, + classification, content, changelog, status, @@ -165,6 +168,7 @@ INSERT INTO document_versions ( title, owner_id, version_number, + classification, content, changelog, status, @@ -178,6 +182,7 @@ VALUES ( @title, @owner_id, @version_number, + @classification, @content, @changelog, @status, @@ -192,6 +197,7 @@ VALUES ( "title": p.Title, "owner_id": p.OwnerID, "version_number": p.VersionNumber, + "classification": p.Classification, "content": p.Content, "changelog": p.Changelog, "status": p.Status, @@ -221,6 +227,7 @@ SELECT title, owner_id, version_number, + classification, content, changelog, status, @@ -272,6 +279,7 @@ SELECT title, owner_id, version_number, + classification, content, changelog, status, @@ -321,6 +329,7 @@ SELECT title, owner_id, version_number, + classification, content, changelog, status, @@ -372,6 +381,7 @@ UPDATE document_versions SET status = @status, content = @content, published_at = @published_at, + classification = @classification, updated_at = @updated_at WHERE %s AND id = @document_version_id @@ -387,6 +397,7 @@ WHERE %s "status": p.Status, "content": p.Content, "published_at": p.PublishedAt, + "classification": p.Classification, "updated_at": p.UpdatedAt, } maps.Copy(args, scope.SQLArguments()) diff --git a/pkg/coredata/migrations/20251018T081657Z.sql b/pkg/coredata/migrations/20251018T081657Z.sql new file mode 100644 index 000000000..b29572738 --- /dev/null +++ b/pkg/coredata/migrations/20251018T081657Z.sql @@ -0,0 +1,12 @@ +CREATE TYPE document_classification AS ENUM ( + 'PUBLIC', + 'INTERNAL', + 'CONFIDENTIAL', + 'SECRET' +); + +ALTER TABLE documents ADD COLUMN classification document_classification NOT NULL DEFAULT 'CONFIDENTIAL'; +ALTER TABLE documents ALTER COLUMN classification DROP DEFAULT; + +ALTER TABLE document_versions ADD COLUMN classification document_classification NOT NULL DEFAULT 'CONFIDENTIAL'; +ALTER TABLE document_versions ALTER COLUMN classification DROP DEFAULT; diff --git a/pkg/probo/document_service.go b/pkg/probo/document_service.go index e8406f283..95e85bd09 100644 --- a/pkg/probo/document_service.go +++ b/pkg/probo/document_service.go @@ -45,10 +45,20 @@ type ( Title string Content string OwnerID gid.GID + Classification coredata.DocumentClassification DocumentType coredata.DocumentType TrustCenterVisibility *coredata.TrustCenterVisibility } + UpdateDocumentRequest struct { + DocumentID gid.GID + Title *string + OwnerID *gid.GID + Classification *coredata.DocumentClassification + DocumentType *coredata.DocumentType + TrustCenterVisibility *coredata.TrustCenterVisibility + } + UpdateDocumentVersionRequest struct { ID gid.GID Content string @@ -308,6 +318,7 @@ func (s *DocumentService) Create( Title: req.Title, DocumentType: req.DocumentType, TrustCenterVisibility: coredata.TrustCenterVisibilityNone, + Classification: req.Classification, CreatedAt: now, UpdatedAt: now, } @@ -317,15 +328,16 @@ func (s *DocumentService) Create( } documentVersion := &coredata.DocumentVersion{ - ID: documentVersionID, - DocumentID: documentID, - Title: req.Title, - OwnerID: req.OwnerID, - VersionNumber: 1, - Content: req.Content, - Status: coredata.DocumentStatusDraft, - CreatedAt: now, - UpdatedAt: now, + ID: documentVersionID, + DocumentID: documentID, + Title: req.Title, + OwnerID: req.OwnerID, + VersionNumber: 1, + Content: req.Content, + Status: coredata.DocumentStatusDraft, + Classification: req.Classification, + CreatedAt: now, + UpdatedAt: now, } err := s.svc.pg.WithTx( @@ -563,6 +575,7 @@ func (s *DocumentService) UpdateVersion( documentVersion.Title = document.Title documentVersion.OwnerID = document.OwnerID + documentVersion.Classification = document.Classification documentVersion.Content = req.Content documentVersion.UpdatedAt = time.Now() @@ -764,6 +777,7 @@ func (s *DocumentService) CreateDraft( draftVersion.Title = document.Title draftVersion.OwnerID = document.OwnerID draftVersion.VersionNumber = latestVersion.VersionNumber + 1 + draftVersion.Classification = document.Classification draftVersion.Content = latestVersion.Content draftVersion.Status = coredata.DocumentStatusDraft draftVersion.CreatedAt = now @@ -1108,11 +1122,7 @@ func (s *DocumentService) ListForRiskID( func (s *DocumentService) Update( ctx context.Context, - documentID gid.GID, - newOwnerID *gid.GID, - documentType *coredata.DocumentType, - title *string, - trustCenterVisibility *coredata.TrustCenterVisibility, + req UpdateDocumentRequest, ) (*coredata.Document, error) { document := &coredata.Document{} people := &coredata.People{} @@ -1121,27 +1131,35 @@ func (s *DocumentService) Update( err := s.svc.pg.WithTx( ctx, func(tx pg.Conn) error { - if err := document.LoadByID(ctx, tx, s.svc.scope, documentID); err != nil { - return fmt.Errorf("cannot load document %q: %w", documentID, err) + if err := document.LoadByID(ctx, tx, s.svc.scope, req.DocumentID); err != nil { + return fmt.Errorf("cannot load document %q: %w", req.DocumentID, err) } - if newOwnerID != nil { - if err := people.LoadByID(ctx, tx, s.svc.scope, *newOwnerID); err != nil { - return fmt.Errorf("cannot load new owner %q: %w", *newOwnerID, err) + if req.Title != nil { + document.Title = *req.Title + } + + if req.Classification != nil { + document.Classification = *req.Classification + } + + if req.DocumentType != nil { + document.DocumentType = *req.DocumentType + } + + if req.DocumentType != nil { + document.DocumentType = *req.DocumentType + } + + if req.TrustCenterVisibility != nil { + document.TrustCenterVisibility = *req.TrustCenterVisibility + } + + if req.OwnerID != nil { + if err := people.LoadByID(ctx, tx, s.svc.scope, *req.OwnerID); err != nil { + return fmt.Errorf("cannot load owner %q: %w", *req.OwnerID, err) } - document.OwnerID = *newOwnerID - } - - if documentType != nil { - document.DocumentType = *documentType - } - - if title != nil { - document.Title = *title - } - - if trustCenterVisibility != nil { - document.TrustCenterVisibility = *trustCenterVisibility + document.OwnerID = people.ID } document.UpdatedAt = now diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 3d02674ae..ff298dfc6 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -95,11 +95,17 @@ enum PeopleKind enum InvitationStatus @goModel(model: "github.com/getprobo/probo/pkg/coredata.InvitationStatus") { PENDING - @goEnum(value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusPending") + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusPending" + ) ACCEPTED - @goEnum(value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusAccepted") + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusAccepted" + ) EXPIRED - @goEnum(value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusExpired") + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusExpired" + ) } enum DocumentStatus @@ -961,6 +967,28 @@ enum DocumentType ) } +enum DocumentClassification + @goModel( + model: "github.com/getprobo/probo/pkg/coredata.DocumentClassification" + ) { + PUBLIC + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.DocumentClassificationPublic" + ) + INTERNAL + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.DocumentClassificationInternal" + ) + CONFIDENTIAL + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.DocumentClassificationConfidential" + ) + SECRET + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.DocumentClassificationSecret" + ) +} + enum AssetType @goModel(model: "github.com/getprobo/probo/pkg/coredata.AssetType") { PHYSICAL @@ -1220,7 +1248,9 @@ enum SnapshotOrderField } enum MembershipOrderField - @goModel(model: "github.com/getprobo/probo/pkg/coredata.MembershipOrderField") { + @goModel( + model: "github.com/getprobo/probo/pkg/coredata.MembershipOrderField" + ) { FULL_NAME @goEnum( value: "github.com/getprobo/probo/pkg/coredata.MembershipOrderFieldFullName" @@ -1240,7 +1270,9 @@ enum MembershipOrderField } enum InvitationOrderField - @goModel(model: "github.com/getprobo/probo/pkg/coredata.InvitationOrderField") { + @goModel( + model: "github.com/getprobo/probo/pkg/coredata.InvitationOrderField" + ) { FULL_NAME @goEnum( value: "github.com/getprobo/probo/pkg/coredata.InvitationOrderFieldFullName" @@ -2077,6 +2109,7 @@ type Document implements Node { title: String! description: String! documentType: DocumentType! + classification: DocumentClassification! currentPublishedVersion: Int trustCenterVisibility: TrustCenterVisibility! owner: People! @goField(forceResolver: true) @@ -3540,6 +3573,7 @@ input CreateDocumentInput { content: String! ownerId: ID! documentType: DocumentType! + classification: DocumentClassification! trustCenterVisibility: TrustCenterVisibility } @@ -3549,6 +3583,7 @@ input UpdateDocumentInput { content: String ownerId: ID documentType: DocumentType + classification: DocumentClassification trustCenterVisibility: TrustCenterVisibility } @@ -4174,6 +4209,7 @@ type DocumentVersion implements Node { content: String! changelog: String! title: String! + classification: DocumentClassification! owner: People! @goField(forceResolver: true) signatures( diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index b78faa39f..d074d7dd0 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -607,6 +607,7 @@ type ComplexityRoot struct { } Document struct { + Classification func(childComplexity int) int Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int CreatedAt func(childComplexity int) int CurrentPublishedVersion func(childComplexity int) int @@ -633,18 +634,19 @@ type ComplexityRoot struct { } DocumentVersion struct { - Changelog func(childComplexity int) int - Content func(childComplexity int) int - CreatedAt func(childComplexity int) int - Document func(childComplexity int) int - ID func(childComplexity int) int - Owner func(childComplexity int) int - PublishedAt func(childComplexity int) int - Signatures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder, filter *types.DocumentVersionSignatureFilter) int - Status func(childComplexity int) int - Title func(childComplexity int) int - UpdatedAt func(childComplexity int) int - Version func(childComplexity int) int + Changelog func(childComplexity int) int + Classification func(childComplexity int) int + Content func(childComplexity int) int + CreatedAt func(childComplexity int) int + Document func(childComplexity int) int + ID func(childComplexity int) int + Owner func(childComplexity int) int + PublishedAt func(childComplexity int) int + Signatures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder, filter *types.DocumentVersionSignatureFilter) int + Status func(childComplexity int) int + Title func(childComplexity int) int + UpdatedAt func(childComplexity int) int + Version func(childComplexity int) int } DocumentVersionConnection struct { @@ -3487,6 +3489,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.DeleteVendorServicePayload.DeletedVendorServiceID(childComplexity), true + case "Document.classification": + if e.complexity.Document.Classification == nil { + break + } + + return e.complexity.Document.Classification(childComplexity), true + case "Document.controls": if e.complexity.Document.Controls == nil { break @@ -3623,6 +3632,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.DocumentVersion.Changelog(childComplexity), true + case "DocumentVersion.classification": + if e.complexity.DocumentVersion.Classification == nil { + break + } + + return e.complexity.DocumentVersion.Classification(childComplexity), true + case "DocumentVersion.content": if e.complexity.DocumentVersion.Content == nil { break @@ -9146,11 +9162,17 @@ enum PeopleKind enum InvitationStatus @goModel(model: "github.com/getprobo/probo/pkg/coredata.InvitationStatus") { PENDING - @goEnum(value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusPending") + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusPending" + ) ACCEPTED - @goEnum(value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusAccepted") + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusAccepted" + ) EXPIRED - @goEnum(value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusExpired") + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.InvitationStatusExpired" + ) } enum DocumentStatus @@ -10012,6 +10034,28 @@ enum DocumentType ) } +enum DocumentClassification + @goModel( + model: "github.com/getprobo/probo/pkg/coredata.DocumentClassification" + ) { + PUBLIC + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.DocumentClassificationPublic" + ) + INTERNAL + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.DocumentClassificationInternal" + ) + CONFIDENTIAL + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.DocumentClassificationConfidential" + ) + SECRET + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.DocumentClassificationSecret" + ) +} + enum AssetType @goModel(model: "github.com/getprobo/probo/pkg/coredata.AssetType") { PHYSICAL @@ -10271,7 +10315,9 @@ enum SnapshotOrderField } enum MembershipOrderField - @goModel(model: "github.com/getprobo/probo/pkg/coredata.MembershipOrderField") { + @goModel( + model: "github.com/getprobo/probo/pkg/coredata.MembershipOrderField" + ) { FULL_NAME @goEnum( value: "github.com/getprobo/probo/pkg/coredata.MembershipOrderFieldFullName" @@ -10291,7 +10337,9 @@ enum MembershipOrderField } enum InvitationOrderField - @goModel(model: "github.com/getprobo/probo/pkg/coredata.InvitationOrderField") { + @goModel( + model: "github.com/getprobo/probo/pkg/coredata.InvitationOrderField" + ) { FULL_NAME @goEnum( value: "github.com/getprobo/probo/pkg/coredata.InvitationOrderFieldFullName" @@ -11128,6 +11176,7 @@ type Document implements Node { title: String! description: String! documentType: DocumentType! + classification: DocumentClassification! currentPublishedVersion: Int trustCenterVisibility: TrustCenterVisibility! owner: People! @goField(forceResolver: true) @@ -12591,6 +12640,7 @@ input CreateDocumentInput { content: String! ownerId: ID! documentType: DocumentType! + classification: DocumentClassification! trustCenterVisibility: TrustCenterVisibility } @@ -12600,6 +12650,7 @@ input UpdateDocumentInput { content: String ownerId: ID documentType: DocumentType + classification: DocumentClassification trustCenterVisibility: TrustCenterVisibility } @@ -13225,6 +13276,7 @@ type DocumentVersion implements Node { content: String! changelog: String! title: String! + classification: DocumentClassification! owner: People! @goField(forceResolver: true) signatures( @@ -32063,6 +32115,50 @@ func (ec *executionContext) fieldContext_Document_documentType(_ context.Context return fc, nil } +func (ec *executionContext) _Document_classification(ctx context.Context, field graphql.CollectedField, obj *types.Document) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Document_classification(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Classification, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(coredata.DocumentClassification) + fc.Result = res + return ec.marshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Document_classification(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Document", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type DocumentClassification does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Document_currentPublishedVersion(ctx context.Context, field graphql.CollectedField, obj *types.Document) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Document_currentPublishedVersion(ctx, field) if err != nil { @@ -32773,6 +32869,8 @@ func (ec *executionContext) fieldContext_DocumentEdge_node(_ context.Context, fi return ec.fieldContext_Document_description(ctx, field) case "documentType": return ec.fieldContext_Document_documentType(ctx, field) + case "classification": + return ec.fieldContext_Document_classification(ctx, field) case "currentPublishedVersion": return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": @@ -32887,6 +32985,8 @@ func (ec *executionContext) fieldContext_DocumentVersion_document(_ context.Cont return ec.fieldContext_Document_description(ctx, field) case "documentType": return ec.fieldContext_Document_documentType(ctx, field) + case "classification": + return ec.fieldContext_Document_classification(ctx, field) case "currentPublishedVersion": return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": @@ -33130,6 +33230,50 @@ func (ec *executionContext) fieldContext_DocumentVersion_title(_ context.Context return fc, nil } +func (ec *executionContext) _DocumentVersion_classification(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersion) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DocumentVersion_classification(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Classification, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(coredata.DocumentClassification) + fc.Result = res + return ec.marshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DocumentVersion_classification(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DocumentVersion", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type DocumentClassification does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _DocumentVersion_owner(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersion) (ret graphql.Marshaler) { fc, err := ec.fieldContext_DocumentVersion_owner(ctx, field) if err != nil { @@ -33587,6 +33731,8 @@ func (ec *executionContext) fieldContext_DocumentVersionEdge_node(_ context.Cont return ec.fieldContext_DocumentVersion_changelog(ctx, field) case "title": return ec.fieldContext_DocumentVersion_title(ctx, field) + case "classification": + return ec.fieldContext_DocumentVersion_classification(ctx, field) case "owner": return ec.fieldContext_DocumentVersion_owner(ctx, field) case "signatures": @@ -33701,6 +33847,8 @@ func (ec *executionContext) fieldContext_DocumentVersionSignature_documentVersio return ec.fieldContext_DocumentVersion_changelog(ctx, field) case "title": return ec.fieldContext_DocumentVersion_title(ctx, field) + case "classification": + return ec.fieldContext_DocumentVersion_classification(ctx, field) case "owner": return ec.fieldContext_DocumentVersion_owner(ctx, field) case "signatures": @@ -51743,6 +51891,8 @@ func (ec *executionContext) fieldContext_PublishDocumentVersionPayload_documentV return ec.fieldContext_DocumentVersion_changelog(ctx, field) case "title": return ec.fieldContext_DocumentVersion_title(ctx, field) + case "classification": + return ec.fieldContext_DocumentVersion_classification(ctx, field) case "owner": return ec.fieldContext_DocumentVersion_owner(ctx, field) case "signatures": @@ -51807,6 +51957,8 @@ func (ec *executionContext) fieldContext_PublishDocumentVersionPayload_document( return ec.fieldContext_Document_description(ctx, field) case "documentType": return ec.fieldContext_Document_documentType(ctx, field) + case "classification": + return ec.fieldContext_Document_classification(ctx, field) case "currentPublishedVersion": return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": @@ -57128,6 +57280,8 @@ func (ec *executionContext) fieldContext_TrustCenterDocumentAccess_document(_ co return ec.fieldContext_Document_description(ctx, field) case "documentType": return ec.fieldContext_Document_documentType(ctx, field) + case "classification": + return ec.fieldContext_Document_classification(ctx, field) case "currentPublishedVersion": return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": @@ -58597,6 +58751,8 @@ func (ec *executionContext) fieldContext_UpdateDocumentPayload_document(_ contex return ec.fieldContext_Document_description(ctx, field) case "documentType": return ec.fieldContext_Document_documentType(ctx, field) + case "classification": + return ec.fieldContext_Document_classification(ctx, field) case "currentPublishedVersion": return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": @@ -58673,6 +58829,8 @@ func (ec *executionContext) fieldContext_UpdateDocumentVersionPayload_documentVe return ec.fieldContext_DocumentVersion_changelog(ctx, field) case "title": return ec.fieldContext_DocumentVersion_title(ctx, field) + case "classification": + return ec.fieldContext_DocumentVersion_classification(ctx, field) case "owner": return ec.fieldContext_DocumentVersion_owner(ctx, field) case "signatures": @@ -69128,7 +69286,7 @@ func (ec *executionContext) unmarshalInputCreateDocumentInput(ctx context.Contex asMap[k] = v } - fieldsInOrder := [...]string{"organizationId", "title", "content", "ownerId", "documentType", "trustCenterVisibility"} + fieldsInOrder := [...]string{"organizationId", "title", "content", "ownerId", "documentType", "classification", "trustCenterVisibility"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -69170,6 +69328,13 @@ func (ec *executionContext) unmarshalInputCreateDocumentInput(ctx context.Contex return it, err } it.DocumentType = data + case "classification": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("classification")) + data, err := ec.unmarshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification(ctx, v) + if err != nil { + return it, err + } + it.Classification = data case "trustCenterVisibility": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("trustCenterVisibility")) data, err := ec.unmarshalOTrustCenterVisibility2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐTrustCenterVisibility(ctx, v) @@ -73421,7 +73586,7 @@ func (ec *executionContext) unmarshalInputUpdateDocumentInput(ctx context.Contex asMap[k] = v } - fieldsInOrder := [...]string{"id", "title", "content", "ownerId", "documentType", "trustCenterVisibility"} + fieldsInOrder := [...]string{"id", "title", "content", "ownerId", "documentType", "classification", "trustCenterVisibility"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -73463,6 +73628,13 @@ func (ec *executionContext) unmarshalInputUpdateDocumentInput(ctx context.Contex return it, err } it.DocumentType = data + case "classification": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("classification")) + data, err := ec.unmarshalODocumentClassification2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification(ctx, v) + if err != nil { + return it, err + } + it.Classification = data case "trustCenterVisibility": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("trustCenterVisibility")) data, err := ec.unmarshalOTrustCenterVisibility2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐTrustCenterVisibility(ctx, v) @@ -80617,6 +80789,11 @@ func (ec *executionContext) _Document(ctx context.Context, sel ast.SelectionSet, if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "classification": + out.Values[i] = ec._Document_classification(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "currentPublishedVersion": out.Values[i] = ec._Document_currentPublishedVersion(ctx, field, obj) case "trustCenterVisibility": @@ -81002,6 +81179,11 @@ func (ec *executionContext) _DocumentVersion(ctx context.Context, sel ast.Select if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "classification": + out.Values[i] = ec._DocumentVersion_classification(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "owner": field := field @@ -95590,6 +95772,38 @@ func (ec *executionContext) marshalNDocument2ᚖgithubᚗcomᚋgetproboᚋprobo return ec._Document(ctx, sel, v) } +func (ec *executionContext) unmarshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification(ctx context.Context, v any) (coredata.DocumentClassification, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification(ctx context.Context, sel ast.SelectionSet, v coredata.DocumentClassification) graphql.Marshaler { + _ = sel + res := graphql.MarshalString(marshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification = map[string]coredata.DocumentClassification{ + "PUBLIC": coredata.DocumentClassificationPublic, + "INTERNAL": coredata.DocumentClassificationInternal, + "CONFIDENTIAL": coredata.DocumentClassificationConfidential, + "SECRET": coredata.DocumentClassificationSecret, + } + marshalNDocumentClassification2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification = map[coredata.DocumentClassification]string{ + coredata.DocumentClassificationPublic: "PUBLIC", + coredata.DocumentClassificationInternal: "INTERNAL", + coredata.DocumentClassificationConfidential: "CONFIDENTIAL", + coredata.DocumentClassificationSecret: "SECRET", + } +) + func (ec *executionContext) marshalNDocumentConnection2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDocumentConnection(ctx context.Context, sel ast.SelectionSet, v types.DocumentConnection) graphql.Marshaler { return ec._DocumentConnection(ctx, sel, &v) } @@ -101170,6 +101384,40 @@ func (ec *executionContext) marshalODocument2ᚖgithubᚗcomᚋgetproboᚋprobo return ec._Document(ctx, sel, v) } +func (ec *executionContext) unmarshalODocumentClassification2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification(ctx context.Context, v any) (*coredata.DocumentClassification, error) { + if v == nil { + return nil, nil + } + tmp, err := graphql.UnmarshalString(v) + res := unmarshalODocumentClassification2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification[tmp] + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalODocumentClassification2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification(ctx context.Context, sel ast.SelectionSet, v *coredata.DocumentClassification) graphql.Marshaler { + if v == nil { + return graphql.Null + } + _ = sel + _ = ctx + res := graphql.MarshalString(marshalODocumentClassification2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification[*v]) + return res +} + +var ( + unmarshalODocumentClassification2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification = map[string]coredata.DocumentClassification{ + "PUBLIC": coredata.DocumentClassificationPublic, + "INTERNAL": coredata.DocumentClassificationInternal, + "CONFIDENTIAL": coredata.DocumentClassificationConfidential, + "SECRET": coredata.DocumentClassificationSecret, + } + marshalODocumentClassification2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐDocumentClassification = map[coredata.DocumentClassification]string{ + coredata.DocumentClassificationPublic: "PUBLIC", + coredata.DocumentClassificationInternal: "INTERNAL", + coredata.DocumentClassificationConfidential: "CONFIDENTIAL", + coredata.DocumentClassificationSecret: "SECRET", + } +) + func (ec *executionContext) unmarshalODocumentFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDocumentFilter(ctx context.Context, v any) (*types.DocumentFilter, error) { if v == nil { return nil, nil diff --git a/pkg/server/api/console/v1/types/document.go b/pkg/server/api/console/v1/types/document.go index 28a74dba7..ccdf82922 100644 --- a/pkg/server/api/console/v1/types/document.go +++ b/pkg/server/api/console/v1/types/document.go @@ -78,6 +78,7 @@ func NewDocument(document *coredata.Document) *Document { ID: document.ID, Title: document.Title, DocumentType: document.DocumentType, + Classification: document.Classification, CurrentPublishedVersion: document.CurrentPublishedVersion, TrustCenterVisibility: document.TrustCenterVisibility, CreatedAt: document.CreatedAt, diff --git a/pkg/server/api/console/v1/types/document_version.go b/pkg/server/api/console/v1/types/document_version.go index a6f9adf23..7fc993381 100644 --- a/pkg/server/api/console/v1/types/document_version.go +++ b/pkg/server/api/console/v1/types/document_version.go @@ -54,14 +54,15 @@ func NewDocumentVersionEdge(documentVersion *coredata.DocumentVersion, orderBy c func NewDocumentVersion(documentVersion *coredata.DocumentVersion) *DocumentVersion { return &DocumentVersion{ - ID: documentVersion.ID, - Version: documentVersion.VersionNumber, - Title: documentVersion.Title, - Content: documentVersion.Content, - Status: documentVersion.Status, - PublishedAt: documentVersion.PublishedAt, - Changelog: documentVersion.Changelog, - CreatedAt: documentVersion.CreatedAt, - UpdatedAt: documentVersion.UpdatedAt, + ID: documentVersion.ID, + Version: documentVersion.VersionNumber, + Title: documentVersion.Title, + Content: documentVersion.Content, + Status: documentVersion.Status, + Classification: documentVersion.Classification, + PublishedAt: documentVersion.PublishedAt, + Changelog: documentVersion.Changelog, + CreatedAt: documentVersion.CreatedAt, + UpdatedAt: documentVersion.UpdatedAt, } } diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index 3382dbc38..5e365f3bf 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -351,6 +351,7 @@ type CreateDocumentInput struct { Content string `json:"content"` OwnerID gid.GID `json:"ownerId"` DocumentType coredata.DocumentType `json:"documentType"` + Classification coredata.DocumentClassification `json:"classification"` TrustCenterVisibility *coredata.TrustCenterVisibility `json:"trustCenterVisibility,omitempty"` } @@ -1006,18 +1007,19 @@ type DeleteVendorServicePayload struct { } type Document struct { - ID gid.GID `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - DocumentType coredata.DocumentType `json:"documentType"` - CurrentPublishedVersion *int `json:"currentPublishedVersion,omitempty"` - TrustCenterVisibility coredata.TrustCenterVisibility `json:"trustCenterVisibility"` - Owner *People `json:"owner"` - Organization *Organization `json:"organization"` - Versions *DocumentVersionConnection `json:"versions"` - Controls *ControlConnection `json:"controls"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID gid.GID `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + DocumentType coredata.DocumentType `json:"documentType"` + Classification coredata.DocumentClassification `json:"classification"` + CurrentPublishedVersion *int `json:"currentPublishedVersion,omitempty"` + TrustCenterVisibility coredata.TrustCenterVisibility `json:"trustCenterVisibility"` + Owner *People `json:"owner"` + Organization *Organization `json:"organization"` + Versions *DocumentVersionConnection `json:"versions"` + Controls *ControlConnection `json:"controls"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } func (Document) IsNode() {} @@ -1033,18 +1035,19 @@ type DocumentFilter struct { } type DocumentVersion struct { - ID gid.GID `json:"id"` - Document *Document `json:"document"` - Status coredata.DocumentStatus `json:"status"` - Version int `json:"version"` - Content string `json:"content"` - Changelog string `json:"changelog"` - Title string `json:"title"` - Owner *People `json:"owner"` - Signatures *DocumentVersionSignatureConnection `json:"signatures"` - PublishedAt *time.Time `json:"publishedAt,omitempty"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID gid.GID `json:"id"` + Document *Document `json:"document"` + Status coredata.DocumentStatus `json:"status"` + Version int `json:"version"` + Content string `json:"content"` + Changelog string `json:"changelog"` + Title string `json:"title"` + Classification coredata.DocumentClassification `json:"classification"` + Owner *People `json:"owner"` + Signatures *DocumentVersionSignatureConnection `json:"signatures"` + PublishedAt *time.Time `json:"publishedAt,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } func (DocumentVersion) IsNode() {} @@ -1789,12 +1792,13 @@ type UpdateDatumPayload struct { } type UpdateDocumentInput struct { - ID gid.GID `json:"id"` - Title *string `json:"title,omitempty"` - Content *string `json:"content,omitempty"` - OwnerID *gid.GID `json:"ownerId,omitempty"` - DocumentType *coredata.DocumentType `json:"documentType,omitempty"` - TrustCenterVisibility *coredata.TrustCenterVisibility `json:"trustCenterVisibility,omitempty"` + ID gid.GID `json:"id"` + Title *string `json:"title,omitempty"` + Content *string `json:"content,omitempty"` + OwnerID *gid.GID `json:"ownerId,omitempty"` + DocumentType *coredata.DocumentType `json:"documentType,omitempty"` + Classification *coredata.DocumentClassification `json:"classification,omitempty"` + TrustCenterVisibility *coredata.TrustCenterVisibility `json:"trustCenterVisibility,omitempty"` } type UpdateDocumentPayload struct { diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 64c4c18b2..38072404a 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -2566,11 +2566,14 @@ func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.Updat document, err := prb.Documents.Update( ctx, - input.ID, - input.OwnerID, - input.DocumentType, - input.Title, - input.TrustCenterVisibility, + probo.UpdateDocumentRequest{ + DocumentID: input.ID, + Title: input.Title, + OwnerID: input.OwnerID, + Classification: input.Classification, + DocumentType: input.DocumentType, + TrustCenterVisibility: input.TrustCenterVisibility, + }, ) if err != nil {