Files
probo/apps/console/src/hooks/graph/ProcessingActivityGraph.ts
Sacha Al Himdani 9ac71f948f Update contact email to hello@probo.com
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2026-06-09 16:45:23 +02:00

727 lines
20 KiB
TypeScript

// Copyright (c) 2025-2026 Probo Inc <hello@probo.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.
import { promisifyMutation, sprintf } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import { useConfirm } from "@probo/ui";
import { useMutation } from "react-relay";
import { graphql } from "relay-runtime";
import { useMutationWithToasts } from "../useMutationWithToasts";
/* eslint-disable relay/unused-fields, relay/must-colocate-fragment-spreads */
export const ProcessingActivitiesConnectionKey
= "ProcessingActivitiesPage_processingActivities";
export type ProcessingActivityDPIAResidualRisk = "LOW" | "MEDIUM" | "HIGH";
export const processingActivitiesQuery = graphql`
query ProcessingActivityGraphListQuery(
$organizationId: ID!
) {
node(id: $organizationId) {
... on Organization {
canCreateProcessingActivity: permission(
action: "core:processing-activity:create"
)
canPublishProcessingActivities: permission(
action: "core:processing-activity:publish"
)
canPublishDataProtectionImpactAssessments: permission(
action: "core:data-protection-impact-assessment:publish"
)
canPublishTransferImpactAssessments: permission(
action: "core:transfer-impact-assessment:publish"
)
processingActivitiesDocument {
id
defaultApprovers {
id
}
}
dataProtectionImpactAssessmentsDocument {
id
defaultApprovers {
id
}
}
transferImpactAssessmentsDocument {
id
defaultApprovers {
id
}
}
...ProcessingActivitiesPageFragment
...ProcessingActivitiesPageDPIAFragment
...ProcessingActivitiesPageTIAFragment
}
}
}
`;
export const processingActivityNodeQuery = graphql`
query ProcessingActivityGraphNodeQuery($processingActivityId: ID!) {
node(id: $processingActivityId) {
... on ProcessingActivity {
id
name
purpose
dataSubjectCategory
personalDataCategory
specialOrCriminalData
consentEvidenceLink
lawfulBasis
recipients
location
internationalTransfers
transferSafeguards
retentionPeriod
securityMeasures
dataProtectionImpactAssessmentNeeded
transferImpactAssessmentNeeded
lastReviewDate
nextReviewDate
role
dataProtectionOfficer {
id
fullName
}
thirdParties(first: 50) {
edges {
node {
id
name
websiteUrl
category
}
}
}
dataProtectionImpactAssessment {
id
description
necessityAndProportionality
potentialRisk
mitigations
residualRisk
createdAt
updatedAt
canUpdate: permission(
action: "core:data-protection-impact-assessment:update"
)
canDelete: permission(
action: "core:data-protection-impact-assessment:delete"
)
}
transferImpactAssessment {
id
dataSubjects
legalMechanism
transfer
localLawRisk
supplementaryMeasures
createdAt
updatedAt
canUpdate: permission(
action: "core:transfer-impact-assessment:update"
)
canDelete: permission(
action: "core:transfer-impact-assessment:delete"
)
}
organization {
id
name
}
createdAt
updatedAt
canCreateDPIA: permission(
action: "core:data-protection-impact-assessment:create"
)
canCreateTIA: permission(
action: "core:transfer-impact-assessment:create"
)
canUpdate: permission(action: "core:processing-activity:update")
canDelete: permission(action: "core:processing-activity:delete")
}
}
}
`;
export const createProcessingActivityMutation = graphql`
mutation ProcessingActivityGraphCreateMutation(
$input: CreateProcessingActivityInput!
$connections: [ID!]!
) {
createProcessingActivity(input: $input) {
processingActivityEdge @prependEdge(connections: $connections) {
node {
id
name
purpose
dataSubjectCategory
personalDataCategory
specialOrCriminalData
consentEvidenceLink
lawfulBasis
recipients
location
internationalTransfers
transferSafeguards
retentionPeriod
securityMeasures
dataProtectionImpactAssessmentNeeded
transferImpactAssessmentNeeded
lastReviewDate
nextReviewDate
role
dataProtectionOfficer {
id
fullName
}
thirdParties(first: 50) {
edges {
node {
id
name
websiteUrl
}
}
}
createdAt
canUpdate: permission(action: "core:processing-activity:update")
canDelete: permission(action: "core:processing-activity:delete")
}
}
}
}
`;
export const updateProcessingActivityMutation = graphql`
mutation ProcessingActivityGraphUpdateMutation(
$input: UpdateProcessingActivityInput!
) {
updateProcessingActivity(input: $input) {
processingActivity {
id
name
purpose
dataSubjectCategory
personalDataCategory
specialOrCriminalData
consentEvidenceLink
lawfulBasis
recipients
location
internationalTransfers
transferSafeguards
retentionPeriod
securityMeasures
dataProtectionImpactAssessmentNeeded
transferImpactAssessmentNeeded
lastReviewDate
nextReviewDate
role
dataProtectionOfficer {
id
fullName
}
thirdParties(first: 50) {
edges {
node {
id
name
websiteUrl
}
}
}
updatedAt
}
}
}
`;
export const deleteProcessingActivityMutation = graphql`
mutation ProcessingActivityGraphDeleteMutation(
$input: DeleteProcessingActivityInput!
$connections: [ID!]!
) {
deleteProcessingActivity(input: $input) {
deletedProcessingActivityId @deleteEdge(connections: $connections)
}
}
`;
export const useDeleteProcessingActivity = (
processingActivity: { id: string; name: string },
connectionId: string,
) => {
const { __ } = useTranslate();
const [mutate] = useMutationWithToasts(deleteProcessingActivityMutation, {
successMessage: __("Processing activity deleted successfully"),
errorMessage: __("Failed to delete processing activity"),
});
const confirm = useConfirm();
return () => {
confirm(
() =>
mutate({
variables: {
input: {
processingActivityId: processingActivity.id,
},
connections: [connectionId],
},
}),
{
message: sprintf(
__(
"This will permanently delete the processing activity %s. This action cannot be undone.",
),
processingActivity.name,
),
},
);
};
};
export const useCreateProcessingActivity = (connectionId?: string) => {
// eslint-disable-next-line relay/generated-typescript-types
const [mutate] = useMutation(createProcessingActivityMutation);
const { __ } = useTranslate();
return (input: {
organizationId: string;
name: string;
purpose?: string;
dataSubjectCategory?: string;
personalDataCategory?: string;
specialOrCriminalData?: string;
consentEvidenceLink?: string;
lawfulBasis?: string;
recipients?: string;
location?: string;
internationalTransfers: boolean;
transferSafeguards?: string;
retentionPeriod?: string;
securityMeasures?: string;
dataProtectionImpactAssessmentNeeded?: string;
transferImpactAssessmentNeeded?: string;
lastReviewDate?: string;
nextReviewDate?: string;
role: string;
dataProtectionOfficerId?: string;
thirdPartyIds?: string[];
}) => {
if (!input.organizationId) {
return alert(
__("Failed to create processing activity: organization is required"),
);
}
if (!input.name) {
return alert(
__("Failed to create processing activity: name is required"),
);
}
return promisifyMutation(mutate)({
variables: {
input: {
organizationId: input.organizationId,
name: input.name,
purpose: input.purpose,
dataSubjectCategory: input.dataSubjectCategory,
personalDataCategory: input.personalDataCategory,
specialOrCriminalData: input.specialOrCriminalData,
consentEvidenceLink: input.consentEvidenceLink,
lawfulBasis: input.lawfulBasis,
recipients: input.recipients,
location: input.location,
internationalTransfers: input.internationalTransfers,
transferSafeguards: input.transferSafeguards,
retentionPeriod: input.retentionPeriod,
securityMeasures: input.securityMeasures,
dataProtectionImpactAssessmentNeeded:
input.dataProtectionImpactAssessmentNeeded,
transferImpactAssessmentNeeded: input.transferImpactAssessmentNeeded,
lastReviewDate: input.lastReviewDate,
nextReviewDate: input.nextReviewDate,
role: input.role,
dataProtectionOfficerId: input.dataProtectionOfficerId,
thirdPartyIds: input.thirdPartyIds,
},
connections: connectionId ? [connectionId] : [],
},
});
};
};
export const useUpdateProcessingActivity = () => {
// eslint-disable-next-line relay/generated-typescript-types
const [mutate] = useMutation(updateProcessingActivityMutation);
const { __ } = useTranslate();
return (input: {
id: string;
name?: string;
purpose?: string;
dataSubjectCategory?: string;
personalDataCategory?: string;
specialOrCriminalData?: string;
consentEvidenceLink?: string;
lawfulBasis?: string;
recipients?: string;
location?: string;
internationalTransfers?: boolean;
transferSafeguards?: string;
retentionPeriod?: string;
securityMeasures?: string;
dataProtectionImpactAssessmentNeeded?: string;
transferImpactAssessmentNeeded?: string;
lastReviewDate?: string | null;
nextReviewDate?: string | null;
role?: string;
dataProtectionOfficerId?: string | null;
thirdPartyIds?: string[];
}) => {
if (!input.id) {
return alert(__("Failed to update processing activity: ID is required"));
}
return promisifyMutation(mutate)({
variables: {
input,
},
});
};
};
export const createDataProtectionImpactAssessmentMutation = graphql`
mutation ProcessingActivityGraphCreateDPIAMutation(
$input: CreateDataProtectionImpactAssessmentInput!
) {
createDataProtectionImpactAssessment(input: $input) {
dataProtectionImpactAssessment {
id
description
necessityAndProportionality
potentialRisk
mitigations
residualRisk
createdAt
updatedAt
canUpdate: permission(
action: "core:data-protection-impact-assessment:update"
)
canDelete: permission(
action: "core:data-protection-impact-assessment:delete"
)
processingActivity {
id
dataProtectionImpactAssessment {
id
description
necessityAndProportionality
potentialRisk
mitigations
residualRisk
createdAt
updatedAt
canUpdate: permission(
action: "core:data-protection-impact-assessment:update"
)
canDelete: permission(
action: "core:data-protection-impact-assessment:delete"
)
}
}
}
}
}
`;
export const updateDataProtectionImpactAssessmentMutation = graphql`
mutation ProcessingActivityGraphUpdateDPIAMutation(
$input: UpdateDataProtectionImpactAssessmentInput!
) {
updateDataProtectionImpactAssessment(input: $input) {
dataProtectionImpactAssessment {
id
description
necessityAndProportionality
potentialRisk
mitigations
residualRisk
createdAt
updatedAt
}
}
}
`;
export const deleteDataProtectionImpactAssessmentMutation = graphql`
mutation ProcessingActivityGraphDeleteDPIAMutation(
$input: DeleteDataProtectionImpactAssessmentInput!
) {
deleteDataProtectionImpactAssessment(input: $input) {
deletedDataProtectionImpactAssessmentId
}
}
`;
export const useCreateDataProtectionImpactAssessment = () => {
// eslint-disable-next-line relay/generated-typescript-types
const [mutate] = useMutation(createDataProtectionImpactAssessmentMutation);
const { __ } = useTranslate();
return (input: {
processingActivityId: string;
description?: string;
necessityAndProportionality?: string;
potentialRisk?: string;
mitigations?: string;
residualRisk?: ProcessingActivityDPIAResidualRisk;
}) => {
if (!input.processingActivityId) {
return alert(
__("Failed to create DPIA: Processing Activity ID is required"),
);
}
return promisifyMutation(mutate)({
variables: {
input,
},
});
};
};
export const useUpdateDataProtectionImpactAssessment = () => {
// eslint-disable-next-line relay/generated-typescript-types
const [mutate] = useMutation(updateDataProtectionImpactAssessmentMutation);
const { __ } = useTranslate();
return (input: {
id: string;
description?: string;
necessityAndProportionality?: string;
potentialRisk?: string;
mitigations?: string;
residualRisk?: ProcessingActivityDPIAResidualRisk;
}) => {
if (!input.id) {
return alert(__("Failed to update DPIA: ID is required"));
}
return promisifyMutation(mutate)({
variables: {
input,
},
});
};
};
export const useDeleteDataProtectionImpactAssessment = (
dpia: { id: string },
options?: { onSuccess?: () => void },
) => {
const { __ } = useTranslate();
const [mutate] = useMutationWithToasts(
deleteDataProtectionImpactAssessmentMutation,
{
successMessage: __("DPIA deleted successfully"),
errorMessage: __("Failed to delete DPIA"),
},
);
const confirm = useConfirm();
return () => {
confirm(
() =>
mutate({
variables: {
input: {
dataProtectionImpactAssessmentId: dpia.id,
},
},
onSuccess: options?.onSuccess,
}),
{
message: __(
"This will permanently delete this Data Protection Impact Assessment. This action cannot be undone.",
),
},
);
};
};
export const createTransferImpactAssessmentMutation = graphql`
mutation ProcessingActivityGraphCreateTIAMutation(
$input: CreateTransferImpactAssessmentInput!
) {
createTransferImpactAssessment(input: $input) {
transferImpactAssessment {
id
dataSubjects
legalMechanism
transfer
localLawRisk
supplementaryMeasures
createdAt
updatedAt
canUpdate: permission(
action: "core:transfer-impact-assessment:update"
)
canDelete: permission(
action: "core:transfer-impact-assessment:delete"
)
processingActivity {
id
transferImpactAssessment {
id
dataSubjects
legalMechanism
transfer
localLawRisk
supplementaryMeasures
createdAt
updatedAt
canUpdate: permission(
action: "core:transfer-impact-assessment:update"
)
canDelete: permission(
action: "core:transfer-impact-assessment:delete"
)
}
}
}
}
}
`;
export const updateTransferImpactAssessmentMutation = graphql`
mutation ProcessingActivityGraphUpdateTIAMutation(
$input: UpdateTransferImpactAssessmentInput!
) {
updateTransferImpactAssessment(input: $input) {
transferImpactAssessment {
id
dataSubjects
legalMechanism
transfer
localLawRisk
supplementaryMeasures
createdAt
updatedAt
}
}
}
`;
export const deleteTransferImpactAssessmentMutation = graphql`
mutation ProcessingActivityGraphDeleteTIAMutation(
$input: DeleteTransferImpactAssessmentInput!
) {
deleteTransferImpactAssessment(input: $input) {
deletedTransferImpactAssessmentId
}
}
`;
export const useCreateTransferImpactAssessment = () => {
// eslint-disable-next-line relay/generated-typescript-types
const [mutate] = useMutation(createTransferImpactAssessmentMutation);
const { __ } = useTranslate();
return (input: {
processingActivityId: string;
dataSubjects?: string;
legalMechanism?: string;
transfer?: string;
localLawRisk?: string;
supplementaryMeasures?: string;
}) => {
if (!input.processingActivityId) {
return alert(
__("Failed to create TIA: Processing Activity ID is required"),
);
}
return promisifyMutation(mutate)({
variables: {
input,
},
});
};
};
export const useUpdateTransferImpactAssessment = () => {
// eslint-disable-next-line relay/generated-typescript-types
const [mutate] = useMutation(updateTransferImpactAssessmentMutation);
const { __ } = useTranslate();
return (input: {
id: string;
dataSubjects?: string;
legalMechanism?: string;
transfer?: string;
localLawRisk?: string;
supplementaryMeasures?: string;
}) => {
if (!input.id) {
return alert(__("Failed to update TIA: ID is required"));
}
return promisifyMutation(mutate)({
variables: {
input,
},
});
};
};
export const useDeleteTransferImpactAssessment = (
tia: { id: string },
options?: { onSuccess?: () => void },
) => {
const { __ } = useTranslate();
const [mutate] = useMutationWithToasts(
deleteTransferImpactAssessmentMutation,
{
successMessage: __("TIA deleted successfully"),
errorMessage: __("Failed to delete TIA"),
},
);
const confirm = useConfirm();
return () => {
confirm(
() =>
mutate({
variables: {
input: {
transferImpactAssessmentId: tia.id,
},
},
onSuccess: options?.onSuccess,
}),
{
message: __(
"This will permanently delete this Transfer Impact Assessment. This action cannot be undone.",
),
},
);
};
};