feat(gtm-manager): Rename to ourdigital-gtm-manager and add dataLayer injector

BREAKING CHANGE: Renamed from 13-gtm-audit to 13-ourdigital-gtm-manager

New Features:
- DataLayerInjector class for generating custom HTML tags
- Support for 20+ GA4 event types (ecommerce, forms, video, engagement)
- Subcommand structure: audit and inject modes
- Preset tag generation (ecommerce, engagement, all)
- DOM scraping option for dynamic value extraction
- Generate tags from audit report findings
- Korean payment method support (카카오페이, 네이버페이, 토스)

CLI Changes:
- `python gtm_manager.py audit --url ...` for auditing
- `python gtm_manager.py inject --preset ecommerce` for tag generation
- `python gtm_manager.py inject --event purchase --event add_to_cart`
- `python gtm_manager.py inject --from-audit report.json`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-20 20:55:42 +09:00
parent fb2a653866
commit ec5e30c825
14 changed files with 1027 additions and 285 deletions

View File

@@ -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
```

View File

@@ -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

View File

@@ -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
```

View File

@@ -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

View File

@@ -1,18 +1,30 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
GTM Audit Script - Comprehensive Google Tag Manager audit with form tracking, GTM Manager - Comprehensive Google Tag Manager management toolkit.
e-commerce checkout flow, and advanced dataLayer validation.
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: 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: 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) --container Expected GTM container ID (e.g., GTM-XXXXXX)
--journey Journey type: pageview, scroll, click, form, checkout, datalayer, full --journey Journey type: pageview, scroll, click, form, checkout, datalayer, full
--output Output file path (default: gtm_audit_report.json) --output Output file/directory path
--timeout Page load timeout in ms (default: 30000) --notion Export results to Notion database
--headless Run in headless mode (default: True)
""" """
import argparse import argparse
@@ -1343,22 +1355,587 @@ class NotionExporter:
self._make_request("PATCH", f"/blocks/{page_id}/children", {"children": blocks}) 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 = ["<script>"]
if include_comments:
lines.append(f"// GTM Custom HTML Tag: {event_name}")
lines.append(f"// {template['description']}")
lines.append("// Generated by OurDigital GTM Manager")
lines.append("")
# Check if this is an ecommerce event with items
has_items = "items_params" in template
if has_items:
lines.append("(function() {")
lines.append(" // Clear previous ecommerce data")
lines.append(" window.dataLayer.push({ ecommerce: null });")
lines.append("")
if use_scraping and template.get("scrape_selectors"):
lines.extend(self._generate_scraping_code(template, event_name))
else:
lines.extend(self._generate_manual_ecommerce_code(event_name, template, options))
lines.append("})();")
else:
# Simple event without items
lines.append("window.dataLayer.push({")
lines.append(f" 'event': '{event_name}',")
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},")
# Add custom params as placeholders
for param in template.get("custom_params", []):
lines.append(f" '{param}': /* TODO: set {param} */,")
lines.append("});")
lines.append("</script>")
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"<!-- {tag['name']} -->\n")
f.write(f"<!-- {tag['description']} -->\n")
f.write(f"<!-- Trigger: {tag['trigger_recommendation']} -->\n")
if tag.get("trigger_selector"):
f.write(f"<!-- Trigger Selector: {tag['trigger_selector']} -->\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("<!-- All Generated GTM Custom HTML Tags -->\n")
f.write("<!-- Generated by OurDigital GTM Manager -->\n\n")
for tag in self.generated_tags:
f.write(f"\n<!-- ========== {tag['event']} ========== -->\n")
f.write(f"<!-- {tag['description']} -->\n")
f.write(f"<!-- Trigger: {tag['trigger_recommendation']} -->\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(): def main():
parser = argparse.ArgumentParser(description="GTM Audit Tool") parser = argparse.ArgumentParser(
parser.add_argument("--url", required=True, help="Target URL to audit") description="GTM Manager - Audit and manage Google Tag Manager implementations",
parser.add_argument("--container", help="Expected GTM container ID (e.g., GTM-XXXXXX)") formatter_class=argparse.RawDescriptionHelpFormatter,
parser.add_argument("--journey", default="full", 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"], choices=["pageview", "scroll", "click", "form", "checkout", "datalayer", "full"],
help="Journey type to simulate") help="Journey type to simulate")
parser.add_argument("--output", default="gtm_audit_report.json", help="Output file path") audit_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)") audit_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") audit_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") audit_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)") audit_parser.add_argument("--notion-database", help="Notion database ID")
parser.add_argument("--notion-detailed", action="store_true", help="Add detailed content to Notion page") 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() 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( auditor = GTMAuditor(
url=args.url, url=args.url,
container_id=args.container, container_id=args.container,
@@ -1370,6 +1947,23 @@ def main():
auditor.save_report(args.output) auditor.save_report(args.output)
auditor.print_summary() 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 # Export to Notion if requested
if args.notion: if args.notion:
try: try:
@@ -1383,5 +1977,97 @@ def main():
print(f"❌ Notion export failed: {e}") 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__": if __name__ == "__main__":
main() main()