diff --git a/ourdigital-custom-skills/13-gtm-audit/CLAUDE.md b/ourdigital-custom-skills/13-gtm-audit/CLAUDE.md deleted file mode 100644 index 5ffacd5..0000000 --- a/ourdigital-custom-skills/13-gtm-audit/CLAUDE.md +++ /dev/null @@ -1,151 +0,0 @@ -# GTM Audit Tool - -Automated Google Tag Manager audit toolkit using Playwright browser automation. - -## Project Overview - -This tool audits GTM container installations, validates dataLayer events, tests form tracking, simulates e-commerce checkout flows, and generates comprehensive reports. - -## Quick Commands - -```bash -# Install dependencies -pip install playwright -playwright install chromium - -# Run full audit -python gtm_audit.py --url "https://example.com" --journey full - -# Form tracking audit -python gtm_audit.py --url "https://example.com/contact" --journey form - -# E-commerce checkout flow -python gtm_audit.py --url "https://example.com/cart" --journey checkout - -# DataLayer deep inspection -python gtm_audit.py --url "https://example.com" --journey datalayer - -# With specific container validation -python gtm_audit.py --url "https://example.com" --container "GTM-XXXXXX" -``` - -## Journey Types - -| Journey | Description | -|---------|-------------| -| `pageview` | Basic page load + scroll simulation | -| `scroll` | Scroll depth trigger testing (25%, 50%, 75%, 90%) | -| `form` | Form discovery, field analysis, interaction simulation | -| `checkout` | E-commerce flow: cart → checkout → shipping → payment → purchase | -| `datalayer` | Deep dataLayer validation and event sequence analysis | -| `full` | All of the above combined | - -## Output - -Generates `gtm_audit_report.json` with: -- Container status (installed, position, duplicates) -- DataLayer analysis (events, validation issues, sequence errors) -- Form analysis (forms found, tracking readiness, missing events) -- Checkout analysis (elements detected, flow issues) -- Network requests (GA4, Meta, LinkedIn, etc.) -- Recommendations and checklist - -## Notion Integration - -Export audit results directly to Notion database for tracking and collaboration. - -```bash -# Export to default Notion database (OurDigital GTM Audit Log) -python gtm_audit.py --url "https://example.com" --notion - -# Export with detailed content (issues, recommendations, checklist) -python gtm_audit.py --url "https://example.com" --notion --notion-detailed - -# Export to custom Notion database -python gtm_audit.py --url "https://example.com" --notion --notion-database "your-database-id" -``` - -### Notion Database Schema - -| Property | Type | Description | -|----------|------|-------------| -| Site | Title | Domain name of audited site | -| Audit ID | Text | Unique identifier (GTM-domain-date-hash) | -| URL | URL | Full audited URL | -| Audit Date | Date | When audit was performed | -| Journey Type | Select | Audit journey type | -| GTM Status | Select | Installed / Not Found / Multiple Containers | -| Container IDs | Text | GTM container IDs found | -| Tags Fired | Multi-select | GA4, Google Ads, Meta Pixel, etc. | -| Issues Count | Number | Total issues found | -| Critical Issues | Number | Critical/error severity issues | -| Audit Status | Select | Pass / Warning / Fail | -| Summary | Text | Quick summary of findings | - -### Environment Variables - -Set `NOTION_TOKEN` or `NOTION_API_KEY` for Notion API authentication: - -```bash -export NOTION_TOKEN="secret_xxxxx" -``` - -### Default Database - -Default Notion database: [OurDigital GTM Audit Log](https://www.notion.so/2cf581e58a1e8163997fccb387156a20) - -## Key Files - -- `gtm_audit.py` - Main audit script -- `docs/ga4_events.md` - GA4 event specifications -- `docs/ecommerce_schema.md` - E-commerce dataLayer structures -- `docs/form_tracking.md` - Form event patterns -- `docs/checkout_flow.md` - Checkout funnel sequence -- `docs/datalayer_validation.md` - Validation rules -- `docs/common_issues.md` - Frequent problems and fixes - -## Coding Guidelines - -When modifying this tool: - -1. **Tag Destinations**: Add new platforms to `TAG_DESTINATIONS` dict -2. **Event Validation**: Add requirements to `GA4_EVENT_REQUIREMENTS` dict -3. **Form Selectors**: Extend `FormAnalyzer.discover_forms()` for custom forms -4. **Checkout Elements**: Add selectors to `CheckoutFlowAnalyzer.detect_checkout_elements()` - -## Korean Market Considerations - -- Support Korean payment methods (카카오페이, 네이버페이, 토스) -- Handle KRW currency (no decimals) -- Include Kakao Pixel and Naver Analytics patterns -- Korean button text patterns (장바구니, 결제하기, 주문하기) - -## Testing a New Site - -1. Run with `--journey full` first to get complete picture -2. Check `gtm_audit_report.json` for issues -3. Focus on specific areas with targeted journey types -4. Use `--container GTM-XXXXXX` to validate specific container - -## Common Tasks - -### Add support for new tag platform -```python -# In TAG_DESTINATIONS dict -"NewPlatform": [ - r"tracking\.newplatform\.com", - r"pixel\.newplatform\.com", -], -``` - -### Add custom form field detection -```python -# In FormAnalyzer.discover_forms() -# Add new field types or selectors -``` - -### Extend checkout flow for specific platform -```python -# In CheckoutFlowAnalyzer.detect_checkout_elements() -# Add platform-specific selectors -``` diff --git a/ourdigital-custom-skills/13-gtm-audit/README.md b/ourdigital-custom-skills/13-gtm-audit/README.md deleted file mode 100644 index 74b8e87..0000000 --- a/ourdigital-custom-skills/13-gtm-audit/README.md +++ /dev/null @@ -1,115 +0,0 @@ -# GTM Audit Tool - -Automated Google Tag Manager audit toolkit powered by Playwright. - -## Features - -- **Container Detection**: Verify GTM installation, position, and duplicates -- **DataLayer Validation**: Event structure, types, sequence checking -- **Form Tracking**: Form discovery, field analysis, event verification -- **E-commerce Checkout**: Full funnel flow simulation and validation -- **Multi-Platform**: GA4, Meta Pixel, LinkedIn, Google Ads, Kakao, Naver -- **Notion Integration**: Export audit results to Notion database for tracking - -## Installation - -```bash -# Clone or download -cd gtm-audit-claude-code - -# Install dependencies -pip install -r requirements.txt - -# Install Playwright browsers -playwright install chromium -``` - -## Usage - -```bash -# Full audit -python gtm_audit.py --url "https://yoursite.com" --journey full - -# Specific container validation -python gtm_audit.py --url "https://yoursite.com" --container "GTM-XXXXXX" - -# Form tracking only -python gtm_audit.py --url "https://yoursite.com/contact" --journey form - -# E-commerce checkout -python gtm_audit.py --url "https://yoursite.com/cart" --journey checkout -``` - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `--url` | Target URL (required) | - | -| `--container` | Expected GTM container ID | None | -| `--journey` | Audit type: pageview, scroll, form, checkout, datalayer, full | full | -| `--output` | Output file path | gtm_audit_report.json | -| `--timeout` | Page load timeout (ms) | 30000 | -| `--headless` | Run browser headless | True | -| `--notion` | Export results to Notion database | False | -| `--notion-database` | Custom Notion database ID | OurDigital GTM Audit Log | -| `--notion-detailed` | Add detailed content to Notion page | False | - -## Output - -Generates JSON report with: -- Container status -- DataLayer events and validation issues -- Form analysis and tracking readiness -- Checkout flow analysis -- Network requests by destination -- Recommendations and checklist - -## Notion Integration - -Export audit results directly to Notion for team collaboration and historical tracking. - -```bash -# Set Notion API token -export NOTION_TOKEN="secret_xxxxx" - -# Export to Notion (default database) -python gtm_audit.py --url "https://yoursite.com" --notion - -# Export with detailed content (issues, recommendations, checklist) -python gtm_audit.py --url "https://yoursite.com" --notion --notion-detailed -``` - -The audit creates a new row in the Notion database with: -- Site name and URL -- Audit date and unique ID -- GTM status and container IDs -- Tags fired (GA4, Meta, etc.) -- Issue counts and overall status -- Summary of findings - -## Using with Claude Code - -This project includes a `CLAUDE.md` file optimized for use with Claude Code. - -```bash -# In your terminal -claude - -# Then ask Claude to run audits -> Run a GTM audit on https://example.com -> Check the form tracking on https://example.com/contact -> Analyze the checkout flow issues in the latest report -``` - -## Documentation - -See `docs/` folder for: -- GA4 event specifications -- E-commerce dataLayer schemas -- Form tracking patterns -- Checkout flow sequences -- Common issues and fixes - -## License - -MIT diff --git a/ourdigital-custom-skills/13-ourdigital-gtm-manager/CLAUDE.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/CLAUDE.md new file mode 100644 index 0000000..514fc84 --- /dev/null +++ b/ourdigital-custom-skills/13-ourdigital-gtm-manager/CLAUDE.md @@ -0,0 +1,182 @@ +# OurDigital GTM Manager + +Comprehensive Google Tag Manager management toolkit - audit, analyze, and generate dataLayer implementations. + +## Project Overview + +This tool provides two main capabilities: +1. **Audit**: Validate GTM installations, analyze dataLayer events, test form/checkout tracking +2. **Inject**: Generate custom HTML tags for dataLayer pushes when direct code access is unavailable + +## Quick Commands + +```bash +# Install dependencies +pip install playwright +playwright install chromium + +# AUDIT MODE +# Run full audit +python gtm_manager.py audit --url "https://example.com" --journey full + +# Audit with Notion export +python gtm_manager.py audit --url "https://example.com" --notion + +# Audit and generate missing tags +python gtm_manager.py audit --url "https://example.com" --generate-tags + +# INJECT MODE +# List available event types +python gtm_manager.py inject --list-events + +# Generate all ecommerce tags +python gtm_manager.py inject --preset ecommerce --output ./tags + +# Generate specific event tags +python gtm_manager.py inject --event purchase --event add_to_cart + +# Generate from audit report +python gtm_manager.py inject --from-audit gtm_audit_report.json + +# Generate with DOM scraping +python gtm_manager.py inject --event view_item --scrape +``` + +## Audit Mode + +### Journey Types + +| Journey | Description | +|---------|-------------| +| `pageview` | Basic page load + scroll simulation | +| `scroll` | Scroll depth trigger testing (25%, 50%, 75%, 90%) | +| `form` | Form discovery, field analysis, interaction simulation | +| `checkout` | E-commerce flow: cart → checkout → shipping → payment → purchase | +| `datalayer` | Deep dataLayer validation and event sequence analysis | +| `full` | All of the above combined | + +### Audit Output + +Generates `gtm_audit_report.json` with: +- Container status (installed, position, duplicates) +- DataLayer analysis (events, validation issues, sequence errors) +- Form analysis (forms found, tracking readiness, missing events) +- Checkout analysis (elements detected, flow issues) +- Network requests (GA4, Meta, LinkedIn, etc.) +- Recommendations and checklist + +## Inject Mode + +Generate GTM custom HTML tags for dataLayer injection when you can't modify source code directly. + +### Event Categories + +**Ecommerce:** +- `view_item`, `add_to_cart`, `remove_from_cart`, `view_cart` +- `begin_checkout`, `add_shipping_info`, `add_payment_info`, `purchase` + +**Forms & Leads:** +- `form_submit`, `form_start`, `generate_lead` + +**Engagement:** +- `scroll`, `file_download`, `search`, `outbound_click`, `share` + +**Video:** +- `video_start`, `video_progress`, `video_complete` + +**User:** +- `login`, `sign_up`, `page_view` + +### Presets + +| Preset | Events Included | +|--------|-----------------| +| `ecommerce` | All 8 ecommerce events | +| `engagement` | Forms, scroll, downloads, video, search, sharing | +| `all` | Everything including page_view, login, sign_up | + +### Generated Tag Features + +- GA4-compliant dataLayer structure +- Ecommerce object clearing before pushes +- DOM scraping option for dynamic values +- Trigger recommendations and selectors +- Korean payment method support + +## Notion Integration + +Export audit results directly to Notion database for tracking and collaboration. + +```bash +# Export to default Notion database (OurDigital GTM Audit Log) +python gtm_manager.py audit --url "https://example.com" --notion + +# Export with detailed content +python gtm_manager.py audit --url "https://example.com" --notion --notion-detailed +``` + +### Environment Variables + +```bash +export NOTION_TOKEN="secret_xxxxx" +``` + +### Default Database + +Default Notion database: [OurDigital GTM Audit Log](https://www.notion.so/2cf581e58a1e8163997fccb387156a20) + +## Key Files + +- `gtm_manager.py` - Main script with audit and inject functionality +- `docs/ga4_events.md` - GA4 event specifications +- `docs/ecommerce_schema.md` - E-commerce dataLayer structures +- `docs/form_tracking.md` - Form event patterns +- `docs/checkout_flow.md` - Checkout funnel sequence +- `docs/datalayer_validation.md` - Validation rules +- `docs/common_issues.md` - Frequent problems and fixes + +## Coding Guidelines + +When modifying this tool: + +1. **Tag Destinations**: Add new platforms to `TAG_DESTINATIONS` dict +2. **Event Validation**: Add requirements to `GA4_EVENT_REQUIREMENTS` dict +3. **Event Templates**: Add new events to `DataLayerInjector.EVENT_TEMPLATES` +4. **Form Selectors**: Extend `FormAnalyzer.discover_forms()` for custom forms +5. **Checkout Elements**: Add selectors to `CheckoutFlowAnalyzer.detect_checkout_elements()` + +## Korean Market Considerations + +- Support Korean payment methods (카카오페이, 네이버페이, 토스) +- Handle KRW currency (no decimals) +- Include Kakao Pixel and Naver Analytics patterns +- Korean button text patterns (장바구니, 결제하기, 주문하기) + +## Common Tasks + +### Add support for new tag platform +```python +# In TAG_DESTINATIONS dict +"NewPlatform": [ + r"tracking\.newplatform\.com", + r"pixel\.newplatform\.com", +], +``` + +### Add new event type for injection +```python +# In DataLayerInjector.EVENT_TEMPLATES +"custom_event": { + "description": "Track custom action", + "params": { + "custom_param": "/* value */", + }, + "trigger": "Custom Trigger", +}, +``` + +### Extend checkout flow for specific platform +```python +# In CheckoutFlowAnalyzer.detect_checkout_elements() +# Add platform-specific selectors +``` diff --git a/ourdigital-custom-skills/13-ourdigital-gtm-manager/README.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/README.md new file mode 100644 index 0000000..9c47230 --- /dev/null +++ b/ourdigital-custom-skills/13-ourdigital-gtm-manager/README.md @@ -0,0 +1,140 @@ +# OurDigital GTM Manager + +Comprehensive Google Tag Manager management toolkit powered by Playwright. + +## Features + +- **Audit Mode**: Validate GTM installations, dataLayer events, forms, and checkout flows +- **Inject Mode**: Generate custom HTML tags for dataLayer pushes +- **Container Detection**: Verify GTM installation, position, and duplicates +- **DataLayer Validation**: Event structure, types, sequence checking +- **Form Tracking**: Form discovery, field analysis, event verification +- **E-commerce Checkout**: Full funnel flow simulation and validation +- **Multi-Platform**: GA4, Meta Pixel, LinkedIn, Google Ads, Kakao, Naver +- **Notion Integration**: Export audit results to Notion database + +## Installation + +```bash +cd ourdigital-gtm-manager + +# Install dependencies +pip install -r requirements.txt + +# Install Playwright browsers +playwright install chromium +``` + +## Audit Mode + +Analyze existing GTM implementations: + +```bash +# Full audit +python gtm_manager.py audit --url "https://yoursite.com" --journey full + +# Specific container validation +python gtm_manager.py audit --url "https://yoursite.com" --container "GTM-XXXXXX" + +# Form tracking only +python gtm_manager.py audit --url "https://yoursite.com/contact" --journey form + +# Audit and generate missing tags +python gtm_manager.py audit --url "https://yoursite.com" --generate-tags + +# Export to Notion +python gtm_manager.py audit --url "https://yoursite.com" --notion +``` + +### Audit Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--url` | Target URL (required) | - | +| `--container` | Expected GTM container ID | None | +| `--journey` | pageview, scroll, form, checkout, datalayer, full | full | +| `--output` | Output file path | gtm_audit_report.json | +| `--generate-tags` | Generate missing dataLayer tags | False | +| `--notion` | Export to Notion database | False | + +## Inject Mode + +Generate GTM custom HTML tags when you can't modify source code directly: + +```bash +# List available event types +python gtm_manager.py inject --list-events + +# Generate all ecommerce tags +python gtm_manager.py inject --preset ecommerce --output ./tags + +# Generate specific events +python gtm_manager.py inject --event purchase --event add_to_cart + +# Generate from audit report +python gtm_manager.py inject --from-audit gtm_audit_report.json + +# Generate with DOM scraping code +python gtm_manager.py inject --event view_item --scrape +``` + +### Inject Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--event` | Event type(s) to generate | - | +| `--preset` | ecommerce, engagement, all | - | +| `--from-audit` | Generate from audit report | - | +| `--output` | Output directory | ./gtm_tags | +| `--format` | html or json | html | +| `--currency` | Currency code | KRW | +| `--scrape` | Generate DOM scraping code | False | +| `--list-events` | Show available events | - | + +### Supported Events + +**Ecommerce**: view_item, add_to_cart, remove_from_cart, view_cart, begin_checkout, add_shipping_info, add_payment_info, purchase + +**Forms**: form_submit, form_start, generate_lead + +**Engagement**: scroll, file_download, search, outbound_click, share + +**Video**: video_start, video_progress, video_complete + +**User**: login, sign_up, page_view + +## Notion Integration + +```bash +# Set Notion API token +export NOTION_TOKEN="secret_xxxxx" + +# Export audit to Notion +python gtm_manager.py audit --url "https://yoursite.com" --notion --notion-detailed +``` + +## Using with Claude Code + +This project includes a `CLAUDE.md` file optimized for Claude Code. + +```bash +claude + +# Then ask Claude: +> Run a GTM audit on https://example.com +> Generate ecommerce dataLayer tags for my site +> Check the checkout flow and create missing tags +``` + +## Documentation + +See `docs/` folder for: +- GA4 event specifications +- E-commerce dataLayer schemas +- Form tracking patterns +- Checkout flow sequences +- Common issues and fixes + +## License + +MIT diff --git a/ourdigital-custom-skills/13-gtm-audit/docs/checkout_flow.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/checkout_flow.md similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/docs/checkout_flow.md rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/checkout_flow.md diff --git a/ourdigital-custom-skills/13-gtm-audit/docs/common_issues.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/common_issues.md similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/docs/common_issues.md rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/common_issues.md diff --git a/ourdigital-custom-skills/13-gtm-audit/docs/datalayer_validation.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/datalayer_validation.md similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/docs/datalayer_validation.md rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/datalayer_validation.md diff --git a/ourdigital-custom-skills/13-gtm-audit/docs/ecommerce_schema.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/ecommerce_schema.md similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/docs/ecommerce_schema.md rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/ecommerce_schema.md diff --git a/ourdigital-custom-skills/13-gtm-audit/docs/form_tracking.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/form_tracking.md similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/docs/form_tracking.md rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/form_tracking.md diff --git a/ourdigital-custom-skills/13-gtm-audit/docs/ga4_events.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/ga4_events.md similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/docs/ga4_events.md rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/ga4_events.md diff --git a/ourdigital-custom-skills/13-gtm-audit/docs/report_template.md b/ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/report_template.md similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/docs/report_template.md rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/docs/report_template.md diff --git a/ourdigital-custom-skills/13-gtm-audit/gtm_audit.py b/ourdigital-custom-skills/13-ourdigital-gtm-manager/gtm_manager.py similarity index 64% rename from ourdigital-custom-skills/13-gtm-audit/gtm_audit.py rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/gtm_manager.py index 3dc5cf5..4523c2d 100644 --- a/ourdigital-custom-skills/13-gtm-audit/gtm_audit.py +++ b/ourdigital-custom-skills/13-ourdigital-gtm-manager/gtm_manager.py @@ -1,18 +1,30 @@ #!/usr/bin/env python3 """ -GTM Audit Script - Comprehensive Google Tag Manager audit with form tracking, -e-commerce checkout flow, and advanced dataLayer validation. +GTM Manager - Comprehensive Google Tag Manager management toolkit. + +Features: +- Audit: GTM container validation, dataLayer analysis, form/checkout tracking +- Inject: Generate custom HTML tags for dataLayer pushes when direct code access is unavailable +- Export: Send audit results to Notion database Usage: - python gtm_audit.py --url "https://example.com" [options] + # Audit mode + python gtm_manager.py audit --url "https://example.com" --journey full + + # Inject mode - generate dataLayer push tags + python gtm_manager.py inject --event purchase --output tags/ + + # Generate tags from audit report + python gtm_manager.py inject --from-audit gtm_audit_report.json Options: - --url Target URL to audit (required) + audit Run GTM audit on a URL + inject Generate custom HTML tags for dataLayer injection + --url Target URL to audit (required for audit) --container Expected GTM container ID (e.g., GTM-XXXXXX) --journey Journey type: pageview, scroll, click, form, checkout, datalayer, full - --output Output file path (default: gtm_audit_report.json) - --timeout Page load timeout in ms (default: 30000) - --headless Run in headless mode (default: True) + --output Output file/directory path + --notion Export results to Notion database """ import argparse @@ -1343,22 +1355,587 @@ class NotionExporter: self._make_request("PATCH", f"/blocks/{page_id}/children", {"children": blocks}) +class DataLayerInjector: + """Generate GTM custom HTML tags for dataLayer injection. + + Use when direct code access is unavailable and you need to push + dataLayer events through GTM custom HTML tags. + """ + + # Event templates with required/recommended parameters + EVENT_TEMPLATES = { + "page_view": { + "description": "Track page views with custom dimensions", + "params": { + "page_title": "document.title", + "page_location": "window.location.href", + "page_path": "window.location.pathname", + }, + "custom_params": ["content_group", "author", "publish_date"], + "trigger": "All Pages", + }, + "view_item": { + "description": "Track product detail page views", + "params": { + "currency": "'KRW'", + "value": "/* product price */", + }, + "items_params": ["item_id", "item_name", "item_brand", "item_category", "price", "quantity"], + "trigger": "Product Detail Page", + "scrape_selectors": { + "item_name": [".product-title", ".product-name", "h1.title", "[itemprop='name']"], + "price": [".product-price", ".price", "[itemprop='price']", ".sale-price"], + "item_id": ["[data-product-id]", "[data-sku]", ".product-sku"], + "item_brand": [".brand", "[itemprop='brand']", ".product-brand"], + "item_category": [".category", ".breadcrumb", "[itemprop='category']"], + }, + }, + "add_to_cart": { + "description": "Track add to cart actions", + "params": { + "currency": "'KRW'", + "value": "/* cart value */", + }, + "items_params": ["item_id", "item_name", "item_brand", "item_category", "price", "quantity"], + "trigger": "Add to Cart Button Click", + "trigger_selector": "button.add-to-cart, .btn-cart, [data-action='add-to-cart']", + }, + "remove_from_cart": { + "description": "Track remove from cart actions", + "params": { + "currency": "'KRW'", + "value": "/* removed item value */", + }, + "items_params": ["item_id", "item_name", "price", "quantity"], + "trigger": "Remove from Cart Click", + "trigger_selector": "button.remove, .btn-remove, [data-action='remove']", + }, + "view_cart": { + "description": "Track cart page views", + "params": { + "currency": "'KRW'", + "value": "/* total cart value */", + }, + "items_params": ["item_id", "item_name", "price", "quantity"], + "trigger": "Cart Page", + }, + "begin_checkout": { + "description": "Track checkout initiation", + "params": { + "currency": "'KRW'", + "value": "/* checkout value */", + "coupon": "/* coupon code if any */", + }, + "items_params": ["item_id", "item_name", "price", "quantity"], + "trigger": "Checkout Page", + }, + "add_shipping_info": { + "description": "Track shipping info submission", + "params": { + "currency": "'KRW'", + "value": "/* order value */", + "shipping_tier": "/* shipping method */", + }, + "items_params": ["item_id", "item_name", "price", "quantity"], + "trigger": "Shipping Form Submit", + }, + "add_payment_info": { + "description": "Track payment info submission", + "params": { + "currency": "'KRW'", + "value": "/* order value */", + "payment_type": "/* payment method */", + }, + "items_params": ["item_id", "item_name", "price", "quantity"], + "trigger": "Payment Form Submit", + "payment_types_kr": ["신용카드", "카카오페이", "네이버페이", "토스", "무통장입금", "휴대폰결제"], + }, + "purchase": { + "description": "Track completed purchases", + "params": { + "transaction_id": "/* unique order ID */", + "currency": "'KRW'", + "value": "/* total order value */", + "tax": "/* tax amount */", + "shipping": "/* shipping cost */", + "coupon": "/* coupon code if any */", + }, + "items_params": ["item_id", "item_name", "item_brand", "item_category", "price", "quantity"], + "trigger": "Order Confirmation Page", + "scrape_selectors": { + "transaction_id": [".order-number", ".order-id", "[data-order-id]"], + "value": [".order-total", ".total-price", ".grand-total"], + }, + }, + "generate_lead": { + "description": "Track lead form submissions", + "params": { + "currency": "'KRW'", + "value": "/* lead value if applicable */", + }, + "custom_params": ["form_id", "form_name", "lead_source"], + "trigger": "Lead Form Submit", + }, + "form_submit": { + "description": "Track general form submissions", + "params": {}, + "custom_params": ["form_id", "form_name", "form_destination"], + "trigger": "Form Submission", + }, + "form_start": { + "description": "Track form interaction start", + "params": {}, + "custom_params": ["form_id", "form_name"], + "trigger": "Form Field Focus", + }, + "scroll": { + "description": "Track scroll depth", + "params": { + "percent_scrolled": "/* 25, 50, 75, 90, 100 */", + }, + "trigger": "Scroll Depth", + }, + "file_download": { + "description": "Track file downloads", + "params": { + "file_name": "/* filename */", + "file_extension": "/* pdf, xlsx, etc */", + "link_url": "/* download URL */", + }, + "trigger": "File Download Click", + "trigger_selector": "a[href$='.pdf'], a[href$='.xlsx'], a[href$='.docx'], a[download]", + }, + "video_start": { + "description": "Track video play start", + "params": { + "video_title": "/* video title */", + "video_provider": "/* youtube, vimeo, etc */", + "video_url": "/* video URL */", + }, + "trigger": "Video Start", + }, + "video_progress": { + "description": "Track video progress milestones", + "params": { + "video_title": "/* video title */", + "video_percent": "/* 25, 50, 75, 100 */", + "video_current_time": "/* seconds watched */", + }, + "trigger": "Video Progress", + }, + "video_complete": { + "description": "Track video completion", + "params": { + "video_title": "/* video title */", + "video_duration": "/* total duration */", + }, + "trigger": "Video Complete", + }, + "search": { + "description": "Track site search", + "params": { + "search_term": "/* search query */", + }, + "custom_params": ["search_results_count"], + "trigger": "Search Results Page", + "scrape_selectors": { + "search_term": ["input[name='q']", "input[name='search']", ".search-input"], + }, + }, + "login": { + "description": "Track user login", + "params": { + "method": "/* login method */", + }, + "trigger": "Login Success", + }, + "sign_up": { + "description": "Track user registration", + "params": { + "method": "/* signup method */", + }, + "trigger": "Signup Success", + }, + "share": { + "description": "Track social sharing", + "params": { + "method": "/* share platform */", + "content_type": "/* article, product, etc */", + "item_id": "/* content ID */", + }, + "trigger": "Share Button Click", + }, + "outbound_click": { + "description": "Track outbound link clicks", + "params": { + "link_url": "/* destination URL */", + "link_domain": "/* destination domain */", + "outbound": "true", + }, + "trigger": "Outbound Link Click", + }, + } + + def __init__(self, currency="KRW"): + self.currency = currency + self.generated_tags = [] + + def generate_tag(self, event_name, options=None): + """Generate a custom HTML tag for a specific event.""" + options = options or {} + + if event_name not in self.EVENT_TEMPLATES: + return {"error": f"Unknown event: {event_name}. Available: {list(self.EVENT_TEMPLATES.keys())}"} + + template = self.EVENT_TEMPLATES[event_name] + + # Build the tag + tag = { + "event": event_name, + "name": f"cHTML - dataLayer - {event_name}", + "description": template["description"], + "trigger_recommendation": template.get("trigger", "Custom"), + "trigger_selector": template.get("trigger_selector"), + "html": self._generate_html(event_name, template, options), + "scrape_selectors": template.get("scrape_selectors", {}), + } + + self.generated_tags.append(tag) + return tag + + def _generate_html(self, event_name, template, options): + """Generate the custom HTML tag code.""" + use_scraping = options.get("use_scraping", False) + include_comments = options.get("include_comments", True) + + lines = ["") + + return "\n".join(lines) + + def _generate_scraping_code(self, template, event_name): + """Generate code that scrapes values from DOM.""" + lines = [] + selectors = template.get("scrape_selectors", {}) + + lines.append(" // Scrape product/order data from page") + lines.append(" function getText(selectors) {") + lines.append(" for (var i = 0; i < selectors.length; i++) {") + lines.append(" var el = document.querySelector(selectors[i]);") + lines.append(" if (el) return el.textContent.trim();") + lines.append(" }") + lines.append(" return '';") + lines.append(" }") + lines.append("") + lines.append(" function getPrice(selectors) {") + lines.append(" var text = getText(selectors);") + lines.append(" var num = text.replace(/[^0-9]/g, '');") + lines.append(" return num ? parseInt(num, 10) : 0;") + lines.append(" }") + lines.append("") + + # Generate variable assignments + for field, sels in selectors.items(): + sel_str = json.dumps(sels) + if field in ["price", "value"]: + lines.append(f" var {field} = getPrice({sel_str});") + else: + lines.append(f" var {field} = getText({sel_str});") + + lines.append("") + lines.append(" window.dataLayer.push({") + lines.append(f" 'event': '{event_name}',") + lines.append(" 'ecommerce': {") + lines.append(f" 'currency': '{self.currency}',") + + if "value" in selectors or "price" in selectors: + lines.append(" 'value': value || price,") + if "transaction_id" in selectors: + lines.append(" 'transaction_id': transaction_id,") + + lines.append(" 'items': [{") + + items_params = template.get("items_params", []) + for param in items_params: + if param in selectors: + lines.append(f" '{param}': {param},") + elif param == "quantity": + lines.append(f" '{param}': 1,") + elif param == "price": + lines.append(f" '{param}': price,") + + lines.append(" }]") + lines.append(" }") + lines.append(" });") + + return lines + + def _generate_manual_ecommerce_code(self, event_name, template, options): + """Generate ecommerce code with manual value placeholders.""" + lines = [] + + lines.append(" window.dataLayer.push({") + lines.append(f" 'event': '{event_name}',") + lines.append(" 'ecommerce': {") + + for param, default_value in template.get("params", {}).items(): + if param == "currency": + lines.append(f" '{param}': '{self.currency}',") + else: + lines.append(f" '{param}': {default_value},") + + lines.append(" 'items': [{") + + for param in template.get("items_params", []): + lines.append(f" '{param}': /* TODO: set {param} */,") + + lines.append(" }]") + lines.append(" }") + lines.append(" });") + + return lines + + def generate_from_audit(self, audit_report): + """Generate tags based on audit findings.""" + recommendations = [] + + # Check for missing events + datalayer = audit_report.get("datalayer_analysis", {}) + existing_events = [e.get("event") for e in datalayer.get("events", [])] + + # Form analysis + form_analysis = audit_report.get("form_analysis", {}) + if form_analysis.get("forms_found") and "form_submit" not in existing_events: + recommendations.append({ + "event": "form_submit", + "reason": f"Found {len(form_analysis['forms_found'])} form(s) but no form_submit event", + }) + self.generate_tag("form_submit") + + # Checkout analysis + checkout = audit_report.get("checkout_analysis", {}) + elements = checkout.get("elements_found", {}) + + if elements.get("addToCart") and "add_to_cart" not in existing_events: + recommendations.append({ + "event": "add_to_cart", + "reason": "Add to cart elements found but no add_to_cart event", + }) + self.generate_tag("add_to_cart") + + if elements.get("checkout") and "begin_checkout" not in existing_events: + recommendations.append({ + "event": "begin_checkout", + "reason": "Checkout elements found but no begin_checkout event", + }) + self.generate_tag("begin_checkout") + + # Check for missing ecommerce sequence + for event in CHECKOUT_SEQUENCE: + if event not in existing_events: + if event not in [r["event"] for r in recommendations]: + recommendations.append({ + "event": event, + "reason": f"Missing from checkout sequence", + }) + self.generate_tag(event) + + return { + "recommendations": recommendations, + "generated_tags": self.generated_tags, + } + + def generate_all_ecommerce(self): + """Generate all ecommerce-related tags.""" + ecommerce_events = [ + "view_item", "add_to_cart", "remove_from_cart", "view_cart", + "begin_checkout", "add_shipping_info", "add_payment_info", "purchase" + ] + + for event in ecommerce_events: + self.generate_tag(event) + + return self.generated_tags + + def generate_engagement_tags(self): + """Generate engagement tracking tags.""" + engagement_events = [ + "form_submit", "form_start", "scroll", "file_download", + "video_start", "video_progress", "video_complete", + "search", "outbound_click", "share" + ] + + for event in engagement_events: + self.generate_tag(event) + + return self.generated_tags + + def save_tags(self, output_path, format="html"): + """Save generated tags to files.""" + import os + + if not self.generated_tags: + return {"error": "No tags generated yet"} + + os.makedirs(output_path, exist_ok=True) + saved_files = [] + + for tag in self.generated_tags: + event = tag["event"] + + if format == "html": + filename = f"{event}_tag.html" + filepath = os.path.join(output_path, filename) + with open(filepath, "w", encoding="utf-8") as f: + f.write(f"\n") + f.write(f"\n") + f.write(f"\n") + if tag.get("trigger_selector"): + f.write(f"\n") + f.write("\n") + f.write(tag["html"]) + saved_files.append(filepath) + + elif format == "json": + filename = f"{event}_tag.json" + filepath = os.path.join(output_path, filename) + with open(filepath, "w", encoding="utf-8") as f: + json.dump(tag, f, indent=2, ensure_ascii=False) + saved_files.append(filepath) + + # Also save a combined file + combined_path = os.path.join(output_path, f"all_tags.{format}") + if format == "html": + with open(combined_path, "w", encoding="utf-8") as f: + f.write("\n") + f.write("\n\n") + for tag in self.generated_tags: + f.write(f"\n\n") + f.write(f"\n") + f.write(f"\n\n") + f.write(tag["html"]) + f.write("\n\n") + else: + with open(combined_path, "w", encoding="utf-8") as f: + json.dump(self.generated_tags, f, indent=2, ensure_ascii=False) + + saved_files.append(combined_path) + + return {"saved_files": saved_files, "count": len(self.generated_tags)} + + def print_tag(self, tag): + """Print a tag to console.""" + print(f"\n{'='*60}") + print(f"📦 {tag['name']}") + print(f" {tag['description']}") + print(f" Trigger: {tag['trigger_recommendation']}") + if tag.get("trigger_selector"): + print(f" Selector: {tag['trigger_selector']}") + print(f"{'='*60}") + print(tag["html"]) + print() + + def main(): - parser = argparse.ArgumentParser(description="GTM Audit Tool") - parser.add_argument("--url", required=True, help="Target URL to audit") - parser.add_argument("--container", help="Expected GTM container ID (e.g., GTM-XXXXXX)") - parser.add_argument("--journey", default="full", - choices=["pageview", "scroll", "click", "form", "checkout", "datalayer", "full"], - help="Journey type to simulate") - parser.add_argument("--output", default="gtm_audit_report.json", help="Output file path") - parser.add_argument("--timeout", type=int, default=30000, help="Page load timeout (ms)") - parser.add_argument("--headless", action="store_true", default=True, help="Run headless") - parser.add_argument("--notion", action="store_true", help="Export results to Notion database") - parser.add_argument("--notion-database", help="Notion database ID (uses default if not specified)") - parser.add_argument("--notion-detailed", action="store_true", help="Add detailed content to Notion page") + parser = argparse.ArgumentParser( + description="GTM Manager - Audit and manage Google Tag Manager implementations", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Audit a website + python gtm_manager.py audit --url https://example.com + + # Generate ecommerce dataLayer tags + python gtm_manager.py inject --preset ecommerce --output ./tags + + # Generate tags from audit report + python gtm_manager.py inject --from-audit gtm_audit_report.json + + # Generate specific event tag + python gtm_manager.py inject --event purchase --event add_to_cart + """ + ) + + subparsers = parser.add_subparsers(dest="command", help="Command to run") + + # Audit subcommand + audit_parser = subparsers.add_parser("audit", help="Audit GTM implementation on a URL") + audit_parser.add_argument("--url", required=True, help="Target URL to audit") + audit_parser.add_argument("--container", help="Expected GTM container ID (e.g., GTM-XXXXXX)") + audit_parser.add_argument("--journey", default="full", + choices=["pageview", "scroll", "click", "form", "checkout", "datalayer", "full"], + help="Journey type to simulate") + audit_parser.add_argument("--output", default="gtm_audit_report.json", help="Output file path") + audit_parser.add_argument("--timeout", type=int, default=30000, help="Page load timeout (ms)") + audit_parser.add_argument("--headless", action="store_true", default=True, help="Run headless") + audit_parser.add_argument("--notion", action="store_true", help="Export results to Notion database") + audit_parser.add_argument("--notion-database", help="Notion database ID") + audit_parser.add_argument("--notion-detailed", action="store_true", help="Add detailed content to Notion page") + audit_parser.add_argument("--generate-tags", action="store_true", help="Generate missing dataLayer tags after audit") + + # Inject subcommand + inject_parser = subparsers.add_parser("inject", help="Generate dataLayer injection tags") + inject_parser.add_argument("--event", action="append", help="Event type(s) to generate (can be repeated)") + inject_parser.add_argument("--preset", choices=["ecommerce", "engagement", "all"], + help="Generate preset group of tags") + inject_parser.add_argument("--from-audit", help="Generate tags based on audit report JSON file") + inject_parser.add_argument("--output", default="./gtm_tags", help="Output directory for generated tags") + inject_parser.add_argument("--format", choices=["html", "json"], default="html", help="Output format") + inject_parser.add_argument("--currency", default="KRW", help="Currency code for ecommerce events") + inject_parser.add_argument("--scrape", action="store_true", help="Generate DOM scraping code for values") + inject_parser.add_argument("--list-events", action="store_true", help="List available event types") args = parser.parse_args() + if args.command == "audit": + run_audit(args) + elif args.command == "inject": + run_inject(args) + else: + parser.print_help() + + +def run_audit(args): + """Run GTM audit.""" auditor = GTMAuditor( url=args.url, container_id=args.container, @@ -1370,6 +1947,23 @@ def main(): auditor.save_report(args.output) auditor.print_summary() + # Generate missing tags if requested + if args.generate_tags: + print("\n🏷️ Generating missing dataLayer tags...") + injector = DataLayerInjector() + result = injector.generate_from_audit(report) + + if result["recommendations"]: + print(f" Found {len(result['recommendations'])} missing event(s)") + for rec in result["recommendations"]: + print(f" - {rec['event']}: {rec['reason']}") + + output_dir = args.output.replace(".json", "_tags") + save_result = injector.save_tags(output_dir) + print(f" Saved {save_result['count']} tags to {output_dir}/") + else: + print(" No missing events detected") + # Export to Notion if requested if args.notion: try: @@ -1383,5 +1977,97 @@ def main(): print(f"❌ Notion export failed: {e}") +def run_inject(args): + """Run dataLayer injection tag generation.""" + injector = DataLayerInjector(currency=args.currency) + + # List available events + if args.list_events: + print("\n📋 Available Event Types:") + print("=" * 60) + + categories = { + "Ecommerce": ["view_item", "add_to_cart", "remove_from_cart", "view_cart", + "begin_checkout", "add_shipping_info", "add_payment_info", "purchase"], + "Forms & Leads": ["form_submit", "form_start", "generate_lead"], + "Engagement": ["scroll", "file_download", "search", "outbound_click", "share"], + "Video": ["video_start", "video_progress", "video_complete"], + "User": ["login", "sign_up"], + "Page": ["page_view"], + } + + for category, events in categories.items(): + print(f"\n{category}:") + for event in events: + template = DataLayerInjector.EVENT_TEMPLATES.get(event, {}) + desc = template.get("description", "") + print(f" - {event}: {desc}") + + return + + # Generate from audit report + if args.from_audit: + try: + with open(args.from_audit, "r", encoding="utf-8") as f: + report = json.load(f) + + print(f"📊 Analyzing audit report: {args.from_audit}") + result = injector.generate_from_audit(report) + + if result["recommendations"]: + print(f"\n🔍 Found {len(result['recommendations'])} missing event(s):") + for rec in result["recommendations"]: + print(f" - {rec['event']}: {rec['reason']}") + else: + print(" No missing events detected in audit report") + + except FileNotFoundError: + print(f"❌ Audit report not found: {args.from_audit}") + return + except json.JSONDecodeError: + print(f"❌ Invalid JSON in audit report: {args.from_audit}") + return + + # Generate preset groups + elif args.preset: + print(f"📦 Generating {args.preset} preset tags...") + + if args.preset == "ecommerce": + injector.generate_all_ecommerce() + elif args.preset == "engagement": + injector.generate_engagement_tags() + elif args.preset == "all": + injector.generate_all_ecommerce() + injector.generate_engagement_tags() + for event in ["page_view", "login", "sign_up"]: + injector.generate_tag(event) + + # Generate specific events + elif args.event: + print(f"📦 Generating tags for: {', '.join(args.event)}") + + for event in args.event: + options = {"use_scraping": args.scrape} + result = injector.generate_tag(event, options) + if "error" in result: + print(f" ❌ {result['error']}") + else: + injector.print_tag(result) + + else: + print("❌ Please specify --event, --preset, or --from-audit") + print(" Use --list-events to see available event types") + return + + # Save generated tags + if injector.generated_tags: + save_result = injector.save_tags(args.output, format=args.format) + print(f"\n✅ Saved {save_result['count']} tag(s) to {args.output}/") + for filepath in save_result["saved_files"][:5]: + print(f" - {filepath}") + if len(save_result["saved_files"]) > 5: + print(f" ... and {len(save_result['saved_files']) - 5} more") + + if __name__ == "__main__": main() diff --git a/ourdigital-custom-skills/13-gtm-audit/requirements.txt b/ourdigital-custom-skills/13-ourdigital-gtm-manager/requirements.txt similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/requirements.txt rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/requirements.txt diff --git a/ourdigital-custom-skills/13-gtm-audit/setup.sh b/ourdigital-custom-skills/13-ourdigital-gtm-manager/setup.sh similarity index 100% rename from ourdigital-custom-skills/13-gtm-audit/setup.sh rename to ourdigital-custom-skills/13-ourdigital-gtm-manager/setup.sh