Change document version

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-06-03 14:55:54 -07:00
parent 21e7891f9b
commit b54cb6d3fc
22 changed files with 1247 additions and 146 deletions

View File

@@ -25,7 +25,7 @@ import type { ShowDocumentViewQuery } from "./__generated__/ShowDocumentViewQuer
import { ShowDocumentViewPublishMutation } from "./__generated__/ShowDocumentViewPublishMutation.graphql"; import { ShowDocumentViewPublishMutation } from "./__generated__/ShowDocumentViewPublishMutation.graphql";
import { ShowDocumentViewCreateDraftMutation } from "./__generated__/ShowDocumentViewCreateDraftMutation.graphql"; import { ShowDocumentViewCreateDraftMutation } from "./__generated__/ShowDocumentViewCreateDraftMutation.graphql";
import { ShowDocumentViewUpdateDocumentMutation } from "./__generated__/ShowDocumentViewUpdateDocumentMutation.graphql"; import { ShowDocumentViewUpdateDocumentMutation } from "./__generated__/ShowDocumentViewUpdateDocumentMutation.graphql";
import { ShowDocumentViewUpdateDocumentTypeMutation } from "./__generated__/ShowDocumentViewUpdateDocumentTypeMutation.graphql"; import { ShowDocumentViewGenerateChangelogMutation } from "./__generated__/ShowDocumentViewGenerateChangelogMutation.graphql";
import type { DocumentType } from "./__generated__/DocumentListViewCreateMutation.graphql"; import type { DocumentType } from "./__generated__/DocumentListViewCreateMutation.graphql";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
@@ -90,6 +90,10 @@ const documentViewQuery = graphql`
content content
changelog changelog
publishedAt publishedAt
title
owner {
fullName
}
publishedBy { publishedBy {
fullName fullName
} }
@@ -114,6 +118,7 @@ const publishDocumentVersionMutation = graphql`
id id
status status
publishedAt publishedAt
changelog
publishedBy { publishedBy {
fullName fullName
} }
@@ -144,6 +149,7 @@ const updateDocumentMutation = graphql`
document { document {
id id
documentType documentType
title
owner { owner {
id id
fullName fullName
@@ -154,6 +160,14 @@ const updateDocumentMutation = graphql`
} }
`; `;
const generateChangelogMutation = graphql`
mutation ShowDocumentViewGenerateChangelogMutation($input: GenerateDocumentChangelogInput!) {
generateDocumentChangelog(input: $input) {
changelog
}
}
`;
function ShowDocumentContent({ function ShowDocumentContent({
queryRef, queryRef,
}: { }: {
@@ -176,8 +190,18 @@ function ShowDocumentContent({
const [isSignaturesModalOpen, setIsSignaturesModalOpen] = useState(false); const [isSignaturesModalOpen, setIsSignaturesModalOpen] = useState(false);
const [isEditingOwner, setIsEditingOwner] = useState(false); const [isEditingOwner, setIsEditingOwner] = useState(false);
const [isEditingType, setIsEditingType] = useState(false); const [isEditingType, setIsEditingType] = useState(false);
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [editedTitle, setEditedTitle] = useState(documentValue.title || '');
const [isPublishDialogOpen, setIsPublishDialogOpen] = useState(false);
const [publishChangelog, setPublishChangelog] = useState('');
const [isGeneratingChangelog, setIsGeneratingChangelog] = useState(false);
const printContentRef = useRef<HTMLDivElement>(null); const printContentRef = useRef<HTMLDivElement>(null);
// Keep editedTitle in sync with documentValue.title
useEffect(() => {
setEditedTitle(documentValue.title || '');
}, [documentValue.title]);
const [publishDraft, isPublishInFlight] = const [publishDraft, isPublishInFlight] =
useMutation<ShowDocumentViewPublishMutation>(publishDocumentVersionMutation); useMutation<ShowDocumentViewPublishMutation>(publishDocumentVersionMutation);
const [createDraft, isCreateDraftInFlight] = const [createDraft, isCreateDraftInFlight] =
@@ -186,6 +210,7 @@ function ShowDocumentContent({
); );
const [updateDocument, isUpdatingDocument] = const [updateDocument, isUpdatingDocument] =
useMutation<ShowDocumentViewUpdateDocumentMutation>(updateDocumentMutation); useMutation<ShowDocumentViewUpdateDocumentMutation>(updateDocumentMutation);
const [generateChangelog] = useMutation<ShowDocumentViewGenerateChangelogMutation>(generateChangelogMutation);
const latestVersionEdge = documentValue.latestVersion?.edges[0]; const latestVersionEdge = documentValue.latestVersion?.edges[0];
const latestVersionNode = latestVersionEdge?.node; const latestVersionNode = latestVersionEdge?.node;
@@ -300,12 +325,57 @@ function ShowDocumentContent({
// Navigate to publish flow // Navigate to publish flow
const handlePublish = useCallback(() => { const handlePublish = useCallback(() => {
setIsPublishDialogOpen(true);
setPublishChangelog("");
// Generate changelog
setIsGeneratingChangelog(true);
generateChangelog({
variables: {
input: {
documentId: documentValue.id,
},
},
onCompleted: (response, errors) => {
setIsGeneratingChangelog(false);
if (errors) {
toast({
title: "Error",
description: errors[0]?.message || "An unknown error occurred",
variant: "destructive",
});
return;
}
const generatedChangelog = response.generateDocumentChangelog.changelog;
setPublishChangelog(prev => {
if (prev.trim()) {
return `${prev}\n${generatedChangelog}`;
}
return generatedChangelog;
});
},
onError: (error) => {
setIsGeneratingChangelog(false);
toast({
title: "Error",
description: error.message || "An unknown error occurred",
variant: "destructive",
});
},
});
}, [documentValue.id, generateChangelog, toast]);
// Confirm publish
const confirmPublish = useCallback(() => {
if (!documentValue.id) return; if (!documentValue.id) return;
publishDraft({ publishDraft({
variables: { variables: {
input: { input: {
documentId: documentValue.id, documentId: documentValue.id,
changelog: publishChangelog,
}, },
}, },
onCompleted: (_, errors) => { onCompleted: (_, errors) => {
@@ -323,6 +393,7 @@ function ShowDocumentContent({
description: `The document has been published successfully`, description: `The document has been published successfully`,
}); });
setIsPublishDialogOpen(false);
// Reload the query to refresh the data // Reload the query to refresh the data
loadQuery({ documentId: documentValue.id, organizationId: organizationId! }); loadQuery({ documentId: documentValue.id, organizationId: organizationId! });
}, },
@@ -334,7 +405,7 @@ function ShowDocumentContent({
}); });
}, },
}); });
}, [documentValue.id, publishDraft, toast, loadQuery]); }, [documentValue.id, publishDraft, toast, loadQuery, publishChangelog, organizationId]);
// Open version history modal // Open version history modal
const handleVersionHistoryClick = useCallback(() => { const handleVersionHistoryClick = useCallback(() => {
@@ -658,7 +729,84 @@ function ShowDocumentContent({
<div className="bg-gray-50 rounded-lg border border-solid-b shadow-sm p-6 mb-4"> <div className="bg-gray-50 rounded-lg border border-solid-b shadow-sm p-6 mb-4">
<div className="grid grid-cols-2 gap-y-3"> <div className="grid grid-cols-2 gap-y-3">
<div> <div>
<span className="font-medium">Document Title:</span> {documentValue.title} <div className="flex items-center gap-2">
<span className="font-medium">Document Title:</span>
{isEditingTitle ? (
<div className="flex items-center gap-2">
<input
type="text"
className="h-9 w-[250px] rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors"
value={editedTitle}
onChange={(e) => setEditedTitle(e.target.value)}
placeholder="Enter document title"
/>
<Button
variant="outline"
size="sm"
onClick={() => {
if (editedTitle.trim()) {
updateDocument({
variables: {
input: {
id: documentValue.id,
title: editedTitle.trim(),
},
},
onCompleted: (_, errors) => {
if (errors) {
toast({
title: "Error updating title",
description: errors[0]?.message || "An unknown error occurred",
variant: "destructive",
});
return;
}
toast({
title: "Success",
description: "Document title updated successfully",
});
setIsEditingTitle(false);
loadQuery({ documentId: documentValue.id, organizationId: organizationId! });
},
onError: (error) => {
toast({
title: "Error updating title",
description: error.message || "An unknown error occurred",
variant: "destructive",
});
},
});
}
}}
disabled={!editedTitle.trim() || isUpdatingDocument}
>
Save
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
setIsEditingTitle(false);
setEditedTitle(documentValue.title || '');
}}
disabled={isUpdatingDocument}
>
Cancel
</Button>
</div>
) : (
<div className="flex items-center gap-2">
<span>{documentValue.title}</span>
<Button
variant="outline"
size="sm"
onClick={() => setIsEditingTitle(true)}
>
Change
</Button>
</div>
)}
</div>
</div> </div>
<div> <div>
<span className="font-medium">Version:</span> {latestVersionNode.version || "N/A"} <span className="font-medium">Version:</span> {latestVersionNode.version || "N/A"}
@@ -826,6 +974,50 @@ function ShowDocumentContent({
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Publish Confirmation Dialog */}
<Dialog open={isPublishDialogOpen} onOpenChange={setIsPublishDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Publish Document</DialogTitle>
<DialogDescription>
Please review and edit the changelog before publishing the document.
</DialogDescription>
</DialogHeader>
<div className="py-4">
<Label htmlFor="changelog">Changelog</Label>
<textarea
id="changelog"
className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
value={publishChangelog}
onChange={(e) => setPublishChangelog(e.target.value)}
placeholder="Enter changelog message"
/>
{isGeneratingChangelog && (
<div className="mt-2 flex items-center gap-2 text-sm text-muted-foreground">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
<span>Generating changelog...</span>
</div>
)}
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsPublishDialogOpen(false)}
disabled={isPublishInFlight}
>
Cancel
</Button>
<Button
variant="default"
onClick={confirmPublish}
disabled={isPublishInFlight || !publishChangelog.trim()}
>
{isPublishInFlight ? "Publishing..." : "Publish"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</PageTemplate> </PageTemplate>
); );
} }
@@ -834,10 +1026,26 @@ export default function ShowDocumentView() {
const [queryRef, loadQuery] = const [queryRef, loadQuery] =
useQueryLoader<ShowDocumentViewQuery>(documentViewQuery); useQueryLoader<ShowDocumentViewQuery>(documentViewQuery);
const { documentId, organizationId } = useParams(); const { documentId, organizationId } = useParams();
const { toast } = useToast();
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
loadQuery({ documentId: documentId!, organizationId: organizationId! }); if (!organizationId || !documentId) {
}, [loadQuery, documentId, organizationId]); toast({
title: "Error",
description: "Invalid document or organization ID",
variant: "destructive",
});
navigate("/");
return;
}
loadQuery({ documentId, organizationId });
}, [loadQuery, documentId, organizationId, toast, navigate]);
if (!organizationId || !documentId) {
return null;
}
if (!queryRef) { if (!queryRef) {
return <ShowDocumentViewSkeleton />; return <ShowDocumentViewSkeleton />;

View File

@@ -22,9 +22,13 @@ export const documentVersionsFragment = graphql`
version version
status status
content content
title
changelog changelog
publishedAt publishedAt
updatedAt updatedAt
owner {
fullName
}
publishedBy { publishedBy {
fullName fullName
} }
@@ -122,6 +126,14 @@ export function VersionHistoryModal({
•{" "} •{" "}
{formatDateTime(version.publishedAt || version.updatedAt)} {formatDateTime(version.publishedAt || version.updatedAt)}
</div> </div>
{version.changelog && (
<div className="text-xs text-tertiary mt-1">
Changelog: {version.changelog}
</div>
)}
<div className="text-xs text-tertiary mt-1">
Owner: {version.owner?.fullName || "Unknown"}
</div>
</div> </div>
</div> </div>
))} ))}
@@ -130,8 +142,12 @@ export function VersionHistoryModal({
{/* Content Area */} {/* Content Area */}
<div className="flex-1 p-6 relative overflow-y-auto"> <div className="flex-1 p-6 relative overflow-y-auto">
<h2 className="text-2xl font-semibold mb-6">{data.title}</h2> {selectedVersion && (
<h2 className="text-2xl font-semibold mb-6">
{versions.find((v) => v.version === selectedVersion)
?.title || data.title}
</h2>
)}
<div className="prose prose-olive max-w-none"> <div className="prose prose-olive max-w-none">
{selectedVersion && ( {selectedVersion && (
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}> <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>

View File

@@ -0,0 +1,92 @@
/**
* @generated SignedSource<<7743b633410bdb28ce017cc0799f2df4>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type GenerateDocumentChangelogInput = {
documentId: string;
};
export type ShowDocumentViewGenerateChangelogMutation$variables = {
input: GenerateDocumentChangelogInput;
};
export type ShowDocumentViewGenerateChangelogMutation$data = {
readonly generateDocumentChangelog: {
readonly changelog: string;
};
};
export type ShowDocumentViewGenerateChangelogMutation = {
response: ShowDocumentViewGenerateChangelogMutation$data;
variables: ShowDocumentViewGenerateChangelogMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "GenerateDocumentChangelogPayload",
"kind": "LinkedField",
"name": "generateDocumentChangelog",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "changelog",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ShowDocumentViewGenerateChangelogMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ShowDocumentViewGenerateChangelogMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "8051b5412b1d2d73a7fe686890e86f41",
"id": null,
"metadata": {},
"name": "ShowDocumentViewGenerateChangelogMutation",
"operationKind": "mutation",
"text": "mutation ShowDocumentViewGenerateChangelogMutation(\n $input: GenerateDocumentChangelogInput!\n) {\n generateDocumentChangelog(input: $input) {\n changelog\n }\n}\n"
}
};
})();
(node as any).hash = "60be598c9c528b2173be7be580057ea7";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<91076d34daad785f7fce9f5a2cbc4018>> * @generated SignedSource<<f67241da996f7e38c84c64b99084ed97>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -11,6 +11,7 @@
import { ConcreteRequest } from 'relay-runtime'; import { ConcreteRequest } from 'relay-runtime';
export type DocumentStatus = "DRAFT" | "PUBLISHED"; export type DocumentStatus = "DRAFT" | "PUBLISHED";
export type PublishDocumentVersionInput = { export type PublishDocumentVersionInput = {
changelog?: string | null | undefined;
documentId: string; documentId: string;
}; };
export type ShowDocumentViewPublishMutation$variables = { export type ShowDocumentViewPublishMutation$variables = {
@@ -23,6 +24,7 @@ export type ShowDocumentViewPublishMutation$data = {
readonly id: string; readonly id: string;
}; };
readonly documentVersion: { readonly documentVersion: {
readonly changelog: string;
readonly id: string; readonly id: string;
readonly publishedAt: string | null | undefined; readonly publishedAt: string | null | undefined;
readonly publishedBy: { readonly publishedBy: {
@@ -93,6 +95,13 @@ v5 = {
"storageKey": null "storageKey": null
}, },
v6 = { v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "changelog",
"storageKey": null
},
v7 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
@@ -126,6 +135,7 @@ return {
(v2/*: any*/), (v2/*: any*/),
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -134,7 +144,7 @@ return {
"name": "publishedBy", "name": "publishedBy",
"plural": false, "plural": false,
"selections": [ "selections": [
(v6/*: any*/) (v7/*: any*/)
], ],
"storageKey": null "storageKey": null
} }
@@ -174,6 +184,7 @@ return {
(v2/*: any*/), (v2/*: any*/),
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -182,7 +193,7 @@ return {
"name": "publishedBy", "name": "publishedBy",
"plural": false, "plural": false,
"selections": [ "selections": [
(v6/*: any*/), (v7/*: any*/),
(v2/*: any*/) (v2/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -196,16 +207,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "794539aab3ddce5d004e72d10b980ca4", "cacheID": "9e097de4f6f61bd4762e57b4863c573b",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "ShowDocumentViewPublishMutation", "name": "ShowDocumentViewPublishMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation ShowDocumentViewPublishMutation(\n $input: PublishDocumentVersionInput!\n) {\n publishDocumentVersion(input: $input) {\n document {\n id\n currentPublishedVersion\n }\n documentVersion {\n id\n status\n publishedAt\n publishedBy {\n fullName\n id\n }\n }\n }\n}\n" "text": "mutation ShowDocumentViewPublishMutation(\n $input: PublishDocumentVersionInput!\n) {\n publishDocumentVersion(input: $input) {\n document {\n id\n currentPublishedVersion\n }\n documentVersion {\n id\n status\n publishedAt\n changelog\n publishedBy {\n fullName\n id\n }\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "66e590251b58966c2ac09625759332f7"; (node as any).hash = "9b2033c7f39d178fb9a25828ade6af3c";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<bf950636055ed83dd68cd6e513a4d83e>> * @generated SignedSource<<edd3d7d537e2ed956853a8be083189fa>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -30,11 +30,15 @@ export type ShowDocumentViewQuery$data = {
readonly content: string; readonly content: string;
readonly createdAt: string; readonly createdAt: string;
readonly id: string; readonly id: string;
readonly owner: {
readonly fullName: string;
};
readonly publishedAt: string | null | undefined; readonly publishedAt: string | null | undefined;
readonly publishedBy: { readonly publishedBy: {
readonly fullName: string; readonly fullName: string;
} | null | undefined; } | null | undefined;
readonly status: DocumentStatus; readonly status: DocumentStatus;
readonly title: string;
readonly updatedAt: string; readonly updatedAt: string;
readonly version: number; readonly version: number;
}; };
@@ -212,20 +216,23 @@ v19 = {
"name": "publishedAt", "name": "publishedAt",
"storageKey": null "storageKey": null
}, },
v20 = { v20 = [
(v11/*: any*/)
],
v21 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "__typename", "name": "__typename",
"storageKey": null "storageKey": null
}, },
v21 = { v22 = {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 100 "value": 100
}, },
v22 = [ v23 = [
(v21/*: any*/), (v22/*: any*/),
{ {
"kind": "Literal", "kind": "Literal",
"name": "orderBy", "name": "orderBy",
@@ -235,14 +242,14 @@ v22 = [
} }
} }
], ],
v23 = { v24 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "cursor", "name": "cursor",
"storageKey": null "storageKey": null
}, },
v24 = { v25 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "PageInfo", "concreteType": "PageInfo",
@@ -267,23 +274,33 @@ v24 = {
], ],
"storageKey": null "storageKey": null
}, },
v25 = [ v26 = [
(v11/*: any*/), (v11/*: any*/),
(v4/*: any*/) (v4/*: any*/)
], ],
v26 = { v27 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "People", "concreteType": "People",
"kind": "LinkedField", "kind": "LinkedField",
"name": "publishedBy", "name": "publishedBy",
"plural": false, "plural": false,
"selections": (v25/*: any*/), "selections": (v26/*: any*/),
"storageKey": null "storageKey": null
}, },
v27 = [ v28 = [
(v21/*: any*/) (v22/*: any*/)
]; ],
v29 = {
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": (v26/*: any*/),
"storageKey": null
};
return { return {
"fragment": { "fragment": {
"argumentDefinitions": (v0/*: any*/), "argumentDefinitions": (v0/*: any*/),
@@ -374,6 +391,17 @@ return {
(v17/*: any*/), (v17/*: any*/),
(v18/*: any*/), (v18/*: any*/),
(v19/*: any*/), (v19/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": (v20/*: any*/),
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -381,9 +409,7 @@ return {
"kind": "LinkedField", "kind": "LinkedField",
"name": "publishedBy", "name": "publishedBy",
"plural": false, "plural": false,
"selections": [ "selections": (v20/*: any*/),
(v11/*: any*/)
],
"storageKey": null "storageKey": null
}, },
(v7/*: any*/), (v7/*: any*/),
@@ -422,7 +448,7 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v20/*: any*/), (v21/*: any*/),
(v4/*: any*/), (v4/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
@@ -430,7 +456,7 @@ return {
(v2/*: any*/), (v2/*: any*/),
{ {
"alias": null, "alias": null,
"args": (v22/*: any*/), "args": (v23/*: any*/),
"concreteType": "PeopleConnection", "concreteType": "PeopleConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "peoples", "name": "peoples",
@@ -455,21 +481,21 @@ return {
(v4/*: any*/), (v4/*: any*/),
(v11/*: any*/), (v11/*: any*/),
(v12/*: any*/), (v12/*: any*/),
(v20/*: any*/) (v21/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v23/*: any*/) (v24/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v24/*: any*/) (v25/*: any*/)
], ],
"storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})" "storageKey": "peoples(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
}, },
{ {
"alias": null, "alias": null,
"args": (v22/*: any*/), "args": (v23/*: any*/),
"filters": [ "filters": [
"orderBy" "orderBy"
], ],
@@ -493,7 +519,7 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v20/*: any*/), (v21/*: any*/),
(v4/*: any*/), (v4/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
@@ -540,10 +566,10 @@ return {
(v16/*: any*/), (v16/*: any*/),
(v19/*: any*/), (v19/*: any*/),
(v8/*: any*/), (v8/*: any*/),
(v26/*: any*/), (v27/*: any*/),
{ {
"alias": null, "alias": null,
"args": (v27/*: any*/), "args": (v28/*: any*/),
"concreteType": "DocumentVersionSignatureConnection", "concreteType": "DocumentVersionSignatureConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "signatures", "name": "signatures",
@@ -594,7 +620,7 @@ return {
"kind": "LinkedField", "kind": "LinkedField",
"name": "signedBy", "name": "signedBy",
"plural": false, "plural": false,
"selections": (v25/*: any*/), "selections": (v26/*: any*/),
"storageKey": null "storageKey": null
}, },
{ {
@@ -604,24 +630,24 @@ return {
"kind": "LinkedField", "kind": "LinkedField",
"name": "requestedBy", "name": "requestedBy",
"plural": false, "plural": false,
"selections": (v25/*: any*/), "selections": (v26/*: any*/),
"storageKey": null "storageKey": null
}, },
(v20/*: any*/) (v21/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v23/*: any*/) (v24/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v24/*: any*/) (v25/*: any*/)
], ],
"storageKey": "signatures(first:100)" "storageKey": "signatures(first:100)"
}, },
{ {
"alias": null, "alias": null,
"args": (v27/*: any*/), "args": (v28/*: any*/),
"filters": null, "filters": null,
"handle": "connection", "handle": "connection",
"key": "SignaturesModal_documentVersions_signatures", "key": "SignaturesModal_documentVersions_signatures",
@@ -671,10 +697,12 @@ return {
(v15/*: any*/), (v15/*: any*/),
(v16/*: any*/), (v16/*: any*/),
(v17/*: any*/), (v17/*: any*/),
(v5/*: any*/),
(v18/*: any*/), (v18/*: any*/),
(v19/*: any*/), (v19/*: any*/),
(v8/*: any*/), (v8/*: any*/),
(v26/*: any*/) (v29/*: any*/),
(v27/*: any*/)
], ],
"storageKey": null "storageKey": null
} }
@@ -714,7 +742,9 @@ return {
(v17/*: any*/), (v17/*: any*/),
(v18/*: any*/), (v18/*: any*/),
(v19/*: any*/), (v19/*: any*/),
(v26/*: any*/), (v5/*: any*/),
(v29/*: any*/),
(v27/*: any*/),
(v7/*: any*/), (v7/*: any*/),
(v8/*: any*/) (v8/*: any*/)
], ],
@@ -736,16 +766,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "a1b25325fb4a764d130253f86435efcb", "cacheID": "7e1dfac8673eeb68f1d3821db3ca95c2",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "ShowDocumentViewQuery", "name": "ShowDocumentViewQuery",
"operationKind": "query", "operationKind": "query",
"text": "query ShowDocumentViewQuery(\n $documentId: ID!\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n name\n ...PeopleSelector_organization\n }\n id\n }\n node(id: $documentId) {\n __typename\n id\n ... on Document {\n title\n description\n createdAt\n updatedAt\n currentPublishedVersion\n documentType\n owner {\n id\n fullName\n primaryEmailAddress\n }\n ...SignaturesModal_documentVersions\n ...VersionHistoryModal_documentVersions\n latestVersion: versions(first: 1) {\n edges {\n node {\n id\n version\n status\n content\n changelog\n publishedAt\n publishedBy {\n fullName\n id\n }\n createdAt\n updatedAt\n }\n }\n }\n }\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment SignaturesModal_documentVersions on Document {\n title\n documentVersions: versions(first: 10) {\n edges {\n node {\n id\n version\n status\n publishedAt\n updatedAt\n publishedBy {\n fullName\n id\n }\n signatures(first: 100) {\n edges {\n node {\n id\n state\n signedAt\n requestedAt\n signedBy {\n fullName\n id\n }\n requestedBy {\n fullName\n id\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment VersionHistoryModal_documentVersions on Document {\n title\n owner {\n fullName\n id\n }\n versionHistory: versions(first: 20) {\n edges {\n node {\n id\n version\n status\n content\n changelog\n publishedAt\n updatedAt\n publishedBy {\n fullName\n id\n }\n }\n }\n }\n}\n" "text": "query ShowDocumentViewQuery(\n $documentId: ID!\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n name\n ...PeopleSelector_organization\n }\n id\n }\n node(id: $documentId) {\n __typename\n id\n ... on Document {\n title\n description\n createdAt\n updatedAt\n currentPublishedVersion\n documentType\n owner {\n id\n fullName\n primaryEmailAddress\n }\n ...SignaturesModal_documentVersions\n ...VersionHistoryModal_documentVersions\n latestVersion: versions(first: 1) {\n edges {\n node {\n id\n version\n status\n content\n changelog\n publishedAt\n title\n owner {\n fullName\n id\n }\n publishedBy {\n fullName\n id\n }\n createdAt\n updatedAt\n }\n }\n }\n }\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment SignaturesModal_documentVersions on Document {\n title\n documentVersions: versions(first: 10) {\n edges {\n node {\n id\n version\n status\n publishedAt\n updatedAt\n publishedBy {\n fullName\n id\n }\n signatures(first: 100) {\n edges {\n node {\n id\n state\n signedAt\n requestedAt\n signedBy {\n fullName\n id\n }\n requestedBy {\n fullName\n id\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment VersionHistoryModal_documentVersions on Document {\n title\n owner {\n fullName\n id\n }\n versionHistory: versions(first: 20) {\n edges {\n node {\n id\n version\n status\n content\n title\n changelog\n publishedAt\n updatedAt\n owner {\n fullName\n id\n }\n publishedBy {\n fullName\n id\n }\n }\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "96c84ddc6ada95da2302068bae68f825"; (node as any).hash = "41461d6b97a3672ae58a36fbb5615489";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<f884c800d6c27056d14eec073a481151>> * @generated SignedSource<<5cf1d2c84b94cf4c7d35965f67b36191>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -31,6 +31,7 @@ export type ShowDocumentViewUpdateDocumentMutation$data = {
readonly id: string; readonly id: string;
readonly primaryEmailAddress: string; readonly primaryEmailAddress: string;
}; };
readonly title: string;
}; };
}; };
}; };
@@ -85,6 +86,13 @@ v2 = [
"name": "documentType", "name": "documentType",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -136,16 +144,16 @@ return {
"selections": (v2/*: any*/) "selections": (v2/*: any*/)
}, },
"params": { "params": {
"cacheID": "62cd235bd1903d80012364fbc3358054", "cacheID": "81d866e2f2de8bc1d7d440bfb2c66144",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "ShowDocumentViewUpdateDocumentMutation", "name": "ShowDocumentViewUpdateDocumentMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation ShowDocumentViewUpdateDocumentMutation(\n $input: UpdateDocumentInput!\n) {\n updateDocument(input: $input) {\n document {\n id\n documentType\n owner {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n}\n" "text": "mutation ShowDocumentViewUpdateDocumentMutation(\n $input: UpdateDocumentInput!\n) {\n updateDocument(input: $input) {\n document {\n id\n documentType\n title\n owner {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "094ff9193be9f023152588380f5114b5"; (node as any).hash = "acaf0d38623ccc096f19c9d6c3e05c69";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<a88582302d627481e4214530f287e233>> * @generated SignedSource<<00c4778ab72567e2c573d98bdbbc4c5f>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -22,11 +22,15 @@ export type VersionHistoryModal_documentVersions$data = {
readonly changelog: string; readonly changelog: string;
readonly content: string; readonly content: string;
readonly id: string; readonly id: string;
readonly owner: {
readonly fullName: string;
};
readonly publishedAt: string | null | undefined; readonly publishedAt: string | null | undefined;
readonly publishedBy: { readonly publishedBy: {
readonly fullName: string; readonly fullName: string;
} | null | undefined; } | null | undefined;
readonly status: DocumentStatus; readonly status: DocumentStatus;
readonly title: string;
readonly updatedAt: string; readonly updatedAt: string;
readonly version: number; readonly version: number;
}; };
@@ -40,7 +44,14 @@ export type VersionHistoryModal_documentVersions$key = {
}; };
const node: ReaderFragment = (function(){ const node: ReaderFragment = (function(){
var v0 = [ var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
v1 = [
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -48,30 +59,25 @@ var v0 = [
"name": "fullName", "name": "fullName",
"storageKey": null "storageKey": null
} }
]; ],
v2 = {
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": (v1/*: any*/),
"storageKey": null
};
return { return {
"argumentDefinitions": [], "argumentDefinitions": [],
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
"name": "VersionHistoryModal_documentVersions", "name": "VersionHistoryModal_documentVersions",
"selections": [ "selections": [
{ (v0/*: any*/),
"alias": null, (v2/*: any*/),
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": (v0/*: any*/),
"storageKey": null
},
{ {
"alias": "versionHistory", "alias": "versionHistory",
"args": [ "args": [
@@ -130,6 +136,7 @@ return {
"name": "content", "name": "content",
"storageKey": null "storageKey": null
}, },
(v0/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -151,6 +158,7 @@ return {
"name": "updatedAt", "name": "updatedAt",
"storageKey": null "storageKey": null
}, },
(v2/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -158,7 +166,7 @@ return {
"kind": "LinkedField", "kind": "LinkedField",
"name": "publishedBy", "name": "publishedBy",
"plural": false, "plural": false,
"selections": (v0/*: any*/), "selections": (v1/*: any*/),
"storageKey": null "storageKey": null
} }
], ],
@@ -176,6 +184,6 @@ return {
}; };
})(); })();
(node as any).hash = "ffcba061a60a144c663de41a48e784d1"; (node as any).hash = "344e822be4a3eb20d147ecd1c3ea7ced";
export default node; export default node;

41
pkg/agents/agents.go Normal file
View File

@@ -0,0 +1,41 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package agents
import (
"github.com/openai/openai-go"
"github.com/openai/openai-go/option"
"go.gearno.de/kit/log"
)
type (
Agent struct {
l *log.Logger
cfg Config
client *openai.Client
}
Config struct {
OpenAIAPIKey string
Temperature float64
ModelName string
}
)
func NewAgent(l *log.Logger, cfg Config) *Agent {
client := openai.NewClient(option.WithAPIKey(cfg.OpenAIAPIKey))
return &Agent{l: l, cfg: cfg, client: &client}
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package agents
import (
"context"
"fmt"
"github.com/openai/openai-go"
"github.com/openai/openai-go/packages/param"
)
const (
changelogGeneratorSystemPrompt = `
# Role:You are an assistant that creates clear and concise changelogs.
# Objective
Given two versions of a document — the "old version" and the "new version" — identify and summarize all meaningful changes between them.
Focus on additions, deletions, modifications, and restructuring.
# Response Format
Respond with simple and short phrases that describe the changes, if possible use a single phrase.
# Change types
Change types can include: "Added", "Removed", "Updated", "Reworded", "Reorganized", "Fixed", etc.
# SOP
- Be objective and neutral in tone.
- Do not comment on the quality of the change.
- Use the language of the document.
**Example output format:**
Respond ONLY with the phrase that describes the changes. No explanation, no markdown, no preamble. Like this:
Added Clause about sharing personal information with trusted partners
`
)
func (a *Agent) GenerateChangelog(ctx context.Context, oldContent string, newContent string) (*string, error) {
model := openai.ChatModel(a.cfg.ModelName)
chatCompletion, err := a.client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Messages: []openai.ChatCompletionMessageParamUnion{
openai.SystemMessage(changelogGeneratorSystemPrompt),
openai.UserMessage(fmt.Sprintf(`Old content: %s`, oldContent)),
openai.UserMessage(fmt.Sprintf(`New content: %s`, newContent)),
},
Model: model,
Temperature: param.NewOpt(a.cfg.Temperature),
})
if err != nil {
return nil, fmt.Errorf("failed to parse vendor info: %w", err)
}
if len(chatCompletion.Choices) == 0 {
return nil, fmt.Errorf("no completion choices returned from API")
}
return &chatCompletion.Choices[0].Message.Content, nil
}

View File

@@ -20,24 +20,10 @@ import (
"fmt" "fmt"
"github.com/openai/openai-go" "github.com/openai/openai-go"
"github.com/openai/openai-go/option"
"github.com/openai/openai-go/packages/param" "github.com/openai/openai-go/packages/param"
"go.gearno.de/kit/log"
) )
type ( type (
VendorAssessment struct {
l *log.Logger
cfg Config
client *openai.Client
}
Config struct {
OpenAIAPIKey string
Temperature float64
ModelName string
}
vendorInfo struct { vendorInfo struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
@@ -58,7 +44,7 @@ type (
) )
const ( const (
systemPrompt = ` assessVendorSystemPrompt = `
# Role: You are a compliance assistant. # Role: You are a compliance assistant.
# Objective # Objective
@@ -136,21 +122,15 @@ const (
` `
) )
func NewVendorAssessment(l *log.Logger, cfg Config) *VendorAssessment { func (a *Agent) AssessVendor(ctx context.Context, websiteURL string) (*vendorInfo, error) {
client := openai.NewClient(option.WithAPIKey(cfg.OpenAIAPIKey)) model := openai.ChatModel(a.cfg.ModelName)
chatCompletion, err := a.client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
return &VendorAssessment{l: l, cfg: cfg, client: &client}
}
func (va *VendorAssessment) Fetch(ctx context.Context, websiteURL string) (*vendorInfo, error) {
model := openai.ChatModel(va.cfg.ModelName)
chatCompletion, err := va.client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Messages: []openai.ChatCompletionMessageParamUnion{ Messages: []openai.ChatCompletionMessageParamUnion{
openai.SystemMessage(systemPrompt), openai.SystemMessage(assessVendorSystemPrompt),
openai.UserMessage(websiteURL), openai.UserMessage(websiteURL),
}, },
Model: model, Model: model,
Temperature: param.NewOpt(va.cfg.Temperature), Temperature: param.NewOpt(a.cfg.Temperature),
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse vendor info: %w", err) return nil, fmt.Errorf("failed to parse vendor info: %w", err)

View File

@@ -30,6 +30,8 @@ type (
DocumentVersion struct { DocumentVersion struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
DocumentID gid.GID `db:"document_id"` DocumentID gid.GID `db:"document_id"`
Title string `db:"title"`
OwnerID gid.GID `db:"owner_id"`
VersionNumber int `db:"version_number"` VersionNumber int `db:"version_number"`
Content string `db:"content"` Content string `db:"content"`
Changelog string `db:"changelog"` Changelog string `db:"changelog"`
@@ -55,6 +57,8 @@ func (p *DocumentVersions) LoadByDocumentID(
SELECT SELECT
id, id,
document_id, document_id,
title,
owner_id,
version_number, version_number,
content, content,
changelog, changelog,
@@ -71,10 +75,11 @@ WHERE
AND document_id = @document_id AND document_id = @document_id
AND %s AND %s
` `
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"document_id": documentID} args := pgx.StrictNamedArgs{
"document_id": documentID,
}
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
maps.Copy(args, cursor.SQLArguments()) maps.Copy(args, cursor.SQLArguments())
@@ -112,6 +117,8 @@ func (p *DocumentVersion) LoadByID(
SELECT SELECT
id, id,
document_id, document_id,
title,
owner_id,
version_number, version_number,
content, content,
changelog, changelog,
@@ -131,7 +138,9 @@ LIMIT 1;
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"document_version_id": documentVersionID} args := pgx.StrictNamedArgs{
"document_version_id": documentVersionID,
}
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
@@ -159,6 +168,8 @@ INSERT INTO document_versions (
tenant_id, tenant_id,
id, id,
document_id, document_id,
title,
owner_id,
version_number, version_number,
content, content,
changelog, changelog,
@@ -166,10 +177,13 @@ INSERT INTO document_versions (
status, status,
created_at, created_at,
updated_at updated_at
) VALUES ( )
VALUES (
@tenant_id, @tenant_id,
@id, @id,
@document_id, @document_id,
@title,
@owner_id,
@version_number, @version_number,
@content, @content,
@changelog, @changelog,
@@ -179,24 +193,24 @@ INSERT INTO document_versions (
@updated_at @updated_at
) )
` `
now := time.Now()
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(), "tenant_id": scope.GetTenantID(),
"id": p.ID, "id": p.ID,
"document_id": p.DocumentID, "document_id": p.DocumentID,
"title": p.Title,
"owner_id": p.OwnerID,
"version_number": p.VersionNumber, "version_number": p.VersionNumber,
"content": p.Content, "content": p.Content,
"changelog": p.Changelog, "changelog": p.Changelog,
"created_by": p.CreatedBy, "created_by": p.CreatedBy,
"status": p.Status, "status": p.Status,
"created_at": now, "created_at": p.CreatedAt,
"updated_at": now, "updated_at": p.UpdatedAt,
} }
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("error creating/updating document version: %w", err) return fmt.Errorf("error creating document version: %w", err)
} }
return nil return nil
@@ -213,6 +227,8 @@ func (p *DocumentVersion) LoadByDocumentIDAndVersionNumber(
SELECT SELECT
id, id,
document_id, document_id,
title,
owner_id,
version_number, version_number,
content, content,
changelog, changelog,
@@ -237,7 +253,6 @@ LIMIT 1;
"document_id": documentID, "document_id": documentID,
"version_number": versionNumber, "version_number": versionNumber,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
@@ -265,6 +280,8 @@ func (p *DocumentVersion) LoadLatestVersion(
SELECT SELECT
id, id,
document_id, document_id,
title,
owner_id,
version_number, version_number,
content, content,
changelog, changelog,
@@ -282,13 +299,11 @@ WHERE
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT 1; LIMIT 1;
` `
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"document_id": documentID, "document_id": documentID,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
@@ -313,6 +328,8 @@ func (p DocumentVersion) Update(
) error { ) error {
q := ` q := `
UPDATE document_versions SET UPDATE document_versions SET
title = @title,
owner_id = @owner_id,
changelog = @changelog, changelog = @changelog,
status = @status, status = @status,
content = @content, content = @content,
@@ -320,12 +337,15 @@ UPDATE document_versions SET
published_at = @published_at, published_at = @published_at,
updated_at = @updated_at updated_at = @updated_at
WHERE %s WHERE %s
AND id = @document_version_id;` AND id = @document_version_id
`
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"document_version_id": p.ID, "document_version_id": p.ID,
"title": p.Title,
"owner_id": p.OwnerID,
"changelog": p.Changelog, "changelog": p.Changelog,
"status": p.Status, "status": p.Status,
"content": p.Content, "content": p.Content,

View File

@@ -0,0 +1,35 @@
ALTER INDEX policy_versions_pkey RENAME TO document_versions_pkey;
ALTER TABLE document_versions
RENAME CONSTRAINT policy_versions_policy_id_version_number_key
TO document_versions_document_id_version_number_key;
ALTER TABLE document_versions
RENAME CONSTRAINT policy_versions_published_by_fkey
TO document_versions_published_by_fkey;
ALTER TABLE document_versions
ADD CONSTRAINT document_versions_created_by_fkey
FOREIGN KEY (created_by) REFERENCES peoples(id)
ON DELETE RESTRICT
ON UPDATE CASCADE;
ALTER TABLE document_versions
ADD COLUMN title TEXT,
ADD COLUMN owner_id TEXT;
UPDATE document_versions dv
SET title = d.title,
owner_id = d.owner_id
FROM documents d
WHERE dv.document_id = d.id;
ALTER TABLE document_versions
ALTER COLUMN title SET NOT NULL,
ALTER COLUMN owner_id SET NOT NULL;
ALTER TABLE document_versions
ADD CONSTRAINT document_versions_owner_id_fkey
FOREIGN KEY (owner_id) REFERENCES peoples(id)
ON DELETE RESTRICT
ON UPDATE CASCADE;

View File

@@ -69,10 +69,58 @@ func (s *DocumentService) Get(
return document, nil return document, nil
} }
func (s DocumentService) GenerateChangelog(
ctx context.Context,
documentID gid.GID,
) (*string, error) {
draftVersion := &coredata.DocumentVersion{}
publishedVersion := &coredata.DocumentVersion{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
if err := draftVersion.LoadLatestVersion(ctx, conn, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load draft version: %w", err)
}
if draftVersion.Status != coredata.DocumentStatusDraft {
return fmt.Errorf("latest version is not a draft")
}
document := &coredata.Document{}
if err := document.LoadByID(ctx, conn, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load document: %w", err)
}
if document.CurrentPublishedVersion == nil {
publishedVersion.Content = ""
} else {
if err := publishedVersion.LoadByDocumentIDAndVersionNumber(ctx, conn, s.svc.scope, documentID, *document.CurrentPublishedVersion); err != nil {
return fmt.Errorf("cannot load published version: %w", err)
}
}
return nil
},
)
if err != nil {
return nil, err
}
changelog, err := s.svc.agent.GenerateChangelog(ctx, publishedVersion.Content, draftVersion.Content)
if err != nil {
return nil, fmt.Errorf("failed to generate changelog: %w", err)
}
return changelog, nil
}
func (s *DocumentService) PublishVersion( func (s *DocumentService) PublishVersion(
ctx context.Context, ctx context.Context,
documentID gid.GID, documentID gid.GID,
publishedBy gid.GID, publishedBy gid.GID,
changelog *string,
) (*coredata.Document, *coredata.DocumentVersion, error) { ) (*coredata.Document, *coredata.DocumentVersion, error) {
document := &coredata.Document{} document := &coredata.Document{}
documentVersion := &coredata.DocumentVersion{} documentVersion := &coredata.DocumentVersion{}
@@ -93,6 +141,10 @@ func (s *DocumentService) PublishVersion(
return fmt.Errorf("cannot publish version") return fmt.Errorf("cannot publish version")
} }
if changelog != nil {
documentVersion.Changelog = *changelog
}
document.CurrentPublishedVersion = &documentVersion.VersionNumber document.CurrentPublishedVersion = &documentVersion.VersionNumber
document.UpdatedAt = now document.UpdatedAt = now
@@ -142,6 +194,8 @@ func (s *DocumentService) Create(
documentVersion := &coredata.DocumentVersion{ documentVersion := &coredata.DocumentVersion{
ID: documentVersionID, ID: documentVersionID,
DocumentID: documentID, DocumentID: documentID,
Title: req.Title,
OwnerID: req.OwnerID,
VersionNumber: 1, VersionNumber: 1,
Content: req.Content, Content: req.Content,
Status: coredata.DocumentStatusDraft, Status: coredata.DocumentStatusDraft,
@@ -149,6 +203,7 @@ func (s *DocumentService) Create(
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
err := s.svc.pg.WithTx( err := s.svc.pg.WithTx(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
@@ -348,6 +403,7 @@ func (s *DocumentService) UpdateVersion(
req UpdateDocumentVersionRequest, req UpdateDocumentVersionRequest,
) (*coredata.DocumentVersion, error) { ) (*coredata.DocumentVersion, error) {
documentVersion := &coredata.DocumentVersion{} documentVersion := &coredata.DocumentVersion{}
document := &coredata.Document{}
err := s.svc.pg.WithTx( err := s.svc.pg.WithTx(
ctx, ctx,
@@ -356,10 +412,16 @@ func (s *DocumentService) UpdateVersion(
return fmt.Errorf("cannot load document version %q: %w", req.ID, err) return fmt.Errorf("cannot load document version %q: %w", req.ID, err)
} }
if err := document.LoadByID(ctx, conn, s.svc.scope, documentVersion.DocumentID); err != nil {
return fmt.Errorf("cannot load document %q: %w", documentVersion.DocumentID, err)
}
if documentVersion.Status != coredata.DocumentStatusDraft { if documentVersion.Status != coredata.DocumentStatusDraft {
return fmt.Errorf("cannot update published version") return fmt.Errorf("cannot update published version")
} }
documentVersion.Title = document.Title
documentVersion.OwnerID = document.OwnerID
documentVersion.Content = req.Content documentVersion.Content = req.Content
documentVersion.UpdatedAt = time.Now() documentVersion.UpdatedAt = time.Now()
@@ -473,12 +535,17 @@ func (s *DocumentService) CreateDraft(
draftVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType) draftVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
latestVersion := &coredata.DocumentVersion{} latestVersion := &coredata.DocumentVersion{}
document := &coredata.Document{}
draftVersion := &coredata.DocumentVersion{} draftVersion := &coredata.DocumentVersion{}
now := time.Now() now := time.Now()
err := s.svc.pg.WithTx( err := s.svc.pg.WithTx(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
if err := document.LoadByID(ctx, conn, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load document: %w", err)
}
if err := latestVersion.LoadLatestVersion(ctx, conn, s.svc.scope, documentID); err != nil { if err := latestVersion.LoadLatestVersion(ctx, conn, s.svc.scope, documentID); err != nil {
return fmt.Errorf("cannot load latest version: %w", err) return fmt.Errorf("cannot load latest version: %w", err)
} }
@@ -489,6 +556,8 @@ func (s *DocumentService) CreateDraft(
draftVersion.ID = draftVersionID draftVersion.ID = draftVersionID
draftVersion.DocumentID = documentID draftVersion.DocumentID = documentID
draftVersion.Title = document.Title
draftVersion.OwnerID = document.OwnerID
draftVersion.VersionNumber = latestVersion.VersionNumber + 1 draftVersion.VersionNumber = latestVersion.VersionNumber + 1
draftVersion.Content = latestVersion.Content draftVersion.Content = latestVersion.Content
draftVersion.Status = coredata.DocumentStatusDraft draftVersion.Status = coredata.DocumentStatusDraft
@@ -640,6 +709,7 @@ func (s *DocumentService) Update(
documentID gid.GID, documentID gid.GID,
newOwnerID *gid.GID, newOwnerID *gid.GID,
documentType *coredata.DocumentType, documentType *coredata.DocumentType,
title *string,
) (*coredata.Document, error) { ) (*coredata.Document, error) {
document := &coredata.Document{} document := &coredata.Document{}
people := &coredata.People{} people := &coredata.People{}
@@ -662,6 +732,11 @@ func (s *DocumentService) Update(
if documentType != nil { if documentType != nil {
document.DocumentType = *documentType document.DocumentType = *documentType
} }
if title != nil {
document.Title = *title
}
document.UpdatedAt = now document.UpdatedAt = now
if err := document.Update(ctx, tx, s.svc.scope); err != nil { if err := document.Update(ctx, tx, s.svc.scope); err != nil {

View File

@@ -29,13 +29,13 @@ import (
type ( type (
Service struct { Service struct {
pg *pg.Client pg *pg.Client
s3 *s3.Client s3 *s3.Client
bucket string bucket string
encryptionKey cipher.EncryptionKey encryptionKey cipher.EncryptionKey
hostname string hostname string
tokenSecret string tokenSecret string
vendorAssessment agents.Config agentConfig agents.Config
} }
TenantService struct { TenantService struct {
@@ -46,7 +46,7 @@ type (
scope coredata.Scoper scope coredata.Scoper
hostname string hostname string
tokenSecret string tokenSecret string
vendorAssessment *agents.VendorAssessment agent *agents.Agent
Frameworks *FrameworkService Frameworks *FrameworkService
Measures *MeasureService Measures *MeasureService
Tasks *TaskService Tasks *TaskService
@@ -72,20 +72,20 @@ func NewService(
bucket string, bucket string,
hostname string, hostname string,
tokenSecret string, tokenSecret string,
vendorAssessment agents.Config, agentConfig agents.Config,
) (*Service, error) { ) (*Service, error) {
if bucket == "" { if bucket == "" {
return nil, fmt.Errorf("bucket is required") return nil, fmt.Errorf("bucket is required")
} }
svc := &Service{ svc := &Service{
pg: pgClient, pg: pgClient,
s3: s3Client, s3: s3Client,
bucket: bucket, bucket: bucket,
encryptionKey: encryptionKey, encryptionKey: encryptionKey,
hostname: hostname, hostname: hostname,
tokenSecret: tokenSecret, tokenSecret: tokenSecret,
vendorAssessment: vendorAssessment, agentConfig: agentConfig,
} }
return svc, nil return svc, nil
@@ -93,14 +93,14 @@ func NewService(
func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService { func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService := &TenantService{ tenantService := &TenantService{
pg: s.pg, pg: s.pg,
s3: s.s3, s3: s.s3,
bucket: s.bucket, bucket: s.bucket,
encryptionKey: s.encryptionKey, encryptionKey: s.encryptionKey,
hostname: s.hostname, hostname: s.hostname,
scope: coredata.NewScope(tenantID), scope: coredata.NewScope(tenantID),
tokenSecret: s.tokenSecret, tokenSecret: s.tokenSecret,
vendorAssessment: agents.NewVendorAssessment(nil, s.vendorAssessment), agent: agents.NewAgent(nil, s.agentConfig),
} }
tenantService.Frameworks = &FrameworkService{svc: tenantService} tenantService.Frameworks = &FrameworkService{svc: tenantService}

View File

@@ -447,7 +447,7 @@ func (s VendorService) Assess(
ctx context.Context, ctx context.Context,
req AssessVendorRequest, req AssessVendorRequest,
) (*coredata.Vendor, error) { ) (*coredata.Vendor, error) {
vendorInfo, err := s.svc.vendorAssessment.Fetch(ctx, req.WebsiteURL) vendorInfo, err := s.svc.agent.AssessVendor(ctx, req.WebsiteURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to assess vendor info: %w", err) return nil, fmt.Errorf("failed to assess vendor info: %w", err)
} }

View File

@@ -187,13 +187,13 @@ func (impl *Implm) Run(
} }
} }
vendorAssessmentConfig := agents.Config{ agentConfig := agents.Config{
OpenAIAPIKey: impl.cfg.OpenAI.APIKey, OpenAIAPIKey: impl.cfg.OpenAI.APIKey,
Temperature: impl.cfg.OpenAI.Temperature, Temperature: impl.cfg.OpenAI.Temperature,
ModelName: impl.cfg.OpenAI.ModelName, ModelName: impl.cfg.OpenAI.ModelName,
} }
vendorAssessment := agents.NewVendorAssessment(l.Named("vendor-assessment"), vendorAssessmentConfig) agent := agents.NewAgent(l.Named("agent"), agentConfig)
usrmgrService, err := usrmgr.NewService( usrmgrService, err := usrmgr.NewService(
ctx, ctx,
@@ -215,7 +215,7 @@ func (impl *Implm) Run(
impl.cfg.AWS.Bucket, impl.cfg.AWS.Bucket,
impl.cfg.Hostname, impl.cfg.Hostname,
impl.cfg.Auth.Cookie.Secret, impl.cfg.Auth.Cookie.Secret,
vendorAssessmentConfig, agentConfig,
) )
if err != nil { if err != nil {
return fmt.Errorf("cannot create probo service: %w", err) return fmt.Errorf("cannot create probo service: %w", err)
@@ -227,7 +227,7 @@ func (impl *Implm) Run(
Probo: proboService, Probo: proboService,
Usrmgr: usrmgrService, Usrmgr: usrmgrService,
ConnectorRegistry: defaultConnectorRegistry, ConnectorRegistry: defaultConnectorRegistry,
VendorAssessment: vendorAssessment, Agent: agent,
SafeRedirect: &saferedirect.SafeRedirect{AllowedHost: impl.cfg.Hostname}, SafeRedirect: &saferedirect.SafeRedirect{AllowedHost: impl.cfg.Hostname},
Logger: l.Named("http.server"), Logger: l.Named("http.server"),
Auth: console_v1.AuthConfig{ Auth: console_v1.AuthConfig{

View File

@@ -1157,6 +1157,9 @@ type Mutation {
publishDocumentVersion( publishDocumentVersion(
input: PublishDocumentVersionInput! input: PublishDocumentVersionInput!
): PublishDocumentVersionPayload! ): PublishDocumentVersionPayload!
generateDocumentChangelog(
input: GenerateDocumentChangelogInput!
): GenerateDocumentChangelogPayload!
createDraftDocumentVersion( createDraftDocumentVersion(
input: CreateDraftDocumentVersionInput! input: CreateDraftDocumentVersionInput!
): CreateDraftDocumentVersionPayload! ): CreateDraftDocumentVersionPayload!
@@ -1746,6 +1749,8 @@ type DocumentVersion implements Node {
version: Int! version: Int!
content: String! content: String!
changelog: String! changelog: String!
title: String!
owner: People! @goField(forceResolver: true)
signatures( signatures(
first: Int first: Int
@@ -1827,6 +1832,7 @@ type RequestSignaturePayload {
input PublishDocumentVersionInput { input PublishDocumentVersionInput {
documentId: ID! documentId: ID!
changelog: String
} }
type PublishDocumentVersionPayload { type PublishDocumentVersionPayload {
@@ -1885,6 +1891,14 @@ type ExportAuditPayload {
url: String! url: String!
} }
input GenerateDocumentChangelogInput {
documentId: ID!
}
type GenerateDocumentChangelogPayload {
changelog: String!
}
input AssessVendorInput { input AssessVendorInput {
id: ID! id: ID!
websiteUrl: String! websiteUrl: String!

View File

@@ -343,10 +343,12 @@ type ComplexityRoot struct {
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
Document func(childComplexity int) int Document func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Owner func(childComplexity int) int
PublishedAt func(childComplexity int) int PublishedAt func(childComplexity int) int
PublishedBy func(childComplexity int) int PublishedBy func(childComplexity int) int
Signatures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder) int Signatures func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder) int
Status func(childComplexity int) int Status func(childComplexity int) int
Title func(childComplexity int) int
UpdatedAt func(childComplexity int) int UpdatedAt func(childComplexity int) int
Version func(childComplexity int) int Version func(childComplexity int) int
} }
@@ -437,6 +439,10 @@ type ComplexityRoot struct {
EvidenceEdge func(childComplexity int) int EvidenceEdge func(childComplexity int) int
} }
GenerateDocumentChangelogPayload struct {
Changelog func(childComplexity int) int
}
ImportFrameworkPayload struct { ImportFrameworkPayload struct {
FrameworkEdge func(childComplexity int) int FrameworkEdge func(childComplexity int) int
} }
@@ -513,6 +519,7 @@ type ComplexityRoot struct {
DeleteVendorComplianceReport func(childComplexity int, input types.DeleteVendorComplianceReportInput) int DeleteVendorComplianceReport func(childComplexity int, input types.DeleteVendorComplianceReportInput) int
ExportAudit func(childComplexity int, input types.ExportAuditInput) int ExportAudit func(childComplexity int, input types.ExportAuditInput) int
FulfillEvidence func(childComplexity int, input types.FulfillEvidenceInput) int FulfillEvidence func(childComplexity int, input types.FulfillEvidenceInput) int
GenerateDocumentChangelog func(childComplexity int, input types.GenerateDocumentChangelogInput) int
ImportFramework func(childComplexity int, input types.ImportFrameworkInput) int ImportFramework func(childComplexity int, input types.ImportFrameworkInput) int
ImportMeasure func(childComplexity int, input types.ImportMeasureInput) int ImportMeasure func(childComplexity int, input types.ImportMeasureInput) int
InviteUser func(childComplexity int, input types.InviteUserInput) int InviteUser func(childComplexity int, input types.InviteUserInput) int
@@ -889,6 +896,7 @@ type DocumentResolver interface {
type DocumentVersionResolver interface { type DocumentVersionResolver interface {
Document(ctx context.Context, obj *types.DocumentVersion) (*types.Document, error) Document(ctx context.Context, obj *types.DocumentVersion) (*types.Document, error)
Owner(ctx context.Context, obj *types.DocumentVersion) (*types.People, error)
Signatures(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder) (*types.DocumentVersionSignatureConnection, error) Signatures(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder) (*types.DocumentVersionSignatureConnection, error)
PublishedBy(ctx context.Context, obj *types.DocumentVersion) (*types.People, error) PublishedBy(ctx context.Context, obj *types.DocumentVersion) (*types.People, error)
} }
@@ -963,6 +971,7 @@ type MutationResolver interface {
UpdateDocument(ctx context.Context, input types.UpdateDocumentInput) (*types.UpdateDocumentPayload, error) UpdateDocument(ctx context.Context, input types.UpdateDocumentInput) (*types.UpdateDocumentPayload, error)
DeleteDocument(ctx context.Context, input types.DeleteDocumentInput) (*types.DeleteDocumentPayload, error) DeleteDocument(ctx context.Context, input types.DeleteDocumentInput) (*types.DeleteDocumentPayload, error)
PublishDocumentVersion(ctx context.Context, input types.PublishDocumentVersionInput) (*types.PublishDocumentVersionPayload, error) PublishDocumentVersion(ctx context.Context, input types.PublishDocumentVersionInput) (*types.PublishDocumentVersionPayload, error)
GenerateDocumentChangelog(ctx context.Context, input types.GenerateDocumentChangelogInput) (*types.GenerateDocumentChangelogPayload, error)
CreateDraftDocumentVersion(ctx context.Context, input types.CreateDraftDocumentVersionInput) (*types.CreateDraftDocumentVersionPayload, error) CreateDraftDocumentVersion(ctx context.Context, input types.CreateDraftDocumentVersionInput) (*types.CreateDraftDocumentVersionPayload, error)
UpdateDocumentVersion(ctx context.Context, input types.UpdateDocumentVersionInput) (*types.UpdateDocumentVersionPayload, error) UpdateDocumentVersion(ctx context.Context, input types.UpdateDocumentVersionInput) (*types.UpdateDocumentVersionPayload, error)
RequestSignature(ctx context.Context, input types.RequestSignatureInput) (*types.RequestSignaturePayload, error) RequestSignature(ctx context.Context, input types.RequestSignatureInput) (*types.RequestSignaturePayload, error)
@@ -1897,6 +1906,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.DocumentVersion.ID(childComplexity), true return e.complexity.DocumentVersion.ID(childComplexity), true
case "DocumentVersion.owner":
if e.complexity.DocumentVersion.Owner == nil {
break
}
return e.complexity.DocumentVersion.Owner(childComplexity), true
case "DocumentVersion.publishedAt": case "DocumentVersion.publishedAt":
if e.complexity.DocumentVersion.PublishedAt == nil { if e.complexity.DocumentVersion.PublishedAt == nil {
break break
@@ -1930,6 +1946,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.DocumentVersion.Status(childComplexity), true return e.complexity.DocumentVersion.Status(childComplexity), true
case "DocumentVersion.title":
if e.complexity.DocumentVersion.Title == nil {
break
}
return e.complexity.DocumentVersion.Title(childComplexity), true
case "DocumentVersion.updatedAt": case "DocumentVersion.updatedAt":
if e.complexity.DocumentVersion.UpdatedAt == nil { if e.complexity.DocumentVersion.UpdatedAt == nil {
break break
@@ -2278,6 +2301,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.FulfillEvidencePayload.EvidenceEdge(childComplexity), true return e.complexity.FulfillEvidencePayload.EvidenceEdge(childComplexity), true
case "GenerateDocumentChangelogPayload.changelog":
if e.complexity.GenerateDocumentChangelogPayload.Changelog == nil {
break
}
return e.complexity.GenerateDocumentChangelogPayload.Changelog(childComplexity), true
case "ImportFrameworkPayload.frameworkEdge": case "ImportFrameworkPayload.frameworkEdge":
if e.complexity.ImportFrameworkPayload.FrameworkEdge == nil { if e.complexity.ImportFrameworkPayload.FrameworkEdge == nil {
break break
@@ -2892,6 +2922,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Mutation.FulfillEvidence(childComplexity, args["input"].(types.FulfillEvidenceInput)), true return e.complexity.Mutation.FulfillEvidence(childComplexity, args["input"].(types.FulfillEvidenceInput)), true
case "Mutation.generateDocumentChangelog":
if e.complexity.Mutation.GenerateDocumentChangelog == nil {
break
}
args, err := ec.field_Mutation_generateDocumentChangelog_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.GenerateDocumentChangelog(childComplexity, args["input"].(types.GenerateDocumentChangelogInput)), true
case "Mutation.importFramework": case "Mutation.importFramework":
if e.complexity.Mutation.ImportFramework == nil { if e.complexity.Mutation.ImportFramework == nil {
break break
@@ -4547,6 +4589,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputExportAuditInput, ec.unmarshalInputExportAuditInput,
ec.unmarshalInputFrameworkOrder, ec.unmarshalInputFrameworkOrder,
ec.unmarshalInputFulfillEvidenceInput, ec.unmarshalInputFulfillEvidenceInput,
ec.unmarshalInputGenerateDocumentChangelogInput,
ec.unmarshalInputImportFrameworkInput, ec.unmarshalInputImportFrameworkInput,
ec.unmarshalInputImportMeasureInput, ec.unmarshalInputImportMeasureInput,
ec.unmarshalInputInviteUserInput, ec.unmarshalInputInviteUserInput,
@@ -5837,6 +5880,9 @@ type Mutation {
publishDocumentVersion( publishDocumentVersion(
input: PublishDocumentVersionInput! input: PublishDocumentVersionInput!
): PublishDocumentVersionPayload! ): PublishDocumentVersionPayload!
generateDocumentChangelog(
input: GenerateDocumentChangelogInput!
): GenerateDocumentChangelogPayload!
createDraftDocumentVersion( createDraftDocumentVersion(
input: CreateDraftDocumentVersionInput! input: CreateDraftDocumentVersionInput!
): CreateDraftDocumentVersionPayload! ): CreateDraftDocumentVersionPayload!
@@ -6426,6 +6472,8 @@ type DocumentVersion implements Node {
version: Int! version: Int!
content: String! content: String!
changelog: String! changelog: String!
title: String!
owner: People! @goField(forceResolver: true)
signatures( signatures(
first: Int first: Int
@@ -6507,6 +6555,7 @@ type RequestSignaturePayload {
input PublishDocumentVersionInput { input PublishDocumentVersionInput {
documentId: ID! documentId: ID!
changelog: String
} }
type PublishDocumentVersionPayload { type PublishDocumentVersionPayload {
@@ -6565,6 +6614,14 @@ type ExportAuditPayload {
url: String! url: String!
} }
input GenerateDocumentChangelogInput {
documentId: ID!
}
type GenerateDocumentChangelogPayload {
changelog: String!
}
input AssessVendorInput { input AssessVendorInput {
id: ID! id: ID!
websiteUrl: String! websiteUrl: String!
@@ -8799,6 +8856,29 @@ func (ec *executionContext) field_Mutation_fulfillEvidence_argsInput(
return zeroVal, nil return zeroVal, nil
} }
func (ec *executionContext) field_Mutation_generateDocumentChangelog_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_generateDocumentChangelog_argsInput(ctx, rawArgs)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_generateDocumentChangelog_argsInput(
ctx context.Context,
rawArgs map[string]any,
) (types.GenerateDocumentChangelogInput, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
if tmp, ok := rawArgs["input"]; ok {
return ec.unmarshalNGenerateDocumentChangelogInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateDocumentChangelogInput(ctx, tmp)
}
var zeroVal types.GenerateDocumentChangelogInput
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_importFramework_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_importFramework_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -17134,6 +17214,116 @@ func (ec *executionContext) fieldContext_DocumentVersion_changelog(_ context.Con
return fc, nil return fc, nil
} }
func (ec *executionContext) _DocumentVersion_title(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersion) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_DocumentVersion_title(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.Title, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_DocumentVersion_title(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "DocumentVersion",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _DocumentVersion_owner(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersion) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_DocumentVersion_owner(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.DocumentVersion().Owner(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*types.People)
fc.Result = res
return ec.marshalNPeople2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPeople(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_DocumentVersion_owner(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "DocumentVersion",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_People_id(ctx, field)
case "fullName":
return ec.fieldContext_People_fullName(ctx, field)
case "primaryEmailAddress":
return ec.fieldContext_People_primaryEmailAddress(ctx, field)
case "additionalEmailAddresses":
return ec.fieldContext_People_additionalEmailAddresses(ctx, field)
case "kind":
return ec.fieldContext_People_kind(ctx, field)
case "position":
return ec.fieldContext_People_position(ctx, field)
case "contractStartDate":
return ec.fieldContext_People_contractStartDate(ctx, field)
case "contractEndDate":
return ec.fieldContext_People_contractEndDate(ctx, field)
case "createdAt":
return ec.fieldContext_People_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_People_updatedAt(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type People", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _DocumentVersion_signatures(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersion) (ret graphql.Marshaler) { func (ec *executionContext) _DocumentVersion_signatures(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersion) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_DocumentVersion_signatures(ctx, field) fc, err := ec.fieldContext_DocumentVersion_signatures(ctx, field)
if err != nil { if err != nil {
@@ -17586,6 +17776,10 @@ func (ec *executionContext) fieldContext_DocumentVersionEdge_node(_ context.Cont
return ec.fieldContext_DocumentVersion_content(ctx, field) return ec.fieldContext_DocumentVersion_content(ctx, field)
case "changelog": case "changelog":
return ec.fieldContext_DocumentVersion_changelog(ctx, field) return ec.fieldContext_DocumentVersion_changelog(ctx, field)
case "title":
return ec.fieldContext_DocumentVersion_title(ctx, field)
case "owner":
return ec.fieldContext_DocumentVersion_owner(ctx, field)
case "signatures": case "signatures":
return ec.fieldContext_DocumentVersion_signatures(ctx, field) return ec.fieldContext_DocumentVersion_signatures(ctx, field)
case "publishedBy": case "publishedBy":
@@ -17698,6 +17892,10 @@ func (ec *executionContext) fieldContext_DocumentVersionSignature_documentVersio
return ec.fieldContext_DocumentVersion_content(ctx, field) return ec.fieldContext_DocumentVersion_content(ctx, field)
case "changelog": case "changelog":
return ec.fieldContext_DocumentVersion_changelog(ctx, field) return ec.fieldContext_DocumentVersion_changelog(ctx, field)
case "title":
return ec.fieldContext_DocumentVersion_title(ctx, field)
case "owner":
return ec.fieldContext_DocumentVersion_owner(ctx, field)
case "signatures": case "signatures":
return ec.fieldContext_DocumentVersion_signatures(ctx, field) return ec.fieldContext_DocumentVersion_signatures(ctx, field)
case "publishedBy": case "publishedBy":
@@ -19768,6 +19966,50 @@ func (ec *executionContext) fieldContext_FulfillEvidencePayload_evidenceEdge(_ c
return fc, nil return fc, nil
} }
func (ec *executionContext) _GenerateDocumentChangelogPayload_changelog(ctx context.Context, field graphql.CollectedField, obj *types.GenerateDocumentChangelogPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_GenerateDocumentChangelogPayload_changelog(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.Changelog, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_GenerateDocumentChangelogPayload_changelog(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "GenerateDocumentChangelogPayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _ImportFrameworkPayload_frameworkEdge(ctx context.Context, field graphql.CollectedField, obj *types.ImportFrameworkPayload) (ret graphql.Marshaler) { func (ec *executionContext) _ImportFrameworkPayload_frameworkEdge(ctx context.Context, field graphql.CollectedField, obj *types.ImportFrameworkPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ImportFrameworkPayload_frameworkEdge(ctx, field) fc, err := ec.fieldContext_ImportFrameworkPayload_frameworkEdge(ctx, field)
if err != nil { if err != nil {
@@ -23473,6 +23715,65 @@ func (ec *executionContext) fieldContext_Mutation_publishDocumentVersion(ctx con
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_generateDocumentChangelog(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_generateDocumentChangelog(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().GenerateDocumentChangelog(rctx, fc.Args["input"].(types.GenerateDocumentChangelogInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*types.GenerateDocumentChangelogPayload)
fc.Result = res
return ec.marshalNGenerateDocumentChangelogPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateDocumentChangelogPayload(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_generateDocumentChangelog(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "changelog":
return ec.fieldContext_GenerateDocumentChangelogPayload_changelog(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type GenerateDocumentChangelogPayload", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_generateDocumentChangelog_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_createDraftDocumentVersion(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_createDraftDocumentVersion(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_createDraftDocumentVersion(ctx, field) fc, err := ec.fieldContext_Mutation_createDraftDocumentVersion(ctx, field)
if err != nil { if err != nil {
@@ -26456,6 +26757,10 @@ func (ec *executionContext) fieldContext_PublishDocumentVersionPayload_documentV
return ec.fieldContext_DocumentVersion_content(ctx, field) return ec.fieldContext_DocumentVersion_content(ctx, field)
case "changelog": case "changelog":
return ec.fieldContext_DocumentVersion_changelog(ctx, field) return ec.fieldContext_DocumentVersion_changelog(ctx, field)
case "title":
return ec.fieldContext_DocumentVersion_title(ctx, field)
case "owner":
return ec.fieldContext_DocumentVersion_owner(ctx, field)
case "signatures": case "signatures":
return ec.fieldContext_DocumentVersion_signatures(ctx, field) return ec.fieldContext_DocumentVersion_signatures(ctx, field)
case "publishedBy": case "publishedBy":
@@ -29462,6 +29767,10 @@ func (ec *executionContext) fieldContext_UpdateDocumentVersionPayload_documentVe
return ec.fieldContext_DocumentVersion_content(ctx, field) return ec.fieldContext_DocumentVersion_content(ctx, field)
case "changelog": case "changelog":
return ec.fieldContext_DocumentVersion_changelog(ctx, field) return ec.fieldContext_DocumentVersion_changelog(ctx, field)
case "title":
return ec.fieldContext_DocumentVersion_title(ctx, field)
case "owner":
return ec.fieldContext_DocumentVersion_owner(ctx, field)
case "signatures": case "signatures":
return ec.fieldContext_DocumentVersion_signatures(ctx, field) return ec.fieldContext_DocumentVersion_signatures(ctx, field)
case "publishedBy": case "publishedBy":
@@ -37516,6 +37825,33 @@ func (ec *executionContext) unmarshalInputFulfillEvidenceInput(ctx context.Conte
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputGenerateDocumentChangelogInput(ctx context.Context, obj any) (types.GenerateDocumentChangelogInput, error) {
var it types.GenerateDocumentChangelogInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"documentId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "documentId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("documentId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.DocumentID = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputImportFrameworkInput(ctx context.Context, obj any) (types.ImportFrameworkInput, error) { func (ec *executionContext) unmarshalInputImportFrameworkInput(ctx context.Context, obj any) (types.ImportFrameworkInput, error) {
var it types.ImportFrameworkInput var it types.ImportFrameworkInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -37734,7 +38070,7 @@ func (ec *executionContext) unmarshalInputPublishDocumentVersionInput(ctx contex
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"documentId"} fieldsInOrder := [...]string{"documentId", "changelog"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@@ -37748,6 +38084,13 @@ func (ec *executionContext) unmarshalInputPublishDocumentVersionInput(ctx contex
return it, err return it, err
} }
it.DocumentID = data it.DocumentID = data
case "changelog":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("changelog"))
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
it.Changelog = data
} }
} }
@@ -42067,6 +42410,47 @@ func (ec *executionContext) _DocumentVersion(ctx context.Context, sel ast.Select
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1) atomic.AddUint32(&out.Invalids, 1)
} }
case "title":
out.Values[i] = ec._DocumentVersion_title(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1)
}
case "owner":
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._DocumentVersion_owner(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) })
case "signatures": case "signatures":
field := field field := field
@@ -43084,6 +43468,45 @@ func (ec *executionContext) _FulfillEvidencePayload(ctx context.Context, sel ast
return out return out
} }
var generateDocumentChangelogPayloadImplementors = []string{"GenerateDocumentChangelogPayload"}
func (ec *executionContext) _GenerateDocumentChangelogPayload(ctx context.Context, sel ast.SelectionSet, obj *types.GenerateDocumentChangelogPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, generateDocumentChangelogPayloadImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("GenerateDocumentChangelogPayload")
case "changelog":
out.Values[i] = ec._GenerateDocumentChangelogPayload_changelog(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.processDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var importFrameworkPayloadImplementors = []string{"ImportFrameworkPayload"} var importFrameworkPayloadImplementors = []string{"ImportFrameworkPayload"}
func (ec *executionContext) _ImportFrameworkPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ImportFrameworkPayload) graphql.Marshaler { func (ec *executionContext) _ImportFrameworkPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ImportFrameworkPayload) graphql.Marshaler {
@@ -43850,6 +44273,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "generateDocumentChangelog":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_generateDocumentChangelog(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "createDraftDocumentVersion": case "createDraftDocumentVersion":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_createDraftDocumentVersion(ctx, field) return ec._Mutation_createDraftDocumentVersion(ctx, field)
@@ -49850,6 +50280,25 @@ func (ec *executionContext) marshalNFulfillEvidencePayload2ᚖgithubᚗcomᚋget
return ec._FulfillEvidencePayload(ctx, sel, v) return ec._FulfillEvidencePayload(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNGenerateDocumentChangelogInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateDocumentChangelogInput(ctx context.Context, v any) (types.GenerateDocumentChangelogInput, error) {
res, err := ec.unmarshalInputGenerateDocumentChangelogInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNGenerateDocumentChangelogPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateDocumentChangelogPayload(ctx context.Context, sel ast.SelectionSet, v types.GenerateDocumentChangelogPayload) graphql.Marshaler {
return ec._GenerateDocumentChangelogPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNGenerateDocumentChangelogPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateDocumentChangelogPayload(ctx context.Context, sel ast.SelectionSet, v *types.GenerateDocumentChangelogPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._GenerateDocumentChangelogPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx context.Context, v any) (gid.GID, error) { func (ec *executionContext) unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx context.Context, v any) (gid.GID, error) {
res, err := types.UnmarshalGIDScalar(v) res, err := types.UnmarshalGIDScalar(v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)

View File

@@ -46,6 +46,7 @@ func NewDocumentVersion(documentVersion *coredata.DocumentVersion) *DocumentVers
return &DocumentVersion{ return &DocumentVersion{
ID: documentVersion.ID, ID: documentVersion.ID,
Version: documentVersion.VersionNumber, Version: documentVersion.VersionNumber,
Title: documentVersion.Title,
Content: documentVersion.Content, Content: documentVersion.Content,
Status: documentVersion.Status, Status: documentVersion.Status,
PublishedAt: documentVersion.PublishedAt, PublishedAt: documentVersion.PublishedAt,

View File

@@ -557,6 +557,8 @@ type DocumentVersion struct {
Version int `json:"version"` Version int `json:"version"`
Content string `json:"content"` Content string `json:"content"`
Changelog string `json:"changelog"` Changelog string `json:"changelog"`
Title string `json:"title"`
Owner *People `json:"owner"`
Signatures *DocumentVersionSignatureConnection `json:"signatures"` Signatures *DocumentVersionSignatureConnection `json:"signatures"`
PublishedBy *People `json:"publishedBy,omitempty"` PublishedBy *People `json:"publishedBy,omitempty"`
PublishedAt *time.Time `json:"publishedAt,omitempty"` PublishedAt *time.Time `json:"publishedAt,omitempty"`
@@ -682,6 +684,14 @@ type FulfillEvidencePayload struct {
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"` EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
} }
type GenerateDocumentChangelogInput struct {
DocumentID gid.GID `json:"documentId"`
}
type GenerateDocumentChangelogPayload struct {
Changelog string `json:"changelog"`
}
type ImportFrameworkInput struct { type ImportFrameworkInput struct {
OrganizationID gid.GID `json:"organizationId"` OrganizationID gid.GID `json:"organizationId"`
File graphql.Upload `json:"file"` File graphql.Upload `json:"file"`
@@ -812,6 +822,7 @@ type PeopleEdge struct {
type PublishDocumentVersionInput struct { type PublishDocumentVersionInput struct {
DocumentID gid.GID `json:"documentId"` DocumentID gid.GID `json:"documentId"`
Changelog *string `json:"changelog,omitempty"`
} }
type PublishDocumentVersionPayload struct { type PublishDocumentVersionPayload struct {

View File

@@ -302,6 +302,23 @@ func (r *documentVersionResolver) Document(ctx context.Context, obj *types.Docum
return types.NewDocument(document), nil return types.NewDocument(document), nil
} }
// Owner is the resolver for the owner field.
func (r *documentVersionResolver) Owner(ctx context.Context, obj *types.DocumentVersion) (*types.People, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
documentVersion, err := svc.Documents.GetVersion(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get document version: %w", err))
}
owner, err := svc.Peoples.Get(ctx, documentVersion.OwnerID)
if err != nil {
panic(fmt.Errorf("cannot get owner: %w", err))
}
return types.NewPeople(owner), nil
}
// Signatures is the resolver for the signatures field. // Signatures is the resolver for the signatures field.
func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder) (*types.DocumentVersionSignatureConnection, error) { func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder) (*types.DocumentVersionSignatureConnection, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
@@ -1491,6 +1508,7 @@ func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.Updat
input.ID, input.ID,
input.OwnerID, input.OwnerID,
input.DocumentType, input.DocumentType,
input.Title,
) )
if err != nil { if err != nil {
@@ -1526,7 +1544,7 @@ func (r *mutationResolver) PublishDocumentVersion(ctx context.Context, input typ
panic(fmt.Errorf("cannot get people: %w", err)) panic(fmt.Errorf("cannot get people: %w", err))
} }
document, documentVersion, err := svc.Documents.PublishVersion(ctx, input.DocumentID, people.ID) document, documentVersion, err := svc.Documents.PublishVersion(ctx, input.DocumentID, people.ID, input.Changelog)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot publish document version: %w", err)) panic(fmt.Errorf("cannot publish document version: %w", err))
} }
@@ -1537,6 +1555,20 @@ func (r *mutationResolver) PublishDocumentVersion(ctx context.Context, input typ
}, nil }, nil
} }
// GenerateDocumentChangelog is the resolver for the generateDocumentChangelog field.
func (r *mutationResolver) GenerateDocumentChangelog(ctx context.Context, input types.GenerateDocumentChangelogInput) (*types.GenerateDocumentChangelogPayload, error) {
svc := GetTenantService(ctx, r.proboSvc, input.DocumentID.TenantID())
changelog, err := svc.Documents.GenerateChangelog(ctx, input.DocumentID)
if err != nil {
panic(fmt.Errorf("cannot generate document changelog: %w", err))
}
return &types.GenerateDocumentChangelogPayload{
Changelog: *changelog,
}, nil
}
// CreateDraftDocumentVersion is the resolver for the createDraftDocumentVersion field. // CreateDraftDocumentVersion is the resolver for the createDraftDocumentVersion field.
func (r *mutationResolver) CreateDraftDocumentVersion(ctx context.Context, input types.CreateDraftDocumentVersionInput) (*types.CreateDraftDocumentVersionPayload, error) { func (r *mutationResolver) CreateDraftDocumentVersion(ctx context.Context, input types.CreateDraftDocumentVersionInput) (*types.CreateDraftDocumentVersionPayload, error) {
svc := GetTenantService(ctx, r.proboSvc, input.DocumentID.TenantID()) svc := GetTenantService(ctx, r.proboSvc, input.DocumentID.TenantID())

View File

@@ -38,7 +38,7 @@ type Config struct {
Usrmgr *usrmgr.Service Usrmgr *usrmgr.Service
Auth console_v1.AuthConfig Auth console_v1.AuthConfig
ConnectorRegistry *connector.ConnectorRegistry ConnectorRegistry *connector.ConnectorRegistry
VendorAssessment *agents.VendorAssessment Agent *agents.Agent
SafeRedirect *saferedirect.SafeRedirect SafeRedirect *saferedirect.SafeRedirect
Logger *log.Logger Logger *log.Logger
} }