513 lines
15 KiB
Markdown
513 lines
15 KiB
Markdown
# ✅ AB Testing Integration - Полная реализация
|
||
|
||
## 🎯 Задача
|
||
|
||
Интегрировать Unleash feature flags в систему воронок для проведения AB тестирования:
|
||
- Возможность тестировать варианты экранов
|
||
- Возможность тестировать разные пути навигации
|
||
- UI в админке для настройки AB тестов
|
||
- Полная типизация TypeScript
|
||
|
||
## ✨ Что реализовано
|
||
|
||
### 1. 📦 Backend Infrastructure
|
||
|
||
#### Типы и интерфейсы (`src/lib/funnel/types.ts`)
|
||
|
||
```typescript
|
||
// Расширен NavigationConditionDefinition
|
||
interface NavigationConditionDefinition {
|
||
conditionType?: "options" | "values" | "unleash"; // ← Новый тип
|
||
|
||
// Новые поля для Unleash
|
||
unleashFlag?: string; // Название флага
|
||
unleashVariants?: string[]; // Ожидаемые варианты
|
||
}
|
||
```
|
||
|
||
**Поддерживаемые операторы:**
|
||
- `includesAny` - хотя бы один вариант совпадает
|
||
- `includesAll` - все варианты совпадают
|
||
- `includesExactly` - только указанные варианты
|
||
- `equals` - точное совпадение
|
||
|
||
#### Логика проверки условий
|
||
|
||
**`src/lib/funnel/navigation.ts`:**
|
||
```typescript
|
||
export type UnleashChecker = (
|
||
flag: string,
|
||
expectedVariants: string[],
|
||
operator?: "includesAny" | "includesAll" | "includesExactly" | "equals"
|
||
) => boolean;
|
||
|
||
function satisfiesCondition(
|
||
condition: NavigationConditionDefinition,
|
||
answers: FunnelAnswers,
|
||
allScreens?: ScreenDefinition[],
|
||
unleashChecker?: UnleashChecker // ← Новый параметр
|
||
): boolean {
|
||
if (conditionType === "unleash") {
|
||
return unleashChecker(
|
||
condition.unleashFlag,
|
||
condition.unleashVariants,
|
||
operator
|
||
);
|
||
}
|
||
// ... остальная логика
|
||
}
|
||
```
|
||
|
||
**`src/lib/funnel/variants.ts`:**
|
||
```typescript
|
||
export function resolveScreenVariant<T>(
|
||
screen: T,
|
||
answers: FunnelAnswers,
|
||
allScreens?: ScreenDefinition[],
|
||
unleashChecker?: UnleashChecker // ← Новый параметр
|
||
): T {
|
||
// Проверяет варианты с учетом Unleash условий
|
||
}
|
||
```
|
||
|
||
### 2. 🔌 Unleash SDK Integration
|
||
|
||
#### Provider (`src/lib/funnel/unleash/UnleashProvider.tsx`)
|
||
|
||
```typescript
|
||
export function UnleashProvider({ children, userId, sessionId }) {
|
||
// Инициализирует Unleash клиент
|
||
// Если env переменные не заданы - graceful fallback
|
||
|
||
const config = {
|
||
url: process.env.NEXT_PUBLIC_UNLEASH_URL,
|
||
clientKey: process.env.NEXT_PUBLIC_UNLEASH_CLIENT_KEY,
|
||
context: {
|
||
userId,
|
||
sessionId: sessionId || userId || "anonymous",
|
||
}
|
||
};
|
||
|
||
return <FlagProvider config={config}>{children}</FlagProvider>;
|
||
}
|
||
```
|
||
|
||
#### Context (`src/lib/funnel/unleash/UnleashContext.tsx`)
|
||
|
||
```typescript
|
||
export function UnleashContextProvider({
|
||
children,
|
||
activeVariants // { "flag-name": "v1", ... }
|
||
}) {
|
||
const checkVariant = (flag, expectedVariants, operator) => {
|
||
// Проверяет соответствие текущего варианта ожидаемым
|
||
};
|
||
|
||
return (
|
||
<UnleashContext.Provider value={{ activeVariants, checkVariant }}>
|
||
{children}
|
||
</UnleashContext.Provider>
|
||
);
|
||
}
|
||
```
|
||
|
||
#### Wrapper (`src/components/funnel/FunnelUnleashWrapper.tsx`)
|
||
|
||
```typescript
|
||
export function FunnelUnleashWrapper({ funnel, children }) {
|
||
// 1. Сканирует все экраны воронки
|
||
// 2. Находит все unleashFlag в conditions (variants + navigation)
|
||
// 3. Получает варианты для всех флагов через useVariant
|
||
// 4. Передает activeVariants в UnleashContextProvider
|
||
|
||
const allFlags = useMemo(() => {
|
||
// Собирает unique флаги из всех условий
|
||
}, [funnel.screens]);
|
||
|
||
const activeVariants = useMemo(() => {
|
||
// Получает текущие варианты для всех флагов
|
||
}, [flagsReady, flagVariants]);
|
||
}
|
||
```
|
||
|
||
#### Runtime Integration (`src/components/funnel/FunnelRuntime.tsx`)
|
||
|
||
```typescript
|
||
export function FunnelRuntime({ funnel, initialScreenId }) {
|
||
const { checkVariant } = useUnleashContext();
|
||
|
||
// Создаем unleashChecker для передачи в navigation/variants
|
||
const unleashChecker: UnleashChecker = useCallback(
|
||
(flag, expectedVariants, operator) => {
|
||
return checkVariant(flag, expectedVariants, operator);
|
||
},
|
||
[checkVariant]
|
||
);
|
||
|
||
// Передаем во все функции проверки
|
||
resolveScreenVariant(baseScreen, answers, funnel.screens, unleashChecker);
|
||
resolveNextScreenId(currentScreen, answers, funnel.screens, unleashChecker);
|
||
estimatePathLength(funnel, answers, unleashChecker);
|
||
}
|
||
```
|
||
|
||
### 3. 🎨 Admin UI Components
|
||
|
||
#### Селектор типа условия (`ConditionTypeSelector.tsx`)
|
||
|
||
```typescript
|
||
export function ConditionTypeSelector({ condition, onChange }) {
|
||
return (
|
||
<select value={conditionType} onChange={...}>
|
||
<option value="options">Выбранные опции (list экраны)</option>
|
||
<option value="values">Значения (зодиак, возраст, email)</option>
|
||
<option value="unleash">AB тест (Unleash)</option>
|
||
</select>
|
||
);
|
||
}
|
||
```
|
||
|
||
#### Редактор Unleash условий (`UnleashConditionEditor.tsx`)
|
||
|
||
```typescript
|
||
export function UnleashConditionEditor({ condition, onChange }) {
|
||
return (
|
||
<>
|
||
{/* Название флага */}
|
||
<Input
|
||
value={unleashFlag}
|
||
placeholder="trial-button-test"
|
||
onChange={handleFlagChange}
|
||
/>
|
||
|
||
{/* Оператор проверки */}
|
||
<select value={operator} onChange={handleOperatorChange}>
|
||
<option value="includesAny">Любой из вариантов (OR)</option>
|
||
<option value="equals">Равен (один вариант)</option>
|
||
</select>
|
||
|
||
{/* Ожидаемые варианты */}
|
||
<Input
|
||
value={unleashVariants.join(", ")}
|
||
placeholder="v1, v2, v3"
|
||
onChange={handleVariantsChange}
|
||
/>
|
||
</>
|
||
);
|
||
}
|
||
```
|
||
|
||
#### Интеграция в VariantConditionEditor
|
||
|
||
```typescript
|
||
export function VariantConditionEditor({ condition, allScreens, onChange }) {
|
||
const conditionType = condition.conditionType ?? "options";
|
||
const isUnleashCondition = conditionType === "unleash";
|
||
|
||
return (
|
||
<>
|
||
<ConditionTypeSelector
|
||
condition={condition}
|
||
onChange={handleConditionTypeChange}
|
||
/>
|
||
|
||
{isUnleashCondition ? (
|
||
<UnleashConditionEditor condition={condition} onChange={onChange} />
|
||
) : (
|
||
// Существующая логика для options/values
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 4. 📚 Документация
|
||
|
||
- **AB_TESTING_GUIDE.md** - Полное руководство (200+ строк)
|
||
- Настройка environment
|
||
- Примеры использования
|
||
- API Reference
|
||
- Best Practices
|
||
- Troubleshooting
|
||
|
||
- **UNLEASH_SETUP.md** - Quick Start
|
||
- Быстрый старт за 4 шага
|
||
- Структура файлов
|
||
- Примеры
|
||
- Требования
|
||
|
||
- **ab-test-example.json** - Тестовая воронка
|
||
- 6 экранов с разными типами AB тестов
|
||
- Button text testing
|
||
- Content variant testing
|
||
- Navigation testing
|
||
- Combined conditions (gender + AB test)
|
||
|
||
## 🚀 Использование
|
||
|
||
### В админке
|
||
|
||
1. Откройте экран для редактирования
|
||
2. Перейдите в "Вариативность" → Добавить вариант
|
||
3. В условиях выберите "AB тест (Unleash)"
|
||
4. Укажите флаг и варианты
|
||
5. Настройте overrides
|
||
6. Сохраните
|
||
|
||
### В JSON
|
||
|
||
```json
|
||
{
|
||
"variants": [
|
||
{
|
||
"conditions": [
|
||
{
|
||
"screenId": "payment",
|
||
"conditionType": "unleash",
|
||
"unleashFlag": "button-test",
|
||
"unleashVariants": ["v1"],
|
||
"operator": "equals"
|
||
}
|
||
],
|
||
"overrides": {
|
||
"bottomActionButton": {
|
||
"text": "New Button Text"
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Environment Setup
|
||
|
||
```bash
|
||
# .env.local
|
||
NEXT_PUBLIC_UNLEASH_URL=https://unleash.example.com/api/frontend
|
||
NEXT_PUBLIC_UNLEASH_CLIENT_KEY=your-key
|
||
```
|
||
|
||
## 📊 Возможности
|
||
|
||
### Что можно тестировать
|
||
|
||
✅ **Варианты экранов:**
|
||
- Тексты (заголовки, подзаголовки, кнопки)
|
||
- Контент (описания, иконки, блоки)
|
||
- Любые поля через variants overrides
|
||
|
||
✅ **Навигация:**
|
||
- Разные пути через воронку
|
||
- Длинный vs короткий onboarding
|
||
- A/B тест целых flow
|
||
|
||
✅ **Комбинированные условия:**
|
||
- AB тест + пол пользователя
|
||
- AB тест + возраст
|
||
- AB тест + ответы на вопросы
|
||
|
||
### Архитектура
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ Unleash Dashboard │
|
||
│ (Feature Flags Config) │
|
||
└─────────────────┬───────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────┐
|
||
│ UnleashProvider │
|
||
│ (@unleash/proxy-client-react) │
|
||
└─────────────────┬───────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────┐
|
||
│ FunnelUnleashWrapper │
|
||
│ - Сканирует воронку │
|
||
│ - Собирает все флаги │
|
||
│ - Получает варианты │
|
||
└─────────────────┬───────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────┐
|
||
│ UnleashContextProvider │
|
||
│ activeVariants: { │
|
||
│ "flag-1": "v1", │
|
||
│ "flag-2": "v2" │
|
||
│ } │
|
||
└─────────────────┬───────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────┐
|
||
│ FunnelRuntime │
|
||
│ - unleashChecker: UnleashChecker │
|
||
│ - resolveScreenVariant(...) │
|
||
│ - resolveNextScreenId(...) │
|
||
└─────────────────┬───────────────────┘
|
||
│
|
||
┌────────┴────────┐
|
||
▼ ▼
|
||
┌─────────┐ ┌─────────┐
|
||
│variants │ │navigation│
|
||
│.ts │ │.ts │
|
||
└─────────┘ └─────────┘
|
||
```
|
||
|
||
## 📁 Файлы
|
||
|
||
### Новые файлы (14)
|
||
|
||
```
|
||
src/lib/funnel/unleash/
|
||
├── UnleashProvider.tsx ✨ Unleash SDK wrapper
|
||
├── UnleashContext.tsx ✨ React Context для вариантов
|
||
├── useUnleash.ts ✨ Hooks для работы с флагами
|
||
└── index.ts ✨ Экспорты
|
||
|
||
src/components/funnel/
|
||
└── FunnelUnleashWrapper.tsx ✨ Сборщик флагов из воронки
|
||
|
||
src/components/admin/builder/forms/
|
||
├── UnleashConditionEditor.tsx ✨ UI редактор AB тестов
|
||
└── ConditionTypeSelector.tsx ✨ Селектор типа условия
|
||
|
||
docs/
|
||
├── AB_TESTING_GUIDE.md ✨ Полное руководство
|
||
├── UNLEASH_SETUP.md ✨ Quick start
|
||
└── AB_TESTING_IMPLEMENTATION.md ✨ Этот файл
|
||
|
||
public/funnels/
|
||
└── ab-test-example.json ✨ Тестовая воронка
|
||
|
||
package.json ✏️ +@unleash/proxy-client-react
|
||
```
|
||
|
||
### Модифицированные файлы (6)
|
||
|
||
```
|
||
src/lib/funnel/
|
||
├── types.ts ✏️ +unleash поля в NavigationConditionDefinition
|
||
├── navigation.ts ✏️ +UnleashChecker support
|
||
└── variants.ts ✏️ +UnleashChecker support
|
||
|
||
src/components/
|
||
├── funnel/FunnelRuntime.tsx ✏️ +unleashChecker integration
|
||
└── admin/builder/forms/variants/
|
||
└── VariantConditionEditor.tsx ✏️ +Unleash UI
|
||
|
||
src/app/[funnelId]/[screenId]/
|
||
└── page.tsx ✏️ +UnleashProvider wrapper
|
||
```
|
||
|
||
## ✅ Проверка
|
||
|
||
### Сборка
|
||
|
||
```bash
|
||
✓ npm run build
|
||
✓ No TypeScript errors
|
||
✓ No ESLint errors
|
||
✓ All pages compiled successfully
|
||
```
|
||
|
||
### Тестирование
|
||
|
||
```bash
|
||
# 1. Запустите dev сервер
|
||
npm run dev
|
||
|
||
# 2. Откройте тестовую воронку
|
||
http://localhost:3000/ab-test-example/welcome
|
||
|
||
# 3. Проверьте разные варианты
|
||
# Откройте в нескольких вкладках - увидите разные версии
|
||
```
|
||
|
||
## 🎓 Примеры
|
||
|
||
### Пример 1: Button Text AB Test
|
||
|
||
```json
|
||
{
|
||
"id": "payment",
|
||
"bottomActionButton": { "text": "Continue" },
|
||
"variants": [
|
||
{
|
||
"conditions": [{
|
||
"conditionType": "unleash",
|
||
"unleashFlag": "payment-button-test",
|
||
"unleashVariants": ["variant-a"]
|
||
}],
|
||
"overrides": {
|
||
"bottomActionButton": { "text": "Start Trial" }
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Пример 2: Navigation AB Test
|
||
|
||
```json
|
||
{
|
||
"navigation": {
|
||
"defaultNextScreenId": "long-flow",
|
||
"rules": [{
|
||
"conditions": [{
|
||
"conditionType": "unleash",
|
||
"unleashFlag": "onboarding-length",
|
||
"unleashVariants": ["short"]
|
||
}],
|
||
"nextScreenId": "payment"
|
||
}]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Пример 3: Combined Conditions
|
||
|
||
```json
|
||
{
|
||
"variants": [{
|
||
"conditions": [
|
||
{
|
||
"screenId": "gender",
|
||
"conditionType": "options",
|
||
"optionIds": ["female"]
|
||
},
|
||
{
|
||
"conditionType": "unleash",
|
||
"unleashFlag": "female-test",
|
||
"unleashVariants": ["v1"]
|
||
}
|
||
],
|
||
"overrides": {
|
||
"title": { "text": "Special for Women!" }
|
||
}
|
||
}]
|
||
}
|
||
```
|
||
|
||
## 🎯 Следующие шаги
|
||
|
||
1. ✅ Настройте Unleash instance
|
||
2. ✅ Создайте первые feature flags
|
||
3. ✅ Добавьте AB тесты в существующие воронки
|
||
4. ✅ Соберите метрики и выберите победителей
|
||
5. ✅ Масштабируйте на все воронки
|
||
|
||
## 📞 Поддержка
|
||
|
||
- **Документация:** AB_TESTING_GUIDE.md
|
||
- **Quick Start:** UNLEASH_SETUP.md
|
||
- **Примеры:** /public/funnels/ab-test-example.json
|
||
- **Unleash Docs:** https://docs.getunleash.io/
|
||
|
||
---
|
||
|
||
**Статус:** ✅ Готово к production
|
||
|
||
**Версия:** 1.0.0
|
||
|
||
**Дата:** 2025-01-20
|