diff --git a/README.md b/README.md index 30eb495..4a5191a 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,12 @@ googleTag: googleAnalytics: # Enable provider enabled: true + # Custom tag template settings + templates: + # Path to a custom json request value variable template file, defaults to the embedded one + jsonRequestValueVariable: '' + # Path to a custom gtag client template file, defaults to the embedded one + gtagClient: '' # Google Consent settings googleConsent: # Enable consent mode @@ -294,6 +300,10 @@ umami: websiteId: '' # Endpoint url of the umami api endpointUrl: https://umami.your-domain.com + # Custom tag template settings + templates: + # Path to a custom tag template file, defaults to the embedded one + tag: '' # Google Consent settings googleConsent: # Enable consent mode @@ -320,6 +330,12 @@ criteo: partnerId: 123456 # Criteo applicaiton id applicationId: com.foomo + # Custom tag template settings + templates: + # Path to a custom events api tag template file, defaults to the manually installed one + eventsApiTag: '' + # Path to a custom user identification template file, defaults to the manually installed one + userIdentification: '' # Google Tag Manager server container settings serverContainer: # Path to the go.mod file @@ -347,6 +363,10 @@ facebook: apiAccessToken: '' # Code used to verify that your server events are received correctly by Conversions API testEventToken: '' + # Custom tag template settings + templates: + # Path to a custom conversions api tag template file, defaults to the manually installed one + conversionsApiTag: '' # Google Tag Manager server container settings # Google Consent settings googleConsent: @@ -377,6 +397,14 @@ emarsys: enabled: true # Emarsys merchant id merchantId: '' + # Custom tag template settings + templates: + # Path to a custom web extend tag template file, defaults to the embedded one + webExtendTag: '' + # Path to a custom initialization client template file, defaults to the embedded one + initializationClient: '' + # Path to a custom initialization tag template file, defaults to the embedded one + initializationTag: '' # Google Consent settings googleConsent: # Enable consent mode @@ -403,6 +431,10 @@ tracify: token: '' # Tracify customer site id customerSiteId: '' + # Custom tag template settings + templates: + # Path to a custom tag template file, defaults to the embedded one + tag: '' # Google Consent settings googleConsent: # Enable consent mode @@ -436,6 +468,10 @@ cookiebot: # Name of the manually installed Cookiebot CMP tag template # "https://tagmanager.google.com/gallery/#/owners/cybotcorp/templates/gtm-templates-cookiebot-cmp templateName: Cookiebot CMP + # Custom tag template settings + templates: + # Path to a custom cmp tag template file, defaults to the manually installed one + tag: '' # Cookiebot id cookiebotId: '' # CDN Region (eu, com) @@ -463,6 +499,12 @@ cookiebot: utm: # Enable provider enabled: true + # Custom tag template settings + templates: + # Path to a custom attribution variable template file, defaults to the embedded one + attributionVariable: '' + # Path to a custom cookie writer tag template file, defaults to the embedded one + cookieWriterTag: '' # Google Consent settings googleConsent: # Enable consent mode @@ -476,8 +518,10 @@ mixpanel: enabled: true # Project Token projectToken: '' - # Lookup the manually installed "Mixpanel" tag template instead of provisioning it - lookupTagTemplate: false + # Custom tag template settings + templates: + # Path to a custom tag template file, defaults to the embedded one + tag: '' # Google Consent settings googleConsent: # Enable consent mode @@ -517,6 +561,10 @@ pinterest: apiAccessToken: '' # Enable test mode testModeEnabled: false + # Custom tag template settings + templates: + # Path to a custom conversions tag template file, defaults to the manually installed one + tag: '' # Google Consent settings googleConsent: # Enable consent mode diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 6958aa2..d0d4657 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -8,8 +8,8 @@ Thank you for your interest in contributing to our project! This guide will help 2. Create a new branch: `git checkout -b feature/your-feature-name` 3. Make your changes 4. Check and fix code style and formatting issues: `make lint` -5. Run checks: `make test` -6. Build the project: `make build` +5. Run tests: `make test` +6. Run final checks: `make check` 7. Commit your changes using the conventions below 8. Push your branch to your fork 9. Open a pull request @@ -29,10 +29,13 @@ We follow [Conventional Commits](https://www.conventionalcommits.org/) for clear ## Pull Request Guidelines -1. Update documentation if needed -2. Ensure all tests pass -3. Address any feedback from code reviews -4. Once approved, your PR will be merged +1. Adhere to the coding and style guidelines of the project +2. Perform a self-review of your own code +3. Comment your code, particularly in hard-to-understand areas +4. Make corresponding changes to the documentation +5. Ensure all tests pass +6. Address any feedback from code reviews +7. Once approved, your PR will be merged ## Code of Conduct & Security diff --git a/docs/providers/cookiebot.md b/docs/providers/cookiebot.md index 87b5cc4..e033963 100644 --- a/docs/providers/cookiebot.md +++ b/docs/providers/cookiebot.md @@ -8,6 +8,8 @@ Cookiebot CMP integration. The CLI does **not** install the Cookiebot tag templa cookiebot: enabled: true templateName: Cookiebot CMP # must match the imported template's name + templates: + tag: '' cookiebotId: '' cdnRegion: eu # eu or com urlPassthrough: false @@ -29,3 +31,4 @@ cookiebot: | `urlPassthrough` | Pass identifiers via URL parameters when consent is denied. | | `advertiserConsentModeEnabled` | Enable advertiser consent mode signals. | | `regionSettings[]` | Per-region default consent states. Leave `region` blank for the global default. | +| `templates.tag` | Path to a custom CMP tag template file. Empty (default) looks up the manually installed template. Overrides `templateName` lookup. | diff --git a/docs/providers/criteo.md b/docs/providers/criteo.md index da08c7d..62f1e62 100644 --- a/docs/providers/criteo.md +++ b/docs/providers/criteo.md @@ -10,6 +10,9 @@ criteo: callerId: 123 partnerId: 123456 applicationId: com.foomo + templates: + eventsApiTag: '' + userIdentification: '' serverContainer: directory: . packages: @@ -30,3 +33,5 @@ criteo: | `partnerId` | Criteo partner ID. | | `applicationId` | Application identifier (often the reversed-domain form). | | `serverContainer.packages` | Events to forward to Criteo. | +| `templates.eventsApiTag` | Path to a custom Events API tag template file. Empty (default) looks up the manually installed template. | +| `templates.userIdentification` | Path to a custom User Identification template file. Empty (default) looks up the manually installed template. | diff --git a/docs/providers/emarsys.md b/docs/providers/emarsys.md index 16607bb..6b279e7 100644 --- a/docs/providers/emarsys.md +++ b/docs/providers/emarsys.md @@ -8,6 +8,10 @@ Emarsys server-side integration. emarsys: enabled: true merchantId: '' + templates: + webExtendTag: '' + initializationClient: '' + initializationTag: '' googleConsent: enabled: true mode: analytics_storage @@ -26,3 +30,6 @@ emarsys: | `merchantId` | Emarsys merchant ID. | | `googleConsent.mode` | Defaults to `analytics_storage`. | | `serverContainer.packages` | Events to forward to Emarsys. | +| `templates.webExtendTag` | Path to a custom web extend tag template file. Empty (default) provisions the embedded template. | +| `templates.initializationClient` | Path to a custom initialization client template file. Empty (default) provisions the embedded template. | +| `templates.initializationTag` | Path to a custom initialization tag template file. Empty (default) provisions the embedded template. | diff --git a/docs/providers/facebook.md b/docs/providers/facebook.md index d14af7c..c6ac9fa 100644 --- a/docs/providers/facebook.md +++ b/docs/providers/facebook.md @@ -10,6 +10,8 @@ facebook: pixelId: '' apiAccessToken: '' testEventToken: '' + templates: + conversionsApiTag: '' googleConsent: enabled: true mode: ad_storage @@ -36,6 +38,7 @@ facebook: | `testEventToken` | Optional — use Meta's test events tool to verify the payload. | | `googleConsent.mode` | Defaults to `ad_storage`. | | `serverContainer.packages` | Events to send via Conversions API. | +| `templates.conversionsApiTag` | Path to a custom Conversions API tag template file. Empty (default) looks up the manually installed template. | ## Test events diff --git a/docs/providers/google-analytics.md b/docs/providers/google-analytics.md index 5c14102..2e4e948 100644 --- a/docs/providers/google-analytics.md +++ b/docs/providers/google-analytics.md @@ -8,6 +8,9 @@ GA4 web + server-side tagging. googleAnalytics: enabled: true propertyId: '' # used by `sesamy open ga` + templates: + jsonRequestValueVariable: '' + gtagClient: '' googleConsent: enabled: true mode: analytics_storage @@ -41,3 +44,5 @@ googleAnalytics: | `googleGTagJSOverride.ecommerceItems` | Normalize ecommerce items in the override. | | `webContainer.packages` | Events handled in the web container. | | `serverContainer.packages` | Events handled in the server container. | +| `templates.jsonRequestValueVariable` | Path to a custom JSON request value variable template file. Empty (default) provisions the embedded template. | +| `templates.gtagClient` | Path to a custom gtag client template file. Empty (default) provisions the embedded template. | diff --git a/docs/providers/microsoft-ads.md b/docs/providers/microsoft-ads.md index 2a5ae6f..919688c 100644 --- a/docs/providers/microsoft-ads.md +++ b/docs/providers/microsoft-ads.md @@ -8,6 +8,8 @@ Microsoft Ads (Bing UET) server-side conversion tracking. microsoftAds: enabled: true tagId: '' + templates: + conversionTag: '' googleConsent: enabled: true mode: ad_storage @@ -30,6 +32,7 @@ microsoftAds: | `googleConsent.mode` | Defaults to `ad_storage`. | | `conversion.serverContainer.settings.` | List of `{ conversionLabel }` pairs — one tag per entry. | | `conversion.serverContainer.packages` | Events to wire conversion tags for. | +| `templates.conversionTag` | Path to a custom conversion tag template file. Empty (default) provisions the embedded template. | ::: tip Field names are based on the current schema. Run `sesamy config -c sesamy.yaml` to see the resolved values your build will use. diff --git a/docs/providers/mixpanel.md b/docs/providers/mixpanel.md index 45d1a7a..02e85d9 100644 --- a/docs/providers/mixpanel.md +++ b/docs/providers/mixpanel.md @@ -10,7 +10,8 @@ Track tags send UTM attribution data, so the [UTM](./utm) provider must be enabl mixpanel: enabled: true projectToken: '' - lookupTagTemplate: false + templates: + tag: '' googleConsent: enabled: true mode: analytics_storage @@ -38,7 +39,7 @@ mixpanel: | Field | Purpose | |-------|---------| | `projectToken` | Mixpanel project token. | -| `lookupTagTemplate` | When `true`, look up an existing `Mixpanel` tag template instead of provisioning the built-in one. Defaults to `false`. | +| `templates.tag` | Path to a custom tag template file. Empty (default) provisions the embedded template. | | `googleConsent.mode` | Defaults to `analytics_storage`. | | `serverContainer.userId` | Event-data property holding the user ID, forwarded to Mixpanel. | | `serverContainer.track.packages` | Events to forward to Mixpanel's `/track` endpoint. | @@ -47,6 +48,14 @@ mixpanel: By default the `Mixpanel` tag template is provisioned by the CLI — no manual template install required. -Set `lookupTagTemplate: true` to use a template you manage yourself instead. The CLI then looks up a -template named `Mixpanel` in the server container and fails if it is missing — install the -[Mixpanel](https://github.com/stape-io/mixpanel-tag) template by stape-io from the GTM gallery first. +Set `templates.tag` to the path of a template file to provision your own instead: + +```yaml +mixpanel: + templates: + tag: ./templates/mixpanel.tpl +``` + +The file is uploaded verbatim as the template named `Mixpanel`, so it must be a complete GTM template +(`___INFO___`, `___TEMPLATE_PARAMETERS___`, `___SANDBOXED_JS_FOR_SERVER___`, …). Use the +[Mixpanel](https://github.com/stape-io/mixpanel-tag) template by stape-io as a starting point. diff --git a/docs/providers/pinterest.md b/docs/providers/pinterest.md index 6235fca..8bf568a 100644 --- a/docs/providers/pinterest.md +++ b/docs/providers/pinterest.md @@ -10,6 +10,8 @@ pinterest: advertiserId: '' apiAccessToken: '' testModeEnabled: false + templates: + tag: '' googleConsent: enabled: true mode: analytics_storage @@ -34,3 +36,4 @@ pinterest: | `testModeEnabled` | Send events with the `test` flag set — won't count toward live data. | | `googleConsent.mode` | Defaults to `analytics_storage`. | | `serverContainer.packages` | Events to send. | +| `templates.tag` | Path to a custom conversions tag template file. Empty (default) looks up the manually installed template. | diff --git a/docs/providers/tracify.md b/docs/providers/tracify.md index 93656fb..2aa95ee 100644 --- a/docs/providers/tracify.md +++ b/docs/providers/tracify.md @@ -9,6 +9,8 @@ tracify: enabled: true token: '' customerSiteId: '' + templates: + tag: '' googleConsent: enabled: true mode: analytics_storage @@ -29,3 +31,4 @@ tracify: | `customerSiteId` | Tracify customer site identifier. | | `googleConsent.mode` | Defaults to `analytics_storage`. | | `serverContainer.packages` | Events to forward. | +| `templates.tag` | Path to a custom tag template file. Empty (default) provisions the embedded template. | diff --git a/docs/providers/umami.md b/docs/providers/umami.md index 0329f08..42b6a4c 100644 --- a/docs/providers/umami.md +++ b/docs/providers/umami.md @@ -10,6 +10,8 @@ umami: domain: your-domain.com websiteId: '' endpointUrl: https://umami.your-domain.com + templates: + tag: '' googleConsent: enabled: true mode: analytics_storage @@ -29,3 +31,4 @@ umami: | `endpointUrl` | Base URL of your Umami instance. | | `googleConsent.mode` | Defaults to `analytics_storage`. | | `serverContainer.packages` | Events to forward. | +| `templates.tag` | Path to a custom tag template file. Empty (default) provisions the embedded template. | diff --git a/docs/providers/utm.md b/docs/providers/utm.md index 16cf67d..e7a5e15 100644 --- a/docs/providers/utm.md +++ b/docs/providers/utm.md @@ -7,6 +7,9 @@ UTM attribution for the server container. Persists the incoming UTM parameters i ```yaml utm: enabled: true + templates: + attributionVariable: '' + cookieWriterTag: '' googleConsent: enabled: true mode: ad_storage @@ -15,6 +18,8 @@ utm: | Field | Purpose | |-------|---------| | `googleConsent.mode` | Defaults to `ad_storage`. | +| `templates.attributionVariable` | Path to a custom attribution variable template file. Empty (default) provisions the embedded template. | +| `templates.cookieWriterTag` | Path to a custom cookie writer tag template file. Empty (default) provisions the embedded template. | ## What gets provisioned diff --git a/pkg/config/cookiebot.go b/pkg/config/cookiebot.go index 977da16..8952893 100644 --- a/pkg/config/cookiebot.go +++ b/pkg/config/cookiebot.go @@ -15,4 +15,11 @@ type Cookiebot struct { AdvertiserConsentModeEnabled bool `json:"advertiserConsentModeEnabled" yaml:"advertiserConsentModeEnabled"` // Default Consent state RegionSettings []CookiebotRegionSetting `json:"regionSettings" yaml:"regionSettings"` + // Custom tag template settings + Templates CookiebotTemplates `json:"templates" yaml:"templates"` +} + +type CookiebotTemplates struct { + // Path to a custom cmp tag template file, defaults to the manually installed one + Tag string `json:"tag" yaml:"tag"` } diff --git a/pkg/config/criteo.go b/pkg/config/criteo.go index f941f4e..f6200c5 100644 --- a/pkg/config/criteo.go +++ b/pkg/config/criteo.go @@ -13,8 +13,17 @@ type Criteo struct { PartnerID string `json:"partnerId" yaml:"partnerId"` // Criteo application id ApplicationID string `json:"applicationId" yaml:"applicationId"` + // Custom tag template settings + Templates CriteoTemplates `json:"templates" yaml:"templates"` // Google Consent settings GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"` // Google Tag Manager server container settings ServerContainer contemplate.Config `json:"serverContainer" yaml:"serverContainer"` } + +type CriteoTemplates struct { + // Path to a custom events api tag template file, defaults to the manually installed one + EventsAPITag string `json:"eventsApiTag" yaml:"eventsApiTag"` + // Path to a custom user identification template file, defaults to the manually installed one + UserIdentification string `json:"userIdentification" yaml:"userIdentification"` +} diff --git a/pkg/config/emarsys.go b/pkg/config/emarsys.go index ea6901d..6e727fe 100644 --- a/pkg/config/emarsys.go +++ b/pkg/config/emarsys.go @@ -13,6 +13,8 @@ type Emarsys struct { TestMode bool `json:"testMode" yaml:"testMode"` // Enable debug mode DebugMode bool `json:"debugMode" yaml:"debugMode"` + // Custom tag template settings + Templates EmarsysTemplates `json:"templates" yaml:"templates"` // Google Consent settings GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"` // Google Tag Manager web container settings @@ -20,3 +22,12 @@ type Emarsys struct { // Google Tag Manager server container settings ServerContainer contemplate.Config `json:"serverContainer" yaml:"serverContainer"` } + +type EmarsysTemplates struct { + // Path to a custom web extend tag template file, defaults to the embedded one + WebExtendTag string `json:"webExtendTag" yaml:"webExtendTag"` + // Path to a custom initialization client template file, defaults to the embedded one + InitializationClient string `json:"initializationClient" yaml:"initializationClient"` + // Path to a custom initialization tag template file, defaults to the embedded one + InitializationTag string `json:"initializationTag" yaml:"initializationTag"` +} diff --git a/pkg/config/facebook.go b/pkg/config/facebook.go index 82dc449..318190b 100644 --- a/pkg/config/facebook.go +++ b/pkg/config/facebook.go @@ -11,6 +11,8 @@ type ( PixelID string `json:"pixelId" yaml:"pixelId"` APIAccessToken string `json:"apiAccessToken" yaml:"apiAccessToken"` TestEventToken string `json:"testEventToken" yaml:"testEventToken"` + // Custom tag template settings + Templates FacebookTemplates `json:"templates" yaml:"templates"` // Google Consent settings GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"` // Google Tag Manager server container settings @@ -20,6 +22,10 @@ type ( contemplate.Config `json:",inline" yaml:",squash"` Settings map[string]FacebookConversionAPITag `json:"settings" yaml:"settings"` } + FacebookTemplates struct { + // Path to a custom conversions api tag template file, defaults to the manually installed one + ConversionsAPITag string `json:"conversionsApiTag" yaml:"conversionsApiTag"` + } ) func (s *FacebookServerContainer) Setting(eventName string) FacebookConversionAPITag { diff --git a/pkg/config/googleanalytics.go b/pkg/config/googleanalytics.go index 7131c82..491b315 100644 --- a/pkg/config/googleanalytics.go +++ b/pkg/config/googleanalytics.go @@ -17,8 +17,17 @@ type GoogleAnalytics struct { GoogleGTagJSOverride GoogleAnalyticsGTagJSOverride `json:"googleGTagJSOverride" yaml:"googleGTagJSOverride"` // Enable mpv2 user data transformation (experimental) EnableMPv2UserDataTransformation bool `json:"enableMPv2UserDataTransformation" yaml:"enableMPv2UserDataTransformation"` + // Custom tag template settings + Templates GoogleAnalyticsTemplates `json:"templates" yaml:"templates"` // Google Tag Manager web container settings WebContainer contemplate.Config `json:"webContainer" yaml:"webContainer"` // Google Tag Manager server container settings ServerContainer contemplate.Config `json:"serverContainer" yaml:"serverContainer"` } + +type GoogleAnalyticsTemplates struct { + // Path to a custom json request value variable template file, defaults to the embedded one + JSONRequestValueVariable string `json:"jsonRequestValueVariable" yaml:"jsonRequestValueVariable"` + // Path to a custom gtag client template file, defaults to the embedded one + GTagClient string `json:"gtagClient" yaml:"gtagClient"` +} diff --git a/pkg/config/microsoftads.go b/pkg/config/microsoftads.go index ade76de..c02fb95 100644 --- a/pkg/config/microsoftads.go +++ b/pkg/config/microsoftads.go @@ -9,4 +9,11 @@ type MicrosoftAds struct { TagID string `json:"tagId" yaml:"tagId"` // Microsoft Ads Conversion settings Conversion MicrosoftAdsConversion `json:"conversion" yaml:"conversion"` + // Custom tag template settings + Templates MicrosoftAdsTemplates `json:"templates" yaml:"templates"` +} + +type MicrosoftAdsTemplates struct { + // Path to a custom conversion tag template file, defaults to the embedded one + ConversionTag string `json:"conversionTag" yaml:"conversionTag"` } diff --git a/pkg/config/mixpanel.go b/pkg/config/mixpanel.go index 1c8b16f..4e42200 100644 --- a/pkg/config/mixpanel.go +++ b/pkg/config/mixpanel.go @@ -10,12 +10,16 @@ type ( Enabled bool `json:"enabled" yaml:"enabled"` // Mixpanel project token ProjectToken string `json:"projectToken" yaml:"projectToken"` - // Lookup tag template instead of provisioning it - LookupTagTemplate bool `json:"lookupTagTemplate" yaml:"lookupTagTemplate"` // Google Consent settings GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"` // Google Tag Manager server container settings ServerContainer MixpanelServerContainer `json:"serverContainer" yaml:"serverContainer"` + // Custom tag template settings + Templates MixpanelTemplates `json:"templates" yaml:"templates"` + } + MixpanelTemplates struct { + // Path to a custom tag template file, defaults to the embedded one + Tag string `json:"tag" yaml:"tag"` } MixpanelServerContainer struct { UserID string `json:"userId" yaml:"userId"` diff --git a/pkg/config/pinterest.go b/pkg/config/pinterest.go index ef0e648..3871b2e 100644 --- a/pkg/config/pinterest.go +++ b/pkg/config/pinterest.go @@ -13,8 +13,15 @@ type Pinterest struct { APIAccessToken string `json:"apiAccessToken" yaml:"apiAccessToken"` // Enable test mode TestModeEnabled bool `json:"testModeEnabled" yaml:"testModeEnabled"` + // Custom tag template settings + Templates PinterestTemplates `json:"templates" yaml:"templates"` // Google Consent settings GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"` // Google Tag Manager server container settings ServerContainer contemplate.Config `json:"serverContainer" yaml:"serverContainer"` } + +type PinterestTemplates struct { + // Path to a custom conversions tag template file, defaults to the manually installed one + Tag string `json:"tag" yaml:"tag"` +} diff --git a/pkg/config/tracify.go b/pkg/config/tracify.go index 42e948f..fcc8066 100644 --- a/pkg/config/tracify.go +++ b/pkg/config/tracify.go @@ -13,8 +13,15 @@ type Tracify struct { CustomerSiteID string `json:"customerSiteId" yaml:"customerSiteId"` // Enable stating mode StagingModeEnabled bool `json:"stagingModeEnabled" yaml:"stagingModeEnabled"` + // Custom tag template settings + Templates TracifyTemplates `json:"templates" yaml:"templates"` // Google Consent settings GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"` // Google Tag Manager server container settings ServerContainer contemplate.Config `json:"serverContainer" yaml:"serverContainer"` } + +type TracifyTemplates struct { + // Path to a custom tag template file, defaults to the embedded one + Tag string `json:"tag" yaml:"tag"` +} diff --git a/pkg/config/umami.go b/pkg/config/umami.go index c488f94..63d56ce 100644 --- a/pkg/config/umami.go +++ b/pkg/config/umami.go @@ -10,8 +10,15 @@ type Umami struct { Domain string `json:"domain" yaml:"domain"` WebsiteID string `json:"websiteId" yaml:"websiteId"` EndpointURL string `json:"endpointUrl" yaml:"endpointUrl"` + // Custom tag template settings + Templates UmamiTemplates `json:"templates" yaml:"templates"` // Google Consent settings GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"` // Google Tag Manager server container settings ServerContainer contemplate.Config `json:"serverContainer" yaml:"serverContainer"` } + +type UmamiTemplates struct { + // Path to a custom tag template file, defaults to the embedded one + Tag string `json:"tag" yaml:"tag"` +} diff --git a/pkg/config/utm.go b/pkg/config/utm.go index b500efb..396d8fc 100644 --- a/pkg/config/utm.go +++ b/pkg/config/utm.go @@ -3,6 +3,15 @@ package config type Utm struct { // Enable provider Enabled bool `json:"enabled" yaml:"enabled"` + // Custom tag template settings + Templates UtmTemplates `json:"templates" yaml:"templates"` // Google Consent settings GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"` } + +type UtmTemplates struct { + // Path to a custom attribution variable template file, defaults to the embedded one + AttributionVariable string `json:"attributionVariable" yaml:"attributionVariable"` + // Path to a custom cookie writer tag template file, defaults to the embedded one + CookieWriterTag string `json:"cookieWriterTag" yaml:"cookieWriterTag"` +} diff --git a/pkg/provider/cookiebot/web.go b/pkg/provider/cookiebot/web.go index 1f3b961..38531f2 100644 --- a/pkg/provider/cookiebot/web.go +++ b/pkg/provider/cookiebot/web.go @@ -6,7 +6,9 @@ import ( "github.com/foomo/sesamy-cli/pkg/config" "github.com/foomo/sesamy-cli/pkg/provider/cookiebot/web/tag" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" "github.com/pkg/errors" + tagmanager2 "google.golang.org/api/tagmanager/v2" ) func Web(ctx context.Context, tm *tagmanager.TagManager, cfg config.Cookiebot) error { @@ -16,7 +18,9 @@ func Web(ctx context.Context, tm *tagmanager.TagManager, cfg config.Cookiebot) e } { // create event tags - temmplate, err := tm.LookupTemplate(ctx, cfg.TemplateName) + temmplate, err := commontemplate.ResolveOrLookup(ctx, tm, cfg.Templates.Tag, cfg.TemplateName, func() (*tagmanager2.CustomTemplate, error) { + return tm.LookupTemplate(ctx, cfg.TemplateName) + }) if err != nil { return errors.Wrapf(err, "Failed to lookup `%s`, please install the `%s` gallery tag template first (%s)", cfg.TemplateName, "Cookiebot CMP", "https://tagmanager.google.com/gallery/#/owners/cybotcorp/templates/gtm-templates-cookiebot-cmp") } diff --git a/pkg/provider/criteo/server.go b/pkg/provider/criteo/server.go index 7580cd3..277fb74 100644 --- a/pkg/provider/criteo/server.go +++ b/pkg/provider/criteo/server.go @@ -10,9 +10,11 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/googleconsent" googleconsentvariable "github.com/foomo/sesamy-cli/pkg/provider/googleconsent/server/variable" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" commonvariable "github.com/foomo/sesamy-cli/pkg/tagmanager/common/variable" "github.com/foomo/sesamy-cli/pkg/utils" "github.com/pkg/errors" + tagmanager2 "google.golang.org/api/tagmanager/v2" ) func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg config.Criteo) error { @@ -21,7 +23,9 @@ func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg return err } - template, err := tm.LookupTemplate(ctx, NameCriteoEventsAPITemplate) + template, err := commontemplate.ResolveOrLookup(ctx, tm, cfg.Templates.EventsAPITag, NameCriteoEventsAPITemplate, func() (*tagmanager2.CustomTemplate, error) { + return tm.LookupTemplate(ctx, NameCriteoEventsAPITemplate) + }) if err != nil { if errors.Is(err, tagmanager.ErrNotFound) { l.Warn("Please install the 'Criteo Events API' Tag Template manually first") diff --git a/pkg/provider/criteo/web.go b/pkg/provider/criteo/web.go index 773974f..05ed3c9 100644 --- a/pkg/provider/criteo/web.go +++ b/pkg/provider/criteo/web.go @@ -8,7 +8,9 @@ import ( "github.com/foomo/sesamy-cli/pkg/config" client "github.com/foomo/sesamy-cli/pkg/provider/criteo/web/tag" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" commonvariable "github.com/foomo/sesamy-cli/pkg/tagmanager/common/variable" + tagmanager2 "google.golang.org/api/tagmanager/v2" ) func Web(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg config.Criteo) error { @@ -17,7 +19,9 @@ func Web(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg con return err } - template, err := tm.LookupTemplate(ctx, NameCriteoUserIdentificationTemplate) + template, err := commontemplate.ResolveOrLookup(ctx, tm, cfg.Templates.UserIdentification, NameCriteoUserIdentificationTemplate, func() (*tagmanager2.CustomTemplate, error) { + return tm.LookupTemplate(ctx, NameCriteoUserIdentificationTemplate) + }) if err != nil { if errors.Is(err, tagmanager.ErrNotFound) { l.Warn("Please install the 'Criteo User Identification' template manually first") diff --git a/pkg/provider/emarsys/server.go b/pkg/provider/emarsys/server.go index e54dc0c..e7e3e27 100644 --- a/pkg/provider/emarsys/server.go +++ b/pkg/provider/emarsys/server.go @@ -12,6 +12,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/googleconsent" googleconsentvariable "github.com/foomo/sesamy-cli/pkg/provider/googleconsent/server/variable" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" commonvariable "github.com/foomo/sesamy-cli/pkg/tagmanager/common/variable" "github.com/foomo/sesamy-cli/pkg/utils" "github.com/pkg/errors" @@ -29,12 +30,22 @@ func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg return err } - tagTemplate, err := tm.UpsertCustomTemplate(ctx, template.NewEmarsysWebExtendTag(NameServerEmarsysWebExtendTagTemplate)) + webExtendTemplate, err := commontemplate.Resolve(cfg.Templates.WebExtendTag, NameServerEmarsysWebExtendTagTemplate, template.NewEmarsysWebExtendTag(NameServerEmarsysWebExtendTagTemplate)) if err != nil { return err } - clientTemplate, err := tm.UpsertCustomTemplate(ctx, template.NewEmarsysInitializationClient(NameServerEmarsysInitalizationClientTemplate)) + tagTemplate, err := tm.UpsertCustomTemplate(ctx, webExtendTemplate) + if err != nil { + return err + } + + initializationClientTemplate, err := commontemplate.Resolve(cfg.Templates.InitializationClient, NameServerEmarsysInitalizationClientTemplate, template.NewEmarsysInitializationClient(NameServerEmarsysInitalizationClientTemplate)) + if err != nil { + return err + } + + clientTemplate, err := tm.UpsertCustomTemplate(ctx, initializationClientTemplate) if err != nil { return err } diff --git a/pkg/provider/emarsys/web.go b/pkg/provider/emarsys/web.go index 4dac16a..65b35d8 100644 --- a/pkg/provider/emarsys/web.go +++ b/pkg/provider/emarsys/web.go @@ -10,6 +10,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/googletag" commonvariable "github.com/foomo/sesamy-cli/pkg/provider/googletag/web/variable" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" commontrigger "github.com/foomo/sesamy-cli/pkg/tagmanager/common/trigger" "github.com/pkg/errors" ) @@ -21,7 +22,12 @@ func Web(ctx context.Context, tm *tagmanager.TagManager, cfg config.Emarsys) err } { // create initialization tag - tagTemplate, err := tm.UpsertCustomTemplate(ctx, template.NewEmarsysInitializationTag(NameWebEmarsysInitalizationTagTemplate)) + initializationTemplate, err := commontemplate.Resolve(cfg.Templates.InitializationTag, NameWebEmarsysInitalizationTagTemplate, template.NewEmarsysInitializationTag(NameWebEmarsysInitalizationTagTemplate)) + if err != nil { + return err + } + + tagTemplate, err := tm.UpsertCustomTemplate(ctx, initializationTemplate) if err != nil { return err } diff --git a/pkg/provider/facebook/server.go b/pkg/provider/facebook/server.go index c7781a8..c2b29a8 100644 --- a/pkg/provider/facebook/server.go +++ b/pkg/provider/facebook/server.go @@ -10,6 +10,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/googleconsent" googleconsentvariable "github.com/foomo/sesamy-cli/pkg/provider/googleconsent/server/variable" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" commonvariable "github.com/foomo/sesamy-cli/pkg/tagmanager/common/variable" "github.com/foomo/sesamy-cli/pkg/utils" "github.com/pkg/errors" @@ -43,7 +44,9 @@ func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg } } - template, err := tm.LookupTemplate(ctx, NameConversionsAPITagTemplate) + template, err := commontemplate.ResolveOrLookup(ctx, tm, cfg.Templates.ConversionsAPITag, NameConversionsAPITagTemplate, func() (*tagmanager2.CustomTemplate, error) { + return tm.LookupTemplate(ctx, NameConversionsAPITagTemplate) + }) if errors.Is(err, tagmanager.ErrNotFound) { l.Warn("Please install the 'Conversions API Tag' by 'facebookincubator' template manually first") return err diff --git a/pkg/provider/googleanalytics/server.go b/pkg/provider/googleanalytics/server.go index 90b8f66..69495cd 100644 --- a/pkg/provider/googleanalytics/server.go +++ b/pkg/provider/googleanalytics/server.go @@ -13,6 +13,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/googletag" "github.com/foomo/sesamy-cli/pkg/provider/googletagmanager" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" servertemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/server/template" servertransformation "github.com/foomo/sesamy-cli/pkg/tagmanager/server/transformation" servertrigger "github.com/foomo/sesamy-cli/pkg/tagmanager/server/trigger" @@ -61,7 +62,12 @@ func Server(ctx context.Context, tm *tagmanager.TagManager, cfg config.GoogleAna } if cfg.EnableMPv2UserDataTransformation { - userDataTemplate, err := tm.UpsertCustomTemplate(ctx, servertemplate.NewJSONRequestValue(NameJSONRequestValueTemplate)) + jsonRequestValueTemplate, err := commontemplate.Resolve(cfg.Templates.JSONRequestValueVariable, NameJSONRequestValueTemplate, servertemplate.NewJSONRequestValue(NameJSONRequestValueTemplate)) + if err != nil { + return err + } + + userDataTemplate, err := tm.UpsertCustomTemplate(ctx, jsonRequestValueTemplate) if err != nil { return err } @@ -87,7 +93,12 @@ func Server(ctx context.Context, tm *tagmanager.TagManager, cfg config.GoogleAna } if cfg.GoogleGTagJSOverride.Enabled { - template, err := tm.UpsertCustomTemplate(ctx, googleanalyticstemplate.NewGoogleGTagClient(NameGoogleGTagClientTemplate)) + gtagClientTemplate, err := commontemplate.Resolve(cfg.Templates.GTagClient, NameGoogleGTagClientTemplate, googleanalyticstemplate.NewGoogleGTagClient(NameGoogleGTagClientTemplate)) + if err != nil { + return err + } + + template, err := tm.UpsertCustomTemplate(ctx, gtagClientTemplate) if err != nil { return err } diff --git a/pkg/provider/microsoftads/server.go b/pkg/provider/microsoftads/server.go index d08ed58..927f8a5 100644 --- a/pkg/provider/microsoftads/server.go +++ b/pkg/provider/microsoftads/server.go @@ -11,6 +11,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/microsoftads/server/template" "github.com/foomo/sesamy-cli/pkg/provider/microsoftads/server/trigger" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" commonvariable "github.com/foomo/sesamy-cli/pkg/tagmanager/common/variable" "github.com/foomo/sesamy-cli/pkg/utils" "github.com/pkg/errors" @@ -28,7 +29,12 @@ func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg } if cfg.Conversion.Enabled { - tagTemplate, err := tm.UpsertCustomTemplate(ctx, template.NewConversionTag(NameConversionsTagTemplate)) + conversionTemplate, err := commontemplate.Resolve(cfg.Templates.ConversionTag, NameConversionsTagTemplate, template.NewConversionTag(NameConversionsTagTemplate)) + if err != nil { + return err + } + + tagTemplate, err := tm.UpsertCustomTemplate(ctx, conversionTemplate) if err != nil { return err } diff --git a/pkg/provider/mixpanel/server.go b/pkg/provider/mixpanel/server.go index 55c4878..20d8809 100644 --- a/pkg/provider/mixpanel/server.go +++ b/pkg/provider/mixpanel/server.go @@ -13,6 +13,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/mixpanel/server/trigger" utmprovider "github.com/foomo/sesamy-cli/pkg/provider/utm" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" commonvariable "github.com/foomo/sesamy-cli/pkg/tagmanager/common/variable" "github.com/foomo/sesamy-cli/pkg/tagmanager/server/variable" "github.com/foomo/sesamy-cli/pkg/utils" @@ -31,19 +32,14 @@ func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg return err } - var template *tagmanager2.CustomTemplate - if cfg.LookupTagTemplate { - if template, err = tm.LookupTemplate(ctx, NameTagTemplate); err != nil { - if errors.Is(err, tagmanager.ErrNotFound) { - l.Warn("Please install the 'Mixpanel' by stape-io Tag Template manually first") - } + tagTemplate, err := commontemplate.Resolve(cfg.Templates.Tag, NameTagTemplate, mixpaneltemplate.NewMixpanelTag(NameTagTemplate)) + if err != nil { + return err + } - return err - } - } else { - if template, err = tm.UpsertCustomTemplate(ctx, mixpaneltemplate.NewMixpanelTag(NameTagTemplate)); err != nil { - return err - } + template, err := tm.UpsertCustomTemplate(ctx, tagTemplate) + if err != nil { + return err } projectToken, err := tm.UpsertVariable(ctx, folder, commonvariable.NewConstant(NamePrjectTokenConstant, cfg.ProjectToken)) diff --git a/pkg/provider/pinterest/server.go b/pkg/provider/pinterest/server.go index 4b49c3d..42941c7 100644 --- a/pkg/provider/pinterest/server.go +++ b/pkg/provider/pinterest/server.go @@ -10,8 +10,10 @@ import ( containertag "github.com/foomo/sesamy-cli/pkg/provider/pinterest/server/tag" "github.com/foomo/sesamy-cli/pkg/provider/pinterest/server/trigger" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" "github.com/foomo/sesamy-cli/pkg/utils" "github.com/pkg/errors" + tagmanager2 "google.golang.org/api/tagmanager/v2" ) func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg config.Pinterest) error { @@ -20,7 +22,9 @@ func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg return err } - template, err := tm.LookupTemplate(ctx, NameTagTemplate) + template, err := commontemplate.ResolveOrLookup(ctx, tm, cfg.Templates.Tag, NameTagTemplate, func() (*tagmanager2.CustomTemplate, error) { + return tm.LookupTemplate(ctx, NameTagTemplate) + }) if err != nil { if errors.Is(err, tagmanager.ErrNotFound) { l.Warn("Please install the 'Pinterest API for Conversions Tag' Tag Template manually first") diff --git a/pkg/provider/tracify/server.go b/pkg/provider/tracify/server.go index 9af7b4d..91f5b7f 100644 --- a/pkg/provider/tracify/server.go +++ b/pkg/provider/tracify/server.go @@ -11,6 +11,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/tracify/server/template" "github.com/foomo/sesamy-cli/pkg/provider/tracify/server/trigger" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" commonvariable "github.com/foomo/sesamy-cli/pkg/tagmanager/common/variable" "github.com/foomo/sesamy-cli/pkg/utils" "github.com/pkg/errors" @@ -33,7 +34,12 @@ func Server(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, cfg return err } - tagTemplate, err := tm.UpsertCustomTemplate(ctx, template.NewTracifyTag(NameTracifyServerTagTemplate)) + tracifyTemplate, err := commontemplate.Resolve(cfg.Templates.Tag, NameTracifyServerTagTemplate, template.NewTracifyTag(NameTracifyServerTagTemplate)) + if err != nil { + return err + } + + tagTemplate, err := tm.UpsertCustomTemplate(ctx, tracifyTemplate) if err != nil { return err } diff --git a/pkg/provider/umami/server.go b/pkg/provider/umami/server.go index e11de31..d167109 100644 --- a/pkg/provider/umami/server.go +++ b/pkg/provider/umami/server.go @@ -10,6 +10,7 @@ import ( containertemplate "github.com/foomo/sesamy-cli/pkg/provider/umami/server/template" "github.com/foomo/sesamy-cli/pkg/provider/umami/server/trigger" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" "github.com/foomo/sesamy-cli/pkg/utils" "github.com/pkg/errors" ) @@ -20,7 +21,12 @@ func Server(ctx context.Context, tm *tagmanager.TagManager, cfg config.Umami) er return err } - template, err := tm.UpsertCustomTemplate(ctx, containertemplate.NewUmami(Name)) + umamiTemplate, err := commontemplate.Resolve(cfg.Templates.Tag, Name, containertemplate.NewUmami(Name)) + if err != nil { + return err + } + + template, err := tm.UpsertCustomTemplate(ctx, umamiTemplate) if err != nil { return err } diff --git a/pkg/provider/utm/server.go b/pkg/provider/utm/server.go index cc7492a..eadf1bb 100644 --- a/pkg/provider/utm/server.go +++ b/pkg/provider/utm/server.go @@ -11,6 +11,7 @@ import ( "github.com/foomo/sesamy-cli/pkg/provider/utm/server/trigger" containervariable "github.com/foomo/sesamy-cli/pkg/provider/utm/server/variable" "github.com/foomo/sesamy-cli/pkg/tagmanager" + commontemplate "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" "github.com/pkg/errors" ) @@ -20,7 +21,12 @@ func Server(ctx context.Context, tm *tagmanager.TagManager, cfg config.Utm) erro return err } - variableTemplate, err := tm.UpsertCustomTemplate(ctx, containertemplate.NewUtmAttribution(NameUtmAttributionVariableTemplate)) + attributionTemplate, err := commontemplate.Resolve(cfg.Templates.AttributionVariable, NameUtmAttributionVariableTemplate, containertemplate.NewUtmAttribution(NameUtmAttributionVariableTemplate)) + if err != nil { + return err + } + + variableTemplate, err := tm.UpsertCustomTemplate(ctx, attributionTemplate) if err != nil { return err } @@ -29,7 +35,12 @@ func Server(ctx context.Context, tm *tagmanager.TagManager, cfg config.Utm) erro return err } - tagTemplate, err := tm.UpsertCustomTemplate(ctx, containertemplate.NewUtmAttributionCookieWriter(NameUtmAttributionCookieWriterTemplate)) + cookieWriterTemplate, err := commontemplate.Resolve(cfg.Templates.CookieWriterTag, NameUtmAttributionCookieWriterTemplate, containertemplate.NewUtmAttributionCookieWriter(NameUtmAttributionCookieWriterTemplate)) + if err != nil { + return err + } + + tagTemplate, err := tm.UpsertCustomTemplate(ctx, cookieWriterTemplate) if err != nil { return err } diff --git a/pkg/tagmanager/common/template/customtemplate.go b/pkg/tagmanager/common/template/customtemplate.go new file mode 100644 index 0000000..b90bee4 --- /dev/null +++ b/pkg/tagmanager/common/template/customtemplate.go @@ -0,0 +1,57 @@ +package template + +import ( + "context" + "os" + + "github.com/pkg/errors" + "google.golang.org/api/tagmanager/v2" +) + +// NewFromFile loads a custom template from the given path. The file contents are +// used verbatim, so it must be a complete GTM template. +func NewFromFile(name, filename string) (*tagmanager.CustomTemplate, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, errors.Wrap(err, "failed to read tag template: "+filename) + } + + return &tagmanager.CustomTemplate{ + Name: name, + TemplateData: string(data), + }, nil +} + +// Resolve returns the template loaded from filename, or embedded if filename is empty. +func Resolve(filename, name string, embedded *tagmanager.CustomTemplate) (*tagmanager.CustomTemplate, error) { + if filename == "" { + return embedded, nil + } + + return NewFromFile(name, filename) +} + +// Upserter provisions a custom template, e.g. tagmanager.TagManager. +type Upserter interface { + UpsertCustomTemplate(ctx context.Context, item *tagmanager.CustomTemplate) (*tagmanager.CustomTemplate, error) +} + +// ResolveOrLookup provisions the template from filename when set, otherwise it falls +// back to lookup, which is used by providers relying on a manually installed template. +func ResolveOrLookup( + ctx context.Context, + tm Upserter, + filename, name string, + lookup func() (*tagmanager.CustomTemplate, error), +) (*tagmanager.CustomTemplate, error) { + if filename == "" { + return lookup() + } + + item, err := NewFromFile(name, filename) + if err != nil { + return nil, err + } + + return tm.UpsertCustomTemplate(ctx, item) +} diff --git a/pkg/tagmanager/common/template/customtemplate_test.go b/pkg/tagmanager/common/template/customtemplate_test.go new file mode 100644 index 0000000..984b8a6 --- /dev/null +++ b/pkg/tagmanager/common/template/customtemplate_test.go @@ -0,0 +1,153 @@ +package template_test + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/foomo/sesamy-cli/pkg/tagmanager/common/template" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/api/tagmanager/v2" +) + +func TestNewFromFile(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + data string + filename string + wantErr bool + }{ + { + name: "reads template verbatim", + data: "___INFO___\n\n{\n \"type\": \"TAG\"\n}\n", + filename: "mixpanel.tpl", + }, + { + name: "keeps percent verbs untouched", + data: "___INFO___ 100% \"displayName\": \"%s\"\n", + filename: "verbs.tpl", + }, + { + name: "missing file", + filename: "does-not-exist.tpl", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + path := filepath.Join(t.TempDir(), tt.filename) + if !tt.wantErr { + require.NoError(t, os.WriteFile(path, []byte(tt.data), 0o600)) + } + + actual, err := template.NewFromFile("Mixpanel", path) + if tt.wantErr { + require.Error(t, err) + assert.Nil(t, actual) + + return + } + + require.NoError(t, err) + assert.Equal(t, "Mixpanel", actual.Name) + assert.Equal(t, tt.data, actual.TemplateData) + }) + } +} + +func TestResolve(t *testing.T) { + t.Parallel() + + embedded := &tagmanager.CustomTemplate{Name: "Embedded", TemplateData: "___INFO___ embedded"} + + t.Run("empty path returns embedded", func(t *testing.T) { + t.Parallel() + + actual, err := template.Resolve("", "Custom", embedded) + require.NoError(t, err) + assert.Same(t, embedded, actual) + }) + + t.Run("path overrides embedded", func(t *testing.T) { + t.Parallel() + + path := filepath.Join(t.TempDir(), "custom.tpl") + require.NoError(t, os.WriteFile(path, []byte("___INFO___ custom"), 0o600)) + + actual, err := template.Resolve(path, "Custom", embedded) + require.NoError(t, err) + assert.Equal(t, "Custom", actual.Name) + assert.Equal(t, "___INFO___ custom", actual.TemplateData) + }) + + t.Run("missing path errors", func(t *testing.T) { + t.Parallel() + + actual, err := template.Resolve(filepath.Join(t.TempDir(), "nope.tpl"), "Custom", embedded) + require.Error(t, err) + assert.Nil(t, actual) + }) +} + +type upserterStub struct { + called bool + item *tagmanager.CustomTemplate +} + +func (u *upserterStub) UpsertCustomTemplate(_ context.Context, item *tagmanager.CustomTemplate) (*tagmanager.CustomTemplate, error) { + u.called = true + u.item = item + + return item, nil +} + +func TestResolveOrLookup(t *testing.T) { + t.Parallel() + + looked := &tagmanager.CustomTemplate{Name: "Gallery", TemplateData: "___INFO___ gallery"} + lookup := func() (*tagmanager.CustomTemplate, error) { return looked, nil } + + t.Run("empty path looks up without upserting", func(t *testing.T) { + t.Parallel() + + tm := &upserterStub{} + + actual, err := template.ResolveOrLookup(t.Context(), tm, "", "Custom", lookup) + require.NoError(t, err) + assert.Same(t, looked, actual) + assert.False(t, tm.called, "must not provision when no override is set") + }) + + t.Run("path upserts instead of looking up", func(t *testing.T) { + t.Parallel() + + path := filepath.Join(t.TempDir(), "custom.tpl") + require.NoError(t, os.WriteFile(path, []byte("___INFO___ custom"), 0o600)) + + tm := &upserterStub{} + + actual, err := template.ResolveOrLookup(t.Context(), tm, path, "Custom", lookup) + require.NoError(t, err) + require.True(t, tm.called, "must provision the custom template") + assert.Equal(t, "Custom", actual.Name) + assert.Equal(t, "___INFO___ custom", actual.TemplateData) + }) + + t.Run("missing path errors without upserting", func(t *testing.T) { + t.Parallel() + + tm := &upserterStub{} + + actual, err := template.ResolveOrLookup(t.Context(), tm, filepath.Join(t.TempDir(), "nope.tpl"), "Custom", lookup) + require.Error(t, err) + assert.Nil(t, actual) + assert.False(t, tm.called) + }) +} diff --git a/pkg/tagmanager/tagmanager.go b/pkg/tagmanager/tagmanager.go index 7f62ab4..5a34dfb 100644 --- a/pkg/tagmanager/tagmanager.go +++ b/pkg/tagmanager/tagmanager.go @@ -928,8 +928,8 @@ func (t *TagManager) UpsertCustomTemplate(ctx context.Context, item *tagmanager. } else if strings.Trim(item.TemplateData, "\n") == strings.Trim(cache.TemplateData, "\n") { l.Info("└ ✔︎ OK", "id", cache.TemplateId) } else { - edits := myers.ComputeEdits(span.URIFromPath("item.txt"), item.TemplateData, cache.TemplateData) - diff := gotextdiff.ToUnified("item.txt", "cache.txt", item.TemplateData, edits) + edits := myers.ComputeEdits(span.URIFromPath("item.tpl"), cache.TemplateData, item.TemplateData) + diff := gotextdiff.ToUnified("item.tpl", "cache.tpl", cache.TemplateData, edits) l.Info("└ 🔄 Update", "id", cache.TemplateId) l.Info(fmt.Sprint(diff)) diff --git a/sesamy.schema.json b/sesamy.schema.json index 741fda1..54abbc6 100644 --- a/sesamy.schema.json +++ b/sesamy.schema.json @@ -187,6 +187,10 @@ "regionSettings": { "$ref": "#/$defs/[]config.CookiebotRegionSetting", "description": "Default Consent state" + }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.CookiebotTemplates", + "description": "Custom tag template settings" } }, "additionalProperties": false, @@ -222,6 +226,16 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.CookiebotTemplates": { + "properties": { + "tag": { + "type": "string", + "description": "Path to a custom cmp tag template file, defaults to the manually installed one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.Criteo": { "properties": { "enabled": { @@ -240,6 +254,10 @@ "type": "string", "description": "Criteo application id" }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.CriteoTemplates", + "description": "Custom tag template settings" + }, "googleConsent": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleConsent", "description": "Google Consent settings" @@ -252,6 +270,20 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.CriteoTemplates": { + "properties": { + "eventsApiTag": { + "type": "string", + "description": "Path to a custom events api tag template file, defaults to the manually installed one" + }, + "userIdentification": { + "type": "string", + "description": "Path to a custom user identification template file, defaults to the manually installed one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.Emarsys": { "properties": { "enabled": { @@ -270,6 +302,10 @@ "type": "boolean", "description": "Enable debug mode" }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.EmarsysTemplates", + "description": "Custom tag template settings" + }, "googleConsent": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleConsent", "description": "Google Consent settings" @@ -286,6 +322,24 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.EmarsysTemplates": { + "properties": { + "webExtendTag": { + "type": "string", + "description": "Path to a custom web extend tag template file, defaults to the embedded one" + }, + "initializationClient": { + "type": "string", + "description": "Path to a custom initialization client template file, defaults to the embedded one" + }, + "initializationTag": { + "type": "string", + "description": "Path to a custom initialization tag template file, defaults to the embedded one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.Facebook": { "properties": { "enabled": { @@ -301,6 +355,10 @@ "testEventToken": { "type": "string" }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.FacebookTemplates", + "description": "Custom tag template settings" + }, "googleConsent": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleConsent", "description": "Google Consent settings" @@ -342,6 +400,16 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.FacebookTemplates": { + "properties": { + "conversionsApiTag": { + "type": "string", + "description": "Path to a custom conversions api tag template file, defaults to the manually installed one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.GoogleAPI": { "properties": { "credentials": { @@ -466,6 +534,10 @@ "type": "boolean", "description": "Enable mpv2 user data transformation (experimental)" }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleAnalyticsTemplates", + "description": "Custom tag template settings" + }, "webContainer": { "$ref": "#/$defs/github.com.foomo.gocontemplate.pkg.contemplate.Config", "description": "Google Tag Manager web container settings" @@ -496,6 +568,20 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.GoogleAnalyticsTemplates": { + "properties": { + "jsonRequestValueVariable": { + "type": "string", + "description": "Path to a custom json request value variable template file, defaults to the embedded one" + }, + "gtagClient": { + "type": "string", + "description": "Path to a custom gtag client template file, defaults to the embedded one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.GoogleConsent": { "properties": { "enabled": { @@ -659,6 +745,10 @@ "conversion": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.MicrosoftAdsConversion", "description": "Microsoft Ads Conversion settings" + }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.MicrosoftAdsTemplates", + "description": "Custom tag template settings" } }, "additionalProperties": false, @@ -705,6 +795,16 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.MicrosoftAdsTemplates": { + "properties": { + "conversionTag": { + "type": "string", + "description": "Path to a custom conversion tag template file, defaults to the embedded one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.Mixpanel": { "properties": { "enabled": { @@ -715,10 +815,6 @@ "type": "string", "description": "Mixpanel project token" }, - "lookupTagTemplate": { - "type": "boolean", - "description": "Lookup tag template instead of provisioning it" - }, "googleConsent": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleConsent", "description": "Google Consent settings" @@ -726,6 +822,10 @@ "serverContainer": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.MixpanelServerContainer", "description": "Google Tag Manager server container settings" + }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.MixpanelTemplates", + "description": "Custom tag template settings" } }, "additionalProperties": false, @@ -756,6 +856,16 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.MixpanelTemplates": { + "properties": { + "tag": { + "type": "string", + "description": "Path to a custom tag template file, defaults to the embedded one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.Pinterest": { "properties": { "enabled": { @@ -774,6 +884,10 @@ "type": "boolean", "description": "Enable test mode" }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.PinterestTemplates", + "description": "Custom tag template settings" + }, "googleConsent": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleConsent", "description": "Google Consent settings" @@ -786,6 +900,16 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.PinterestTemplates": { + "properties": { + "tag": { + "type": "string", + "description": "Path to a custom conversions tag template file, defaults to the manually installed one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.Tracify": { "properties": { "enabled": { @@ -804,6 +928,10 @@ "type": "boolean", "description": "Enable stating mode" }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.TracifyTemplates", + "description": "Custom tag template settings" + }, "googleConsent": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleConsent", "description": "Google Consent settings" @@ -816,6 +944,16 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.TracifyTemplates": { + "properties": { + "tag": { + "type": "string", + "description": "Path to a custom tag template file, defaults to the embedded one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.TypeScript": { "properties": { "directory": { @@ -846,6 +984,10 @@ "endpointUrl": { "type": "string" }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.UmamiTemplates", + "description": "Custom tag template settings" + }, "googleConsent": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleConsent", "description": "Google Consent settings" @@ -858,12 +1000,26 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.UmamiTemplates": { + "properties": { + "tag": { + "type": "string", + "description": "Path to a custom tag template file, defaults to the embedded one" + } + }, + "additionalProperties": false, + "type": "object" + }, "github.com.foomo.sesamy-cli.pkg.config.Utm": { "properties": { "enabled": { "type": "boolean", "description": "Enable provider" }, + "templates": { + "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.UtmTemplates", + "description": "Custom tag template settings" + }, "googleConsent": { "$ref": "#/$defs/github.com.foomo.sesamy-cli.pkg.config.GoogleConsent", "description": "Google Consent settings" @@ -872,6 +1028,20 @@ "additionalProperties": false, "type": "object" }, + "github.com.foomo.sesamy-cli.pkg.config.UtmTemplates": { + "properties": { + "attributionVariable": { + "type": "string", + "description": "Path to a custom attribution variable template file, defaults to the embedded one" + }, + "cookieWriterTag": { + "type": "string", + "description": "Path to a custom cookie writer tag template file, defaults to the embedded one" + } + }, + "additionalProperties": false, + "type": "object" + }, "map[string][]config.GoogleAdsConversionTracking": { "additionalProperties": { "$ref": "#/$defs/[]config.GoogleAdsConversionTracking"