Add processing activity exports

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-12-22 17:36:50 +01:00
parent 2a49c1e627
commit c76764b69a
29 changed files with 4021 additions and 226 deletions

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;