Skip to content

feat(retention): 코호트 기반 리텐션 대시보드 구현 - #71

Merged
Geunone2 merged 2 commits into
developfrom
#68-feat-retention
Jul 1, 2026
Merged

feat(retention): 코호트 기반 리텐션 대시보드 구현#71
Geunone2 merged 2 commits into
developfrom
#68-feat-retention

Conversation

@Geunone2

@Geunone2 Geunone2 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

🛠️ 설명 (Description)

PostHog 이벤트 데이터를 기반으로 코호트 기반 리텐션 분석 대시보드를 구현합니다.
같은 주에 처음 방문한 사용자 그룹(코호트)이 이후 주에도 재방문하는 비율을 최근 12주 기준으로 추적합니다.

📄 설계 문서 (Design Document)

📝 변경 사항 요약 (Summary)

  • PostHog HogQL로 84일치 (person_id, week) 쌍을 조회하고, JS 순수 함수로 코호트 행렬·잔존율을 계산하는 데이터 파이프라인 구현
  • KPI 4개 (평균 W1 리텐션율 / 최고 리텐션 코호트 / 전체 추적 사용자 / 이번 주 신규 코호트) 집계 및 표시
  • 코호트 히트맵: 행 헤더에 날짜 범위(4/20 ~ 4/26) + 신규 사용자 수 표시, 잔존율에 따른 컬러 스케일 적용
  • 주차별 신규 사용자(바) & W1 잔존율(라인) 콤보 차트, 재방문 비율 반원 파이 차트 추가
  • 스켈레톤 로딩, 에러 토스트, TanStack Query abort signal 지원

💁 변경 사항 이유 (Why)

  • JS 순수 함수로 코호트 행렬 계산 → 쿼리 단순 유지 & 로직 테스트 가능성 확보
  • 날짜 범위 + 신규 사용자 수 → 각 코호트가 어느 기간인지 직관적 파악 가능
  • 콤보 차트 & 히트맵 분리 → 각각 독립적으로 읽을 수 있는 정보 단위 유지

✅ 테스트 계획 (Test Plan)

  • buildRetentionResponse 단위 테스트 작성 완료 (빈 데이터, W1 재방문, 미래 주차 null 처리, 중복 제거 등 8개 케이스)
  • 실 PostHog 데이터로 렌더링 동작 확인

🔗 관련 이슈 (Related Issues)

☑️ 체크리스트 (Checklist)

  • 코드가 프로젝트 코딩 컨벤션을 따릅니다.
  • 테스트 코드가 작성되었고, 통과했습니다.
  • 필요한 경우, 다른 팀원에게 리뷰를 요청했습니다.

👀 리뷰어를 위한 참고 사항 (Notes for Reviewers)

➕ 추가 정보 (Additional Information)

Summary by CodeRabbit

  • New Features

    • 리텐션 대시보드가 새로 추가되어, 코호트별 잔존율과 신규 사용자 추이를 한눈에 볼 수 있습니다.
    • 평균 1주차 리텐션, 최고 리텐션 코호트, 전체 추적 사용자, 이번 주 신규 코호트 KPI가 표시됩니다.
    • 신규 사용자/1주차 리텐션 차트, 전체 리텐션 반원 차트, 코호트 히트맵이 함께 제공됩니다.
  • Bug Fixes

    • 데이터가 없거나 불러오기 실패 시 더 명확한 빈 상태와 오류 안내가 표시됩니다.

-PostHog HogQL로 12주(person_id, week) 데이터를 조회,
- JS 순수 함수로 코호트 행렬과 잔존율 계산
- KPI 4개, 콤보 차트, 반원 파이, GA(Google Analytics) 스타일 히트맵으로 시각화

Resolves: #68
See also: None
@Geunone2
Geunone2 requested a review from Lilium0422 June 30, 2026 10:16
@Geunone2 Geunone2 self-assigned this Jun 30, 2026
@Geunone2 Geunone2 added the feat 새로운 기능 요청 또는 구현 label Jun 30, 2026
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 33f22015-5064-48fd-b07d-5605b9671fda

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

/dashboard/events/retention 페이지의 placeholder 상태를 제거하고 코호트 기반 리텐션 분석 대시보드를 신규 구현한다. retention.ts에서 타입·집계 로직(buildRetentionResponse)·날짜 유틸을 정의하고, getRetentionServer()가 KST 기준으로 PostHog에서 84일 범위의 원시 데이터를 수집해 집계한다. /api/posthog/retention GET 라우트와 클라이언트 훅 useRetention()이 데이터 통신을 담당하며, RetentionDashboard 뷰가 KPI 카드·콤보차트·반원 파이차트·코호트 히트맵 위젯을 조합해 렌더링한다. 페이지는 서버 컴포넌트에서 React Query prefetchQueryHydrationBoundary로 초기 데이터를 하이드레이션한다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 코호트 기반 리텐션 대시보드 구현이라는 핵심 변경을 간결하게 잘 요약합니다.
Linked Issues check ✅ Passed 12주 코호트, KPI 4개, 복합 차트, 하프 파이, 히트맵과 테스트까지 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed 플레이스홀더 대시보드를 리텐션 분석 기능으로 대체하는 범위 안의 변경만 보이며, 별도 무관한 수정은 보이지 않습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch #68-feat-retention

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/entities/event/model/retention.test.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

export된 날짜 포맷터에도 테스트를 추가해 주세요.

formatWeekRange, formatShortDate, formatWeekOfMonth는 export된 유틸 함수인데 검증이 빠져 있습니다. 특히 formatWeekRange의 월 경계(예: "2026-06-29""6/29 ~ 7/5")와 formatWeekOfMonth의 주차 계산은 회귀가 생기기 쉬운 지점이라 케이스로 고정해 두면 좋습니다.

As per coding guidelines: "Test business logic in utility functions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/entities/event/model/retention.test.ts` at line 1, Add coverage in
retention.test for the exported date-formatting utilities in retention,
especially formatWeekRange, formatShortDate, and formatWeekOfMonth. Extend the
existing test file to import these symbols and add focused cases for week-range
formatting across a month boundary such as 2026-06-29 to 6/29 ~ 7/5, plus
week-of-month calculations that verify the correct ordinal week is returned.
Keep the tests aligned with the current behavior of buildRetentionResponse and
addWeeks while locking the utility outputs to prevent regressions.

Source: Coding guidelines

src/entities/event/model/retention.ts (1)

73-106: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

윈도우 경계 코호트의 "신규" 과대계상에 유의하세요.

84일 범위만 조회하므로, 윈도우 시작 이전부터 활동하던 사용자도 "윈도우 내 첫 방문 주"를 코호트 진입 주차로 잡습니다(firstWeek = [...weeks].sort()[0]). 그 결과 가장 오래된 쪽 코호트는 실제로는 재방문자인데 신규로 분류되어 코호트 크기가 부풀고 잔존율 해석이 왜곡될 수 있습니다.

데이터 파이프라인 한계상 즉시 고칠 필요는 없지만, 대시보드에 "최근 N주 코호트만 신뢰" 같은 가이드 문구를 추가하거나 경계 코호트를 별도 표기하는 방안을 고려해 보세요. 코호트 분석의 left-censoring 개념을 함께 살펴보면 도움이 됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/entities/event/model/retention.ts` around lines 73 - 106, In
retention.ts, the cohort-building logic around sortedCohortWeeks and the
firstWeek calculation is left-censored because users active before the 84-day
window are being counted as new in their first week inside the window. Update
the retention/cohort output or surrounding dashboard behavior to clearly flag or
exclude the oldest boundary cohort, and add guidance that only recent N weeks
should be treated as reliable. Keep the fix localized to the retention cohort
generation flow so the misleading “new” classification does not affect
interpretation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/dashboard/events/retention/page.tsx`:
- Around line 9-17: `useRetention()` in `getRetention.ts` is missing a
`staleTime`, so the data prefetched in `RetentionDashboard` via `prefetchQuery`
becomes stale immediately after hydration and triggers an extra refetch. Update
the query options in `useRetention()` to set an appropriate `staleTime` so the
server-fetched retention data is reused on mount, and keep the existing
`queryKey`/`getRetentionServer` flow intact.

In `@src/widgets/posthog/RetentionHalfPieChart.tsx`:
- Around line 26-46: The retention pie chart in RetentionHalfPieChart computes
notRetainedPct as 100 when totalTrackedUsers is 0, which makes empty data render
as “미방문 100%”. Update the retainedUsersCount/totalTrackedUsers percentage logic
so the zero-user case returns 0 for both retainedPct and notRetainedPct, or
bypasses chartData generation entirely for that branch. Keep the fix localized
around the retainedPct, notRetainedPct, and chartData construction in
RetentionHalfPieChart.

In `@src/widgets/posthog/RetentionHeatmap.tsx`:
- Around line 99-119: The absolute user count in RetentionHeatmap is only
exposed via the hover-only tooltip, so keyboard and screen reader users can’t
access it. Update the cell rendering in RetentionHeatmap to include count in the
aria-label alongside the existing rate text, and make the tooltip open on focus
as well as hover by adding focus-visible/focus-based behavior to the same
cell/tooltip wrapper so both interactions reveal the count.

---

Nitpick comments:
In `@src/entities/event/model/retention.test.ts`:
- Line 1: Add coverage in retention.test for the exported date-formatting
utilities in retention, especially formatWeekRange, formatShortDate, and
formatWeekOfMonth. Extend the existing test file to import these symbols and add
focused cases for week-range formatting across a month boundary such as
2026-06-29 to 6/29 ~ 7/5, plus week-of-month calculations that verify the
correct ordinal week is returned. Keep the tests aligned with the current
behavior of buildRetentionResponse and addWeeks while locking the utility
outputs to prevent regressions.

In `@src/entities/event/model/retention.ts`:
- Around line 73-106: In retention.ts, the cohort-building logic around
sortedCohortWeeks and the firstWeek calculation is left-censored because users
active before the 84-day window are being counted as new in their first week
inside the window. Update the retention/cohort output or surrounding dashboard
behavior to clearly flag or exclude the oldest boundary cohort, and add guidance
that only recent N weeks should be treated as reliable. Keep the fix localized
to the retention cohort generation flow so the misleading “new” classification
does not affect interpretation.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 817bfcb1-8db7-455b-9591-b9f5a938e36d

📥 Commits

Reviewing files that changed from the base of the PR and between 23b6e5d and 4fc9be7.

📒 Files selected for processing (11)
  • app/api/posthog/retention/route.ts
  • app/dashboard/events/retention/page.tsx
  • src/entities/event/api/getRetention.ts
  • src/entities/event/api/getRetentionServer.ts
  • src/entities/event/model/retention.test.ts
  • src/entities/event/model/retention.ts
  • src/views/posthog/RetentionDashboard.tsx
  • src/widgets/posthog/RetentionComboChart.tsx
  • src/widgets/posthog/RetentionDashboardSkeleton.tsx
  • src/widgets/posthog/RetentionHalfPieChart.tsx
  • src/widgets/posthog/RetentionHeatmap.tsx

Comment thread app/dashboard/events/retention/page.tsx
Comment thread src/widgets/posthog/RetentionHalfPieChart.tsx
Comment thread src/widgets/posthog/RetentionHeatmap.tsx
Comment thread src/entities/event/api/getRetentionServer.ts
- useRetention에 staleTime 추가해 SSR 프리패치 데이터 즉시 재조회 방지
- RetentionHalfPieChart: totalTrakedUsers=0 시, notRetainedPct 오표시 수정
- RetentionHeatmap: aria-label에 count 포함 및 포커스 툴팁 접근성 개선
- getRetentionServer: KST 월요일 계산을 dayjs로 교체

Resolves: #68
See also: None
@Geunone2
Geunone2 merged commit 62a0865 into develop Jul 1, 2026
1 check passed
@Geunone2
Geunone2 deleted the #68-feat-retention branch July 1, 2026 10:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능 요청 또는 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 리텐션(Retention) 분석 대시보드 구현

2 participants