-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT/#386] 믹스패널 붙였어요 #407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 35 commits
e8bff17
9bd5d74
0a22c86
c9adc9a
c2cf3c8
04d051f
320c149
3d05a8a
efcb99f
f4d6ec8
5781b8a
1364f73
f3a852f
75e710f
cade0fc
7c8919f
9839722
eb0e9c7
8077f5a
bcfa1c8
122a69c
f6dc277
4ad657e
03b7a03
67682d7
80d50b2
c6de6e6
9c22fa8
ae0ad49
1ba8be0
0056ac3
a54d41b
71131fa
a27aefd
fbe4eca
3842fbc
ec840e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,54 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.spoony.spoony.core.analytics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.content.Context | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.mixpanel.android.mpmetrics.MixpanelAPI | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.spoony.spoony.BuildConfig.MIXPANEL_KEY | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import dagger.hilt.android.qualifiers.ApplicationContext | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.inject.Inject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.json.JSONObject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import timber.log.Timber | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class MixPanelTracker @Inject constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @ApplicationContext private val context: Context | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val mixpanel = MixpanelAPI.getInstance( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MIXPANEL_KEY, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun setUserProfile(userId: String, properties: Map<String, Any>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mixpanel.identify(userId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| properties.forEach { (key, value) -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mixpanel.people.set(key, value) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun resetUserProfile() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mixpanel.reset() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun track(eventName: String) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Timber.tag("mixpanel").d(eventName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mixpanel.track(eventName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun track(eventName: String, properties: String) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Timber.tag("mixpanel").d("$eventName $properties") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mixpanel.track(eventName, properties.toJsonObject()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun track(eventName: String, properties: JSONObject) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Timber.tag("mixpanel").d("$eventName $properties") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mixpanel.track(eventName, properties) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
31
to
39
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요건 구현 방식을 바꿔서 2개가 생긴건데요 혹시 나중에 쓸일이 있을까 싶어서 놔두긴 했는데 뭐.. 필요한 사람이 다시 만드는걸로 하죠ㅋㅋ
Comment on lines
+31
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Release 로그에 PII 노출 위험: Timber에 이벤트/프로퍼티 출력
아래처럼 디버그 빌드에서만 로깅하도록 가드하는 것을 권장합니다: import org.json.JSONObject
import timber.log.Timber
+import com.spoony.spoony.BuildConfig
...
fun track(eventName: String) {
- Timber.tag("mixpanel").d(eventName)
+ if (BuildConfig.DEBUG) {
+ Timber.tag("mixpanel").d(eventName)
+ }
mixpanel.track(eventName)
}
fun track(eventName: String, properties: JSONObject) {
- Timber.tag("mixpanel").d("$eventName $properties")
+ if (BuildConfig.DEBUG) {
+ Timber.tag("mixpanel").d("$eventName $properties")
+ }
mixpanel.track(eventName, properties)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private fun String.toJsonObject(): JSONObject { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JSONObject(this) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e: Exception) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Timber.e(e) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JSONObject() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package com.spoony.spoony.core.analytics.events | ||
|
|
||
| import com.spoony.spoony.core.analytics.MixPanelTracker | ||
| import jakarta.inject.Inject | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이벤트 관련 파일들만 javax -> jakarta로 수정할게요~~ |
||
| import org.json.JSONObject | ||
|
|
||
| class AnalyticsEvents @Inject constructor( | ||
| private val tracker: MixPanelTracker | ||
| ) { | ||
| fun appOpen() { | ||
| tracker.track("app_open") | ||
| } | ||
|
|
||
| fun signupCompleted(signupMethod: String) { | ||
| tracker.track( | ||
| eventName = "signup_completed", | ||
| properties = JSONObject().apply { | ||
| put("signup_method", signupMethod) | ||
| } | ||
| ) | ||
Hyobeen-Park marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| fun loginSuccess() { | ||
| tracker.track("login_success") | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| package com.spoony.spoony.core.analytics.events | ||
|
|
||
| import com.spoony.spoony.core.analytics.MixPanelTracker | ||
| import jakarta.inject.Inject | ||
| import org.json.JSONArray | ||
| import org.json.JSONObject | ||
|
|
||
| class CommonEvents @Inject constructor( | ||
| private val tracker: MixPanelTracker | ||
| ) { | ||
| fun tabEntered(tabName: String) { | ||
| tracker.track( | ||
| eventName = "tab_entered", | ||
| properties = JSONObject().apply { | ||
| put("tab_name", tabName) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun reviewViewed( | ||
| reviewId: Int, | ||
| authorUserId: Int, | ||
| placeName: String, | ||
| category: String, | ||
| menuCount: Int, | ||
| satisfactionScore: Double, | ||
| reviewLength: Int, | ||
| photoCount: Int, | ||
| hasDisappointment: Boolean, | ||
| savedCount: Int, | ||
| isSelfReview: Boolean, | ||
| isFollowedUserReview: Boolean, | ||
| isSavedReview: Boolean | ||
|
Comment on lines
21
to
25
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런식으로 파라미터가 10개가 넘어가는 경우가 꽤 있어요. 어떤 값이 어떤 파라미터에 해당하는지 한번에 파악하기 어렵다고 생각이 들어요.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 흠냐냐 사실 이벤트를 위한 모델을 만드는 것에 대해 조금 많이 회의적이긴 한데요ㅎㅎ 겹치는 파라미터가 너무 많기도 하고 해서 일단 가장 많이 겹치는 파라미터만 data class로 만들었습니다!! |
||
| // entryPoint: String | ||
| ) { | ||
| tracker.track( | ||
| eventName = "review_viewed", | ||
| properties = JSONObject().apply { | ||
| put("review_id", reviewId) | ||
| put("author_user_id", authorUserId) | ||
| put("place_name", placeName) | ||
| put("category", category) | ||
| put("menu_count", menuCount) | ||
| put("satisfaction_score", satisfactionScore) | ||
| put("review_length", reviewLength) | ||
| put("photo_count", photoCount) | ||
| put("has_disappointment", hasDisappointment) | ||
| put("saved_count", savedCount) | ||
| put("is_self_review", isSelfReview) | ||
| put("is_followed_user_review", isFollowedUserReview) | ||
| put("is_saved_review", isSavedReview) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun reviewEdited( | ||
| reviewId: Int, | ||
| authorUserId: Int, | ||
| placeName: String, | ||
| category: String, | ||
| menuCount: Int, | ||
| satisfactionScore: Float, | ||
| reviewLength: Int, | ||
| photoCount: Int, | ||
| hasDisappointment: Boolean, | ||
| savedCount: Int | ||
| // entryPoint: String | ||
| ) { | ||
| tracker.track( | ||
| eventName = "review_edited", | ||
| properties = JSONObject().apply { | ||
| put("review_id", reviewId) | ||
| put("author_user_id", authorUserId) | ||
| put("place_name", placeName) | ||
| put("category", category) | ||
| put("menu_count", menuCount) | ||
| put("satisfaction_score", satisfactionScore) | ||
| put("review_length", reviewLength) | ||
| put("photo_count", photoCount) | ||
| put("has_disappointment", hasDisappointment) | ||
| put("saved_count", savedCount) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun profileViewed( | ||
| profileUserId: Int, | ||
| isSelfProfile: Boolean, | ||
| isFollowingProfileUser: Boolean | ||
| // entryPoint: String | ||
| ) { | ||
| tracker.track( | ||
| eventName = "profile_viewed", | ||
| properties = JSONObject().apply { | ||
| put("profile_user_id", profileUserId) | ||
| put("is_self_profile", isSelfProfile) | ||
| put("is_following_profile_user", isFollowingProfileUser) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun followUser( | ||
| followedUserId: Int, | ||
| entryPoint: String | ||
| ) { | ||
| tracker.track( | ||
| eventName = "follow_user", | ||
| properties = JSONObject().apply { | ||
| put("followed_user_id", followedUserId) | ||
| put("entry_point", entryPoint) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun unfollowUser( | ||
| unfollowedUserId: Int, | ||
| entryPoint: String | ||
| ) { | ||
| tracker.track( | ||
| eventName = "unfollow_user", | ||
| properties = JSONObject().apply { | ||
| put("unfollowed_user_id", unfollowedUserId) | ||
| put("entry_point", entryPoint) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun followUserFromReview( | ||
| reviewId: Int, | ||
| authorUserId: Int, | ||
| placeName: String, | ||
| category: String, | ||
| menuCount: Int, | ||
| satisfactionScore: Double, | ||
|
||
| reviewLength: Int, | ||
| photoCount: Int, | ||
| hasDisappointment: Boolean, | ||
| savedCount: Int | ||
| ) { | ||
| tracker.track( | ||
| eventName = "follow_user_from_review", | ||
| properties = JSONObject().apply { | ||
| put("review_id", reviewId) | ||
| put("author_user_id", authorUserId) | ||
| put("place_name", placeName) | ||
| put("category", category) | ||
| put("menu_count", menuCount) | ||
| put("satisfaction_score", satisfactionScore) | ||
| put("review_length", reviewLength) | ||
| put("photo_count", photoCount) | ||
| put("has_disappointment", hasDisappointment) | ||
| put("saved_count", savedCount) | ||
| put("entry_point", "review") | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun unfollowUserFromReview( | ||
| reviewId: Int, | ||
| authorUserId: Int, | ||
| placeName: String, | ||
| category: String, | ||
| menuCount: Int, | ||
| satisfactionScore: Double, | ||
| reviewLength: Int, | ||
| photoCount: Int, | ||
| hasDisappointment: Boolean, | ||
| savedCount: Int | ||
| ) { | ||
| tracker.track( | ||
| eventName = "unfollow_user_from_review", | ||
| properties = JSONObject().apply { | ||
| put("review_id", reviewId) | ||
| put("author_user_id", authorUserId) | ||
| put("place_name", placeName) | ||
| put("category", category) | ||
| put("menu_count", menuCount) | ||
| put("satisfaction_score", satisfactionScore) | ||
| put("review_length", reviewLength) | ||
| put("photo_count", photoCount) | ||
| put("has_disappointment", hasDisappointment) | ||
| put("saved_count", savedCount) | ||
| put("entry_point", "review") | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun filterApplied( | ||
| pageApplied: String, | ||
| localReviewFilter: Boolean? = null, | ||
| regionFilters: List<String> = listOf(), | ||
| categoryFilters: List<String> = listOf(), | ||
| ageGroupFilters: List<String> = listOf() | ||
| ) { | ||
| tracker.track( | ||
| eventName = "filter_applied", | ||
| properties = JSONObject().apply { | ||
| put("page_applied", pageApplied) | ||
| put("local_review_filter", localReviewFilter) | ||
| put("region_filters", JSONArray(regionFilters)) | ||
| put("category_filters", JSONArray(categoryFilters)) | ||
| put("age_group_filters", JSONArray(ageGroupFilters)) | ||
| } | ||
| ) | ||
| } | ||
Hyobeen-Park marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.spoony.spoony.core.analytics.events | ||
|
|
||
| import com.spoony.spoony.core.analytics.MixPanelTracker | ||
| import jakarta.inject.Inject | ||
| import org.json.JSONObject | ||
|
|
||
| class ExploreEvents @Inject constructor( | ||
| private val tracker: MixPanelTracker | ||
| ) { | ||
| fun sortSelected(sortType: String) { | ||
| tracker.track( | ||
| eventName = "sort_selected", | ||
| properties = JSONObject().apply { | ||
| put("sort_type", sortType) | ||
| } | ||
| ) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| fun exploreSearched( | ||
| searchTargetType: String, | ||
| searchTerm: String | ||
| ) { | ||
| tracker.track( | ||
| eventName = "explore_searched", | ||
| properties = JSONObject().apply { | ||
| put("search_target_type", searchTargetType) | ||
| put("search_term", searchTerm) | ||
| } | ||
| ) | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.spoony.spoony.core.analytics.events | ||
|
|
||
| import com.spoony.spoony.core.analytics.MixPanelTracker | ||
| import jakarta.inject.Inject | ||
| import org.json.JSONObject | ||
|
|
||
| class MapEvents @Inject constructor( | ||
| private val tracker: MixPanelTracker | ||
| ) { | ||
| fun mapSearched( | ||
| locationType: String, | ||
| searchTerm: String | ||
| ) { | ||
| tracker.track( | ||
| eventName = "map_searched", | ||
| properties = JSONObject().apply { | ||
| put("location_type", locationType) | ||
| put("search_term", searchTerm) | ||
| } | ||
| ) | ||
Hyobeen-Park marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
빈 키 fallback이 BuildConfig 생성을 깨뜨립니다.
buildConfigField의 세 번째 인자는 리터럴이어야 하는데, fallback으로 전달한""는 따옴표가 없어public static final String MIXPANEL_KEY = ;형태로 생성돼 바로 컴파일 에러가 납니다. 최소한""대신"\"\""을 넘기거나, 값이 없을 때는 명시적으로 에러를 던지도록 처리해 주세요.buildConfigField( "String", "MIXPANEL_KEY", - properties["mixpanelDevKey"] as? String ?: "" + (properties["mixpanelDevKey"] as? String) ?: "\"\"" ) @@ buildConfigField( "String", "MIXPANEL_KEY", - properties["mixpanelProdKey"] as? String ?: "" + (properties["mixpanelProdKey"] as? String) ?: "\"\"" )Also applies to: 72-75
🤖 Prompt for AI Agents