Skip to content

[feature] AB테스트를 위한 실험 구조 구축#1358

Merged
seongwon030 merged 15 commits intodevelop-fefrom
feature/#1354-mixpanel-experiment-hook-MOA-763
Mar 28, 2026
Merged

[feature] AB테스트를 위한 실험 구조 구축#1358
seongwon030 merged 15 commits intodevelop-fefrom
feature/#1354-mixpanel-experiment-hook-MOA-763

Conversation

@seongwon030
Copy link
Copy Markdown
Member

@seongwon030 seongwon030 commented Mar 28, 2026

#️⃣연관된 이슈

ex) #1354

📝작업 내용

Mixpanel 이벤트 데이터를 바탕으로 퍼널 특정 구간(지원 버튼 CTA, 팝업 등)에 대한 A/B 실험을 했었습니다.
개발자가 좀 더 쉽게 AB테스트를 할 수 있도록 localStorage 기반 실험 구조를 만들었습니다.

동작 방식

  1. 앱 최초 로딩 시 initializeExperiments()ALL_EXPERIMENTS를 순회하며 배리언트를 배정한다.
  2. 이미 배정된 실험은 재배정하지 않는다. (같은 유저는 항상 같은 배리언트를 본다)
  3. 배정 결과는 localStoragemoadong_experiments 키에 저장된다.
  4. 컴포넌트에서는 useExperimentVariant(experimentDefinition)으로 배리언트를 읽어 분기한다.

새 실험 추가 방법

// 1. definitions.ts에 실험 정의 추가
export const myExperiment = {
  key: 'my_experiment_v1',
  variants: ['A', 'B'] as const,
  defaultVariant: 'A',
  weights: { A: 50, B: 50 },
} satisfies ExperimentDefinition<'A' | 'B'>;

export const ALL_EXPERIMENTS = [..., myExperiment] as const;

// 2. 컴포넌트에서 분기
const variant = useExperimentVariant(myExperiment);
return variant === 'B' ? <NewVersion /> : <DefaultVersion />;

자세한 내용은 docs/experiments.md 참고.

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • New Features

    • 클라이언트 A/B 테스트 시스템 도입: 변형 선택·할당, 로컬에 할당 보존, 앱 시작 시 초기화 및 컴포넌트용 사용 훅 제공
  • Documentation

    • A/B 테스트 가이드(설정 절차·버전/네이밍 규칙·리셋 방법·추적권장 포함) 추가
    • Mixpanel 주간 리포트용 관리자·사용자 프롬프트·템플릿 및 주간 리포트 샘플 문서 추가
    • 개발/운영용 내부 가이드 및 에이전트 명세 문서 다수 추가

@seongwon030 seongwon030 self-assigned this Mar 28, 2026
@seongwon030 seongwon030 added ✨ Feature 기능 개발 💻 FE Frontend AB TEST AB 테스트 관련 labels Mar 28, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moadong Ready Ready Preview, Comment Mar 28, 2026 8:10am

@seongwon030 seongwon030 requested review from oesnuj and suhyun113 March 28, 2026 07:14
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

Warning

Rate limit exceeded

@seongwon030 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 13 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 13 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dd9694d0-b3d5-4eae-8519-fcfcffb5358c

📥 Commits

Reviewing files that changed from the base of the PR and between 864a64c and fef5a9b.

📒 Files selected for processing (1)
  • frontend/.claude/commands/create-e2e-test.md

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

클라이언트 측 A/B 테스트 프레임워크와 Mixpanel 주간 리포트 관련 문서를 추가합니다. 실험 타입·정의·가중치 기반 할당·localStorage 저장·초기화·React 훅 및 관련 문서(운영 지침·프롬프트·샘플 리포트)가 도입됩니다.

Changes

Cohort / File(s) Summary
문서: 실험 및 리포팅
docs/experiments.md, docs/mixpanel-reporting.md, docs/mixpanel-weekly-report-prompts.md, docs/mixpanel-admin-weekly-report-prompts.md, docs/weekly-reports/2026-W12-user-mixpanel-report.md
클라이언트 A/B 실험 운영 가이드(폴더 구조, 등록 절차, localStorage 저장 규칙, 초기화/리셋 방법) 및 Mixpanel 주간 리포트/프롬프트/템플릿/샘플 리포트 문서 추가.
실험 타입 및 정의
frontend/src/experiments/types.ts, frontend/src/experiments/definitions.ts
타입 선언(ExperimentVariant, ExperimentDefinition, ExperimentAssignments) 추가 및 예시 실험(main_banner_v1, apply_button_copy_v1)과 ALL_EXPERIMENTS 내보냄.
실험 저장소 및 할당 로직
frontend/src/experiments/ExperimentRepository.ts
moadong_experiments 키로 localStorage 안전 읽기/쓰기, 가중치 기반 선택(pickWeightedVariant), 기존 유효 할당 보존·신규 할당 생성, resetAssignments() 포함한 singleton experimentRepository 추가.
초기화 및 훅 통합
frontend/src/experiments/initializeExperiments.ts, frontend/src/hooks/Experiment/useExperimentVariant.ts, frontend/src/index.tsx
initializeExperiments() 함수 추가 및 index에서 호출하도록 삽입. React 훅 useExperimentVariant로 컴포넌트가 저장된 할당을 조회하도록 통합.
기타 리포지토리 문서/설정
.gitignore, frontend/CLAUDE.md, frontend/.claude/...
.gitignore 일부 항목 제거 및 프론트엔드 설명서·Claude 에이전트/명령 관련 문서 다수 추가.

Sequence Diagram(s)

sequenceDiagram
    participant App as Frontend App
    participant Init as initializeExperiments()
    participant Repo as ExperimentRepository
    participant Storage as localStorage
    participant Hook as useExperimentVariant(Component)

    App->>Init: initializeExperiments()
    Init->>Repo: fetchAndAssignExperiments(ALL_EXPERIMENTS)
    Repo->>Storage: Read "moadong_experiments"
    alt Existing valid assignment
        Storage-->>Repo: stored assignments
        Repo->>Repo: validate assignments vs variants
    else New or invalid assignment
        Repo->>Repo: pickWeightedVariant() per experiment
        Repo->>Storage: Write updated assignments
    end
    Repo-->>Init: complete

    Note over App,Hook: Component 렌더링 시
    Hook->>Repo: getVariant(experiment)
    Repo->>Storage: Read "moadong_experiments"
    Storage-->>Repo: assignments
    Repo->>Repo: return assigned or default variant
    Repo-->>Hook: variant
    Hook-->>App: render with variant
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • [release] v1.1.10 #1006: frontend/src/index.tsx 초기화 흐름 변경과 관련 — 본 PR이 index 초기화에 initializeExperiments()를 추가함으로써 초기화 순서 충돌 가능성 확인 필요.

Suggested reviewers

  • oesnuj
  • lepitaaar
  • suhyun113
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 실험 구조 구축(A/B 테스트)이라는 주요 변경사항을 명확하게 요약하며, 변경사항과 완전히 관련있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#1354-mixpanel-experiment-hook-MOA-763

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 28, 2026

🎨 UI 변경사항을 확인해주세요

변경된 스토리를 Chromatic에서 확인해주세요.

구분 링크
🔍 변경사항 리뷰 https://www.chromatic.com/build?appId=67904e61c16daa99a63b44a7&number=114
📖 Storybook https://67904e61c16daa99a63b44a7-tuqpgjyeaa.chromatic.com/

2개 스토리 변경 · 전체 56개 스토리 · 22개 컴포넌트

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
docs/weekly-reports/2026-W12-user-mixpanel-report.md (1)

12-12: 제출 지표의 기준(전체 vs 순차 퍼널)을 명시해 주세요.

같은 문서에서 제출 유저가 3(KPI)인데 퍼널 끝은 1로 보여 해석 충돌이 발생합니다. 순차 퍼널 기준이라면 Line 17에 strict/sequential funnel임을 명시해 오해를 막는 게 좋습니다.

Also applies to: 17-19

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/weekly-reports/2026-W12-user-mixpanel-report.md` at line 12, Clarify
that the report shows "overall vs sequential (strict) funnel" metrics for the
Application Form Submitted line (the string "Application Form Submitted
(events/users): `11 / 3` vs `34 / 33`") by labeling which number is the overall
metric and which is the strict/sequential-funnel metric, and add a short note
explaining the KPI count (3) refers to the overall metric while the sequential
funnel end shows 1 due to strict step matching; also update the block that
currently contains the text "strict/sequential funnel" to explicitly state
"overall / strict (sequential) funnel" and apply the same labeling/clarification
to the analogous metrics mentioned later (the items referenced in the comment
for lines 17-19).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/experiments.md`:
- Around line 25-26: The docs reference the wrong localStorage key; update the
documentation to use the actual implementation key "moadong_experiments"
(instead of "moadong_experiment_assignments") wherever the key is mentioned
(including the initialization guide and any lines that instruct storing JSON),
so the doc matches the code used by useExperimentVariant and the runtime
initialization logic.

In `@docs/mixpanel-reporting.md`:
- Around line 9-10: The two links in docs/mixpanel-reporting.md use absolute
local paths and will break for others; update the links for "사용자(학생) 흐름 리포트"
(mixpanel-weekly-report-prompts.md) and "관리자(Admin) 흐름 리포트"
(mixpanel-admin-weekly-report-prompts.md) to use repository-relative paths
(e.g., ./mixpanel-weekly-report-prompts.md or
docs/mixpanel-weekly-report-prompts.md) instead of /Users/..., and make the same
change for the other occurrence noted (line 14).

In `@frontend/src/experiments/ExperimentRepository.ts`:
- Around line 9-16: safeReadAssignments currently parses localStorage without
validating shape, causing runtime errors when the parsed value is null, an
array, or has non-string values; update safeReadAssignments to validate that
parsed result is a plain object (not null/array) and that each property value is
a string before casting to ExperimentAssignments, returning {} if validation
fails; reference the ASSIGNMENT_STORAGE_KEY read and the ExperimentAssignments
type when adding checks so callers of safeReadAssignments (and later code
accessing assignment records) always receive a safe string-to-string map.
- Around line 18-20: The write path isn't protected against Storage exceptions:
wrap the localStorage.setItem call inside writeAssignments and the
localStorage.removeItem call (the one that clears ASSIGNMENT_STORAGE_KEY) in a
try/catch that catches and silently handles SecurityError/QuotaExceededError
(and any other thrown error), log the error for debugging (or use the existing
logger), and ensure the app continues with a safe fallback (e.g., return without
throwing) so behavior matches safeReadAssignments; locate these fixes by
updating function writeAssignments and the block that calls
localStorage.removeItem with ASSIGNMENT_STORAGE_KEY.

---

Nitpick comments:
In `@docs/weekly-reports/2026-W12-user-mixpanel-report.md`:
- Line 12: Clarify that the report shows "overall vs sequential (strict) funnel"
metrics for the Application Form Submitted line (the string "Application Form
Submitted (events/users): `11 / 3` vs `34 / 33`") by labeling which number is
the overall metric and which is the strict/sequential-funnel metric, and add a
short note explaining the KPI count (3) refers to the overall metric while the
sequential funnel end shows 1 due to strict step matching; also update the block
that currently contains the text "strict/sequential funnel" to explicitly state
"overall / strict (sequential) funnel" and apply the same labeling/clarification
to the analogous metrics mentioned later (the items referenced in the comment
for lines 17-19).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f36763bc-0c5c-4f40-9070-c0e8132d9229

📥 Commits

Reviewing files that changed from the base of the PR and between 709cbf3 and b89c6cc.

📒 Files selected for processing (11)
  • docs/experiments.md
  • docs/mixpanel-admin-weekly-report-prompts.md
  • docs/mixpanel-reporting.md
  • docs/mixpanel-weekly-report-prompts.md
  • docs/weekly-reports/2026-W12-user-mixpanel-report.md
  • frontend/src/experiments/ExperimentRepository.ts
  • frontend/src/experiments/definitions.ts
  • frontend/src/experiments/initializeExperiments.ts
  • frontend/src/experiments/types.ts
  • frontend/src/hooks/Experiment/useExperimentVariant.ts
  • frontend/src/index.tsx

Copy link
Copy Markdown
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요청하신 Always Approve 정책에 따라 승인합니다.

동시에 재발 방지 차원에서 인라인 코멘트로 아래 확인 포인트를 남겼습니다.

  • 실험 저장 write 예외 처리(fail-open)
  • 실험 스토리지 key 문서/구현 정합성
  • mixpanel-reporting 문서의 로컬 절대경로 링크를 상대경로로 교정

다음 커밋에서 위 코멘트 반영 여부를 확인하겠습니다.

@seongwon030
Copy link
Copy Markdown
Member Author

Member

이은호픈클로 제발 그만

- writeAssignments에 try-catch 추가 (용량 초과, 권한 거부 시 앱 초기화 중단 방지)
- safeReadAssignments에 JSON 구조 검증 추가 (isObjectRecord 타입 가드, string 값 필터링)

Made-with: Cursor
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
frontend/src/experiments/ExperimentRepository.ts (1)

95-97: ⚠️ Potential issue | 🟠 Major

resetAssignments도 Storage 예외를 보호해야 합니다.

Line 96의 localStorage.removeItem은 브라우저 정책/권한 상태에 따라 throw할 수 있어, 현재 구현은 리셋 호출 시 예외 전파 가능성이 남아 있습니다. writeAssignments와 동일하게 try/catch로 fail-open 처리해 주세요.

🛠️ 제안 수정
   resetAssignments() {
-    localStorage.removeItem(ASSIGNMENT_STORAGE_KEY);
+    try {
+      localStorage.removeItem(ASSIGNMENT_STORAGE_KEY);
+    } catch {
+      // localStorage 삭제 실패는 무시하고 진행한다.
+    }
   }

검증 방법(읽기 전용): 아래 스크립트로 현재 removeItem 호출이 보호되지 않았는지 확인할 수 있습니다.

#!/bin/bash
set -euo pipefail

echo "[1] resetAssignments 주변 코드 확인"
sed -n '90,102p' frontend/src/experiments/ExperimentRepository.ts

echo
echo "[2] ASSIGNMENT_STORAGE_KEY removeItem 호출 위치/컨텍스트 확인"
rg -n -C2 'localStorage\.removeItem\(ASSIGNMENT_STORAGE_KEY\)' frontend/src/experiments/ExperimentRepository.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/experiments/ExperimentRepository.ts` around lines 95 - 97, The
resetAssignments method currently calls
localStorage.removeItem(ASSIGNMENT_STORAGE_KEY) without protection and can
throw; update resetAssignments to mirror writeAssignments by wrapping the
removeItem call in a try/catch and failing open (swallow/log the error but do
not rethrow) so a storage exception won't propagate to callers; reference
resetAssignments and ASSIGNMENT_STORAGE_KEY when making the change and follow
the same error-handling pattern used in writeAssignments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/experiments.md`:
- Around line 10-19: The fenced code block that lists the experiment files (the
block beginning with "frontend/src/experiments/") is missing a language tag and
triggers MD040; add a language identifier (e.g., `text`) to the opening
triple-backticks of that code fence so it becomes ```text, leaving the block
content unchanged; this targets the code block that shows
"frontend/src/experiments/" and the subsequent "frontend/src/hooks/Experiment/"
listing.

In `@frontend/.claude/commands/create-e2e-test.md`:
- Line 10: The instruction "성공 될때까지 개선" in the create-e2e-test command is
unsafe—replace it with explicit retry and failure policies: enforce a maximum
retry count (e.g., maxRetries), a backoff strategy, and clear abort conditions
(e.g., stop on N consecutive failures or total runtime limit), and require
printing a failure report with test names, error traces, and flakiness hints
when retries are exhausted; update the command text to mention these fields
(maxRetries, backoffSeconds, abortOnConsecutiveFailures, failureReport) so CI
won't loop indefinitely or hide root causes.

---

Duplicate comments:
In `@frontend/src/experiments/ExperimentRepository.ts`:
- Around line 95-97: The resetAssignments method currently calls
localStorage.removeItem(ASSIGNMENT_STORAGE_KEY) without protection and can
throw; update resetAssignments to mirror writeAssignments by wrapping the
removeItem call in a try/catch and failing open (swallow/log the error but do
not rethrow) so a storage exception won't propagate to callers; reference
resetAssignments and ASSIGNMENT_STORAGE_KEY when making the change and follow
the same error-handling pattern used in writeAssignments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e065d8c0-a2b3-48ee-adca-a6cc7011e3c3

📥 Commits

Reviewing files that changed from the base of the PR and between 002a5fc and 864a64c.

📒 Files selected for processing (10)
  • .gitignore
  • docs/experiments.md
  • docs/mixpanel-reporting.md
  • frontend/.claude/agents/API훅부서.md
  • frontend/.claude/commands/create-e2e-test.md
  • frontend/.claude/commands/find-e2e-test.md
  • frontend/.claude/commands/tm/auto-implement-tasks.md
  • frontend/CLAUDE.md
  • frontend/src/experiments/ExperimentRepository.ts
  • frontend/src/hooks/Experiment/useExperimentVariant.ts
💤 Files with no reviewable changes (1)
  • .gitignore
✅ Files skipped from review due to trivial changes (6)
  • frontend/.claude/commands/tm/auto-implement-tasks.md
  • frontend/.claude/commands/find-e2e-test.md
  • frontend/src/hooks/Experiment/useExperimentVariant.ts
  • frontend/CLAUDE.md
  • docs/mixpanel-reporting.md
  • frontend/.claude/agents/API훅부서.md

@seongwon030 seongwon030 merged commit 68fbac9 into develop-fe Mar 28, 2026
5 checks passed
@seongwon030 seongwon030 linked an issue Mar 28, 2026 that may be closed by this pull request
2 tasks
@coderabbitai coderabbitai bot mentioned this pull request Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AB TEST AB 테스트 관련 💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] MOA-763 믹스패널 실험 관리 훅 제작

2 participants