refactor(gtm): Split into lightweight audit and comprehensive manager

- 13-ourdigital-gtm-audit: Lightweight audit-only tool (original)
  - GTM container validation
  - DataLayer event checking
  - Form and checkout analysis
  - No Notion integration, no inject mode

- 14-ourdigital-gtm-manager: Comprehensive management toolkit
  - Full audit capabilities
  - DataLayerInjector for custom HTML tag generation
  - Notion integration for audit logging
  - 20+ GA4 event templates

🤖 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 22:46:16 +09:00
parent ec5e30c825
commit 31506e026d
24 changed files with 2747 additions and 0 deletions

View File

@@ -0,0 +1,287 @@
# DataLayer Validation Reference
## DataLayer Structure Basics
### Proper Initialization
```javascript
// Must appear BEFORE GTM script
<script>
window.dataLayer = window.dataLayer || [];
</script>
<!-- GTM script here -->
```
### Push Syntax
```javascript
// Correct
dataLayer.push({ event: "page_view", page_title: "Home" });
// Wrong - direct assignment
dataLayer = [{ event: "page_view" }]; // ❌ Overwrites array
```
## Validation Rules
### Event Names
| Rule | Valid | Invalid |
|------|-------|---------|
| Alphanumeric + underscore | `add_to_cart` | `add-to-cart` |
| Max 40 characters | `purchase` | (too long names) |
| Case sensitive | `addToCart``addtocart` | - |
| No spaces | `form_submit` | `form submit` |
| No special chars | `click_cta` | `click@cta` |
### Parameter Names
| Rule | Valid | Invalid |
|------|-------|---------|
| Max 40 characters | `item_category` | (too long) |
| Alphanumeric + underscore | `user_id` | `user-id` |
| Cannot start with `_` | `custom_param` | `_private` |
| Cannot start with number | `step_1` | `1_step` |
### Data Types
| Parameter | Expected Type | Example |
|-----------|---------------|---------|
| value | number | `29.99` not `"29.99"` |
| currency | string (ISO 4217) | `"USD"`, `"KRW"` |
| transaction_id | string | `"T_12345"` |
| quantity | integer | `2` not `2.0` |
| price | number | `45000` |
| items | array | `[{...}, {...}]` |
### Type Validation Code
```javascript
function validateDataLayerPush(data) {
const issues = [];
// Check value is number
if (data.ecommerce?.value !== undefined) {
if (typeof data.ecommerce.value !== 'number') {
issues.push(`value should be number, got ${typeof data.ecommerce.value}`);
}
}
// Check currency format
if (data.ecommerce?.currency) {
if (!/^[A-Z]{3}$/.test(data.ecommerce.currency)) {
issues.push(`currency should be 3-letter ISO code`);
}
}
// Check items array
if (data.ecommerce?.items) {
if (!Array.isArray(data.ecommerce.items)) {
issues.push(`items should be array`);
} else {
data.ecommerce.items.forEach((item, i) => {
if (!item.item_id) issues.push(`items[${i}] missing item_id`);
if (!item.item_name) issues.push(`items[${i}] missing item_name`);
if (item.price && typeof item.price !== 'number') {
issues.push(`items[${i}].price should be number`);
}
if (item.quantity && !Number.isInteger(item.quantity)) {
issues.push(`items[${i}].quantity should be integer`);
}
});
}
}
return issues;
}
```
## E-commerce Object Clearing
### Why Clear?
GA4 may merge previous ecommerce data with new events.
### Correct Pattern
```javascript
// Clear first
dataLayer.push({ ecommerce: null });
// Then push new event
dataLayer.push({
event: "view_item",
ecommerce: { ... }
});
```
### Validation Check
```javascript
function checkEcommerceClear(dataLayerArray) {
let lastHadEcommerce = false;
const issues = [];
dataLayerArray.forEach((item, i) => {
const hasEcommerce = 'ecommerce' in item;
const isNull = item.ecommerce === null;
if (hasEcommerce && !isNull && lastHadEcommerce) {
issues.push({
index: i,
message: 'Missing ecommerce:null before this push'
});
}
lastHadEcommerce = hasEcommerce && !isNull;
});
return issues;
}
```
## Event Sequence Validation
### Expected Sequences
**E-commerce Purchase Flow:**
```
view_item_list? → view_item → add_to_cart → view_cart →
begin_checkout → add_shipping_info → add_payment_info → purchase
```
**Form Submission:**
```
form_start → form_submit → generate_lead?
```
**User Authentication:**
```
login | sign_up
```
### Sequence Validator
```javascript
function validateSequence(events, expectedOrder) {
const eventNames = events
.filter(e => e.event)
.map(e => e.event);
let lastIndex = -1;
const issues = [];
eventNames.forEach(event => {
const index = expectedOrder.indexOf(event);
if (index !== -1) {
if (index < lastIndex) {
issues.push(`${event} fired out of expected order`);
}
lastIndex = index;
}
});
return issues;
}
```
## Duplicate Event Detection
### Common Duplicates
- Multiple `page_view` on single page load
- `purchase` firing on page refresh
- Click events on bubbling elements
### Detection Code
```javascript
function findDuplicates(events) {
const seen = {};
const duplicates = [];
events.forEach((event, i) => {
if (!event.event) return;
const key = JSON.stringify(event);
if (seen[key]) {
duplicates.push({
event: event.event,
firstIndex: seen[key],
duplicateIndex: i
});
} else {
seen[key] = i;
}
});
return duplicates;
}
```
## Real-time Monitoring Setup
### Console Monitoring
```javascript
// Paste in browser console to monitor pushes
(function() {
const original = dataLayer.push;
dataLayer.push = function() {
console.group('📊 dataLayer.push');
console.log('Data:', arguments[0]);
console.log('Time:', new Date().toISOString());
console.groupEnd();
return original.apply(this, arguments);
};
console.log('✅ DataLayer monitoring active');
})();
```
### Export DataLayer
```javascript
// Copy full dataLayer to clipboard
copy(JSON.stringify(dataLayer, null, 2));
```
## Validation Checklist
### Structure
- [ ] dataLayer initialized before GTM
- [ ] Using push() not assignment
- [ ] Event names follow conventions
- [ ] Parameter names follow conventions
### Data Types
- [ ] value is number
- [ ] currency is 3-letter code
- [ ] quantity is integer
- [ ] items is array
- [ ] Required fields present
### E-commerce
- [ ] ecommerce:null before each push
- [ ] items array has item_id and item_name
- [ ] transaction_id is unique
- [ ] Consistent currency across events
### Sequence
- [ ] Events fire in logical order
- [ ] No duplicate events
- [ ] Purchase fires only once
## Debug Tools
### GTM Preview Mode
- Real-time event inspection
- Variable value checking
- Tag firing verification
### GA4 DebugView
- Live event stream
- Parameter validation
- User property tracking
### Browser Console
```javascript
// View current dataLayer
console.table(dataLayer);
// Filter by event
dataLayer.filter(d => d.event === 'purchase');
```