Add new gdpr registries

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-12-16 16:56:33 +01:00
parent 9084d74b06
commit 91b42c6cd1
44 changed files with 12700 additions and 463 deletions

View File

@@ -14,10 +14,12 @@ import {
IconTrashCan,
Table,
useConfirm,
Tabs,
TabItem,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { usePageTitle } from "@probo/hooks";
import { getLawfulBasisLabel } from "../../../components/form/ProcessingActivityEnumOptions";
import { getLawfulBasisLabel, getResidualRiskLabel } from "../../../components/form/ProcessingActivityEnumOptions";
import {
ConnectionHandler,
graphql,
@@ -37,8 +39,16 @@ import type {
ProcessingActivitiesPageFragment$key,
ProcessingActivitiesPageFragment$data,
} from "./__generated__/ProcessingActivitiesPageFragment.graphql";
import type {
ProcessingActivitiesPageDPIAFragment$key,
ProcessingActivitiesPageDPIAFragment$data,
} from "./__generated__/ProcessingActivitiesPageDPIAFragment.graphql";
import type {
ProcessingActivitiesPageTIAFragment$key,
ProcessingActivitiesPageTIAFragment$data,
} from "./__generated__/ProcessingActivitiesPageTIAFragment.graphql";
import { PermissionsContext } from "/providers/PermissionsContext";
import { use } from "react";
import { use, useState } from "react";
import type { ProcessingActivityGraphListQuery } from "/hooks/graph/__generated__/ProcessingActivityGraphListQuery.graphql";
interface ProcessingActivitiesPageProps {
@@ -86,12 +96,87 @@ const processingActivitiesPageFragment = graphql`
}
`;
const dpiaListPageFragment = graphql`
fragment ProcessingActivitiesPageDPIAFragment on Organization
@refetchable(queryName: "ProcessingActivitiesPageDPIARefetchQuery")
@argumentDefinitions(
first: { type: "Int", defaultValue: 10 }
after: { type: "CursorKey" }
) {
id
dataProtectionImpactAssessments(
first: $first
after: $after
)
@connection(key: "ProcessingActivitiesPage_dataProtectionImpactAssessments") {
__id
totalCount
edges {
node {
id
description
potentialRisk
residualRisk
processingActivity {
id
name
}
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
const tiaListPageFragment = graphql`
fragment ProcessingActivitiesPageTIAFragment on Organization
@refetchable(queryName: "ProcessingActivitiesPageTIARefetchQuery")
@argumentDefinitions(
first: { type: "Int", defaultValue: 10 }
after: { type: "CursorKey" }
) {
id
transferImpactAssessments(
first: $first
after: $after
)
@connection(key: "ProcessingActivitiesPage_transferImpactAssessments") {
__id
totalCount
edges {
node {
id
dataSubjects
transfer
localLawRisk
processingActivity {
id
name
}
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivitiesPageProps) {
const { __ } = useTranslate();
const organizationId = useOrganizationId();
const { snapshotId } = useParams<{ snapshotId?: string }>();
const isSnapshotMode = Boolean(snapshotId);
const { isAuthorized } = use(PermissionsContext);
const [activeTab, setActiveTab] = useState<"activities" | "dpia" | "tia">("activities");
usePageTitle(__("Processing Activities"));
@@ -101,21 +186,43 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
);
const {
data,
loadNext,
hasNext,
isLoadingNext,
data: activitiesData,
loadNext: loadNextActivities,
hasNext: hasNextActivities,
isLoadingNext: isLoadingNextActivities,
} = usePaginationFragment<
ProcessingActivityGraphListQuery,
ProcessingActivitiesPageFragment$key
>(processingActivitiesPageFragment, organization.node);
const {
data: dpiaData,
loadNext: loadNextDPIAs,
hasNext: hasNextDPIAs,
isLoadingNext: isLoadingNextDPIAs,
} = usePaginationFragment<
ProcessingActivityGraphListQuery,
ProcessingActivitiesPageDPIAFragment$key
>(dpiaListPageFragment, organization.node);
const {
data: tiaData,
loadNext: loadNextTIAs,
hasNext: hasNextTIAs,
isLoadingNext: isLoadingNextTIAs,
} = usePaginationFragment<
ProcessingActivityGraphListQuery,
ProcessingActivitiesPageTIAFragment$key
>(tiaListPageFragment, organization.node);
const connectionId = ConnectionHandler.getConnectionID(
organizationId,
ProcessingActivitiesConnectionKey,
{ filter: { snapshotId: snapshotId || null } }
);
const activities = data?.processingActivities?.edges?.map((edge) => edge.node) ?? [];
const activities = activitiesData?.processingActivities?.edges?.map((edge) => edge.node) ?? [];
const dpias = dpiaData?.dataProtectionImpactAssessments?.edges?.map((edge) => edge.node) ?? [];
const tias = tiaData?.transferImpactAssessments?.edges?.map((edge) => edge.node) ?? [];
const hasAnyAction = !isSnapshotMode && (
isAuthorized("ProcessingActivity", "updateProcessingActivity") ||
@@ -128,7 +235,7 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
<SnapshotBanner snapshotId={snapshotId} />
)}
<PageHeader title={__("Processing Activities")} description={__("Manage your processing activities under GDPR")}>
{!isSnapshotMode && (
{!isSnapshotMode && activeTab === "activities" && (
isAuthorized("Organization", "createProcessingActivity") && (
<CreateProcessingActivityDialog
organizationId={organizationId}
@@ -142,55 +249,165 @@ export default function ProcessingActivitiesPage({ queryRef }: ProcessingActivit
)}
</PageHeader>
{activities.length > 0 ? (
<Card>
<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>}
</Tr>
</Thead>
<Tbody>
{activities.map((activity) => (
<ActivityRow
key={activity.id}
activity={activity}
connectionId={connectionId}
hasAnyAction={hasAnyAction}
/>
))}
</Tbody>
</Table>
<Tabs>
<TabItem active={activeTab === "activities"} onClick={() => setActiveTab("activities")}>
{__("Processing Activities")}
</TabItem>
<TabItem active={activeTab === "dpia"} onClick={() => setActiveTab("dpia")}>
{__("Data Protection Impact Assessments")}
</TabItem>
<TabItem active={activeTab === "tia"} onClick={() => setActiveTab("tia")}>
{__("Transfer Impact Assessments")}
</TabItem>
</Tabs>
{hasNext && (
<div className="p-4 border-t">
<Button
variant="secondary"
onClick={() => loadNext(10)}
disabled={isLoadingNext}
>
{isLoadingNext ? __("Loading...") : __("Load more")}
</Button>
</div>
{activeTab === "activities" && (
<>
{activities.length > 0 ? (
<Card>
<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>}
</Tr>
</Thead>
<Tbody>
{activities.map((activity) => (
<ActivityRow
key={activity.id}
activity={activity}
connectionId={connectionId}
hasAnyAction={hasAnyAction}
/>
))}
</Tbody>
</Table>
{hasNextActivities && (
<div className="p-4 border-t">
<Button
variant="secondary"
onClick={() => loadNextActivities(10)}
disabled={isLoadingNextActivities}
>
{isLoadingNextActivities ? __("Loading...") : __("Load more")}
</Button>
</div>
)}
</Card>
) : (
<Card padded>
<div className="text-center py-12">
<h3 className="text-lg font-semibold mb-2">
{__("No processing activities yet")}
</h3>
<p className="text-txt-tertiary mb-4">
{__("Create your first processing activity to get started with GDPR compliance.")}
</p>
</div>
</Card>
)}
</Card>
) : (
<Card padded>
<div className="text-center py-12">
<h3 className="text-lg font-semibold mb-2">
{__("No processing activities yet")}
</h3>
<p className="text-txt-tertiary mb-4">
{__("Create your first processing activity to get started with GDPR compliance.")}
</p>
</div>
</Card>
</>
)}
{activeTab === "dpia" && (
<>
{dpias.length > 0 ? (
<Card>
<Table>
<Thead>
<Tr>
<Th>{__("Processing Activity")}</Th>
<Th>{__("Description")}</Th>
<Th>{__("Potential Risk")}</Th>
<Th>{__("Residual Risk")}</Th>
</Tr>
</Thead>
<Tbody>
{dpias.map((dpia) => (
<DPIARow key={dpia.id} dpia={dpia} />
))}
</Tbody>
</Table>
{hasNextDPIAs && (
<div className="p-4 border-t">
<Button
variant="secondary"
onClick={() => loadNextDPIAs(10)}
disabled={isLoadingNextDPIAs}
>
{isLoadingNextDPIAs ? __("Loading...") : __("Load more")}
</Button>
</div>
)}
</Card>
) : (
<Card padded>
<div className="text-center py-12">
<h3 className="text-lg font-semibold mb-2">
{__("No Data Protection Impact Assessments yet")}
</h3>
<p className="text-txt-tertiary mb-4">
{__("DPIAs are created from within individual processing activities.")}
</p>
</div>
</Card>
)}
</>
)}
{activeTab === "tia" && (
<>
{tias.length > 0 ? (
<Card>
<Table>
<Thead>
<Tr>
<Th>{__("Processing Activity")}</Th>
<Th>{__("Data Subjects")}</Th>
<Th>{__("Transfer")}</Th>
<Th>{__("Local Law Risk")}</Th>
</Tr>
</Thead>
<Tbody>
{tias.map((tia) => (
<TIARow key={tia.id} tia={tia} />
))}
</Tbody>
</Table>
{hasNextTIAs && (
<div className="p-4 border-t">
<Button
variant="secondary"
onClick={() => loadNextTIAs(10)}
disabled={isLoadingNextTIAs}
>
{isLoadingNextTIAs ? __("Loading...") : __("Load more")}
</Button>
</div>
)}
</Card>
) : (
<Card padded>
<div className="text-center py-12">
<h3 className="text-lg font-semibold mb-2">
{__("No Transfer Impact Assessments yet")}
</h3>
<p className="text-txt-tertiary mb-4">
{__("TIAs are created from within individual processing activities.")}
</p>
</div>
</Card>
)}
</>
)}
</div>
);
@@ -275,3 +492,80 @@ function ActivityRow({
</Tr>
);
}
function DPIARow({
dpia,
}: {
dpia: NodeOf<NonNullable<ProcessingActivitiesPageDPIAFragment$data['dataProtectionImpactAssessments']>>;
}) {
const organizationId = useOrganizationId();
const { __ } = useTranslate();
const { snapshotId } = useParams<{ snapshotId?: string }>();
const isSnapshotMode = Boolean(snapshotId);
const activityUrl = isSnapshotMode && snapshotId
? `/organizations/${organizationId}/snapshots/${snapshotId}/processing-activities/${dpia.processingActivity.id}#dpia`
: `/organizations/${organizationId}/processing-activities/${dpia.processingActivity.id}#dpia`;
return (
<Tr to={activityUrl}>
<Td>
<span className="font-semibold">{dpia.processingActivity.name}</span>
</Td>
<Td>
<span className="text-sm text-txt-secondary line-clamp-2">
{dpia.description || "-"}
</span>
</Td>
<Td>
<span className="text-sm text-txt-secondary line-clamp-2">
{dpia.potentialRisk || "-"}
</span>
</Td>
<Td>
{dpia.residualRisk ? (
<Badge variant={dpia.residualRisk === "LOW" ? "success" : dpia.residualRisk === "MEDIUM" ? "warning" : "danger"}>
{getResidualRiskLabel(dpia.residualRisk, __)}
</Badge>
) : "-"}
</Td>
</Tr>
);
}
function TIARow({
tia,
}: {
tia: NodeOf<NonNullable<ProcessingActivitiesPageTIAFragment$data['transferImpactAssessments']>>;
}) {
const organizationId = useOrganizationId();
const { snapshotId } = useParams<{ snapshotId?: string }>();
const isSnapshotMode = Boolean(snapshotId);
const activityUrl = isSnapshotMode && snapshotId
? `/organizations/${organizationId}/snapshots/${snapshotId}/processing-activities/${tia.processingActivity.id}#tia`
: `/organizations/${organizationId}/processing-activities/${tia.processingActivity.id}#tia`;
return (
<Tr to={activityUrl}>
<Td>
<span className="font-semibold">{tia.processingActivity.name}</span>
</Td>
<Td>
<span className="text-sm text-txt-secondary line-clamp-2">
{tia.dataSubjects || "-"}
</span>
</Td>
<Td>
<span className="text-sm text-txt-secondary line-clamp-2">
{tia.transfer || "-"}
</span>
</Td>
<Td>
<span className="text-sm text-txt-secondary line-clamp-2">
{tia.localLawRisk || "-"}
</span>
</Td>
</Tr>
);
}

View File

@@ -0,0 +1,257 @@
/**
* @generated SignedSource<<eaa19415d42ca227134bd17febaa0a89>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type ProcessingActivityDPIAResidualRisk = "HIGH" | "LOW" | "MEDIUM";
import { FragmentRefs } from "relay-runtime";
export type ProcessingActivitiesPageDPIAFragment$data = {
readonly dataProtectionImpactAssessments: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: any;
readonly description: string | null | undefined;
readonly id: string;
readonly potentialRisk: string | null | undefined;
readonly processingActivity: {
readonly id: string;
readonly name: string;
};
readonly residualRisk: ProcessingActivityDPIAResidualRisk | null | undefined;
readonly updatedAt: any;
};
}>;
readonly pageInfo: {
readonly endCursor: any | null | undefined;
readonly hasNextPage: boolean;
};
readonly totalCount: number;
};
readonly id: string;
readonly " $fragmentType": "ProcessingActivitiesPageDPIAFragment";
};
export type ProcessingActivitiesPageDPIAFragment$key = {
readonly " $data"?: ProcessingActivitiesPageDPIAFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"ProcessingActivitiesPageDPIAFragment">;
};
import ProcessingActivitiesPageDPIARefetchQuery_graphql from './ProcessingActivitiesPageDPIARefetchQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"dataProtectionImpactAssessments"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": 10,
"kind": "LocalArgument",
"name": "first"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "first",
"cursor": "after",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "first",
"cursor": "after"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": ProcessingActivitiesPageDPIARefetchQuery_graphql,
"identifierInfo": {
"identifierField": "id",
"identifierQueryVariableName": "id"
}
}
},
"name": "ProcessingActivitiesPageDPIAFragment",
"selections": [
(v1/*: any*/),
{
"alias": "dataProtectionImpactAssessments",
"args": null,
"concreteType": "ProcessingActivityDPIAConnection",
"kind": "LinkedField",
"name": "__ProcessingActivitiesPage_dataProtectionImpactAssessments_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivityDPIAEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivityDPIA",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "potentialRisk",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualRisk",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivity",
"kind": "LinkedField",
"name": "processingActivity",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
};
})();
(node as any).hash = "e49266b9c7b8a8b00dc43813ce499f67";
export default node;

View File

@@ -0,0 +1,296 @@
/**
* @generated SignedSource<<026a23e95a9115022526fa5d6f0cb999>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type ProcessingActivitiesPageDPIARefetchQuery$variables = {
after?: any | null | undefined;
first?: number | null | undefined;
id: string;
};
export type ProcessingActivitiesPageDPIARefetchQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"ProcessingActivitiesPageDPIAFragment">;
};
};
export type ProcessingActivitiesPageDPIARefetchQuery = {
response: ProcessingActivitiesPageDPIARefetchQuery$data;
variables: ProcessingActivitiesPageDPIARefetchQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": 10,
"kind": "LocalArgument",
"name": "first"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = [
{
"kind": "Variable",
"name": "after",
"variableName": "after"
},
{
"kind": "Variable",
"name": "first",
"variableName": "first"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ProcessingActivitiesPageDPIARefetchQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": (v2/*: any*/),
"kind": "FragmentSpread",
"name": "ProcessingActivitiesPageDPIAFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ProcessingActivitiesPageDPIARefetchQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "ProcessingActivityDPIAConnection",
"kind": "LinkedField",
"name": "dataProtectionImpactAssessments",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivityDPIAEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivityDPIA",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "potentialRisk",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualRisk",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivity",
"kind": "LinkedField",
"name": "processingActivity",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
(v3/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"filters": null,
"handle": "connection",
"key": "ProcessingActivitiesPage_dataProtectionImpactAssessments",
"kind": "LinkedHandle",
"name": "dataProtectionImpactAssessments"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "8e3896e68907df53052a3418bf646240",
"id": null,
"metadata": {},
"name": "ProcessingActivitiesPageDPIARefetchQuery",
"operationKind": "query",
"text": "query ProcessingActivitiesPageDPIARefetchQuery(\n $after: CursorKey\n $first: Int = 10\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...ProcessingActivitiesPageDPIAFragment_2HEEH6\n id\n }\n}\n\nfragment ProcessingActivitiesPageDPIAFragment_2HEEH6 on Organization {\n id\n dataProtectionImpactAssessments(first: $first, after: $after) {\n totalCount\n edges {\n node {\n id\n description\n potentialRisk\n residualRisk\n processingActivity {\n id\n name\n }\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n}\n"
}
};
})();
(node as any).hash = "e49266b9c7b8a8b00dc43813ce499f67";
export default node;

View File

@@ -0,0 +1,256 @@
/**
* @generated SignedSource<<7f0d0a852bdb0ca1fdd8a49252654563>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type ProcessingActivitiesPageTIAFragment$data = {
readonly id: string;
readonly transferImpactAssessments: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: any;
readonly dataSubjects: string | null | undefined;
readonly id: string;
readonly localLawRisk: string | null | undefined;
readonly processingActivity: {
readonly id: string;
readonly name: string;
};
readonly transfer: string | null | undefined;
readonly updatedAt: any;
};
}>;
readonly pageInfo: {
readonly endCursor: any | null | undefined;
readonly hasNextPage: boolean;
};
readonly totalCount: number;
};
readonly " $fragmentType": "ProcessingActivitiesPageTIAFragment";
};
export type ProcessingActivitiesPageTIAFragment$key = {
readonly " $data"?: ProcessingActivitiesPageTIAFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"ProcessingActivitiesPageTIAFragment">;
};
import ProcessingActivitiesPageTIARefetchQuery_graphql from './ProcessingActivitiesPageTIARefetchQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"transferImpactAssessments"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": 10,
"kind": "LocalArgument",
"name": "first"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "first",
"cursor": "after",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "first",
"cursor": "after"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": ProcessingActivitiesPageTIARefetchQuery_graphql,
"identifierInfo": {
"identifierField": "id",
"identifierQueryVariableName": "id"
}
}
},
"name": "ProcessingActivitiesPageTIAFragment",
"selections": [
(v1/*: any*/),
{
"alias": "transferImpactAssessments",
"args": null,
"concreteType": "ProcessingActivityTIAConnection",
"kind": "LinkedField",
"name": "__ProcessingActivitiesPage_transferImpactAssessments_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivityTIAEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivityTIA",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "dataSubjects",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "transfer",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "localLawRisk",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivity",
"kind": "LinkedField",
"name": "processingActivity",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
};
})();
(node as any).hash = "0262ad5df883e750870f63e6d18d2c93";
export default node;

View File

@@ -0,0 +1,296 @@
/**
* @generated SignedSource<<fa37df5257783c56111f7d5a8838b759>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type ProcessingActivitiesPageTIARefetchQuery$variables = {
after?: any | null | undefined;
first?: number | null | undefined;
id: string;
};
export type ProcessingActivitiesPageTIARefetchQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"ProcessingActivitiesPageTIAFragment">;
};
};
export type ProcessingActivitiesPageTIARefetchQuery = {
response: ProcessingActivitiesPageTIARefetchQuery$data;
variables: ProcessingActivitiesPageTIARefetchQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": 10,
"kind": "LocalArgument",
"name": "first"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = [
{
"kind": "Variable",
"name": "after",
"variableName": "after"
},
{
"kind": "Variable",
"name": "first",
"variableName": "first"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ProcessingActivitiesPageTIARefetchQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": (v2/*: any*/),
"kind": "FragmentSpread",
"name": "ProcessingActivitiesPageTIAFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ProcessingActivitiesPageTIARefetchQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "ProcessingActivityTIAConnection",
"kind": "LinkedField",
"name": "transferImpactAssessments",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivityTIAEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivityTIA",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "dataSubjects",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "transfer",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "localLawRisk",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ProcessingActivity",
"kind": "LinkedField",
"name": "processingActivity",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
(v3/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"filters": null,
"handle": "connection",
"key": "ProcessingActivitiesPage_transferImpactAssessments",
"kind": "LinkedHandle",
"name": "transferImpactAssessments"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "339dd59f375bcb83665bb36d05950916",
"id": null,
"metadata": {},
"name": "ProcessingActivitiesPageTIARefetchQuery",
"operationKind": "query",
"text": "query ProcessingActivitiesPageTIARefetchQuery(\n $after: CursorKey\n $first: Int = 10\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...ProcessingActivitiesPageTIAFragment_2HEEH6\n id\n }\n}\n\nfragment ProcessingActivitiesPageTIAFragment_2HEEH6 on Organization {\n id\n transferImpactAssessments(first: $first, after: $after) {\n totalCount\n edges {\n node {\n id\n dataSubjects\n transfer\n localLawRisk\n processingActivity {\n id\n name\n }\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n}\n"
}
};
})();
(node as any).hash = "0262ad5df883e750870f63e6d18d2c93";
export default node;

View File

@@ -12,6 +12,7 @@ import {
Label,
Checkbox,
Select,
Input,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { z } from "zod";
@@ -19,13 +20,15 @@ import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { useCreateProcessingActivity } from "../../../../hooks/graph/ProcessingActivityGraph";
import { Controller } from "react-hook-form";
import { VendorsMultiSelectField } from "/components/form/VendorsMultiSelectField";
import { formatError, type GraphQLError } from "@probo/helpers";
import { PeopleSelectField } from "/components/form/PeopleSelectField";
import { formatError, formatDatetime, type GraphQLError } from "@probo/helpers";
import {
SpecialOrCriminalDataOptions,
LawfulBasisOptions,
TransferSafeguardsOptions,
DataProtectionImpactAssessmentOptions,
TransferImpactAssessmentOptions,
RoleOptions,
} from "../../../../components/form/ProcessingActivityEnumOptions";
const schema = z.object({
@@ -44,6 +47,10 @@ const schema = z.object({
securityMeasures: z.string().optional(),
dataProtectionImpactAssessment: z.enum(["NEEDED", "NOT_NEEDED"] as const),
transferImpactAssessment: z.enum(["NEEDED", "NOT_NEEDED"] as const),
lastReviewDate: z.string().optional(),
nextReviewDate: z.string().optional(),
role: z.enum(["CONTROLLER", "PROCESSOR"] as const),
dataProtectionOfficerId: z.string().optional(),
vendorIds: z.array(z.string()).optional(),
});
@@ -83,6 +90,10 @@ export function CreateProcessingActivityDialog({
securityMeasures: "",
dataProtectionImpactAssessment: "NOT_NEEDED" as const,
transferImpactAssessment: "NOT_NEEDED" as const,
lastReviewDate: "",
nextReviewDate: "",
role: "PROCESSOR" as const,
dataProtectionOfficerId: "",
vendorIds: [],
},
});
@@ -106,6 +117,10 @@ export function CreateProcessingActivityDialog({
securityMeasures: formData.securityMeasures || undefined,
dataProtectionImpactAssessment: formData.dataProtectionImpactAssessment || undefined,
transferImpactAssessment: formData.transferImpactAssessment || undefined,
lastReviewDate: formatDatetime(formData.lastReviewDate),
nextReviewDate: formatDatetime(formData.nextReviewDate),
role: formData.role,
dataProtectionOfficerId: formData.dataProtectionOfficerId || undefined,
vendorIds: formData.vendorIds,
});
@@ -145,6 +160,28 @@ export function CreateProcessingActivityDialog({
required
/>
<div>
<Label htmlFor="role">{__("Role")}</Label>
<Controller
control={control}
name="role"
render={({ field }) => (
<Select
id="role"
placeholder={__("Select role")}
onValueChange={field.onChange}
value={field.value}
className="w-full"
>
<RoleOptions />
</Select>
)}
/>
{formState.errors.role && (
<p className="text-sm text-txt-danger mt-1">{formState.errors.role.message}</p>
)}
</div>
<div>
<Label>{__("Purpose")}</Label>
<Textarea
@@ -218,6 +255,31 @@ export function CreateProcessingActivityDialog({
<p className="text-sm text-txt-danger mt-1">{formState.errors.lawfulBasis.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="lastReviewDate">{__("Last Review Date")}</Label>
<Input
id="lastReviewDate"
type="date"
{...register("lastReviewDate")}
/>
</div>
<div className="space-y-2">
<Label htmlFor="nextReviewDate">{__("Next Review Date")}</Label>
<Input
id="nextReviewDate"
type="date"
{...register("nextReviewDate")}
/>
</div>
<PeopleSelectField
organizationId={organizationId}
control={control}
name="dataProtectionOfficerId"
label={__("Data Protection Officer")}
/>
</div>
<div className="space-y-4">