Skip to content
This repository was archived by the owner on Mar 27, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions examples/consent-workflow-triggers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
consent-workflow-triggers:
- name: Erasure on opt-out
action-type: ERASURE
data-subject-type: Customer
is-silent: true
allow-unauthenticated: false
is-active: true
purposes:
- tracking-type: Advertising
matching-state: false
- name: Access on request
action-type: ACCESS
data-subject-type: Customer
is-silent: false
allow-unauthenticated: false
is-active: true
purposes:
- tracking-type: Analytics
matching-state: true
56 changes: 56 additions & 0 deletions src/codecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1968,6 +1968,54 @@ export type SiloDiscoveryResultInput = t.TypeOf<
typeof SiloDiscoveryResultInput
>;

/**
* Input for a purpose associated with a consent workflow trigger
*/
export const ConsentWorkflowTriggerPurposeInput = t.type({
/** The tracking type slug of the purpose */
'tracking-type': t.string,
/** The matching consent state for the purpose */
'matching-state': t.boolean,
});

/** Type override */
export type ConsentWorkflowTriggerPurposeInput = t.TypeOf<
typeof ConsentWorkflowTriggerPurposeInput
>;

/**
* Input to define a consent workflow trigger
*/
export const ConsentWorkflowTriggerInput = t.intersection([
t.type({
/** The name of the consent workflow trigger */
name: t.string,
}),
t.partial({
/** The trigger condition as a JSON string */
'trigger-condition': t.string,
/** The action type (e.g. ERASURE, ACCESS) */
'action-type': t.string,
/** The data subject type */
'data-subject-type': t.string,
/** Whether the trigger runs silently */
'is-silent': t.boolean,
/** Whether unauthenticated requests are allowed */
'allow-unauthenticated': t.boolean,
/** Whether the trigger is active */
'is-active': t.boolean,
/** Titles of data silos associated with this trigger */
'data-silo-titles': t.array(t.string),
/** Purposes and their matching consent states */
purposes: t.array(ConsentWorkflowTriggerPurposeInput),
}),
]);

/** Type override */
export type ConsentWorkflowTriggerInput = t.TypeOf<
typeof ConsentWorkflowTriggerInput
>;

export const TranscendInput = t.partial({
/**
* Action items
Expand Down Expand Up @@ -2097,6 +2145,14 @@ export const TranscendInput = t.partial({
* The full list of silo discovery results
*/
'system-discovery': t.array(SiloDiscoveryResultInput),
/**
* Consent workflow trigger definitions
*/
'consent-workflow-triggers': t.array(ConsentWorkflowTriggerInput),
/**
* Preference management options for multi and single selects
*/
'preference-options': t.array(ConsentPreferenceTopicOptionValue),
});

/** Type override */
Expand Down
16 changes: 16 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,15 @@ export const TR_PUSH_RESOURCE_SCOPE_MAP: {
ScopeName.ManageConsentManager,
ScopeName.ManagePreferenceStoreSettings,
],
[TranscendPullResource.PreferenceOptions]: [
ScopeName.ManagePreferenceStoreSettings,
],
[TranscendPullResource.SystemDiscovery]: [ScopeName.ManageDataMap],
[TranscendPullResource.ConsentWorkflowTriggers]: [
ScopeName.ManageConsentManager,
ScopeName.ViewDataSubjectRequestSettings,
ScopeName.ViewConsentManager,
],
};

/**
Expand Down Expand Up @@ -136,7 +144,13 @@ export const TR_PULL_RESOURCE_SCOPE_MAP: {
ScopeName.ViewConsentManager,
ScopeName.ViewPreferenceStoreSettings,
],
[TranscendPullResource.PreferenceOptions]: [
ScopeName.ViewPreferenceStoreSettings,
],
[TranscendPullResource.SystemDiscovery]: [ScopeName.ViewDataMap],
[TranscendPullResource.ConsentWorkflowTriggers]: [
ScopeName.ViewConsentManager,
],
};

export const TR_YML_RESOURCE_TO_FIELD_NAME: Record<
Expand Down Expand Up @@ -175,7 +189,9 @@ export const TR_YML_RESOURCE_TO_FIELD_NAME: Record<
[TranscendPullResource.Assessments]: 'assessments',
[TranscendPullResource.AssessmentTemplates]: 'assessment-templates',
[TranscendPullResource.Purposes]: 'purposes',
[TranscendPullResource.PreferenceOptions]: 'preference-options',
[TranscendPullResource.SystemDiscovery]: 'system-discovery',
[TranscendPullResource.ConsentWorkflowTriggers]: 'consent-workflow-triggers',
};

export const SCOPES_BY_TITLE = keyBy(
Expand Down
2 changes: 2 additions & 0 deletions src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export enum TranscendPullResource {
Assessments = 'assessments',
AssessmentTemplates = 'assessmentTemplates',
Purposes = 'purposes',
PreferenceOptions = 'preferenceOptions',
SystemDiscovery = 'systemDiscovery',
ConsentWorkflowTriggers = 'consentWorkflowTriggers',
}

/**
Expand Down
16 changes: 16 additions & 0 deletions src/lib/docgen/createPullResourceScopesTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,29 @@ const RESOURCE_DOCUMENTATION: Record<
(https://app.transcend.io/consent-manager/regional-experiences/purposes)',
],
},
[TranscendPullResource.PreferenceOptions]: {
description:
'Preference management options for multi and single select preference topics.',
markdownLinks: [
'[Preference Management -> Preference Topics -> Options]\
(https://app.transcend.io/preference-store/preference-topics/preference-options)',
],
},
[TranscendPullResource.SystemDiscovery]: {
description: 'System discovery results',
markdownLinks: [
'[System Discovery]\
(https://app.transcend.io/data-map/data-inventory/silo-discovery)',
],
},
[TranscendPullResource.ConsentWorkflowTriggers]: {
description:
'Consent workflow trigger definitions that automate privacy request workflows based on consent state changes.',
markdownLinks: [
'[Consent Management -> Consent Workflows]\
(https://app.transcend.io/consent-manager/consent-workflows)',
],
},
};

/**
Expand Down
71 changes: 71 additions & 0 deletions src/lib/graphql/fetchAllConsentWorkflowTriggers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { GraphQLClient } from 'graphql-request';
import { CONSENT_WORKFLOW_TRIGGERS } from './gqls';
import { makeGraphQLRequest } from './makeGraphQLRequest';

export interface ConsentWorkflowTrigger {
/** ID of the trigger */
id: string;
/** Name of the trigger */
name: string;
/** JSON string of the trigger condition */
triggerCondition: string | null;
/** Whether the trigger runs silently */
isSilent: boolean;
/** Whether unauthenticated requests are allowed */
allowUnauthenticated: boolean;
/** Whether the trigger is active */
isActive: boolean;
/** The workflow config ID */
workflowConfigId: string | null;
/** The request action associated with the trigger */
action: {
/** Action type (e.g. ERASURE, ACCESS) */
type: string;
};
/** The data subject associated with the trigger */
subject: {
/** Data subject type */
type: string;
};
/** Data silos associated with this trigger */
dataSilos: {
/** Title of data silo */
title: string;
}[];
}

const PAGE_SIZE = 20;

/**
* Fetch all consent workflow triggers in the organization
*
* @param client - GraphQL client
* @returns All consent workflow triggers in the organization
*/
export async function fetchAllConsentWorkflowTriggers(
client: GraphQLClient,
): Promise<ConsentWorkflowTrigger[]> {
const triggers: ConsentWorkflowTrigger[] = [];
let offset = 0;

let shouldContinue = false;
do {
const {
consentWorkflowTriggers: { nodes },
} = await makeGraphQLRequest<{
/** Consent workflow triggers */
consentWorkflowTriggers: {
/** List */
nodes: ConsentWorkflowTrigger[];
};
}>(client, CONSENT_WORKFLOW_TRIGGERS, {
first: PAGE_SIZE,
offset,
});
triggers.push(...nodes);
offset += PAGE_SIZE;
shouldContinue = nodes.length === PAGE_SIZE;
} while (shouldContinue);

return triggers.sort((a, b) => a.name.localeCompare(b.name));
}
53 changes: 53 additions & 0 deletions src/lib/graphql/fetchAllPreferenceOptionValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { GraphQLClient } from 'graphql-request';
import { PREFERENCE_OPTION_VALUES } from './gqls';
import { makeGraphQLRequest } from './makeGraphQLRequest';

export interface PreferenceOptionValue {
/** ID of preference option value */
id: string;
/** Slug of preference option value */
slug: string;
/** Title of preference option value */
title: {
/** ID */
id: string;
/** Default message */
defaultMessage: string;
};
}

const PAGE_SIZE = 20;

/**
* Fetch all preference option values in the organization
*
* @param client - GraphQL client
* @returns All preference option values in the organization
*/
export async function fetchAllPreferenceOptionValues(
client: GraphQLClient,
): Promise<PreferenceOptionValue[]> {
const preferenceOptionValues: PreferenceOptionValue[] = [];
let offset = 0;

let shouldContinue = false;
do {
const {
preferenceOptionValues: { nodes },
} = await makeGraphQLRequest<{
/** Preference option values */
preferenceOptionValues: {
/** List */
nodes: PreferenceOptionValue[];
};
}>(client, PREFERENCE_OPTION_VALUES, {
first: PAGE_SIZE,
offset,
});
preferenceOptionValues.push(...nodes);
offset += PAGE_SIZE;
shouldContinue = nodes.length === PAGE_SIZE;
} while (shouldContinue);

return preferenceOptionValues.sort((a, b) => a.slug.localeCompare(b.slug));
}
48 changes: 48 additions & 0 deletions src/lib/graphql/gqls/consentWorkflowTrigger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { gql } from 'graphql-request';

export const CONSENT_WORKFLOW_TRIGGERS = gql`
query TranscendCliConsentWorkflowTriggers($first: Int!, $offset: Int!) {
consentWorkflowTriggers(first: $first, offset: $offset) {
nodes {
id
name
triggerCondition
isSilent
allowUnauthenticated
isActive
workflowConfigId
action {
type
}
subject {
type
}
dataSilos {
title
}
}
totalCount
}
}
`;

export const CREATE_OR_UPDATE_CONSENT_WORKFLOW_TRIGGER = gql`
mutation TranscendCliCreateOrUpdateConsentWorkflowTrigger(
$input: CreateOrUpdateConsentWorkflowTriggerInput!
) {
createOrUpdateConsentWorkflowTrigger(input: $input) {
consentWorkflowTrigger {
id
name
}
}
}
`;

export const DELETE_CONSENT_WORKFLOW_TRIGGERS = gql`
mutation TranscendCliDeleteConsentWorkflowTriggers($ids: [ID!]!) {
deleteConsentWorkflowTriggers(ids: $ids) {
clientMutationId
}
}
`;
1 change: 1 addition & 0 deletions src/lib/graphql/gqls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ export * from './processingPurpose';
export * from './processingActivity';
export * from './sombraVersion';
export * from './siloDiscoveryResult';
export * from './consentWorkflowTrigger';
41 changes: 41 additions & 0 deletions src/lib/graphql/gqls/preferenceTopic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,44 @@ export const PREFERENCE_TOPICS = gql`
}
}
`;

export const CREATE_OR_UPDATE_PREFERENCE_OPTION_VALUES = gql`
mutation TranscendCliCreateOrUpdatePreferenceOptionValues(
$input: CreateOrUpdatePreferenceOptionValuesInput!
) {
createOrUpdatePreferenceOptionValues(input: $input) {
preferenceOptionValues {
id
slug
}
}
}
`;

export const PREFERENCE_OPTION_VALUES = gql`
query TranscendCliPreferenceOptionValues($first: Int!, $offset: Int!) {
preferenceOptionValues(first: $first, offset: $offset) {
nodes {
id
title {
id
defaultMessage
}
slug
}
}
}
`;

export const CREATE_OR_UPDATE_PREFERENCE_TOPIC = gql`
mutation TranscendCliCreateOrUpdatePreferenceTopic(
$input: CreateOrUpdatePreferenceTopicInput!
) {
createOrUpdatePreferenceTopic(input: $input) {
preferenceTopic {
id
slug
}
}
}
`;
Loading
Loading