From 68d553d858d428298ba3d65e983e2b4fa2a41812 Mon Sep 17 00:00:00 2001 From: adatzer Date: Fri, 27 Oct 2023 18:44:31 +0300 Subject: [PATCH 1/3] Add GitHub actions (close #1) --- .github/workflows/cd.yml | 58 ++++++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 29 ++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..e05db53 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,58 @@ +name: cd + +on: + push: + tags: + - '*.*.*' + +jobs: + version: + runs-on: ubuntu-22.04 + outputs: + v_tag: ${{ steps.version.outputs.TAG_VERSION }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Get tag version + id: version + run: echo "TAG_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + lint: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.9 + + - name: Install yamllint + run: | + python -m pip install --upgrade pip + python -m pip install --user yamllint + + - name: Lint metadata file + run: | + yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" metadata.yaml + + release: + needs: ["lint", "version"] + runs-on: ubuntu-22.04 + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Release + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ needs.version.outputs.v_tag }} + name: Version ${{ needs.version.outputs.v_tag }} + draft: false + prerelease: ${{ contains(needs.version.outputs.v_tag, 'rc') }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e4a8a0a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: ci + +on: + push: + branches: + - main + pull_request: + +jobs: + lint: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.9 + + - name: Install yamllint + run: | + python -m pip install --upgrade pip + python -m pip install --user yamllint + + - name: Lint metadata file + run: | + yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" metadata.yaml From dd1521c6abff2ec85de0bf0246b8f609d8f332bf Mon Sep 17 00:00:00 2001 From: adatzer Date: Wed, 8 Nov 2023 08:15:45 +0200 Subject: [PATCH 2/3] Add initial template (close #2) --- template.tpl | 2400 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2400 insertions(+) create mode 100644 template.tpl diff --git a/template.tpl b/template.tpl new file mode 100644 index 0000000..75c704a --- /dev/null +++ b/template.tpl @@ -0,0 +1,2400 @@ +___TERMS_OF_SERVICE___ + +By creating or modifying this file you agree to Google Tag Manager's Community +Template Gallery Developer Terms of Service available at +https://developers.google.com/tag-manager/gallery-tos (or such other URL as +Google may provide), as modified from time to time. + + +___INFO___ + +{ + "displayName": "Snowplow v3 Ecommerce", + "description": "Instrument your Snowplow Ecommerce tracking with Snowplow JavaScript tracker library (v3).", + "__wm": "VGVtcGxhdGUtQXV0aG9yX1Nub3dwbG93QW5hbHl0aWNzVjNUYWctU2ltby1BaGF2YQ\u003d\u003d", + "securityGroups": [], + "categories": [ + "ANALYTICS" + ], + "id": "cvt_temp_public_id", + "type": "TAG", + "version": 1, + "brand": { + "displayName": "snowplow", + "id": "github.com_snowplow", + "thumbnail": "data:image/png;base64,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jQB7s1+193/x9WGk2qpIZHb3Lnds1fNk5KV/kUpBxT1kgI3r/uzxs3/6w4u5yf8lfRGrSASd9//LxPHp2YdVG9b92aOX/vW/mtXzlOEQrdg0LX6esp9/8Zd/8l1825872wwwk5tMVB/tnf77//4/Xnv87B83ov88g4ddmIFYLD0wnERKIpciMpKyDWvnE/XVGfEyZGaCQ38PU5faq1z5OReLAzJjUwkkE1vOf/1Pb9Xx4NweDCCWSw3cTJ4dvMU0kqx3u6stzlbtduEhuuVSrbvS6CVDItNce/tAM4OHfV4I+gGodMnWxvRCZCHCTIRsxMzDXjxEd0TxYPZ//u//8+ff/dP/Wxv08xCtJlqxcfqb90/+222V/2Yq6GchNTwDccz0tw+jJ85/ehFplU3ktzMtO1Op1l28KGg5BhBH9Wa/au/88HP2ESKj8B6SWxhAHIdedCPSJdnSmGJ7ErcwgITA9LcPo/1ffH34vetsLU7+EhnHa9v/8stDu7f3h+EGftgwgIRMJjeZ+P3fTfyGZySkU1N9deY3f113kdVVbmMACam5x0/LJvIP6jKF7a2J/INald5aRNGKjdPRik35qopN09GKjflErEZ6eibZjQGEiIiU8CY6EREpYQAhIiIlDCBERKSEAYSIiJQwgBARkRIGECIiUsIAQkREShhAiIhICQMIEREpYQAhIiIlDCBERKSEAYSIiJQwgBARkRIGECIiUsIAQkREShhAiIhICQMIEREpYQAhIiIlDCBERKSEAYSIiJQwgBARkRIGECIiUsIAQkREShhAiIhICQMIEREpYQAhIiIlDCBERKSEAYSIiJQwgBARkRIGECIiUsIAQkREShhAiIhICQMIEREpYQAhIiIlDCBERKSEAYSIiJQwgBARkRIGECIiUsIAQkREShhAiIhICQMIEREpYQAhIiIlDCBERKSEAYSIiJQwgBARkRIGECIiUsIAQkREShhAiIhICQMIEREpYQAhIiIlDCBERKSEAYSIiJT8/wAAAP//k6HgDMXTtssAAAAASUVORK5CYII\u003d" + }, + "containerContexts": [ + "WEB" + ] +} + + +___TEMPLATE_PARAMETERS___ + +[ + { + "type": "SELECT", + "name": "ecomAPI", + "displayName": "Ecommerce API", + "macrosInSelect": false, + "selectItems": [ + { + "value": "spEcommerce", + "displayValue": "Snowplow Ecommerce" + }, + { + "value": "ga4Ecommerce", + "displayValue": "Google Analytics 4 Ecommerce" + }, + { + "value": "uaEcommerce", + "displayValue": "Universal Analytics Enhanced Ecommerce" + } + ], + "simpleValueType": true, + "defaultValue": "spEcommerce", + "help": "Use the native Snowplow Ecommerce API or transitional GA4/UA ecommerce adapter APIs for existing dataLayer implementations using those formats. To get full value from the Snowplow Ecommerce plugin we recommend using the native API when possible.", + "alwaysInSummary": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + }, + { + "type": "GROUP", + "name": "snowplowGroup", + "displayName": "Tracking Parameters", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "SELECT", + "name": "spEcomFunction", + "displayName": "Ecommerce Function", + "macrosInSelect": true, + "selectItems": [ + { + "value": "trackProductView", + "displayValue": "Track Product View" + }, + { + "value": "trackAddToCart", + "displayValue": "Track Add To Cart" + }, + { + "value": "trackRemoveFromCart", + "displayValue": "Track Remove From Cart" + }, + { + "value": "trackProductListView", + "displayValue": "Track Product List View" + }, + { + "value": "trackProductListClick", + "displayValue": "Track Product List Click" + }, + { + "value": "trackPromotionView", + "displayValue": "Track Promotion View" + }, + { + "value": "trackPromotionClick", + "displayValue": "Track Promotion Click" + }, + { + "value": "trackCheckoutStep", + "displayValue": "Track Checkout Step" + }, + { + "value": "trackTransaction", + "displayValue": "Track Transaction" + }, + { + "value": "trackRefund", + "displayValue": "Track Refund" + }, + { + "value": "trackTransactionError", + "displayValue": "Track Transaction Error" + }, + { + "value": "setPageType", + "displayValue": "Set Page Type" + }, + { + "value": "setEcommerceUser", + "displayValue": "Set Ecommerce User" + } + ], + "simpleValueType": true, + "help": "Specify the Snowplow Ecommerce function to use.", + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ], + "defaultValue": "trackProductView", + "alwaysInSummary": true, + "enablingConditions": [] + }, + { + "type": "TEXT", + "name": "spEcomArg", + "displayName": "Ecommerce Argument", + "simpleValueType": true, + "alwaysInSummary": true, + "help": "Specify the argument to the ecommerce function. This can be a Variable that evaluates to a corresponding object.", + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ], + "enablingConditions": [] + }, + { + "displayName": "Additional Tracking Parameters", + "name": "commonEventProperties", + "groupStyle": "ZIPPY_CLOSED", + "type": "GROUP", + "subParams": [ + { + "help": "Use this table to attach custom context entities to the Snowplow event. Each row can be set to a Google Tag Manager variable that returns an \u003cstrong\u003earray\u003c/strong\u003e of custom contexts to add to the event hit. \u003ca href\u003d\"https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/javascript-tracker/javascript-tracker-v3/tracking-events/#custom-context\"\u003eRead more\u003c/a\u003e.", + "displayName": "Add Custom Context Entities", + "name": "customContexts", + "simpleTableColumns": [ + { + "macrosInSelect": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ], + "defaultValue": "", + "displayName": "Context Entities", + "name": "entitiesVariable", + "type": "SELECT" + } + ], + "type": "SIMPLE_TABLE" + }, + { + "help": "Set this to a UNIX timestamp in case you want to override the default timestamp used by Snowplow. \u003ca href\u003d\"https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/web-tracker/tracking-events/#setting-the-true-timestamp\"\u003eRead more\u003c/a\u003e.", + "displayName": "Set Custom Timestamp", + "simpleValueType": true, + "name": "trueTimestamp", + "valueUnit": "milliseconds (UNIX timestamp)", + "type": "TEXT", + "valueHint": "Use system time", + "valueValidators": [ + { + "type": "POSITIVE_NUMBER" + } + ] + } + ], + "enablingConditions": [ + { + "paramName": "spEcomFunction", + "paramValue": "trackProductView", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackAddToCart", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackRemoveFromCart", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackProductListView", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackProductListClick", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackPromotionView", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackPromotionClick", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackCheckoutStep", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackTransaction", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackRefund", + "type": "EQUALS" + }, + { + "paramName": "spEcomFunction", + "paramValue": "trackTransactionError", + "type": "EQUALS" + } + ] + } + ], + "enablingConditions": [ + { + "paramName": "ecomAPI", + "paramValue": "spEcommerce", + "type": "EQUALS" + } + ] + }, + { + "type": "GROUP", + "name": "ga4Group", + "displayName": "Tracking Parameters", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "SELECT", + "name": "ga4EcomFunction", + "displayName": "GA4 Ecommerce Function", + "macrosInSelect": true, + "selectItems": [ + { + "value": "trackGA4ViewItemList", + "displayValue": "Track GA4 View Item List" + }, + { + "value": "trackGA4SelectItem", + "displayValue": "Track GA4 Select Item" + }, + { + "value": "trackGA4ViewItem", + "displayValue": "Track GA4 View Item" + }, + { + "value": "trackGA4ViewPromotion", + "displayValue": "Track GA4 View Promotion" + }, + { + "value": "trackGA4SelectPromotion", + "displayValue": "Track GA4 Select Promotion" + }, + { + "value": "trackGA4AddToCart", + "displayValue": "Track GA4 Add To Cart" + }, + { + "value": "trackGA4RemoveFromCart", + "displayValue": "Track GA4 Remove From Cart" + }, + { + "value": "trackGA4BeginCheckout", + "displayValue": "Track GA4 Begin Checkout" + }, + { + "value": "trackGA4AddShippingInfo", + "displayValue": "Track GA4 Add Shipping Info" + }, + { + "value": "trackGA4AddPaymentOptions", + "displayValue": "Track GA4 Add Payment Options" + }, + { + "value": "trackGA4Transaction", + "displayValue": "Track GA4 Transaction" + } + ], + "simpleValueType": true, + "defaultValue": "trackGA4ViewItemList", + "alwaysInSummary": true, + "enablingConditions": [], + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ], + "help": "Specify the Google Analytics 4 Ecommerce function to use." + }, + { + "type": "GROUP", + "name": "ga4EcomArgsGroup", + "displayName": "Ecommerce Arguments", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "TEXT", + "name": "ga4DataLayerEcommerce", + "displayName": "DataLayer ecommerce", + "simpleValueType": true, + "help": "The dataLayer ecommerce variable", + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ], + "enablingConditions": [ + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4BeginCheckout", + "type": "NOT_EQUALS" + } + ] + }, + { + "type": "TEXT", + "name": "ga4EcomOptions", + "displayName": "Options object", + "simpleValueType": true, + "help": "Additional information for the ecommerce event (e.g.including currency, finalCartValue, step etc)", + "enablingConditions": [ + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4ViewItemList", + "type": "EQUALS" + }, + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4SelectItem", + "type": "EQUALS" + }, + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4ViewItem", + "type": "EQUALS" + }, + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4AddToCart", + "type": "EQUALS" + }, + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4RemoveFromCart", + "type": "EQUALS" + }, + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4BeginCheckout", + "type": "EQUALS" + }, + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4AddShippingInfo", + "type": "EQUALS" + }, + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4AddPaymentOptions", + "type": "EQUALS" + }, + { + "paramName": "ga4EcomFunction", + "paramValue": "trackGA4Transaction", + "type": "EQUALS" + } + ] + } + ], + "enablingConditions": [] + } + ], + "enablingConditions": [ + { + "paramName": "ecomAPI", + "paramValue": "ga4Ecommerce", + "type": "EQUALS" + } + ] + }, + { + "type": "GROUP", + "name": "uaGroup", + "displayName": "Tracking Parameters", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "SELECT", + "name": "uaEcomFunction", + "displayName": "UA Enhanced Ecommerce Function", + "macrosInSelect": true, + "selectItems": [ + { + "value": "trackEnhancedEcommerceProductListView", + "displayValue": "Track Enhanced Ecommerce Product List View" + }, + { + "value": "trackEnhancedEcommerceProductListClick", + "displayValue": "Track Enhanced Ecommerce Product List Click" + }, + { + "value": "trackEnhancedEcommerceProductDetail", + "displayValue": "Track Enhanced Ecommerce Product Detail" + }, + { + "value": "trackEnhancedEcommercePromoView", + "displayValue": "Track Enhanced Ecommerce Promo View" + }, + { + "value": "trackEnhancedEcommercePromoClick", + "displayValue": "Track Enhanced Ecommerce Promo Click" + }, + { + "value": "trackEnhancedEcommerceAddToCart", + "displayValue": "Track Enhanced Ecommerce Add To Cart" + }, + { + "value": "trackEnhancedEcommerceRemoveFromCart", + "displayValue": "Track Enhanced Ecommerce Remove From Cart" + }, + { + "value": "trackEnhancedEcommerceCheckoutStep", + "displayValue": "Track Enhanced Ecommerce Checkout Step" + }, + { + "value": "trackEnhancedEcommercePurchase", + "displayValue": "Track Enhanced Ecommerce Purchase" + } + ], + "simpleValueType": true, + "alwaysInSummary": true, + "defaultValue": "trackEnhancedEcommerceProductListView", + "enablingConditions": [], + "help": "Specify the Universal Analytics Enhanced Ecommerce function to use.", + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + }, + { + "type": "GROUP", + "name": "uaEcomArgsGroup", + "displayName": "Ecommerce Arguments", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "TEXT", + "name": "uaDataLayerEcommerce", + "displayName": "DataLayer ecommerce", + "simpleValueType": true, + "help": "The dataLayer ecommerce variable", + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + }, + { + "type": "TEXT", + "name": "uaEcomOptions", + "displayName": "Options object", + "simpleValueType": true, + "help": "Additional information for the ecommerce event (e.g.including currency, finalCartValue, etc)", + "enablingConditions": [ + { + "paramName": "uaEcomFunction", + "paramValue": "trackEnhancedEcommerceProductListView", + "type": "EQUALS" + }, + { + "paramName": "uaEcomFunction", + "paramValue": "trackEnhancedEcommerceProductListClick", + "type": "EQUALS" + }, + { + "paramName": "uaEcomFunction", + "paramValue": "trackEnhancedEcommerceProductDetail", + "type": "EQUALS" + }, + { + "paramName": "uaEcomFunction", + "paramValue": "trackEnhancedEcommerceAddToCart", + "type": "EQUALS" + }, + { + "paramName": "uaEcomFunction", + "paramValue": "trackEnhancedEcommerceRemoveFromCart", + "type": "EQUALS" + }, + { + "paramName": "uaEcomFunction", + "paramValue": "trackEnhancedEcommerceCheckoutStep", + "type": "EQUALS" + }, + { + "paramName": "uaEcomFunction", + "paramValue": "trackEnhancedEcommercePurchase", + "type": "EQUALS" + } + ] + } + ], + "enablingConditions": [] + } + ], + "enablingConditions": [ + { + "paramName": "ecomAPI", + "paramValue": "uaEcommerce", + "type": "EQUALS" + } + ] + }, + { + "displayName": "Snowplow Tracker and Ecommerce Plugin Settings", + "name": "trackerConfiguration", + "groupStyle": "ZIPPY_OPEN", + "type": "GROUP", + "subParams": [ + { + "type": "GROUP", + "name": "trackerSettingsGroup", + "displayName": "Tracker Settings", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "help": "Set to a Google Tag Manager variable of type \"Snowplow v3 Settings\".", + "macrosInSelect": true, + "selectItems": [ + { + "displayValue": "Select a Snowplow v3 Settings variable", + "value": "select" + } + ], + "displayName": "Tracker Settings", + "defaultValue": "select", + "simpleValueType": true, + "name": "trackerConfigurationVariable", + "type": "SELECT", + "valueValidators": [ + { + "type": "REGEX", + "args": [ + "^(?!select)$" + ], + "enablingConditions": [], + "errorMessage": "Please choose a GTM variable of type \"Snowplow v3 Settings\"." + } + ] + } + ] + }, + { + "type": "GROUP", + "name": "pluginSettingsGroup", + "displayName": "Plugin Settings", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "SELECT", + "name": "pluginLibrary", + "displayName": "Snowplow Ecommerce Plugin Library", + "macrosInSelect": false, + "selectItems": [ + { + "value": "jsDelivr", + "displayValue": "jsDelivr" + }, + { + "value": "unpkg", + "displayValue": "unpkg" + }, + { + "value": "selfHosted", + "displayValue": "Self-hosted" + }, + { + "value": "doNotAdd", + "displayValue": "Do not add" + } + ], + "simpleValueType": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ], + "defaultValue": "selfHosted" + }, + { + "type": "TEXT", + "name": "selfHostedPluginUrl", + "displayName": "Self-hosted Plugin URL", + "simpleValueType": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ], + "enablingConditions": [ + { + "paramName": "pluginLibrary", + "paramValue": "selfHosted", + "type": "EQUALS" + } + ] + }, + { + "type": "TEXT", + "name": "pluginVersion", + "displayName": "Plugin Library Version", + "simpleValueType": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ], + "valueHint": "3.16.0", + "enablingConditions": [ + { + "paramName": "pluginLibrary", + "paramValue": "jsDelivr", + "type": "EQUALS" + }, + { + "paramName": "pluginLibrary", + "paramValue": "unpkg", + "type": "EQUALS" + } + ] + } + ] + } + ] + } +] + + +___SANDBOXED_JS_FOR_WEB_TEMPLATE___ + +const callInWindow = require('callInWindow'); +const copyFromWindow = require('copyFromWindow'); +const createQueue = require('createQueue'); +const getType = require('getType'); +const injectScript = require('injectScript'); +const log = require('logToConsole'); +const makeInteger = require('makeInteger'); +const makeNumber = require('makeNumber'); +const makeString = require('makeString'); +const setInWindow = require('setInWindow'); +const templateStorage = require('templateStorage'); + +// Constants +const SNOWPLOW_WINDOW_NAMESPACE = 'GlobalSnowplowNamespace'; +const SNOWPLOW_TRACKER_LIST = 'snowplow_tracker_list'; +const SNOWPLOW_GLOBAL_NAME = 'snowplow'; +const ERROR_LOG_PREFIX = '[ERROR GTM / Snowplow v3 Ecommerce] '; +const PLUGIN_PKG = '@snowplow/browser-plugin-snowplow-ecommerce@'; +const PLUGIN_DIST = '/dist/index.umd.min.js'; +const JSDELIVR = 'https://cdn.jsdelivr.net/npm/'; +const UNPKG = 'https://unpkg.com/'; +const FAILS = { + settingsVar: 'Invalid settings variable provided', + trackerLib: 'Tracker configuration is missing sp.js library URL', + trackerName: 'Tracker configuration is missing tracker name', + trackerCollector: 'Tracker configuration is missing collector endpoint', + ecomArg: 'Non object argument provided to ecommerce function', + ecomFunction: 'Invalid function provided to ecommerce API', +}; +const VALID_SP_FUNCTIONS = [ + 'trackProductView', + 'trackAddToCart', + 'trackRemoveFromCart', + 'trackProductListView', + 'trackProductListClick', + 'trackPromotionView', + 'trackPromotionClick', + 'trackCheckoutStep', + 'trackTransaction', + 'trackRefund', + 'trackTransactionError', + 'setPageType', + 'setEcommerceUser', +]; +const VALID_GA4_FUNCTIONS = [ + 'trackGA4ViewItemList', + 'trackGA4SelectItem', + 'trackGA4ViewItem', + 'trackGA4ViewPromotion', + 'trackGA4SelectPromotion', + 'trackGA4AddToCart', + 'trackGA4RemoveFromCart', + 'trackGA4BeginCheckout', + 'trackGA4AddShippingInfo', + 'trackGA4AddPaymentOptions', + 'trackGA4Transaction', +]; +const VALID_UA_FUNCTIONS = [ + 'trackEnhancedEcommerceProductListView', + 'trackEnhancedEcommerceProductListClick', + 'trackEnhancedEcommerceProductDetail', + 'trackEnhancedEcommercePromoView', + 'trackEnhancedEcommercePromoClick', + 'trackEnhancedEcommerceAddToCart', + 'trackEnhancedEcommerceRemoveFromCart', + 'trackEnhancedEcommerceCheckoutStep', + 'trackEnhancedEcommercePurchase', +]; + +// Helpers +/** + * Adds a tracker name to the list of initialized trackers. + * + * @param {string[]} trackersList - The list of tracker names + * @param {string} trackerName - The tracker name + */ +const pushToTrackerList = (trackersList, trackerName) => { + trackersList.push(trackerName); + templateStorage.setItem(SNOWPLOW_TRACKER_LIST, trackersList); +}; + +/** + * Builds the Snowplow global namespace and returns the tracker. + * + * @returns {Object} The global snowplow object + */ +const getSp = () => { + const globalName = SNOWPLOW_GLOBAL_NAME; + const snowplow = copyFromWindow(globalName); + if (snowplow) { + return snowplow; + } + + const globalNamespace = createQueue(SNOWPLOW_WINDOW_NAMESPACE); + globalNamespace(globalName); + // Can't use createArgumentsQueue here since the Snowplow tracker library + // does not work with GTM's wrapper + const snowplowQueue = globalName.concat('.q'); + const snowplowQueuePush = snowplowQueue.concat('.push'); + setInWindow(globalName, function () { + callInWindow(snowplowQueuePush, arguments); + }); + createQueue(snowplowQueue); + return copyFromWindow(globalName); +}; + +/** + * Creates the Snowplow Ecommerce plugin URL. + * + * @param {Object} tagConfig - The tag configuration data + * @returns {string} The plugin URL + */ +const mkPluginURL = (tagConfig) => { + const pluginLib = tagConfig.pluginLibrary; + switch (pluginLib) { + case 'jsDelivr': + return JSDELIVR + PLUGIN_PKG + tagConfig.pluginVersion + PLUGIN_DIST; + case 'unpkg': + return UNPKG + PLUGIN_PKG + tagConfig.pluginVersion + PLUGIN_DIST; + default: + return tagConfig.selfHostedPluginUrl; + } +}; + +/** + * Logs a message and fails the tag. + * + * @param {string} - The message to log + */ +const fail = (msg) => { + log(ERROR_LOG_PREFIX + msg); + return data.gtmOnFailure(); +}; + +/** + * Merges a source object onto target object. + * + * @param {Object} - The target object + * @param {Object} - The source object + * @param {string} - The merge strategy to use on common props + * @returns {Object} The target modified + */ +const mergeTo = (target, obj, strategy) => { + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + switch (strategy) { + case 'target': + if (!target.hasOwnProperty(key)) { + target[key] = obj[key]; + } + break; + default: + target[key] = obj[key]; + } + } + } + return target; +}; + +/** + * Normalizes a value. + * + * @param {*} val - The value to normalize + * @returns {*} The normalized value + */ +const normalize = (val) => { + const typeOfVal = getType(val); + if (typeOfVal === 'string') { + switch (val) { + case 'null': + return null; + case 'true': + return true; + case 'false': + return false; + case '': + return val; + default: + const asNumber = makeNumber(val); + return asNumber == val ? asNumber : val; + } + } + return val; +}; + +/** + * Normalizes the v3 Settings Variable's return object. + * Also separates the tracker options from initialization options. + * + * @param {Object} settings - The settings object + * @returns {Object} The resulting object + */ +const normalizeSettings = (settings) => { + const result = { + trackerOptions: settings.trackerOptions, + initOptions: {}, + }; + for (let prop in settings) { + if (settings.hasOwnProperty(prop) && prop !== 'trackerOptions') { + result.initOptions[prop] = normalize(settings[prop]); + } + } + return result; +}; + +/** + * Helper that returns a valid tracker configuration object. + * + * @param {Object} tagConfig - The tag configuration data + * @returns {Object} An object with information about tracker settings + */ +const getTrackerConfiguration = (tagConfig) => { + const settings = tagConfig.trackerConfigurationVariable; + if (!settings || settings.type !== 'snowplow' || !settings.trackerOptions) { + return { fails: FAILS.settingsVar }; + } + if (!settings.trackerOptions.libUrl) { + return { fails: FAILS.trackerLib }; + } + if (!settings.trackerOptions.trackerName) { + return { fails: FAILS.trackerName }; + } + if (!settings.trackerOptions.collectorEndpoint) { + return { fails: FAILS.trackerCollector }; + } + return normalizeSettings(settings); +}; + +/** + * Helper to create commands with tracker identifier. + * Assumes its argument is string. + * + * @param {string} trackerIdentifier - The tracker namespace + * @returns {string} The command to be called for this tracker + */ +const withTrackerId = (trackerIdentifier) => { + return function (commandName) { + return commandName + ':' + trackerIdentifier; + }; +}; + +/** + * Creates the contexts array to attach to the event. + * + * @param {Object} tagConfig - The tag configuration data + * @returns {Object[]} The contexts array + */ +const mkCustomContexts = (tagConfig) => { + const zeroVal = []; + const configCtx = tagConfig.customContexts; + if (configCtx && configCtx.length > 0) { + return configCtx.reduce((acc, curr) => { + const ctx = curr.entitiesVariable; + if (getType(ctx) === 'array') { + return acc.concat(ctx); + } + // if not array, ignore + return acc; + }, zeroVal); + } + return zeroVal; +}; + +/** + * Creates the common event parameters from tag configuration. + * + * @param {Object} tagConfig - The tag configuration data + * @returns {Object} A representation of common event parameters + */ +const getCommonParams = (tagConfig) => { + const params = {}; + const contextToAdd = mkCustomContexts(tagConfig); + if (contextToAdd.length > 0) { + params.context = contextToAdd; + } + + const trueTstamp = makeInteger(tagConfig.trueTimestamp); + if (trueTstamp) { + params.timestamp = { type: 'ttm', value: trueTstamp }; + } + + return params; +}; + +/** + * Tracks an event using the Snowplow Ecommerce API. + * + * @param {string} spName - The tracker namespace + * @param {function} commander - A function to create tracker commands + * @param {Object} tagConfig - The tag configuration data + * @returns {string} A status message + */ +const handleSnowplowEcommerce = (spName, commander, tagConfig) => { + const method = makeString(tagConfig.spEcomFunction); + if (VALID_SP_FUNCTIONS.indexOf(method) === -1) { + return FAILS.ecomFunction; + } + + const args = tagConfig.spEcomArg; + if (getType(args) !== 'object') { + return FAILS.ecomArg; + } + + const commonParams = getCommonParams(tagConfig); + spName(commander(method), mergeTo(args, commonParams)); + return 'ok'; +}; + +/** + * Tracks an event using the GA4 Ecommerce migration API. + * + * @param {string} spName - The tracker namespace + * @param {function} commander - A function to create tracker commands + * @param {Object} tagConfig - The tag configuration data + * @returns {string} A status message + */ +const handleGA4Ecommerce = (spName, commander, tagConfig) => { + const method = makeString(tagConfig.ga4EcomFunction); + if (VALID_GA4_FUNCTIONS.indexOf(method) === -1) { + return FAILS.ecomFunction; + } + + const ecom = tagConfig.ga4DataLayerEcommerce || {}; + const opts = tagConfig.ga4EcomOptions || {}; + if ([ecom, opts].some((arg) => getType(arg) !== 'object')) { + return FAILS.ecomArg; + } + + switch (method) { + case 'trackGA4ViewPromotion': + spName(commander(method), ecom); + break; + case 'trackGA4SelectPromotion': + spName(commander(method), ecom); + break; + case 'trackGA4BeginCheckout': + spName(commander(method), opts); + break; + default: + spName(commander(method), mergeTo(ecom, opts, 'target')); + } + return 'ok'; +}; + +/** + * Tracks an event using the UA Ecommerce migration API. + * + * @param {string} spName - The tracker namespace + * @param {function} commander - A function to create tracker commands + * @param {Object} tagConfig - The tag configuration data + * @returns {string} A status message + */ +const handleUAEcommerce = (spName, commander, tagConfig) => { + const method = makeString(tagConfig.uaEcomFunction); + if (VALID_UA_FUNCTIONS.indexOf(method) === -1) { + return FAILS.ecomFunction; + } + + const ecom = tagConfig.uaDataLayerEcommerce; + const opts = tagConfig.uaEcomOptions || {}; + if ([ecom, opts].some((arg) => getType(arg) !== 'object')) { + return FAILS.ecomArg; + } + + switch (method) { + case 'trackEnhancedEcommercePromoView': + spName(commander(method), ecom); + break; + case 'trackEnhancedEcommercePromoClick': + spName(commander(method), ecom); + break; + default: + spName(commander(method), mergeTo(ecom, opts, 'target')); + } + return 'ok'; +}; + +// Main +const trackerList = templateStorage.getItem(SNOWPLOW_TRACKER_LIST) || []; +const spGlobalName = getSp(); +const config = getTrackerConfiguration(data); +if (config.fails) { + return fail(config.fails); +} + +const spLoadLib = config.trackerOptions.libUrl; +const trackerName = config.trackerOptions.trackerName; +const endpoint = config.trackerOptions.collectorEndpoint; + +// Only initialize the tracker if it hasn't been initialized yet +if (trackerList.indexOf(trackerName) === -1) { + spGlobalName('newTracker', trackerName, endpoint, config.initOptions); + pushToTrackerList(trackerList, trackerName); +} + +const mkCommand = withTrackerId(trackerName); +if (data.pluginLibrary !== 'doNotAdd') { + spGlobalName(mkCommand('addPlugin'), mkPluginURL(data), [ + 'snowplowEcommerceAccelerator', + 'SnowplowEcommercePlugin', + ]); +} + +const status = { msg: 'pending' }; +switch (data.ecomAPI) { + case 'spEcommerce': + status.msg = handleSnowplowEcommerce(spGlobalName, mkCommand, data); + break; + case 'ga4Ecommerce': + status.msg = handleGA4Ecommerce(spGlobalName, mkCommand, data); + break; + case 'uaEcommerce': + status.msg = handleUAEcommerce(spGlobalName, mkCommand, data); + break; + default: + status.msg = 'No Ecommerce API specified'; +} + +if (status.msg !== 'ok') { + return fail(status.msg); +} + +if (spLoadLib !== 'doNotLoad') { + injectScript(spLoadLib, data.gtmOnSuccess, data.gtmOnFailure, 'splibrary'); +} else { + data.gtmOnSuccess(); +} + + +___WEB_PERMISSIONS___ + +[ + { + "instance": { + "key": { + "publicId": "access_globals", + "versionId": "1" + }, + "param": [ + { + "key": "keys", + "value": { + "type": 2, + "listItem": [ + { + "type": 3, + "mapKey": [ + { + "type": 1, + "string": "key" + }, + { + "type": 1, + "string": "read" + }, + { + "type": 1, + "string": "write" + }, + { + "type": 1, + "string": "execute" + } + ], + "mapValue": [ + { + "type": 1, + "string": "GlobalSnowplowNamespace" + }, + { + "type": 8, + "boolean": true + }, + { + "type": 8, + "boolean": true + }, + { + "type": 8, + "boolean": false + } + ] + }, + { + "type": 3, + "mapKey": [ + { + "type": 1, + "string": "key" + }, + { + "type": 1, + "string": "read" + }, + { + "type": 1, + "string": "write" + }, + { + "type": 1, + "string": "execute" + } + ], + "mapValue": [ + { + "type": 1, + "string": "snowplow" + }, + { + "type": 8, + "boolean": true + }, + { + "type": 8, + "boolean": true + }, + { + "type": 8, + "boolean": false + } + ] + }, + { + "type": 3, + "mapKey": [ + { + "type": 1, + "string": "key" + }, + { + "type": 1, + "string": "read" + }, + { + "type": 1, + "string": "write" + }, + { + "type": 1, + "string": "execute" + } + ], + "mapValue": [ + { + "type": 1, + "string": "snowplow.q" + }, + { + "type": 8, + "boolean": true + }, + { + "type": 8, + "boolean": true + }, + { + "type": 8, + "boolean": false + } + ] + }, + { + "type": 3, + "mapKey": [ + { + "type": 1, + "string": "key" + }, + { + "type": 1, + "string": "read" + }, + { + "type": 1, + "string": "write" + }, + { + "type": 1, + "string": "execute" + } + ], + "mapValue": [ + { + "type": 1, + "string": "snowplow.q.push" + }, + { + "type": 8, + "boolean": false + }, + { + "type": 8, + "boolean": false + }, + { + "type": 8, + "boolean": true + } + ] + } + ] + } + } + ] + }, + "clientAnnotations": { + "isEditedByUser": true + }, + "isRequired": true + }, + { + "instance": { + "key": { + "publicId": "inject_script", + "versionId": "1" + }, + "param": [ + { + "key": "urls", + "value": { + "type": 2, + "listItem": [ + { + "type": 1, + "string": "https://*.cloudfront.net/*" + }, + { + "type": 1, + "string": "https://storage.googleapis.com/*" + }, + { + "type": 1, + "string": "https://cdn.jsdelivr.net/*" + }, + { + "type": 1, + "string": "https://unpkg.com/*" + } + ] + } + } + ] + }, + "clientAnnotations": { + "isEditedByUser": true + }, + "isRequired": true + }, + { + "instance": { + "key": { + "publicId": "logging", + "versionId": "1" + }, + "param": [ + { + "key": "environments", + "value": { + "type": 1, + "string": "debug" + } + } + ] + }, + "clientAnnotations": { + "isEditedByUser": true + }, + "isRequired": true + }, + { + "instance": { + "key": { + "publicId": "access_template_storage", + "versionId": "1" + }, + "param": [] + }, + "isRequired": true + } +] + + +___TESTS___ + +scenarios: +- name: Assert on fail scenarios + code: | + const mockDataA = {}; + const expErrA = logPrefix + 'Invalid settings variable provided'; + + const mockDataB = { + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: {}, + }, + }; + const expErrB = + logPrefix + 'Tracker configuration is missing sp.js library URL'; + + const mockDataC = { + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'foo', + }, + }, + }; + const expErrC = logPrefix + 'Tracker configuration is missing tracker name'; + + const mockDataD = { + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'foo', + trackerName: 'bar', + }, + }, + }; + const expErrD = + logPrefix + 'Tracker configuration is missing collector endpoint'; + + const mockDataE = { + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'foo', + trackerName: 'bar', + collectorEndpoint: 'baz', + }, + }, + ecomAPI: 'fail', + }; + const expErrE = logPrefix + 'No Ecommerce API specified'; + + const mockDataF = { + ecomAPI: 'uaEcommerce', + uaEcomFunction: 'trackEnhancedEcommerceProductListView', + uaDataLayerEcommerce: 'fail', + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'doNotLoad', + trackerName: 'sp1', + collectorEndpoint: 'foo', + }, + }, + pluginLibrary: 'doNotAdd', + }; + const mockDataG = { + ecomAPI: 'ga4Ecommerce', + ga4EcomFunction: 'trackGA4ViewItemList', + ga4DataLayerEcommerce: {}, + ga4EcomOptions: 'fail', + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'doNotLoad', + trackerName: 'sp1', + collectorEndpoint: 'foo', + }, + }, + pluginLibrary: 'doNotAdd', + }; + const mockDataH = { + ecomAPI: 'spEcommerce', + spEcomFunction: 'trackProductView', + spEcomArg: 'fail', + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'doNotLoad', + trackerName: 'sp1', + collectorEndpoint: 'foo', + }, + }, + pluginLibrary: 'doNotAdd', + }; + const expErrFGH = + logPrefix + 'Non object argument provided to ecommerce function'; + + const functionFromVar = 'fail'; + const mockDataI = { + ecomAPI: 'spEcommerce', + spEcomFunction: functionFromVar, + spEcomArg: 'fail', + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'doNotLoad', + trackerName: 'sp1', + collectorEndpoint: 'foo', + }, + }, + pluginLibrary: 'doNotAdd', + }; + const mockDataJ = { + ecomAPI: 'ga4Ecommerce', + spEcomFunction: functionFromVar, + spEcomArg: 'fail', + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'doNotLoad', + trackerName: 'sp1', + collectorEndpoint: 'foo', + }, + }, + pluginLibrary: 'doNotAdd', + }; + const mockDataK = { + ecomAPI: 'uaEcommerce', + spEcomFunction: functionFromVar, + spEcomArg: 'fail', + trackerConfigurationVariable: { + type: 'snowplow', + trackerOptions: { + libUrl: 'doNotLoad', + trackerName: 'sp1', + collectorEndpoint: 'foo', + }, + }, + pluginLibrary: 'doNotAdd', + }; + const expErrIJK = + logPrefix + 'Invalid function provided to ecommerce API'; + + + // Call runCode to run the template's code. + runCode(mockDataA); + assertApi('logToConsole').wasCalledWith(expErrA); + + // Call runCode to run the template's code. + runCode(mockDataB); + assertApi('logToConsole').wasCalledWith(expErrB); + + // Call runCode to run the template's code. + runCode(mockDataC); + assertApi('logToConsole').wasCalledWith(expErrC); + + // Call runCode to run the template's code. + runCode(mockDataD); + assertApi('logToConsole').wasCalledWith(expErrD); + + // Call runCode to run the template's code. + runCode(mockDataE); + assertApi('logToConsole').wasCalledWith(expErrE); + + // Call runCode to run the template's code. + runCode(mockDataF); + runCode(mockDataG); + runCode(mockDataH); + assertApi('logToConsole').wasCalledWith(expErrFGH); + + runCode(mockDataI); + runCode(mockDataJ); + runCode(mockDataK); + assertApi('logToConsole').wasCalledWith(expErrIJK); + + assertApi('gtmOnFailure').wasCalled(); + assertApi('gtmOnSuccess').wasNotCalled(); +- name: Test newTracker and addPlugin calls + code: | + const trackerOpts = { + trackerName: 'testTracker1', + collectorEndpoint: 'http://localhost:9090', + libUrl: + 'https://cdn.jsdelivr.net/npm/@snowplow/javascript-tracker@3.16.0/dist/sp.min.js', + }; + const mockData = { + ecomAPI: 'spEcommerce', + spEcomFunction: 'trackProductView', + spEcomArg: {}, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + const mockInitSettings = mockSettingsVariable(trackerOpts, 'normalized'); + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let newTrackerCalledTimes = 0; + let newTrackerArgs = []; + let addPluginCalledTimes = 0; + let addPluginArgs = []; + let trackCalledTimes = 0; + let trackCalledArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + newTrackerCalledTimes++; + newTrackerArgs.push(arguments.slice(1)); + break; + case arguments[0].indexOf('addPlugin') === 0: + addPluginCalledTimes++; + addPluginArgs.push(arguments); + break; + default: + } + }; + } + }); + + assertThat( + templateStorage.getItem('snowplow_tracker_list') || [] + ).doesNotContain('testTracker1'); + + // first run, should call newTracker and update tracker list + runCode(mockData); + assertThat(newTrackerCalledTimes).isEqualTo(1); + assertThat(templateStorage.getItem('snowplow_tracker_list')).contains( + 'testTracker1' + ); + const expectedArgs = [ + 'testTracker1', + 'http://localhost:9090', + mockInitSettings, + ]; + assertThat(newTrackerArgs.length).isEqualTo(1); + assertThat(newTrackerArgs[0]).isEqualTo(expectedArgs); + + assertThat(addPluginCalledTimes).isEqualTo(0); + + // prepare for second run + mockData.pluginLibrary = 'selfHosted'; + mockData.selfHostedPluginUrl = 'https://foo.test'; + + // second run, should not call newTracker and tracker list should not change + runCode(mockData); + assertThat(newTrackerCalledTimes).isEqualTo(1); + assertThat(newTrackerArgs.length).isEqualTo(1); + assertThat(templateStorage.getItem('snowplow_tracker_list')).contains( + 'testTracker1' + ); + + assertThat(addPluginCalledTimes).isEqualTo(1); + assertThat(addPluginArgs.length).isEqualTo(1); + const expectedPluginArgs0 = [ + 'addPlugin:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + mockData.selfHostedPluginUrl, + ['snowplowEcommerceAccelerator', 'SnowplowEcommercePlugin'], + ]; + assertThat(addPluginArgs[0]).isEqualTo(expectedPluginArgs0); + + // prepare for third run + mockData.selfHostedPluginUrl = undefined; + mockData.pluginLibrary = 'jsDelivr'; + mockData.pluginVersion = '3.16.0'; + + runCode(mockData); + assertThat(newTrackerCalledTimes).isEqualTo(1); + assertThat(newTrackerArgs.length).isEqualTo(1); + assertThat(templateStorage.getItem('snowplow_tracker_list')).contains( + 'testTracker1' + ); + + assertThat(addPluginCalledTimes).isEqualTo(2); + assertThat(addPluginArgs.length).isEqualTo(2); + const expectedPluginArgs1 = [ + 'addPlugin:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + 'https://cdn.jsdelivr.net/npm/@snowplow/browser-plugin-snowplow-ecommerce@3.16.0/dist/index.umd.min.js', + ['snowplowEcommerceAccelerator', 'SnowplowEcommercePlugin'], + ]; + assertThat(addPluginArgs[1]).isEqualTo(expectedPluginArgs1); + + // prepare for third run + mockData.pluginLibrary = 'unpkg'; + mockData.pluginVersion = '3.15.0'; + + runCode(mockData); + assertThat(newTrackerCalledTimes).isEqualTo(1); + assertThat(newTrackerArgs.length).isEqualTo(1); + assertThat(templateStorage.getItem('snowplow_tracker_list')).contains( + 'testTracker1' + ); + + assertThat(addPluginCalledTimes).isEqualTo(3); + assertThat(addPluginArgs.length).isEqualTo(3); + const expectedPluginArgs2 = [ + 'addPlugin:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + 'https://unpkg.com/@snowplow/browser-plugin-snowplow-ecommerce@3.15.0/dist/index.umd.min.js', + ['snowplowEcommerceAccelerator', 'SnowplowEcommercePlugin'], + ]; + assertThat(addPluginArgs[2]).isEqualTo(expectedPluginArgs2); + + assertApi('gtmOnSuccess').wasCalled(); + assertApi('gtmOnFailure').wasNotCalled(); +- name: Test with doNotLoad tracker library + code: | + const trackerOpts = { + trackerName: 'testTracker2', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'spEcommerce', + spEcomFunction: 'trackProductView', + spEcomArg: {}, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + return; + }; + } + }); + + runCode(mockData); + assertApi('injectScript').wasNotCalled(); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test Snowplow productView + code: | + const productViewObject = { + id: '12345', + name: 'Baseball T', + brand: 'Snowplow', + category: 'apparel', + price: 200, + currency: 'USD', + }; + const trackerOpts = { + trackerName: 'testTracker4', + collectorEndpoint: 'http://localhost:9090', + libUrl: + 'https://cdn.jsdelivr.net/npm/@snowplow/javascript-tracker@3.16.0/dist/sp.min.js', + }; + const mockData = { + ecomAPI: 'spEcommerce', + spEcomFunction: 'trackProductView', + spEcomArg: productViewObject, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'selfHosted', + selfHostedPluginUrl: 'https://foo.test', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let trackArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + trackArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(1); + assertThat(trackArgs.length).isEqualTo(1); + const expectedArgs = [ + 'trackProductView:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + productViewObject, + ]; + assertThat(trackArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test Snowplow transaction error with additional params + code: | + const userDataCtx = { + schema: 'iglu:com.google.tag-manager.server-side/user_data/jsonschema/1-0-0', + data: { email_address: 'foo@bar.io' }, + }; + const mobileCtx = { + schema: 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-2', + data: { + osType: 'myOsType', + osVersion: 'myOsVersion', + deviceManufacturer: 'myDevMan', + deviceModel: 'myDevModel', + }, + }; + const transactionError = { + error_code: 'E522', + }; + const trackerOpts = { + trackerName: 'testTracker4', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'spEcommerce', + spEcomFunction: 'trackTransactionError', + spEcomArg: transactionError, + customContexts: [ + { + entitiesVariable: [userDataCtx], + }, + { + entitiesVariable: [mobileCtx], + }, + ], + trueTimestamp: '1699374606382', + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let trackArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + trackArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(1); + assertThat(trackArgs.length).isEqualTo(1); + const expectedFunctionArg = { + error_code: transactionError.error_code, + context: [userDataCtx, mobileCtx], + timestamp: { + type: 'ttm', + value: makeInt(mockData.trueTimestamp), + }, + }; + const expectedArgs = [ + 'trackTransactionError:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + expectedFunctionArg, + ]; + assertThat(trackArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test Snowplow setPageType + code: | + const newPageCtx = { + type: 'homepage', + language: 'ja', + locale: 'ja_JP', + }; + const trackerOpts = { + trackerName: 'testTracker4', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'spEcommerce', + spEcomFunction: 'setPageType', + spEcomArg: newPageCtx, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let setCalledTimes = 0; + let setArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + break; + case arguments[0].indexOf('set') === 0: + setCalledTimes++; + setArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(0); + assertThat(setCalledTimes).isEqualTo(1); + assertThat(setArgs.length).isEqualTo(1); + const expectedArgs = [ + 'setPageType:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + newPageCtx, + ]; + assertThat(setArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test Snowplow setEcommerceUser + code: | + const newEcomUser = { + id: 'tester', + is_guest: false, + email: 'tester@testing.io', + }; + const trackerOpts = { + trackerName: 'testTracker4', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'spEcommerce', + spEcomFunction: 'setEcommerceUser', + spEcomArg: newEcomUser, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let setCalledTimes = 0; + let setArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + break; + case arguments[0].indexOf('set') === 0: + setCalledTimes++; + setArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(0); + assertThat(setCalledTimes).isEqualTo(1); + assertThat(setArgs.length).isEqualTo(1); + const expectedArgs = [ + 'setEcommerceUser:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + newEcomUser, + ]; + assertThat(setArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test GA4 view_item_list + code: | + const trackerOpts = { + trackerName: 'testTracker', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'ga4Ecommerce', + ga4EcomFunction: 'trackGA4ViewItemList', + ga4DataLayerEcommerce: ga4ViewItemListEcommerce, + ga4EcomOptions: { currency: 'EUR' }, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let trackArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + trackArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(1); + assertThat(trackArgs.length).isEqualTo(1); + const ecomCopy = json.parse(json.stringify(ga4ViewItemListEcommerce)); + ecomCopy.currency = 'EUR'; + const expectedArgs = [ + 'trackGA4ViewItemList:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + ecomCopy + ]; + assertThat(trackArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test GA4 begin_checkout + code: | + const checkoutStep = { step: 2 }; + const trackerOpts = { + trackerName: 'testTracker', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'ga4Ecommerce', + ga4EcomFunction: 'trackGA4BeginCheckout', + //ga4DataLayerEcommerce: ga4ViewItemListEcommerce, + ga4EcomOptions: checkoutStep, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let trackArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + trackArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(1); + assertThat(trackArgs.length).isEqualTo(1); + const expectedArgs = [ + 'trackGA4BeginCheckout:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + checkoutStep, + ]; + assertThat(trackArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test GA4 add_to_cart + code: | + const options = { + currency: 'EUR', + finalCartValue: 100, + }; + const trackerOpts = { + trackerName: 'testTracker', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'ga4Ecommerce', + ga4EcomFunction: 'trackGA4AddToCart', + ga4DataLayerEcommerce: ga4AddToCart, + ga4EcomOptions: options, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let trackArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + trackArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(1); + assertThat(trackArgs.length).isEqualTo(1); + const ecomCopy = json.parse(json.stringify(ga4AddToCart)); + ecomCopy.finalCartValue = 100; + const expectedArgs = [ + 'trackGA4AddToCart:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + ecomCopy, + ]; + assertThat(trackArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test UA product_detail + code: | + const options = { currency: 'EUR' }; + const trackerOpts = { + trackerName: 'testTracker', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'uaEcommerce', + uaEcomFunction: 'trackEnhancedEcommerceProductDetail', + uaDataLayerEcommerce: uaProductDetail, + uaEcomOptions: options, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let trackArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + trackArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(1); + assertThat(trackArgs.length).isEqualTo(1); + const expectedArgs = [ + 'trackEnhancedEcommerceProductDetail:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + uaProductDetail + ]; + assertThat(trackArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +- name: Test UA promo_view + code: | + const trackerOpts = { + trackerName: 'testTracker', + collectorEndpoint: 'http://localhost:9090', + libUrl: 'doNotLoad', + }; + const mockData = { + ecomAPI: 'uaEcommerce', + uaEcomFunction: 'trackEnhancedEcommercePromoView', + uaDataLayerEcommerce: uaPromoView, + trackerConfigurationVariable: mockSettingsVariable(trackerOpts, 'original'), + pluginLibrary: 'doNotAdd', + }; + + const trackerLib = mockData.trackerConfigurationVariable.trackerOptions.libUrl; + mockInjectScript(trackerLib); + + let trackCalledTimes = 0; + let trackArgs = []; + mock('copyFromWindow', (key) => { + if (key === 'snowplow') { + return function () { + switch (true) { + case arguments[0] === 'newTracker': + break; + case arguments[0].indexOf('addPlugin') === 0: + break; + case arguments[0].indexOf('track') === 0: + trackCalledTimes++; + trackArgs.push(arguments); + break; + default: + } + }; + } + }); + + runCode(mockData); + + assertThat(trackCalledTimes).isEqualTo(1); + assertThat(trackArgs.length).isEqualTo(1); + const expectedArgs = [ + 'trackEnhancedEcommercePromoView:'.concat( + mockData.trackerConfigurationVariable.trackerOptions.trackerName + ), + uaPromoView, + ]; + assertThat(trackArgs[0]).isEqualTo(expectedArgs); + assertApi('gtmOnSuccess').wasCalled(); +setup: |- + const getType = require('getType'); + const json = require('JSON'); + const log = require('logToConsole'); + const makeInt = require('makeInteger'); + const templateStorage = require('templateStorage'); + const logPrefix = '[ERROR GTM / Snowplow v3 Ecommerce] '; + + function mockInjectScript(mockUrl) { + mock('injectScript', (url, onsuccess, onfailure) => { + if (url === mockUrl) { + onsuccess(); + } else { + onfailure(); + } + return; + }); + } + + function mockSettingsVariable(trackerOpts, variant) { + const settingsVar = { + type: 'snowplow', + appId: 'testApp', + platform: 'web', + respectDoNotTrack: false, + stateStorageStrategy: 'cookieAndLocalStorage', + cookieDomain: false, + discoverRootDomain: true, + cookieName: '_sp_', + cookieLifetime: 63072000, + cookieSameSite: 'Lax', + cookieSecure: true, + sessionCookieTimeout: variant === 'original' ? '1800' : 1800, + maxLocalStorageQueueSize: variant === 'original' ? '1000' : 1000, + eventMethod: 'post', + encodeBase64: true, + bufferSize: variant === 'original' ? '1' : 1, + postPath: '/com.snowplowanalytics.snowplow/tp2', + maxPostBytes: variant === 'original' ? '40000' : 40000, + connectionTimeout: variant === 'original' ? '5000' : 5000, + anonymousTracking: false, + contexts: { + webPage: true, + performanceTiming: false, + gaCookies: false, + clientHints: false, + geolocation: false, + session: true, + }, + }; + if (variant === 'original') { + settingsVar.trackerOptions = trackerOpts; + } + return settingsVar; + } + + const ga4ViewItemListEcommerce = { + item_list_id: 'related_products', + item_list_name: 'Related products', + items: [ + { + item_id: 'SKU_12345', + item_name: 'Stan and Friends Tee', + affiliation: 'Google Merchandise Store', + coupon: 'SUMMER_FUN', + discount: 2.22, + index: 0, + item_brand: 'Google', + item_category: 'Apparel', + item_category2: 'Adult', + item_category3: 'Shirts', + item_category4: 'Crew', + item_category5: 'Short sleeve', + item_list_id: 'related_products', + item_list_name: 'Related Products', + item_variant: 'green', + location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo', + price: 9.99, + quantity: 1, + }, + { + item_id: 'SKU_12346', + item_name: "Google Grey Women's Tee", + affiliation: 'Google Merchandise Store', + coupon: 'SUMMER_FUN', + discount: 3.33, + index: 1, + item_brand: 'Google', + item_category: 'Apparel', + item_category2: 'Adult', + item_category3: 'Shirts', + item_category4: 'Crew', + item_category5: 'Short sleeve', + item_list_id: 'related_products', + item_list_name: 'Related Products', + item_variant: 'gray', + location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo', + price: 20.99, + promotion_id: 'P_12345', + promotion_name: 'Summer Sale', + quantity: 1, + }, + ], + }; + + const uaProductDetail = { + currency: 'USD', + detail: { + actionField: { list: 'Apparel Gallery' }, + products: [ + { + name: 'Triblend Android T-Shirt', + id: '12345', + price: '15.25', + brand: 'Google', + category: 'Apparel', + variant: 'Gray', + }, + ], + }, + }; + + const uaPromoView = { + promoView: { + promotions: [ + { + id: 'JUNE_PROMO13', + name: 'June Sale', + creative: 'banner1', + position: 'slot1', + }, + { + id: 'FREE_SHIP13', + name: 'Free Shipping Promo', + creative: 'skyscraper1', + position: 'slot2', + }, + ], + }, + }; + + const ga4AddToCart = { + currency: 'USD', + value: 7.77, + items: [ + { + item_id: 'SKU_12345', + item_name: 'Stan and Friends Tee', + affiliation: 'Google Merchandise Store', + coupon: 'SUMMER_FUN', + discount: 2.22, + index: 0, + item_brand: 'Google', + item_category: 'Apparel', + item_category2: 'Adult', + item_category3: 'Shirts', + item_category4: 'Crew', + item_category5: 'Short sleeve', + item_list_id: 'related_products', + item_list_name: 'Related Products', + item_variant: 'green', + location_id: 'ChIJIQBpAG2ahYAR_6128GcTUEo', + price: 9.99, + quantity: 1, + }, + ], + }; + + +___NOTES___ + +Created on 16/10/2023, 09:46:49 From e2b4d42d67305cbcd487416b354c5b071ef2221c Mon Sep 17 00:00:00 2001 From: adatzer Date: Wed, 8 Nov 2023 09:24:23 +0200 Subject: [PATCH 3/3] Prepare for initial release --- CONTRIBUTING.md | 80 ++++++++++++++++++++++++++++++++++ README.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++- metadata.yaml | 9 ++++ 3 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 metadata.yaml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9271f20 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing + +The Snowplow v3 Ecommerce GTM Tag Template is maintained by the Engineering team at Snowplow. We welcome suggestions for improvements and bug fixes to all Snowplow GTM Repositories. + +We are extremely grateful for all contributions we receive, whether that is reporting an issue or a change to the code which can be made in the form of a pull request. + +For support requests, please use our community support Discourse forum: https://discourse.snowplow.io/. + +## Setting up an Environment + +Instructions on how to build and run tests are available in the [README.md](README.md). The README will also list any requirements that you will need to install first before being able to build and run the tests. + +You should ensure you are comfortable building and testing the existing release before adding new functionality or fixing issues. + +## Issues + +### Creating an issue + +The project contains an issue template which should help guiding you through the process. However, please keep in mind that support requests should go to our Discourse forum: https://discourse.snowplow.io/ and not GitHub issues. + +It's also a good idea to log an issue before starting to work on a pull request to discuss it with the maintainers. A pull request is just one solution to a problem and it is often a good idea to talk about the problem with the maintainers first. + +### Working on an issue + +If you see an issue you would like to work on, please let us know in the issue! That will help us in terms of scheduling and +not doubling the amount of work. + +If you don't know where to start contributing, you can look at +[the issues labeled `good first issue`](https://github.com/snowplow/snowplow-gtm-tag-template-ecommerce-v3/labels/good%20first%20issue). + +## Pull requests + +These are a few guidelines to keep in mind when opening pull requests. + +### Guidelines + +Please supply a good PR description. These are very helpful and help the maintainers to understand _why_ the change has been made, not just _what_ changes have been made. + +Please try and keep your PR to a single feature of fix. This might mean breaking up a feature into multiple PRs but this makes it easier for the maintainers to review and also reduces the risk in each change. + +Please review your own PR as you would do it you were a reviewer first. This is a great way to spot any mistakes you made when writing the change. Additionally, ensure your code compiles and all tests pass. + +### Commit hygiene + +We keep a strict 1-to-1 correspondance between commits and issues, as such our commit messages are formatted in the following +fashion: + +`Issue Description (closes #1234)` + +for example: + +`Fix Issue with Tracker (closes #1234)` + +### Writing tests + +Whenever necessary, it's good practice to add the corresponding tests to whichever feature you are working on. +Any non-trivial PR must have tests and will not be accepted without them. + +### Feedback cycle + +Reviews should happen fairly quickly during weekdays. +If you feel your pull request has been forgotten, please ping one or more maintainers in the pull request. + +### Getting your pull request merged + +If your pull request is fairly chunky, there might be a non-trivial delay between the moment the pull request is approved and the moment it gets merged. This is because your pull request will have been scheduled for a specific milestone which might or might not be actively worked on by a maintainer at the moment. + +### Contributor license agreement + +We require outside contributors to sign a Contributor license agreement (or CLA) before we can merge their pull requests. +You can find more information on the topic in [the dedicated page](https://docs.snowplow.io/docs/contributing/contributor-license-agreement/). +The @snowplowcla bot will guide you through the process. + +## Getting in touch + +### Community support requests + +Please do not log an issue if you are asking for support, all of our community support requests go through our Discourse forum: https://discourse.snowplow.io/. + +Posting your problem there ensures more people will see it and you should get support faster than creating a new issue on GitHub. Please do create a new issue on GitHub if you think you've found a bug though! diff --git a/README.md b/README.md index 7e1f336..64b9288 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,110 @@ -# Snowplow v3 Ecommerce Custom GTM Template +# Snowplow v3 Ecommerce GTM Tag Template -A GTM Tag template for implementing Ecommerce tracking using Snowplow JavaScript tracker v3 and its Snowplow Ecommerce plugin. +[![early-release]][tracker-classification] +[![License][license-image]][license] +[![Release][release-image]][releases] + +[Snowplow][snowplow] is a scalable open-source platform for rich, high quality, low-latency data collection. It is designed to collect high quality, complete behavioral data for enterprise business. + +## Overview + +This is a Google Tag Manager [custom tag template][gtm-custom-template] for implementing Ecommerce tracking using the [Snowplow JavaScript tracker v3][javascript-tracker] and its [Snowplow Ecommerce plugin][snowplow-ecommerce-plugin]. + +It has been designed to be used with the [Snowplow v3 Settings variable template][gtm-v3-settings-variable] and can be implemented alongside the [Snowplow v3 tag template][gtm-v3-tag]. + +## Quickstart + +### Installing from the Google Tag Manager Gallery + +_Coming soon!_ + +### Manual Installation + +To manually install the tag template: + +1. Download `template.tpl` +2. Create a new Tag template in the Templates section of your GTM container +3. Click the More Actions menu and select Import +4. Import `template.tpl` downloaded in Step 1 +5. Click Save. + +## Find out more + +| Technical Docs | +|-----------------------------------| +| [![i1][techdocs-image]][techdocs] | +| [Technical Docs][techdocs] | + +## Maintainer Quickstart + +Work on the template should be done in Google Tag Manager's native **template editor**. This is to ensure the template has access to all the latest features of the template editor, and to make sure it passes GTM's own validation when exporting the changes. + +To **import** the template into Google Tag Manager: + +1. In a Google Tag Manager **web** container, browse to **Templates** and click to create a new template. +2. From the template action menu, choose **Import**. +3. Locate the `template.tpl` file from this repo, and import it into the template editor. + +Make the changes you wish. Make sure the unit tests pass (in the **Tests** tab of the editor). Update the tests if necessary. + +Once you're done, follow these steps: + +1. **Save** the template in the template editor. +2. From the action menu, choose **Export**. +3. Replace the `template.tpl` file in this repo with the exported file (make sure to keep `template.tpl` as its name). +4. **Commit** the changes to the `template.tpl` file and repeat if necessary. +5. Copy the last **commit hash**. +6. Edit `metadata.yaml` in the template folder, and add the hash with its `changeNotes` as the latest version. +7. Move the previous latest version into the list of `Older versions`. +8. Save changes to `metadata.yaml` and **commit** them using a **Prepare for x.y.z release** commit message. +9. Finally, push the changes to the repo (should include at least 2 commits as described above). + +Once the tag is published in the GTM [community gallery][gtm-gallery] the template will be updated automatically within some time after the changes. + +## Contributing + +Feedback and contributions are welcome! If you have identified a bug, please log an issue on this repo. For all other feedback, discussion or questions please open a thread on our [discourse forum][discourse]. + +| Contributing | +|-------------------------------------------| +| [![i2][contributing-image]][contributing] | +| [Contributing][contributing] | + +## Copyright and license + +Copyright (c) 2023 Snowplow Analytics Ltd. + +Licensed under the **[Apache License, Version 2.0][license]** (the "License"); +you may not use this software except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[tracker-classification]: https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/tracker-maintenance-classification/ +[early-release]: https://img.shields.io/static/v1?style=flat&label=Snowplow&message=Early%20Release&color=014477&labelColor=9ba0aa&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAeFBMVEVMaXGXANeYANeXANZbAJmXANeUANSQAM+XANeMAMpaAJhZAJeZANiXANaXANaOAM2WANVnAKWXANZ9ALtmAKVaAJmXANZaAJlXAJZdAJxaAJlZAJdbAJlbAJmQAM+UANKZANhhAJ+EAL+BAL9oAKZnAKVjAKF1ALNBd8J1AAAAKHRSTlMAa1hWXyteBTQJIEwRgUh2JjJon21wcBgNfmc+JlOBQjwezWF2l5dXzkW3/wAAAHpJREFUeNokhQOCA1EAxTL85hi7dXv/E5YPCYBq5DeN4pcqV1XbtW/xTVMIMAZE0cBHEaZhBmIQwCFofeprPUHqjmD/+7peztd62dWQRkvrQayXkn01f/gWp2CrxfjY7rcZ5V7DEMDQgmEozFpZqLUYDsNwOqbnMLwPAJEwCopZxKttAAAAAElFTkSuQmCC + +[license]: https://www.apache.org/licenses/LICENSE-2.0 +[license-image]: https://img.shields.io/badge/license-Apache--2-blue.svg?style=flat + +[releases]: https://github.com/snowplow/snowplow-gtm-tag-template-ecommerce-v3/releases +[release-image]: https://img.shields.io/github/v/release/snowplow/snowplow-gtm-tag-template-ecommerce-v3 + +[snowplow]: https://snowplow.io +[discourse]: https://discourse.snowplow.io + +[gtm-v3-tag]: https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/web-tracker/google-tag-manager-custom-template/tag-template-guide/ +[gtm-v3-settings-variable]: https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/web-tracker/google-tag-manager-custom-template/settings-variable-guide/ +[javascript-tracker]: https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/ +[snowplow-ecommerce-plugin]: https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/web-tracker/plugins/snowplow-ecommerce/ + +[techdocs]: https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/web-tracker/google-tag-manager-custom-template/v3-tags/ecommerce-tag-template/ +[techdocs-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/techdocs.png + +[contributing-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/contributing.png +[contributing]: CONTRIBUTING.md + +[gtm-custom-template]: https://developers.google.com/tag-manager/templates +[gtm-gallery]: https://tagmanager.google.com/gallery diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 0000000..df6cf63 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,9 @@ +homepage: "https://snowplow.io" +documentation: "https://docs.snowplow.io/docs/collecting-data/collecting-from-own-applications/javascript-trackers/web-tracker/google-tag-manager-custom-template/v3-tags/ecommerce-tag-template/" +versions: + # Latest version + - sha: dd1521c6abff2ec85de0bf0246b8f609d8f332bf + changeNotes: |2 + Add initial template (#2) + Add GitHub actions (#1) + # Older versions