w-funnel/docs/UNLEASH_LAZY_IMPRESSION_IMPLEMENTATION.md
2025-10-22 22:42:01 +02:00

345 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# ✅ Реализация Lazy Impression для Unleash AB тестов
## 🎯 Цель
Отправлять impression события в Google Analytics **только когда пользователь реально видит экран** с AB тестом, но при этом **загружать все флаги заранее** для быстрых переходов.
---
## 🏗️ Архитектура решения
```
┌─────────────────────────────────────────────────────────────┐
│ FunnelUnleashWrapper │
│ ✅ Загружает ВСЕ флаги из воронки заранее │
│ ✅ Сохраняет активные варианты в UnleashContext │
│ ❌ НЕ отправляет impression автоматически │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ UnleashContext │
│ activeVariants: { "trial-test": "v1", "flow-test": "v2" } │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ FunnelRuntime │
│ 1. Собирает флаги для ТЕКУЩЕГО экрана │
│ 2. Получает варианты из activeVariants │
│ 3. Отправляет impression через sendUnleashImpression() │
│ 4. sessionStorage предотвращает дубликаты │
└─────────────────────────────────────────────────────────────┘
```
---
## 📝 Реализованные изменения
### 1. Убрана автоматическая отправка из `useUnleash.ts`
**Было:**
```typescript
// useUnleash автоматически подписывался на события impression
useEffect(() => {
unleashClient.on("impression", handleImpression);
// Отправлял события сразу при загрузке флагов
}, [unleashClient]);
```
**Стало:**
```typescript
// useUnleash теперь только получает вариант, не отправляет события
export function useUnleash({ flag }: UseUnleashProps) {
const variant = useVariant(flag);
return { variant: variant?.name };
}
```
### 2. Создан `sendImpression.ts` - ручная отправка
```typescript
export function sendUnleashImpression(flag: string, variant: string | undefined) {
// Проверки валидности
if (!variant || variant === "disabled") return;
if (typeof window === "undefined") return;
// ✅ Защита от дубликатов через sessionStorage
const storageKey = `unleash_impression_${flag}_${variant}`;
if (sessionStorage.getItem(storageKey)) return;
// Отправка в GA
if (window.gtag) {
window.gtag("event", "experiment_impression", {
app_name: "witlab-funnel",
feature: flag,
treatment: variant,
});
sessionStorage.setItem(storageKey, "true");
}
}
```
**Особенности:**
- ✅ sessionStorage - предотвращает дубликаты при перезагрузке
- ✅ Graceful degradation - не падает если GA не установлена
- ✅ Debug логи в development режиме
### 3. Добавлена логика в `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]);
// Отправляем impression когда экран меняется
useEffect(() => {
if (currentScreenFlags.length === 0) return;
currentScreenFlags.forEach((flag) => {
const variant = activeVariants[flag];
sendUnleashImpression(flag, variant);
});
}, [currentScreenFlags, activeVariants]);
```
**Ключевые моменты:**
- Собираем флаги **только для текущего экрана**
- Получаем варианты из `activeVariants` (уже загружены)
- Отправляем impression **при рендере экрана**
- `sessionStorage` не дает отправить дважды
---
## ✅ Преимущества решения
| Критерий | Результат |
|----------|-----------|
| **Точность аналитики** | ✅ Impression = пользователь увидел экран |
| **Нет дубликатов** | ✅ sessionStorage предотвращает |
| **Скорость загрузки** | ✅ Флаги загружаются заранее (preload) |
| **Плавность переходов** | ✅ Нет задержек - все уже готово |
| **Обратная совместимость** | ✅ Существующая логика не затронута |
---
## 📊 Примеры работы
### Сценарий 1: Пользователь проходит воронку
```
T+0ms: Открывает /soulmate/onboarding
→ FunnelUnleashWrapper загружает все флаги
→ activeVariants: { "trial-test": "v1", "flow-test": "short" }
T+500ms: Экран onboarding рендерится
→ FunnelRuntime собирает флаги для onboarding: []
→ Impression не отправляется (нет AB тестов)
T+10s: Переходит на /soulmate/payment
→ Экран payment рендерится
→ FunnelRuntime собирает флаги: ["trial-test"]
→ ✅ Отправка: experiment_impression { feature: "trial-test", treatment: "v1" }
→ sessionStorage: "unleash_impression_trial-test_v1" = "true"
T+20s: Переходит на /soulmate/gender
→ Экран gender рендерится
→ FunnelRuntime собирает флаги: ["flow-test"]
→ ✅ Отправка: experiment_impression { feature: "flow-test", treatment: "short" }
```
### Сценарий 2: Пользователь перезагружает страницу
```
T+0ms: На экране payment
→ FunnelRuntime пытается отправить impression для "trial-test"
→ sessionStorage.getItem("unleash_impression_trial-test_v1") = "true"
→ ❌ Отправка пропущена (уже было)
→ Console: [Unleash Impression] Skipped (already sent)
```
### Сценарий 3: Без Google Analytics
```
T+0ms: GA не установлена
→ window.gtag = undefined
→ sendUnleashImpression() проверяет window.gtag
→ ❌ Отправка пропущена (gracefully)
→ Console: [Unleash Impression] Google Analytics not available
→ ✅ AB тесты продолжают работать нормально
```
---
## 🔍 Тестирование
### 1. Проверка impression событий
```bash
# 1. Запустить dev сервер
npm run dev:full
# 2. Открыть консоль браузера
# 3. Увидеть логи:
[Unleash Impression] Sent: { feature: "trial-test", variant: "v1" }
# 4. Перезагрузить страницу (F5)
# 5. Увидеть:
[Unleash Impression] Skipped (already sent): { feature: "trial-test", variant: "v1" }
```
### 2. Проверка sessionStorage
```javascript
// В консоли браузера:
Object.keys(sessionStorage)
.filter(key => key.startsWith('unleash_impression_'))
.forEach(key => console.log(key, sessionStorage.getItem(key)));
// Output:
// unleash_impression_trial-test_v1 "true"
// unleash_impression_flow-test_short "true"
```
### 3. Проверка в Google Analytics
```
1. Откройте GA → Realtime → Events
2. Найдите событие: experiment_impression
3. Параметры должны быть:
- app_name: "witlab-funnel"
- feature: "trial-test"
- treatment: "v1"
```
### 4. Очистка для повторного тестирования
```javascript
// В консоли браузера:
Object.keys(sessionStorage)
.filter(key => key.startsWith('unleash_impression_'))
.forEach(key => sessionStorage.removeItem(key));
```
---
## 🐛 Debug и мониторинг
### Development логи
```javascript
// При отправке нового impression:
[Unleash Impression] Sent: {
feature: "trial-button-test",
variant: "v1"
}
// При попытке отправить дубликат:
[Unleash Impression] Skipped (already sent): {
feature: "trial-button-test",
variant: "v1"
}
// Если GA не доступна:
[Unleash Impression] Google Analytics not available
```
### Production мониторинг
```javascript
// Логи отключены в production
// Для мониторинга используйте:
// 1. Google Analytics DebugView
// 2. Network tab (запросы к google-analytics.com)
// 3. Sentry/другие error tracking сервисы
```
---
## 📦 Файлы изменений
| Файл | Изменение | Статус |
|------|-----------|--------|
| `src/lib/funnel/unleash/useUnleash.ts` | Убрана автоотправка impression | ✅ Изменен |
| `src/lib/funnel/unleash/sendImpression.ts` | Новый файл для ручной отправки | ✅ Создан |
| `src/lib/funnel/unleash/index.ts` | Экспорт sendUnleashImpression | ✅ Обновлен |
| `src/components/funnel/FunnelRuntime.tsx` | Логика отправки по экранам | ✅ Изменен |
| `src/lib/funnel/unleash/useScreenUnleash.ts` | Альтернативный подход (не используется) | Создан |
---
## 🎯 Результаты
### ✅ Решенные проблемы:
1. **Преждевременная отправка** - ИСПРАВЛЕНО
- Было: события для всех экранов сразу
- Стало: события только для видимых экранов
2. **Дубликаты при перезагрузке** - ИСПРАВЛЕНО
- Было: повторная отправка при F5
- Стало: sessionStorage блокирует дубликаты
3. **Завышенные метрики** - ИСПРАВЛЕНО
- Было: 100% impression vs 20% достигших
- Стало: impression = реальная видимость
### ✅ Сохраненные преимущества:
1. **Быстрые переходы** - флаги загружаются заранее
2. **Нет задержек** - все готово к моменту рендера
3. **Graceful degradation** - работает без GA
4. **Обратная совместимость** - не ломает существующий код
---
## 🔄 Миграция (если нужно откатить)
Если понадобится вернуться к старой логике:
```typescript
// В useUnleash.ts восстановить:
useEffect(() => {
unleashClient.on("impression", handleImpression);
return () => unleashClient.off("impression", handleImpression);
}, [unleashClient]);
// В FunnelRuntime.tsx удалить:
// - currentScreenFlags
// - useEffect с sendUnleashImpression
```
---
## 📚 Связанные документы
- `UNLEASH_ANALYTICS_FLOW.md` - Как работает отправка в GA
- `UNLEASH_ANALYTICS_FIX.md` - Анализ проблемы и варианты решения
- `AB_TESTING_GUIDE.md` - Общее руководство по AB тестам
---
**Дата:** 2025-01-20
**Статус:** ✅ Реализовано и протестировано
**Версия:** 1.0