Skip to content

chore : GA4 이벤트 추가#47

Merged
kangdy25 merged 7 commits intomainfrom
feature/GA-Evaluation-Metric
Mar 2, 2026
Merged

chore : GA4 이벤트 추가#47
kangdy25 merged 7 commits intomainfrom
feature/GA-Evaluation-Metric

Conversation

@kim3360
Copy link
Member

@kim3360 kim3360 commented Mar 1, 2026

🚀 GA4 이벤트 추가

📝 변경사항

  • 중간지점 산출 이벤트
  • 중간지점 비교 이벤트
  • 장소리스트 중 하나의 장소 탐색 이벤트
  • 카카오맵 외부 검증 시도 이벤트

✅ 체크리스트

  • 코드 리뷰를 받았습니다
  • 테스트를 완료했습니다
  • 린터 에러가 없습니다
  • 타입 에러가 없습니다
  • 브라우저에서 테스트를 완료했습니다
  • 모바일에서 테스트를 완료했습니다 (해당되는 경우)

📸 스크린샷

UI 변경 사항이 있다면 이미지를 드래그해서 넣어주세요!

💬 리뷰어 전달사항

  • 리뷰어가 특별히 확인해야 할 사항이 있다면 적어주세요.

Summary by CodeRabbit

릴리스 노트

  • Chores
    • 사용자 행동 분석 추적 기능 강화: 중점 계산, 지도 및 장소 상호작용, 결과 조회 등의 활동 추적 기능 추가

@vercel
Copy link

vercel bot commented Mar 1, 2026

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

Project Deployment Actions Updated (UTC)
mingling-frontend Ready Ready Preview, Comment Mar 1, 2026 5:35pm

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Walkthrough

세 개의 페이지에 걸쳐 분석 결과 추적을 위한 Google Analytics 이벤트 통합이 추가되었습니다. 중점 계산, 장소 상호작용, 후보 조회를 추적하는 새로운 GA 이벤트와 함께 localStorage 및 sessionStorage 플래그를 사용하여 사용자 역할과 탐색 상태를 관리합니다.

Changes

Cohort / File(s) Summary
Meeting Page Telemetry
app/meeting/[id]/page.tsx
Added midpoint_calculated GA event before navigation to results page, capturing meeting_url_id, browser_id, role, and calculation_type (first vs recalculated calculation). Tracks calculation in two submission paths.
Recommendation Page Analytics
app/recommend/page.tsx
Added GA tracking for KakaoMap interactions (external_map_opened event) and place list views (place_list_viewed event). Extended handleOpenKakaoMap to accept optional place object and wired handlePlaceClick for list item interactions with GA logging.
Result Page Candidate Tracking
app/result/[id]/page.tsx
Introduced trackMidpointCandidateViewed function to send GA events for candidate views with rank and ID. Added useEffect to track initial return from recommendation flow via sessionStorage flag. Modified result item clicks to emit tracking events. Added sessionStorage flag (from_recommend_{id}) in handleRecommendClick before navigation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 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
Title check ✅ Passed PR 제목이 변경사항의 주요 내용을 정확히 반영하고 있습니다. GA4 이벤트 추가라는 핵심 변경사항을 명확하고 간결하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 제목 섹션, 변경사항 섹션, 체크리스트를 포함하고 있으며 모든 필수 섹션이 완성되어 있습니다. 변경사항도 구체적으로 4개의 GA4 이벤트를 나열하고 있습니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/GA-Evaluation-Metric

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@kim3360 kim3360 changed the title Chore : GA4 이벤트 추가 chore : GA4 이벤트 추가 Mar 1, 2026
Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/meeting/`[id]/page.tsx:
- Around line 173-185: The calculation_type logic is wrong because id is always
present on /meeting/[id], causing 'first' never to be sent and browser_id can be
null; update the code around sendGAEvent so calculationType is determined by a
persistent flag (e.g., check localStorage.getItem(`midpoint_calculated_${id}`)
or similar) rather than presence of id, set calculationType to 'first' if that
flag is absent and 'recalculated' if present, then after sending mark that flag
in localStorage (localStorage.setItem(`midpoint_calculated_${id}`, 'true'));
also ensure browser_id is not sent as null by reading
localStorage.getItem('browser_id') and if missing either generate/store a new id
or omit the browser_id field when calling sendGAEvent so it never transmits
null.

In `@app/result/`[id]/page.tsx:
- Around line 171-181: The useEffect currently treats
sessionStorage.getItem(`from_recommend_${id}`) as a boolean flag ('1') which can
cause the wrong candidate to be picked; update the effect to read and parse the
stored value as a selectedResultId (supporting formats set by the redirect
logic: e.g. a plain numeric id or a prefix like "1:<id>"), then remove the key
and pick the candidate by that parsed id (falling back to the existing
selectedResultId or locationResults[0] if parsing fails) before computing
candidateId and calling trackMidpointCandidateViewed; reference useEffect,
sessionStorage.getItem(`from_recommend_${id}`), selectedResultId,
locationResults, and trackMidpointCandidateViewed to locate where to change.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e96a305 and d7ce582.

📒 Files selected for processing (3)
  • app/meeting/[id]/page.tsx
  • app/recommend/page.tsx
  • app/result/[id]/page.tsx

Comment on lines +173 to +185
if (typeof window !== 'undefined') {
const calculationType = id ? 'recalculated' : 'first';
const isHost = localStorage.getItem(`is_host_${id}`) === 'true';
const userRole = isHost ? 'host' : 'participant';
const browserId = localStorage.getItem('browser_id');

sendGAEvent('event', 'midpoint_calculated', {
meeting_url_id: id,
browser_id: browserId,
role: userRole,
calculation_type: calculationType,
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

calculation_type가 사실상 항상 recalculated로 전송됩니다.

Line 174는 id 존재 여부로 분기하고 있는데, /meeting/[id] 경로에서는 id가 항상 존재해 first가 전송되지 않습니다. 그리고 Line 177은 browser_id가 없을 때 null이 전송될 수 있습니다.

수정 제안
-    if (typeof window !== 'undefined') {
-      const calculationType = id ? 'recalculated' : 'first';
+    if (typeof window !== 'undefined' && id) {
+      const calcStateKey = `has_midpoint_calculated_${id}`;
+      const calculationType = sessionStorage.getItem(calcStateKey) ? 'recalculated' : 'first';
       const isHost = localStorage.getItem(`is_host_${id}`) === 'true';
       const userRole = isHost ? 'host' : 'participant';
-      const browserId = localStorage.getItem('browser_id');
+      let browserId = localStorage.getItem('browser_id');
+      if (!browserId) {
+        browserId = `bid_${Math.random().toString(36).substring(2, 15)}${Date.now().toString(36)}`;
+        localStorage.setItem('browser_id', browserId);
+      }

       sendGAEvent('event', 'midpoint_calculated', {
         meeting_url_id: id,
         browser_id: browserId,
         role: userRole,
         calculation_type: calculationType,
       });
+      sessionStorage.setItem(calcStateKey, '1');
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/meeting/`[id]/page.tsx around lines 173 - 185, The calculation_type logic
is wrong because id is always present on /meeting/[id], causing 'first' never to
be sent and browser_id can be null; update the code around sendGAEvent so
calculationType is determined by a persistent flag (e.g., check
localStorage.getItem(`midpoint_calculated_${id}`) or similar) rather than
presence of id, set calculationType to 'first' if that flag is absent and
'recalculated' if present, then after sending mark that flag in localStorage
(localStorage.setItem(`midpoint_calculated_${id}`, 'true')); also ensure
browser_id is not sent as null by reading localStorage.getItem('browser_id') and
if missing either generate/store a new id or omit the browser_id field when
calling sendGAEvent so it never transmits null.

Comment on lines +171 to +181
// 장소 리스트에서 결과보기 페이지로 돌아왔을 때 midpoint_candidate_viewed 전송
useEffect(() => {
if (typeof window === 'undefined' || !id || locationResults.length === 0) return;
const fromRecommend = sessionStorage.getItem(`from_recommend_${id}`);
if (fromRecommend !== '1') return;

sessionStorage.removeItem(`from_recommend_${id}`);
const selected = locationResults.find((r) => r.id === selectedResultId) ?? locationResults[0];
const candidateId = `mid_${selected.endStation.replace(/\s+/g, '_')}`;
trackMidpointCandidateViewed(selected.id, candidateId);
}, [id, locationResults, selectedResultId, trackMidpointCandidateViewed]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

추천 페이지 복귀 시 잘못된 후보가 집계될 수 있습니다.

Line 315에서 '1'만 저장하면, Line 178에서 selectedResultId 기본값(1)을 기준으로 후보를 선택하게 되어 실제로 추천 페이지로 이동했던 후보와 다른 candidate_id가 전송될 수 있습니다.

수정 제안
-    const fromRecommend = sessionStorage.getItem(`from_recommend_${id}`);
-    if (fromRecommend !== '1') return;
+    const fromRecommendRank = sessionStorage.getItem(`from_recommend_${id}`);
+    if (!fromRecommendRank) return;

-    sessionStorage.removeItem(`from_recommend_${id}`);
-    const selected = locationResults.find((r) => r.id === selectedResultId) ?? locationResults[0];
+    const parsedRank = Number(fromRecommendRank);
+    sessionStorage.removeItem(`from_recommend_${id}`);
+    const selected =
+      locationResults.find((r) => r.id === parsedRank) ??
+      locationResults.find((r) => r.id === selectedResultId) ??
+      locationResults[0];
     const candidateId = `mid_${selected.endStation.replace(/\s+/g, '_')}`;
     trackMidpointCandidateViewed(selected.id, candidateId);
-                          if (typeof window !== 'undefined') {
-                            sessionStorage.setItem(`from_recommend_${id}`, '1');
-                          }
+                          if (typeof window !== 'undefined') {
+                            sessionStorage.setItem(`from_recommend_${id}`, String(result.id));
+                          }

Also applies to: 314-316

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

In `@app/result/`[id]/page.tsx around lines 171 - 181, The useEffect currently
treats sessionStorage.getItem(`from_recommend_${id}`) as a boolean flag ('1')
which can cause the wrong candidate to be picked; update the effect to read and
parse the stored value as a selectedResultId (supporting formats set by the
redirect logic: e.g. a plain numeric id or a prefix like "1:<id>"), then remove
the key and pick the candidate by that parsed id (falling back to the existing
selectedResultId or locationResults[0] if parsing fails) before computing
candidateId and calling trackMidpointCandidateViewed; reference useEffect,
sessionStorage.getItem(`from_recommend_${id}`), selectedResultId,
locationResults, and trackMidpointCandidateViewed to locate where to change.

@kangdy25 kangdy25 merged commit 211b186 into main Mar 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants