# 📊 Анализ 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
```
### 2. Сбор флагов из воронки
**Файл:** `src/components/funnel/FunnelUnleashWrapper.tsx`
```typescript
// Сканирует ВСЕ экраны и собирает уникальные флаги
const allFlags = useMemo(() => {
const flags = new Set();
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();
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: "вариант"
}
```