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