diff --git a/.changeset/tame-years-pull.md b/.changeset/tame-years-pull.md
new file mode 100644
index 00000000..71ee8ad6
--- /dev/null
+++ b/.changeset/tame-years-pull.md
@@ -0,0 +1,7 @@
+---
+'@transcend-io/cli': minor
+---
+
+Add sync support for purposes, preference options, and consent workflow triggers.
+
+This ports the legacy CLI resource sync work into the monorepo, including new pull/push wiring, GraphQL sync helpers, and an example consent workflow trigger config.
diff --git a/packages/cli/README.md b/packages/cli/README.md
index 233e7161..c7dd8e8c 100644
--- a/packages/cli/README.md
+++ b/packages/cli/README.md
@@ -2399,7 +2399,7 @@ transcend consent delete-preference-records \
```txt
USAGE
- transcend inventory pull (--auth value) [--resources all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes|systemDiscovery] [--file value] [--transcendUrl value] [--dataSiloIds value]... [--integrationNames value]... [--trackerStatuses LIVE|NEEDS_REVIEW] [--pageSize value] [--skipDatapoints] [--skipSubDatapoints] [--includeGuessedCategories] [--debug]
+ transcend inventory pull (--auth value) [--resources all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes|preferenceOptions|systemDiscovery|consentWorkflowTriggers] [--file value] [--transcendUrl value] [--dataSiloIds value]... [--integrationNames value]... [--trackerStatuses LIVE|NEEDS_REVIEW] [--pageSize value] [--skipDatapoints] [--skipSubDatapoints] [--includeGuessedCategories] [--debug]
transcend inventory pull --help
Generates a transcend.yml by pulling the configuration from your Transcend instance.
@@ -2413,7 +2413,7 @@ This command can be helpful if you are looking to:
FLAGS
--auth The Transcend API key. The scopes required will vary depending on the operation performed. If in doubt, the Full Admin scope will always work.
- [--resources] The different resource types to pull in. Defaults to dataSilos,enrichers,templates,apiKeys. [all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes|systemDiscovery, separator = ,]
+ [--resources] The different resource types to pull in. Defaults to dataSilos,enrichers,templates,apiKeys. [all|apiKeys|customFields|templates|dataSilos|enrichers|dataFlows|businessEntities|processingActivities|actions|dataSubjects|identifiers|cookies|consentManager|partitions|prompts|promptPartials|promptGroups|agents|agentFunctions|agentFiles|vendors|dataCategories|processingPurposes|actionItems|actionItemCollections|teams|privacyCenters|policies|messages|assessments|assessmentTemplates|purposes|preferenceOptions|systemDiscovery|consentWorkflowTriggers, separator = ,]
[--file] Path to the YAML file to pull into [default = ./transcend.yml]
[--transcendUrl] URL of the Transcend backend. Use https://api.us.transcend.io for US hosting [default = https://api.transcend.io]
[--dataSiloIds]... The UUIDs of the data silos that should be pulled into the YAML file [separator = ,]
@@ -2431,41 +2431,43 @@ FLAGS
The API key permissions for this command vary based on the `resources` argument:
-| Resource | Key in `transcend.yml` | Description | Scopes | Link |
-| ----------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `apiKeys` | `api-keys` | API Key definitions assigned to Data Systems (formerly "Data Silos"). API keys cannot be created through the CLI, but you can map API key usage to Data Systems. | View API Keys | [Developer Tools -> API keys](https://app.transcend.io/infrastructure/api-keys) |
-| `customFields` | `attributes` | Custom Field definitions that define extra metadata for each table in the Admin Dashboard. | View Global Attributes | [Custom Fields](https://app.transcend.io/infrastructure/attributes) |
-| `templates` | `templates` | Email templates. Only template titles can be created and mapped to other resources. | View Email Templates | [DSR Automation -> Email Settings -> Templates](https://app.transcend.io/privacy-requests/email-settings/templates) |
-| `dataSilos` | `data-silos` | The Data System (formerly "Data Silo") definitions. | View Data Map, View Data Subject Request Settings | [Data Inventory -> Data Systems](https://app.transcend.io/data-map/data-inventory/data-silos)
[Infrastructure -> Integrations](https://app.transcend.io/infrastructure/integrations) |
-| `enrichers` | `enrichers` | The Privacy Request enricher configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) |
-| `dataFlows` | `data-flows` | Consent Manager Data Flow definitions. | View Data Flows | [Consent Management -> Data Flows](https://app.transcend.io/consent-manager/data-flows/approved) |
-| `businessEntities` | `business-entities` | The business entities in the Data Inventory. | View Data Inventory | [Data Inventory -> Business Entities](https://app.transcend.io/data-map/data-inventory/business-entities) |
-| `processingActivities` | `processing-activities` | The processing activities in the Data Inventory. | View Data Inventory | [Data Inventory -> Processing Activities](https://app.transcend.io/data-map/data-inventory/processing-activities) |
-| `actions` | `actions` | The privacy request action settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings -> Data Actions](https://app.transcend.io/privacy-requests/settings/data-actions) |
-| `dataSubjects` | `data-subjects` | The privacy request data subject settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings -> Data Subjects](https://app.transcend.io/privacy-requests/settings/data-subjects) |
-| `identifiers` | `identifiers` | The privacy request identifier configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) |
-| `cookies` | `cookies` | Consent Manager Cookie definitions. | View Data Flows | [Consent Management -> Cookies](https://app.transcend.io/consent-manager/cookies/approved) |
-| `consentManager` | `consent-manager` | Consent Manager general settings, including domain list. | View Consent Manager | [Consent Management -> Developer Settings](https://app.transcend.io/consent-manager/developer-settings) |
-| `partitions` | `partitions` | The partitions in the account (often representative of separate data controllers). | View Consent Manager | [Consent Management -> Developer Settings -> Advanced Settings](https://app.transcend.io/consent-manager/developer-settings/advanced-settings) |
-| `prompts` | `prompts` | The Transcend AI prompts | View Prompts | [Prompt Manager -> Browse](https://app.transcend.io/prompts/browse) |
-| `promptPartials` | `prompt-partials` | The Transcend AI prompt partials | View Prompts | [Prompt Manager -> Partials](https://app.transcend.io/prompts/partials) |
-| `promptGroups` | `prompt-groups` | The Transcend AI prompt groups | View Prompts | [Prompt Manager -> Groups](https://app.transcend.io/prompts/groups) |
-| `agents` | `agents` | The agents in Pathfinder. | View Pathfinder | [Pathfinder -> Agents](https://app.transcend.io/pathfinder/agents) |
-| `agentFunctions` | `agent-functions` | The agent functions in Pathfinder. | View Pathfinder | [Pathfinder -> Agent Functions](https://app.transcend.io/pathfinder/agent-functions) |
-| `agentFiles` | `agent-files` | The agent files in Pathfinder. | View Pathfinder | [Pathfinder -> Agent Files](https://app.transcend.io/pathfinder/agent-files) |
-| `vendors` | `vendors` | The vendors in the Data Inventory. | View Data Inventory | [Data Inventory -> Vendors](https://app.transcend.io/data-map/data-inventory/vendors) |
-| `dataCategories` | `data-categories` | The data categories in the Data Inventory. | View Data Inventory | [Data Inventory -> Data Categories](https://app.transcend.io/data-map/data-inventory/data-categories) |
-| `processingPurposes` | `processing-purposes` | The processing purposes in the Data Inventory. | View Data Inventory | [Data Inventory -> Processing Purposes](https://app.transcend.io/data-map/data-inventory/purposes) |
-| `actionItems` | `action-items` | Onboarding-related action items | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) |
-| `actionItemCollections` | `action-item-collections` | Onboarding-related action item group names | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) |
-| `teams` | `teams` | Team definitions of users and scope groupings | View Scopes | [Administration -> Teams](https://app.transcend.io/admin/teams) |
-| `privacyCenters` | `privacy-center` | The Privacy Center settings. | View Privacy Center Layout | [Privacy Center](https://app.transcend.io/privacy-center/general-settings) |
-| `policies` | `policies` | The Privacy Center policies. | View Policies | [Privacy Center -> Policies](https://app.transcend.io/privacy-center/policies) |
-| `messages` | `messages` | Message definitions used across Consent Management, the Privacy Center, email templates and more. | View Internationalization Messages | [Privacy Center -> Messages & Internationalization](https://app.transcend.io/privacy-center/messages-internationalization)
[Consent Management -> Display Settings -> Messages](https://app.transcend.io/consent-manager/display-settings/messages) |
-| `assessments` | `assessments` | Assessment responses. | View Assessments | [Assessments -> Assessments](https://app.transcend.io/assessments/groups) |
-| `assessmentTemplates` | `assessment-templates` | Assessment template configurations. | View Assessments | [Assessment -> Templates](https://app.transcend.io/assessments/form-templates) |
-| `purposes` | `purposes` | Consent purposes and related preference management topics. | View Consent Manager, View Preference Store Settings | [Consent Management -> Regional Experiences -> Purposes](https://app.transcend.io/consent-manager/regional-experiences/purposes) |
-| `systemDiscovery` | `system-discovery` | System discovery results | View Data Map | [System Discovery](https://app.transcend.io/data-map/data-inventory/silo-discovery) |
+| Resource | Key in `transcend.yml` | Description | Scopes | Link |
+| ------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `apiKeys` | `api-keys` | API Key definitions assigned to Data Systems (formerly "Data Silos"). API keys cannot be created through the CLI, but you can map API key usage to Data Systems. | View API Keys | [Developer Tools -> API keys](https://app.transcend.io/infrastructure/api-keys) |
+| `customFields` | `attributes` | Custom Field definitions that define extra metadata for each table in the Admin Dashboard. | View Global Attributes | [Custom Fields](https://app.transcend.io/infrastructure/attributes) |
+| `templates` | `templates` | Email templates. Only template titles can be created and mapped to other resources. | View Email Templates | [DSR Automation -> Email Settings -> Templates](https://app.transcend.io/privacy-requests/email-settings/templates) |
+| `dataSilos` | `data-silos` | The Data System (formerly "Data Silo") definitions. | View Data Map, View Data Subject Request Settings | [Data Inventory -> Data Systems](https://app.transcend.io/data-map/data-inventory/data-silos)
[Infrastructure -> Integrations](https://app.transcend.io/infrastructure/integrations) |
+| `enrichers` | `enrichers` | The Privacy Request enricher configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) |
+| `dataFlows` | `data-flows` | Consent Manager Data Flow definitions. | View Data Flows | [Consent Management -> Data Flows](https://app.transcend.io/consent-manager/data-flows/approved) |
+| `businessEntities` | `business-entities` | The business entities in the Data Inventory. | View Data Inventory | [Data Inventory -> Business Entities](https://app.transcend.io/data-map/data-inventory/business-entities) |
+| `processingActivities` | `processing-activities` | The processing activities in the Data Inventory. | View Data Inventory | [Data Inventory -> Processing Activities](https://app.transcend.io/data-map/data-inventory/processing-activities) |
+| `actions` | `actions` | The privacy request action settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings -> Data Actions](https://app.transcend.io/privacy-requests/settings/data-actions) |
+| `dataSubjects` | `data-subjects` | The privacy request data subject settings. | View Data Subject Request Settings | [DSR Automation -> Request Settings -> Data Subjects](https://app.transcend.io/privacy-requests/settings/data-subjects) |
+| `identifiers` | `identifiers` | The privacy request identifier configurations. | View Identity Verification Settings | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) |
+| `cookies` | `cookies` | Consent Manager Cookie definitions. | View Data Flows | [Consent Management -> Cookies](https://app.transcend.io/consent-manager/cookies/approved) |
+| `consentManager` | `consent-manager` | Consent Manager general settings, including domain list. | View Consent Manager | [Consent Management -> Developer Settings](https://app.transcend.io/consent-manager/developer-settings) |
+| `partitions` | `partitions` | The partitions in the account (often representative of separate data controllers). | View Consent Manager | [Consent Management -> Developer Settings -> Advanced Settings](https://app.transcend.io/consent-manager/developer-settings/advanced-settings) |
+| `prompts` | `prompts` | The Transcend AI prompts | View Prompts | [Prompt Manager -> Browse](https://app.transcend.io/prompts/browse) |
+| `promptPartials` | `prompt-partials` | The Transcend AI prompt partials | View Prompts | [Prompt Manager -> Partials](https://app.transcend.io/prompts/partials) |
+| `promptGroups` | `prompt-groups` | The Transcend AI prompt groups | View Prompts | [Prompt Manager -> Groups](https://app.transcend.io/prompts/groups) |
+| `agents` | `agents` | The agents in Pathfinder. | View Pathfinder | [Pathfinder -> Agents](https://app.transcend.io/pathfinder/agents) |
+| `agentFunctions` | `agent-functions` | The agent functions in Pathfinder. | View Pathfinder | [Pathfinder -> Agent Functions](https://app.transcend.io/pathfinder/agent-functions) |
+| `agentFiles` | `agent-files` | The agent files in Pathfinder. | View Pathfinder | [Pathfinder -> Agent Files](https://app.transcend.io/pathfinder/agent-files) |
+| `vendors` | `vendors` | The vendors in the Data Inventory. | View Data Inventory | [Data Inventory -> Vendors](https://app.transcend.io/data-map/data-inventory/vendors) |
+| `dataCategories` | `data-categories` | The data categories in the Data Inventory. | View Data Inventory | [Data Inventory -> Data Categories](https://app.transcend.io/data-map/data-inventory/data-categories) |
+| `processingPurposes` | `processing-purposes` | The processing purposes in the Data Inventory. | View Data Inventory | [Data Inventory -> Processing Purposes](https://app.transcend.io/data-map/data-inventory/purposes) |
+| `actionItems` | `action-items` | Onboarding-related action items | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) |
+| `actionItemCollections` | `action-item-collections` | Onboarding-related action item group names | View All Action Items | [Action Items](https://app.transcend.io/action-items/all) |
+| `teams` | `teams` | Team definitions of users and scope groupings | View Scopes | [Administration -> Teams](https://app.transcend.io/admin/teams) |
+| `privacyCenters` | `privacy-center` | The Privacy Center settings. | View Privacy Center Layout | [Privacy Center](https://app.transcend.io/privacy-center/general-settings) |
+| `policies` | `policies` | The Privacy Center policies. | View Policies | [Privacy Center -> Policies](https://app.transcend.io/privacy-center/policies) |
+| `messages` | `messages` | Message definitions used across Consent Management, the Privacy Center, email templates and more. | View Internationalization Messages | [Privacy Center -> Messages & Internationalization](https://app.transcend.io/privacy-center/messages-internationalization)
[Consent Management -> Display Settings -> Messages](https://app.transcend.io/consent-manager/display-settings/messages) |
+| `assessments` | `assessments` | Assessment responses. | View Assessments | [Assessments -> Assessments](https://app.transcend.io/assessments/groups) |
+| `assessmentTemplates` | `assessment-templates` | Assessment template configurations. | View Assessments | [Assessment -> Templates](https://app.transcend.io/assessments/form-templates) |
+| `purposes` | `purposes` | Consent purposes and related preference management topics. | View Consent Manager, View Preference Store Settings | [Consent Management -> Regional Experiences -> Purposes](https://app.transcend.io/consent-manager/regional-experiences/purposes) |
+| `preferenceOptions` | `preference-options` | Preference management options for multi and single select preference topics. | View Preference Store Settings | [Preference Management -> Preference Topics -> Options](https://app.transcend.io/preference-store/preference-topics/preference-options) |
+| `systemDiscovery` | `system-discovery` | System discovery results | View Data Map | [System Discovery](https://app.transcend.io/data-map/data-inventory/silo-discovery) |
+| `consentWorkflowTriggers` | `consent-workflow-triggers` | Consent workflow trigger definitions that automate privacy request workflows based on consent state changes. | View Consent Manager | [Consent Management -> Consent Workflows](https://app.transcend.io/consent-manager/consent-workflows) |
#### Examples
@@ -2643,41 +2645,43 @@ FLAGS
The API key permissions for this command vary based on the resources declared as top-level keys in your [`transcend.yml`](#transcendyml) file:
-| Resource | Key in `transcend.yml` | Description | Scopes | Link |
-| ----------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `apiKeys` | `api-keys` | API Key definitions assigned to Data Systems (formerly "Data Silos"). API keys cannot be created through the CLI, but you can map API key usage to Data Systems. | View API Keys | [Developer Tools -> API keys](https://app.transcend.io/infrastructure/api-keys) |
-| `customFields` | `attributes` | Custom Field definitions that define extra metadata for each table in the Admin Dashboard. | Manage Global Attributes | [Custom Fields](https://app.transcend.io/infrastructure/attributes) |
-| `templates` | `templates` | Email templates. Only template titles can be created and mapped to other resources. | Manage Email Templates | [DSR Automation -> Email Settings -> Templates](https://app.transcend.io/privacy-requests/email-settings/templates) |
-| `dataSilos` | `data-silos` | The Data System (formerly "Data Silo") definitions. | Manage Data Map, Connect Data Silos | [Data Inventory -> Data Systems](https://app.transcend.io/data-map/data-inventory/data-silos)
[Infrastructure -> Integrations](https://app.transcend.io/infrastructure/integrations) |
-| `enrichers` | `enrichers` | The Privacy Request enricher configurations. | Manage Request Identity Verification | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) |
-| `dataFlows` | `data-flows` | Consent Manager Data Flow definitions. | Manage Data Flows | [Consent Management -> Data Flows](https://app.transcend.io/consent-manager/data-flows/approved) |
-| `businessEntities` | `business-entities` | The business entities in the Data Inventory. | Manage Data Inventory | [Data Inventory -> Business Entities](https://app.transcend.io/data-map/data-inventory/business-entities) |
-| `processingActivities` | `processing-activities` | The processing activities in the Data Inventory. | Manage Data Map | [Data Inventory -> Processing Activities](https://app.transcend.io/data-map/data-inventory/processing-activities) |
-| `actions` | `actions` | The privacy request action settings. | Manage Data Subject Request Settings | [DSR Automation -> Request Settings -> Data Actions](https://app.transcend.io/privacy-requests/settings/data-actions) |
-| `dataSubjects` | `data-subjects` | The privacy request data subject settings. | Manage Data Subject Request Settings | [DSR Automation -> Request Settings -> Data Subjects](https://app.transcend.io/privacy-requests/settings/data-subjects) |
-| `identifiers` | `identifiers` | The privacy request identifier configurations. | Manage Request Identity Verification | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) |
-| `cookies` | `cookies` | Consent Manager Cookie definitions. | Manage Data Flows | [Consent Management -> Cookies](https://app.transcend.io/consent-manager/cookies/approved) |
-| `consentManager` | `consent-manager` | Consent Manager general settings, including domain list. | Manage Consent Manager Developer Settings | [Consent Management -> Developer Settings](https://app.transcend.io/consent-manager/developer-settings) |
-| `partitions` | `partitions` | The partitions in the account (often representative of separate data controllers). | Manage Consent Manager Developer Settings | [Consent Management -> Developer Settings -> Advanced Settings](https://app.transcend.io/consent-manager/developer-settings/advanced-settings) |
-| `prompts` | `prompts` | The Transcend AI prompts | Manage Prompts | [Prompt Manager -> Browse](https://app.transcend.io/prompts/browse) |
-| `promptPartials` | `prompt-partials` | The Transcend AI prompt partials | Manage Prompts | [Prompt Manager -> Partials](https://app.transcend.io/prompts/partials) |
-| `promptGroups` | `prompt-groups` | The Transcend AI prompt groups | Manage Prompts | [Prompt Manager -> Groups](https://app.transcend.io/prompts/groups) |
-| `agents` | `agents` | The agents in Pathfinder. | Manage Pathfinder | [Pathfinder -> Agents](https://app.transcend.io/pathfinder/agents) |
-| `agentFunctions` | `agent-functions` | The agent functions in Pathfinder. | Manage Pathfinder | [Pathfinder -> Agent Functions](https://app.transcend.io/pathfinder/agent-functions) |
-| `agentFiles` | `agent-files` | The agent files in Pathfinder. | Manage Pathfinder | [Pathfinder -> Agent Files](https://app.transcend.io/pathfinder/agent-files) |
-| `vendors` | `vendors` | The vendors in the Data Inventory. | Manage Data Inventory | [Data Inventory -> Vendors](https://app.transcend.io/data-map/data-inventory/vendors) |
-| `dataCategories` | `data-categories` | The data categories in the Data Inventory. | Manage Data Inventory | [Data Inventory -> Data Categories](https://app.transcend.io/data-map/data-inventory/data-categories) |
-| `processingPurposes` | `processing-purposes` | The processing purposes in the Data Inventory. | Manage Data Inventory | [Data Inventory -> Processing Purposes](https://app.transcend.io/data-map/data-inventory/purposes) |
-| `actionItems` | `action-items` | Onboarding-related action items | Manage All Action Items, View Global Attributes | [Action Items](https://app.transcend.io/action-items/all) |
-| `actionItemCollections` | `action-item-collections` | Onboarding-related action item group names | Manage Action Item Collections | [Action Items](https://app.transcend.io/action-items/all) |
-| `teams` | `teams` | Team definitions of users and scope groupings | Manage Access Controls | [Administration -> Teams](https://app.transcend.io/admin/teams) |
-| `privacyCenters` | `privacy-center` | The Privacy Center settings. | Manage Privacy Center Layout | [Privacy Center](https://app.transcend.io/privacy-center/general-settings) |
-| `policies` | `policies` | The Privacy Center policies. | Manage Policies | [Privacy Center -> Policies](https://app.transcend.io/privacy-center/policies) |
-| `messages` | `messages` | Message definitions used across Consent Management, the Privacy Center, email templates and more. | Manage Internationalization Messages | [Privacy Center -> Messages & Internationalization](https://app.transcend.io/privacy-center/messages-internationalization)
[Consent Management -> Display Settings -> Messages](https://app.transcend.io/consent-manager/display-settings/messages) |
-| `assessments` | `assessments` | Assessment responses. | Manage Assessments | [Assessments -> Assessments](https://app.transcend.io/assessments/groups) |
-| `assessmentTemplates` | `assessment-templates` | Assessment template configurations. | Manage Assessments | [Assessment -> Templates](https://app.transcend.io/assessments/form-templates) |
-| `purposes` | `purposes` | Consent purposes and related preference management topics. | Manage Consent Manager, Manage Preference Store Settings | [Consent Management -> Regional Experiences -> Purposes](https://app.transcend.io/consent-manager/regional-experiences/purposes) |
-| `systemDiscovery` | `system-discovery` | System discovery results | Manage Data Map | [System Discovery](https://app.transcend.io/data-map/data-inventory/silo-discovery) |
+| Resource | Key in `transcend.yml` | Description | Scopes | Link |
+| ------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `apiKeys` | `api-keys` | API Key definitions assigned to Data Systems (formerly "Data Silos"). API keys cannot be created through the CLI, but you can map API key usage to Data Systems. | View API Keys | [Developer Tools -> API keys](https://app.transcend.io/infrastructure/api-keys) |
+| `customFields` | `attributes` | Custom Field definitions that define extra metadata for each table in the Admin Dashboard. | Manage Global Attributes | [Custom Fields](https://app.transcend.io/infrastructure/attributes) |
+| `templates` | `templates` | Email templates. Only template titles can be created and mapped to other resources. | Manage Email Templates | [DSR Automation -> Email Settings -> Templates](https://app.transcend.io/privacy-requests/email-settings/templates) |
+| `dataSilos` | `data-silos` | The Data System (formerly "Data Silo") definitions. | Manage Data Map, Connect Data Silos | [Data Inventory -> Data Systems](https://app.transcend.io/data-map/data-inventory/data-silos)
[Infrastructure -> Integrations](https://app.transcend.io/infrastructure/integrations) |
+| `enrichers` | `enrichers` | The Privacy Request enricher configurations. | Manage Request Identity Verification | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) |
+| `dataFlows` | `data-flows` | Consent Manager Data Flow definitions. | Manage Data Flows | [Consent Management -> Data Flows](https://app.transcend.io/consent-manager/data-flows/approved) |
+| `businessEntities` | `business-entities` | The business entities in the Data Inventory. | Manage Data Inventory | [Data Inventory -> Business Entities](https://app.transcend.io/data-map/data-inventory/business-entities) |
+| `processingActivities` | `processing-activities` | The processing activities in the Data Inventory. | Manage Data Map | [Data Inventory -> Processing Activities](https://app.transcend.io/data-map/data-inventory/processing-activities) |
+| `actions` | `actions` | The privacy request action settings. | Manage Data Subject Request Settings | [DSR Automation -> Request Settings -> Data Actions](https://app.transcend.io/privacy-requests/settings/data-actions) |
+| `dataSubjects` | `data-subjects` | The privacy request data subject settings. | Manage Data Subject Request Settings | [DSR Automation -> Request Settings -> Data Subjects](https://app.transcend.io/privacy-requests/settings/data-subjects) |
+| `identifiers` | `identifiers` | The privacy request identifier configurations. | Manage Request Identity Verification | [DSR Automation -> Identifiers](https://app.transcend.io/privacy-requests/identifiers) |
+| `cookies` | `cookies` | Consent Manager Cookie definitions. | Manage Data Flows | [Consent Management -> Cookies](https://app.transcend.io/consent-manager/cookies/approved) |
+| `consentManager` | `consent-manager` | Consent Manager general settings, including domain list. | Manage Consent Manager Developer Settings | [Consent Management -> Developer Settings](https://app.transcend.io/consent-manager/developer-settings) |
+| `partitions` | `partitions` | The partitions in the account (often representative of separate data controllers). | Manage Consent Manager Developer Settings | [Consent Management -> Developer Settings -> Advanced Settings](https://app.transcend.io/consent-manager/developer-settings/advanced-settings) |
+| `prompts` | `prompts` | The Transcend AI prompts | Manage Prompts | [Prompt Manager -> Browse](https://app.transcend.io/prompts/browse) |
+| `promptPartials` | `prompt-partials` | The Transcend AI prompt partials | Manage Prompts | [Prompt Manager -> Partials](https://app.transcend.io/prompts/partials) |
+| `promptGroups` | `prompt-groups` | The Transcend AI prompt groups | Manage Prompts | [Prompt Manager -> Groups](https://app.transcend.io/prompts/groups) |
+| `agents` | `agents` | The agents in Pathfinder. | Manage Pathfinder | [Pathfinder -> Agents](https://app.transcend.io/pathfinder/agents) |
+| `agentFunctions` | `agent-functions` | The agent functions in Pathfinder. | Manage Pathfinder | [Pathfinder -> Agent Functions](https://app.transcend.io/pathfinder/agent-functions) |
+| `agentFiles` | `agent-files` | The agent files in Pathfinder. | Manage Pathfinder | [Pathfinder -> Agent Files](https://app.transcend.io/pathfinder/agent-files) |
+| `vendors` | `vendors` | The vendors in the Data Inventory. | Manage Data Inventory | [Data Inventory -> Vendors](https://app.transcend.io/data-map/data-inventory/vendors) |
+| `dataCategories` | `data-categories` | The data categories in the Data Inventory. | Manage Data Inventory | [Data Inventory -> Data Categories](https://app.transcend.io/data-map/data-inventory/data-categories) |
+| `processingPurposes` | `processing-purposes` | The processing purposes in the Data Inventory. | Manage Data Inventory | [Data Inventory -> Processing Purposes](https://app.transcend.io/data-map/data-inventory/purposes) |
+| `actionItems` | `action-items` | Onboarding-related action items | Manage All Action Items, View Global Attributes | [Action Items](https://app.transcend.io/action-items/all) |
+| `actionItemCollections` | `action-item-collections` | Onboarding-related action item group names | Manage Action Item Collections | [Action Items](https://app.transcend.io/action-items/all) |
+| `teams` | `teams` | Team definitions of users and scope groupings | Manage Access Controls | [Administration -> Teams](https://app.transcend.io/admin/teams) |
+| `privacyCenters` | `privacy-center` | The Privacy Center settings. | Manage Privacy Center Layout | [Privacy Center](https://app.transcend.io/privacy-center/general-settings) |
+| `policies` | `policies` | The Privacy Center policies. | Manage Policies | [Privacy Center -> Policies](https://app.transcend.io/privacy-center/policies) |
+| `messages` | `messages` | Message definitions used across Consent Management, the Privacy Center, email templates and more. | Manage Internationalization Messages | [Privacy Center -> Messages & Internationalization](https://app.transcend.io/privacy-center/messages-internationalization)
[Consent Management -> Display Settings -> Messages](https://app.transcend.io/consent-manager/display-settings/messages) |
+| `assessments` | `assessments` | Assessment responses. | Manage Assessments | [Assessments -> Assessments](https://app.transcend.io/assessments/groups) |
+| `assessmentTemplates` | `assessment-templates` | Assessment template configurations. | Manage Assessments | [Assessment -> Templates](https://app.transcend.io/assessments/form-templates) |
+| `purposes` | `purposes` | Consent purposes and related preference management topics. | Manage Consent Manager, Manage Preference Store Settings | [Consent Management -> Regional Experiences -> Purposes](https://app.transcend.io/consent-manager/regional-experiences/purposes) |
+| `preferenceOptions` | `preference-options` | Preference management options for multi and single select preference topics. | Manage Preference Store Settings | [Preference Management -> Preference Topics -> Options](https://app.transcend.io/preference-store/preference-topics/preference-options) |
+| `systemDiscovery` | `system-discovery` | System discovery results | Manage Data Map | [System Discovery](https://app.transcend.io/data-map/data-inventory/silo-discovery) |
+| `consentWorkflowTriggers` | `consent-workflow-triggers` | Consent workflow trigger definitions that automate privacy request workflows based on consent state changes. | Manage Consent Manager, View Data Subject Request Settings, View Consent Manager | [Consent Management -> Consent Workflows](https://app.transcend.io/consent-manager/consent-workflows) |
#### Examples
diff --git a/packages/cli/examples/consent-workflow-triggers.yml b/packages/cli/examples/consent-workflow-triggers.yml
new file mode 100644
index 00000000..30414f82
--- /dev/null
+++ b/packages/cli/examples/consent-workflow-triggers.yml
@@ -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
diff --git a/packages/cli/schema/transcend-yml-schema-latest.json b/packages/cli/schema/transcend-yml-schema-latest.json
index 00fd31d3..f035caf4 100644
--- a/packages/cli/schema/transcend-yml-schema-latest.json
+++ b/packages/cli/schema/transcend-yml-schema-latest.json
@@ -59585,6 +59585,81 @@
}
]
}
+ },
+ "consent-workflow-triggers": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "required": ["name"],
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "properties": {
+ "trigger-condition": {
+ "type": "string"
+ },
+ "action-type": {
+ "type": "string"
+ },
+ "data-subject-type": {
+ "type": "string"
+ },
+ "is-silent": {
+ "type": "boolean"
+ },
+ "allow-unauthenticated": {
+ "type": "boolean"
+ },
+ "is-active": {
+ "type": "boolean"
+ },
+ "data-silo-titles": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "purposes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["tracking-type", "matching-state"],
+ "properties": {
+ "tracking-type": {
+ "type": "string"
+ },
+ "matching-state": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "preference-options": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["title", "slug"],
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "slug": {
+ "type": "string"
+ }
+ }
+ }
}
}
}
diff --git a/packages/cli/schema/transcend-yml-schema-v9.json b/packages/cli/schema/transcend-yml-schema-v9.json
index aa164439..e12ee922 100644
--- a/packages/cli/schema/transcend-yml-schema-v9.json
+++ b/packages/cli/schema/transcend-yml-schema-v9.json
@@ -59585,6 +59585,81 @@
}
]
}
+ },
+ "consent-workflow-triggers": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "type": "object",
+ "required": ["name"],
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "properties": {
+ "trigger-condition": {
+ "type": "string"
+ },
+ "action-type": {
+ "type": "string"
+ },
+ "data-subject-type": {
+ "type": "string"
+ },
+ "is-silent": {
+ "type": "boolean"
+ },
+ "allow-unauthenticated": {
+ "type": "boolean"
+ },
+ "is-active": {
+ "type": "boolean"
+ },
+ "data-silo-titles": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "purposes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["tracking-type", "matching-state"],
+ "properties": {
+ "tracking-type": {
+ "type": "string"
+ },
+ "matching-state": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "preference-options": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["title", "slug"],
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "slug": {
+ "type": "string"
+ }
+ }
+ }
}
}
}
diff --git a/packages/cli/src/codecs.ts b/packages/cli/src/codecs.ts
index d268ddef..a4e355ba 100644
--- a/packages/cli/src/codecs.ts
+++ b/packages/cli/src/codecs.ts
@@ -1932,6 +1932,52 @@ export const SiloDiscoveryResultInput = t.intersection([
/** Type override */
export type SiloDiscoveryResultInput = t.TypeOf;
+/**
+ * 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;
+
export const TranscendInput = t.partial({
/**
* Action items
@@ -2061,6 +2107,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 */
diff --git a/packages/cli/src/constants.ts b/packages/cli/src/constants.ts
index 376f176f..9ecd3389 100644
--- a/packages/cli/src/constants.ts
+++ b/packages/cli/src/constants.ts
@@ -72,7 +72,13 @@ 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,
+ ],
};
/**
@@ -119,7 +125,9 @@ 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 = {
@@ -155,7 +163,9 @@ export const TR_YML_RESOURCE_TO_FIELD_NAME: Record Preference Topics -> Options]\
+(https://app.transcend.io/preference-store/preference-topics/preference-options)',
+ ],
+ },
[TranscendPullResource.SystemDiscovery]: {
description: 'System discovery results',
markdownLinks: [
@@ -209,6 +216,14 @@ const RESOURCE_DOCUMENTATION: Record<
(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)',
+ ],
+ },
};
/**
diff --git a/packages/cli/src/lib/graphql/fetchAllConsentWorkflowTriggers.ts b/packages/cli/src/lib/graphql/fetchAllConsentWorkflowTriggers.ts
new file mode 100644
index 00000000..a63dcccf
--- /dev/null
+++ b/packages/cli/src/lib/graphql/fetchAllConsentWorkflowTriggers.ts
@@ -0,0 +1,72 @@
+import { GraphQLClient } from 'graphql-request';
+
+import { CONSENT_WORKFLOW_TRIGGERS } from './gqls/index.js';
+import { makeGraphQLRequest } from './makeGraphQLRequest.js';
+
+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 {
+ 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));
+}
diff --git a/packages/cli/src/lib/graphql/fetchAllPreferenceOptionValues.ts b/packages/cli/src/lib/graphql/fetchAllPreferenceOptionValues.ts
new file mode 100644
index 00000000..5c6f8ce9
--- /dev/null
+++ b/packages/cli/src/lib/graphql/fetchAllPreferenceOptionValues.ts
@@ -0,0 +1,54 @@
+import { GraphQLClient } from 'graphql-request';
+
+import { PREFERENCE_OPTION_VALUES } from './gqls/index.js';
+import { makeGraphQLRequest } from './makeGraphQLRequest.js';
+
+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 {
+ 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));
+}
diff --git a/packages/cli/src/lib/graphql/gqls/consentWorkflowTrigger.ts b/packages/cli/src/lib/graphql/gqls/consentWorkflowTrigger.ts
new file mode 100644
index 00000000..6fbe047e
--- /dev/null
+++ b/packages/cli/src/lib/graphql/gqls/consentWorkflowTrigger.ts
@@ -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
+ }
+ }
+`;
diff --git a/packages/cli/src/lib/graphql/gqls/index.ts b/packages/cli/src/lib/graphql/gqls/index.ts
index 44a12c34..4130783f 100644
--- a/packages/cli/src/lib/graphql/gqls/index.ts
+++ b/packages/cli/src/lib/graphql/gqls/index.ts
@@ -50,3 +50,4 @@ export * from './processingPurpose.js';
export * from './processingActivity.js';
export * from './sombraVersion.js';
export * from './siloDiscoveryResult.js';
+export * from './consentWorkflowTrigger.js';
diff --git a/packages/cli/src/lib/graphql/gqls/preferenceTopic.ts b/packages/cli/src/lib/graphql/gqls/preferenceTopic.ts
index 6aa0fcc2..02b5eb26 100644
--- a/packages/cli/src/lib/graphql/gqls/preferenceTopic.ts
+++ b/packages/cli/src/lib/graphql/gqls/preferenceTopic.ts
@@ -42,3 +42,42 @@ 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
+ }
+ }
+ }
+`;
diff --git a/packages/cli/src/lib/graphql/gqls/purpose.ts b/packages/cli/src/lib/graphql/gqls/purpose.ts
index ecd89bb6..3ec6b953 100644
--- a/packages/cli/src/lib/graphql/gqls/purpose.ts
+++ b/packages/cli/src/lib/graphql/gqls/purpose.ts
@@ -35,3 +35,27 @@ export const PURPOSES = gql`
}
}
`;
+
+export const CREATE_PURPOSE = gql`
+ mutation TranscendCliCreatePurpose($input: TrackingPurposeCreateInput!) {
+ createPurpose(input: $input) {
+ trackingPurpose {
+ id
+ name
+ trackingType
+ }
+ }
+ }
+`;
+
+export const UPDATE_PURPOSE = gql`
+ mutation TranscendCliUpdatePurpose($input: TrackingPurposeUpdateInput!) {
+ updatePurpose(input: $input) {
+ trackingPurpose {
+ id
+ name
+ trackingType
+ }
+ }
+ }
+`;
diff --git a/packages/cli/src/lib/graphql/index.ts b/packages/cli/src/lib/graphql/index.ts
index c14e6cec..5881e7de 100644
--- a/packages/cli/src/lib/graphql/index.ts
+++ b/packages/cli/src/lib/graphql/index.ts
@@ -89,3 +89,8 @@ export * from './syncTemplates.js';
export * from './syncVendors.js';
export * from './uploadSiloDiscoveryResults.js';
export * from './fetchAllSiloDiscoveryResults.js';
+export * from './fetchAllConsentWorkflowTriggers.js';
+export * from './fetchAllPreferenceOptionValues.js';
+export * from './syncConsentWorkflowTriggers.js';
+export * from './syncPreferenceOptionValues.js';
+export * from './syncPurposes.js';
diff --git a/packages/cli/src/lib/graphql/pullTranscendConfiguration.ts b/packages/cli/src/lib/graphql/pullTranscendConfiguration.ts
index 61e60df9..2fefed09 100644
--- a/packages/cli/src/lib/graphql/pullTranscendConfiguration.ts
+++ b/packages/cli/src/lib/graphql/pullTranscendConfiguration.ts
@@ -45,7 +45,9 @@ import {
AssessmentSectionQuestionInput,
RiskLogicInput,
ConsentPurpose,
+ type ConsentPreferenceTopicOptionValue,
type SiloDiscoveryResultInput,
+ type ConsentWorkflowTriggerInput,
} from '../../codecs.js';
import { TranscendPullResource } from '../../enums.js';
import { logger } from '../../logger.js';
@@ -59,11 +61,13 @@ import { fetchAllAssessments } from './fetchAllAssessments.js';
import { fetchAllAssessmentTemplates } from './fetchAllAssessmentTemplates.js';
import { fetchAllAttributes } from './fetchAllAttributes.js';
import { fetchAllBusinessEntities } from './fetchAllBusinessEntities.js';
+import { fetchAllConsentWorkflowTriggers } from './fetchAllConsentWorkflowTriggers.js';
import { fetchAllCookies } from './fetchAllCookies.js';
import { fetchAllDataCategories } from './fetchAllDataCategories.js';
import { fetchAllDataFlows } from './fetchAllDataFlows.js';
import { fetchAllMessages } from './fetchAllMessages.js';
import { fetchAllPolicies } from './fetchAllPolicies.js';
+import { fetchAllPreferenceOptionValues } from './fetchAllPreferenceOptionValues.js';
import { fetchAllPrivacyCenters } from './fetchAllPrivacyCenters.js';
import { fetchAllProcessingActivities } from './fetchAllProcessingActivities.js';
import { fetchAllProcessingPurposes } from './fetchAllProcessingPurposes.js';
@@ -181,7 +185,9 @@ export async function pullTranscendConfiguration(
assessments,
assessmentTemplates,
purposes,
+ preferenceOptionValues,
siloDiscoveryResults,
+ consentWorkflowTriggers,
] = await Promise.all([
// Grab all data subjects in the organization
resources.includes(TranscendPullResource.DataSilos) ||
@@ -298,10 +304,18 @@ export async function pullTranscendConfiguration(
resources.includes(TranscendPullResource.Purposes)
? fetchAllPurposesAndPreferences(client)
: [],
+ // Fetch preference option values
+ resources.includes(TranscendPullResource.PreferenceOptions)
+ ? fetchAllPreferenceOptionValues(client)
+ : [],
// Fetch silo discovery results
resources.includes(TranscendPullResource.SystemDiscovery)
? fetchAllSiloDiscoveryResults(client)
: [],
+ // Fetch consent workflow triggers
+ resources.includes(TranscendPullResource.ConsentWorkflowTriggers)
+ ? fetchAllConsentWorkflowTriggers(client)
+ : [],
]);
const consentManagerTheme =
@@ -1333,6 +1347,39 @@ export async function pullTranscendConfiguration(
);
}
+ // Save preference options
+ if (
+ preferenceOptionValues.length > 0 &&
+ resources.includes(TranscendPullResource.PreferenceOptions)
+ ) {
+ result['preference-options'] = preferenceOptionValues.map(
+ ({ slug, title }): ConsentPreferenceTopicOptionValue => ({
+ slug,
+ title: title.defaultMessage,
+ }),
+ );
+ }
+
+ // Save consent workflow triggers
+ if (
+ consentWorkflowTriggers.length > 0 &&
+ resources.includes(TranscendPullResource.ConsentWorkflowTriggers)
+ ) {
+ result['consent-workflow-triggers'] = consentWorkflowTriggers.map(
+ (trigger): ConsentWorkflowTriggerInput => ({
+ name: trigger.name,
+ 'trigger-condition': trigger.triggerCondition || undefined,
+ 'action-type': trigger.action.type,
+ 'data-subject-type': trigger.subject.type,
+ 'is-silent': trigger.isSilent,
+ 'allow-unauthenticated': trigger.allowUnauthenticated,
+ 'is-active': trigger.isActive,
+ 'data-silo-titles':
+ trigger.dataSilos.length > 0 ? trigger.dataSilos.map((ds) => ds.title) : undefined,
+ }),
+ );
+ }
+
// save email templates
if (
dataSiloIds.length === 0 &&
diff --git a/packages/cli/src/lib/graphql/syncConfigurationToTranscend.ts b/packages/cli/src/lib/graphql/syncConfigurationToTranscend.ts
index 2a0ecf46..2166249b 100644
--- a/packages/cli/src/lib/graphql/syncConfigurationToTranscend.ts
+++ b/packages/cli/src/lib/graphql/syncConfigurationToTranscend.ts
@@ -19,6 +19,7 @@ import { syncAgents } from './syncAgents.js';
import { syncAttribute } from './syncAttribute.js';
import { syncBusinessEntities } from './syncBusinessEntities.js';
import { syncConsentManager } from './syncConsentManager.js';
+import { syncConsentWorkflowTriggers } from './syncConsentWorkflowTriggers.js';
import { syncCookies } from './syncCookies.js';
import { syncDataCategories } from './syncDataCategories.js';
import { syncDataFlows } from './syncDataFlows.js';
@@ -29,12 +30,14 @@ import { syncIdentifier } from './syncIdentifier.js';
import { syncIntlMessages } from './syncIntlMessages.js';
import { syncPartitions } from './syncPartitions.js';
import { syncPolicies } from './syncPolicies.js';
+import { syncPreferenceOptionValues } from './syncPreferenceOptionValues.js';
import { syncPrivacyCenter } from './syncPrivacyCenter.js';
import { syncProcessingActivities } from './syncProcessingActivities.js';
import { syncProcessingPurposes } from './syncProcessingPurposes.js';
import { syncPromptGroups } from './syncPromptGroups.js';
import { syncPromptPartials } from './syncPromptPartials.js';
import { syncPrompts } from './syncPrompts.js';
+import { syncPurposes } from './syncPurposes.js';
import { syncTeams } from './syncTeams.js';
import { syncTemplate } from './syncTemplates.js';
import { syncVendors } from './syncVendors.js';
@@ -102,6 +105,8 @@ export async function syncConfigurationToTranscend(
messages,
policies,
partitions,
+ 'consent-workflow-triggers': consentWorkflowTriggers,
+ purposes,
} = input;
const [identifierByName, dataSubjectsByName, apiKeyTitleMap] = await Promise.all([
@@ -134,6 +139,30 @@ export async function syncConfigurationToTranscend(
}
}
+ // Sync preference option values (before purposes, since purposes may reference them)
+ if (input['preference-options']) {
+ const preferenceOptionsSuccess = await syncPreferenceOptionValues(
+ client,
+ input['preference-options'],
+ );
+ encounteredError = encounteredError || !preferenceOptionsSuccess;
+ }
+
+ // Sync purposes (and nested preference topics)
+ if (purposes) {
+ const purposesSuccess = await syncPurposes(client, purposes);
+ encounteredError = encounteredError || !purposesSuccess;
+ }
+
+ // Sync consent workflow triggers
+ if (consentWorkflowTriggers) {
+ const consentWorkflowTriggersSuccess = await syncConsentWorkflowTriggers(
+ client,
+ consentWorkflowTriggers,
+ );
+ encounteredError = encounteredError || !consentWorkflowTriggersSuccess;
+ }
+
// Sync prompts
if (prompts) {
const promptsSuccess = await syncPrompts(client, prompts);
diff --git a/packages/cli/src/lib/graphql/syncConsentWorkflowTriggers.ts b/packages/cli/src/lib/graphql/syncConsentWorkflowTriggers.ts
new file mode 100644
index 00000000..c5be5764
--- /dev/null
+++ b/packages/cli/src/lib/graphql/syncConsentWorkflowTriggers.ts
@@ -0,0 +1,139 @@
+import colors from 'colors';
+import { GraphQLClient } from 'graphql-request';
+import { keyBy } from 'lodash-es';
+
+import { ConsentWorkflowTriggerInput } from '../../codecs.js';
+import { logger } from '../../logger.js';
+import { mapSeries } from '../bluebird.js';
+import { fetchAllActions, type Action } from './fetchAllActions.js';
+import { fetchAllConsentWorkflowTriggers } from './fetchAllConsentWorkflowTriggers.js';
+import { fetchAllPurposes, type Purpose } from './fetchAllPurposes.js';
+import { fetchAllDataSubjects, type DataSubject } from './fetchDataSubjects.js';
+import { CREATE_OR_UPDATE_CONSENT_WORKFLOW_TRIGGER } from './gqls/index.js';
+import { makeGraphQLRequest } from './makeGraphQLRequest.js';
+
+/**
+ * Sync consent workflow triggers to Transcend
+ *
+ * @param client - GraphQL client
+ * @param inputs - Consent workflow trigger inputs from YAML
+ * @returns True if run without error, returns false if an error occurred
+ */
+export async function syncConsentWorkflowTriggers(
+ client: GraphQLClient,
+ inputs: ConsentWorkflowTriggerInput[],
+): Promise {
+ logger.info(colors.magenta(`Syncing "${inputs.length}" consent workflow triggers...`));
+
+ let encounteredError = false;
+
+ const needsActions = inputs.some((t) => t['action-type']);
+ const needsSubjects = inputs.some((t) => t['data-subject-type']);
+ const needsPurposes = inputs.some((t) => t.purposes?.length);
+
+ const [existingTriggers, actions, dataSubjects, purposes] = await Promise.all([
+ fetchAllConsentWorkflowTriggers(client),
+ needsActions ? fetchAllActions(client) : ([] as Action[]),
+ needsSubjects ? fetchAllDataSubjects(client) : ([] as DataSubject[]),
+ needsPurposes ? fetchAllPurposes(client) : ([] as Purpose[]),
+ ]);
+
+ const triggerByName = keyBy(existingTriggers, 'name');
+ const actionByType = keyBy(actions, 'type') as Record;
+ const dataSubjectByType = keyBy(dataSubjects, 'type') as Record;
+ const purposeByTrackingType = keyBy(purposes, 'trackingType') as Record;
+
+ await mapSeries(inputs, async (trigger) => {
+ try {
+ const existingTrigger = triggerByName[trigger.name];
+
+ // Resolve action type to ID
+ let actionId: string | undefined;
+ if (trigger['action-type']) {
+ const action = actionByType[trigger['action-type']];
+ if (!action) {
+ throw new Error(`Failed to find action with type: ${trigger['action-type']}`);
+ }
+ actionId = action.id;
+ }
+
+ // Resolve data subject type to ID
+ let dataSubjectId: string | undefined;
+ if (trigger['data-subject-type']) {
+ const subject = dataSubjectByType[trigger['data-subject-type']];
+ if (!subject) {
+ throw new Error(`Failed to find data subject with type: ${trigger['data-subject-type']}`);
+ }
+ dataSubjectId = subject.id;
+ }
+
+ // Resolve purpose tracking types to purpose IDs with matching states
+ const consentWorkflowTriggerPurposes = trigger.purposes?.map((purposeInput) => {
+ const purpose = purposeByTrackingType[purposeInput['tracking-type']];
+ if (!purpose) {
+ throw new Error(
+ `Failed to find purpose with trackingType: ${purposeInput['tracking-type']}`,
+ );
+ }
+ return {
+ purposeId: purpose.id,
+ matchingState: purposeInput['matching-state'],
+ };
+ });
+
+ const input: Record = {
+ name: trigger.name,
+ ...(existingTrigger ? { id: existingTrigger.id } : {}),
+ triggerCondition: trigger['trigger-condition'] ?? '{}',
+ ...(actionId ? { actionId } : {}),
+ ...(dataSubjectId ? { dataSubjectId } : {}),
+ ...(trigger['is-silent'] !== undefined ? { isSilent: trigger['is-silent'] } : {}),
+ ...(trigger['allow-unauthenticated'] !== undefined
+ ? { allowUnauthenticated: trigger['allow-unauthenticated'] }
+ : {}),
+ ...(trigger['is-active'] !== undefined ? { isActive: trigger['is-active'] } : {}),
+ ...(existingTrigger && consentWorkflowTriggerPurposes
+ ? { consentWorkflowTriggerPurposes }
+ : {}),
+ };
+
+ const {
+ createOrUpdateConsentWorkflowTrigger: {
+ consentWorkflowTrigger: { id: triggerId },
+ },
+ } = await makeGraphQLRequest<{
+ /** Mutation result */
+ createOrUpdateConsentWorkflowTrigger: {
+ /** Created or updated trigger */
+ consentWorkflowTrigger: {
+ /** Trigger ID */
+ id: string;
+ /** Trigger name */
+ name: string;
+ };
+ };
+ }>(client, CREATE_OR_UPDATE_CONSENT_WORKFLOW_TRIGGER, { input });
+
+ // For newly created triggers, purposes must be attached via a follow-up update
+ if (!existingTrigger && consentWorkflowTriggerPurposes?.length) {
+ await makeGraphQLRequest(client, CREATE_OR_UPDATE_CONSENT_WORKFLOW_TRIGGER, {
+ input: {
+ id: triggerId,
+ consentWorkflowTriggerPurposes,
+ },
+ });
+ }
+
+ logger.info(colors.green(`Successfully synced consent workflow trigger "${trigger.name}"!`));
+ } catch (err) {
+ encounteredError = true;
+ logger.info(
+ colors.red(`Failed to sync consent workflow trigger "${trigger.name}"! - ${err.message}`),
+ );
+ }
+ });
+
+ logger.info(colors.green(`Synced "${inputs.length}" consent workflow triggers!`));
+
+ return !encounteredError;
+}
diff --git a/packages/cli/src/lib/graphql/syncPreferenceOptionValues.ts b/packages/cli/src/lib/graphql/syncPreferenceOptionValues.ts
new file mode 100644
index 00000000..44a56f48
--- /dev/null
+++ b/packages/cli/src/lib/graphql/syncPreferenceOptionValues.ts
@@ -0,0 +1,85 @@
+import colors from 'colors';
+import { GraphQLClient } from 'graphql-request';
+import { keyBy } from 'lodash-es';
+
+import { ConsentPreferenceTopicOptionValue } from '../../codecs.js';
+import { logger } from '../../logger.js';
+import {
+ fetchAllPreferenceOptionValues,
+ type PreferenceOptionValue,
+} from './fetchAllPreferenceOptionValues.js';
+import { CREATE_OR_UPDATE_PREFERENCE_OPTION_VALUES } from './gqls/index.js';
+import { makeGraphQLRequest } from './makeGraphQLRequest.js';
+
+/**
+ * Create or update preference option values
+ *
+ * @param client - GraphQL client
+ * @param optionValues - Preference option values paired with existing IDs
+ * @returns Created/updated preference option values
+ */
+export async function createOrUpdatePreferenceOptionValues(
+ client: GraphQLClient,
+ optionValues: [ConsentPreferenceTopicOptionValue, string | undefined][],
+): Promise {
+ const result = await makeGraphQLRequest<{
+ /** createOrUpdatePreferenceOptionValues mutation */
+ createOrUpdatePreferenceOptionValues: {
+ /** Preference option values */
+ preferenceOptionValues: PreferenceOptionValue[];
+ };
+ }>(client, CREATE_OR_UPDATE_PREFERENCE_OPTION_VALUES, {
+ input: {
+ input: {
+ preferenceOptionValues: optionValues.map(([optionValue, id]) => ({
+ ...optionValue,
+ id,
+ })),
+ },
+ },
+ });
+ return result.createOrUpdatePreferenceOptionValues.preferenceOptionValues;
+}
+
+/**
+ * Sync the preference option values
+ *
+ * @param client - GraphQL client
+ * @param optionValues - Preference option values
+ * @returns True if synced successfully
+ */
+export async function syncPreferenceOptionValues(
+ client: GraphQLClient,
+ optionValues: ConsentPreferenceTopicOptionValue[],
+): Promise {
+ let encounteredError = false;
+ logger.info(colors.magenta(`Syncing "${optionValues.length}" preference option values...`));
+
+ const existing = await fetchAllPreferenceOptionValues(client);
+ const optionValueBySlug = keyBy(existing, 'slug');
+
+ try {
+ logger.info(
+ colors.magenta(
+ `Performing bulk create or update for "${optionValues.length}" preference option values...`,
+ ),
+ );
+
+ await createOrUpdatePreferenceOptionValues(
+ client,
+ optionValues.map((optionValueInput) => [
+ optionValueInput,
+ optionValueBySlug[optionValueInput.slug]?.id,
+ ]),
+ );
+
+ logger.info(
+ colors.green(`Successfully synced "${optionValues.length}" preference option values!`),
+ );
+ } catch (err) {
+ encounteredError = true;
+ logger.info(colors.red(`Failed to sync preference option values! - ${err.message}`));
+ }
+
+ return !encounteredError;
+}
diff --git a/packages/cli/src/lib/graphql/syncPurposes.ts b/packages/cli/src/lib/graphql/syncPurposes.ts
new file mode 100644
index 00000000..8620096f
--- /dev/null
+++ b/packages/cli/src/lib/graphql/syncPurposes.ts
@@ -0,0 +1,262 @@
+import colors from 'colors';
+import { GraphQLClient } from 'graphql-request';
+import { keyBy } from 'lodash-es';
+
+import { ConsentPreferenceTopic, ConsentPurpose } from '../../codecs.js';
+import { logger } from '../../logger.js';
+import { map } from '../bluebird.js';
+import {
+ fetchAllPreferenceOptionValues,
+ type PreferenceOptionValue,
+} from './fetchAllPreferenceOptionValues.js';
+import { PreferenceTopic } from './fetchAllPreferenceTopics.js';
+import {
+ PurposeWithPreferences,
+ fetchAllPurposesAndPreferences,
+} from './fetchAllPurposesAndPreferences.js';
+import { UPDATE_PURPOSE, CREATE_PURPOSE, CREATE_OR_UPDATE_PREFERENCE_TOPIC } from './gqls/index.js';
+import { makeGraphQLRequest } from './makeGraphQLRequest.js';
+
+export interface PreferenceTopicSyncOptions {
+ /** Purpose ID */
+ purposeId: string;
+ /** Preference option values indexed by slug */
+ optionValuesBySlug: Record;
+ /** Existing preference topics indexed by slug */
+ topicsBySlug: Record;
+ /** Concurrency for upload */
+ concurrency: number;
+}
+
+/**
+ * Create or update preference topics for a purpose
+ *
+ * @param client - GraphQL client
+ * @param topics - Preference topics to create or update
+ * @param options - Options
+ */
+export async function createOrUpdatePreferenceTopics(
+ client: GraphQLClient,
+ topics: ConsentPreferenceTopic[],
+ { purposeId, optionValuesBySlug, topicsBySlug, concurrency = 20 }: PreferenceTopicSyncOptions,
+): Promise {
+ await map(
+ topics,
+ async (topic) => {
+ const existingTopic = topicsBySlug[topic.title];
+ await makeGraphQLRequest(client, CREATE_OR_UPDATE_PREFERENCE_TOPIC, {
+ input: {
+ type: topic.type,
+ title: topic.title,
+ showInPrivacyCenter: topic['show-in-privacy-center'],
+ purposeId,
+ ...(topic.options
+ ? {
+ preferenceOptionValueIds: topic.options.map((option) => {
+ const result = optionValuesBySlug[option.slug];
+ if (!result) {
+ throw new Error(
+ `Preference option value with slug "${option.slug}" not found.`,
+ );
+ }
+ return result.id;
+ }),
+ }
+ : {}),
+ ...(existingTopic ? { id: existingTopic.id } : {}),
+ displayDescription: topic.description,
+ defaultConfiguration: topic['default-configuration'],
+ },
+ });
+ },
+ { concurrency },
+ );
+}
+
+/**
+ * Create a new purpose
+ *
+ * @param client - GraphQL client
+ * @param input - Purpose input
+ * @param options - Options for syncing preference topics
+ * @returns Purpose ID
+ */
+export async function createPurpose(
+ client: GraphQLClient,
+ input: ConsentPurpose,
+ options: Omit,
+): Promise {
+ const {
+ createPurpose: { trackingPurpose },
+ } = await makeGraphQLRequest<{
+ /** createPurpose mutation */
+ createPurpose: {
+ /** Purpose */
+ trackingPurpose: {
+ /** ID */
+ id: string;
+ };
+ };
+ }>(client, CREATE_PURPOSE, {
+ input: {
+ trackingType: input.trackingType,
+ showInPrivacyCenter: input['show-in-privacy-center'],
+ showInConsentManager: input['show-in-consent-manager'],
+ optOutSignals: input['opt-out-signals'],
+ name: input.title,
+ isActive: input['is-active'],
+ description: input.description,
+ displayOrder: input['display-order'],
+ configurable: input.configurable,
+ authLevel: input['auth-level'],
+ },
+ });
+ logger.info(colors.green(`Successfully created purpose "${input.title}"!`));
+
+ if (input['preference-topics'] && input['preference-topics'].length > 0) {
+ await createOrUpdatePreferenceTopics(client, input['preference-topics'], {
+ ...options,
+ purposeId: trackingPurpose.id,
+ topicsBySlug: {},
+ });
+ logger.info(
+ colors.green(
+ `Successfully synced ${input['preference-topics'].length} preference topics for purpose "${input.title}"!`,
+ ),
+ );
+ }
+ return trackingPurpose.id;
+}
+
+/**
+ * Update an existing purpose
+ *
+ * @param client - GraphQL client
+ * @param input - Purpose input
+ * @param options - Options for syncing preference topics
+ */
+export async function updatePurpose(
+ client: GraphQLClient,
+ input: ConsentPurpose,
+ options: PreferenceTopicSyncOptions,
+): Promise {
+ await makeGraphQLRequest(client, UPDATE_PURPOSE, {
+ input: {
+ id: options.purposeId,
+ title: input.title,
+ showInPrivacyCenter: input['show-in-privacy-center'],
+ showInConsentManager: input['show-in-consent-manager'],
+ configurable: input.configurable,
+ optOutSignals: input['opt-out-signals'],
+ name: input.title,
+ isActive: input['is-active'],
+ displayOrder: input['display-order'],
+ description: input.description,
+ authLevel: input['auth-level'],
+ },
+ });
+ logger.info(
+ colors.green(
+ `Successfully updated purpose: ${options.purposeId}:${input.title || input.trackingType}!`,
+ ),
+ );
+
+ if (input['preference-topics'] && input['preference-topics'].length > 0) {
+ await createOrUpdatePreferenceTopics(client, input['preference-topics'], options);
+ logger.info(
+ colors.green(
+ `Successfully synced ${input['preference-topics'].length} preference topics for purpose "${
+ input.title || input.trackingType
+ }"!`,
+ ),
+ );
+ }
+}
+
+/**
+ * Sync the purposes
+ *
+ * @param client - GraphQL client
+ * @param purposes - Purposes
+ * @param concurrency - Concurrency
+ * @returns True if synced successfully
+ */
+export async function syncPurposes(
+ client: GraphQLClient,
+ purposes: ConsentPurpose[],
+ concurrency = 20,
+): Promise {
+ let encounteredError = false;
+ logger.info(colors.magenta(`Syncing "${purposes.length}" purposes...`));
+
+ const [existing, existingOptions] = await Promise.all([
+ fetchAllPurposesAndPreferences(client),
+ fetchAllPreferenceOptionValues(client),
+ ]);
+ const purposeByTrackingType = keyBy(existing, 'trackingType');
+ const optionValuesBySlug = keyBy(existingOptions, 'slug');
+
+ const mapPurposesToExisting = purposes.map((purposeInput) => [
+ purposeInput,
+ purposeByTrackingType[purposeInput.trackingType],
+ ]);
+
+ // Create new purposes
+ const newPurposes = mapPurposesToExisting
+ .filter(([, existing]) => !existing)
+ .map(([purposeInput]) => purposeInput as ConsentPurpose);
+ try {
+ logger.info(colors.magenta(`Creating "${newPurposes.length}" new purposes...`));
+ await map(
+ newPurposes,
+ async (purpose) => {
+ await createPurpose(client, purpose, {
+ concurrency,
+ optionValuesBySlug,
+ });
+ },
+ { concurrency },
+ );
+ logger.info(colors.green(`Successfully created ${newPurposes.length} purposes!`));
+ } catch (err) {
+ encounteredError = true;
+ logger.info(colors.red(`Failed to create purposes! - ${err.message}`));
+ }
+
+ // Update existing purposes
+ const existingPurposes = mapPurposesToExisting.filter(
+ (x): x is [ConsentPurpose, PurposeWithPreferences] => !!x[1],
+ );
+ try {
+ logger.info(colors.magenta(`Updating "${existingPurposes.length}" purposes...`));
+ await map(
+ existingPurposes,
+ async ([purposeInput, existingPurpose]) => {
+ try {
+ await updatePurpose(client, purposeInput, {
+ concurrency,
+ optionValuesBySlug,
+ purposeId: existingPurpose.id,
+ topicsBySlug: keyBy(existingPurpose.topics, 'slug'),
+ });
+ } catch (err) {
+ encounteredError = true;
+ logger.info(
+ colors.red(
+ `Failed to update purpose "${existingPurpose.id}" (${purposeInput.trackingType})! - ${err.message}`,
+ ),
+ );
+ }
+ },
+ { concurrency },
+ );
+ logger.info(colors.green(`Successfully updated "${existingPurposes.length}" purposes!`));
+ } catch (err) {
+ encounteredError = true;
+ logger.info(colors.red(`Failed to update purposes! - ${err.message}`));
+ }
+
+ logger.info(colors.green(`Synced "${purposes.length}" purposes!`));
+
+ return !encounteredError;
+}