From fb09bed24712f95a9ce954db475dbfa13c52b7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Mon, 26 Jan 2026 18:58:49 +0400 Subject: [PATCH] Refactor document detail to document layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- ...DocumentActionsDropdownFragment.graphql.ts | 124 +++ ...ropdownn_exportVersionMutation.graphql.ts} | 24 +- ...umentDetailPageDocumentFragment.graphql.ts | 429 --------- ...cumentDetailPagePublishMutation.graphql.ts | 630 -------------- .../DocumentLayoutDrawerFragment.graphql.ts | 65 ++ ...> DocumentLayoutDrawerMutation.graphql.ts} | 32 +- ...phql.ts => DocumentLayoutQuery.graphql.ts} | 745 ++++++++++------ ...ntLayout_publishVersionMutation.graphql.ts | 106 +++ .../core/DocumentTitleFormFragment.graphql.ts | 64 ++ .../core/DocumentTitleFormMutation.graphql.ts | 163 ++++ ...entVersionsDropdownItemFragment.graphql.ts | 75 ++ ...cumentVersionsDropdownMenuQuery.graphql.ts | 246 ++++++ .../UpdateVersionDialogFragment.graphql.ts | 166 ++++ .../documents/PdfDownloadDialog.tsx | 2 +- apps/console/src/hooks/graph/DocumentGraph.ts | 10 - .../documents/DocumentDetailPage.tsx | 823 ------------------ .../documents/DocumentLayout.tsx | 188 ++++ .../documents/DocumentLayoutLoader.tsx | 37 + .../_components/DocumentActionsDropdown.tsx | 220 +++++ .../_components/DocumentLayoutDrawer.tsx | 320 +++++++ .../_components/DocumentTitleForm.tsx | 120 +++ .../_components/DocumentVersionsDropdown.tsx | 37 + .../DocumentVersionsDropdownItem.tsx | 72 ++ .../DocumentVersionsDropdownMenu.tsx | 53 ++ .../_components/UpdateVersionDialog.tsx | 42 +- .../documents/tabs/DocumentDescriptionTab.tsx | 4 +- .../documents/tabs/DocumentSignaturesTab.tsx | 4 +- apps/console/src/routes/documentsRoutes.ts | 15 +- go.sum | 2 + pkg/coredata/document_version.go | 107 ++- pkg/probo/document_service.go | 27 + pkg/server/api/console/v1/schema.graphql | 11 +- pkg/server/api/console/v1/schema/schema.go | 101 ++- .../api/console/v1/types/document_version.go | 49 +- pkg/server/api/console/v1/types/types.go | 22 +- pkg/server/api/console/v1/v1_resolver.go | 36 +- 36 files changed, 2861 insertions(+), 2310 deletions(-) create mode 100644 apps/console/src/__generated__/core/DocumentActionsDropdownFragment.graphql.ts rename apps/console/src/__generated__/core/{DocumentDetailPageExportPDFMutation.graphql.ts => DocumentActionsDropdownn_exportVersionMutation.graphql.ts} (63%) delete mode 100644 apps/console/src/__generated__/core/DocumentDetailPageDocumentFragment.graphql.ts delete mode 100644 apps/console/src/__generated__/core/DocumentDetailPagePublishMutation.graphql.ts create mode 100644 apps/console/src/__generated__/core/DocumentLayoutDrawerFragment.graphql.ts rename apps/console/src/__generated__/core/{DocumentDetailPageUpdateMutation.graphql.ts => DocumentLayoutDrawerMutation.graphql.ts} (75%) rename apps/console/src/__generated__/core/{DocumentGraphNodeQuery.graphql.ts => DocumentLayoutQuery.graphql.ts} (51%) create mode 100644 apps/console/src/__generated__/core/DocumentLayout_publishVersionMutation.graphql.ts create mode 100644 apps/console/src/__generated__/core/DocumentTitleFormFragment.graphql.ts create mode 100644 apps/console/src/__generated__/core/DocumentTitleFormMutation.graphql.ts create mode 100644 apps/console/src/__generated__/core/DocumentVersionsDropdownItemFragment.graphql.ts create mode 100644 apps/console/src/__generated__/core/DocumentVersionsDropdownMenuQuery.graphql.ts create mode 100644 apps/console/src/__generated__/core/UpdateVersionDialogFragment.graphql.ts delete mode 100644 apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx create mode 100644 apps/console/src/pages/organizations/documents/DocumentLayout.tsx create mode 100644 apps/console/src/pages/organizations/documents/DocumentLayoutLoader.tsx create mode 100644 apps/console/src/pages/organizations/documents/_components/DocumentActionsDropdown.tsx create mode 100644 apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx create mode 100644 apps/console/src/pages/organizations/documents/_components/DocumentTitleForm.tsx create mode 100644 apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdown.tsx create mode 100644 apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdownItem.tsx create mode 100644 apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdownMenu.tsx diff --git a/apps/console/src/__generated__/core/DocumentActionsDropdownFragment.graphql.ts b/apps/console/src/__generated__/core/DocumentActionsDropdownFragment.graphql.ts new file mode 100644 index 000000000..663ae04d3 --- /dev/null +++ b/apps/console/src/__generated__/core/DocumentActionsDropdownFragment.graphql.ts @@ -0,0 +1,124 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type DocumentActionsDropdownFragment$data = { + readonly canDelete: boolean; + readonly canUpdate: boolean; + readonly id: string; + readonly title: string; + readonly versions: { + readonly __id: string; + readonly totalCount: number; + }; + readonly " $fragmentSpreads": FragmentRefs<"UpdateVersionDialogFragment">; + readonly " $fragmentType": "DocumentActionsDropdownFragment"; +}; +export type DocumentActionsDropdownFragment$key = { + readonly " $data"?: DocumentActionsDropdownFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"DocumentActionsDropdownFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "DocumentActionsDropdownFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "title", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:document:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:document:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:document:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:document:delete\")" + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 20 + } + ], + "concreteType": "DocumentVersionConnection", + "kind": "LinkedField", + "name": "versions", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "totalCount", + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": "versions(first:20)" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "UpdateVersionDialogFragment" + } + ], + "type": "Document", + "abstractKey": null +}; + +(node as any).hash = "bc3632812ef26d793478c01bd41e7dbe"; + +export default node; diff --git a/apps/console/src/__generated__/core/DocumentDetailPageExportPDFMutation.graphql.ts b/apps/console/src/__generated__/core/DocumentActionsDropdownn_exportVersionMutation.graphql.ts similarity index 63% rename from apps/console/src/__generated__/core/DocumentDetailPageExportPDFMutation.graphql.ts rename to apps/console/src/__generated__/core/DocumentActionsDropdownn_exportVersionMutation.graphql.ts index 2797db967..b8e57af59 100644 --- a/apps/console/src/__generated__/core/DocumentDetailPageExportPDFMutation.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentActionsDropdownn_exportVersionMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<02f25af2f023ddc0a964b44c105ea248>> * @lightSyntaxTransform * @nogrep */ @@ -15,17 +15,17 @@ export type ExportDocumentVersionPDFInput = { withSignatures: boolean; withWatermark: boolean; }; -export type DocumentDetailPageExportPDFMutation$variables = { +export type DocumentActionsDropdownn_exportVersionMutation$variables = { input: ExportDocumentVersionPDFInput; }; -export type DocumentDetailPageExportPDFMutation$data = { +export type DocumentActionsDropdownn_exportVersionMutation$data = { readonly exportDocumentVersionPDF: { readonly data: string; }; }; -export type DocumentDetailPageExportPDFMutation = { - response: DocumentDetailPageExportPDFMutation$data; - variables: DocumentDetailPageExportPDFMutation$variables; +export type DocumentActionsDropdownn_exportVersionMutation = { + response: DocumentActionsDropdownn_exportVersionMutation$data; + variables: DocumentActionsDropdownn_exportVersionMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -67,7 +67,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "DocumentDetailPageExportPDFMutation", + "name": "DocumentActionsDropdownn_exportVersionMutation", "selections": (v1/*: any*/), "type": "Mutation", "abstractKey": null @@ -76,20 +76,20 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "DocumentDetailPageExportPDFMutation", + "name": "DocumentActionsDropdownn_exportVersionMutation", "selections": (v1/*: any*/) }, "params": { - "cacheID": "4c17d48a790ff5f1617c7db604766b83", + "cacheID": "b9228ff1ce940910b277cecf021ebecb", "id": null, "metadata": {}, - "name": "DocumentDetailPageExportPDFMutation", + "name": "DocumentActionsDropdownn_exportVersionMutation", "operationKind": "mutation", - "text": "mutation DocumentDetailPageExportPDFMutation(\n $input: ExportDocumentVersionPDFInput!\n) {\n exportDocumentVersionPDF(input: $input) {\n data\n }\n}\n" + "text": "mutation DocumentActionsDropdownn_exportVersionMutation(\n $input: ExportDocumentVersionPDFInput!\n) {\n exportDocumentVersionPDF(input: $input) {\n data\n }\n}\n" } }; })(); -(node as any).hash = "e8854650389c3ebd1020ec278d8be831"; +(node as any).hash = "1bab1476f7c4231736feefd227063c27"; export default node; diff --git a/apps/console/src/__generated__/core/DocumentDetailPageDocumentFragment.graphql.ts b/apps/console/src/__generated__/core/DocumentDetailPageDocumentFragment.graphql.ts deleted file mode 100644 index 67d727c60..000000000 --- a/apps/console/src/__generated__/core/DocumentDetailPageDocumentFragment.graphql.ts +++ /dev/null @@ -1,429 +0,0 @@ -/** - * @generated SignedSource<> - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @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 canDelete: boolean; - readonly canPublish: boolean; - readonly canUpdate: boolean; - readonly classification: DocumentClassification; - readonly controlsInfo: { - readonly totalCount: number; - }; - readonly documentType: DocumentType; - readonly id: string; - readonly owner: { - readonly fullName: string; - readonly id: string; - }; - readonly title: string; - readonly versions: { - readonly __id: string; - readonly edges: ReadonlyArray<{ - readonly node: { - readonly canDeleteDraft: boolean; - readonly classification: DocumentClassification; - readonly content: string; - readonly id: string; - readonly owner: { - readonly fullName: string; - readonly id: string; - }; - readonly publishedAt: string | null | undefined; - readonly signatures: { - readonly __id: string; - readonly edges: ReadonlyArray<{ - readonly node: { - readonly id: string; - readonly signedBy: { - readonly id: string; - }; - readonly state: DocumentVersionSignatureState; - readonly " $fragmentSpreads": FragmentRefs<"DocumentSignaturesTab_signature">; - }; - }>; - }; - readonly status: DocumentStatus; - readonly updatedAt: string; - readonly version: number; - readonly " $fragmentSpreads": FragmentRefs<"DocumentSignaturesTab_version">; - }; - }>; - }; - readonly " $fragmentSpreads": FragmentRefs<"DocumentControlsTabFragment">; - readonly " $fragmentType": "DocumentDetailPageDocumentFragment"; -}; -export type DocumentDetailPageDocumentFragment$key = { - readonly " $data"?: DocumentDetailPageDocumentFragment$data; - readonly " $fragmentSpreads": FragmentRefs<"DocumentDetailPageDocumentFragment">; -}; - -const node: ReaderFragment = (function(){ -var v0 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null -}, -v1 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "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 -}, -v5 = { - "alias": null, - "args": null, - "concreteType": "PageInfo", - "kind": "LinkedField", - "name": "pageInfo", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "endCursor", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "hasNextPage", - "storageKey": null - } - ], - "storageKey": null -}, -v6 = { - "kind": "ClientExtension", - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "__id", - "storageKey": null - } - ] -}; -return { - "argumentDefinitions": [], - "kind": "Fragment", - "metadata": { - "connection": [ - { - "count": null, - "cursor": null, - "direction": "forward", - "path": null - }, - { - "count": null, - "cursor": null, - "direction": "forward", - "path": [ - "versions" - ] - } - ] - }, - "name": "DocumentDetailPageDocumentFragment", - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "title", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "documentType", - "storageKey": null - }, - (v1/*: any*/), - (v2/*: any*/), - { - "alias": "canUpdate", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document:update" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document:update\")" - }, - { - "alias": "canDelete", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document:delete" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document:delete\")" - }, - { - "alias": "canPublish", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document-version:publish" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document-version:publish\")" - }, - { - "args": null, - "kind": "FragmentSpread", - "name": "DocumentControlsTabFragment" - }, - { - "alias": "controlsInfo", - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 0 - } - ], - "concreteType": "ControlConnection", - "kind": "LinkedField", - "name": "controls", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "totalCount", - "storageKey": null - } - ], - "storageKey": "controls(first:0)" - }, - { - "alias": "versions", - "args": null, - "concreteType": "DocumentVersionConnection", - "kind": "LinkedField", - "name": "__DocumentDetailPage_versions_connection", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "DocumentVersionEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "DocumentVersion", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "content", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "status", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "publishedAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "version", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "updatedAt", - "storageKey": null - }, - (v1/*: any*/), - (v2/*: any*/), - { - "alias": "canDeleteDraft", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document-version:delete-draft" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document-version:delete-draft\")" - }, - { - "args": null, - "kind": "FragmentSpread", - "name": "DocumentSignaturesTab_version" - }, - { - "alias": "signatures", - "args": null, - "concreteType": "DocumentVersionSignatureConnection", - "kind": "LinkedField", - "name": "__DocumentDetailPage_signatures_connection", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "DocumentVersionSignatureEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "DocumentVersionSignature", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "state", - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "People", - "kind": "LinkedField", - "name": "signedBy", - "plural": false, - "selections": [ - (v0/*: any*/) - ], - "storageKey": null - }, - { - "args": null, - "kind": "FragmentSpread", - "name": "DocumentSignaturesTab_signature" - }, - (v3/*: any*/) - ], - "storageKey": null - }, - (v4/*: any*/) - ], - "storageKey": null - }, - (v5/*: any*/), - (v6/*: any*/) - ], - "storageKey": null - }, - (v3/*: any*/) - ], - "storageKey": null - }, - (v4/*: any*/) - ], - "storageKey": null - }, - (v5/*: any*/), - (v6/*: any*/) - ], - "storageKey": null - } - ], - "type": "Document", - "abstractKey": null -}; -})(); - -(node as any).hash = "5ed2f764b1587dd945b0a10c33cee729"; - -export default node; diff --git a/apps/console/src/__generated__/core/DocumentDetailPagePublishMutation.graphql.ts b/apps/console/src/__generated__/core/DocumentDetailPagePublishMutation.graphql.ts deleted file mode 100644 index 90073e5e1..000000000 --- a/apps/console/src/__generated__/core/DocumentDetailPagePublishMutation.graphql.ts +++ /dev/null @@ -1,630 +0,0 @@ -/** - * @generated SignedSource<> - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck - -import { ConcreteRequest } from 'relay-runtime'; -import { FragmentRefs } from "relay-runtime"; -export type PublishDocumentVersionInput = { - changelog?: string | null | undefined; - documentId: string; -}; -export type DocumentDetailPagePublishMutation$variables = { - input: PublishDocumentVersionInput; -}; -export type DocumentDetailPagePublishMutation$data = { - readonly publishDocumentVersion: { - readonly document: { - readonly " $fragmentSpreads": FragmentRefs<"DocumentDetailPageDocumentFragment">; - }; - }; -}; -export type DocumentDetailPagePublishMutation = { - response: DocumentDetailPagePublishMutation$data; - variables: DocumentDetailPagePublishMutation$variables; -}; - -const node: ConcreteRequest = (function(){ -var v0 = [ - { - "defaultValue": null, - "kind": "LocalArgument", - "name": "input" - } -], -v1 = [ - { - "kind": "Variable", - "name": "input", - "variableName": "input" - } -], -v2 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null -}, -v3 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "classification", - "storageKey": null -}, -v4 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fullName", - "storageKey": null -}, -v5 = { - "alias": null, - "args": null, - "concreteType": "People", - "kind": "LinkedField", - "name": "owner", - "plural": false, - "selections": [ - (v2/*: any*/), - (v4/*: any*/) - ], - "storageKey": null -}, -v6 = [ - { - "kind": "Literal", - "name": "first", - "value": 20 - } -], -v7 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null -}, -v8 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "__typename", - "storageKey": null -}, -v9 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "cursor", - "storageKey": null -}, -v10 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "endCursor", - "storageKey": null -}, -v11 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "hasNextPage", - "storageKey": null -}, -v12 = { - "kind": "ClientExtension", - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "__id", - "storageKey": null - } - ] -}, -v13 = [ - { - "kind": "Literal", - "name": "action", - "value": "core:document-version:request-signature" - } -], -v14 = [ - { - "kind": "Literal", - "name": "first", - "value": 1000 - } -], -v15 = { - "alias": null, - "args": null, - "concreteType": "PageInfo", - "kind": "LinkedField", - "name": "pageInfo", - "plural": false, - "selections": [ - (v10/*: any*/), - (v11/*: any*/) - ], - "storageKey": null -}; -return { - "fragment": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Fragment", - "metadata": null, - "name": "DocumentDetailPagePublishMutation", - "selections": [ - { - "alias": null, - "args": (v1/*: any*/), - "concreteType": "PublishDocumentVersionPayload", - "kind": "LinkedField", - "name": "publishDocumentVersion", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Document", - "kind": "LinkedField", - "name": "document", - "plural": false, - "selections": [ - { - "args": null, - "kind": "FragmentSpread", - "name": "DocumentDetailPageDocumentFragment" - } - ], - "storageKey": null - } - ], - "storageKey": null - } - ], - "type": "Mutation", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Operation", - "name": "DocumentDetailPagePublishMutation", - "selections": [ - { - "alias": null, - "args": (v1/*: any*/), - "concreteType": "PublishDocumentVersionPayload", - "kind": "LinkedField", - "name": "publishDocumentVersion", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Document", - "kind": "LinkedField", - "name": "document", - "plural": false, - "selections": [ - (v2/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "title", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "documentType", - "storageKey": null - }, - (v3/*: any*/), - (v5/*: any*/), - { - "alias": "canUpdate", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document:update" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document:update\")" - }, - { - "alias": "canDelete", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document:delete" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document:delete\")" - }, - { - "alias": "canPublish", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document-version:publish" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document-version:publish\")" - }, - { - "alias": null, - "args": (v6/*: any*/), - "concreteType": "ControlConnection", - "kind": "LinkedField", - "name": "controls", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "ControlEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Control", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v2/*: any*/), - (v7/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "sectionTitle", - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "Framework", - "kind": "LinkedField", - "name": "framework", - "plural": false, - "selections": [ - (v2/*: any*/), - (v7/*: any*/) - ], - "storageKey": null - }, - (v8/*: any*/) - ], - "storageKey": null - }, - (v9/*: any*/) - ], - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "PageInfo", - "kind": "LinkedField", - "name": "pageInfo", - "plural": false, - "selections": [ - (v10/*: any*/), - (v11/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "hasPreviousPage", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "startCursor", - "storageKey": null - } - ], - "storageKey": null - }, - (v12/*: any*/) - ], - "storageKey": "controls(first:20)" - }, - { - "alias": null, - "args": (v6/*: any*/), - "filters": [ - "orderBy", - "filter" - ], - "handle": "connection", - "key": "DocumentControlsTab_controls", - "kind": "LinkedHandle", - "name": "controls" - }, - { - "alias": "controlsInfo", - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 0 - } - ], - "concreteType": "ControlConnection", - "kind": "LinkedField", - "name": "controls", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "totalCount", - "storageKey": null - } - ], - "storageKey": "controls(first:0)" - }, - { - "alias": null, - "args": (v6/*: any*/), - "concreteType": "DocumentVersionConnection", - "kind": "LinkedField", - "name": "versions", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "DocumentVersionEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "DocumentVersion", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v2/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "content", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "status", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "publishedAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "version", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "updatedAt", - "storageKey": null - }, - (v3/*: any*/), - (v5/*: any*/), - { - "alias": "canDeleteDraft", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document-version:delete-draft" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document-version:delete-draft\")" - }, - { - "alias": "canRequestSignature", - "args": (v13/*: any*/), - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document-version:request-signature\")" - }, - { - "alias": null, - "args": (v14/*: any*/), - "concreteType": "DocumentVersionSignatureConnection", - "kind": "LinkedField", - "name": "signatures", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "DocumentVersionSignatureEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "DocumentVersionSignature", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v2/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "state", - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "People", - "kind": "LinkedField", - "name": "signedBy", - "plural": false, - "selections": [ - (v2/*: any*/), - (v4/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "primaryEmailAddress", - "storageKey": null - } - ], - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "signedAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "requestedAt", - "storageKey": null - }, - { - "alias": "canCancel", - "args": (v13/*: any*/), - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document-version:request-signature\")" - }, - (v8/*: any*/) - ], - "storageKey": null - }, - (v9/*: any*/) - ], - "storageKey": null - }, - (v15/*: any*/), - (v12/*: any*/) - ], - "storageKey": "signatures(first:1000)" - }, - { - "alias": null, - "args": (v14/*: any*/), - "filters": [ - "filter" - ], - "handle": "connection", - "key": "DocumentSignaturesTab_signatures", - "kind": "LinkedHandle", - "name": "signatures" - }, - { - "alias": null, - "args": (v14/*: any*/), - "filters": [], - "handle": "connection", - "key": "DocumentDetailPage_signatures", - "kind": "LinkedHandle", - "name": "signatures" - }, - (v8/*: any*/) - ], - "storageKey": null - }, - (v9/*: any*/) - ], - "storageKey": null - }, - (v15/*: any*/), - (v12/*: any*/) - ], - "storageKey": "versions(first:20)" - }, - { - "alias": null, - "args": (v6/*: any*/), - "filters": null, - "handle": "connection", - "key": "DocumentDetailPage_versions", - "kind": "LinkedHandle", - "name": "versions" - } - ], - "storageKey": null - } - ], - "storageKey": null - } - ] - }, - "params": { - "cacheID": "91d0c14af00c2a67732d781e41770a81", - "id": null, - "metadata": {}, - "name": "DocumentDetailPagePublishMutation", - "operationKind": "mutation", - "text": "mutation DocumentDetailPagePublishMutation(\n $input: PublishDocumentVersionInput!\n) {\n publishDocumentVersion(input: $input) {\n document {\n ...DocumentDetailPageDocumentFragment\n id\n }\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 canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n canPublish: permission(action: \"core:document-version:publish\")\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 canDeleteDraft: permission(action: \"core:document-version:delete-draft\")\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 canCancel: permission(action: \"core:document-version:request-signature\")\n}\n\nfragment DocumentSignaturesTab_version on DocumentVersion {\n id\n status\n canRequestSignature: permission(action: \"core:document-version:request-signature\")\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" - } -}; -})(); - -(node as any).hash = "d2b29d8c6e8508e24b0d39ea21069e4a"; - -export default node; diff --git a/apps/console/src/__generated__/core/DocumentLayoutDrawerFragment.graphql.ts b/apps/console/src/__generated__/core/DocumentLayoutDrawerFragment.graphql.ts new file mode 100644 index 000000000..78d8ad1df --- /dev/null +++ b/apps/console/src/__generated__/core/DocumentLayoutDrawerFragment.graphql.ts @@ -0,0 +1,65 @@ +/** + * @generated SignedSource<<927b958ffdaa974f5a5054be4638b60d>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; +import { FragmentRefs } from "relay-runtime"; +export type DocumentLayoutDrawerFragment$data = { + readonly canUpdate: boolean; + readonly documentType: DocumentType; + readonly id: string; + readonly " $fragmentType": "DocumentLayoutDrawerFragment"; +}; +export type DocumentLayoutDrawerFragment$key = { + readonly " $data"?: DocumentLayoutDrawerFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"DocumentLayoutDrawerFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "DocumentLayoutDrawerFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "documentType", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:document:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:document:update\")" + } + ], + "type": "Document", + "abstractKey": null +}; + +(node as any).hash = "a8f55efdb2682fb35a7bc68f3ef1808a"; + +export default node; diff --git a/apps/console/src/__generated__/core/DocumentDetailPageUpdateMutation.graphql.ts b/apps/console/src/__generated__/core/DocumentLayoutDrawerMutation.graphql.ts similarity index 75% rename from apps/console/src/__generated__/core/DocumentDetailPageUpdateMutation.graphql.ts rename to apps/console/src/__generated__/core/DocumentLayoutDrawerMutation.graphql.ts index b64d514d4..fe8184801 100644 --- a/apps/console/src/__generated__/core/DocumentDetailPageUpdateMutation.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentLayoutDrawerMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<7c1bfb7a8cc7cc0f426348c57a17f7c9>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -21,10 +21,10 @@ export type UpdateDocumentInput = { title?: string | null | undefined; trustCenterVisibility?: TrustCenterVisibility | null | undefined; }; -export type DocumentDetailPageUpdateMutation$variables = { +export type DocumentLayoutDrawerMutation$variables = { input: UpdateDocumentInput; }; -export type DocumentDetailPageUpdateMutation$data = { +export type DocumentLayoutDrawerMutation$data = { readonly updateDocument: { readonly document: { readonly classification: DocumentClassification; @@ -34,13 +34,12 @@ export type DocumentDetailPageUpdateMutation$data = { readonly fullName: string; readonly id: string; }; - readonly title: string; }; }; }; -export type DocumentDetailPageUpdateMutation = { - response: DocumentDetailPageUpdateMutation$data; - variables: DocumentDetailPageUpdateMutation$variables; +export type DocumentLayoutDrawerMutation = { + response: DocumentLayoutDrawerMutation$data; + variables: DocumentLayoutDrawerMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -82,13 +81,6 @@ v2 = [ "plural": false, "selections": [ (v1/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "title", - "storageKey": null - }, { "alias": null, "args": null, @@ -134,7 +126,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "DocumentDetailPageUpdateMutation", + "name": "DocumentLayoutDrawerMutation", "selections": (v2/*: any*/), "type": "Mutation", "abstractKey": null @@ -143,20 +135,20 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "DocumentDetailPageUpdateMutation", + "name": "DocumentLayoutDrawerMutation", "selections": (v2/*: any*/) }, "params": { - "cacheID": "865056a65394e413822f798f31351341", + "cacheID": "968dc4701234cb6c5761eeea9faff752", "id": null, "metadata": {}, - "name": "DocumentDetailPageUpdateMutation", + "name": "DocumentLayoutDrawerMutation", "operationKind": "mutation", - "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" + "text": "mutation DocumentLayoutDrawerMutation(\n $input: UpdateDocumentInput!\n) {\n updateDocument(input: $input) {\n document {\n id\n documentType\n classification\n owner {\n id\n fullName\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "bd6acddfc137a6bdce42628d05dc9ff6"; +(node as any).hash = "80901db65cb0c486e23b823bd2c16bc0"; export default node; diff --git a/apps/console/src/__generated__/core/DocumentGraphNodeQuery.graphql.ts b/apps/console/src/__generated__/core/DocumentLayoutQuery.graphql.ts similarity index 51% rename from apps/console/src/__generated__/core/DocumentGraphNodeQuery.graphql.ts rename to apps/console/src/__generated__/core/DocumentLayoutQuery.graphql.ts index 95f8705b1..b5d2a6936 100644 --- a/apps/console/src/__generated__/core/DocumentGraphNodeQuery.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentLayoutQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<2d8a714fdbea520c2365e64524b68da5>> * @lightSyntaxTransform * @nogrep */ @@ -10,17 +10,69 @@ import { ConcreteRequest } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; -export type DocumentGraphNodeQuery$variables = { +export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "SECRET"; +export type DocumentStatus = "DRAFT" | "PUBLISHED"; +export type DocumentVersionSignatureState = "REQUESTED" | "SIGNED"; +export type DocumentLayoutQuery$variables = { documentId: string; }; -export type DocumentGraphNodeQuery$data = { - readonly node: { - readonly " $fragmentSpreads": FragmentRefs<"DocumentDetailPageDocumentFragment">; +export type DocumentLayoutQuery$data = { + readonly document: { + readonly __typename: "Document"; + readonly canPublish: boolean; + readonly classification: DocumentClassification; + readonly controlInfo: { + readonly totalCount: number; + }; + readonly id: string; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; + readonly title: string; + readonly versions: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly canDeleteDraft: boolean; + readonly classification: DocumentClassification; + readonly content: string; + readonly id: string; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; + readonly publishedAt: string | null | undefined; + readonly signatures: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly signedBy: { + readonly id: string; + }; + readonly state: DocumentVersionSignatureState; + readonly " $fragmentSpreads": FragmentRefs<"DocumentSignaturesTab_signature">; + }; + }>; + }; + readonly status: DocumentStatus; + readonly updatedAt: string; + readonly version: number; + readonly " $fragmentSpreads": FragmentRefs<"DocumentSignaturesTab_version">; + }; + }>; + }; + readonly " $fragmentSpreads": FragmentRefs<"DocumentActionsDropdownFragment" | "DocumentControlsTabFragment" | "DocumentLayoutDrawerFragment" | "DocumentTitleFormFragment">; + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other"; }; }; -export type DocumentGraphNodeQuery = { - response: DocumentGraphNodeQuery$data; - variables: DocumentGraphNodeQuery$variables; +export type DocumentLayoutQuery = { + response: DocumentLayoutQuery$data; + variables: DocumentLayoutQuery$variables; }; const node: ConcreteRequest = (function(){ @@ -56,17 +108,24 @@ v4 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "classification", + "name": "title", "storageKey": null }, v5 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "fullName", + "name": "classification", "storageKey": null }, v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null +}, +v7 = { "alias": null, "args": null, "concreteType": "People", @@ -75,46 +134,138 @@ v6 = { "plural": false, "selections": [ (v3/*: any*/), - (v5/*: any*/) + (v6/*: any*/) ], "storageKey": null }, -v7 = [ - { - "kind": "Literal", - "name": "first", - "value": 20 - } -], v8 = { + "alias": "canPublish", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:document-version:publish" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:document-version:publish\")" +}, +v9 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "name", + "name": "totalCount", "storageKey": null }, -v9 = { +v10 = { + "alias": "controlInfo", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 0 + } + ], + "concreteType": "ControlConnection", + "kind": "LinkedField", + "name": "controls", + "plural": false, + "selections": [ + (v9/*: any*/) + ], + "storageKey": "controls(first:0)" +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "content", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null +}, +v13 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "publishedAt", + "storageKey": null +}, +v14 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "version", + "storageKey": null +}, +v15 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null +}, +v16 = { + "alias": "canDeleteDraft", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:document-version:delete-draft" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:document-version:delete-draft\")" +}, +v17 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null +}, +v18 = { "alias": null, "args": null, "kind": "ScalarField", "name": "cursor", "storageKey": null }, -v10 = { +v19 = { "alias": null, "args": null, "kind": "ScalarField", "name": "endCursor", "storageKey": null }, -v11 = { +v20 = { "alias": null, "args": null, "kind": "ScalarField", "name": "hasNextPage", "storageKey": null }, -v12 = { +v21 = { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + (v19/*: any*/), + (v20/*: any*/) + ], + "storageKey": null +}, +v22 = { "kind": "ClientExtension", "selections": [ { @@ -126,31 +277,32 @@ v12 = { } ] }, -v13 = [ +v23 = [ + { + "kind": "Literal", + "name": "first", + "value": 20 + } +], +v24 = [ { "kind": "Literal", "name": "action", "value": "core:document-version:request-signature" } ], -v14 = [ +v25 = [ { "kind": "Literal", "name": "first", "value": 1000 } ], -v15 = { +v26 = { "alias": null, "args": null, - "concreteType": "PageInfo", - "kind": "LinkedField", - "name": "pageInfo", - "plural": false, - "selections": [ - (v10/*: any*/), - (v11/*: any*/) - ], + "kind": "ScalarField", + "name": "name", "storageKey": null }; return { @@ -158,23 +310,152 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "DocumentGraphNodeQuery", + "name": "DocumentLayoutQuery", "selections": [ { - "alias": null, + "alias": "document", "args": (v1/*: any*/), "concreteType": null, "kind": "LinkedField", "name": "node", "plural": false, "selections": [ + (v2/*: any*/), { "kind": "InlineFragment", "selections": [ + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v10/*: any*/), { "args": null, "kind": "FragmentSpread", - "name": "DocumentDetailPageDocumentFragment" + "name": "DocumentTitleFormFragment" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "DocumentActionsDropdownFragment" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "DocumentLayoutDrawerFragment" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "DocumentControlsTabFragment" + }, + { + "alias": "versions", + "args": null, + "concreteType": "DocumentVersionConnection", + "kind": "LinkedField", + "name": "__DocumentLayout_versions_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersionEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersion", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v11/*: any*/), + (v12/*: any*/), + (v13/*: any*/), + (v14/*: any*/), + (v15/*: any*/), + (v5/*: any*/), + (v7/*: any*/), + (v16/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "DocumentSignaturesTab_version" + }, + { + "alias": "signatures", + "args": null, + "concreteType": "DocumentVersionSignatureConnection", + "kind": "LinkedField", + "name": "__DocumentDetailPage_signatures_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersionSignatureEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersionSignature", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v17/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "signedBy", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "DocumentSignaturesTab_signature" + }, + (v2/*: any*/) + ], + "storageKey": null + }, + (v18/*: any*/) + ], + "storageKey": null + }, + (v21/*: any*/), + (v22/*: any*/) + ], + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + }, + (v18/*: any*/) + ], + "storageKey": null + }, + (v21/*: any*/), + (v22/*: any*/) + ], + "storageKey": null } ], "type": "Document", @@ -191,10 +472,10 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "DocumentGraphNodeQuery", + "name": "DocumentLayoutQuery", "selections": [ { - "alias": null, + "alias": "document", "args": (v1/*: any*/), "concreteType": null, "kind": "LinkedField", @@ -206,22 +487,11 @@ return { { "kind": "InlineFragment", "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "title", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "documentType", - "storageKey": null - }, (v4/*: any*/), - (v6/*: any*/), + (v5/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v10/*: any*/), { "alias": "canUpdate", "args": [ @@ -248,148 +518,16 @@ return { "name": "permission", "storageKey": "permission(action:\"core:document:delete\")" }, - { - "alias": "canPublish", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document-version:publish" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document-version:publish\")" - }, { "alias": null, - "args": (v7/*: any*/), - "concreteType": "ControlConnection", - "kind": "LinkedField", - "name": "controls", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "ControlEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Control", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v3/*: any*/), - (v8/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "sectionTitle", - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "Framework", - "kind": "LinkedField", - "name": "framework", - "plural": false, - "selections": [ - (v3/*: any*/), - (v8/*: any*/) - ], - "storageKey": null - }, - (v2/*: any*/) - ], - "storageKey": null - }, - (v9/*: any*/) - ], - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "PageInfo", - "kind": "LinkedField", - "name": "pageInfo", - "plural": false, - "selections": [ - (v10/*: any*/), - (v11/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "hasPreviousPage", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "startCursor", - "storageKey": null - } - ], - "storageKey": null - }, - (v12/*: any*/) - ], - "storageKey": "controls(first:20)" - }, - { - "alias": null, - "args": (v7/*: any*/), - "filters": [ - "orderBy", - "filter" - ], - "handle": "connection", - "key": "DocumentControlsTab_controls", - "kind": "LinkedHandle", - "name": "controls" - }, - { - "alias": "controlsInfo", - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 0 - } - ], - "concreteType": "ControlConnection", - "kind": "LinkedField", - "name": "controls", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "totalCount", - "storageKey": null - } - ], - "storageKey": "controls(first:0)" - }, - { - "alias": null, - "args": (v7/*: any*/), + "args": (v23/*: any*/), "concreteType": "DocumentVersionConnection", "kind": "LinkedField", "name": "versions", "plural": false, "selections": [ + (v9/*: any*/), + (v22/*: any*/), { "alias": null, "args": null, @@ -407,66 +545,25 @@ return { "plural": false, "selections": [ (v3/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "content", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "status", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "publishedAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "version", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "updatedAt", - "storageKey": null - }, - (v4/*: any*/), - (v6/*: any*/), - { - "alias": "canDeleteDraft", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "core:document-version:delete-draft" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"core:document-version:delete-draft\")" - }, + (v12/*: any*/), + (v11/*: any*/), + (v2/*: any*/), + (v13/*: any*/), + (v14/*: any*/), + (v15/*: any*/), + (v5/*: any*/), + (v7/*: any*/), + (v16/*: any*/), { "alias": "canRequestSignature", - "args": (v13/*: any*/), + "args": (v24/*: any*/), "kind": "ScalarField", "name": "permission", "storageKey": "permission(action:\"core:document-version:request-signature\")" }, { "alias": null, - "args": (v14/*: any*/), + "args": (v25/*: any*/), "concreteType": "DocumentVersionSignatureConnection", "kind": "LinkedField", "name": "signatures", @@ -489,13 +586,7 @@ return { "plural": false, "selections": [ (v3/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "state", - "storageKey": null - }, + (v17/*: any*/), { "alias": null, "args": null, @@ -505,7 +596,7 @@ return { "plural": false, "selections": [ (v3/*: any*/), - (v5/*: any*/), + (v6/*: any*/), { "alias": null, "args": null, @@ -532,7 +623,7 @@ return { }, { "alias": "canCancel", - "args": (v13/*: any*/), + "args": (v24/*: any*/), "kind": "ScalarField", "name": "permission", "storageKey": "permission(action:\"core:document-version:request-signature\")" @@ -541,18 +632,18 @@ return { ], "storageKey": null }, - (v9/*: any*/) + (v18/*: any*/) ], "storageKey": null }, - (v15/*: any*/), - (v12/*: any*/) + (v21/*: any*/), + (v22/*: any*/) ], "storageKey": "signatures(first:1000)" }, { "alias": null, - "args": (v14/*: any*/), + "args": (v25/*: any*/), "filters": [ "filter" ], @@ -563,34 +654,136 @@ return { }, { "alias": null, - "args": (v14/*: any*/), + "args": (v25/*: any*/), "filters": [], "handle": "connection", "key": "DocumentDetailPage_signatures", "kind": "LinkedHandle", "name": "signatures" - }, - (v2/*: any*/) + } ], "storageKey": null }, - (v9/*: any*/) + (v18/*: any*/) ], "storageKey": null }, - (v15/*: any*/), - (v12/*: any*/) + (v21/*: any*/) ], "storageKey": "versions(first:20)" }, { "alias": null, - "args": (v7/*: any*/), + "args": (v23/*: any*/), "filters": null, "handle": "connection", - "key": "DocumentDetailPage_versions", + "key": "DocumentLayout_versions", "kind": "LinkedHandle", "name": "versions" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "documentType", + "storageKey": null + }, + { + "alias": null, + "args": (v23/*: any*/), + "concreteType": "ControlConnection", + "kind": "LinkedField", + "name": "controls", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ControlEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Control", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v26/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "sectionTitle", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v3/*: any*/), + (v26/*: any*/) + ], + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + }, + (v18/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + (v19/*: any*/), + (v20/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + }, + (v22/*: any*/) + ], + "storageKey": "controls(first:20)" + }, + { + "alias": null, + "args": (v23/*: any*/), + "filters": [ + "orderBy", + "filter" + ], + "handle": "connection", + "key": "DocumentControlsTab_controls", + "kind": "LinkedHandle", + "name": "controls" } ], "type": "Document", @@ -602,16 +795,34 @@ return { ] }, "params": { - "cacheID": "1fe5b5e75ab7a7aa4b5859ec1c5560c7", + "cacheID": "fdb52683d7fce578839cfff215840057", "id": null, - "metadata": {}, - "name": "DocumentGraphNodeQuery", + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": null + }, + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "document", + "versions" + ] + } + ] + }, + "name": "DocumentLayoutQuery", "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 classification\n owner {\n id\n fullName\n }\n canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n canPublish: permission(action: \"core:document-version:publish\")\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 canDeleteDraft: permission(action: \"core:document-version:delete-draft\")\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 canCancel: permission(action: \"core:document-version:request-signature\")\n}\n\nfragment DocumentSignaturesTab_version on DocumentVersion {\n id\n status\n canRequestSignature: permission(action: \"core:document-version:request-signature\")\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 DocumentLayoutQuery(\n $documentId: ID!\n) {\n document: node(id: $documentId) {\n __typename\n ... on Document {\n id\n title\n classification\n owner {\n id\n fullName\n }\n canPublish: permission(action: \"core:document-version:publish\")\n controlInfo: controls(first: 0) {\n totalCount\n }\n ...DocumentTitleFormFragment\n ...DocumentActionsDropdownFragment\n ...DocumentLayoutDrawerFragment\n ...DocumentControlsTabFragment\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 canDeleteDraft: permission(action: \"core:document-version:delete-draft\")\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 id\n }\n}\n\nfragment DocumentActionsDropdownFragment on Document {\n id\n title\n canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n versions(first: 20) {\n totalCount\n }\n ...UpdateVersionDialogFragment\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 DocumentLayoutDrawerFragment on Document {\n id\n documentType\n canUpdate: permission(action: \"core:document:update\")\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 canCancel: permission(action: \"core:document-version:request-signature\")\n}\n\nfragment DocumentSignaturesTab_version on DocumentVersion {\n id\n status\n canRequestSignature: permission(action: \"core:document-version:request-signature\")\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 DocumentTitleFormFragment on Document {\n id\n title\n canUpdate: permission(action: \"core:document:update\")\n}\n\nfragment LinkedControlsCardFragment on Control {\n id\n name\n sectionTitle\n framework {\n id\n name\n }\n}\n\nfragment UpdateVersionDialogFragment on Document {\n id\n versions(first: 20) {\n edges {\n node {\n id\n status\n content\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" } }; })(); -(node as any).hash = "f7e5a8ebb67a7668627c01ca1c4c6b19"; +(node as any).hash = "eb6bf3db2691763a5d1f4f2435f986e6"; export default node; diff --git a/apps/console/src/__generated__/core/DocumentLayout_publishVersionMutation.graphql.ts b/apps/console/src/__generated__/core/DocumentLayout_publishVersionMutation.graphql.ts new file mode 100644 index 000000000..6ade228d3 --- /dev/null +++ b/apps/console/src/__generated__/core/DocumentLayout_publishVersionMutation.graphql.ts @@ -0,0 +1,106 @@ +/** + * @generated SignedSource<<4617b6f203cf05562dccc80767cd0f9b>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type PublishDocumentVersionInput = { + changelog?: string | null | undefined; + documentId: string; +}; +export type DocumentLayout_publishVersionMutation$variables = { + input: PublishDocumentVersionInput; +}; +export type DocumentLayout_publishVersionMutation$data = { + readonly publishDocumentVersion: { + readonly document: { + readonly id: string; + }; + }; +}; +export type DocumentLayout_publishVersionMutation = { + response: DocumentLayout_publishVersionMutation$data; + variables: DocumentLayout_publishVersionMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "PublishDocumentVersionPayload", + "kind": "LinkedField", + "name": "publishDocumentVersion", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Document", + "kind": "LinkedField", + "name": "document", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "DocumentLayout_publishVersionMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "DocumentLayout_publishVersionMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "2d64ca66f58d2c312868cb6de4d01fd4", + "id": null, + "metadata": {}, + "name": "DocumentLayout_publishVersionMutation", + "operationKind": "mutation", + "text": "mutation DocumentLayout_publishVersionMutation(\n $input: PublishDocumentVersionInput!\n) {\n publishDocumentVersion(input: $input) {\n document {\n id\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "226a81f075451d6db4d4f49e4bab70d6"; + +export default node; diff --git a/apps/console/src/__generated__/core/DocumentTitleFormFragment.graphql.ts b/apps/console/src/__generated__/core/DocumentTitleFormFragment.graphql.ts new file mode 100644 index 000000000..8111072b0 --- /dev/null +++ b/apps/console/src/__generated__/core/DocumentTitleFormFragment.graphql.ts @@ -0,0 +1,64 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type DocumentTitleFormFragment$data = { + readonly canUpdate: boolean; + readonly id: string; + readonly title: string; + readonly " $fragmentType": "DocumentTitleFormFragment"; +}; +export type DocumentTitleFormFragment$key = { + readonly " $data"?: DocumentTitleFormFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"DocumentTitleFormFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "DocumentTitleFormFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "title", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:document:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:document:update\")" + } + ], + "type": "Document", + "abstractKey": null +}; + +(node as any).hash = "5fa870524b66e0b68c0f898982e56ce0"; + +export default node; diff --git a/apps/console/src/__generated__/core/DocumentTitleFormMutation.graphql.ts b/apps/console/src/__generated__/core/DocumentTitleFormMutation.graphql.ts new file mode 100644 index 000000000..9a20d9f71 --- /dev/null +++ b/apps/console/src/__generated__/core/DocumentTitleFormMutation.graphql.ts @@ -0,0 +1,163 @@ +/** + * @generated SignedSource<<4cf4c96941ac10dd2b17b67c89a1dfe2>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +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; + ownerId?: string | null | undefined; + title?: string | null | undefined; + trustCenterVisibility?: TrustCenterVisibility | null | undefined; +}; +export type DocumentTitleFormMutation$variables = { + input: UpdateDocumentInput; +}; +export type DocumentTitleFormMutation$data = { + readonly updateDocument: { + readonly document: { + readonly " $fragmentSpreads": FragmentRefs<"DocumentTitleFormFragment">; + }; + }; +}; +export type DocumentTitleFormMutation = { + response: DocumentTitleFormMutation$data; + variables: DocumentTitleFormMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "DocumentTitleFormMutation", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "UpdateDocumentPayload", + "kind": "LinkedField", + "name": "updateDocument", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Document", + "kind": "LinkedField", + "name": "document", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "DocumentTitleFormFragment" + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "DocumentTitleFormMutation", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "UpdateDocumentPayload", + "kind": "LinkedField", + "name": "updateDocument", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Document", + "kind": "LinkedField", + "name": "document", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "title", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:document:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:document:update\")" + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "f3a48357b051c3b8d7fa74d57a495b63", + "id": null, + "metadata": {}, + "name": "DocumentTitleFormMutation", + "operationKind": "mutation", + "text": "mutation DocumentTitleFormMutation(\n $input: UpdateDocumentInput!\n) {\n updateDocument(input: $input) {\n document {\n ...DocumentTitleFormFragment\n id\n }\n }\n}\n\nfragment DocumentTitleFormFragment on Document {\n id\n title\n canUpdate: permission(action: \"core:document:update\")\n}\n" + } +}; +})(); + +(node as any).hash = "25241f7d6938767f77c8d415ca5e2187"; + +export default node; diff --git a/apps/console/src/__generated__/core/DocumentVersionsDropdownItemFragment.graphql.ts b/apps/console/src/__generated__/core/DocumentVersionsDropdownItemFragment.graphql.ts new file mode 100644 index 000000000..f29703dd9 --- /dev/null +++ b/apps/console/src/__generated__/core/DocumentVersionsDropdownItemFragment.graphql.ts @@ -0,0 +1,75 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +export type DocumentStatus = "DRAFT" | "PUBLISHED"; +import { FragmentRefs } from "relay-runtime"; +export type DocumentVersionsDropdownItemFragment$data = { + readonly id: string; + readonly publishedAt: string | null | undefined; + readonly status: DocumentStatus; + readonly updatedAt: string; + readonly version: number; + readonly " $fragmentType": "DocumentVersionsDropdownItemFragment"; +}; +export type DocumentVersionsDropdownItemFragment$key = { + readonly " $data"?: DocumentVersionsDropdownItemFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"DocumentVersionsDropdownItemFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "DocumentVersionsDropdownItemFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "version", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "publishedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + } + ], + "type": "DocumentVersion", + "abstractKey": null +}; + +(node as any).hash = "923dfbcf3e171fed5ea6509a43c1b4ce"; + +export default node; diff --git a/apps/console/src/__generated__/core/DocumentVersionsDropdownMenuQuery.graphql.ts b/apps/console/src/__generated__/core/DocumentVersionsDropdownMenuQuery.graphql.ts new file mode 100644 index 000000000..121a547c7 --- /dev/null +++ b/apps/console/src/__generated__/core/DocumentVersionsDropdownMenuQuery.graphql.ts @@ -0,0 +1,246 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type DocumentVersionsDropdownMenuQuery$variables = { + documentId: string; +}; +export type DocumentVersionsDropdownMenuQuery$data = { + readonly document: { + readonly __typename: "Document"; + readonly versions: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"DocumentVersionsDropdownItemFragment">; + }; + }>; + }; + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other"; + }; +}; +export type DocumentVersionsDropdownMenuQuery = { + response: DocumentVersionsDropdownMenuQuery$data; + variables: DocumentVersionsDropdownMenuQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "documentId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "documentId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v3 = [ + { + "kind": "Literal", + "name": "first", + "value": 20 + } +], +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "DocumentVersionsDropdownMenuQuery", + "selections": [ + { + "alias": "document", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v3/*: any*/), + "concreteType": "DocumentVersionConnection", + "kind": "LinkedField", + "name": "versions", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersionEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersion", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v4/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "DocumentVersionsDropdownItemFragment" + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "versions(first:20)" + } + ], + "type": "Document", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "DocumentVersionsDropdownMenuQuery", + "selections": [ + { + "alias": "document", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v3/*: any*/), + "concreteType": "DocumentVersionConnection", + "kind": "LinkedField", + "name": "versions", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersionEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersion", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "version", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "publishedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "versions(first:20)" + } + ], + "type": "Document", + "abstractKey": null + }, + (v4/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "914f2c11640879dfb202ba668fb0bff0", + "id": null, + "metadata": {}, + "name": "DocumentVersionsDropdownMenuQuery", + "operationKind": "query", + "text": "query DocumentVersionsDropdownMenuQuery(\n $documentId: ID!\n) {\n document: node(id: $documentId) {\n __typename\n ... on Document {\n versions(first: 20) {\n edges {\n node {\n id\n ...DocumentVersionsDropdownItemFragment\n }\n }\n }\n }\n id\n }\n}\n\nfragment DocumentVersionsDropdownItemFragment on DocumentVersion {\n id\n version\n status\n publishedAt\n updatedAt\n}\n" + } +}; +})(); + +(node as any).hash = "4cc270e68cee032d619dc7a348715d83"; + +export default node; diff --git a/apps/console/src/__generated__/core/UpdateVersionDialogFragment.graphql.ts b/apps/console/src/__generated__/core/UpdateVersionDialogFragment.graphql.ts new file mode 100644 index 000000000..9f028583a --- /dev/null +++ b/apps/console/src/__generated__/core/UpdateVersionDialogFragment.graphql.ts @@ -0,0 +1,166 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +export type DocumentStatus = "DRAFT" | "PUBLISHED"; +import { FragmentRefs } from "relay-runtime"; +export type UpdateVersionDialogFragment$data = { + readonly id: string; + readonly versions: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly content: string; + readonly id: string; + readonly status: DocumentStatus; + }; + }>; + }; + readonly " $fragmentType": "UpdateVersionDialogFragment"; +}; +export type UpdateVersionDialogFragment$key = { + readonly " $data"?: UpdateVersionDialogFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"UpdateVersionDialogFragment">; +}; + +const node: ReaderFragment = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "versions" + ] + } + ] + }, + "name": "UpdateVersionDialogFragment", + "selections": [ + (v0/*: any*/), + { + "alias": "versions", + "args": null, + "concreteType": "DocumentVersionConnection", + "kind": "LinkedField", + "name": "__DocumentLayout_versions_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersionEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "DocumentVersion", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "content", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + } + ], + "type": "Document", + "abstractKey": null +}; +})(); + +(node as any).hash = "0d3c09ff97434f6ce034801e7a22fd22"; + +export default node; diff --git a/apps/console/src/components/documents/PdfDownloadDialog.tsx b/apps/console/src/components/documents/PdfDownloadDialog.tsx index 367e0b67e..a5e7411aa 100644 --- a/apps/console/src/components/documents/PdfDownloadDialog.tsx +++ b/apps/console/src/components/documents/PdfDownloadDialog.tsx @@ -27,7 +27,7 @@ const pdfDownloadSchema = z.object({ type PdfDownloadFormData = z.infer; type Props = { - children: ReactNode; + children?: ReactNode; onDownload: (options: PdfDownloadFormData) => void; isLoading?: boolean; defaultEmail: string; diff --git a/apps/console/src/hooks/graph/DocumentGraph.ts b/apps/console/src/hooks/graph/DocumentGraph.ts index 9a0112571..27055c4b1 100644 --- a/apps/console/src/hooks/graph/DocumentGraph.ts +++ b/apps/console/src/hooks/graph/DocumentGraph.ts @@ -117,13 +117,3 @@ export function useBulkExportDocumentsMutation() { }, ); } - -export const documentNodeQuery = graphql` - query DocumentGraphNodeQuery($documentId: ID!) { - node(id: $documentId) { - ... on Document { - ...DocumentDetailPageDocumentFragment - } - } - } -`; diff --git a/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx b/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx deleted file mode 100644 index 0cd2dcc8a..000000000 --- a/apps/console/src/pages/organizations/documents/DocumentDetailPage.tsx +++ /dev/null @@ -1,823 +0,0 @@ -import { - documentClassifications, - documentTypes, - formatDate, - getDocumentClassificationLabel, - getDocumentTypeLabel, - sprintf, -} from "@probo/helpers"; -import { usePageTitle } from "@probo/hooks"; -import { useTranslate } from "@probo/i18n"; -import { - ActionDropdown, - Avatar, - Badge, - Breadcrumb, - Button, - Drawer, - Dropdown, - DropdownItem, - IconArrowDown, - IconCheckmark1, - IconChevronDown, - IconClock, - IconCrossLargeX, - IconPencil, - IconTrashCan, - Input, - PageHeader, - PropertyRow, - TabBadge, - TabLink, - Tabs, - useConfirm, -} from "@probo/ui"; -import { clsx } from "clsx"; -import { use, useRef, useState } from "react"; -import type { PreloadedQuery } from "react-relay"; -import { - graphql, - loadQuery, - useFragment, - usePreloadedQuery, -} from "react-relay"; -import { - Link, - Outlet, - useLocation, - useNavigate, - useParams, -} from "react-router"; -import { z } from "zod"; - -import type { - DocumentDetailPageDocumentFragment$data, - DocumentDetailPageDocumentFragment$key, -} from "#/__generated__/core/DocumentDetailPageDocumentFragment.graphql"; -import type { DocumentDetailPageExportPDFMutation } from "#/__generated__/core/DocumentDetailPageExportPDFMutation.graphql"; -import type { DocumentDetailPageUpdateMutation } from "#/__generated__/core/DocumentDetailPageUpdateMutation.graphql"; -import type { DocumentGraphNodeQuery } from "#/__generated__/core/DocumentGraphNodeQuery.graphql"; -import { - PdfDownloadDialog, - type PdfDownloadDialogRef, -} from "#/components/documents/PdfDownloadDialog"; -import { ControlledField } from "#/components/form/ControlledField"; -import { DocumentClassificationOptions } from "#/components/form/DocumentClassificationOptions"; -import { DocumentTypeOptions } from "#/components/form/DocumentTypeOptions"; -import { PeopleSelectField } from "#/components/form/PeopleSelectField"; -import { - documentNodeQuery, - useDeleteDocumentMutation, - useDeleteDraftDocumentVersionMutation, -} from "#/hooks/graph/DocumentGraph"; -import { useFormWithSchema } from "#/hooks/useFormWithSchema"; -import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; -import { useOrganizationId } from "#/hooks/useOrganizationId"; -import { CurrentUser } from "#/providers/CurrentUser"; -import type { NodeOf } from "#/types"; - -import UpdateVersionDialog from "./_components/UpdateVersionDialog"; - -type Props = { - queryRef: PreloadedQuery; -}; - -const documentFragment = graphql` - fragment DocumentDetailPageDocumentFragment on Document { - id - title - documentType - classification - owner { - id - fullName - } - canUpdate: permission(action: "core:document:update") - canDelete: permission(action: "core:document:delete") - canPublish: permission(action: "core:document-version:publish") - ...DocumentControlsTabFragment - controlsInfo: controls(first: 0) { - totalCount - } - versions(first: 20) @connection(key: "DocumentDetailPage_versions") { - __id - edges { - node { - id - content - status - publishedAt - version - updatedAt - classification - owner { - id - fullName - } - canDeleteDraft: permission( - action: "core:document-version:delete-draft" - ) - ...DocumentSignaturesTab_version - signatures(first: 1000) - @connection(key: "DocumentDetailPage_signatures", filters: []) { - __id - edges { - node { - id - state - signedBy { - id - } - ...DocumentSignaturesTab_signature - } - } - } - } - } - } - } -`; - -const publishDocumentVersionMutation = graphql` - mutation DocumentDetailPagePublishMutation( - $input: PublishDocumentVersionInput! - ) { - publishDocumentVersion(input: $input) { - document { - ...DocumentDetailPageDocumentFragment - } - } - } -`; - -const exportDocumentVersionPDFMutation = graphql` - mutation DocumentDetailPageExportPDFMutation( - $input: ExportDocumentVersionPDFInput! - ) { - exportDocumentVersionPDF(input: $input) { - data - } - } -`; - -const updateDocumentMutation = graphql` - mutation DocumentDetailPageUpdateMutation($input: UpdateDocumentInput!) { - updateDocument(input: $input) { - document { - id - title - documentType - classification - owner { - id - fullName - } - } - } - } -`; - -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), -}); - -export default function DocumentDetailPage(props: Props) { - const { versionId } = useParams<{ versionId?: string }>(); - const node = usePreloadedQuery(documentNodeQuery, props.queryRef).node; - const document = useFragment( - documentFragment, - node, - ); - const { __ } = useTranslate(); - const organizationId = useOrganizationId(); - const navigate = useNavigate(); - - 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 - ?? document.versions.edges[0].node; - const signatures = currentVersion.signatures?.edges?.map(s => s.node) ?? []; - const signedSignatures = signatures.filter(s => s.state === "SIGNED"); - const isDraft = currentVersion.status === "DRAFT"; - const [publishDocumentVersion, isPublishing] = useMutationWithToasts( - publishDocumentVersionMutation, - { - successMessage: __("Document published successfully."), - errorMessage: __("Failed to publish document"), - }, - ); - const [deleteDocument, isDeleting] = useDeleteDocumentMutation(); - const [deleteDraftDocumentVersion, isDeletingDraft] - = useDeleteDraftDocumentVersionMutation(); - const [exportDocumentVersionPDF, isExporting] - = useMutationWithToasts( - exportDocumentVersionPDFMutation, - { - successMessage: __("PDF download started."), - errorMessage: __("Failed to generate PDF"), - }, - ); - - const { email: defaultEmail } = use(CurrentUser); - const [updateDocument, isUpdatingDocument] - = useMutationWithToasts( - updateDocumentMutation, - { - successMessage: __("Document updated successfully."), - errorMessage: __("Failed to update document"), - }, - ); - const versionConnectionId = document.versions.__id; - - const { register, control, handleSubmit, reset } = useFormWithSchema( - documentUpdateSchema, - { - defaultValues: { - title: document.title, - ownerId: currentVersion.owner?.id || "", - documentType: document.documentType, - classification: currentVersion.classification, - }, - }, - ); - - usePageTitle(document.title); - - const handleUpdateTitle = async (data: { title: string }) => { - await updateDocument({ - variables: { - input: { - id: document.id, - title: data.title, - }, - }, - onSuccess: () => { - setIsEditingTitle(false); - }, - }); - }; - - const handleUpdateOwner = async (data: { ownerId: string }) => { - await updateDocument({ - variables: { - input: { - id: document.id, - ownerId: data.ownerId, - }, - }, - onSuccess: () => { - setIsEditingOwner(false); - }, - }); - }; - - const handleUpdateDocumentType = async (data: { - documentType: (typeof documentTypes)[number]; - }) => { - await updateDocument({ - variables: { - input: { - id: document.id, - documentType: data.documentType, - }, - }, - onSuccess: () => { - setIsEditingType(false); - loadQuery( - props.queryRef.environment, - documentNodeQuery, - props.queryRef.variables, - { fetchPolicy: "network-only" }, - ); - }, - }); - }; - - const handleUpdateClassification = async (data: { - classification: (typeof documentClassifications)[number]; - }) => { - await 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 = async () => { - await publishDocumentVersion({ - variables: { - input: { documentId: document.id }, - }, - }); - }; - - const confirm = useConfirm(); - - const handleDelete = () => { - confirm( - () => - new Promise((resolve) => { - void deleteDocument({ - variables: { - input: { documentId: document.id }, - }, - onSuccess() { - void navigate(`/organizations/${organizationId}/documents`); - resolve(); - }, - onError: () => resolve(), - }); - }), - { - message: sprintf( - __( - "This will permanently delete the document \"%s\". This action cannot be undone.", - ), - document.title, - ), - }, - ); - }; - - const handleDeleteDraft = () => { - confirm( - () => - new Promise((resolve) => { - void deleteDraftDocumentVersion({ - variables: { - input: { documentVersionId: currentVersion.id }, - connections: [versionConnectionId], - }, - onSuccess() { - loadQuery( - props.queryRef.environment, - documentNodeQuery, - props.queryRef.variables, - { fetchPolicy: "network-only" }, - ); - - resolve(); - }, - onError: () => resolve(), - }); - }), - { - message: sprintf( - __( - "This will permanently delete the draft version %s of \"%s\". This action cannot be undone.", - ), - currentVersion.version, - document.title, - ), - }, - ); - }; - - const handleDownloadPdf = async (options: { - withWatermark: boolean; - withSignatures: boolean; - watermarkEmail?: string; - }) => { - const input = { - documentVersionId: currentVersion.id, - withWatermark: options.withWatermark, - withSignatures: options.withSignatures, - ...(options.withWatermark - && options.watermarkEmail && { watermarkEmail: options.watermarkEmail }), - }; - - await exportDocumentVersionPDF({ - variables: { input }, - onCompleted: (data) => { - if (data.exportDocumentVersionPDF?.data) { - const link = window.document.createElement("a"); - link.href = data.exportDocumentVersionPDF.data; - link.download = `${document.title}-v${currentVersion.version}.pdf`; - window.document.body.appendChild(link); - link.click(); - window.document.body.removeChild(link); - } - }, - }); - }; - - const updateDialogRef = useRef<{ open: () => void }>(null); - const pdfDownloadDialogRef = useRef(null); - const controlsCount = document.controlsInfo.totalCount; - const urlPrefix = versionId - ? `/organizations/${organizationId}/documents/${document.id}/versions/${versionId}` - : `/organizations/${organizationId}/documents/${document.id}`; - - return ( - <> - - void handleDownloadPdf(options)} - isLoading={isExporting} - defaultEmail={defaultEmail} - > - {null} - -
-
- -
- {isDraft && document.canPublish && ( - - )} - - {__("Version history")} - - - )} - > - {versions.map(version => ( - - {}} - /> - - ))} - - - - {document.canUpdate && ( - updateDialogRef.current?.open()} - icon={IconPencil} - > - {isDraft ? __("Edit draft document") : __("Create new draft")} - - )} - {isDraft - && versions.length > 1 - && currentVersion.canDeleteDraft && ( - - {__("Delete draft document")} - - )} - pdfDownloadDialogRef.current?.open()} - icon={IconArrowDown} - disabled={isExporting} - > - {__("Download PDF")} - - {document.canDelete && ( - - {__("Delete document")} - - )} - -
-
- - { - if (e.key === "Escape") { - setIsEditingTitle(false); - reset(); - } - if (e.key === "Enter") { - void handleSubmit(handleUpdateTitle)(); - } - }} - /> -
- ) - : ( -
- {document.title} - {document.canUpdate && ( -
- ) - } - /> - - - {__("Description")} - - {__("Controls")} - {controlsCount} - - {!isDraft && ( - - {__("Signatures")} - - {signedSignatures.length} - / - {signatures.length} - - - )} - - - - - -
- {__("Properties")} -
- - {isEditingOwner - ? ( - void handleSubmit(handleUpdateOwner)()} - onCancel={() => { - setIsEditingOwner(false); - reset(); - }} - disabled={isUpdatingDocument} - > - - - ) - : ( - setIsEditingOwner(true)} - canEdit={document.canUpdate} - > - - - {currentVersion.owner?.fullName} - - - )} - - - {isEditingType - ? ( - void handleSubmit(handleUpdateDocumentType)()} - onCancel={() => { - setIsEditingType(false); - reset(); - }} - disabled={isUpdatingDocument} - > - - - - - ) - : ( - setIsEditingType(true)} - canEdit={document.canUpdate} - > -
- {getDocumentTypeLabel(__, document.documentType)} -
-
- )} -
- - {isEditingClassification - ? ( - void handleSubmit(handleUpdateClassification)()} - onCancel={() => { - setIsEditingClassification(false); - reset(); - }} - disabled={isUpdatingDocument} - > - - - - - ) - : ( - setIsEditingClassification(true)} - canEdit={document.canUpdate} - > -
- {getDocumentClassificationLabel( - __, - currentVersion.classification, - )} -
-
- )} -
- - - {isDraft ? __("Draft") : __("Published")} - - - -
- {currentVersion.version} -
-
- -
- {formatDate(currentVersion.updatedAt)} -
-
- {currentVersion.publishedAt && ( - -
- {formatDate(currentVersion.publishedAt)} -
-
- )} -
- - ); -} - -type Version = NodeOf; - -function EditablePropertyContent({ - children, - onSave, - onCancel, - disabled, -}: { - children: React.ReactNode; - onSave: () => void; - onCancel: () => void; - disabled?: boolean; -}) { - return ( -
-
{children}
-
- ); -} - -function ReadOnlyPropertyContent({ - children, - onEdit, - canEdit = true, -}: { - children: React.ReactNode; - onEdit: () => void; - canEdit?: boolean; -}) { - return ( -
- {children} - {canEdit && ( -
- ); -} - -function VersionItem({ - document, - version, - active, - onSelect, - ...props -}: { - document: DocumentDetailPageDocumentFragment$data; - version: Version; - active?: boolean; - onSelect: (v: Version) => void; -}) { - const { dateTimeFormat, __ } = useTranslate(); - const organizationId = useOrganizationId(); - const suffix = useLocation().pathname.split("/").at(-1); - return ( - onSelect(version)} - className="flex items-center gap-2 py-2 px-[10px] w-full hover:bg-tertiary-hover cursor-pointer rounded" - {...props} - > -
-
-
- {version.version} -
-
-
-
- {version.status === "DRAFT" && ( - - {__("Draft")} - - )} -
-
- {dateTimeFormat(version.publishedAt ?? version.updatedAt)} -
-
-
- - ); -} diff --git a/apps/console/src/pages/organizations/documents/DocumentLayout.tsx b/apps/console/src/pages/organizations/documents/DocumentLayout.tsx new file mode 100644 index 000000000..505263f62 --- /dev/null +++ b/apps/console/src/pages/organizations/documents/DocumentLayout.tsx @@ -0,0 +1,188 @@ +import { useTranslate } from "@probo/i18n"; +import { Breadcrumb, Button, IconCheckmark1, PageHeader, TabBadge, TabLink, Tabs } from "@probo/ui"; +import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; +import { Outlet, useParams } from "react-router"; +import { graphql } from "relay-runtime"; + +import type { DocumentLayoutQuery } from "#/__generated__/core/DocumentLayoutQuery.graphql"; +import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; + +import { DocumentActionsDropdownn } from "./_components/DocumentActionsDropdown"; +import { DocumentLayoutDrawer } from "./_components/DocumentLayoutDrawer"; +import { DocumentTitleForm } from "./_components/DocumentTitleForm"; +import { DocumentVersionsDropdown } from "./_components/DocumentVersionsDropdown"; + +export const documentLayoutQuery = graphql` + query DocumentLayoutQuery($documentId: ID!) { + document: node(id: $documentId) { + __typename + ... on Document { + id + title + classification + owner { + id + fullName + } + canPublish: permission(action: "core:document-version:publish") + controlInfo: controls(first: 0) { + totalCount + } + ...DocumentTitleFormFragment + ...DocumentActionsDropdownFragment + ...DocumentLayoutDrawerFragment + ...DocumentControlsTabFragment + versions(first: 20) @connection(key: "DocumentLayout_versions") { + __id + edges { + node { + id + content + status + publishedAt + version + updatedAt + classification + owner { + id + fullName + } + canDeleteDraft: permission( + action: "core:document-version:delete-draft" + ) + ...DocumentSignaturesTab_version + signatures(first: 1000) + @connection(key: "DocumentDetailPage_signatures", filters: []) { + __id + edges { + node { + id + state + signedBy { + id + } + ...DocumentSignaturesTab_signature + } + } + } + } + } + } + } + } + } +`; + +const publishDocumentVersionMutation = graphql` + mutation DocumentLayout_publishVersionMutation( + $input: PublishDocumentVersionInput! + ) { + publishDocumentVersion(input: $input) { + document { + id + } + } + } +`; + +export function DocumentLayout(props: { queryRef: PreloadedQuery }) { + const { queryRef } = props; + + const organizationId = useOrganizationId(); + const { versionId } = useParams(); + + const { __ } = useTranslate(); + + const { document } = usePreloadedQuery(documentLayoutQuery, queryRef); + if (document.__typename !== "Document") { + throw new Error("invalid node type"); + } + + const currentVersion + = document.versions.edges.find(v => v.node.id === versionId)?.node + ?? document.versions.edges[0].node; + const isDraft = currentVersion.status === "DRAFT"; + const signatures = currentVersion.signatures?.edges?.map(s => s.node) ?? []; + const signedSignatures = signatures.filter(s => s.state === "SIGNED"); + + const [publishDocumentVersion, isPublishing] = useMutationWithToasts( + publishDocumentVersionMutation, + { + successMessage: __("Document published successfully."), + errorMessage: __("Failed to publish document"), + }, + ); + + const handlePublish = async () => { + await publishDocumentVersion({ + variables: { + input: { documentId: document.id }, + }, + }); + }; + + const urlPrefix = versionId + ? `/organizations/${organizationId}/documents/${document.id}/versions/${versionId}` + : `/organizations/${organizationId}/documents/${document.id}`; + + return ( + <> +
+
+ + +
+ {isDraft && document.canPublish && ( + + )} + + +
+
+ + } + /> + + + {__("Description")} + + {__("Controls")} + {document.controlInfo.totalCount} + + {!isDraft && ( + + {__("Signatures")} + + {signedSignatures.length} + / + {signatures.length} + + + )} + + + +
+ + + + ); +} diff --git a/apps/console/src/pages/organizations/documents/DocumentLayoutLoader.tsx b/apps/console/src/pages/organizations/documents/DocumentLayoutLoader.tsx new file mode 100644 index 000000000..ad4762d50 --- /dev/null +++ b/apps/console/src/pages/organizations/documents/DocumentLayoutLoader.tsx @@ -0,0 +1,37 @@ +import { Suspense, useEffect } from "react"; +import { useQueryLoader } from "react-relay"; +import { useParams } from "react-router"; + +import type { DocumentLayoutQuery } from "#/__generated__/core/DocumentLayoutQuery.graphql"; +import { PageSkeleton } from "#/components/skeletons/PageSkeleton"; +import { CoreRelayProvider } from "#/providers/CoreRelayProvider"; + +import { DocumentLayout, documentLayoutQuery } from "./DocumentLayout"; + +function DocumentLayoutQueryLoader() { + const { documentId } = useParams(); + if (!documentId) { + throw new Error(":documentId missing in route params"); + } + const [queryRef, loadQuery] = useQueryLoader(documentLayoutQuery); + + useEffect(() => { + if (!queryRef) { + loadQuery({ documentId }); + } + }); + + if (!queryRef) return ; + + return ; +} + +export default function DocumentLayoutLoader() { + return ( + + }> + + + + ); +} diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentActionsDropdown.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentActionsDropdown.tsx new file mode 100644 index 000000000..9573f3f7f --- /dev/null +++ b/apps/console/src/pages/organizations/documents/_components/DocumentActionsDropdown.tsx @@ -0,0 +1,220 @@ +import { sprintf } from "@probo/helpers"; +import { useTranslate } from "@probo/i18n"; +import { ActionDropdown, DropdownItem, IconArrowDown, IconPencil, IconTrashCan, useConfirm } from "@probo/ui"; +import { use, useRef } from "react"; +import { loadQuery, useFragment } from "react-relay"; +import { useNavigate } from "react-router"; +import { graphql } from "relay-runtime"; + +import type { DocumentActionsDropdownFragment$key } from "#/__generated__/core/DocumentActionsDropdownFragment.graphql"; +import type { DocumentActionsDropdownn_exportVersionMutation } from "#/__generated__/core/DocumentActionsDropdownn_exportVersionMutation.graphql"; +import type { DocumentLayoutQuery$data } from "#/__generated__/core/DocumentLayoutQuery.graphql"; +import { PdfDownloadDialog, type PdfDownloadDialogRef } from "#/components/documents/PdfDownloadDialog"; +import { coreEnvironment } from "#/environments"; +import { useDeleteDocumentMutation, useDeleteDraftDocumentVersionMutation } from "#/hooks/graph/DocumentGraph"; +import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; +import { CurrentUser } from "#/providers/CurrentUser"; +import type { NodeOf } from "#/types"; + +import { documentLayoutQuery } from "../DocumentLayout"; + +import UpdateVersionDialog from "./UpdateVersionDialog"; + +const fragment = graphql` + fragment DocumentActionsDropdownFragment on Document { + id + title + canUpdate: permission(action: "core:document:update") + canDelete: permission(action: "core:document:delete") + versions(first: 20) { + __id + totalCount + } + ...UpdateVersionDialogFragment + } +`; + +const exportDocumentVersionMutation = graphql` + mutation DocumentActionsDropdownn_exportVersionMutation( + $input: ExportDocumentVersionPDFInput! + ) { + exportDocumentVersionPDF(input: $input) { + data + } + } +`; + +export function DocumentActionsDropdownn(props: { + currentVersion: NodeOf["versions"]>; + fKey: DocumentActionsDropdownFragment$key; + isDraft: boolean; +}) { + const { currentVersion, fKey, isDraft } = props; + + const organizationId = useOrganizationId(); + const navigate = useNavigate(); + const { __ } = useTranslate(); + const { email: defaultEmail } = use(CurrentUser); + const updateDialogRef = useRef<{ open: () => void }>(null); + const pdfDownloadDialogRef = useRef(null); + const confirm = useConfirm(); + + const document = useFragment(fragment, fKey); + const [deleteDocument, isDeleting] = useDeleteDocumentMutation(); + const [deleteDraftDocumentVersion, isDeletingDraft] + = useDeleteDraftDocumentVersionMutation(); + const [exportDocumentVersion, isExporting] + = useMutationWithToasts( + exportDocumentVersionMutation, + { + successMessage: __("PDF download started."), + errorMessage: __("Failed to generate PDF"), + }, + ); + + const handleDelete = () => { + confirm( + () => + new Promise((resolve) => { + void deleteDocument({ + variables: { + input: { documentId: document.id }, + }, + onSuccess() { + void navigate(`/organizations/${organizationId}/documents`); + resolve(); + }, + onError: () => resolve(), + }); + }), + { + message: sprintf( + __( + "This will permanently delete the document \"%s\". This action cannot be undone.", + ), + document.title, + ), + }, + ); + }; + + const handleDeleteDraft = () => { + confirm( + () => + new Promise((resolve) => { + void deleteDraftDocumentVersion({ + variables: { + input: { documentVersionId: currentVersion.id }, + connections: [document.versions.__id], + }, + onSuccess() { + loadQuery( + coreEnvironment, + documentLayoutQuery, + { documentId: document.id }, + { fetchPolicy: "network-only" }, + ); + + resolve(); + }, + onError: () => resolve(), + }); + }), + { + message: sprintf( + __( + "This will permanently delete the draft version %s of \"%s\". This action cannot be undone.", + ), + currentVersion.version, + document.title, + ), + }, + ); + }; + + const handleExportDocumentVersion = async (options: { + withWatermark: boolean; + withSignatures: boolean; + watermarkEmail?: string; + }) => { + const input = { + documentVersionId: currentVersion.id, + withWatermark: options.withWatermark, + withSignatures: options.withSignatures, + ...(options.withWatermark + && options.watermarkEmail && { watermarkEmail: options.watermarkEmail }), + }; + + await exportDocumentVersion({ + variables: { input }, + onCompleted: (data, errors) => { + if (errors?.length) { + return; + } + + if (data.exportDocumentVersionPDF) { + const link = window.document.createElement("a"); + link.href = data.exportDocumentVersionPDF.data; + link.download = `${document.title}-v${currentVersion.version}.pdf`; + window.document.body.appendChild(link); + link.click(); + window.document.body.removeChild(link); + } + }, + }); + }; + + return ( + <> + + void handleExportDocumentVersion(options)} + isLoading={isExporting} + defaultEmail={defaultEmail} + /> + + {document.canUpdate && ( + updateDialogRef.current?.open()} + icon={IconPencil} + > + {isDraft ? __("Edit draft document") : __("Create new draft")} + + )} + {isDraft + && document.versions.totalCount > 1 + && currentVersion.canDeleteDraft && ( + + {__("Delete draft document")} + + )} + pdfDownloadDialogRef.current?.open()} + icon={IconArrowDown} + disabled={isExporting} + > + {__("Download PDF")} + + {document.canDelete && ( + + {__("Delete document")} + + )} + + + ); +} diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx new file mode 100644 index 000000000..7f46c6d40 --- /dev/null +++ b/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx @@ -0,0 +1,320 @@ +import { documentClassifications, documentTypes, formatDate, getDocumentClassificationLabel, getDocumentTypeLabel } from "@probo/helpers"; +import { useTranslate } from "@probo/i18n"; +import { Avatar, Badge, Button, Drawer, IconCheckmark1, IconCrossLargeX, IconPencil, PropertyRow } from "@probo/ui"; +import { useState } from "react"; +import { loadQuery, useFragment } from "react-relay"; +import { graphql } from "relay-runtime"; +import { z } from "zod"; + +import type { DocumentLayoutDrawerFragment$key } from "#/__generated__/core/DocumentLayoutDrawerFragment.graphql"; +import type { DocumentLayoutDrawerMutation } from "#/__generated__/core/DocumentLayoutDrawerMutation.graphql"; +import type { DocumentLayoutQuery$data } from "#/__generated__/core/DocumentLayoutQuery.graphql"; +import { ControlledField } from "#/components/form/ControlledField"; +import { DocumentClassificationOptions } from "#/components/form/DocumentClassificationOptions"; +import { DocumentTypeOptions } from "#/components/form/DocumentTypeOptions"; +import { PeopleSelectField } from "#/components/form/PeopleSelectField"; +import { coreEnvironment } from "#/environments"; +import { useFormWithSchema } from "#/hooks/useFormWithSchema"; +import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; +import type { NodeOf } from "#/types"; + +import { documentLayoutQuery } from "../DocumentLayout"; + +const fragment = graphql` + fragment DocumentLayoutDrawerFragment on Document { + id + documentType + canUpdate: permission(action: "core:document:update") + } +`; + +const updateDocumentMutation = graphql` + mutation DocumentLayoutDrawerMutation($input: UpdateDocumentInput!) { + updateDocument(input: $input) { + document { + id + documentType + classification + owner { + id + fullName + } + } + } + } +`; + +const schema = z.object({ + ownerId: z.string().min(1, "Owner is required"), + documentType: z.enum(documentTypes), + classification: z.enum(documentClassifications), +}); + +export function DocumentLayoutDrawer(props: { + currentVersion: NodeOf["versions"]>; + fKey: DocumentLayoutDrawerFragment$key; + isDraft: boolean; +}) { + const { currentVersion, fKey, isDraft } = props; + + const organizationId = useOrganizationId(); + const { __ } = useTranslate(); + + const [isEditingOwner, setIsEditingOwner] = useState(false); + const [isEditingType, setIsEditingType] = useState(false); + const [isEditingClassification, setIsEditingClassification] = useState(false); + + const document = useFragment(fragment, fKey); + + const { control, handleSubmit, reset } = useFormWithSchema( + schema, + { + defaultValues: { + ownerId: currentVersion.owner?.id || "", + documentType: document.documentType, + classification: currentVersion.classification, + }, + }, + ); + + const [updateDocument, isUpdatingDocument] + = useMutationWithToasts( + updateDocumentMutation, + { + successMessage: __("Document updated successfully."), + errorMessage: __("Failed to update document"), + }, + ); + + const handleUpdateOwner = async (data: { ownerId: string }) => { + await updateDocument({ + variables: { + input: { + id: document.id, + ownerId: data.ownerId, + }, + }, + onSuccess: () => { + setIsEditingOwner(false); + }, + }); + }; + + const handleUpdateDocumentType = async (data: { + documentType: (typeof documentTypes)[number]; + }) => { + await updateDocument({ + variables: { + input: { + id: document.id, + documentType: data.documentType, + }, + }, + onSuccess: () => { + setIsEditingType(false); + loadQuery( + coreEnvironment, + documentLayoutQuery, + { documentId: document.id }, + { fetchPolicy: "network-only" }, + ); + }, + }); + }; + + const handleUpdateClassification = async (data: { + classification: (typeof documentClassifications)[number]; + }) => { + await updateDocument({ + variables: { + input: { + id: document.id, + classification: data.classification, + }, + }, + onSuccess: () => { + setIsEditingClassification(false); + loadQuery( + coreEnvironment, + documentLayoutQuery, + { documentId: document.id }, + { fetchPolicy: "network-only" }, + ); + }, + }); + }; + + return ( + +
+ {__("Properties")} +
+ + {isEditingOwner + ? ( + void handleSubmit(handleUpdateOwner)()} + onCancel={() => { + setIsEditingOwner(false); + reset(); + }} + disabled={isUpdatingDocument} + > + + + ) + : ( + setIsEditingOwner(true)} + canEdit={document.canUpdate} + > + + + {currentVersion.owner?.fullName} + + + )} + + + {isEditingType + ? ( + void handleSubmit(handleUpdateDocumentType)()} + onCancel={() => { + setIsEditingType(false); + reset(); + }} + disabled={isUpdatingDocument} + > + + + + + ) + : ( + setIsEditingType(true)} + canEdit={document.canUpdate} + > +
+ {getDocumentTypeLabel(__, document.documentType)} +
+
+ )} +
+ + {isEditingClassification + ? ( + void handleSubmit(handleUpdateClassification)()} + onCancel={() => { + setIsEditingClassification(false); + reset(); + }} + disabled={isUpdatingDocument} + > + + + + + ) + : ( + setIsEditingClassification(true)} + canEdit={document.canUpdate} + > +
+ {getDocumentClassificationLabel( + __, + currentVersion.classification, + )} +
+
+ )} +
+ + + {isDraft ? __("Draft") : __("Published")} + + + +
+ {currentVersion.version} +
+
+ +
+ {formatDate(currentVersion.updatedAt)} +
+
+ {currentVersion.publishedAt && ( + +
+ {formatDate(currentVersion.publishedAt)} +
+
+ )} +
+ ); +} + +function EditablePropertyContent({ + children, + onSave, + onCancel, + disabled, +}: { + children: React.ReactNode; + onSave: () => void; + onCancel: () => void; + disabled?: boolean; +}) { + return ( +
+
{children}
+
+ ); +} + +function ReadOnlyPropertyContent({ + children, + onEdit, + canEdit = true, +}: { + children: React.ReactNode; + onEdit: () => void; + canEdit?: boolean; +}) { + return ( +
+ {children} + {canEdit && ( +
+ ); +} diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentTitleForm.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentTitleForm.tsx new file mode 100644 index 000000000..7b7850f8d --- /dev/null +++ b/apps/console/src/pages/organizations/documents/_components/DocumentTitleForm.tsx @@ -0,0 +1,120 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, IconCheckmark1, IconCrossLargeX, IconPencil, Input } from "@probo/ui"; +import { useState } from "react"; +import { useFragment } from "react-relay"; +import { graphql } from "relay-runtime"; +import { z } from "zod"; + +import type { DocumentTitleFormFragment$key } from "#/__generated__/core/DocumentTitleFormFragment.graphql"; +import type { DocumentTitleFormMutation } from "#/__generated__/core/DocumentTitleFormMutation.graphql"; +import { useFormWithSchema } from "#/hooks/useFormWithSchema"; +import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; + +const updateDocumentTitleMutation = graphql` + mutation DocumentTitleFormMutation($input: UpdateDocumentInput!) { + updateDocument(input: $input) { + document { + ...DocumentTitleFormFragment + } + } + } +`; + +const fragment = graphql` + fragment DocumentTitleFormFragment on Document { + id + title + canUpdate: permission(action: "core:document:update") + } +`; + +const schema = z.object({ + title: z.string().min(1, "Title is required").max(255), +}); + +export function DocumentTitleForm(props: { fKey: DocumentTitleFormFragment$key }) { + const { fKey } = props; + + const { __ } = useTranslate(); + + const document = useFragment(fragment, fKey); + const [updateDocument, isUpdatingDocument] + = useMutationWithToasts( + updateDocumentTitleMutation, + { + successMessage: __("Document updated successfully."), + errorMessage: __("Failed to update document"), + }, + ); + + const [isEditingTitle, setIsEditingTitle] = useState(false); + const { register, handleSubmit, reset } = useFormWithSchema( + schema, + { + defaultValues: { + title: document.title, + }, + }, + ); + + const handleUpdateTitle = async (data: { title: string }) => { + await updateDocument({ + variables: { + input: { + id: document.id, + title: data.title, + }, + }, + onSuccess: () => { + setIsEditingTitle(false); + }, + }); + }; + + return isEditingTitle + ? ( +
+ { + if (e.key === "Escape") { + setIsEditingTitle(false); + reset(); + } + if (e.key === "Enter") { + void handleSubmit(handleUpdateTitle)(); + } + }} + /> +
+ ) + : ( +
+ {document.title} + {document.canUpdate && ( +
+ ); +} diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdown.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdown.tsx new file mode 100644 index 000000000..2c1e10df3 --- /dev/null +++ b/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdown.tsx @@ -0,0 +1,37 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, Dropdown, IconChevronDown, IconClock } from "@probo/ui"; +import { useQueryLoader } from "react-relay"; +import { useParams } from "react-router"; + +import type { DocumentVersionsDropdownMenuQuery } from "#/__generated__/core/DocumentVersionsDropdownMenuQuery.graphql"; + +import { DocumentVersionsDropdownMenu, documentVersionsDropdownMenuQuery } from "./DocumentVersionsDropdownMenu"; + +export function DocumentVersionsDropdown(props: { + currentVersionId: string; +}) { + const { currentVersionId } = props; + + const { documentId } = useParams(); + if (!documentId) { + throw new Error(":documentId missing in route params"); + } + const [queryRef, loadQuery] = useQueryLoader(documentVersionsDropdownMenuQuery); + + const { __ } = useTranslate(); + + return ( + open && !queryRef && loadQuery({ documentId })} + toggle={( + + )} + > + {queryRef + && } + + ); +} diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdownItem.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdownItem.tsx new file mode 100644 index 000000000..ed4f643c9 --- /dev/null +++ b/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdownItem.tsx @@ -0,0 +1,72 @@ +import { useTranslate } from "@probo/i18n"; +import { Badge } from "@probo/ui"; +import { clsx } from "clsx"; +import { useFragment } from "react-relay"; +import { Link, useLocation, useParams } from "react-router"; +import { graphql } from "relay-runtime"; + +import type { + DocumentVersionsDropdownItemFragment$key, +} from "#/__generated__/core/DocumentVersionsDropdownItemFragment.graphql"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; + +const fragment = graphql` + fragment DocumentVersionsDropdownItemFragment on DocumentVersion { + id + version + status + publishedAt + updatedAt + } +`; + +export function DocumentVersionsDropdownItem(props: { + fKey: DocumentVersionsDropdownItemFragment$key; + active?: boolean; +}) { + const { fKey, active } = props; + + const { dateTimeFormat, __ } = useTranslate(); + const organizationId = useOrganizationId(); + const { documentId } = useParams(); + if (!documentId) { + throw new Error(":documentId route param missing"); + } + + const version = useFragment(fragment, fKey); + + const suffix = useLocation().pathname.split("/").at(-1); + + return ( + +
+
+
+ {version.version} +
+
+
+
+ {version.status === "DRAFT" && ( + + {__("Draft")} + + )} +
+
+ {dateTimeFormat(version.publishedAt ?? version.updatedAt)} +
+
+
+ + ); +} diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdownMenu.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdownMenu.tsx new file mode 100644 index 000000000..80421c2e5 --- /dev/null +++ b/apps/console/src/pages/organizations/documents/_components/DocumentVersionsDropdownMenu.tsx @@ -0,0 +1,53 @@ +import { DropdownItem } from "@probo/ui"; +import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { DocumentVersionsDropdownMenuQuery } from "#/__generated__/core/DocumentVersionsDropdownMenuQuery.graphql"; + +import { DocumentVersionsDropdownItem } from "./DocumentVersionsDropdownItem"; + +export const documentVersionsDropdownMenuQuery = graphql` + query DocumentVersionsDropdownMenuQuery($documentId: ID!) { + document: node(id: $documentId) { + __typename + ... on Document { + versions(first: 20) { + edges { + node { + id + ...DocumentVersionsDropdownItemFragment + } + } + } + } + } + } +`; + +export function DocumentVersionsDropdownMenu(props: { + currentVersionId: string; + queryRef: PreloadedQuery; +}) { + const { currentVersionId, queryRef } = props; + + const { document } = usePreloadedQuery( + documentVersionsDropdownMenuQuery, + queryRef, + ); + if (document.__typename !== "Document") { + throw new Error("invalid type for node"); + } + + return ( + <> + {document.versions.edges.map(({ node: version }) => ( + + + + ))} + + ); +} diff --git a/apps/console/src/pages/organizations/documents/_components/UpdateVersionDialog.tsx b/apps/console/src/pages/organizations/documents/_components/UpdateVersionDialog.tsx index f278dced9..3359a3471 100644 --- a/apps/console/src/pages/organizations/documents/_components/UpdateVersionDialog.tsx +++ b/apps/console/src/pages/organizations/documents/_components/UpdateVersionDialog.tsx @@ -11,16 +11,32 @@ import { useToast, } from "@probo/ui"; import { type RefObject, useEffect } from "react"; -import { useMutation } from "react-relay"; +import { useFragment, useMutation } from "react-relay"; import { graphql } from "relay-runtime"; import { z } from "zod"; -import type { DocumentDetailPageDocumentFragment$data } from "#/__generated__/core/DocumentDetailPageDocumentFragment.graphql"; import type { UpdateVersionDialogCreateMutation } from "#/__generated__/core/UpdateVersionDialogCreateMutation.graphql"; +import type { UpdateVersionDialogFragment$key } from "#/__generated__/core/UpdateVersionDialogFragment.graphql"; import type { UpdateVersionDialogUpdateMutation } from "#/__generated__/core/UpdateVersionDialogUpdateMutation.graphql"; import { useFormWithSchema } from "#/hooks/useFormWithSchema"; import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; +const fragment = graphql` + fragment UpdateVersionDialogFragment on Document { + id + versions(first: 20) @connection(key: "DocumentLayout_versions") { + __id + edges { + node { + id + status + content + } + } + } + } +`; + const createDraftDocument = graphql` mutation UpdateVersionDialogCreateMutation( $input: CreateDraftDocumentVersionInput! @@ -49,7 +65,7 @@ const createDraftDocument = graphql` } `; -const UpdateDocumentMutation = graphql` +const updateDocumentMutation = graphql` mutation UpdateVersionDialogUpdateMutation( $input: UpdateDocumentVersionInput! ) { @@ -62,9 +78,8 @@ const UpdateDocumentMutation = graphql` } `; -type Props = { - document: DocumentDetailPageDocumentFragment$data; - connectionId: string; +type UpdateVersionDialogProps = { + fKey: UpdateVersionDialogFragment$key; ref: RefObject<{ open: () => void } | null>; }; @@ -72,22 +87,21 @@ const versionSchema = z.object({ content: z.string(), }); -export default function UpdateVersionDialog({ - document, - connectionId, - ref, -}: Props) { +export default function UpdateVersionDialog(props: UpdateVersionDialogProps) { + const { fKey, ref } = props; + const { __ } = useTranslate(); const { toast } = useToast(); const dialogRef = useDialogRef(); + const document = useFragment(fragment, fKey); const version = document.versions.edges[0].node; - const isDraft = version?.status === "DRAFT"; + const isDraft = version.status === "DRAFT"; const [createDraftDocumentVersion, isCreatingDraft] = useMutation(createDraftDocument); const [updateDocumentVersion, isUpdating] = useMutationWithToasts( - UpdateDocumentMutation, + updateDocumentMutation, { successMessage: __("Document updated successfully."), errorMessage: __("Failed to update document"), @@ -132,7 +146,7 @@ export default function UpdateVersionDialog({ input: { documentID: document.id, }, - connections: [connectionId], + connections: [document.versions.__id], }, onCompleted: (createResponse, errors) => { if (errors) { diff --git a/apps/console/src/pages/organizations/documents/tabs/DocumentDescriptionTab.tsx b/apps/console/src/pages/organizations/documents/tabs/DocumentDescriptionTab.tsx index cae268bf3..a1b8ab91e 100644 --- a/apps/console/src/pages/organizations/documents/tabs/DocumentDescriptionTab.tsx +++ b/apps/console/src/pages/organizations/documents/tabs/DocumentDescriptionTab.tsx @@ -1,12 +1,12 @@ import { Markdown } from "@probo/ui"; import { useOutletContext } from "react-router"; -import type { DocumentDetailPageDocumentFragment$data } from "#/__generated__/core/DocumentDetailPageDocumentFragment.graphql"; +import type { DocumentLayoutQuery$data } from "#/__generated__/core/DocumentLayoutQuery.graphql"; import type { NodeOf } from "#/types"; export default function DocumentDescriptionTab() { const { version } = useOutletContext<{ - version: NodeOf; + version: NodeOf["versions"]>; }>(); return (
diff --git a/apps/console/src/pages/organizations/documents/tabs/DocumentSignaturesTab.tsx b/apps/console/src/pages/organizations/documents/tabs/DocumentSignaturesTab.tsx index af735744a..4ac2d037a 100644 --- a/apps/console/src/pages/organizations/documents/tabs/DocumentSignaturesTab.tsx +++ b/apps/console/src/pages/organizations/documents/tabs/DocumentSignaturesTab.tsx @@ -13,7 +13,7 @@ import { Suspense, useEffect, useRef, useState } from "react"; import { graphql, useFragment, useRefetchableFragment } from "react-relay"; import { useOutletContext } from "react-router"; -import type { DocumentDetailPageDocumentFragment$data } from "#/__generated__/core/DocumentDetailPageDocumentFragment.graphql"; +import type { DocumentLayoutQuery$data } from "#/__generated__/core/DocumentLayoutQuery.graphql"; import type { DocumentSignaturesTab_signature$key } from "#/__generated__/core/DocumentSignaturesTab_signature.graphql"; import type { DocumentSignaturesTab_version$key } from "#/__generated__/core/DocumentSignaturesTab_version.graphql"; import type { DocumentSignaturesTabRefetchQuery } from "#/__generated__/core/DocumentSignaturesTabRefetchQuery.graphql"; @@ -22,7 +22,7 @@ import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; import { useOrganizationId } from "#/hooks/useOrganizationId"; import type { ItemOf, NodeOf } from "#/types"; -type Version = NodeOf; +type Version = NodeOf["versions"]>; const versionFragment = graphql` fragment DocumentSignaturesTab_version on DocumentVersion diff --git a/apps/console/src/routes/documentsRoutes.ts b/apps/console/src/routes/documentsRoutes.ts index db0490cc0..d6a924974 100644 --- a/apps/console/src/routes/documentsRoutes.ts +++ b/apps/console/src/routes/documentsRoutes.ts @@ -1,18 +1,12 @@ import { lazy } from "@probo/react-lazy"; import { type AppRoute, - loaderFromQueryLoader, - withQueryRef, } from "@probo/routes"; import { Fragment } from "react"; -import { loadQuery } from "react-relay"; import { type LoaderFunctionArgs, redirect } from "react-router"; -import type { DocumentGraphNodeQuery } from "#/__generated__/core/DocumentGraphNodeQuery.graphql"; import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton"; import { PageSkeleton } from "#/components/skeletons/PageSkeleton"; -import { coreEnvironment } from "#/environments"; -import { documentNodeQuery } from "#/hooks/graph/DocumentGraph"; const documentTabs = (prefix: string) => { return [ @@ -66,14 +60,7 @@ export const documentsRoutes = [ { path: "documents/:documentId", Fallback: PageSkeleton, - loader: loaderFromQueryLoader(({ documentId }) => - loadQuery(coreEnvironment, documentNodeQuery, { - documentId: documentId, - }), - ), - Component: withQueryRef( - lazy(() => import("../pages/organizations/documents/DocumentDetailPage")), - ), + Component: lazy(() => import("#/pages/organizations/documents/DocumentLayoutLoader")), children: [...documentTabs(""), ...documentTabs("versions/:versionId/")], }, ] satisfies AppRoute[]; diff --git a/go.sum b/go.sum index 720ab1a46..65ede2540 100644 --- a/go.sum +++ b/go.sum @@ -322,5 +322,7 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/gotestsum v1.13.0 h1:+Lh454O9mu9AMG1APV4o0y7oDYKyik/3kBOiCqiEpRo= gotest.tools/gotestsum v1.13.0/go.mod h1:7f0NS5hFb0dWr4NtcsAsF0y1kzjEFfAil0HiBQJE03Q= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/pkg/coredata/document_version.go b/pkg/coredata/document_version.go index ff5f6f960..c2dbdf45c 100644 --- a/pkg/coredata/document_version.go +++ b/pkg/coredata/document_version.go @@ -63,7 +63,7 @@ func (dv *DocumentVersion) AuthorizationAttributes(ctx context.Context, conn pg. return map[string]string{"organization_id": organizationID.String()}, nil } -func (p *DocumentVersions) LoadByDocumentID( +func (dv *DocumentVersions) LoadByDocumentID( ctx context.Context, conn pg.Conn, scope Scoper, @@ -113,21 +113,21 @@ WHERE return fmt.Errorf("cannot collect document versions: %w", err) } - *p = documentVersions + *dv = documentVersions return nil } -func (p DocumentVersion) CursorKey(orderBy DocumentVersionOrderField) page.CursorKey { +func (dv DocumentVersion) CursorKey(orderBy DocumentVersionOrderField) page.CursorKey { switch orderBy { case DocumentVersionOrderFieldCreatedAt: - return page.NewCursorKey(p.ID, p.CreatedAt) + return page.NewCursorKey(dv.ID, dv.CreatedAt) } panic(fmt.Sprintf("unsupported order by: %s", orderBy)) } -func (p *DocumentVersion) LoadByID( +func (dv *DocumentVersion) LoadByID( ctx context.Context, conn pg.Conn, scope Scoper, @@ -173,12 +173,12 @@ LIMIT 1; return fmt.Errorf("cannot collect document version: %w", err) } - *p = documentVersion + *dv = documentVersion return nil } -func (p DocumentVersion) Insert( +func (dv DocumentVersion) Insert( ctx context.Context, conn pg.Conn, scope Scoper, @@ -217,18 +217,18 @@ VALUES ( ` args := pgx.StrictNamedArgs{ "tenant_id": scope.GetTenantID(), - "id": p.ID, - "organization_id": p.OrganizationID, - "document_id": p.DocumentID, - "title": p.Title, - "owner_id": p.OwnerID, - "version_number": p.VersionNumber, - "classification": p.Classification, - "content": p.Content, - "changelog": p.Changelog, - "status": p.Status, - "created_at": p.CreatedAt, - "updated_at": p.UpdatedAt, + "id": dv.ID, + "organization_id": dv.OrganizationID, + "document_id": dv.DocumentID, + "title": dv.Title, + "owner_id": dv.OwnerID, + "version_number": dv.VersionNumber, + "classification": dv.Classification, + "content": dv.Content, + "changelog": dv.Changelog, + "status": dv.Status, + "created_at": dv.CreatedAt, + "updated_at": dv.UpdatedAt, } _, err := conn.Exec(ctx, q, args) @@ -247,7 +247,7 @@ VALUES ( return nil } -func (p *DocumentVersion) LoadByDocumentIDAndVersionNumber( +func (dv *DocumentVersion) LoadByDocumentIDAndVersionNumber( ctx context.Context, conn pg.Conn, scope Scoper, @@ -296,12 +296,12 @@ LIMIT 1; return fmt.Errorf("cannot collect document version: %w", err) } - *p = documentVersion + *dv = documentVersion return nil } -func (p *DocumentVersion) LoadLatestVersion( +func (dv *DocumentVersion) LoadLatestVersion( ctx context.Context, conn pg.Conn, scope Scoper, @@ -347,12 +347,12 @@ LIMIT 1; return fmt.Errorf("cannot collect document version: %w", err) } - *p = documentVersion + *dv = documentVersion return nil } -func (p *DocumentVersion) LoadLatestPublishedVersion( +func (dv *DocumentVersion) LoadLatestPublishedVersion( ctx context.Context, conn pg.Conn, scope Scoper, @@ -400,12 +400,12 @@ LIMIT 1; return fmt.Errorf("cannot collect document version: %w", err) } - *p = documentVersion + *dv = documentVersion return nil } -func (p DocumentVersion) Update( +func (dv DocumentVersion) Update( ctx context.Context, conn pg.Conn, scope Scoper, @@ -427,15 +427,15 @@ WHERE %s q = fmt.Sprintf(q, scope.SQLFragment()) args := pgx.StrictNamedArgs{ - "document_version_id": p.ID, - "title": p.Title, - "owner_id": p.OwnerID, - "changelog": p.Changelog, - "status": p.Status, - "content": p.Content, - "published_at": p.PublishedAt, - "classification": p.Classification, - "updated_at": p.UpdatedAt, + "document_version_id": dv.ID, + "title": dv.Title, + "owner_id": dv.OwnerID, + "changelog": dv.Changelog, + "status": dv.Status, + "content": dv.Content, + "published_at": dv.PublishedAt, + "classification": dv.Classification, + "updated_at": dv.UpdatedAt, } maps.Copy(args, scope.SQLArguments()) @@ -447,7 +447,7 @@ WHERE %s return nil } -func (p DocumentVersion) Delete( +func (dv DocumentVersion) Delete( ctx context.Context, conn pg.Conn, scope Scoper, @@ -460,7 +460,7 @@ WHERE %s q = fmt.Sprintf(q, scope.SQLFragment()) args := pgx.NamedArgs{ - "document_version_id": p.ID, + "document_version_id": dv.ID, } maps.Copy(args, scope.SQLArguments()) @@ -472,3 +472,36 @@ WHERE %s return nil } + +func (dv *DocumentVersions) CountByDocumentID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentID gid.GID, + filter *DocumentVersionFilter, +) (int, error) { + q := ` +SELECT + COUNT(id) +FROM + document_versions +WHERE + %s + AND document_id = @document_id + AND %s +` + + q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment()) + + args := pgx.NamedArgs{"document_id": documentID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, filter.SQLArguments()) + + row := conn.QueryRow(ctx, q, args) + var count int + if err := row.Scan(&count); err != nil { + return 0, fmt.Errorf("cannot scan count: %w", err) + } + + return count, nil +} diff --git a/pkg/probo/document_service.go b/pkg/probo/document_service.go index 2cdb0c46b..a5717b71d 100644 --- a/pkg/probo/document_service.go +++ b/pkg/probo/document_service.go @@ -1107,6 +1107,33 @@ func (s *DocumentService) RequestExport( return exportJob, nil } +func (s *DocumentService) CountVersionsForDocumentID( + ctx context.Context, + documentID gid.GID, + filter *coredata.DocumentVersionFilter, +) (int, error) { + var count int + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) (err error) { + documentVersions := &coredata.DocumentVersions{} + count, err = documentVersions.CountByDocumentID(ctx, conn, s.svc.scope, documentID, filter) + if err != nil { + return fmt.Errorf("cannot count document versions: %w", err) + } + + return nil + }, + ) + + if err != nil { + return 0, fmt.Errorf("cannot count document versions: %w", err) + } + + return count, nil +} + func (s *DocumentService) ListVersions( ctx context.Context, documentID gid.GID, diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 57c9abb48..1ccaa3f72 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -2889,9 +2889,13 @@ type VendorRiskAssessmentEdge { node: VendorRiskAssessment! } -type DocumentVersionConnection { +type DocumentVersionConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionConnection" + ) { edges: [DocumentVersionEdge!]! pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) } type DocumentVersionEdge { @@ -4695,10 +4699,7 @@ type DeleteMeasurePayload { deletedMeasureId: ID! } -type DocumentVersion implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersion" - ) { +type DocumentVersion implements Node { id: ID! document: Document! @goField(forceResolver: true) status: DocumentStatus! diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index df19038c9..a0fd27143 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -65,6 +65,7 @@ type ResolverRoot interface { Document() DocumentResolver DocumentConnection() DocumentConnectionResolver DocumentVersion() DocumentVersionResolver + DocumentVersionConnection() DocumentVersionConnectionResolver DocumentVersionSignature() DocumentVersionSignatureResolver Evidence() EvidenceResolver EvidenceConnection() EvidenceConnectionResolver @@ -743,8 +744,9 @@ type ComplexityRoot struct { } DocumentVersionConnection struct { - Edges func(childComplexity int) int - PageInfo func(childComplexity int) int + Edges func(childComplexity int) int + PageInfo func(childComplexity int) int + TotalCount func(childComplexity int) int } DocumentVersionEdge struct { @@ -2020,6 +2022,9 @@ type DocumentVersionResolver interface { Permission(ctx context.Context, obj *types.DocumentVersion, action string) (bool, error) } +type DocumentVersionConnectionResolver interface { + TotalCount(ctx context.Context, obj *types.DocumentVersionConnection) (int, error) +} type DocumentVersionSignatureResolver interface { DocumentVersion(ctx context.Context, obj *types.DocumentVersionSignature) (*types.DocumentVersion, error) @@ -4371,6 +4376,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.complexity.DocumentVersionConnection.PageInfo(childComplexity), true + case "DocumentVersionConnection.totalCount": + if e.complexity.DocumentVersionConnection.TotalCount == nil { + break + } + + return e.complexity.DocumentVersionConnection.TotalCount(childComplexity), true case "DocumentVersionEdge.cursor": if e.complexity.DocumentVersionEdge.Cursor == nil { @@ -13178,9 +13189,13 @@ type VendorRiskAssessmentEdge { node: VendorRiskAssessment! } -type DocumentVersionConnection { +type DocumentVersionConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersionConnection" + ) { edges: [DocumentVersionEdge!]! pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) } type DocumentVersionEdge { @@ -14984,10 +14999,7 @@ type DeleteMeasurePayload { deletedMeasureId: ID! } -type DocumentVersion implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersion" - ) { +type DocumentVersion implements Node { id: ID! document: Document! @goField(forceResolver: true) status: DocumentStatus! @@ -28742,6 +28754,8 @@ func (ec *executionContext) fieldContext_Document_versions(ctx context.Context, return ec.fieldContext_DocumentVersionConnection_edges(ctx, field) case "pageInfo": return ec.fieldContext_DocumentVersionConnection_pageInfo(ctx, field) + case "totalCount": + return ec.fieldContext_DocumentVersionConnection_totalCount(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type DocumentVersionConnection", field.Name) }, @@ -29663,7 +29677,7 @@ func (ec *executionContext) _DocumentVersionConnection_pageInfo(ctx context.Cont return obj.PageInfo, nil }, nil, - ec.marshalNPageInfo2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPageInfo, + ec.marshalNPageInfo2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPageInfo, true, true, ) @@ -29692,6 +29706,35 @@ func (ec *executionContext) fieldContext_DocumentVersionConnection_pageInfo(_ co return fc, nil } +func (ec *executionContext) _DocumentVersionConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersionConnection) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_DocumentVersionConnection_totalCount, + func(ctx context.Context) (any, error) { + return ec.resolvers.DocumentVersionConnection().TotalCount(ctx, obj) + }, + nil, + ec.marshalNInt2int, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_DocumentVersionConnection_totalCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DocumentVersionConnection", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _DocumentVersionEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersionEdge) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -47303,6 +47346,8 @@ func (ec *executionContext) fieldContext_SignableDocument_versions(ctx context.C return ec.fieldContext_DocumentVersionConnection_edges(ctx, field) case "pageInfo": return ec.fieldContext_DocumentVersionConnection_pageInfo(ctx, field) + case "totalCount": + return ec.fieldContext_DocumentVersionConnection_totalCount(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type DocumentVersionConnection", field.Name) }, @@ -75868,13 +75913,49 @@ func (ec *executionContext) _DocumentVersionConnection(ctx context.Context, sel case "edges": out.Values[i] = ec._DocumentVersionConnection_edges(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "pageInfo": out.Values[i] = ec._DocumentVersionConnection_pageInfo(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } + case "totalCount": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._DocumentVersionConnection_totalCount(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/pkg/server/api/console/v1/types/document_version.go b/pkg/server/api/console/v1/types/document_version.go index 018f86938..628dd6a99 100644 --- a/pkg/server/api/console/v1/types/document_version.go +++ b/pkg/server/api/console/v1/types/document_version.go @@ -15,41 +15,30 @@ package types import ( - "time" - "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" ) type ( - DocumentVersion struct { - ID gid.GID `json:"id"` - OrganizationID gid.GID `json:"-"` - 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"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - } - DocumentVersionOrderBy OrderBy[coredata.DocumentVersionOrderField] + + DocumentVersionConnection struct { + TotalCount int + Edges []*DocumentVersionEdge + PageInfo PageInfo + + Resolver any + ParentID gid.GID + Filters *coredata.DocumentVersionFilter + } ) -func (DocumentVersion) IsNode() {} - -func (d DocumentVersion) GetID() gid.GID { - return d.ID -} - -func NewDocumentVersionConnection(page *page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField]) *DocumentVersionConnection { +func NewDocumentVersionConnection( + page *page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField], + parentType any, + parentID gid.GID, +) *DocumentVersionConnection { edges := make([]*DocumentVersionEdge, len(page.Data)) for i, documentVersion := range page.Data { edges[i] = NewDocumentVersionEdge(documentVersion, page.Cursor.OrderBy.Field) @@ -57,7 +46,10 @@ func NewDocumentVersionConnection(page *page.Page[*coredata.DocumentVersion, cor return &DocumentVersionConnection{ Edges: edges, - PageInfo: NewPageInfo(page), + PageInfo: *NewPageInfo(page), + + Resolver: parentType, + ParentID: parentID, } } @@ -80,8 +72,7 @@ func NewDocumentVersionEdge(documentVersion *coredata.DocumentVersion, orderBy c func NewDocumentVersion(documentVersion *coredata.DocumentVersion) *DocumentVersion { return &DocumentVersion{ - ID: documentVersion.ID, - OrganizationID: documentVersion.OrganizationID, + ID: documentVersion.ID, Document: &Document{ ID: documentVersion.DocumentID, }, diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index 6e36be3c7..8f9a400b4 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -1156,11 +1156,27 @@ type DocumentFilter struct { Query *string `json:"query,omitempty"` } -type DocumentVersionConnection struct { - Edges []*DocumentVersionEdge `json:"edges"` - PageInfo *PageInfo `json:"pageInfo"` +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"` + Classification coredata.DocumentClassification `json:"classification"` + Owner *People `json:"owner"` + Signatures *DocumentVersionSignatureConnection `json:"signatures"` + Signed bool `json:"signed"` + PublishedAt *time.Time `json:"publishedAt,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Permission bool `json:"permission"` } +func (DocumentVersion) IsNode() {} +func (this DocumentVersion) GetID() gid.GID { return this.ID } + type DocumentVersionEdge struct { Cursor page.CursorKey `json:"cursor"` Node *DocumentVersion `json:"node"` diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index da6222712..21d3a1d0e 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -939,7 +939,7 @@ func (r *documentResolver) Versions(ctx context.Context, obj *types.Document, fi panic(fmt.Errorf("cannot list document versions: %w", err)) } - return types.NewDocumentVersionConnection(page), nil + return types.NewDocumentVersionConnection(page, r, obj.ID), nil } // Controls is the resolver for the controls field. @@ -1120,6 +1120,32 @@ func (r *documentVersionResolver) Permission(ctx context.Context, obj *types.Doc return r.Resolver.Permission(ctx, obj, action) } +// TotalCount is the resolver for the totalCount field. +func (r *documentVersionConnectionResolver) TotalCount(ctx context.Context, obj *types.DocumentVersionConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionEvidenceList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + switch obj.Resolver.(type) { + case *documentResolver: + filter := &coredata.DocumentVersionFilter{} + if obj.Filters != nil { + filter = obj.Filters + } + count, err := prb.Documents.CountVersionsForDocumentID(ctx, obj.ParentID, filter) + if err != nil { + // TODO no panic use gqlutils.InternalError + panic(fmt.Errorf("cannot count tasks: %w", err)) + } + return count, nil + } + + // TODO no panic use gqlutils.InternalError + panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) +} + // DocumentVersion is the resolver for the documentVersion field. func (r *documentVersionSignatureResolver) DocumentVersion(ctx context.Context, obj *types.DocumentVersionSignature) (*types.DocumentVersion, error) { if err := r.authorize(ctx, obj.ID, probo.ActionDocumentVersionGet); err != nil { @@ -7156,7 +7182,7 @@ func (r *signableDocumentResolver) Versions(ctx context.Context, obj *types.Sign panic(fmt.Errorf("cannot list signable document versions: %w", err)) } - return types.NewDocumentVersionConnection(page), nil + return types.NewDocumentVersionConnection(page, r, obj.ID), nil } // Organization is the resolver for the organization field. @@ -8522,6 +8548,11 @@ func (r *Resolver) DocumentVersion() schema.DocumentVersionResolver { return &documentVersionResolver{r} } +// DocumentVersionConnection returns schema.DocumentVersionConnectionResolver implementation. +func (r *Resolver) DocumentVersionConnection() schema.DocumentVersionConnectionResolver { + return &documentVersionConnectionResolver{r} +} + // DocumentVersionSignature returns schema.DocumentVersionSignatureResolver implementation. func (r *Resolver) DocumentVersionSignature() schema.DocumentVersionSignatureResolver { return &documentVersionSignatureResolver{r} @@ -8754,6 +8785,7 @@ type datumConnectionResolver struct{ *Resolver } type documentResolver struct{ *Resolver } type documentConnectionResolver struct{ *Resolver } type documentVersionResolver struct{ *Resolver } +type documentVersionConnectionResolver struct{ *Resolver } type documentVersionSignatureResolver struct{ *Resolver } type evidenceResolver struct{ *Resolver } type evidenceConnectionResolver struct{ *Resolver }