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; +}