commit
cbc4fad2d5
362
docs/ENHANCED_LOGGING_GUIDE.md
Normal file
362
docs/ENHANCED_LOGGING_GUIDE.md
Normal file
@ -0,0 +1,362 @@
|
||||
# 📊 Улучшенное логирование Google Analytics событий
|
||||
|
||||
## ✨ Что изменилось
|
||||
|
||||
Все события Google Analytics теперь логируются с **детальной информацией** в консоли браузера:
|
||||
|
||||
### 1. **Page View события** (просмотры страниц)
|
||||
### 2. **AB Test Impression события** (показы AB-тестов)
|
||||
|
||||
Все логи используют **console.group** для структурированного отображения и поддерживают цветное форматирование.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Page View события
|
||||
|
||||
### ✅ Успешная отправка
|
||||
|
||||
```
|
||||
▼ [GA] 📊 Page View Event Sent
|
||||
🕐 Timestamp: 2025-10-23T19:15:42.123Z
|
||||
📍 URL: /soulmate/partner-age
|
||||
🌐 Full Location: http://localhost:3000/soulmate/partner-age
|
||||
📄 Page Title: Soulmate Portrait - Partner Age
|
||||
📦 Payload: {
|
||||
page_path: "/soulmate/partner-age",
|
||||
page_location: "http://localhost:3000/soulmate/partner-age",
|
||||
page_title: "Soulmate Portrait - Partner Age"
|
||||
}
|
||||
✅ Status: Successfully sent to Google Analytics
|
||||
```
|
||||
|
||||
### ⚠️ Google Analytics не доступен
|
||||
|
||||
```
|
||||
⚠️ [GA] ⚠️ Page View NOT Sent
|
||||
Reason: Google Analytics not available (window.gtag is undefined)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 AB Test Impression события
|
||||
|
||||
### ✅ Успешная отправка (первый раз)
|
||||
|
||||
```
|
||||
▼ [GA] 🧪 AB Test Impression Event Sent
|
||||
🕐 Timestamp: 2025-10-23T19:15:42.456Z
|
||||
🏷️ Flag: soulmate-onboarding-image
|
||||
🎯 Variant: v1
|
||||
📦 Event Name: experiment_impression
|
||||
📦 Payload: {
|
||||
app_name: "witlab-funnel",
|
||||
feature: "soulmate-onboarding-image",
|
||||
treatment: "v1"
|
||||
}
|
||||
💾 Storage Key: unleash_impression_soulmate-onboarding-image_v1
|
||||
✅ Status: Successfully sent to Google Analytics
|
||||
🔍 Verify in Network tab: Look for /g/collect requests
|
||||
```
|
||||
|
||||
### ⚠️ Пропущено (уже отправлялось)
|
||||
|
||||
```
|
||||
▼ [GA] 🧪 AB Test Impression SKIPPED (Already Sent)
|
||||
🕐 Timestamp: 2025-10-23T19:16:10.789Z
|
||||
🏷️ Flag: soulmate-onboarding-image
|
||||
🎯 Variant: v1
|
||||
💾 Storage Key: unleash_impression_soulmate-onboarding-image_v1
|
||||
⚠️ Reason: Already sent in this session
|
||||
📊 Check sessionStorage to verify
|
||||
```
|
||||
|
||||
### ⚠️ Пропущено (невалидный вариант)
|
||||
|
||||
```
|
||||
▼ [GA] 🧪 AB Test Impression SKIPPED
|
||||
🕐 Timestamp: 2025-10-23T19:15:40.123Z
|
||||
🏷️ Flag: some-test-flag
|
||||
🎯 Variant: disabled
|
||||
⚠️ Reason: Invalid variant (disabled or undefined)
|
||||
```
|
||||
|
||||
### ❌ Ошибка (GA не загружен)
|
||||
|
||||
```
|
||||
▼ [GA] 🧪 AB Test Impression NOT Sent
|
||||
🕐 Timestamp: 2025-10-23T19:15:42.456Z
|
||||
🏷️ Flag: soulmate-trial-grid
|
||||
🎯 Variant: grid
|
||||
❌ Error: Google Analytics not available (window.gtag is undefined)
|
||||
💡 Solution: Check that GoogleAnalytics component is loaded with measurementId
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧹 Очистка истории AB-тестов
|
||||
|
||||
```
|
||||
▼ [GA] 🧪 AB Test Impressions Cleared
|
||||
🕐 Timestamp: 2025-10-23T19:20:15.234Z
|
||||
🧹 Keys Removed: 2
|
||||
📋 Removed Keys: [
|
||||
"unleash_impression_soulmate-onboarding-image_v1",
|
||||
"unleash_impression_soulmate-trial-grid_grid"
|
||||
]
|
||||
✅ Status: All AB test impression history cleared
|
||||
💡 New impressions will be sent on next screen view
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Yandex Metrika события
|
||||
|
||||
### ✅ Успешная отправка
|
||||
|
||||
```
|
||||
▼ [YM] 📊 Page View Event Sent
|
||||
🕐 Timestamp: 2025-10-23T19:15:42.123Z
|
||||
📍 URL: /soulmate/partner-age
|
||||
🔢 Counter ID: 104471567
|
||||
✅ Status: Successfully sent to Yandex Metrika
|
||||
```
|
||||
|
||||
### ⚠️ Counter ID не найден
|
||||
|
||||
```
|
||||
⚠️ [YM] ⚠️ Page View NOT Sent
|
||||
Reason: Counter ID not found
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Цветовая схема
|
||||
|
||||
Логи используют цветовое кодирование для быстрой идентификации:
|
||||
|
||||
| Тип события | Цвет | Значок |
|
||||
|-------------|------|--------|
|
||||
| **GA Page View** | 🔵 Синий (`#4285F4`) | 📊 |
|
||||
| **YM Page View** | 🔴 Красный (`#FF0000`) | 📊 |
|
||||
| **AB Test Sent** | 🔵 Синий (`#4285F4`) | 🧪 |
|
||||
| **AB Test Skipped** | ⚪ Серый (`#9E9E9E`) | 🧪 |
|
||||
| **AB Test Cleared** | 🟠 Оранжевый (`#FF9800`) | 🧪 |
|
||||
| **Warnings** | 🟡 Желтый (`#FFA500`) | ⚠️ |
|
||||
| **Errors** | 🔴 Красный (`#FF0000`) | ❌ |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Как использовать
|
||||
|
||||
### 1. Открыть DevTools Console
|
||||
|
||||
```
|
||||
Chrome/Edge: F12 → Console tab
|
||||
Firefox: F12 → Console tab
|
||||
Safari: Cmd+Option+C
|
||||
```
|
||||
|
||||
### 2. Просмотреть события
|
||||
|
||||
Все события логируются **автоматически**:
|
||||
|
||||
- При каждом переходе между экранами → **Page View**
|
||||
- При попадании на экран с AB-тестом → **AB Test Impression**
|
||||
|
||||
### 3. Развернуть группу
|
||||
|
||||
Клик на `▼` (или стрелку) чтобы увидеть детали:
|
||||
|
||||
```
|
||||
▼ [GA] 📊 Page View Event Sent ← Клик здесь
|
||||
🕐 Timestamp: ...
|
||||
📍 URL: ...
|
||||
...
|
||||
```
|
||||
|
||||
### 4. Проверить Network Tab
|
||||
|
||||
После каждого лога проверьте Network tab:
|
||||
|
||||
```
|
||||
DevTools → Network → Filter: "collect"
|
||||
|
||||
Должны появиться POST запросы:
|
||||
POST /g/collect?v=2&tid=G-XXX&en=page_view...
|
||||
POST /g/collect?v=2&tid=G-XXX&en=experiment_impression...
|
||||
```
|
||||
|
||||
### 5. Проверить sessionStorage
|
||||
|
||||
```javascript
|
||||
// В консоли браузера:
|
||||
Object.keys(sessionStorage)
|
||||
.filter(key => key.startsWith("unleash_impression_"))
|
||||
.forEach(key => console.log(key, sessionStorage.getItem(key)));
|
||||
|
||||
// Вывод:
|
||||
// unleash_impression_soulmate-onboarding-image_v1 "true"
|
||||
// unleash_impression_soulmate-trial-grid_grid "true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование AB-тестов
|
||||
|
||||
### Очистить историю и переотправить
|
||||
|
||||
```javascript
|
||||
// В консоли браузера:
|
||||
import { clearUnleashImpressions } from "@/lib/funnel/unleash";
|
||||
|
||||
// Очистить все impression события
|
||||
clearUnleashImpressions();
|
||||
|
||||
// Перезагрузить страницу
|
||||
window.location.reload();
|
||||
|
||||
// События отправятся заново!
|
||||
```
|
||||
|
||||
### Вручную проверить ключи sessionStorage
|
||||
|
||||
```javascript
|
||||
// Посмотреть все AB test ключи:
|
||||
Object.keys(sessionStorage)
|
||||
.filter(key => key.startsWith("unleash_impression_"));
|
||||
|
||||
// Удалить конкретный ключ:
|
||||
sessionStorage.removeItem("unleash_impression_soulmate-onboarding-image_v1");
|
||||
|
||||
// Удалить все impression ключи:
|
||||
Object.keys(sessionStorage)
|
||||
.filter(key => key.startsWith("unleash_impression_"))
|
||||
.forEach(key => sessionStorage.removeItem(key));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Пример полного flow
|
||||
|
||||
### Сценарий: Пользователь проходит воронку Soulmate
|
||||
|
||||
```
|
||||
1. Открытие /soulmate/onboarding
|
||||
↓
|
||||
[GA] 📊 Page View Event Sent
|
||||
🕐 2025-10-23T19:15:40.000Z
|
||||
📍 /soulmate/onboarding
|
||||
|
||||
[GA] 🧪 AB Test Impression Event Sent
|
||||
🏷️ soulmate-onboarding-image
|
||||
🎯 v1
|
||||
|
||||
2. Переход на /soulmate/gender
|
||||
↓
|
||||
[GA] 📊 Page View Event Sent
|
||||
🕐 2025-10-23T19:16:05.000Z
|
||||
📍 /soulmate/gender
|
||||
|
||||
3. Переход на /soulmate/partner-age
|
||||
↓
|
||||
[GA] 📊 Page View Event Sent
|
||||
🕐 2025-10-23T19:16:30.000Z
|
||||
📍 /soulmate/partner-age
|
||||
|
||||
...18 экранов спустя...
|
||||
|
||||
4. Переход на /soulmate/email
|
||||
↓
|
||||
[GA] 📊 Page View Event Sent
|
||||
🕐 2025-10-23T19:20:15.000Z
|
||||
📍 /soulmate/email
|
||||
|
||||
[GA] 🧪 AB Test Impression Event Sent
|
||||
🏷️ soulmate-trial-grid
|
||||
🎯 grid
|
||||
|
||||
5. Возврат назад на /soulmate/onboarding
|
||||
↓
|
||||
[GA] 📊 Page View Event Sent
|
||||
🕐 2025-10-23T19:21:00.000Z
|
||||
📍 /soulmate/onboarding
|
||||
|
||||
[GA] 🧪 AB Test Impression SKIPPED (Already Sent)
|
||||
🏷️ soulmate-onboarding-image
|
||||
🎯 v1
|
||||
⚠️ Already sent in this session
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Преимущества новых логов
|
||||
|
||||
### ✅ Уверенность в отправке
|
||||
|
||||
Теперь вы **точно видите**:
|
||||
- ✅ Событие отправлено в GA
|
||||
- ⏰ Когда именно (timestamp)
|
||||
- 📦 Какие данные (payload)
|
||||
- 💾 Сохранено в sessionStorage
|
||||
|
||||
### ✅ Легкая отладка
|
||||
|
||||
При проблемах сразу видно:
|
||||
- ❌ GA не загружен
|
||||
- ⚠️ Событие уже отправлялось
|
||||
- ⚠️ Невалидный вариант AB-теста
|
||||
|
||||
### ✅ Удобная навигация
|
||||
|
||||
- Логи сгруппированы (не захламляют консоль)
|
||||
- Цветное кодирование для быстрой идентификации
|
||||
- Эмодзи для визуальных маркеров
|
||||
|
||||
### ✅ Полная трассируемость
|
||||
|
||||
Каждый лог содержит:
|
||||
- 🕐 Timestamp (ISO 8601)
|
||||
- 📍 URL / Flag / Variant
|
||||
- 📦 Полный payload
|
||||
- ✅ Статус отправки
|
||||
- 💡 Подсказки при ошибках
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Связанные документы
|
||||
|
||||
- `GA_AB_TEST_ANALYSIS.md` - Техническая документация системы
|
||||
- `SOULMATE_AB_TESTS_TIMELINE.md` - Детальный анализ AB-тестов Soulmate
|
||||
- `SOULMATE_AB_QUICK_REFERENCE.md` - Быстрая справка по AB-тестам
|
||||
|
||||
---
|
||||
|
||||
## 📝 Примечания
|
||||
|
||||
### Production vs Development
|
||||
|
||||
Логи работают **в обоих режимах**:
|
||||
- **Development**: Все логи показываются
|
||||
- **Production**: Все логи показываются (для проверки в реальном времени)
|
||||
|
||||
Если нужно отключить логи в production, можно обернуть в условие:
|
||||
|
||||
```typescript
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.groupCollapsed(...);
|
||||
}
|
||||
```
|
||||
|
||||
### Производительность
|
||||
|
||||
**console.groupCollapsed** не влияет на производительность:
|
||||
- Группа свернута по умолчанию
|
||||
- Содержимое вычисляется только при раскрытии
|
||||
- Минимальный overhead
|
||||
|
||||
### Browser Support
|
||||
|
||||
Логи работают во всех современных браузерах:
|
||||
- ✅ Chrome/Edge (полная поддержка)
|
||||
- ✅ Firefox (полная поддержка)
|
||||
- ✅ Safari (полная поддержка, без цветов в groupCollapsed)
|
||||
375
docs/GA_AB_TEST_ANALYSIS.md
Normal file
375
docs/GA_AB_TEST_ANALYSIS.md
Normal file
@ -0,0 +1,375 @@
|
||||
# 📊 Анализ Google Analytics и AB-тестов в witlab-funnel
|
||||
|
||||
## 🎯 Краткий ответ
|
||||
|
||||
### Когда отправляется impression событие AB-теста?
|
||||
|
||||
**✅ В момент когда пользователь попадает на экран с AB-тестом**
|
||||
|
||||
- НЕ при загрузке всей воронки
|
||||
- НЕ при инициализации Unleash
|
||||
- Именно при рендеринге конкретного экрана в `FunnelRuntime`
|
||||
|
||||
### Где происходит отправка?
|
||||
|
||||
**Файл:** `src/components/funnel/FunnelRuntime.tsx` (строки 136-150)
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
if (currentScreenFlags.length === 0) return;
|
||||
|
||||
currentScreenFlags.forEach((flag) => {
|
||||
const variant = activeVariants[flag];
|
||||
sendUnleashImpression(flag, variant); // ← ЗДЕСЬ
|
||||
});
|
||||
}, [currentScreenFlags, activeVariants]);
|
||||
```
|
||||
|
||||
### Формат события в GA
|
||||
|
||||
```javascript
|
||||
window.gtag("event", "experiment_impression", {
|
||||
app_name: "witlab-funnel",
|
||||
feature: "trial-button-test",
|
||||
treatment: "v1"
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Архитектура
|
||||
|
||||
### Иерархия компонентов
|
||||
|
||||
```
|
||||
app/[funnelId]/layout.tsx
|
||||
├─ UnleashProvider (Unleash SDK)
|
||||
│ └─ PixelsProvider
|
||||
│ ├─ GoogleAnalytics ← загружает gtag.js
|
||||
│ └─ PageViewTracker
|
||||
│
|
||||
└─ FunnelUnleashWrapper ← собирает все флаги воронки
|
||||
├─ FlagVariantFetcher[] ← загружает варианты
|
||||
└─ FunnelRuntime ← отправляет impression когда экран виден
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ Timeline событий
|
||||
|
||||
```
|
||||
T+0ms Пользователь открывает /funnel/payment
|
||||
T+100ms GoogleAnalytics загружает gtag.js
|
||||
T+200ms FunnelUnleashWrapper сканирует воронку
|
||||
Находит флаги: ["trial-button-test", "payment-variant"]
|
||||
T+300ms Unleash SDK возвращает варианты:
|
||||
• trial-button-test → "v1"
|
||||
• payment-variant → "v2"
|
||||
T+400ms FunnelRuntime монтируется
|
||||
currentScreen = "payment"
|
||||
currentScreenFlags = ["trial-button-test"]
|
||||
T+420ms useEffect срабатывает
|
||||
✅ sendUnleashImpression("trial-button-test", "v1")
|
||||
✅ window.gtag("event", "experiment_impression", {...})
|
||||
T+421ms Событие отправлено в Google Analytics
|
||||
```
|
||||
|
||||
**При переходе на следующий экран:**
|
||||
|
||||
```
|
||||
T+0ms Клик "Continue" → переход на /funnel/gender
|
||||
T+100ms currentScreen = "gender"
|
||||
currentScreenFlags = ["payment-variant"]
|
||||
T+120ms ✅ sendUnleashImpression("payment-variant", "v2")
|
||||
✅ Второе событие отправлено
|
||||
```
|
||||
|
||||
**При возврате назад:**
|
||||
|
||||
```
|
||||
T+0ms Клик "Back" → возврат на /funnel/payment
|
||||
T+100ms currentScreen = "payment"
|
||||
currentScreenFlags = ["trial-button-test"]
|
||||
T+120ms sendUnleashImpression проверяет sessionStorage
|
||||
❌ Уже отправлялось - пропускаем
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Ключевые компоненты
|
||||
|
||||
### 1. Инициализация Google Analytics
|
||||
|
||||
**Файл:** `src/components/analytics/GoogleAnalytics.tsx`
|
||||
|
||||
```typescript
|
||||
<Script src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`} />
|
||||
<Script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${measurementId}', {
|
||||
send_page_view: false // Отключаем автоматические page_view
|
||||
});
|
||||
`
|
||||
}} />
|
||||
```
|
||||
|
||||
### 2. Сбор флагов из воронки
|
||||
|
||||
**Файл:** `src/components/funnel/FunnelUnleashWrapper.tsx`
|
||||
|
||||
```typescript
|
||||
// Сканирует ВСЕ экраны и собирает уникальные флаги
|
||||
const allFlags = useMemo(() => {
|
||||
const flags = new Set<string>();
|
||||
|
||||
funnel.screens.forEach((screen) => {
|
||||
// Из вариантов экрана
|
||||
screen.variants?.forEach((variant) => {
|
||||
variant.conditions.forEach((condition) => {
|
||||
if (condition.conditionType === "unleash") {
|
||||
flags.add(condition.unleashFlag);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Из правил навигации
|
||||
screen.navigation?.rules?.forEach((rule) => {
|
||||
rule.conditions.forEach((condition) => {
|
||||
if (condition.conditionType === "unleash") {
|
||||
flags.add(condition.unleashFlag);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(flags);
|
||||
}, [funnel.screens]);
|
||||
```
|
||||
|
||||
### 3. Определение флагов текущего экрана
|
||||
|
||||
**Файл:** `src/components/funnel/FunnelRuntime.tsx`
|
||||
|
||||
```typescript
|
||||
// Собираем флаги только для ТЕКУЩЕГО экрана
|
||||
const currentScreenFlags = useMemo(() => {
|
||||
const flags = new Set<string>();
|
||||
|
||||
currentScreen.variants?.forEach((variant) => {
|
||||
variant.conditions.forEach((condition) => {
|
||||
if (condition.conditionType === "unleash" && condition.unleashFlag) {
|
||||
flags.add(condition.unleashFlag);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
currentScreen.navigation?.rules?.forEach((rule) => {
|
||||
rule.conditions.forEach((condition) => {
|
||||
if (condition.conditionType === "unleash" && condition.unleashFlag) {
|
||||
flags.add(condition.unleashFlag);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(flags);
|
||||
}, [currentScreen]);
|
||||
```
|
||||
|
||||
### 4. Отправка impression
|
||||
|
||||
**Файл:** `src/components/funnel/FunnelRuntime.tsx`
|
||||
|
||||
```typescript
|
||||
// ✅ ЗДЕСЬ ОТПРАВКА
|
||||
useEffect(() => {
|
||||
if (currentScreenFlags.length === 0) {
|
||||
return; // Нет AB тестов
|
||||
}
|
||||
|
||||
currentScreenFlags.forEach((flag) => {
|
||||
const variant = activeVariants[flag];
|
||||
sendUnleashImpression(flag, variant);
|
||||
});
|
||||
}, [currentScreenFlags, activeVariants]);
|
||||
```
|
||||
|
||||
### 5. Функция sendUnleashImpression
|
||||
|
||||
**Файл:** `src/lib/funnel/unleash/sendImpression.ts`
|
||||
|
||||
```typescript
|
||||
export function sendUnleashImpression(flag: string, variant: string | undefined) {
|
||||
// 1. Валидация варианта
|
||||
if (!variant || variant === "disabled") return;
|
||||
|
||||
// 2. Проверка браузера
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
// 3. ✅ ЗАЩИТА ОТ ДУБЛИКАТОВ
|
||||
const storageKey = `unleash_impression_${flag}_${variant}`;
|
||||
if (sessionStorage.getItem(storageKey)) {
|
||||
return; // Уже отправлялось
|
||||
}
|
||||
|
||||
// 4. Проверка GA
|
||||
if (!window.gtag) {
|
||||
console.warn("Google Analytics not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. ✅ ОТПРАВКА
|
||||
window.gtag("event", "experiment_impression", {
|
||||
app_name: "witlab-funnel",
|
||||
feature: flag,
|
||||
treatment: variant,
|
||||
});
|
||||
|
||||
// 6. Пометка
|
||||
sessionStorage.setItem(storageKey, "true");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Защита от дубликатов
|
||||
|
||||
### sessionStorage предотвращает повторную отправку
|
||||
|
||||
```typescript
|
||||
const storageKey = `unleash_impression_${flag}_${variant}`;
|
||||
// Пример: "unleash_impression_trial-button-test_v1"
|
||||
|
||||
if (sessionStorage.getItem(storageKey)) {
|
||||
return; // Уже отправлялось в этой сессии браузера
|
||||
}
|
||||
|
||||
// Отправка...
|
||||
sessionStorage.setItem(storageKey, "true");
|
||||
```
|
||||
|
||||
### Когда очищается?
|
||||
|
||||
| Действие | Очищается? | Отправится заново? |
|
||||
|----------|------------|-------------------|
|
||||
| Переход между экранами | ❌ | ❌ |
|
||||
| F5 (перезагрузка) | ❌ | ❌ |
|
||||
| Закрытие вкладки | ✅ | ✅ |
|
||||
| Новая вкладка | ✅ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Отладка
|
||||
|
||||
### 1. Console logs (dev mode)
|
||||
|
||||
```javascript
|
||||
[FunnelUnleashWrapper] Active variants: { trial-button-test: "v1" }
|
||||
[Unleash Impression] ✅ Sent successfully: { feature: "trial-button-test", variant: "v1" }
|
||||
[Unleash Impression] Skipped (already sent): { feature: "trial-button-test", variant: "v1" }
|
||||
```
|
||||
|
||||
### 2. Network Tab
|
||||
|
||||
Фильтр: `collect` или `analytics.google.com`
|
||||
|
||||
```
|
||||
POST /g/collect?v=2&tid=G-XXX&en=experiment_impression
|
||||
&ep.app_name=witlab-funnel
|
||||
&ep.feature=trial-button-test
|
||||
&ep.treatment=v1
|
||||
```
|
||||
|
||||
### 3. Google Analytics DebugView
|
||||
|
||||
```javascript
|
||||
// В консоли:
|
||||
localStorage.setItem('google_analytics_debug', '1');
|
||||
// Перезагрузить страницу
|
||||
// Открыть GA → Admin → DebugView
|
||||
```
|
||||
|
||||
### 4. Проверка gtag
|
||||
|
||||
```javascript
|
||||
// В консоли браузера:
|
||||
console.log(typeof window.gtag);
|
||||
// "function" - ✅ GA загружен
|
||||
// "undefined" - ❌ GA не загружен
|
||||
|
||||
// Тестовое событие:
|
||||
window.gtag("event", "test_event", { test_param: "test" });
|
||||
```
|
||||
|
||||
### 5. sessionStorage
|
||||
|
||||
Chrome DevTools → Application → Session Storage
|
||||
|
||||
```
|
||||
unleash_impression_trial-button-test_v1 "true"
|
||||
unleash_impression_onboarding-flow_short "true"
|
||||
```
|
||||
|
||||
### 6. Очистка для тестирования
|
||||
|
||||
```typescript
|
||||
import { clearUnleashImpressions } from "@/lib/funnel/unleash";
|
||||
clearUnleashImpressions();
|
||||
// Очистит все "unleash_impression_*" ключи
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Ключевые файлы
|
||||
|
||||
### Google Analytics
|
||||
- `src/components/analytics/GoogleAnalytics.tsx` - загрузка gtag
|
||||
- `src/components/providers/PixelsProvider.tsx` - провайдер аналитики
|
||||
- `src/components/analytics/PageViewTracker.tsx` - page_view события
|
||||
|
||||
### AB тестирование (Unleash)
|
||||
- `src/lib/funnel/unleash/sendImpression.ts` ← **Отправка impression**
|
||||
- `src/components/funnel/FunnelRuntime.tsx` ← **Момент отправки**
|
||||
- `src/components/funnel/FunnelUnleashWrapper.tsx` - сбор флагов
|
||||
- `src/lib/funnel/unleash/UnleashProvider.tsx` - инициализация Unleash
|
||||
- `src/lib/funnel/unleash/UnleashContext.tsx` - контекст вариантов
|
||||
|
||||
### Документация
|
||||
- `docs/UNLEASH_ANALYTICS_FLOW.md` - подробный flow
|
||||
- `docs/UNLEASH_ANALYTICS_FIX.md` - проблемы и решения
|
||||
- `docs/AB_TESTING_GUIDE.md` - руководство по AB тестам
|
||||
|
||||
---
|
||||
|
||||
## ✅ Итоговая сводка
|
||||
|
||||
### Момент отправки impression
|
||||
|
||||
**Когда пользователь попадает на экран с AB-тестом:**
|
||||
|
||||
1. `FunnelRuntime` рендерится с `currentScreen`
|
||||
2. `currentScreenFlags` вычисляются для текущего экрана
|
||||
3. `useEffect` срабатывает и вызывает `sendUnleashImpression`
|
||||
4. Проверяется `sessionStorage` (не отправлялось ли)
|
||||
5. Отправляется `window.gtag("event", "experiment_impression", {...})`
|
||||
6. Помечается в `sessionStorage` как отправленное
|
||||
|
||||
### Защита от дубликатов
|
||||
|
||||
- ✅ sessionStorage хранит отправленные события
|
||||
- ✅ При возврате назад события НЕ отправляются повторно
|
||||
- ✅ При перезагрузке F5 события НЕ отправляются повторно
|
||||
- ✅ При закрытии вкладки история очищается (новая сессия)
|
||||
|
||||
### Формат данных
|
||||
|
||||
```javascript
|
||||
{
|
||||
event: "experiment_impression",
|
||||
app_name: "witlab-funnel",
|
||||
feature: "название-флага",
|
||||
treatment: "вариант"
|
||||
}
|
||||
```
|
||||
572
docs/GA_AB_TEST_EXAMPLES.md
Normal file
572
docs/GA_AB_TEST_EXAMPLES.md
Normal file
@ -0,0 +1,572 @@
|
||||
# 📊 Примеры работы AB-тестов и Google Analytics
|
||||
|
||||
## 🎬 Сценарий 1: Первый вход пользователя
|
||||
|
||||
### Воронка с 2 AB-тестами
|
||||
|
||||
```json
|
||||
{
|
||||
"meta": {
|
||||
"id": "soulmate",
|
||||
"googleAnalyticsId": "G-XXXXXXXXXX"
|
||||
},
|
||||
"screens": [
|
||||
{
|
||||
"id": "payment",
|
||||
"variants": [{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "trial-button-test",
|
||||
"unleashVariants": ["v1"]
|
||||
}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id": "gender",
|
||||
"navigation": {
|
||||
"rules": [{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "onboarding-flow",
|
||||
"unleashVariants": ["short"]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Временная последовательность
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+0ms: Пользователь открывает /soulmate/payment │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─ Server-side render
|
||||
├─ layout.tsx загружается
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+50ms: UnleashProvider инициализируется │
|
||||
│ Подключение к Unleash API │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+100ms: PixelsProvider монтируется │
|
||||
│ GoogleAnalytics загружает gtag.js │
|
||||
│ window.gtag становится доступен │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+200ms: FunnelUnleashWrapper сканирует воронку │
|
||||
│ Находит флаги: │
|
||||
│ • trial-button-test (из payment.variants) │
|
||||
│ • onboarding-flow (из gender.navigation) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+300ms: FlagVariantFetcher загружает варианты │
|
||||
│ Unleash SDK возвращает: │
|
||||
│ • trial-button-test → "v1" │
|
||||
│ • onboarding-flow → "short" │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+350ms: activeVariants обновляется │
|
||||
│ { │
|
||||
│ "trial-button-test": "v1", │
|
||||
│ "onboarding-flow": "short" │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+400ms: FunnelRuntime монтируется │
|
||||
│ currentScreen = payment экран │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+410ms: currentScreenFlags вычисляется │
|
||||
│ Анализирует payment экран: │
|
||||
│ • payment.variants → trial-button-test │
|
||||
│ • payment.navigation → нет флагов │
|
||||
│ Результат: ["trial-button-test"] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+420ms: useEffect срабатывает │
|
||||
│ currentScreenFlags = ["trial-button-test"] │
|
||||
│ activeVariants["trial-button-test"] = "v1" │
|
||||
│ │
|
||||
│ sendUnleashImpression("trial-button-test", "v1")│
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─ Проверка: вариант валидный ✅
|
||||
├─ Проверка: браузер ✅
|
||||
├─ Проверка sessionStorage: пусто ✅
|
||||
├─ Проверка window.gtag: есть ✅
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+421ms: ОТПРАВКА В GOOGLE ANALYTICS │
|
||||
│ │
|
||||
│ window.gtag("event", "experiment_impression", { │
|
||||
│ app_name: "witlab-funnel", │
|
||||
│ feature: "trial-button-test", │
|
||||
│ treatment: "v1" │
|
||||
│ }); │
|
||||
│ │
|
||||
│ sessionStorage.setItem( │
|
||||
│ "unleash_impression_trial-button-test_v1", │
|
||||
│ "true" │
|
||||
│ ); │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+422ms: Network request отправлен │
|
||||
│ POST /g/collect?v=2&tid=G-XXX │
|
||||
│ &en=experiment_impression │
|
||||
│ &ep.feature=trial-button-test │
|
||||
│ &ep.treatment=v1 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Console (development): │
|
||||
│ [Unleash Impression] ✅ Sent successfully: │
|
||||
│ { feature: "trial-button-test", variant: "v1" } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Сценарий 2: Переход на следующий экран
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+0ms: Пользователь нажимает "Continue" │
|
||||
│ Router.push("/soulmate/gender") │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+50ms: URL изменился → /soulmate/gender │
|
||||
│ FunnelRuntime ре-рендерится │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+100ms: currentScreen обновляется │
|
||||
│ currentScreen = gender экран │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+110ms: currentScreenFlags пересчитывается │
|
||||
│ Анализирует gender экран: │
|
||||
│ • gender.variants → нет флагов │
|
||||
│ • gender.navigation → onboarding-flow │
|
||||
│ Результат: ["onboarding-flow"] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+120ms: useEffect срабатывает (deps изменились) │
|
||||
│ currentScreenFlags = ["onboarding-flow"] │
|
||||
│ activeVariants["onboarding-flow"] = "short" │
|
||||
│ │
|
||||
│ sendUnleashImpression("onboarding-flow", "short")│
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
├─ Проверка sessionStorage:
|
||||
│ "unleash_impression_onboarding-flow_short"
|
||||
│ не найдено ✅
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+121ms: ОТПРАВКА ВТОРОГО СОБЫТИЯ │
|
||||
│ │
|
||||
│ window.gtag("event", "experiment_impression", { │
|
||||
│ app_name: "witlab-funnel", │
|
||||
│ feature: "onboarding-flow", │
|
||||
│ treatment: "short" │
|
||||
│ }); │
|
||||
│ │
|
||||
│ sessionStorage.setItem( │
|
||||
│ "unleash_impression_onboarding-flow_short", │
|
||||
│ "true" │
|
||||
│ ); │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Состояние sessionStorage
|
||||
|
||||
```javascript
|
||||
// После первого экрана:
|
||||
{
|
||||
"unleash_impression_trial-button-test_v1": "true"
|
||||
}
|
||||
|
||||
// После второго экрана:
|
||||
{
|
||||
"unleash_impression_trial-button-test_v1": "true",
|
||||
"unleash_impression_onboarding-flow_short": "true"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Сценарий 3: Возврат назад (защита от дубликатов)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+0ms: Пользователь нажимает "Back" │
|
||||
│ Router.push("/soulmate/payment") │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+50ms: URL изменился → /soulmate/payment │
|
||||
│ FunnelRuntime ре-рендерится │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+100ms: currentScreen = payment (снова) │
|
||||
│ currentScreenFlags = ["trial-button-test"] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+110ms: useEffect срабатывает │
|
||||
│ sendUnleashImpression("trial-button-test", "v1")│
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Проверка sessionStorage: │
|
||||
│ │
|
||||
│ const key = "unleash_impression_trial-button-test_v1"; │
|
||||
│ const alreadySent = sessionStorage.getItem(key); │
|
||||
│ // "true" ← Уже отправлялось! │
|
||||
│ │
|
||||
│ if (alreadySent) { │
|
||||
│ return; // ❌ ОТПРАВКА НЕ ПРОИСХОДИТ │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Console (development): │
|
||||
│ [Unleash Impression] Skipped (already sent): │
|
||||
│ { feature: "trial-button-test", variant: "v1" } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Сценарий 4: Перезагрузка страницы (F5)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Пользователь на экране /soulmate/gender │
|
||||
│ sessionStorage содержит: │
|
||||
│ { │
|
||||
│ "unleash_impression_trial-button-test_v1": "true", │
|
||||
│ "unleash_impression_onboarding-flow_short": "true" │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+0ms: Пользователь нажимает F5 │
|
||||
│ Браузер перезагружает страницу │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ ВАЖНО: sessionStorage НЕ очищается при перезагрузке! │
|
||||
│ Данные сохраняются! │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+500ms: Страница загружена заново │
|
||||
│ FunnelRuntime монтируется снова │
|
||||
│ currentScreen = gender │
|
||||
│ currentScreenFlags = ["onboarding-flow"] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ useEffect срабатывает: │
|
||||
│ sendUnleashImpression("onboarding-flow", "short") │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Проверка sessionStorage: │
|
||||
│ "unleash_impression_onboarding-flow_short" = "true" │
|
||||
│ │
|
||||
│ ❌ УЖЕ ОТПРАВЛЯЛОСЬ - ПРОПУСКАЕМ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Результат:** Даже после F5 события НЕ отправляются повторно.
|
||||
|
||||
---
|
||||
|
||||
## 🎬 Сценарий 5: Новая вкладка / Закрытие браузера
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ ВКЛАДКА 1: Пользователь прошел воронку │
|
||||
│ sessionStorage: │
|
||||
│ { "unleash_impression_trial-button-test_v1": "true" } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Пользователь открывает НОВУЮ ВКЛАДКУ │
|
||||
│ с тем же URL: /soulmate/payment │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ ВКЛАДКА 2: Новая сессия браузера! │
|
||||
│ sessionStorage пустой: │
|
||||
│ {} │
|
||||
│ │
|
||||
│ (sessionStorage изолирован для каждой вкладки) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ FunnelRuntime загружается: │
|
||||
│ sendUnleashImpression("trial-button-test", "v1") │
|
||||
│ │
|
||||
│ Проверка sessionStorage: ПУСТО │
|
||||
│ ✅ СОБЫТИЕ ОТПРАВЛЯЕТСЯ ЗАНОВО │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Важно:** sessionStorage изолирован для каждой вкладки браузера.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Что видно в Google Analytics
|
||||
|
||||
### Events Report
|
||||
|
||||
```
|
||||
Event name: experiment_impression
|
||||
Total events: 2,345
|
||||
|
||||
By feature parameter:
|
||||
┌────────────────────┬───────────┬────────┐
|
||||
│ Feature │ Count │ Users │
|
||||
├────────────────────┼───────────┼────────┤
|
||||
│ trial-button-test │ 1,234 │ 987 │
|
||||
│ onboarding-flow │ 1,111 │ 896 │
|
||||
└────────────────────┴───────────┴────────┘
|
||||
|
||||
By treatment parameter:
|
||||
┌────────────────────┬───────────┬────────┐
|
||||
│ Treatment │ Count │ Users │
|
||||
├────────────────────┼───────────┼────────┤
|
||||
│ v1 │ 634 │ 507 │
|
||||
│ v2 │ 600 │ 480 │
|
||||
│ control │ 555 │ 445 │
|
||||
│ short │ 556 │ 448 │
|
||||
└────────────────────┴───────────┴────────┘
|
||||
|
||||
Cross-tabulation:
|
||||
┌────────────────────┬───────────┬────────┐
|
||||
│ Feature + Treatment│ Count │ Users │
|
||||
├────────────────────┼───────────┼────────┤
|
||||
│ trial-button-test │ │ │
|
||||
│ ├─ v1 │ 634 │ 507 │
|
||||
│ └─ v2 │ 600 │ 480 │
|
||||
│ │ │ │
|
||||
│ onboarding-flow │ │ │
|
||||
│ ├─ short │ 556 │ 448 │
|
||||
│ └─ control │ 555 │ 445 │
|
||||
└────────────────────┴───────────┴────────┘
|
||||
```
|
||||
|
||||
### DebugView (Realtime)
|
||||
|
||||
```
|
||||
User: 12345abc
|
||||
Session ID: sess_xyz789
|
||||
|
||||
Events:
|
||||
┌─────────┬────────────────────────┬────────────────────┐
|
||||
│ Time │ Event │ Parameters │
|
||||
├─────────┼────────────────────────┼────────────────────┤
|
||||
│ 14:32:10│ page_view │ page: /payment │
|
||||
│ 14:32:11│ experiment_impression │ feature: trial-.. │
|
||||
│ │ │ treatment: v1 │
|
||||
│ 14:32:45│ page_view │ page: /gender │
|
||||
│ 14:32:46│ experiment_impression │ feature: onboard.. │
|
||||
│ │ │ treatment: short │
|
||||
└─────────┴────────────────────────┴────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Network Tab Примеры
|
||||
|
||||
### Первое impression событие
|
||||
|
||||
```http
|
||||
POST /g/collect HTTP/1.1
|
||||
Host: www.google-analytics.com
|
||||
|
||||
Query Parameters:
|
||||
v=2
|
||||
tid=G-XXXXXXXXXX
|
||||
_p=1234567890
|
||||
cid=abc-def-ghi-jkl
|
||||
en=experiment_impression ← Event Name
|
||||
epn.value=1
|
||||
ep.app_name=witlab-funnel ← Custom Parameter
|
||||
ep.feature=trial-button-test ← Custom Parameter
|
||||
ep.treatment=v1 ← Custom Parameter
|
||||
_s=1
|
||||
```
|
||||
|
||||
### Второе impression событие
|
||||
|
||||
```http
|
||||
POST /g/collect HTTP/1.1
|
||||
Host: www.google-analytics.com
|
||||
|
||||
Query Parameters:
|
||||
v=2
|
||||
tid=G-XXXXXXXXXX
|
||||
en=experiment_impression
|
||||
ep.app_name=witlab-funnel
|
||||
ep.feature=onboarding-flow ← Другой флаг
|
||||
ep.treatment=short ← Другой вариант
|
||||
_s=2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
### 1. Очистка истории для повторного тестирования
|
||||
|
||||
```javascript
|
||||
// В консоли браузера:
|
||||
import { clearUnleashImpressions } from "@/lib/funnel/unleash";
|
||||
|
||||
// Очистит все impression ключи
|
||||
clearUnleashImpressions();
|
||||
|
||||
// Или вручную:
|
||||
Object.keys(sessionStorage)
|
||||
.filter(key => key.startsWith("unleash_impression_"))
|
||||
.forEach(key => sessionStorage.removeItem(key));
|
||||
|
||||
// После этого события отправятся заново
|
||||
```
|
||||
|
||||
### 2. Проверка текущего состояния
|
||||
|
||||
```javascript
|
||||
// Посмотреть все impression ключи:
|
||||
Object.keys(sessionStorage)
|
||||
.filter(key => key.startsWith("unleash_impression_"))
|
||||
.forEach(key => {
|
||||
console.log(key, sessionStorage.getItem(key));
|
||||
});
|
||||
|
||||
// Вывод:
|
||||
// unleash_impression_trial-button-test_v1 "true"
|
||||
// unleash_impression_onboarding-flow_short "true"
|
||||
```
|
||||
|
||||
### 3. Имитация нового пользователя
|
||||
|
||||
```javascript
|
||||
// 1. Очистить sessionStorage
|
||||
sessionStorage.clear();
|
||||
|
||||
// 2. Открыть воронку в новой вкладке Incognito
|
||||
// ИЛИ
|
||||
// 3. Перезапустить браузер
|
||||
|
||||
// События отправятся как для нового пользователя
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Практические примеры AB тестов
|
||||
|
||||
### Тест 1: Кнопка оплаты
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "payment",
|
||||
"variants": [{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "trial-payment-button",
|
||||
"unleashVariants": ["v1"]
|
||||
}],
|
||||
"override": {
|
||||
"bottomActionButton": {
|
||||
"text": "Start 7-Day Free Trial"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**Unleash возвращает:**
|
||||
- 50% пользователей: `v1` (кнопка "Start 7-Day Free Trial")
|
||||
- 50% пользователей: `disabled` (кнопка "Continue to Payment")
|
||||
|
||||
**GA события:**
|
||||
```javascript
|
||||
// Группа A (v1):
|
||||
{ feature: "trial-payment-button", treatment: "v1" }
|
||||
|
||||
// Группа B (disabled): НЕТ события
|
||||
// потому что sendUnleashImpression пропускает variant="disabled"
|
||||
```
|
||||
|
||||
### Тест 2: Короткая vs длинная воронка
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "intro",
|
||||
"navigation": {
|
||||
"rules": [{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "funnel-length-test",
|
||||
"unleashVariants": ["short"]
|
||||
}],
|
||||
"nextScreenId": "payment"
|
||||
}],
|
||||
"default": "details"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Unleash возвращает:**
|
||||
- 50%: `short` → переход сразу на payment (3 экрана)
|
||||
- 50%: `disabled` → переход на details (5 экранов)
|
||||
|
||||
**GA события для обеих групп:**
|
||||
```javascript
|
||||
// Все пользователи видят intro экран:
|
||||
{ feature: "funnel-length-test", treatment: "short" }
|
||||
{ feature: "funnel-length-test", treatment: "disabled" }
|
||||
|
||||
// Но дальше идут разными путями!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Контрольный список
|
||||
|
||||
### Перед запуском AB теста:
|
||||
|
||||
- [ ] Google Analytics ID настроен в `funnel.meta.googleAnalyticsId`
|
||||
- [ ] Unleash флаг создан и активен
|
||||
- [ ] Unleash возвращает правильные варианты
|
||||
- [ ] События отправляются (проверить Network tab)
|
||||
- [ ] События видны в GA DebugView
|
||||
- [ ] sessionStorage работает корректно
|
||||
|
||||
### При отладке:
|
||||
|
||||
- [ ] `window.gtag` определен (в консоли)
|
||||
- [ ] Console логи показывают "Sent successfully"
|
||||
- [ ] Network tab показывает POST /g/collect
|
||||
- [ ] sessionStorage содержит impression ключи
|
||||
- [ ] DebugView показывает события в реальном времени
|
||||
178
docs/SOULMATE_AB_QUICK_REFERENCE.md
Normal file
178
docs/SOULMATE_AB_QUICK_REFERENCE.md
Normal file
@ -0,0 +1,178 @@
|
||||
# ⚡ Быстрая справка: AB-тесты Soulmate
|
||||
|
||||
## 🧪 Два AB-теста
|
||||
|
||||
### 1️⃣ `soulmate-onboarding-image`
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Экран: onboarding (1-й экран) │
|
||||
│ URL: /soulmate/onboarding │
|
||||
│ Когда: Сразу при входе в воронку │
|
||||
│ Timing: ~420ms после загрузки │
|
||||
│ │
|
||||
│ Тестирует: │
|
||||
│ • v0 = дефолтное изображение │
|
||||
│ • v1 = альтернативное изображение (PNG) │
|
||||
│ • v2 = видео (MP4) │
|
||||
│ │
|
||||
│ JSON: variants блок │
|
||||
└──────────────────────────────────────────┘
|
||||
│
|
||||
▼ GA impression событие
|
||||
|
||||
window.gtag("event", "experiment_impression", {
|
||||
feature: "soulmate-onboarding-image",
|
||||
treatment: "v1"
|
||||
});
|
||||
```
|
||||
|
||||
### 2️⃣ `soulmate-trial-grid`
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Экран: email (19-й экран) │
|
||||
│ URL: /soulmate/email │
|
||||
│ Когда: При достижении email экрана │
|
||||
│ Timing: ~120ms после перехода │
|
||||
│ │
|
||||
│ Тестирует: │
|
||||
│ • coupon = переход на экран с купоном │
|
||||
│ • grid = переход на выбор тарифов │
|
||||
│ │
|
||||
│ JSON: navigation.rules блок │
|
||||
└──────────────────────────────────────────┘
|
||||
│
|
||||
▼ GA impression событие
|
||||
|
||||
window.gtag("event", "experiment_impression", {
|
||||
feature: "soulmate-trial-grid",
|
||||
treatment: "grid"
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📍 Где в коде
|
||||
|
||||
```typescript
|
||||
// src/components/funnel/FunnelRuntime.tsx (строки 103-150)
|
||||
|
||||
// Определяем флаги текущего экрана
|
||||
const currentScreenFlags = useMemo(() => {
|
||||
const flags = new Set<string>();
|
||||
|
||||
// Из variants
|
||||
currentScreen.variants?.forEach((variant) => {
|
||||
if (condition.unleashFlag) flags.add(condition.unleashFlag);
|
||||
});
|
||||
|
||||
// Из navigation.rules
|
||||
currentScreen.navigation?.rules?.forEach((rule) => {
|
||||
if (condition.unleashFlag) flags.add(condition.unleashFlag);
|
||||
});
|
||||
|
||||
return Array.from(flags);
|
||||
}, [currentScreen]);
|
||||
|
||||
// Отправляем impression когда экран виден
|
||||
useEffect(() => {
|
||||
currentScreenFlags.forEach((flag) => {
|
||||
const variant = activeVariants[flag];
|
||||
sendUnleashImpression(flag, variant); // ← ОТПРАВКА
|
||||
});
|
||||
}, [currentScreenFlags, activeVariants]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ Timeline полной воронки
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+0 Открытие /soulmate/onboarding │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ T+420ms ✅ IMPRESSION #1 │
|
||||
│ feature: soulmate-onboarding-image │
|
||||
│ treatment: v1 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ Пользователь проходит 18 экранов
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+5min Достижение /soulmate/email │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ T+5m ✅ IMPRESSION #2 │
|
||||
│ +120ms feature: soulmate-trial-grid │
|
||||
│ treatment: grid │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Быстрая проверка
|
||||
|
||||
### Console (Development mode)
|
||||
|
||||
```javascript
|
||||
// При загрузке onboarding экрана:
|
||||
[Unleash Impression] ✅ Sent successfully:
|
||||
{ feature: "soulmate-onboarding-image", variant: "v1" }
|
||||
|
||||
// При достижении email экрана:
|
||||
[Unleash Impression] ✅ Sent successfully:
|
||||
{ feature: "soulmate-trial-grid", variant: "grid" }
|
||||
```
|
||||
|
||||
### Network Tab
|
||||
|
||||
```
|
||||
POST /g/collect?...
|
||||
&en=experiment_impression
|
||||
&ep.feature=soulmate-onboarding-image
|
||||
&ep.treatment=v1
|
||||
|
||||
POST /g/collect?...
|
||||
&en=experiment_impression
|
||||
&ep.feature=soulmate-trial-grid
|
||||
&ep.treatment=grid
|
||||
```
|
||||
|
||||
### Session Storage
|
||||
|
||||
```javascript
|
||||
sessionStorage = {
|
||||
"unleash_impression_soulmate-onboarding-image_v1": "true",
|
||||
"unleash_impression_soulmate-trial-grid_grid": "true"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Сравнение
|
||||
|
||||
| | Test #1 | Test #2 |
|
||||
|--|---------|---------|
|
||||
| **Флаг** | `soulmate-onboarding-image` | `soulmate-trial-grid` |
|
||||
| **Экран** | `onboarding` (1-й) | `email` (19-й) |
|
||||
| **URL** | `/soulmate/onboarding` | `/soulmate/email` |
|
||||
| **В JSON** | `variants` | `navigation.rules` |
|
||||
| **Тестирует** | Медиа-контент | Навигацию |
|
||||
| **Когда** | При входе в воронку | Почти в конце воронки |
|
||||
|
||||
---
|
||||
|
||||
## ✅ Главное
|
||||
|
||||
1. **Оба флага загружаются сразу** (preload) для быстрых переходов
|
||||
2. **Impression отправляются последовательно** - только когда пользователь видит экран
|
||||
3. **Защита от дубликатов** через sessionStorage работает автоматически
|
||||
4. **Один и тот же код** отправляет события для обоих тестов
|
||||
|
||||
---
|
||||
|
||||
## 📚 Детальная документация
|
||||
|
||||
- `SOULMATE_AB_TESTS_TIMELINE.md` - полный анализ с диаграммами
|
||||
- `GA_AB_TEST_ANALYSIS.md` - техническая документация
|
||||
- `GA_AB_TEST_EXAMPLES.md` - примеры и сценарии
|
||||
567
docs/SOULMATE_AB_TESTS_TIMELINE.md
Normal file
567
docs/SOULMATE_AB_TESTS_TIMELINE.md
Normal file
@ -0,0 +1,567 @@
|
||||
# 🧪 Конкретный анализ AB-тестов в воронке Soulmate
|
||||
|
||||
## 📋 Два AB-теста в воронке
|
||||
|
||||
1. **`soulmate-onboarding-image`** - тест картинки/видео на первом экране
|
||||
2. **`soulmate-trial-grid`** - тест навигации после email экрана
|
||||
|
||||
---
|
||||
|
||||
## 🎯 AB-тест #1: `soulmate-onboarding-image`
|
||||
|
||||
### 📍 Где находится в JSON
|
||||
|
||||
**Файл:** `public/funnels/soulmate.json`
|
||||
|
||||
**Экран:** `"onboarding"` (первый экран воронки)
|
||||
|
||||
**Строки:** 51-106
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "onboarding",
|
||||
"template": "soulmate",
|
||||
"variants": [
|
||||
{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "soulmate-onboarding-image",
|
||||
"unleashVariants": ["v0"]
|
||||
}],
|
||||
"overrides": {}
|
||||
},
|
||||
{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "soulmate-onboarding-image",
|
||||
"unleashVariants": ["v1"]
|
||||
}],
|
||||
"overrides": {
|
||||
"soulmatePortraitsDelivered": {
|
||||
"mediaUrl": "/images/90b8c77f-c0cd-475d-a4de-bcabb3708c59.png",
|
||||
"mediaType": "image"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "soulmate-onboarding-image",
|
||||
"unleashVariants": ["v2"]
|
||||
}],
|
||||
"overrides": {
|
||||
"soulmatePortraitsDelivered": {
|
||||
"mediaUrl": "/images/275472b0-30e0-47d7-a1ab-8090bc9fb236.mp4",
|
||||
"mediaType": "video"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 🎬 Что тестируется
|
||||
|
||||
Три варианта картинки/видео на первом экране:
|
||||
- **v0** - дефолтное изображение `/soulmate-portrait-delivered-male.jpg`
|
||||
- **v1** - альтернативное изображение (PNG)
|
||||
- **v2** - видео (MP4)
|
||||
|
||||
### ⏱️ КОГДА ОТПРАВЛЯЕТСЯ impression событие
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+0ms: Пользователь открывает /soulmate/onboarding │
|
||||
│ Это ПЕРВЫЙ экран воронки │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+100ms: GoogleAnalytics загружается (gtag.js) │
|
||||
│ window.gtag становится доступен │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+200ms: FunnelUnleashWrapper сканирует ВСЮ воронку │
|
||||
│ Находит флаги: │
|
||||
│ • soulmate-onboarding-image (из onboarding) │
|
||||
│ • soulmate-trial-grid (из email) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+300ms: Unleash SDK возвращает варианты │
|
||||
│ • soulmate-onboarding-image → "v1" (например) │
|
||||
│ • soulmate-trial-grid → "grid" (например) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+400ms: FunnelRuntime монтируется │
|
||||
│ currentScreen = onboarding экран │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+410ms: currentScreenFlags вычисляется │
|
||||
│ Анализирует onboarding экран: │
|
||||
│ • onboarding.variants → soulmate-onboarding-image│
|
||||
│ • onboarding.navigation → нет флагов │
|
||||
│ Результат: ["soulmate-onboarding-image"] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+420ms: useEffect срабатывает │
|
||||
│ (FunnelRuntime.tsx, строки 136-150) │
|
||||
│ │
|
||||
│ ✅ sendUnleashImpression( │
|
||||
│ "soulmate-onboarding-image", │
|
||||
│ "v1" │
|
||||
│ ) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+421ms: ✅ ОТПРАВКА В GOOGLE ANALYTICS │
|
||||
│ │
|
||||
│ window.gtag("event", "experiment_impression", { │
|
||||
│ app_name: "witlab-funnel", │
|
||||
│ feature: "soulmate-onboarding-image", │
|
||||
│ treatment: "v1" │
|
||||
│ }); │
|
||||
│ │
|
||||
│ sessionStorage.setItem( │
|
||||
│ "unleash_impression_soulmate-onboarding-image_v1", │
|
||||
│ "true" │
|
||||
│ ); │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Console (development): │
|
||||
│ [Unleash Impression] ✅ Sent successfully: │
|
||||
│ { │
|
||||
│ feature: "soulmate-onboarding-image", │
|
||||
│ variant: "v1" │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 📊 Когда КОНКРЕТНО
|
||||
|
||||
**✅ Impression отправляется:** Сразу при входе пользователя в воронку на ПЕРВЫЙ экран "onboarding"
|
||||
|
||||
**⏰ Timing:** ~420ms после загрузки страницы `/soulmate/onboarding`
|
||||
|
||||
**🔧 Код:**
|
||||
- **Файл:** `src/components/funnel/FunnelRuntime.tsx` (строки 136-150)
|
||||
- **Функция:** `sendUnleashImpression()` из `src/lib/funnel/unleash/sendImpression.ts`
|
||||
|
||||
**📍 Условие отправки:**
|
||||
```typescript
|
||||
// В FunnelRuntime.tsx
|
||||
const currentScreenFlags = useMemo(() => {
|
||||
const flags = new Set<string>();
|
||||
|
||||
// Сканирует onboarding.variants
|
||||
currentScreen.variants?.forEach((variant) => {
|
||||
variant.conditions.forEach((condition) => {
|
||||
if (condition.conditionType === "unleash" && condition.unleashFlag) {
|
||||
flags.add(condition.unleashFlag); // ← "soulmate-onboarding-image"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(flags); // ["soulmate-onboarding-image"]
|
||||
}, [currentScreen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentScreenFlags.length === 0) return;
|
||||
|
||||
currentScreenFlags.forEach((flag) => {
|
||||
const variant = activeVariants[flag]; // "v1"
|
||||
sendUnleashImpression(flag, variant); // ← ОТПРАВКА
|
||||
});
|
||||
}, [currentScreenFlags, activeVariants]);
|
||||
```
|
||||
|
||||
### 🔍 Как проверить в браузере
|
||||
|
||||
```javascript
|
||||
// 1. Открыть /soulmate/onboarding
|
||||
// 2. Открыть DevTools → Console
|
||||
// 3. Увидеть:
|
||||
[Unleash Impression] ✅ Sent successfully: {
|
||||
feature: "soulmate-onboarding-image",
|
||||
variant: "v1" // или "v0", "v2"
|
||||
}
|
||||
|
||||
// 4. DevTools → Network → Фильтр "collect"
|
||||
POST /g/collect?...&en=experiment_impression
|
||||
&ep.feature=soulmate-onboarding-image
|
||||
&ep.treatment=v1
|
||||
|
||||
// 5. DevTools → Application → Session Storage
|
||||
unleash_impression_soulmate-onboarding-image_v1: "true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 AB-тест #2: `soulmate-trial-grid`
|
||||
|
||||
### 📍 Где находится в JSON
|
||||
|
||||
**Файл:** `public/funnels/soulmate.json`
|
||||
|
||||
**Экран:** `"email"` (экран ввода email)
|
||||
|
||||
**Строки:** 2277-2309
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "email",
|
||||
"template": "email",
|
||||
"navigation": {
|
||||
"rules": [
|
||||
{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "soulmate-trial-grid",
|
||||
"unleashVariants": ["coupon"]
|
||||
}],
|
||||
"nextScreenId": "coupon"
|
||||
},
|
||||
{
|
||||
"conditions": [{
|
||||
"conditionType": "unleash",
|
||||
"unleashFlag": "soulmate-trial-grid",
|
||||
"unleashVariants": ["grid"]
|
||||
}],
|
||||
"nextScreenId": "trial-choice"
|
||||
}
|
||||
],
|
||||
"defaultNextScreenId": "coupon"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 🎬 Что тестируется
|
||||
|
||||
Два варианта навигации после email экрана:
|
||||
- **coupon** - переход на экран с купоном (`coupon`)
|
||||
- **grid** - переход на экран с выбором тарифов (`trial-choice`)
|
||||
|
||||
### ⏱️ КОГДА ОТПРАВЛЯЕТСЯ impression событие
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Пользователь прошел всю воронку: │
|
||||
│ onboarding → gender → partner-gender → ... → email │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+0ms: Пользователь попадает на экран /soulmate/email │
|
||||
│ Это 19-й экран воронки (почти в конце) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ ВАЖНО: Unleash уже загружен! │
|
||||
│ activeVariants уже содержит: │
|
||||
│ { │
|
||||
│ "soulmate-onboarding-image": "v1", ← уже отправлено │
|
||||
│ "soulmate-trial-grid": "grid" ← еще НЕ отправлено│
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+100ms: FunnelRuntime обновляется │
|
||||
│ currentScreen = email экран │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+110ms: currentScreenFlags пересчитывается │
|
||||
│ Анализирует email экран: │
|
||||
│ • email.variants → нет unleash флагов │
|
||||
│ • email.navigation.rules → soulmate-trial-grid │
|
||||
│ Результат: ["soulmate-trial-grid"] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+120ms: useEffect срабатывает │
|
||||
│ (FunnelRuntime.tsx, строки 136-150) │
|
||||
│ │
|
||||
│ ✅ sendUnleashImpression( │
|
||||
│ "soulmate-trial-grid", │
|
||||
│ "grid" │
|
||||
│ ) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+121ms: Проверка sessionStorage: │
|
||||
│ "unleash_impression_soulmate-trial-grid_grid" │
|
||||
│ НЕ найдено ✅ (первый раз на этом экране) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ T+122ms: ✅ ОТПРАВКА В GOOGLE ANALYTICS │
|
||||
│ │
|
||||
│ window.gtag("event", "experiment_impression", { │
|
||||
│ app_name: "witlab-funnel", │
|
||||
│ feature: "soulmate-trial-grid", │
|
||||
│ treatment: "grid" │
|
||||
│ }); │
|
||||
│ │
|
||||
│ sessionStorage.setItem( │
|
||||
│ "unleash_impression_soulmate-trial-grid_grid", │
|
||||
│ "true" │
|
||||
│ ); │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Console (development): │
|
||||
│ [Unleash Impression] ✅ Sent successfully: │
|
||||
│ { │
|
||||
│ feature: "soulmate-trial-grid", │
|
||||
│ variant: "grid" │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 📊 Когда КОНКРЕТНО
|
||||
|
||||
**✅ Impression отправляется:** Когда пользователь доходит до экрана "email" (19-й экран воронки)
|
||||
|
||||
**⏰ Timing:** ~120ms после перехода на `/soulmate/email`
|
||||
|
||||
**🔧 Код:**
|
||||
- **Файл:** `src/components/funnel/FunnelRuntime.tsx` (строки 136-150)
|
||||
- **Функция:** `sendUnleashImpression()` из `src/lib/funnel/unleash/sendImpression.ts`
|
||||
|
||||
**📍 Условие отправки:**
|
||||
```typescript
|
||||
// В FunnelRuntime.tsx
|
||||
const currentScreenFlags = useMemo(() => {
|
||||
const flags = new Set<string>();
|
||||
|
||||
// Сканирует email.variants (пусто)
|
||||
// ...
|
||||
|
||||
// Сканирует email.navigation.rules
|
||||
currentScreen.navigation?.rules?.forEach((rule) => {
|
||||
rule.conditions.forEach((condition) => {
|
||||
if (condition.conditionType === "unleash" && condition.unleashFlag) {
|
||||
flags.add(condition.unleashFlag); // ← "soulmate-trial-grid"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(flags); // ["soulmate-trial-grid"]
|
||||
}, [currentScreen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentScreenFlags.length === 0) return;
|
||||
|
||||
currentScreenFlags.forEach((flag) => {
|
||||
const variant = activeVariants[flag]; // "grid"
|
||||
sendUnleashImpression(flag, variant); // ← ОТПРАВКА
|
||||
});
|
||||
}, [currentScreenFlags, activeVariants]);
|
||||
```
|
||||
|
||||
### 🔍 Как проверить в браузере
|
||||
|
||||
```javascript
|
||||
// 1. Пройти всю воронку до экрана email: /soulmate/email
|
||||
// 2. Открыть DevTools → Console
|
||||
// 3. Увидеть:
|
||||
[Unleash Impression] ✅ Sent successfully: {
|
||||
feature: "soulmate-trial-grid",
|
||||
variant: "grid" // или "coupon"
|
||||
}
|
||||
|
||||
// 4. DevTools → Network → Фильтр "collect"
|
||||
POST /g/collect?...&en=experiment_impression
|
||||
&ep.feature=soulmate-trial-grid
|
||||
&ep.treatment=grid
|
||||
|
||||
// 5. DevTools → Application → Session Storage
|
||||
// Должно быть 2 ключа:
|
||||
unleash_impression_soulmate-onboarding-image_v1: "true"
|
||||
unleash_impression_soulmate-trial-grid_grid: "true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Сравнительная таблица
|
||||
|
||||
| Аспект | soulmate-onboarding-image | soulmate-trial-grid |
|
||||
|--------|---------------------------|---------------------|
|
||||
| **Экран** | `onboarding` (1-й экран) | `email` (19-й экран) |
|
||||
| **Позиция в JSON** | `variants` | `navigation.rules` |
|
||||
| **Что тестирует** | Картинка/видео на первом экране | Навигация после email |
|
||||
| **Варианты** | v0, v1, v2 | coupon, grid |
|
||||
| **Когда отправляется** | Сразу при входе в воронку | При достижении email экрана |
|
||||
| **Timing** | T+420ms после загрузки | T+120ms после перехода на email |
|
||||
| **URL** | `/soulmate/onboarding` | `/soulmate/email` |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Финальная timeline для полной воронки
|
||||
|
||||
```
|
||||
T+0ms Пользователь открывает /soulmate/onboarding
|
||||
│
|
||||
T+420ms ✅ IMPRESSION #1: soulmate-onboarding-image → v1
|
||||
│
|
||||
T+5min Пользователь проходит всю воронку
|
||||
│ (gender, partner-gender, analysis-target, partner-age,
|
||||
│ partner-ethnicity, partner-eye-color, partner-hair-length,
|
||||
│ burnout-support, burnout-result, birthdate,
|
||||
│ nature-archetype, love-priority, love-priority-result,
|
||||
│ relationship-block, qualities, qualities-result,
|
||||
│ progress, progress-result)
|
||||
│
|
||||
T+5min Пользователь доходит до /soulmate/email
|
||||
│
|
||||
T+5min ✅ IMPRESSION #2: soulmate-trial-grid → grid
|
||||
+120ms │
|
||||
│
|
||||
T+5min Пользователь вводит email и нажимает Continue
|
||||
+1min │
|
||||
│
|
||||
└─ Переход на trial-choice (если variant = "grid")
|
||||
ИЛИ
|
||||
Переход на coupon (если variant = "coupon")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Ключевые моменты
|
||||
|
||||
### 1. Оба флага загружаются ЗАРАНЕЕ
|
||||
|
||||
```
|
||||
FunnelUnleashWrapper сканирует ВСЮ воронку при загрузке
|
||||
→ Находит оба флага сразу:
|
||||
• soulmate-onboarding-image
|
||||
• soulmate-trial-grid
|
||||
→ Unleash SDK загружает варианты для ОБОИХ флагов
|
||||
→ Варианты сохраняются в activeVariants
|
||||
```
|
||||
|
||||
**Результат:** Быстрый переход между экранами (флаги уже загружены).
|
||||
|
||||
### 2. Impression отправляются ПОСЛЕДОВАТЕЛЬНО
|
||||
|
||||
```
|
||||
Экран onboarding → impression для soulmate-onboarding-image
|
||||
Экран email → impression для soulmate-trial-grid
|
||||
```
|
||||
|
||||
**Результат:** Точная аналитика - события отправляются только когда пользователь РЕАЛЬНО видит экран.
|
||||
|
||||
### 3. Защита от дубликатов работает
|
||||
|
||||
```
|
||||
sessionStorage хранит:
|
||||
{
|
||||
"unleash_impression_soulmate-onboarding-image_v1": "true",
|
||||
"unleash_impression_soulmate-trial-grid_grid": "true"
|
||||
}
|
||||
```
|
||||
|
||||
**Результат:** Даже если пользователь вернется назад или перезагрузит страницу, события НЕ отправятся повторно.
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Отладка
|
||||
|
||||
### Проверка отправки первого теста
|
||||
|
||||
```bash
|
||||
# 1. Открыть /soulmate/onboarding
|
||||
# 2. В консоли должно появиться:
|
||||
[Unleash Impression] ✅ Sent successfully: {
|
||||
feature: "soulmate-onboarding-image",
|
||||
variant: "v1"
|
||||
}
|
||||
|
||||
# 3. В Network tab:
|
||||
POST /g/collect?...
|
||||
&ep.feature=soulmate-onboarding-image
|
||||
&ep.treatment=v1
|
||||
```
|
||||
|
||||
### Проверка отправки второго теста
|
||||
|
||||
```bash
|
||||
# 1. Пройти до /soulmate/email
|
||||
# 2. В консоли должно появиться:
|
||||
[Unleash Impression] ✅ Sent successfully: {
|
||||
feature: "soulmate-trial-grid",
|
||||
variant: "grid"
|
||||
}
|
||||
|
||||
# 3. В Network tab:
|
||||
POST /g/collect?...
|
||||
&ep.feature=soulmate-trial-grid
|
||||
&ep.treatment=grid
|
||||
```
|
||||
|
||||
### Проверка что оба события отправлены
|
||||
|
||||
```javascript
|
||||
// В консоли браузера:
|
||||
Object.keys(sessionStorage)
|
||||
.filter(key => key.startsWith("unleash_impression_"))
|
||||
.forEach(key => console.log(key, sessionStorage.getItem(key)));
|
||||
|
||||
// Вывод (если пользователь дошел до email):
|
||||
// unleash_impression_soulmate-onboarding-image_v1 "true"
|
||||
// unleash_impression_soulmate-trial-grid_grid "true"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Итоговый ответ
|
||||
|
||||
### Когда отправляется `soulmate-onboarding-image`?
|
||||
|
||||
**🎯 Сразу при входе в воронку на первый экран "onboarding"**
|
||||
- URL: `/soulmate/onboarding`
|
||||
- Timing: ~420ms после загрузки страницы
|
||||
- Экран: 1-й из ~20 экранов воронки
|
||||
|
||||
### Когда отправляется `soulmate-trial-grid`?
|
||||
|
||||
**🎯 Когда пользователь доходит до экрана "email" (почти в конце воронки)**
|
||||
- URL: `/soulmate/email`
|
||||
- Timing: ~120ms после перехода на экран
|
||||
- Экран: 19-й из ~20 экранов воронки
|
||||
|
||||
### Код отправки (для обоих)
|
||||
|
||||
**Файл:** `src/components/funnel/FunnelRuntime.tsx`
|
||||
|
||||
**Строки:** 136-150
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
if (currentScreenFlags.length === 0) return;
|
||||
|
||||
currentScreenFlags.forEach((flag) => {
|
||||
const variant = activeVariants[flag];
|
||||
sendUnleashImpression(flag, variant); // ← ЗДЕСЬ
|
||||
});
|
||||
}, [currentScreenFlags, activeVariants]);
|
||||
```
|
||||
|
||||
**Функция:** `src/lib/funnel/unleash/sendImpression.ts`
|
||||
|
||||
```typescript
|
||||
export function sendUnleashImpression(flag: string, variant: string | undefined) {
|
||||
// Проверки...
|
||||
|
||||
window.gtag("event", "experiment_impression", {
|
||||
app_name: "witlab-funnel",
|
||||
feature: flag,
|
||||
treatment: variant,
|
||||
});
|
||||
|
||||
sessionStorage.setItem(`unleash_impression_${flag}_${variant}`, "true");
|
||||
}
|
||||
```
|
||||
@ -17,15 +17,36 @@ export function PageViewTracker() {
|
||||
|
||||
useEffect(() => {
|
||||
const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : "");
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Track page view in Google Analytics
|
||||
if (typeof window !== "undefined" && typeof window.gtag === "function") {
|
||||
window.gtag("event", "page_view", {
|
||||
const payload = {
|
||||
page_path: url,
|
||||
page_location: window.location.href,
|
||||
page_title: document.title,
|
||||
});
|
||||
console.log(`[GA] Page view tracked: ${url}`);
|
||||
};
|
||||
|
||||
window.gtag("event", "page_view", payload);
|
||||
|
||||
// Детальное логирование
|
||||
console.groupCollapsed(
|
||||
`%c[GA] 📊 Page View Event Sent`,
|
||||
'color: #4285F4; font-weight: bold'
|
||||
);
|
||||
console.log('🕐 Timestamp:', timestamp);
|
||||
console.log('📍 URL:', url);
|
||||
console.log('🌐 Full Location:', window.location.href);
|
||||
console.log('📄 Page Title:', document.title);
|
||||
console.log('📦 Payload:', payload);
|
||||
console.log('✅ Status: Successfully sent to Google Analytics');
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.warn(
|
||||
`%c[GA] ⚠️ Page View NOT Sent`,
|
||||
'color: #FFA500; font-weight: bold',
|
||||
'\nReason: Google Analytics not available (window.gtag is undefined)'
|
||||
);
|
||||
}
|
||||
|
||||
// Track page view in Yandex Metrika
|
||||
@ -33,7 +54,23 @@ export function PageViewTracker() {
|
||||
const counterId = window.__YM_COUNTER_ID__;
|
||||
if (counterId) {
|
||||
window.ym(counterId, "hit", url);
|
||||
console.log(`[YM] Page view tracked: ${url}`);
|
||||
|
||||
// Детальное логирование
|
||||
console.groupCollapsed(
|
||||
`%c[YM] 📊 Page View Event Sent`,
|
||||
'color: #FF0000; font-weight: bold'
|
||||
);
|
||||
console.log('🕐 Timestamp:', timestamp);
|
||||
console.log('📍 URL:', url);
|
||||
console.log('🔢 Counter ID:', counterId);
|
||||
console.log('✅ Status: Successfully sent to Yandex Metrika');
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.warn(
|
||||
`%c[YM] ⚠️ Page View NOT Sent`,
|
||||
'color: #FFA500; font-weight: bold',
|
||||
'\nReason: Counter ID not found'
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
@ -10,23 +10,25 @@
|
||||
* @param variant - вариант который получил пользователь
|
||||
*/
|
||||
export function sendUnleashImpression(flag: string, variant: string | undefined) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("[Unleash Impression] Called with:", { flag, variant });
|
||||
}
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Проверяем что вариант валидный
|
||||
if (!variant || variant === "disabled") {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("[Unleash Impression] Skipped: invalid variant");
|
||||
}
|
||||
console.groupCollapsed(
|
||||
`%c[GA] 🧪 AB Test Impression SKIPPED`,
|
||||
'color: #9E9E9E; font-weight: bold'
|
||||
);
|
||||
console.log('🕐 Timestamp:', timestamp);
|
||||
console.log('🏷️ Flag:', flag);
|
||||
console.log('🎯 Variant:', variant);
|
||||
console.log('⚠️ Reason: Invalid variant (disabled or undefined)');
|
||||
console.groupEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем что браузерное окружение
|
||||
if (typeof window === "undefined") {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("[Unleash Impression] Skipped: not browser environment");
|
||||
}
|
||||
console.warn('[GA] 🧪 AB Test Impression SKIPPED: Not browser environment');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -36,39 +38,62 @@ export function sendUnleashImpression(flag: string, variant: string | undefined)
|
||||
|
||||
if (alreadySent) {
|
||||
// Уже отправляли - пропускаем
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("[Unleash Impression] Skipped (already sent):", {
|
||||
feature: flag,
|
||||
variant: variant,
|
||||
});
|
||||
}
|
||||
console.groupCollapsed(
|
||||
`%c[GA] 🧪 AB Test Impression SKIPPED (Already Sent)`,
|
||||
'color: #9E9E9E; font-weight: bold'
|
||||
);
|
||||
console.log('🕐 Timestamp:', timestamp);
|
||||
console.log('🏷️ Flag:', flag);
|
||||
console.log('🎯 Variant:', variant);
|
||||
console.log('💾 Storage Key:', storageKey);
|
||||
console.log('⚠️ Reason: Already sent in this session');
|
||||
console.log('📊 Check sessionStorage to verify');
|
||||
console.groupEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем наличие gtag
|
||||
if (!window.gtag) {
|
||||
console.warn("[Unleash Impression] ❌ Google Analytics not available (window.gtag is undefined)");
|
||||
console.warn("[Unleash Impression] Check that GoogleAnalytics component is loaded with measurementId");
|
||||
console.groupCollapsed(
|
||||
`%c[GA] 🧪 AB Test Impression NOT Sent`,
|
||||
'color: #FF0000; font-weight: bold'
|
||||
);
|
||||
console.log('🕐 Timestamp:', timestamp);
|
||||
console.log('🏷️ Flag:', flag);
|
||||
console.log('🎯 Variant:', variant);
|
||||
console.log('❌ Error: Google Analytics not available (window.gtag is undefined)');
|
||||
console.log('💡 Solution: Check that GoogleAnalytics component is loaded with measurementId');
|
||||
console.groupEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
// Отправляем событие в Google Analytics
|
||||
window.gtag("event", "experiment_impression", {
|
||||
// Подготавливаем payload
|
||||
const payload = {
|
||||
app_name: "witlab-funnel",
|
||||
feature: flag,
|
||||
treatment: variant,
|
||||
});
|
||||
};
|
||||
|
||||
// Отправляем событие в Google Analytics
|
||||
window.gtag("event", "experiment_impression", payload);
|
||||
|
||||
// Помечаем что отправили
|
||||
sessionStorage.setItem(storageKey, "true");
|
||||
|
||||
// Debug в development
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("[Unleash Impression] ✅ Sent successfully:", {
|
||||
feature: flag,
|
||||
variant: variant,
|
||||
});
|
||||
}
|
||||
// Детальное логирование успешной отправки
|
||||
console.groupCollapsed(
|
||||
`%c[GA] 🧪 AB Test Impression Event Sent`,
|
||||
'color: #4285F4; font-weight: bold'
|
||||
);
|
||||
console.log('🕐 Timestamp:', timestamp);
|
||||
console.log('🏷️ Flag:', flag);
|
||||
console.log('🎯 Variant:', variant);
|
||||
console.log('📦 Event Name:', 'experiment_impression');
|
||||
console.log('📦 Payload:', payload);
|
||||
console.log('💾 Storage Key:', storageKey);
|
||||
console.log('✅ Status: Successfully sent to Google Analytics');
|
||||
console.log('🔍 Verify in Network tab: Look for /g/collect requests');
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,11 +105,21 @@ export function clearUnleashImpressions() {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(sessionStorage)
|
||||
.filter(key => key.startsWith("unleash_impression_"))
|
||||
.forEach(key => sessionStorage.removeItem(key));
|
||||
const keysToRemove = Object.keys(sessionStorage)
|
||||
.filter(key => key.startsWith("unleash_impression_"));
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
keysToRemove.forEach(key => sessionStorage.removeItem(key));
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("[Unleash Impression] Cleared all impression history");
|
||||
}
|
||||
console.groupCollapsed(
|
||||
`%c[GA] 🧪 AB Test Impressions Cleared`,
|
||||
'color: #FF9800; font-weight: bold'
|
||||
);
|
||||
console.log('🕐 Timestamp:', timestamp);
|
||||
console.log('🧹 Keys Removed:', keysToRemove.length);
|
||||
console.log('📋 Removed Keys:', keysToRemove);
|
||||
console.log('✅ Status: All AB test impression history cleared');
|
||||
console.log('💡 New impressions will be sent on next screen view');
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user