w-funnel/docs/GA_AB_TEST_ANALYSIS.md
2025-10-23 21:16:09 +02:00

376 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📊 Анализ 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: "вариант"
}
```