Add processing activity exports
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -16,6 +16,10 @@ import {
|
||||
useConfirm,
|
||||
Tabs,
|
||||
TabItem,
|
||||
IconArrowDown,
|
||||
IconChevronDown,
|
||||
Spinner,
|
||||
Dropdown,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
@@ -32,7 +36,8 @@ import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { useParams } from "react-router";
|
||||
import { CreateProcessingActivityDialog } from "./dialogs/CreateProcessingActivityDialog";
|
||||
import { deleteProcessingActivityMutation, ProcessingActivitiesConnectionKey, processingActivitiesQuery } from "../../../hooks/graph/ProcessingActivityGraph";
|
||||
import { sprintf, promisifyMutation } from "@probo/helpers";
|
||||
import { sprintf, promisifyMutation, downloadFile, toDateInput } from "@probo/helpers";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||
import type { NodeOf } from "/types";
|
||||
import type {
|
||||
@@ -174,6 +179,36 @@ const tiaListPageFragment = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const exportProcessingActivitiesPDFMutation = graphql`
|
||||
mutation ProcessingActivitiesPageExportPDFMutation(
|
||||
$input: ExportProcessingActivitiesPDFInput!
|
||||
) {
|
||||
exportProcessingActivitiesPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const exportDataProtectionImpactAssessmentsPDFMutation = graphql`
|
||||
mutation ProcessingActivitiesPageExportDPIAPDFMutation(
|
||||
$input: ExportDataProtectionImpactAssessmentsPDFInput!
|
||||
) {
|
||||
exportDataProtectionImpactAssessmentsPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const exportTransferImpactAssessmentsPDFMutation = graphql`
|
||||
mutation ProcessingActivitiesPageExportTIAPDFMutation(
|
||||
$input: ExportTransferImpactAssessmentsPDFInput!
|
||||
) {
|
||||
exportTransferImpactAssessmentsPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivitiesPageProps) {
|
||||
const { __ } = useTranslate();
|
||||
const organizationId = useOrganizationId();
|
||||
@@ -233,12 +268,169 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
|
||||
isAuthorized("ProcessingActivity", "deleteProcessingActivity")
|
||||
);
|
||||
|
||||
const canExportPDF = isAuthorized("ProcessingActivity", "exportProcessingActivitiesPDF");
|
||||
const [exportPDF, isExportingPDF] = useMutationWithToasts<{
|
||||
response: {
|
||||
exportProcessingActivitiesPDF?: {
|
||||
data: string;
|
||||
};
|
||||
};
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: string;
|
||||
filter: { snapshotId: string } | null;
|
||||
};
|
||||
};
|
||||
}>(
|
||||
exportProcessingActivitiesPDFMutation,
|
||||
{
|
||||
successMessage: __("PDF download started."),
|
||||
errorMessage: __("Failed to generate PDF"),
|
||||
}
|
||||
);
|
||||
|
||||
const handleExportPDF = () => {
|
||||
exportPDF({
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: organizationId,
|
||||
filter: snapshotId ? { snapshotId } : null,
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (data.exportProcessingActivitiesPDF?.data) {
|
||||
downloadFile(
|
||||
data.exportProcessingActivitiesPDF.data,
|
||||
`processing-activities-${toDateInput(new Date().toISOString())}.pdf`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const canExportDPIAPDF = isAuthorized("Organization", "exportDataProtectionImpactAssessmentsPDF");
|
||||
const [exportDPIAPDF, isExportingDPIAPDF] = useMutationWithToasts<{
|
||||
response: {
|
||||
exportDataProtectionImpactAssessmentsPDF?: {
|
||||
data: string;
|
||||
};
|
||||
};
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: string;
|
||||
filter: { snapshotId: string } | null;
|
||||
};
|
||||
};
|
||||
}>(
|
||||
exportDataProtectionImpactAssessmentsPDFMutation,
|
||||
{
|
||||
successMessage: __("PDF download started."),
|
||||
errorMessage: __("Failed to generate PDF"),
|
||||
}
|
||||
);
|
||||
|
||||
const handleExportDPIAPDF = () => {
|
||||
exportDPIAPDF({
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: organizationId,
|
||||
filter: snapshotId ? { snapshotId } : null,
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (data.exportDataProtectionImpactAssessmentsPDF?.data) {
|
||||
downloadFile(
|
||||
data.exportDataProtectionImpactAssessmentsPDF.data,
|
||||
`data-protection-impact-assessments-${toDateInput(new Date().toISOString())}.pdf`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const canExportTIAPDF = isAuthorized("Organization", "exportTransferImpactAssessmentsPDF");
|
||||
const [exportTIAPDF, isExportingTIAPDF] = useMutationWithToasts<{
|
||||
response: {
|
||||
exportTransferImpactAssessmentsPDF?: {
|
||||
data: string;
|
||||
};
|
||||
};
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: string;
|
||||
filter: { snapshotId: string } | null;
|
||||
};
|
||||
};
|
||||
}>(
|
||||
exportTransferImpactAssessmentsPDFMutation,
|
||||
{
|
||||
successMessage: __("PDF download started."),
|
||||
errorMessage: __("Failed to generate PDF"),
|
||||
}
|
||||
);
|
||||
|
||||
const handleExportTIAPDF = () => {
|
||||
exportTIAPDF({
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: organizationId,
|
||||
filter: snapshotId ? { snapshotId } : null,
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (data.exportTransferImpactAssessmentsPDF?.data) {
|
||||
downloadFile(
|
||||
data.exportTransferImpactAssessmentsPDF.data,
|
||||
`transfer-impact-assessments-${toDateInput(new Date().toISOString())}.pdf`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{isSnapshotMode && snapshotId && (
|
||||
<SnapshotBanner snapshotId={snapshotId} />
|
||||
)}
|
||||
<PageHeader title={__("Processing Activities")} description={__("Manage your processing activities under GDPR")}>
|
||||
{(canExportPDF || canExportDPIAPDF || canExportTIAPDF) && (
|
||||
<Dropdown
|
||||
toggle={
|
||||
<Button variant="secondary" icon={IconArrowDown} iconAfter={IconChevronDown}>
|
||||
{__("Export")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{canExportPDF && (
|
||||
<DropdownItem
|
||||
onClick={handleExportPDF}
|
||||
disabled={isExportingPDF}
|
||||
icon={isExportingPDF ? Spinner : undefined}
|
||||
>
|
||||
{__("Processing Activities")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
{canExportDPIAPDF && (
|
||||
<DropdownItem
|
||||
onClick={handleExportDPIAPDF}
|
||||
disabled={isExportingDPIAPDF}
|
||||
icon={isExportingDPIAPDF ? Spinner : undefined}
|
||||
>
|
||||
{__("Data Protection Impact Assessments")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
{canExportTIAPDF && (
|
||||
<DropdownItem
|
||||
onClick={handleExportTIAPDF}
|
||||
disabled={isExportingTIAPDF}
|
||||
icon={isExportingTIAPDF ? Spinner : undefined}
|
||||
>
|
||||
{__("Transfer Impact Assessments")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</Dropdown>
|
||||
)}
|
||||
{!isSnapshotMode && activeTab === "activities" && (
|
||||
isAuthorized("Organization", "createProcessingActivity") && (
|
||||
<CreateProcessingActivityDialog
|
||||
@@ -272,13 +464,13 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Name")}</Th>
|
||||
<Th>{__("Purpose")}</Th>
|
||||
<Th>{__("Data Subject")}</Th>
|
||||
<Th>{__("Lawful Basis")}</Th>
|
||||
<Th>{__("Location")}</Th>
|
||||
<Th>{__("International Transfers")}</Th>
|
||||
{hasAnyAction && <Th>{__("Actions")}</Th>}
|
||||
<Th className="px-3">{__("Name")}</Th>
|
||||
<Th className="px-3">{__("Purpose")}</Th>
|
||||
<Th className="px-3">{__("Data Subject")}</Th>
|
||||
<Th className="px-3">{__("Lawful Basis")}</Th>
|
||||
<Th className="px-3">{__("Location")}</Th>
|
||||
<Th className="px-3">{__("International Transfers")}</Th>
|
||||
{hasAnyAction && <Th className="px-3">{__("Actions")}</Th>}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @generated SignedSource<<2f43f6874d8b6d515cf244934b7b19ea>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ExportDataProtectionImpactAssessmentsPDFInput = {
|
||||
filter?: DataProtectionImpactAssessmentFilter | null | undefined;
|
||||
organizationId: string;
|
||||
};
|
||||
export type DataProtectionImpactAssessmentFilter = {
|
||||
snapshotId?: string | null | undefined;
|
||||
};
|
||||
export type ProcessingActivitiesPageExportDPIAPDFMutation$variables = {
|
||||
input: ExportDataProtectionImpactAssessmentsPDFInput;
|
||||
};
|
||||
export type ProcessingActivitiesPageExportDPIAPDFMutation$data = {
|
||||
readonly exportDataProtectionImpactAssessmentsPDF: {
|
||||
readonly data: string;
|
||||
};
|
||||
};
|
||||
export type ProcessingActivitiesPageExportDPIAPDFMutation = {
|
||||
response: ProcessingActivitiesPageExportDPIAPDFMutation$data;
|
||||
variables: ProcessingActivitiesPageExportDPIAPDFMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "ExportDataProtectionImpactAssessmentsPDFPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "exportDataProtectionImpactAssessmentsPDF",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "data",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ProcessingActivitiesPageExportDPIAPDFMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ProcessingActivitiesPageExportDPIAPDFMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "851a1c0def7719185cf3a67954696231",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ProcessingActivitiesPageExportDPIAPDFMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation ProcessingActivitiesPageExportDPIAPDFMutation(\n $input: ExportDataProtectionImpactAssessmentsPDFInput!\n) {\n exportDataProtectionImpactAssessmentsPDF(input: $input) {\n data\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "3c2a96fb29c61791e7df39c7b8174c90";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @generated SignedSource<<82ece1ab5e9585bfcce605e38fe9563a>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ExportProcessingActivitiesPDFInput = {
|
||||
filter?: ProcessingActivityFilter | null | undefined;
|
||||
organizationId: string;
|
||||
};
|
||||
export type ProcessingActivityFilter = {
|
||||
snapshotId?: string | null | undefined;
|
||||
};
|
||||
export type ProcessingActivitiesPageExportPDFMutation$variables = {
|
||||
input: ExportProcessingActivitiesPDFInput;
|
||||
};
|
||||
export type ProcessingActivitiesPageExportPDFMutation$data = {
|
||||
readonly exportProcessingActivitiesPDF: {
|
||||
readonly data: string;
|
||||
};
|
||||
};
|
||||
export type ProcessingActivitiesPageExportPDFMutation = {
|
||||
response: ProcessingActivitiesPageExportPDFMutation$data;
|
||||
variables: ProcessingActivitiesPageExportPDFMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "ExportProcessingActivitiesPDFPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "exportProcessingActivitiesPDF",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "data",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ProcessingActivitiesPageExportPDFMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ProcessingActivitiesPageExportPDFMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "85140bef5e55c8ae4fdb139965375f13",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ProcessingActivitiesPageExportPDFMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation ProcessingActivitiesPageExportPDFMutation(\n $input: ExportProcessingActivitiesPDFInput!\n) {\n exportProcessingActivitiesPDF(input: $input) {\n data\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "cc777bb4440f6aed36c2fb92c1bc7e0b";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @generated SignedSource<<b8fb35bae12056fe9451875e34aab319>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ExportTransferImpactAssessmentsPDFInput = {
|
||||
filter?: TransferImpactAssessmentFilter | null | undefined;
|
||||
organizationId: string;
|
||||
};
|
||||
export type TransferImpactAssessmentFilter = {
|
||||
snapshotId?: string | null | undefined;
|
||||
};
|
||||
export type ProcessingActivitiesPageExportTIAPDFMutation$variables = {
|
||||
input: ExportTransferImpactAssessmentsPDFInput;
|
||||
};
|
||||
export type ProcessingActivitiesPageExportTIAPDFMutation$data = {
|
||||
readonly exportTransferImpactAssessmentsPDF: {
|
||||
readonly data: string;
|
||||
};
|
||||
};
|
||||
export type ProcessingActivitiesPageExportTIAPDFMutation = {
|
||||
response: ProcessingActivitiesPageExportTIAPDFMutation$data;
|
||||
variables: ProcessingActivitiesPageExportTIAPDFMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "ExportTransferImpactAssessmentsPDFPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "exportTransferImpactAssessmentsPDF",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "data",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "ProcessingActivitiesPageExportTIAPDFMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "ProcessingActivitiesPageExportTIAPDFMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "b41ab5f56ee5dc4436069c4e2a97f8d8",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ProcessingActivitiesPageExportTIAPDFMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation ProcessingActivitiesPageExportTIAPDFMutation(\n $input: ExportTransferImpactAssessmentsPDFInput!\n) {\n exportTransferImpactAssessmentsPDF(input: $input) {\n data\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "3fe289d5564d0617074825d8c58f88f8";
|
||||
|
||||
export default node;
|
||||
@@ -2596,3 +2596,392 @@ func TestProcessingActivity_Snapshot_DPIA_TIA(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessingActivity_ExportPDF(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
t.Run("export processing activities PDF", func(t *testing.T) {
|
||||
_ = factory.NewProcessingActivity(owner).
|
||||
WithName("PA Export Test 1").
|
||||
WithLawfulBasis("CONSENT").
|
||||
Create()
|
||||
_ = factory.NewProcessingActivity(owner).
|
||||
WithName("PA Export Test 2").
|
||||
WithLawfulBasis("LEGITIMATE_INTEREST").
|
||||
Create()
|
||||
|
||||
query := `
|
||||
mutation ExportProcessingActivitiesPDF($input: ExportProcessingActivitiesPDFInput!) {
|
||||
exportProcessingActivitiesPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
ExportProcessingActivitiesPDF struct {
|
||||
Data string `json:"data"`
|
||||
} `json:"exportProcessingActivitiesPDF"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": owner.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, result.ExportProcessingActivitiesPDF.Data)
|
||||
assert.Contains(t, result.ExportProcessingActivitiesPDF.Data, "data:application/pdf;base64,")
|
||||
})
|
||||
|
||||
t.Run("export processing activities PDF with snapshot filter", func(t *testing.T) {
|
||||
_ = factory.NewProcessingActivity(owner).
|
||||
WithName("PA Snapshot Export Test").
|
||||
Create()
|
||||
|
||||
// Create snapshot
|
||||
var snapshotResult struct {
|
||||
CreateSnapshot struct {
|
||||
SnapshotEdge struct {
|
||||
Node struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"node"`
|
||||
} `json:"snapshotEdge"`
|
||||
} `json:"createSnapshot"`
|
||||
}
|
||||
err := owner.Execute(`
|
||||
mutation($input: CreateSnapshotInput!) {
|
||||
createSnapshot(input: $input) {
|
||||
snapshotEdge {
|
||||
node { id }
|
||||
}
|
||||
}
|
||||
}
|
||||
`, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": owner.GetOrganizationID().String(),
|
||||
"name": fmt.Sprintf("PA Export Snapshot Test %d", time.Now().UnixNano()),
|
||||
"type": "PROCESSING_ACTIVITIES",
|
||||
},
|
||||
}, &snapshotResult)
|
||||
require.NoError(t, err)
|
||||
snapshotID := snapshotResult.CreateSnapshot.SnapshotEdge.Node.ID
|
||||
|
||||
query := `
|
||||
mutation ExportProcessingActivitiesPDF($input: ExportProcessingActivitiesPDFInput!) {
|
||||
exportProcessingActivitiesPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
ExportProcessingActivitiesPDF struct {
|
||||
Data string `json:"data"`
|
||||
} `json:"exportProcessingActivitiesPDF"`
|
||||
}
|
||||
|
||||
err = owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": owner.GetOrganizationID().String(),
|
||||
"filter": map[string]any{
|
||||
"snapshotId": snapshotID,
|
||||
},
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, result.ExportProcessingActivitiesPDF.Data)
|
||||
assert.Contains(t, result.ExportProcessingActivitiesPDF.Data, "data:application/pdf;base64,")
|
||||
})
|
||||
|
||||
t.Run("export fails with no processing activities", func(t *testing.T) {
|
||||
newOwner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
query := `
|
||||
mutation ExportProcessingActivitiesPDF($input: ExportProcessingActivitiesPDFInput!) {
|
||||
exportProcessingActivitiesPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
_, err := newOwner.Do(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": newOwner.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
})
|
||||
testutil.RequireErrorCode(t, err, "NOT_FOUND")
|
||||
assert.Contains(t, err.Error(), "no processing activities found")
|
||||
})
|
||||
}
|
||||
|
||||
func TestDataProtectionImpactAssessment_ExportPDF(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
t.Run("export DPIA PDF", func(t *testing.T) {
|
||||
pa1ID := factory.NewProcessingActivity(owner).
|
||||
WithName("DPIA Export Test PA 1").
|
||||
Create()
|
||||
pa2ID := factory.NewProcessingActivity(owner).
|
||||
WithName("DPIA Export Test PA 2").
|
||||
Create()
|
||||
|
||||
_, err := owner.Do(`
|
||||
mutation($input: CreateDataProtectionImpactAssessmentInput!) {
|
||||
createDataProtectionImpactAssessment(input: $input) {
|
||||
dataProtectionImpactAssessment { id }
|
||||
}
|
||||
}
|
||||
`, map[string]any{
|
||||
"input": map[string]any{
|
||||
"processingActivityId": pa1ID,
|
||||
"description": "DPIA 1 description",
|
||||
"residualRisk": "LOW",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = owner.Do(`
|
||||
mutation($input: CreateDataProtectionImpactAssessmentInput!) {
|
||||
createDataProtectionImpactAssessment(input: $input) {
|
||||
dataProtectionImpactAssessment { id }
|
||||
}
|
||||
}
|
||||
`, map[string]any{
|
||||
"input": map[string]any{
|
||||
"processingActivityId": pa2ID,
|
||||
"description": "DPIA 2 description",
|
||||
"residualRisk": "MEDIUM",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
query := `
|
||||
mutation ExportDataProtectionImpactAssessmentsPDF($input: ExportDataProtectionImpactAssessmentsPDFInput!) {
|
||||
exportDataProtectionImpactAssessmentsPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
ExportDataProtectionImpactAssessmentsPDF struct {
|
||||
Data string `json:"data"`
|
||||
} `json:"exportDataProtectionImpactAssessmentsPDF"`
|
||||
}
|
||||
|
||||
err = owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": owner.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, result.ExportDataProtectionImpactAssessmentsPDF.Data)
|
||||
assert.Contains(t, result.ExportDataProtectionImpactAssessmentsPDF.Data, "data:application/pdf;base64,")
|
||||
})
|
||||
|
||||
t.Run("export fails with no DPIAs", func(t *testing.T) {
|
||||
newOwner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
query := `
|
||||
mutation ExportDataProtectionImpactAssessmentsPDF($input: ExportDataProtectionImpactAssessmentsPDFInput!) {
|
||||
exportDataProtectionImpactAssessmentsPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
_, err := newOwner.Do(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": newOwner.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
})
|
||||
testutil.RequireErrorCode(t, err, "NOT_FOUND")
|
||||
assert.Contains(t, err.Error(), "no data protection impact assessments found")
|
||||
})
|
||||
}
|
||||
|
||||
func TestTransferImpactAssessment_ExportPDF(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
t.Run("export TIA PDF", func(t *testing.T) {
|
||||
pa1ID := factory.NewProcessingActivity(owner).
|
||||
WithName("TIA Export Test PA 1").
|
||||
Create()
|
||||
pa2ID := factory.NewProcessingActivity(owner).
|
||||
WithName("TIA Export Test PA 2").
|
||||
Create()
|
||||
|
||||
_, err := owner.Do(`
|
||||
mutation($input: CreateTransferImpactAssessmentInput!) {
|
||||
createTransferImpactAssessment(input: $input) {
|
||||
transferImpactAssessment { id }
|
||||
}
|
||||
}
|
||||
`, map[string]any{
|
||||
"input": map[string]any{
|
||||
"processingActivityId": pa1ID,
|
||||
"dataSubjects": "TIA 1 subjects",
|
||||
"transfer": "EU to US",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = owner.Do(`
|
||||
mutation($input: CreateTransferImpactAssessmentInput!) {
|
||||
createTransferImpactAssessment(input: $input) {
|
||||
transferImpactAssessment { id }
|
||||
}
|
||||
}
|
||||
`, map[string]any{
|
||||
"input": map[string]any{
|
||||
"processingActivityId": pa2ID,
|
||||
"dataSubjects": "TIA 2 subjects",
|
||||
"transfer": "EU to UK",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
query := `
|
||||
mutation ExportTransferImpactAssessmentsPDF($input: ExportTransferImpactAssessmentsPDFInput!) {
|
||||
exportTransferImpactAssessmentsPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
ExportTransferImpactAssessmentsPDF struct {
|
||||
Data string `json:"data"`
|
||||
} `json:"exportTransferImpactAssessmentsPDF"`
|
||||
}
|
||||
|
||||
err = owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": owner.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, result.ExportTransferImpactAssessmentsPDF.Data)
|
||||
assert.Contains(t, result.ExportTransferImpactAssessmentsPDF.Data, "data:application/pdf;base64,")
|
||||
})
|
||||
|
||||
t.Run("export fails with no TIAs", func(t *testing.T) {
|
||||
newOwner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
query := `
|
||||
mutation ExportTransferImpactAssessmentsPDF($input: ExportTransferImpactAssessmentsPDFInput!) {
|
||||
exportTransferImpactAssessmentsPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
_, err := newOwner.Do(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": newOwner.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
})
|
||||
testutil.RequireErrorCode(t, err, "NOT_FOUND")
|
||||
assert.Contains(t, err.Error(), "no transfer impact assessments found")
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessingActivity_ExportPDF_RBAC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("owner can export PDF", func(t *testing.T) {
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
_ = factory.NewProcessingActivity(owner).WithName("RBAC Export Test").Create()
|
||||
|
||||
query := `
|
||||
mutation ExportProcessingActivitiesPDF($input: ExportProcessingActivitiesPDFInput!) {
|
||||
exportProcessingActivitiesPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
ExportProcessingActivitiesPDF struct {
|
||||
Data string `json:"data"`
|
||||
} `json:"exportProcessingActivitiesPDF"`
|
||||
}
|
||||
|
||||
err := owner.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": owner.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err, "owner should be able to export PDF")
|
||||
assert.NotEmpty(t, result.ExportProcessingActivitiesPDF.Data)
|
||||
})
|
||||
|
||||
t.Run("admin can export PDF", func(t *testing.T) {
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
admin := testutil.NewClientInOrg(t, testutil.RoleAdmin, owner)
|
||||
_ = factory.NewProcessingActivity(owner).WithName("RBAC Export Test").Create()
|
||||
|
||||
query := `
|
||||
mutation ExportProcessingActivitiesPDF($input: ExportProcessingActivitiesPDFInput!) {
|
||||
exportProcessingActivitiesPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
ExportProcessingActivitiesPDF struct {
|
||||
Data string `json:"data"`
|
||||
} `json:"exportProcessingActivitiesPDF"`
|
||||
}
|
||||
|
||||
err := admin.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": admin.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err, "admin should be able to export PDF")
|
||||
assert.NotEmpty(t, result.ExportProcessingActivitiesPDF.Data)
|
||||
})
|
||||
|
||||
t.Run("viewer can export PDF", func(t *testing.T) {
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
viewer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner)
|
||||
_ = factory.NewProcessingActivity(owner).WithName("RBAC Export Test").Create()
|
||||
|
||||
query := `
|
||||
mutation ExportProcessingActivitiesPDF($input: ExportProcessingActivitiesPDFInput!) {
|
||||
exportProcessingActivitiesPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var result struct {
|
||||
ExportProcessingActivitiesPDF struct {
|
||||
Data string `json:"data"`
|
||||
} `json:"exportProcessingActivitiesPDF"`
|
||||
}
|
||||
|
||||
err := viewer.Execute(query, map[string]any{
|
||||
"input": map[string]any{
|
||||
"organizationId": viewer.GetOrganizationID().String(),
|
||||
"filter": nil,
|
||||
},
|
||||
}, &result)
|
||||
require.NoError(t, err, "viewer should be able to export PDF")
|
||||
assert.NotEmpty(t, result.ExportProcessingActivitiesPDF.Data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -249,6 +249,9 @@ const (
|
||||
ActionEnableSAML Action = "enableSAML"
|
||||
ActionExportDocumentVersionPDF Action = "exportDocumentVersionPDF"
|
||||
ActionExportSignableVersionDocumentPDF Action = "exportSignableVersionDocumentPDF"
|
||||
ActionExportProcessingActivitiesPDF Action = "exportProcessingActivitiesPDF"
|
||||
ActionExportDataProtectionImpactAssessmentsPDF Action = "exportDataProtectionImpactAssessmentsPDF"
|
||||
ActionExportTransferImpactAssessmentsPDF Action = "exportTransferImpactAssessmentsPDF"
|
||||
ActionExportFramework Action = "exportFramework"
|
||||
ActionGenerateDocumentChangelog Action = "generateDocumentChangelog"
|
||||
ActionGenerateFrameworkStateOfApplicability Action = "generateFrameworkStateOfApplicability"
|
||||
@@ -305,6 +308,9 @@ var Permissions = map[uint16]map[Action][]Role{
|
||||
ActionListContinualImprovements: NonEmployeeRoles,
|
||||
ActionListRightsRequests: NonEmployeeRoles,
|
||||
ActionListProcessingActivities: NonEmployeeRoles,
|
||||
ActionExportProcessingActivitiesPDF: NonEmployeeRoles,
|
||||
ActionExportDataProtectionImpactAssessmentsPDF: NonEmployeeRoles,
|
||||
ActionExportTransferImpactAssessmentsPDF: NonEmployeeRoles,
|
||||
ActionListSnapshots: NonEmployeeRoles,
|
||||
ActionConfirmEmail: NonEmployeeRoles,
|
||||
ActionAcceptInvitation: NonEmployeeRoles,
|
||||
@@ -694,6 +700,7 @@ var Permissions = map[uint16]map[Action][]Role{
|
||||
ActionGetDataProtectionOfficer: NonEmployeeRoles,
|
||||
ActionGetDataProtectionImpactAssessment: NonEmployeeRoles,
|
||||
ActionGetTransferImpactAssessment: NonEmployeeRoles,
|
||||
ActionExportProcessingActivitiesPDF: NonEmployeeRoles,
|
||||
|
||||
ActionUpdateProcessingActivity: EditRoles,
|
||||
ActionDeleteProcessingActivity: EditRoles,
|
||||
|
||||
@@ -35,6 +35,12 @@ func (e ErrDataProtectionImpactAssessmentNotFound) Error() string {
|
||||
return fmt.Sprintf("data protection impact assessment not found: %q", e.Identifier)
|
||||
}
|
||||
|
||||
type ErrNoDataProtectionImpactAssessmentsFound struct{}
|
||||
|
||||
func (e ErrNoDataProtectionImpactAssessmentsFound) Error() string {
|
||||
return "no data protection impact assessments found"
|
||||
}
|
||||
|
||||
type (
|
||||
DataProtectionImpactAssessment struct {
|
||||
ID gid.GID `db:"id"`
|
||||
@@ -151,6 +157,56 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dpias *DataProtectionImpactAssessments) LoadAllByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *DataProtectionImpactAssessmentFilter,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
organization_id,
|
||||
processing_activity_id,
|
||||
description,
|
||||
necessity_and_proportionality,
|
||||
potential_risk,
|
||||
mitigations,
|
||||
residual_risk,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
processing_activity_data_protection_impact_assessments
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query data protection impact assessments: %w", err)
|
||||
}
|
||||
|
||||
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[DataProtectionImpactAssessment])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect data protection impact assessments: %w", err)
|
||||
}
|
||||
|
||||
*dpias = results
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dpia *DataProtectionImpactAssessment) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -429,4 +485,3 @@ WHERE dpia.tenant_id = @tenant_id AND dpia.organization_id = @organization_id AN
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -53,3 +53,9 @@ func (f *DataProtectionImpactAssessmentFilter) SQLFragment() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *DataProtectionImpactAssessmentFilter) SnapshotID() *gid.GID {
|
||||
if f.snapshotID == nil || *f.snapshotID == nil {
|
||||
return nil
|
||||
}
|
||||
return *f.snapshotID
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@ import (
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type ErrNoProcessingActivitiesFound struct{}
|
||||
|
||||
func (e ErrNoProcessingActivitiesFound) Error() string {
|
||||
return "no processing activities found"
|
||||
}
|
||||
|
||||
type (
|
||||
ProcessingActivity struct {
|
||||
ID gid.GID `db:"id"`
|
||||
@@ -231,6 +237,70 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProcessingActivities) LoadAllByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *ProcessingActivityFilter,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
organization_id,
|
||||
name,
|
||||
purpose,
|
||||
data_subject_category,
|
||||
personal_data_category,
|
||||
special_or_criminal_data,
|
||||
consent_evidence_link,
|
||||
lawful_basis,
|
||||
recipients,
|
||||
location,
|
||||
international_transfers,
|
||||
transfer_safeguards,
|
||||
retention_period,
|
||||
security_measures,
|
||||
data_protection_impact_assessment_needed,
|
||||
transfer_impact_assessment_needed,
|
||||
last_review_date,
|
||||
next_review_date,
|
||||
role,
|
||||
data_protection_officer_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
processing_activities
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query processing activities: %w", err)
|
||||
}
|
||||
|
||||
processingActivities, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ProcessingActivity])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect processing activities: %w", err)
|
||||
}
|
||||
|
||||
*p = processingActivities
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProcessingActivity) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -52,3 +52,10 @@ func (f *ProcessingActivityFilter) SQLFragment() string {
|
||||
return "snapshot_id = @filter_snapshot_id"
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ProcessingActivityFilter) SnapshotID() *gid.GID {
|
||||
if f.snapshotID == nil || *f.snapshotID == nil {
|
||||
return nil
|
||||
}
|
||||
return *f.snapshotID
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -99,6 +99,7 @@ func (s *Snapshots) CountByOrganizationID(
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *SnapshotFilter,
|
||||
) (int, error) {
|
||||
q := `
|
||||
SELECT
|
||||
@@ -108,12 +109,14 @@ FROM
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
row := conn.QueryRow(ctx, q, args)
|
||||
|
||||
|
||||
65
pkg/coredata/snapshot_filter.go
Normal file
65
pkg/coredata/snapshot_filter.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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 coredata
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type (
|
||||
SnapshotFilter struct {
|
||||
snapshotType *SnapshotsType
|
||||
beforeDate *time.Time
|
||||
}
|
||||
)
|
||||
|
||||
func NewSnapshotFilter(snapshotType *SnapshotsType) *SnapshotFilter {
|
||||
return &SnapshotFilter{
|
||||
snapshotType: snapshotType,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *SnapshotFilter) WithBeforeDate(beforeDate *time.Time) *SnapshotFilter {
|
||||
f.beforeDate = beforeDate
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *SnapshotFilter) SQLArguments() pgx.NamedArgs {
|
||||
args := pgx.NamedArgs{
|
||||
"filter_snapshot_type": f.snapshotType,
|
||||
"filter_before_date": f.beforeDate,
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func (f *SnapshotFilter) SQLFragment() string {
|
||||
return `
|
||||
(
|
||||
CASE
|
||||
WHEN @filter_snapshot_type::snapshots_type IS NOT NULL THEN
|
||||
type = @filter_snapshot_type::snapshots_type
|
||||
ELSE TRUE
|
||||
END
|
||||
AND
|
||||
CASE
|
||||
WHEN @filter_before_date::timestamptz IS NOT NULL THEN
|
||||
created_at <= @filter_before_date::timestamptz
|
||||
ELSE TRUE
|
||||
END
|
||||
)`
|
||||
}
|
||||
@@ -35,6 +35,12 @@ func (e ErrTransferImpactAssessmentNotFound) Error() string {
|
||||
return fmt.Sprintf("transfer impact assessment not found: %q", e.Identifier)
|
||||
}
|
||||
|
||||
type ErrNoTransferImpactAssessmentsFound struct{}
|
||||
|
||||
func (e ErrNoTransferImpactAssessmentsFound) Error() string {
|
||||
return "no transfer impact assessments found"
|
||||
}
|
||||
|
||||
type (
|
||||
TransferImpactAssessment struct {
|
||||
ID gid.GID `db:"id"`
|
||||
@@ -151,6 +157,56 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tias *TransferImpactAssessments) LoadAllByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *TransferImpactAssessmentFilter,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
snapshot_id,
|
||||
source_id,
|
||||
organization_id,
|
||||
processing_activity_id,
|
||||
data_subjects,
|
||||
legal_mechanism,
|
||||
transfer,
|
||||
local_law_risk,
|
||||
supplementary_measures,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
processing_activity_transfer_impact_assessments
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query transfer impact assessments: %w", err)
|
||||
}
|
||||
|
||||
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[TransferImpactAssessment])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect transfer impact assessments: %w", err)
|
||||
}
|
||||
|
||||
*tias = results
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tia *TransferImpactAssessment) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -429,4 +485,3 @@ WHERE tia.tenant_id = @tenant_id AND tia.organization_id = @organization_id AND
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -53,3 +53,9 @@ func (f *TransferImpactAssessmentFilter) SQLFragment() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TransferImpactAssessmentFilter) SnapshotID() *gid.GID {
|
||||
if f.snapshotID == nil || *f.snapshotID == nil {
|
||||
return nil
|
||||
}
|
||||
return *f.snapshotID
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -850,6 +850,74 @@ WHERE %s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Vendors) LoadAllByProcessingActivities(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *ProcessingActivityFilter,
|
||||
) (map[gid.GID][]string, error) {
|
||||
q := `
|
||||
WITH filtered_processing_activities AS (
|
||||
SELECT
|
||||
pa.id
|
||||
FROM
|
||||
processing_activities pa
|
||||
WHERE
|
||||
pa.tenant_id = @tenant_id
|
||||
AND pa.organization_id = @organization_id
|
||||
AND %s
|
||||
),
|
||||
filtered_vendors AS (
|
||||
SELECT
|
||||
v.id,
|
||||
v.name
|
||||
FROM
|
||||
vendors v
|
||||
WHERE
|
||||
v.tenant_id = @tenant_id
|
||||
)
|
||||
SELECT
|
||||
pav.processing_activity_id,
|
||||
fv.name
|
||||
FROM
|
||||
processing_activity_vendors pav
|
||||
INNER JOIN
|
||||
filtered_vendors fv ON fv.id = pav.vendor_id
|
||||
INNER JOIN
|
||||
filtered_processing_activities fpa ON fpa.id = pav.processing_activity_id
|
||||
WHERE
|
||||
pav.tenant_id = @tenant_id
|
||||
ORDER BY
|
||||
pav.processing_activity_id, fv.name
|
||||
`
|
||||
q = fmt.Sprintf(q, filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"organization_id": organizationID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot query vendors: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
vendorMap := make(map[gid.GID][]string)
|
||||
for rows.Next() {
|
||||
var processingActivityID gid.GID
|
||||
var vendorName string
|
||||
if err := rows.Scan(&processingActivityID, &vendorName); err != nil {
|
||||
return nil, fmt.Errorf("cannot scan vendor: %w", err)
|
||||
}
|
||||
vendorMap[processingActivityID] = append(vendorMap[processingActivityID], vendorName)
|
||||
}
|
||||
|
||||
return vendorMap, nil
|
||||
}
|
||||
|
||||
func (d Vendors) InsertDataSnapshots(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
315
pkg/docgen/data_protection_impact_assessments_template.html
Normal file
315
pkg/docgen/data_protection_impact_assessments_template.html
Normal file
@@ -0,0 +1,315 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Data Protection Impact Assessments Export</title>
|
||||
<style>
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 2.5cm;
|
||||
@bottom-right {
|
||||
content: "Page " counter(page) " of " counter(pages);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 7.5pt;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Cover page */
|
||||
.cover-page {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.company-header {
|
||||
margin-bottom: 30px;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-height: 50px;
|
||||
max-width: 250px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 16pt;
|
||||
color: #1a1a1a;
|
||||
margin-top: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.export-title {
|
||||
font-size: 22pt;
|
||||
font-weight: normal;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 25px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.meta-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.meta-table td {
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #333;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.meta-table td:first-child {
|
||||
font-weight: 600;
|
||||
width: 25%;
|
||||
background: #f8f8f8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.classification {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.purpose-section {
|
||||
margin: 30px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-text {
|
||||
font-size: 10pt;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
|
||||
/* Assessment page */
|
||||
.assessment-page {
|
||||
page-break-before: always;
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.assessment-page:last-child {
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
.assessment-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.assessment-field {
|
||||
margin-bottom: 8px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.assessment-field-label {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 12px 0 6px 0;
|
||||
page-break-after: avoid;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.assessment-field-value {
|
||||
color: #000;
|
||||
font-size: 7.5pt;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.empty-value {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Annex page */
|
||||
.annex-page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.annex-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.annex-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.annex-section-title {
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 15px 0 10px 0;
|
||||
}
|
||||
|
||||
.annex-subsection-title {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
.annex-enum-list {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.annex-enum-item {
|
||||
margin-bottom: 8px;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.annex-enum-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.annex-enum-description {
|
||||
color: #000;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cover-page">
|
||||
<div class="company-header">
|
||||
{{- if .CompanyHorizontalLogoBase64}}
|
||||
{{imgTag .CompanyHorizontalLogoBase64 "Company Logo" "company-logo"}}
|
||||
{{- else}}
|
||||
<div class="company-name">{{.CompanyName}}</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
|
||||
<h1 class="export-title">Data Protection Impact Assessments</h1>
|
||||
|
||||
<div class="document-meta">
|
||||
<table class="meta-table">
|
||||
<tr>
|
||||
<td>Classification</td>
|
||||
<td>
|
||||
<span class="classification">CONFIDENTIAL</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{{.Version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Published</td>
|
||||
<td>{{.PublishedAt.Format "January 2, 2006"}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="purpose-section">
|
||||
<div class="purpose-title">1. Purpose</div>
|
||||
<div class="purpose-text">
|
||||
This document contains Data Protection Impact Assessments (DPIAs) conducted for processing activities
|
||||
that present a high risk to individuals' rights and freedoms. DPIAs are systematic assessments that
|
||||
evaluate the necessity, proportionality, and risks associated with data processing operations, along
|
||||
with the measures implemented to mitigate identified risks.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{- range $index, $assessment := .Assessments}}
|
||||
<div class="assessment-page">
|
||||
{{if eq $index 0}}
|
||||
<h1 class="annex-title">2. Records</h1>
|
||||
{{end}}
|
||||
<h1 class="annex-section-title">2.{{add $index 1}} {{$assessment.ProcessingActivityName}}</h1>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Description</div>
|
||||
<div class="assessment-field-value">{{if .Description}}{{.Description}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Necessity and Proportionality</div>
|
||||
<div class="assessment-field-value">{{if .NecessityAndProportionality}}{{.NecessityAndProportionality}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Potential Risk</div>
|
||||
<div class="assessment-field-value">{{if .PotentialRisk}}{{.PotentialRisk}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Mitigations</div>
|
||||
<div class="assessment-field-value">{{if .Mitigations}}{{.Mitigations}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Residual Risk</div>
|
||||
<div class="assessment-field-value">{{if .ResidualRisk}}{{.ResidualRisk | formatResidualRisk}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
<div class="annex-page">
|
||||
<h1 class="annex-title">3. Annexes</h1>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-section-title">3.1 Lexicon</div>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Residual Risk</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Low:</span>
|
||||
<span class="annex-enum-description">The residual risk after implementing mitigation measures is considered low. The processing activity poses minimal risk to individuals' rights and freedoms.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Medium:</span>
|
||||
<span class="annex-enum-description">The residual risk after implementing mitigation measures is considered medium. The processing activity poses a moderate risk to individuals' rights and freedoms, requiring ongoing monitoring.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">High:</span>
|
||||
<span class="annex-enum-description">The residual risk after implementing mitigation measures is considered high. The processing activity poses significant risk to individuals' rights and freedoms, requiring enhanced safeguards and regular review.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -23,21 +23,36 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
gmhtml "github.com/yuin/goldmark/renderer/html"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
const (
|
||||
firstPageTOCItems = 21
|
||||
otherPageTOCItems = 28
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed template.html
|
||||
htmlTemplateContent string
|
||||
|
||||
//go:embed processing_activities_template.html
|
||||
processingActivitiesTemplateContent string
|
||||
|
||||
//go:embed data_protection_impact_assessments_template.html
|
||||
dataProtectionImpactAssessmentsTemplateContent string
|
||||
|
||||
//go:embed transfer_impact_assessments_template.html
|
||||
transferImpactAssessmentsTemplateContent string
|
||||
|
||||
templateFuncs = template.FuncMap{
|
||||
"now": func() time.Time { return time.Now() },
|
||||
"eq": func(a, b string) bool { return a == b },
|
||||
"eq": func(a, b any) bool { return a == b },
|
||||
"string": func(v fmt.Stringer) string { return v.String() },
|
||||
"lower": func(s string) string { return strings.ToLower(s) },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"classificationString": func(c Classification) string { return string(c) },
|
||||
"formatContent": func(content string) template.HTML {
|
||||
md := goldmark.New(
|
||||
@@ -56,9 +71,111 @@ var (
|
||||
"imgTag": func(src, alt, class string) template.HTML {
|
||||
return template.HTML(fmt.Sprintf(`<img src="%s" alt="%s" class="%s">`, html.EscapeString(src), html.EscapeString(alt), html.EscapeString(class)))
|
||||
},
|
||||
"formatLawfulBasis": func(basis coredata.ProcessingActivityLawfulBasis) string {
|
||||
switch basis {
|
||||
case coredata.ProcessingActivityLawfulBasisConsent:
|
||||
return "Consent"
|
||||
case coredata.ProcessingActivityLawfulBasisContractualNecessity:
|
||||
return "Contractual Necessity"
|
||||
case coredata.ProcessingActivityLawfulBasisLegalObligation:
|
||||
return "Legal Obligation"
|
||||
case coredata.ProcessingActivityLawfulBasisLegitimateInterest:
|
||||
return "Legitimate Interest"
|
||||
case coredata.ProcessingActivityLawfulBasisPublicTask:
|
||||
return "Public Task"
|
||||
case coredata.ProcessingActivityLawfulBasisVitalInterests:
|
||||
return "Vital Interests"
|
||||
default:
|
||||
return basis.String()
|
||||
}
|
||||
},
|
||||
"formatSpecialOrCriminalData": func(data coredata.ProcessingActivitySpecialOrCriminalDatum) string {
|
||||
switch data {
|
||||
case coredata.ProcessingActivitySpecialOrCriminalDatumYes:
|
||||
return "Yes"
|
||||
case coredata.ProcessingActivitySpecialOrCriminalDatumNo:
|
||||
return "No"
|
||||
case coredata.ProcessingActivitySpecialOrCriminalDatumPossible:
|
||||
return "Possible"
|
||||
default:
|
||||
return data.String()
|
||||
}
|
||||
},
|
||||
"formatTransferSafeguard": func(safeguard *coredata.ProcessingActivityTransferSafeguard) string {
|
||||
if safeguard == nil {
|
||||
return ""
|
||||
}
|
||||
switch *safeguard {
|
||||
case coredata.ProcessingActivityTransferSafeguardStandardContractualClauses:
|
||||
return "Standard Contractual Clauses"
|
||||
case coredata.ProcessingActivityTransferSafeguardBindingCorporateRules:
|
||||
return "Binding Corporate Rules"
|
||||
case coredata.ProcessingActivityTransferSafeguardAdequacyDecision:
|
||||
return "Adequacy Decision"
|
||||
case coredata.ProcessingActivityTransferSafeguardDerogations:
|
||||
return "Derogations"
|
||||
case coredata.ProcessingActivityTransferSafeguardCodesOfConduct:
|
||||
return "Codes of Conduct"
|
||||
case coredata.ProcessingActivityTransferSafeguardCertificationMechanisms:
|
||||
return "Certification Mechanisms"
|
||||
default:
|
||||
return safeguard.String()
|
||||
}
|
||||
},
|
||||
"formatDPIANeeded": func(needed coredata.ProcessingActivityDataProtectionImpactAssessment) string {
|
||||
switch needed {
|
||||
case coredata.ProcessingActivityDataProtectionImpactAssessmentNeeded:
|
||||
return "Yes"
|
||||
case coredata.ProcessingActivityDataProtectionImpactAssessmentNotNeeded:
|
||||
return "No"
|
||||
default:
|
||||
return needed.String()
|
||||
}
|
||||
},
|
||||
"formatTIANeeded": func(needed coredata.ProcessingActivityTransferImpactAssessment) string {
|
||||
switch needed {
|
||||
case coredata.ProcessingActivityTransferImpactAssessmentNeeded:
|
||||
return "Yes"
|
||||
case coredata.ProcessingActivityTransferImpactAssessmentNotNeeded:
|
||||
return "No"
|
||||
default:
|
||||
return needed.String()
|
||||
}
|
||||
},
|
||||
"formatRole": func(role coredata.ProcessingActivityRole) string {
|
||||
switch role {
|
||||
case coredata.ProcessingActivityRoleController:
|
||||
return "Controller"
|
||||
case coredata.ProcessingActivityRoleProcessor:
|
||||
return "Processor"
|
||||
default:
|
||||
return role.String()
|
||||
}
|
||||
},
|
||||
"formatResidualRisk": func(risk *coredata.DataProtectionImpactAssessmentResidualRisk) string {
|
||||
if risk == nil {
|
||||
return ""
|
||||
}
|
||||
switch *risk {
|
||||
case coredata.DataProtectionImpactAssessmentResidualRiskLow:
|
||||
return "Low"
|
||||
case coredata.DataProtectionImpactAssessmentResidualRiskMedium:
|
||||
return "Medium"
|
||||
case coredata.DataProtectionImpactAssessmentResidualRiskHigh:
|
||||
return "High"
|
||||
default:
|
||||
return risk.String()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
documentTemplate = template.Must(template.New("document").Funcs(templateFuncs).Parse(htmlTemplateContent))
|
||||
|
||||
processingActivitiesTemplate = template.Must(template.New("processingActivities").Funcs(templateFuncs).Parse(processingActivitiesTemplateContent))
|
||||
|
||||
dataProtectionImpactAssessmentsTemplate = template.Must(template.New("dataProtectionImpactAssessments").Funcs(templateFuncs).Parse(dataProtectionImpactAssessmentsTemplateContent))
|
||||
|
||||
transferImpactAssessmentsTemplate = template.Must(template.New("transferImpactAssessments").Funcs(templateFuncs).Parse(transferImpactAssessmentsTemplateContent))
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -82,6 +199,71 @@ type (
|
||||
State coredata.DocumentVersionSignatureState
|
||||
RequestedAt time.Time
|
||||
}
|
||||
|
||||
ProcessingActivityTableData struct {
|
||||
CompanyName string
|
||||
CompanyHorizontalLogoBase64 string
|
||||
Version int
|
||||
PublishedAt time.Time
|
||||
Activities []ProcessingActivityRowData
|
||||
}
|
||||
|
||||
ProcessingActivityRowData struct {
|
||||
Name string
|
||||
Purpose *string
|
||||
DataSubjectCategory *string
|
||||
PersonalDataCategory *string
|
||||
SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalDatum
|
||||
ConsentEvidenceLink *string
|
||||
LawfulBasis coredata.ProcessingActivityLawfulBasis
|
||||
Recipients *string
|
||||
Location *string
|
||||
InternationalTransfers bool
|
||||
TransferSafeguards *coredata.ProcessingActivityTransferSafeguard
|
||||
RetentionPeriod *string
|
||||
SecurityMeasures *string
|
||||
DataProtectionImpactAssessmentNeeded coredata.ProcessingActivityDataProtectionImpactAssessment
|
||||
TransferImpactAssessmentNeeded coredata.ProcessingActivityTransferImpactAssessment
|
||||
LastReviewDate *time.Time
|
||||
NextReviewDate *time.Time
|
||||
Role coredata.ProcessingActivityRole
|
||||
DataProtectionOfficerFullName *string
|
||||
Vendors string
|
||||
}
|
||||
|
||||
DataProtectionImpactAssessmentTableData struct {
|
||||
CompanyName string
|
||||
CompanyHorizontalLogoBase64 string
|
||||
Version int
|
||||
PublishedAt time.Time
|
||||
Assessments []DataProtectionImpactAssessmentRowData
|
||||
}
|
||||
|
||||
DataProtectionImpactAssessmentRowData struct {
|
||||
ProcessingActivityName string
|
||||
Description *string
|
||||
NecessityAndProportionality *string
|
||||
PotentialRisk *string
|
||||
Mitigations *string
|
||||
ResidualRisk *coredata.DataProtectionImpactAssessmentResidualRisk
|
||||
}
|
||||
|
||||
TransferImpactAssessmentTableData struct {
|
||||
CompanyName string
|
||||
CompanyHorizontalLogoBase64 string
|
||||
Version int
|
||||
PublishedAt time.Time
|
||||
Assessments []TransferImpactAssessmentRowData
|
||||
}
|
||||
|
||||
TransferImpactAssessmentRowData struct {
|
||||
ProcessingActivityName string
|
||||
DataSubjects *string
|
||||
LegalMechanism *string
|
||||
Transfer *string
|
||||
LocalLawRisk *string
|
||||
SupplementaryMeasures *string
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -99,3 +281,30 @@ func RenderHTML(data DocumentData) ([]byte, error) {
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func RenderProcessingActivitiesTableHTML(data ProcessingActivityTableData) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := processingActivitiesTemplate.Execute(&buf, data); err != nil {
|
||||
return nil, fmt.Errorf("cannot execute processing activities template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func RenderDataProtectionImpactAssessmentsTableHTML(data DataProtectionImpactAssessmentTableData) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := dataProtectionImpactAssessmentsTemplate.Execute(&buf, data); err != nil {
|
||||
return nil, fmt.Errorf("cannot execute data protection impact assessments template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func RenderTransferImpactAssessmentsTableHTML(data TransferImpactAssessmentTableData) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := transferImpactAssessmentsTemplate.Execute(&buf, data); err != nil {
|
||||
return nil, fmt.Errorf("cannot execute transfer impact assessments template: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ func TestTemplateFunctions(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("eq function", func(t *testing.T) {
|
||||
eqFunc := templateFuncs["eq"].(func(string, string) bool)
|
||||
eqFunc := templateFuncs["eq"].(func(any, any) bool)
|
||||
assert.True(t, eqFunc("test", "test"))
|
||||
assert.False(t, eqFunc("test", "other"))
|
||||
})
|
||||
|
||||
535
pkg/docgen/processing_activities_template.html
Normal file
535
pkg/docgen/processing_activities_template.html
Normal file
@@ -0,0 +1,535 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Processing Activities Export</title>
|
||||
<style>
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 2.5cm;
|
||||
@bottom-right {
|
||||
content: "Page " counter(page) " of " counter(pages);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 7.5pt;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Cover page */
|
||||
.cover-page {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.company-header {
|
||||
margin-bottom: 30px;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-height: 50px;
|
||||
max-width: 250px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 16pt;
|
||||
color: #1a1a1a;
|
||||
margin-top: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.export-title {
|
||||
font-size: 22pt;
|
||||
font-weight: normal;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 25px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.meta-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.meta-table td {
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #333;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.meta-table td:first-child {
|
||||
font-weight: 600;
|
||||
width: 25%;
|
||||
background: #f8f8f8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.classification {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.purpose-section {
|
||||
margin: 30px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-text {
|
||||
font-size: 10pt;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
|
||||
/* Activity page */
|
||||
.activity-page {
|
||||
page-break-before: always;
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.activity-page:last-child {
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.activity-section {
|
||||
margin-bottom: 8px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.activity-section-title {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 12px 0 6px 0;
|
||||
page-break-after: avoid;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.activity-field {
|
||||
margin-bottom: 8px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.activity-field-label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 3px;
|
||||
font-size: 7.5pt;
|
||||
}
|
||||
|
||||
.activity-field-value {
|
||||
color: #000;
|
||||
font-size: 7.5pt;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.empty-value {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.activity-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.activity-grid-3 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Annex page */
|
||||
.annex-page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.annex-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.annex-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.annex-section-title {
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 15px 0 10px 0;
|
||||
}
|
||||
|
||||
.annex-subsection-title {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
.annex-enum-list {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.annex-enum-item {
|
||||
margin-bottom: 8px;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.annex-enum-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.annex-enum-description {
|
||||
color: #000;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cover-page">
|
||||
<div class="company-header">
|
||||
{{- if .CompanyHorizontalLogoBase64}}
|
||||
{{imgTag .CompanyHorizontalLogoBase64 "Company Logo" "company-logo"}}
|
||||
{{- else}}
|
||||
<div class="company-name">{{.CompanyName}}</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
|
||||
<h1 class="export-title">Processing Activities</h1>
|
||||
|
||||
<div class="document-meta">
|
||||
<table class="meta-table">
|
||||
<tr>
|
||||
<td>Classification</td>
|
||||
<td>
|
||||
<span class="classification">CONFIDENTIAL</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{{.Version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Published</td>
|
||||
<td>{{.PublishedAt.Format "January 2, 2006"}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="purpose-section">
|
||||
<div class="purpose-title">1. Purpose</div>
|
||||
<div class="purpose-text">
|
||||
This document provides a comprehensive overview of all processing activities within the organization.
|
||||
It serves as a record of personal data processing operations, documenting the purposes, legal bases,
|
||||
data categories, and associated safeguards for each activity.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{- range $index, $activity := .Activities}}
|
||||
<div class="activity-page">
|
||||
{{if eq $index 0}}
|
||||
<h1 class="annex-title">2. Records</h1>
|
||||
{{end}}
|
||||
<h1 class="annex-section-title">2.{{add $index 1}} {{$activity.Name}}</h1>
|
||||
|
||||
<div class="activity-section">
|
||||
<div class="activity-section-title">2.{{add $index 1}}.1 General Information</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Purpose</div>
|
||||
<div class="activity-field-value">{{if .Purpose}}{{.Purpose}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
<div class="activity-grid">
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Role</div>
|
||||
<div class="activity-field-value">{{.Role | formatRole}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="activity-section">
|
||||
<div class="activity-section-title">2.{{add $index 1}}.2 Data Categories</div>
|
||||
<div class="activity-grid-3">
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Data Subject Category</div>
|
||||
<div class="activity-field-value">{{if .DataSubjectCategory}}{{.DataSubjectCategory}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Personal Data Category</div>
|
||||
<div class="activity-field-value">{{if .PersonalDataCategory}}{{.PersonalDataCategory}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Special/Criminal Data</div>
|
||||
<div class="activity-field-value">{{.SpecialOrCriminalData | formatSpecialOrCriminalData}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="activity-section">
|
||||
<div class="activity-section-title">2.{{add $index 1}}.3 Legal Basis</div>
|
||||
<div class="activity-grid">
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Lawful Basis</div>
|
||||
<div class="activity-field-value">{{.LawfulBasis | formatLawfulBasis}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Consent Evidence Link</div>
|
||||
<div class="activity-field-value">{{if .ConsentEvidenceLink}}{{.ConsentEvidenceLink}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="activity-section">
|
||||
<div class="activity-section-title">2.{{add $index 1}}.4 Data Sharing & Transfers</div>
|
||||
<div class="activity-grid">
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Recipients</div>
|
||||
<div class="activity-field-value">{{if .Recipients}}{{.Recipients}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Location</div>
|
||||
<div class="activity-field-value">{{if .Location}}{{.Location}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">International Transfers</div>
|
||||
<div class="activity-field-value">{{if .InternationalTransfers}}Yes{{else}}No{{end}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Transfer Safeguards</div>
|
||||
<div class="activity-field-value">{{if .TransferSafeguards}}{{.TransferSafeguards | formatTransferSafeguard}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="activity-section">
|
||||
<div class="activity-section-title">2.{{add $index 1}}.5 Retention & Security</div>
|
||||
<div class="activity-grid">
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Retention Period</div>
|
||||
<div class="activity-field-value">{{if .RetentionPeriod}}{{.RetentionPeriod}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Security Measures</div>
|
||||
<div class="activity-field-value">{{if .SecurityMeasures}}{{.SecurityMeasures}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="activity-section">
|
||||
<div class="activity-section-title">2.{{add $index 1}}.6 Assessments & Reviews</div>
|
||||
<div class="activity-grid">
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">DPIA Needed</div>
|
||||
<div class="activity-field-value">{{.DataProtectionImpactAssessmentNeeded | formatDPIANeeded}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">TIA Needed</div>
|
||||
<div class="activity-field-value">{{.TransferImpactAssessmentNeeded | formatTIANeeded}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Last Review Date</div>
|
||||
<div class="activity-field-value">{{if .LastReviewDate}}{{.LastReviewDate.Format "January 2, 2006"}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Next Review Date</div>
|
||||
<div class="activity-field-value">{{if .NextReviewDate}}{{.NextReviewDate.Format "January 2, 2006"}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="activity-section">
|
||||
<div class="activity-section-title">2.{{add $index 1}}.7 Responsible Parties</div>
|
||||
<div class="activity-grid">
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Data Protection Officer</div>
|
||||
<div class="activity-field-value">{{if .DataProtectionOfficerFullName}}{{.DataProtectionOfficerFullName}}{{else}}<span class="empty-value">Not assigned</span>{{end}}</div>
|
||||
</div>
|
||||
<div class="activity-field">
|
||||
<div class="activity-field-label">Vendors</div>
|
||||
<div class="activity-field-value">{{if .Vendors}}{{.Vendors}}{{else}}<span class="empty-value">None</span>{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
<div class="annex-page">
|
||||
<h1 class="annex-title">3. Annexes</h1>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-section-title">3.1 Lexicon</div>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Role</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Controller:</span>
|
||||
<span class="annex-enum-description">The entity that determines the purposes and means of processing personal data.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Processor:</span>
|
||||
<span class="annex-enum-description">The entity that processes personal data on behalf of the controller.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Lawful Basis</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Consent:</span>
|
||||
<span class="annex-enum-description">The data subject has given consent to the processing of their personal data.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Contractual Necessity:</span>
|
||||
<span class="annex-enum-description">Processing is necessary for the performance of a contract to which the data subject is party.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Legal Obligation:</span>
|
||||
<span class="annex-enum-description">Processing is necessary for compliance with a legal obligation to which the controller is subject.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Legitimate Interest:</span>
|
||||
<span class="annex-enum-description">Processing is necessary for the purposes of the legitimate interests pursued by the controller or a third party.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Public Task:</span>
|
||||
<span class="annex-enum-description">Processing is necessary for the performance of a task carried out in the public interest or in the exercise of official authority.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Vital Interests:</span>
|
||||
<span class="annex-enum-description">Processing is necessary to protect the vital interests of the data subject or of another natural person.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Special/Criminal Data</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">The processing activity involves special categories of personal data or data relating to criminal convictions and offences.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">The processing activity does not involve special categories of personal data or data relating to criminal convictions and offences.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Possible:</span>
|
||||
<span class="annex-enum-description">The processing activity may involve special categories of personal data or data relating to criminal convictions and offences.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">Transfer Safeguards</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Standard Contractual Clauses:</span>
|
||||
<span class="annex-enum-description">Standard contractual clauses approved by the European Commission are used to ensure adequate protection for international transfers.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Binding Corporate Rules:</span>
|
||||
<span class="annex-enum-description">Binding corporate rules approved by a supervisory authority are used to ensure adequate protection for international transfers.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Adequacy Decision:</span>
|
||||
<span class="annex-enum-description">The European Commission has determined that the third country ensures an adequate level of protection.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Derogations:</span>
|
||||
<span class="annex-enum-description">A derogation under Article 49 of the GDPR is used for the international transfer.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Codes of Conduct:</span>
|
||||
<span class="annex-enum-description">An approved code of conduct together with binding and enforceable commitments is used to ensure adequate protection.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Certification Mechanisms:</span>
|
||||
<span class="annex-enum-description">An approved certification mechanism together with binding and enforceable commitments is used to ensure adequate protection.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">DPIA Needed</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">A Data Protection Impact Assessment is required for this processing activity.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">A Data Protection Impact Assessment is not required for this processing activity.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="annex-section">
|
||||
<div class="annex-subsection-title">TIA Needed</div>
|
||||
<ul class="annex-enum-list">
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">Yes:</span>
|
||||
<span class="annex-enum-description">A Transfer Impact Assessment is required for this processing activity.</span>
|
||||
</li>
|
||||
<li class="annex-enum-item">
|
||||
<span class="annex-enum-name">No:</span>
|
||||
<span class="annex-enum-description">A Transfer Impact Assessment is not required for this processing activity.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
288
pkg/docgen/transfer_impact_assessments_template.html
Normal file
288
pkg/docgen/transfer_impact_assessments_template.html
Normal file
@@ -0,0 +1,288 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Transfer Impact Assessments Export</title>
|
||||
<style>
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 2.5cm;
|
||||
@bottom-right {
|
||||
content: "Page " counter(page) " of " counter(pages);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 7.5pt;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Cover page */
|
||||
.cover-page {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.company-header {
|
||||
margin-bottom: 30px;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-height: 50px;
|
||||
max-width: 250px;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 16pt;
|
||||
color: #1a1a1a;
|
||||
margin-top: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.export-title {
|
||||
font-size: 22pt;
|
||||
font-weight: normal;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 25px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.meta-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.meta-table td {
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #333;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.meta-table td:first-child {
|
||||
font-weight: 600;
|
||||
width: 25%;
|
||||
background: #f8f8f8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.classification {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.purpose-section {
|
||||
margin: 30px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.purpose-text {
|
||||
font-size: 10pt;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
|
||||
/* Assessment page */
|
||||
.assessment-page {
|
||||
page-break-before: always;
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.assessment-page:last-child {
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
.assessment-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.assessment-field {
|
||||
margin-bottom: 8px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.assessment-field-label {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 12px 0 6px 0;
|
||||
page-break-after: avoid;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.assessment-field-value {
|
||||
color: #000;
|
||||
font-size: 7.5pt;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.empty-value {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.annex-page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.annex-title {
|
||||
font-size: 15pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 0 0 15px 0;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.annex-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.annex-section-title {
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin: 15px 0 10px 0;
|
||||
}
|
||||
|
||||
.annex-subsection-title {
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
.annex-enum-list {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.annex-enum-item {
|
||||
margin-bottom: 8px;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.annex-enum-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.annex-enum-description {
|
||||
color: #000;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cover-page">
|
||||
<div class="company-header">
|
||||
{{- if .CompanyHorizontalLogoBase64}}
|
||||
{{imgTag .CompanyHorizontalLogoBase64 "Company Logo" "company-logo"}}
|
||||
{{- else}}
|
||||
<div class="company-name">{{.CompanyName}}</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
|
||||
<h1 class="export-title">Transfer Impact Assessments</h1>
|
||||
|
||||
<div class="document-meta">
|
||||
<table class="meta-table">
|
||||
<tr>
|
||||
<td>Classification</td>
|
||||
<td>
|
||||
<span class="classification">CONFIDENTIAL</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{{.Version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Published</td>
|
||||
<td>{{.PublishedAt.Format "January 2, 2006"}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="purpose-section">
|
||||
<div class="purpose-title">1. Purpose</div>
|
||||
<div class="purpose-text">
|
||||
This document contains Transfer Impact Assessments (TIAs) conducted for processing activities involving
|
||||
international transfers of personal data to countries outside the European Economic Area (EEA). TIAs
|
||||
evaluate the legal mechanisms used for transfers, assess risks related to local laws in destination
|
||||
countries, and document supplementary measures implemented to ensure an adequate level of data protection.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{- range $index, $assessment := .Assessments}}
|
||||
<div class="assessment-page">
|
||||
{{if eq $index 0}}
|
||||
<h1 class="annex-title">2. Records</h1>
|
||||
{{end}}
|
||||
<h1 class="annex-section-title">2.{{add $index 1}} {{$assessment.ProcessingActivityName}}</h1>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Data Subjects</div>
|
||||
<div class="assessment-field-value">{{if .DataSubjects}}{{.DataSubjects}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Transfer</div>
|
||||
<div class="assessment-field-value">{{if .Transfer}}{{.Transfer}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Legal Mechanism</div>
|
||||
<div class="assessment-field-value">{{if .LegalMechanism}}{{.LegalMechanism}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Local Law Risk</div>
|
||||
<div class="assessment-field-value">{{if .LocalLawRisk}}{{.LocalLawRisk}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-field">
|
||||
<div class="assessment-field-label">Supplementary Measures</div>
|
||||
<div class="assessment-field-value">{{if .SupplementaryMeasures}}{{.SupplementaryMeasures}}{{else}}<span class="empty-value">Not specified</span>{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -17,17 +17,21 @@ package probo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/docgen"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/html2pdf"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
type DataProtectionImpactAssessmentService struct {
|
||||
svc *TenantService
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -298,3 +302,130 @@ func (s *DataProtectionImpactAssessmentService) Delete(
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *DataProtectionImpactAssessmentService) ExportPDF(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
filter *coredata.DataProtectionImpactAssessmentFilter,
|
||||
) ([]byte, error) {
|
||||
var tableData docgen.DataProtectionImpactAssessmentTableData
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
var assessments coredata.DataProtectionImpactAssessments
|
||||
if err := assessments.LoadAllByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter); err != nil {
|
||||
return fmt.Errorf("cannot load data protection impact assessments: %w", err)
|
||||
}
|
||||
|
||||
if len(assessments) == 0 {
|
||||
return &coredata.ErrNoDataProtectionImpactAssessmentsFound{}
|
||||
}
|
||||
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
horizontalLogoBase64 := ""
|
||||
if organization.HorizontalLogoFileID != nil {
|
||||
fileRecord := &coredata.File{}
|
||||
fileErr := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
})
|
||||
if fileErr == nil {
|
||||
base64Data, mimeType, logoErr := s.svc.fileManager.GetFileBase64(ctx, fileRecord)
|
||||
if logoErr == nil {
|
||||
horizontalLogoBase64 = fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var snapshots coredata.Snapshots
|
||||
snapshotType := coredata.SnapshotsTypeProcessingActivities
|
||||
|
||||
var version int
|
||||
var publishedAt time.Time
|
||||
|
||||
if snapshotID := filter.SnapshotID(); snapshotID != nil {
|
||||
snapshot := &coredata.Snapshot{}
|
||||
if err := snapshot.LoadByID(ctx, conn, s.svc.scope, *snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot load snapshot: %w", err)
|
||||
}
|
||||
publishedAt = snapshot.CreatedAt
|
||||
snapshotFilter := coredata.NewSnapshotFilter(&snapshotType).WithBeforeDate(&snapshot.CreatedAt)
|
||||
snapshotCount, err := snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, snapshotFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count processing activities snapshots: %w", err)
|
||||
}
|
||||
version = snapshotCount
|
||||
} else {
|
||||
publishedAt = time.Now()
|
||||
snapshotFilter := coredata.NewSnapshotFilter(&snapshotType)
|
||||
snapshotCount, err := snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, snapshotFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count processing activities snapshots: %w", err)
|
||||
}
|
||||
version = snapshotCount + 1
|
||||
}
|
||||
|
||||
assessmentRows := make([]docgen.DataProtectionImpactAssessmentRowData, len(assessments))
|
||||
for i, assessment := range assessments {
|
||||
processingActivity := &coredata.ProcessingActivity{}
|
||||
if err := processingActivity.LoadByID(ctx, conn, s.svc.scope, assessment.ProcessingActivityID); err != nil {
|
||||
return fmt.Errorf("cannot load processing activity: %w", err)
|
||||
}
|
||||
|
||||
assessmentRows[i] = docgen.DataProtectionImpactAssessmentRowData{
|
||||
ProcessingActivityName: processingActivity.Name,
|
||||
Description: assessment.Description,
|
||||
NecessityAndProportionality: assessment.NecessityAndProportionality,
|
||||
PotentialRisk: assessment.PotentialRisk,
|
||||
Mitigations: assessment.Mitigations,
|
||||
ResidualRisk: assessment.ResidualRisk,
|
||||
}
|
||||
}
|
||||
|
||||
tableData = docgen.DataProtectionImpactAssessmentTableData{
|
||||
CompanyName: organization.Name,
|
||||
CompanyHorizontalLogoBase64: horizontalLogoBase64,
|
||||
Version: version,
|
||||
PublishedAt: publishedAt,
|
||||
Assessments: assessmentRows,
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
htmlContent, err := docgen.RenderDataProtectionImpactAssessmentsTableHTML(tableData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot render HTML: %w", err)
|
||||
}
|
||||
|
||||
cfg := html2pdf.RenderConfig{
|
||||
PageFormat: html2pdf.PageFormatA4,
|
||||
Orientation: html2pdf.OrientationPortrait,
|
||||
MarginTop: html2pdf.NewMarginInches(0.98),
|
||||
MarginBottom: html2pdf.NewMarginInches(0.98),
|
||||
MarginLeft: html2pdf.NewMarginInches(0.98),
|
||||
MarginRight: html2pdf.NewMarginInches(0.98),
|
||||
PrintBackground: true,
|
||||
Scale: 1.0,
|
||||
}
|
||||
|
||||
pdfReader, err := s.html2pdfConverter.GeneratePDF(ctx, htmlContent, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate PDF: %w", err)
|
||||
}
|
||||
|
||||
pdfData, err := io.ReadAll(pdfReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read PDF data: %w", err)
|
||||
}
|
||||
|
||||
return pdfData, nil
|
||||
}
|
||||
|
||||
@@ -17,17 +17,22 @@ package probo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/docgen"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/html2pdf"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
type ProcessingActivityService struct {
|
||||
svc *TenantService
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -382,3 +387,159 @@ func (s ProcessingActivityService) CountForOrganizationID(
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *ProcessingActivityService) ExportPDF(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
filter *coredata.ProcessingActivityFilter,
|
||||
) ([]byte, error) {
|
||||
var tableData docgen.ProcessingActivityTableData
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
var processingActivities coredata.ProcessingActivities
|
||||
if err := processingActivities.LoadAllByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter); err != nil {
|
||||
return fmt.Errorf("cannot load processing activities: %w", err)
|
||||
}
|
||||
|
||||
if len(processingActivities) == 0 {
|
||||
return &coredata.ErrNoProcessingActivitiesFound{}
|
||||
}
|
||||
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
horizontalLogoBase64 := ""
|
||||
if organization.HorizontalLogoFileID != nil {
|
||||
fileRecord := &coredata.File{}
|
||||
fileErr := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
})
|
||||
if fileErr == nil {
|
||||
base64Data, mimeType, logoErr := s.svc.fileManager.GetFileBase64(ctx, fileRecord)
|
||||
if logoErr == nil {
|
||||
horizontalLogoBase64 = fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var vendors coredata.Vendors
|
||||
vendorMap, err := vendors.LoadAllByProcessingActivities(ctx, conn, s.svc.scope, organizationID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load vendors: %w", err)
|
||||
}
|
||||
|
||||
var snapshots coredata.Snapshots
|
||||
snapshotType := coredata.SnapshotsTypeProcessingActivities
|
||||
|
||||
var version int
|
||||
var publishedAt time.Time
|
||||
|
||||
if snapshotID := filter.SnapshotID(); snapshotID != nil {
|
||||
snapshot := &coredata.Snapshot{}
|
||||
if err := snapshot.LoadByID(ctx, conn, s.svc.scope, *snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot load snapshot: %w", err)
|
||||
}
|
||||
publishedAt = snapshot.CreatedAt
|
||||
snapshotFilter := coredata.NewSnapshotFilter(&snapshotType).WithBeforeDate(&snapshot.CreatedAt)
|
||||
snapshotCount, err := snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, snapshotFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count processing activities snapshots: %w", err)
|
||||
}
|
||||
version = snapshotCount
|
||||
} else {
|
||||
publishedAt = time.Now()
|
||||
snapshotFilter := coredata.NewSnapshotFilter(&snapshotType)
|
||||
snapshotCount, err := snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, snapshotFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count processing activities snapshots: %w", err)
|
||||
}
|
||||
version = snapshotCount + 1
|
||||
}
|
||||
|
||||
activities := make([]docgen.ProcessingActivityRowData, len(processingActivities))
|
||||
for i, pa := range processingActivities {
|
||||
dpoFullName := (*string)(nil)
|
||||
if pa.DataProtectionOfficerID != nil {
|
||||
dpo := &coredata.People{}
|
||||
if err := dpo.LoadByID(ctx, conn, s.svc.scope, *pa.DataProtectionOfficerID); err == nil {
|
||||
dpoFullName = &dpo.FullName
|
||||
}
|
||||
}
|
||||
|
||||
vendorsList := ""
|
||||
if vendorNames, ok := vendorMap[pa.ID]; ok && len(vendorNames) > 0 {
|
||||
vendorsList = strings.Join(vendorNames, ", ")
|
||||
}
|
||||
|
||||
activities[i] = docgen.ProcessingActivityRowData{
|
||||
Name: pa.Name,
|
||||
Purpose: pa.Purpose,
|
||||
DataSubjectCategory: pa.DataSubjectCategory,
|
||||
PersonalDataCategory: pa.PersonalDataCategory,
|
||||
SpecialOrCriminalData: pa.SpecialOrCriminalData,
|
||||
ConsentEvidenceLink: pa.ConsentEvidenceLink,
|
||||
LawfulBasis: pa.LawfulBasis,
|
||||
Recipients: pa.Recipients,
|
||||
Location: pa.Location,
|
||||
InternationalTransfers: pa.InternationalTransfers,
|
||||
TransferSafeguards: pa.TransferSafeguard,
|
||||
RetentionPeriod: pa.RetentionPeriod,
|
||||
SecurityMeasures: pa.SecurityMeasures,
|
||||
DataProtectionImpactAssessmentNeeded: pa.DataProtectionImpactAssessmentNeeded,
|
||||
TransferImpactAssessmentNeeded: pa.TransferImpactAssessmentNeeded,
|
||||
LastReviewDate: pa.LastReviewDate,
|
||||
NextReviewDate: pa.NextReviewDate,
|
||||
Role: pa.Role,
|
||||
DataProtectionOfficerFullName: dpoFullName,
|
||||
Vendors: vendorsList,
|
||||
}
|
||||
}
|
||||
|
||||
tableData = docgen.ProcessingActivityTableData{
|
||||
CompanyName: organization.Name,
|
||||
CompanyHorizontalLogoBase64: horizontalLogoBase64,
|
||||
Version: version,
|
||||
PublishedAt: publishedAt,
|
||||
Activities: activities,
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
htmlContent, err := docgen.RenderProcessingActivitiesTableHTML(tableData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate HTML: %w", err)
|
||||
}
|
||||
|
||||
cfg := html2pdf.RenderConfig{
|
||||
PageFormat: html2pdf.PageFormatA4,
|
||||
Orientation: html2pdf.OrientationPortrait,
|
||||
MarginTop: html2pdf.NewMarginInches(0.98),
|
||||
MarginBottom: html2pdf.NewMarginInches(0.98),
|
||||
MarginLeft: html2pdf.NewMarginInches(0.98),
|
||||
MarginRight: html2pdf.NewMarginInches(0.98),
|
||||
PrintBackground: true,
|
||||
Scale: 1.0,
|
||||
}
|
||||
|
||||
pdfReader, err := s.html2pdfConverter.GeneratePDF(ctx, htmlContent, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate PDF: %w", err)
|
||||
}
|
||||
|
||||
pdfData, err := io.ReadAll(pdfReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read PDF data: %w", err)
|
||||
}
|
||||
|
||||
return pdfData, nil
|
||||
}
|
||||
|
||||
@@ -252,9 +252,18 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
tenantService.Snapshots = &SnapshotService{svc: tenantService}
|
||||
tenantService.ContinualImprovements = &ContinualImprovementService{svc: tenantService}
|
||||
tenantService.RightsRequests = &RightsRequestService{svc: tenantService}
|
||||
tenantService.ProcessingActivities = &ProcessingActivityService{svc: tenantService}
|
||||
tenantService.DataProtectionImpactAssessments = &DataProtectionImpactAssessmentService{svc: tenantService}
|
||||
tenantService.TransferImpactAssessments = &TransferImpactAssessmentService{svc: tenantService}
|
||||
tenantService.ProcessingActivities = &ProcessingActivityService{
|
||||
svc: tenantService,
|
||||
html2pdfConverter: s.html2pdfConverter,
|
||||
}
|
||||
tenantService.DataProtectionImpactAssessments = &DataProtectionImpactAssessmentService{
|
||||
svc: tenantService,
|
||||
html2pdfConverter: s.html2pdfConverter,
|
||||
}
|
||||
tenantService.TransferImpactAssessments = &TransferImpactAssessmentService{
|
||||
svc: tenantService,
|
||||
html2pdfConverter: s.html2pdfConverter,
|
||||
}
|
||||
tenantService.Files = &FileService{svc: tenantService}
|
||||
tenantService.CustomDomains = &CustomDomainService{
|
||||
svc: tenantService,
|
||||
|
||||
@@ -192,7 +192,8 @@ func (s *SnapshotService) CountForOrganizationID(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
snapshots := coredata.Snapshots{}
|
||||
count, err = snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID)
|
||||
filter := coredata.NewSnapshotFilter(nil)
|
||||
count, err = snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count snapshots: %w", err)
|
||||
}
|
||||
|
||||
@@ -17,17 +17,21 @@ package probo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/docgen"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/html2pdf"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
type TransferImpactAssessmentService struct {
|
||||
svc *TenantService
|
||||
html2pdfConverter *html2pdf.Converter
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -298,3 +302,130 @@ func (s *TransferImpactAssessmentService) Delete(
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TransferImpactAssessmentService) ExportPDF(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
filter *coredata.TransferImpactAssessmentFilter,
|
||||
) ([]byte, error) {
|
||||
var tableData docgen.TransferImpactAssessmentTableData
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
var assessments coredata.TransferImpactAssessments
|
||||
if err := assessments.LoadAllByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter); err != nil {
|
||||
return fmt.Errorf("cannot load transfer impact assessments: %w", err)
|
||||
}
|
||||
|
||||
if len(assessments) == 0 {
|
||||
return &coredata.ErrNoTransferImpactAssessmentsFound{}
|
||||
}
|
||||
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
horizontalLogoBase64 := ""
|
||||
if organization.HorizontalLogoFileID != nil {
|
||||
fileRecord := &coredata.File{}
|
||||
fileErr := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return fileRecord.LoadByID(ctx, conn, s.svc.scope, *organization.HorizontalLogoFileID)
|
||||
})
|
||||
if fileErr == nil {
|
||||
base64Data, mimeType, logoErr := s.svc.fileManager.GetFileBase64(ctx, fileRecord)
|
||||
if logoErr == nil {
|
||||
horizontalLogoBase64 = fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var snapshots coredata.Snapshots
|
||||
snapshotType := coredata.SnapshotsTypeProcessingActivities
|
||||
|
||||
var version int
|
||||
var publishedAt time.Time
|
||||
|
||||
if snapshotID := filter.SnapshotID(); snapshotID != nil {
|
||||
snapshot := &coredata.Snapshot{}
|
||||
if err := snapshot.LoadByID(ctx, conn, s.svc.scope, *snapshotID); err != nil {
|
||||
return fmt.Errorf("cannot load snapshot: %w", err)
|
||||
}
|
||||
publishedAt = snapshot.CreatedAt
|
||||
snapshotFilter := coredata.NewSnapshotFilter(&snapshotType).WithBeforeDate(&snapshot.CreatedAt)
|
||||
snapshotCount, err := snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, snapshotFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count processing activities snapshots: %w", err)
|
||||
}
|
||||
version = snapshotCount
|
||||
} else {
|
||||
publishedAt = time.Now()
|
||||
snapshotFilter := coredata.NewSnapshotFilter(&snapshotType)
|
||||
snapshotCount, err := snapshots.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, snapshotFilter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count processing activities snapshots: %w", err)
|
||||
}
|
||||
version = snapshotCount + 1
|
||||
}
|
||||
|
||||
assessmentRows := make([]docgen.TransferImpactAssessmentRowData, len(assessments))
|
||||
for i, assessment := range assessments {
|
||||
processingActivity := &coredata.ProcessingActivity{}
|
||||
if err := processingActivity.LoadByID(ctx, conn, s.svc.scope, assessment.ProcessingActivityID); err != nil {
|
||||
return fmt.Errorf("cannot load processing activity: %w", err)
|
||||
}
|
||||
|
||||
assessmentRows[i] = docgen.TransferImpactAssessmentRowData{
|
||||
ProcessingActivityName: processingActivity.Name,
|
||||
DataSubjects: assessment.DataSubjects,
|
||||
LegalMechanism: assessment.LegalMechanism,
|
||||
Transfer: assessment.Transfer,
|
||||
LocalLawRisk: assessment.LocalLawRisk,
|
||||
SupplementaryMeasures: assessment.SupplementaryMeasures,
|
||||
}
|
||||
}
|
||||
|
||||
tableData = docgen.TransferImpactAssessmentTableData{
|
||||
CompanyName: organization.Name,
|
||||
CompanyHorizontalLogoBase64: horizontalLogoBase64,
|
||||
Version: version,
|
||||
PublishedAt: publishedAt,
|
||||
Assessments: assessmentRows,
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
htmlContent, err := docgen.RenderTransferImpactAssessmentsTableHTML(tableData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot render HTML: %w", err)
|
||||
}
|
||||
|
||||
cfg := html2pdf.RenderConfig{
|
||||
PageFormat: html2pdf.PageFormatA4,
|
||||
Orientation: html2pdf.OrientationPortrait,
|
||||
MarginTop: html2pdf.NewMarginInches(0.98),
|
||||
MarginBottom: html2pdf.NewMarginInches(0.98),
|
||||
MarginLeft: html2pdf.NewMarginInches(0.98),
|
||||
MarginRight: html2pdf.NewMarginInches(0.98),
|
||||
PrintBackground: true,
|
||||
Scale: 1.0,
|
||||
}
|
||||
|
||||
pdfReader, err := s.html2pdfConverter.GeneratePDF(ctx, htmlContent, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate PDF: %w", err)
|
||||
}
|
||||
|
||||
pdfData, err := io.ReadAll(pdfReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read PDF data: %w", err)
|
||||
}
|
||||
|
||||
return pdfData, nil
|
||||
}
|
||||
|
||||
@@ -3310,6 +3310,15 @@ type Mutation {
|
||||
exportSignableVersionDocumentPDF(
|
||||
input: ExportSignableDocumentVersionPDFInput!
|
||||
): ExportSignableDocumentVersionPDFPayload!
|
||||
exportProcessingActivitiesPDF(
|
||||
input: ExportProcessingActivitiesPDFInput!
|
||||
): ExportProcessingActivitiesPDFPayload!
|
||||
exportDataProtectionImpactAssessmentsPDF(
|
||||
input: ExportDataProtectionImpactAssessmentsPDFInput!
|
||||
): ExportDataProtectionImpactAssessmentsPDFPayload!
|
||||
exportTransferImpactAssessmentsPDF(
|
||||
input: ExportTransferImpactAssessmentsPDFInput!
|
||||
): ExportTransferImpactAssessmentsPDFPayload!
|
||||
createVendorRiskAssessment(
|
||||
input: CreateVendorRiskAssessmentInput!
|
||||
): CreateVendorRiskAssessmentPayload!
|
||||
@@ -3941,6 +3950,21 @@ input ExportSignableDocumentVersionPDFInput {
|
||||
documentVersionId: ID!
|
||||
}
|
||||
|
||||
input ExportProcessingActivitiesPDFInput {
|
||||
organizationId: ID!
|
||||
filter: ProcessingActivityFilter
|
||||
}
|
||||
|
||||
input ExportDataProtectionImpactAssessmentsPDFInput {
|
||||
organizationId: ID!
|
||||
filter: DataProtectionImpactAssessmentFilter
|
||||
}
|
||||
|
||||
input ExportTransferImpactAssessmentsPDFInput {
|
||||
organizationId: ID!
|
||||
filter: TransferImpactAssessmentFilter
|
||||
}
|
||||
|
||||
input DeleteDocumentInput {
|
||||
documentId: ID!
|
||||
}
|
||||
@@ -4600,6 +4624,18 @@ type ExportSignableDocumentVersionPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type ExportProcessingActivitiesPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type ExportDataProtectionImpactAssessmentsPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type ExportTransferImpactAssessmentsPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type UpdateDocumentPayload {
|
||||
document: Document!
|
||||
}
|
||||
|
||||
@@ -779,6 +779,10 @@ type ComplexityRoot struct {
|
||||
Node func(childComplexity int) int
|
||||
}
|
||||
|
||||
ExportDataProtectionImpactAssessmentsPDFPayload struct {
|
||||
Data func(childComplexity int) int
|
||||
}
|
||||
|
||||
ExportDocumentVersionPDFPayload struct {
|
||||
Data func(childComplexity int) int
|
||||
}
|
||||
@@ -787,10 +791,18 @@ type ComplexityRoot struct {
|
||||
ExportJobID func(childComplexity int) int
|
||||
}
|
||||
|
||||
ExportProcessingActivitiesPDFPayload struct {
|
||||
Data func(childComplexity int) int
|
||||
}
|
||||
|
||||
ExportSignableDocumentVersionPDFPayload struct {
|
||||
Data func(childComplexity int) int
|
||||
}
|
||||
|
||||
ExportTransferImpactAssessmentsPDFPayload struct {
|
||||
Data func(childComplexity int) int
|
||||
}
|
||||
|
||||
File struct {
|
||||
CreatedAt func(childComplexity int) int
|
||||
DownloadURL func(childComplexity int) int
|
||||
@@ -1042,9 +1054,12 @@ type ComplexityRoot struct {
|
||||
DeleteVendorService func(childComplexity int, input types.DeleteVendorServiceInput) int
|
||||
DisableSaml func(childComplexity int, input types.DisableSAMLInput) int
|
||||
EnableSaml func(childComplexity int, input types.EnableSAMLInput) int
|
||||
ExportDataProtectionImpactAssessmentsPDF func(childComplexity int, input types.ExportDataProtectionImpactAssessmentsPDFInput) int
|
||||
ExportDocumentVersionPDF func(childComplexity int, input types.ExportDocumentVersionPDFInput) int
|
||||
ExportFramework func(childComplexity int, input types.ExportFrameworkInput) int
|
||||
ExportProcessingActivitiesPDF func(childComplexity int, input types.ExportProcessingActivitiesPDFInput) int
|
||||
ExportSignableVersionDocumentPDF func(childComplexity int, input types.ExportSignableDocumentVersionPDFInput) int
|
||||
ExportTransferImpactAssessmentsPDF func(childComplexity int, input types.ExportTransferImpactAssessmentsPDFInput) int
|
||||
GenerateDocumentChangelog func(childComplexity int, input types.GenerateDocumentChangelogInput) int
|
||||
GenerateFrameworkStateOfApplicability func(childComplexity int, input types.GenerateFrameworkStateOfApplicabilityInput) int
|
||||
GetTrustCenterFile func(childComplexity int, input types.GetTrustCenterFileInput) int
|
||||
@@ -2200,6 +2215,9 @@ type MutationResolver interface {
|
||||
SignDocument(ctx context.Context, input types.SignDocumentInput) (*types.SignDocumentPayload, error)
|
||||
ExportDocumentVersionPDF(ctx context.Context, input types.ExportDocumentVersionPDFInput) (*types.ExportDocumentVersionPDFPayload, error)
|
||||
ExportSignableVersionDocumentPDF(ctx context.Context, input types.ExportSignableDocumentVersionPDFInput) (*types.ExportSignableDocumentVersionPDFPayload, error)
|
||||
ExportProcessingActivitiesPDF(ctx context.Context, input types.ExportProcessingActivitiesPDFInput) (*types.ExportProcessingActivitiesPDFPayload, error)
|
||||
ExportDataProtectionImpactAssessmentsPDF(ctx context.Context, input types.ExportDataProtectionImpactAssessmentsPDFInput) (*types.ExportDataProtectionImpactAssessmentsPDFPayload, error)
|
||||
ExportTransferImpactAssessmentsPDF(ctx context.Context, input types.ExportTransferImpactAssessmentsPDFInput) (*types.ExportTransferImpactAssessmentsPDFPayload, error)
|
||||
CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error)
|
||||
AssessVendor(ctx context.Context, input types.AssessVendorInput) (*types.AssessVendorPayload, error)
|
||||
CreateAsset(ctx context.Context, input types.CreateAssetInput) (*types.CreateAssetPayload, error)
|
||||
@@ -4350,6 +4368,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.EvidenceEdge.Node(childComplexity), true
|
||||
|
||||
case "ExportDataProtectionImpactAssessmentsPDFPayload.data":
|
||||
if e.complexity.ExportDataProtectionImpactAssessmentsPDFPayload.Data == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.ExportDataProtectionImpactAssessmentsPDFPayload.Data(childComplexity), true
|
||||
|
||||
case "ExportDocumentVersionPDFPayload.data":
|
||||
if e.complexity.ExportDocumentVersionPDFPayload.Data == nil {
|
||||
break
|
||||
@@ -4364,6 +4389,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.ExportFrameworkPayload.ExportJobID(childComplexity), true
|
||||
|
||||
case "ExportProcessingActivitiesPDFPayload.data":
|
||||
if e.complexity.ExportProcessingActivitiesPDFPayload.Data == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.ExportProcessingActivitiesPDFPayload.Data(childComplexity), true
|
||||
|
||||
case "ExportSignableDocumentVersionPDFPayload.data":
|
||||
if e.complexity.ExportSignableDocumentVersionPDFPayload.Data == nil {
|
||||
break
|
||||
@@ -4371,6 +4403,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.ExportSignableDocumentVersionPDFPayload.Data(childComplexity), true
|
||||
|
||||
case "ExportTransferImpactAssessmentsPDFPayload.data":
|
||||
if e.complexity.ExportTransferImpactAssessmentsPDFPayload.Data == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.ExportTransferImpactAssessmentsPDFPayload.Data(childComplexity), true
|
||||
|
||||
case "File.createdAt":
|
||||
if e.complexity.File.CreatedAt == nil {
|
||||
break
|
||||
@@ -5943,6 +5982,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.EnableSaml(childComplexity, args["input"].(types.EnableSAMLInput)), true
|
||||
case "Mutation.exportDataProtectionImpactAssessmentsPDF":
|
||||
if e.complexity.Mutation.ExportDataProtectionImpactAssessmentsPDF == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_exportDataProtectionImpactAssessmentsPDF_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.ExportDataProtectionImpactAssessmentsPDF(childComplexity, args["input"].(types.ExportDataProtectionImpactAssessmentsPDFInput)), true
|
||||
case "Mutation.exportDocumentVersionPDF":
|
||||
if e.complexity.Mutation.ExportDocumentVersionPDF == nil {
|
||||
break
|
||||
@@ -5965,6 +6015,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.ExportFramework(childComplexity, args["input"].(types.ExportFrameworkInput)), true
|
||||
case "Mutation.exportProcessingActivitiesPDF":
|
||||
if e.complexity.Mutation.ExportProcessingActivitiesPDF == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_exportProcessingActivitiesPDF_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.ExportProcessingActivitiesPDF(childComplexity, args["input"].(types.ExportProcessingActivitiesPDFInput)), true
|
||||
case "Mutation.exportSignableVersionDocumentPDF":
|
||||
if e.complexity.Mutation.ExportSignableVersionDocumentPDF == nil {
|
||||
break
|
||||
@@ -5976,6 +6037,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.ExportSignableVersionDocumentPDF(childComplexity, args["input"].(types.ExportSignableDocumentVersionPDFInput)), true
|
||||
case "Mutation.exportTransferImpactAssessmentsPDF":
|
||||
if e.complexity.Mutation.ExportTransferImpactAssessmentsPDF == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_exportTransferImpactAssessmentsPDF_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.ExportTransferImpactAssessmentsPDF(childComplexity, args["input"].(types.ExportTransferImpactAssessmentsPDFInput)), true
|
||||
case "Mutation.generateDocumentChangelog":
|
||||
if e.complexity.Mutation.GenerateDocumentChangelog == nil {
|
||||
break
|
||||
@@ -10030,9 +10102,12 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
||||
ec.unmarshalInputDocumentVersionSignatureOrder,
|
||||
ec.unmarshalInputEnableSAMLInput,
|
||||
ec.unmarshalInputEvidenceOrder,
|
||||
ec.unmarshalInputExportDataProtectionImpactAssessmentsPDFInput,
|
||||
ec.unmarshalInputExportDocumentVersionPDFInput,
|
||||
ec.unmarshalInputExportFrameworkInput,
|
||||
ec.unmarshalInputExportProcessingActivitiesPDFInput,
|
||||
ec.unmarshalInputExportSignableDocumentVersionPDFInput,
|
||||
ec.unmarshalInputExportTransferImpactAssessmentsPDFInput,
|
||||
ec.unmarshalInputFrameworkOrder,
|
||||
ec.unmarshalInputFulfillEvidenceInput,
|
||||
ec.unmarshalInputGenerateDocumentChangelogInput,
|
||||
@@ -13530,6 +13605,15 @@ type Mutation {
|
||||
exportSignableVersionDocumentPDF(
|
||||
input: ExportSignableDocumentVersionPDFInput!
|
||||
): ExportSignableDocumentVersionPDFPayload!
|
||||
exportProcessingActivitiesPDF(
|
||||
input: ExportProcessingActivitiesPDFInput!
|
||||
): ExportProcessingActivitiesPDFPayload!
|
||||
exportDataProtectionImpactAssessmentsPDF(
|
||||
input: ExportDataProtectionImpactAssessmentsPDFInput!
|
||||
): ExportDataProtectionImpactAssessmentsPDFPayload!
|
||||
exportTransferImpactAssessmentsPDF(
|
||||
input: ExportTransferImpactAssessmentsPDFInput!
|
||||
): ExportTransferImpactAssessmentsPDFPayload!
|
||||
createVendorRiskAssessment(
|
||||
input: CreateVendorRiskAssessmentInput!
|
||||
): CreateVendorRiskAssessmentPayload!
|
||||
@@ -14161,6 +14245,21 @@ input ExportSignableDocumentVersionPDFInput {
|
||||
documentVersionId: ID!
|
||||
}
|
||||
|
||||
input ExportProcessingActivitiesPDFInput {
|
||||
organizationId: ID!
|
||||
filter: ProcessingActivityFilter
|
||||
}
|
||||
|
||||
input ExportDataProtectionImpactAssessmentsPDFInput {
|
||||
organizationId: ID!
|
||||
filter: DataProtectionImpactAssessmentFilter
|
||||
}
|
||||
|
||||
input ExportTransferImpactAssessmentsPDFInput {
|
||||
organizationId: ID!
|
||||
filter: TransferImpactAssessmentFilter
|
||||
}
|
||||
|
||||
input DeleteDocumentInput {
|
||||
documentId: ID!
|
||||
}
|
||||
@@ -14820,6 +14919,18 @@ type ExportSignableDocumentVersionPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type ExportProcessingActivitiesPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type ExportDataProtectionImpactAssessmentsPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type ExportTransferImpactAssessmentsPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type UpdateDocumentPayload {
|
||||
document: Document!
|
||||
}
|
||||
@@ -17125,6 +17236,17 @@ func (ec *executionContext) field_Mutation_enableSAML_args(ctx context.Context,
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_exportDataProtectionImpactAssessmentsPDF_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNExportDataProtectionImpactAssessmentsPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportDataProtectionImpactAssessmentsPDFInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_exportDocumentVersionPDF_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
@@ -17147,6 +17269,17 @@ func (ec *executionContext) field_Mutation_exportFramework_args(ctx context.Cont
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_exportProcessingActivitiesPDF_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNExportProcessingActivitiesPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportProcessingActivitiesPDFInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_exportSignableVersionDocumentPDF_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
@@ -17158,6 +17291,17 @@ func (ec *executionContext) field_Mutation_exportSignableVersionDocumentPDF_args
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_exportTransferImpactAssessmentsPDF_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNExportTransferImpactAssessmentsPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportTransferImpactAssessmentsPDFInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, 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{}
|
||||
@@ -29640,6 +29784,35 @@ func (ec *executionContext) fieldContext_EvidenceEdge_node(_ context.Context, fi
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _ExportDataProtectionImpactAssessmentsPDFPayload_data(ctx context.Context, field graphql.CollectedField, obj *types.ExportDataProtectionImpactAssessmentsPDFPayload) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_ExportDataProtectionImpactAssessmentsPDFPayload_data,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Data, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNString2string,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_ExportDataProtectionImpactAssessmentsPDFPayload_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "ExportDataProtectionImpactAssessmentsPDFPayload",
|
||||
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) _ExportDocumentVersionPDFPayload_data(ctx context.Context, field graphql.CollectedField, obj *types.ExportDocumentVersionPDFPayload) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -29698,6 +29871,35 @@ func (ec *executionContext) fieldContext_ExportFrameworkPayload_exportJobId(_ co
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _ExportProcessingActivitiesPDFPayload_data(ctx context.Context, field graphql.CollectedField, obj *types.ExportProcessingActivitiesPDFPayload) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_ExportProcessingActivitiesPDFPayload_data,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Data, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNString2string,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_ExportProcessingActivitiesPDFPayload_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "ExportProcessingActivitiesPDFPayload",
|
||||
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) _ExportSignableDocumentVersionPDFPayload_data(ctx context.Context, field graphql.CollectedField, obj *types.ExportSignableDocumentVersionPDFPayload) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -29727,6 +29929,35 @@ func (ec *executionContext) fieldContext_ExportSignableDocumentVersionPDFPayload
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _ExportTransferImpactAssessmentsPDFPayload_data(ctx context.Context, field graphql.CollectedField, obj *types.ExportTransferImpactAssessmentsPDFPayload) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_ExportTransferImpactAssessmentsPDFPayload_data,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Data, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNString2string,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_ExportTransferImpactAssessmentsPDFPayload_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "ExportTransferImpactAssessmentsPDFPayload",
|
||||
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) _File_id(ctx context.Context, field graphql.CollectedField, obj *types.File) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -37412,6 +37643,141 @@ func (ec *executionContext) fieldContext_Mutation_exportSignableVersionDocumentP
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_exportProcessingActivitiesPDF(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_Mutation_exportProcessingActivitiesPDF,
|
||||
func(ctx context.Context) (any, error) {
|
||||
fc := graphql.GetFieldContext(ctx)
|
||||
return ec.resolvers.Mutation().ExportProcessingActivitiesPDF(ctx, fc.Args["input"].(types.ExportProcessingActivitiesPDFInput))
|
||||
},
|
||||
nil,
|
||||
ec.marshalNExportProcessingActivitiesPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportProcessingActivitiesPDFPayload,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_exportProcessingActivitiesPDF(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 "data":
|
||||
return ec.fieldContext_ExportProcessingActivitiesPDFPayload_data(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ExportProcessingActivitiesPDFPayload", 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_exportProcessingActivitiesPDF_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_exportDataProtectionImpactAssessmentsPDF(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_Mutation_exportDataProtectionImpactAssessmentsPDF,
|
||||
func(ctx context.Context) (any, error) {
|
||||
fc := graphql.GetFieldContext(ctx)
|
||||
return ec.resolvers.Mutation().ExportDataProtectionImpactAssessmentsPDF(ctx, fc.Args["input"].(types.ExportDataProtectionImpactAssessmentsPDFInput))
|
||||
},
|
||||
nil,
|
||||
ec.marshalNExportDataProtectionImpactAssessmentsPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportDataProtectionImpactAssessmentsPDFPayload,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_exportDataProtectionImpactAssessmentsPDF(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 "data":
|
||||
return ec.fieldContext_ExportDataProtectionImpactAssessmentsPDFPayload_data(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ExportDataProtectionImpactAssessmentsPDFPayload", 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_exportDataProtectionImpactAssessmentsPDF_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_exportTransferImpactAssessmentsPDF(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_Mutation_exportTransferImpactAssessmentsPDF,
|
||||
func(ctx context.Context) (any, error) {
|
||||
fc := graphql.GetFieldContext(ctx)
|
||||
return ec.resolvers.Mutation().ExportTransferImpactAssessmentsPDF(ctx, fc.Args["input"].(types.ExportTransferImpactAssessmentsPDFInput))
|
||||
},
|
||||
nil,
|
||||
ec.marshalNExportTransferImpactAssessmentsPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportTransferImpactAssessmentsPDFPayload,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_exportTransferImpactAssessmentsPDF(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 "data":
|
||||
return ec.fieldContext_ExportTransferImpactAssessmentsPDFPayload_data(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ExportTransferImpactAssessmentsPDFPayload", 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_exportTransferImpactAssessmentsPDF_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_createVendorRiskAssessment(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -65022,6 +65388,40 @@ func (ec *executionContext) unmarshalInputEvidenceOrder(ctx context.Context, obj
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputExportDataProtectionImpactAssessmentsPDFInput(ctx context.Context, obj any) (types.ExportDataProtectionImpactAssessmentsPDFInput, error) {
|
||||
var it types.ExportDataProtectionImpactAssessmentsPDFInput
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"organizationId", "filter"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "organizationId":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationId"))
|
||||
data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.OrganizationID = data
|
||||
case "filter":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("filter"))
|
||||
data, err := ec.unmarshalODataProtectionImpactAssessmentFilter2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDataProtectionImpactAssessmentFilter(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.Filter = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputExportDocumentVersionPDFInput(ctx context.Context, obj any) (types.ExportDocumentVersionPDFInput, error) {
|
||||
var it types.ExportDocumentVersionPDFInput
|
||||
asMap := map[string]any{}
|
||||
@@ -65097,6 +65497,40 @@ func (ec *executionContext) unmarshalInputExportFrameworkInput(ctx context.Conte
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputExportProcessingActivitiesPDFInput(ctx context.Context, obj any) (types.ExportProcessingActivitiesPDFInput, error) {
|
||||
var it types.ExportProcessingActivitiesPDFInput
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"organizationId", "filter"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "organizationId":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationId"))
|
||||
data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.OrganizationID = data
|
||||
case "filter":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("filter"))
|
||||
data, err := ec.unmarshalOProcessingActivityFilter2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐProcessingActivityFilter(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.Filter = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputExportSignableDocumentVersionPDFInput(ctx context.Context, obj any) (types.ExportSignableDocumentVersionPDFInput, error) {
|
||||
var it types.ExportSignableDocumentVersionPDFInput
|
||||
asMap := map[string]any{}
|
||||
@@ -65124,6 +65558,40 @@ func (ec *executionContext) unmarshalInputExportSignableDocumentVersionPDFInput(
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputExportTransferImpactAssessmentsPDFInput(ctx context.Context, obj any) (types.ExportTransferImpactAssessmentsPDFInput, error) {
|
||||
var it types.ExportTransferImpactAssessmentsPDFInput
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"organizationId", "filter"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "organizationId":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationId"))
|
||||
data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.OrganizationID = data
|
||||
case "filter":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("filter"))
|
||||
data, err := ec.unmarshalOTransferImpactAssessmentFilter2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐTransferImpactAssessmentFilter(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.Filter = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputFrameworkOrder(ctx context.Context, obj any) (types.FrameworkOrderBy, error) {
|
||||
var it types.FrameworkOrderBy
|
||||
asMap := map[string]any{}
|
||||
@@ -76415,6 +76883,45 @@ func (ec *executionContext) _EvidenceEdge(ctx context.Context, sel ast.Selection
|
||||
return out
|
||||
}
|
||||
|
||||
var exportDataProtectionImpactAssessmentsPDFPayloadImplementors = []string{"ExportDataProtectionImpactAssessmentsPDFPayload"}
|
||||
|
||||
func (ec *executionContext) _ExportDataProtectionImpactAssessmentsPDFPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ExportDataProtectionImpactAssessmentsPDFPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, exportDataProtectionImpactAssessmentsPDFPayloadImplementors)
|
||||
|
||||
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("ExportDataProtectionImpactAssessmentsPDFPayload")
|
||||
case "data":
|
||||
out.Values[i] = ec._ExportDataProtectionImpactAssessmentsPDFPayload_data(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 exportDocumentVersionPDFPayloadImplementors = []string{"ExportDocumentVersionPDFPayload"}
|
||||
|
||||
func (ec *executionContext) _ExportDocumentVersionPDFPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ExportDocumentVersionPDFPayload) graphql.Marshaler {
|
||||
@@ -76493,6 +77000,45 @@ func (ec *executionContext) _ExportFrameworkPayload(ctx context.Context, sel ast
|
||||
return out
|
||||
}
|
||||
|
||||
var exportProcessingActivitiesPDFPayloadImplementors = []string{"ExportProcessingActivitiesPDFPayload"}
|
||||
|
||||
func (ec *executionContext) _ExportProcessingActivitiesPDFPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ExportProcessingActivitiesPDFPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, exportProcessingActivitiesPDFPayloadImplementors)
|
||||
|
||||
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("ExportProcessingActivitiesPDFPayload")
|
||||
case "data":
|
||||
out.Values[i] = ec._ExportProcessingActivitiesPDFPayload_data(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 exportSignableDocumentVersionPDFPayloadImplementors = []string{"ExportSignableDocumentVersionPDFPayload"}
|
||||
|
||||
func (ec *executionContext) _ExportSignableDocumentVersionPDFPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ExportSignableDocumentVersionPDFPayload) graphql.Marshaler {
|
||||
@@ -76532,6 +77078,45 @@ func (ec *executionContext) _ExportSignableDocumentVersionPDFPayload(ctx context
|
||||
return out
|
||||
}
|
||||
|
||||
var exportTransferImpactAssessmentsPDFPayloadImplementors = []string{"ExportTransferImpactAssessmentsPDFPayload"}
|
||||
|
||||
func (ec *executionContext) _ExportTransferImpactAssessmentsPDFPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ExportTransferImpactAssessmentsPDFPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, exportTransferImpactAssessmentsPDFPayloadImplementors)
|
||||
|
||||
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("ExportTransferImpactAssessmentsPDFPayload")
|
||||
case "data":
|
||||
out.Values[i] = ec._ExportTransferImpactAssessmentsPDFPayload_data(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 fileImplementors = []string{"File"}
|
||||
|
||||
func (ec *executionContext) _File(ctx context.Context, sel ast.SelectionSet, obj *types.File) graphql.Marshaler {
|
||||
@@ -79042,6 +79627,27 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "exportProcessingActivitiesPDF":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_exportProcessingActivitiesPDF(ctx, field)
|
||||
})
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "exportDataProtectionImpactAssessmentsPDF":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_exportDataProtectionImpactAssessmentsPDF(ctx, field)
|
||||
})
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "exportTransferImpactAssessmentsPDF":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_exportTransferImpactAssessmentsPDF(ctx, field)
|
||||
})
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "createVendorRiskAssessment":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_createVendorRiskAssessment(ctx, field)
|
||||
@@ -93526,6 +94132,25 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNExportDataProtectionImpactAssessmentsPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportDataProtectionImpactAssessmentsPDFInput(ctx context.Context, v any) (types.ExportDataProtectionImpactAssessmentsPDFInput, error) {
|
||||
res, err := ec.unmarshalInputExportDataProtectionImpactAssessmentsPDFInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNExportDataProtectionImpactAssessmentsPDFPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportDataProtectionImpactAssessmentsPDFPayload(ctx context.Context, sel ast.SelectionSet, v types.ExportDataProtectionImpactAssessmentsPDFPayload) graphql.Marshaler {
|
||||
return ec._ExportDataProtectionImpactAssessmentsPDFPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNExportDataProtectionImpactAssessmentsPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportDataProtectionImpactAssessmentsPDFPayload(ctx context.Context, sel ast.SelectionSet, v *types.ExportDataProtectionImpactAssessmentsPDFPayload) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._ExportDataProtectionImpactAssessmentsPDFPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNExportDocumentVersionPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportDocumentVersionPDFInput(ctx context.Context, v any) (types.ExportDocumentVersionPDFInput, error) {
|
||||
res, err := ec.unmarshalInputExportDocumentVersionPDFInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
@@ -93564,6 +94189,25 @@ func (ec *executionContext) marshalNExportFrameworkPayload2ᚖgoᚗproboᚗinc
|
||||
return ec._ExportFrameworkPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNExportProcessingActivitiesPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportProcessingActivitiesPDFInput(ctx context.Context, v any) (types.ExportProcessingActivitiesPDFInput, error) {
|
||||
res, err := ec.unmarshalInputExportProcessingActivitiesPDFInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNExportProcessingActivitiesPDFPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportProcessingActivitiesPDFPayload(ctx context.Context, sel ast.SelectionSet, v types.ExportProcessingActivitiesPDFPayload) graphql.Marshaler {
|
||||
return ec._ExportProcessingActivitiesPDFPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNExportProcessingActivitiesPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportProcessingActivitiesPDFPayload(ctx context.Context, sel ast.SelectionSet, v *types.ExportProcessingActivitiesPDFPayload) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._ExportProcessingActivitiesPDFPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNExportSignableDocumentVersionPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportSignableDocumentVersionPDFInput(ctx context.Context, v any) (types.ExportSignableDocumentVersionPDFInput, error) {
|
||||
res, err := ec.unmarshalInputExportSignableDocumentVersionPDFInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
@@ -93583,6 +94227,25 @@ func (ec *executionContext) marshalNExportSignableDocumentVersionPDFPayload2ᚖg
|
||||
return ec._ExportSignableDocumentVersionPDFPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNExportTransferImpactAssessmentsPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportTransferImpactAssessmentsPDFInput(ctx context.Context, v any) (types.ExportTransferImpactAssessmentsPDFInput, error) {
|
||||
res, err := ec.unmarshalInputExportTransferImpactAssessmentsPDFInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNExportTransferImpactAssessmentsPDFPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportTransferImpactAssessmentsPDFPayload(ctx context.Context, sel ast.SelectionSet, v types.ExportTransferImpactAssessmentsPDFPayload) graphql.Marshaler {
|
||||
return ec._ExportTransferImpactAssessmentsPDFPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNExportTransferImpactAssessmentsPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐExportTransferImpactAssessmentsPDFPayload(ctx context.Context, sel ast.SelectionSet, v *types.ExportTransferImpactAssessmentsPDFPayload) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._ExportTransferImpactAssessmentsPDFPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNFramework2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐFramework(ctx context.Context, sel ast.SelectionSet, v types.Framework) graphql.Marshaler {
|
||||
return ec._Framework(ctx, sel, &v)
|
||||
}
|
||||
|
||||
@@ -1220,6 +1220,15 @@ type EvidenceEdge struct {
|
||||
Node *Evidence `json:"node"`
|
||||
}
|
||||
|
||||
type ExportDataProtectionImpactAssessmentsPDFInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Filter *DataProtectionImpactAssessmentFilter `json:"filter,omitempty"`
|
||||
}
|
||||
|
||||
type ExportDataProtectionImpactAssessmentsPDFPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type ExportDocumentVersionPDFInput struct {
|
||||
DocumentVersionID gid.GID `json:"documentVersionId"`
|
||||
WithWatermark bool `json:"withWatermark"`
|
||||
@@ -1239,6 +1248,15 @@ type ExportFrameworkPayload struct {
|
||||
ExportJobID gid.GID `json:"exportJobId"`
|
||||
}
|
||||
|
||||
type ExportProcessingActivitiesPDFInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Filter *ProcessingActivityFilter `json:"filter,omitempty"`
|
||||
}
|
||||
|
||||
type ExportProcessingActivitiesPDFPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type ExportSignableDocumentVersionPDFInput struct {
|
||||
DocumentVersionID gid.GID `json:"documentVersionId"`
|
||||
}
|
||||
@@ -1247,6 +1265,15 @@ type ExportSignableDocumentVersionPDFPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type ExportTransferImpactAssessmentsPDFInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Filter *TransferImpactAssessmentFilter `json:"filter,omitempty"`
|
||||
}
|
||||
|
||||
type ExportTransferImpactAssessmentsPDFPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
ID gid.GID `json:"id"`
|
||||
MimeType string `json:"mimeType"`
|
||||
|
||||
@@ -3745,6 +3745,84 @@ func (r *mutationResolver) ExportSignableVersionDocumentPDF(ctx context.Context,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExportProcessingActivitiesPDF is the resolver for the exportProcessingActivitiesPDF field.
|
||||
func (r *mutationResolver) ExportProcessingActivitiesPDF(ctx context.Context, input types.ExportProcessingActivitiesPDFInput) (*types.ExportProcessingActivitiesPDFPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.OrganizationID, authz.ActionExportProcessingActivitiesPDF)
|
||||
|
||||
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
||||
|
||||
var snapshotIDPtr *gid.GID
|
||||
if input.Filter != nil {
|
||||
snapshotIDPtr = input.Filter.SnapshotID
|
||||
}
|
||||
processingActivityFilter := coredata.NewProcessingActivityFilter(&snapshotIDPtr)
|
||||
|
||||
pdf, err := prb.ProcessingActivities.ExportPDF(ctx, input.OrganizationID, processingActivityFilter)
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrNoProcessingActivitiesFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot export processing activities PDF: %w", err))
|
||||
}
|
||||
|
||||
return &types.ExportProcessingActivitiesPDFPayload{
|
||||
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExportDataProtectionImpactAssessmentsPDF is the resolver for the exportDataProtectionImpactAssessmentsPDF field.
|
||||
func (r *mutationResolver) ExportDataProtectionImpactAssessmentsPDF(ctx context.Context, input types.ExportDataProtectionImpactAssessmentsPDFInput) (*types.ExportDataProtectionImpactAssessmentsPDFPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.OrganizationID, authz.ActionExportDataProtectionImpactAssessmentsPDF)
|
||||
|
||||
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
||||
|
||||
var snapshotIDPtr *gid.GID
|
||||
if input.Filter != nil {
|
||||
snapshotIDPtr = input.Filter.SnapshotID
|
||||
}
|
||||
dpiaFilter := coredata.NewDataProtectionImpactAssessmentFilter(&snapshotIDPtr)
|
||||
|
||||
pdf, err := prb.DataProtectionImpactAssessments.ExportPDF(ctx, input.OrganizationID, dpiaFilter)
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrNoDataProtectionImpactAssessmentsFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot export data protection impact assessments PDF: %w", err))
|
||||
}
|
||||
|
||||
return &types.ExportDataProtectionImpactAssessmentsPDFPayload{
|
||||
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExportTransferImpactAssessmentsPDF is the resolver for the exportTransferImpactAssessmentsPDF field.
|
||||
func (r *mutationResolver) ExportTransferImpactAssessmentsPDF(ctx context.Context, input types.ExportTransferImpactAssessmentsPDFInput) (*types.ExportTransferImpactAssessmentsPDFPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.OrganizationID, authz.ActionExportTransferImpactAssessmentsPDF)
|
||||
|
||||
prb := r.ProboService(ctx, input.OrganizationID.TenantID())
|
||||
|
||||
var snapshotIDPtr *gid.GID
|
||||
if input.Filter != nil {
|
||||
snapshotIDPtr = input.Filter.SnapshotID
|
||||
}
|
||||
tiaFilter := coredata.NewTransferImpactAssessmentFilter(&snapshotIDPtr)
|
||||
|
||||
pdf, err := prb.TransferImpactAssessments.ExportPDF(ctx, input.OrganizationID, tiaFilter)
|
||||
if err != nil {
|
||||
var errNotFound *coredata.ErrNoTransferImpactAssessmentsFound
|
||||
if errors.As(err, &errNotFound) {
|
||||
return nil, gqlutils.NotFound(errNotFound)
|
||||
}
|
||||
panic(fmt.Errorf("cannot export transfer impact assessments PDF: %w", err))
|
||||
}
|
||||
|
||||
return &types.ExportTransferImpactAssessmentsPDFPayload{
|
||||
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateVendorRiskAssessment is the resolver for the createVendorRiskAssessment field.
|
||||
func (r *mutationResolver) CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error) {
|
||||
r.MustBeAuthorized(ctx, input.VendorID, authz.ActionCreateVendorRiskAssessment)
|
||||
|
||||
Reference in New Issue
Block a user