727 lines
20 KiB
TypeScript
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.",
|
|
),
|
|
},
|
|
);
|
|
};
|
|
};
|