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:
@@ -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
|
|
||||||
```
|
|
||||||
@@ -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
|
|
||||||
182
ourdigital-custom-skills/13-ourdigital-gtm-manager/CLAUDE.md
Normal file
182
ourdigital-custom-skills/13-ourdigital-gtm-manager/CLAUDE.md
Normal 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
|
||||||
|
```
|
||||||
140
ourdigital-custom-skills/13-ourdigital-gtm-manager/README.md
Normal file
140
ourdigital-custom-skills/13-ourdigital-gtm-manager/README.md
Normal 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
|
||||||
@@ -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="""
|
||||||
choices=["pageview", "scroll", "click", "form", "checkout", "datalayer", "full"],
|
Examples:
|
||||||
help="Journey type to simulate")
|
# Audit a website
|
||||||
parser.add_argument("--output", default="gtm_audit_report.json", help="Output file path")
|
python gtm_manager.py audit --url https://example.com
|
||||||
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")
|
# Generate ecommerce dataLayer tags
|
||||||
parser.add_argument("--notion", action="store_true", help="Export results to Notion database")
|
python gtm_manager.py inject --preset ecommerce --output ./tags
|
||||||
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")
|
# 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()
|
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()
|
||||||
Reference in New Issue
Block a user