-
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 all 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,40 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 jakarta.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: JSONObject) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Timber.tag("mixpanel").d("$eventName $properties") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mixpanel.track(eventName, properties) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun loginSuccess() { | ||
| tracker.track("login_success") | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| package com.spoony.spoony.core.analytics.events | ||
|
|
||
| import com.spoony.spoony.core.analytics.MixPanelTracker | ||
| import com.spoony.spoony.core.analytics.model.ReviewTrackingModel | ||
| 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( | ||
| reviewTrackingModel: ReviewTrackingModel, | ||
| isSelfReview: Boolean, | ||
| isFollowedUserReview: Boolean, | ||
| isSavedReview: Boolean | ||
| ) { | ||
| tracker.track( | ||
| eventName = "review_viewed", | ||
| properties = JSONObject().apply { | ||
| put("review_id", reviewTrackingModel.reviewId) | ||
| put("author_user_id", reviewTrackingModel.authorUserId) | ||
| put("place_name", reviewTrackingModel.placeName) | ||
| put("category", reviewTrackingModel.category) | ||
| put("menu_count", reviewTrackingModel.menuCount) | ||
| put("satisfaction_score", reviewTrackingModel.satisfactionScore) | ||
| put("review_length", reviewTrackingModel.reviewLength) | ||
| put("photo_count", reviewTrackingModel.photoCount) | ||
| put("has_disappointment", reviewTrackingModel.hasDisappointment) | ||
| put("saved_count", reviewTrackingModel.savedCount) | ||
| put("is_self_review", isSelfReview) | ||
| put("is_followed_user_review", isFollowedUserReview) | ||
| put("is_saved_review", isSavedReview) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun reviewEdited( | ||
| reviewTrackingModel: ReviewTrackingModel | ||
| ) { | ||
| tracker.track( | ||
| eventName = "review_edited", | ||
| properties = JSONObject().apply { | ||
| put("review_id", reviewTrackingModel.reviewId) | ||
| put("author_user_id", reviewTrackingModel.authorUserId) | ||
| put("place_name", reviewTrackingModel.placeName) | ||
| put("category", reviewTrackingModel.category) | ||
| put("menu_count", reviewTrackingModel.menuCount) | ||
| put("satisfaction_score", reviewTrackingModel.satisfactionScore) | ||
| put("review_length", reviewTrackingModel.reviewLength) | ||
| put("photo_count", reviewTrackingModel.photoCount) | ||
| put("has_disappointment", reviewTrackingModel.hasDisappointment) | ||
| put("saved_count", reviewTrackingModel.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( | ||
| reviewTrackingModel: ReviewTrackingModel | ||
| ) { | ||
| tracker.track( | ||
| eventName = "follow_user_from_review", | ||
| properties = JSONObject().apply { | ||
| put("review_id", reviewTrackingModel.reviewId) | ||
| put("author_user_id", reviewTrackingModel.authorUserId) | ||
| put("place_name", reviewTrackingModel.placeName) | ||
| put("category", reviewTrackingModel.category) | ||
| put("menu_count", reviewTrackingModel.menuCount) | ||
| put("satisfaction_score", reviewTrackingModel.satisfactionScore) | ||
| put("review_length", reviewTrackingModel.reviewLength) | ||
| put("photo_count", reviewTrackingModel.photoCount) | ||
| put("has_disappointment", reviewTrackingModel.hasDisappointment) | ||
| put("saved_count", reviewTrackingModel.savedCount) | ||
| put("entry_point", "review") | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun unfollowUserFromReview( | ||
| reviewTrackingModel: ReviewTrackingModel | ||
| ) { | ||
| tracker.track( | ||
| eventName = "unfollow_user_from_review", | ||
| properties = JSONObject().apply { | ||
| put("review_id", reviewTrackingModel.reviewId) | ||
| put("author_user_id", reviewTrackingModel.authorUserId) | ||
| put("place_name", reviewTrackingModel.placeName) | ||
| put("category", reviewTrackingModel.category) | ||
| put("menu_count", reviewTrackingModel.menuCount) | ||
| put("satisfaction_score", reviewTrackingModel.satisfactionScore) | ||
| put("review_length", reviewTrackingModel.reviewLength) | ||
| put("photo_count", reviewTrackingModel.photoCount) | ||
| put("has_disappointment", reviewTrackingModel.hasDisappointment) | ||
| put("saved_count", reviewTrackingModel.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)) | ||
| } | ||
| ) | ||
| } | ||
| } |
| 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) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| fun exploreSearched( | ||
| searchTargetType: String, | ||
| searchTerm: String | ||
| ) { | ||
| tracker.track( | ||
| eventName = "explore_searched", | ||
| properties = JSONObject().apply { | ||
| put("search_target_type", searchTargetType) | ||
| put("search_term", searchTerm) | ||
| } | ||
| ) | ||
| } | ||
| } |
| 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) | ||
| } | ||
| ) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.spoony.spoony.core.analytics.events | ||
|
|
||
| import androidx.compose.runtime.staticCompositionLocalOf | ||
| import jakarta.inject.Inject | ||
|
|
||
| val LocalTracker = staticCompositionLocalOf<MixPanelEvents> { | ||
| error("No MixPanelEvents provided") | ||
| } | ||
|
|
||
| class MixPanelEvents @Inject constructor( | ||
| val userProperties: MixPanelUserProperties, | ||
| val analyticsEvents: AnalyticsEvents, | ||
| val commonEvents: CommonEvents, | ||
| val onboardingEvents: OnboardingEvents, | ||
| val spoonDrawEvents: SpoonDrawEvents, | ||
| val mapEvents: MapEvents, | ||
| val exploreEvents: ExploreEvents, | ||
| val registerEvents: RegisterEvents, | ||
| val mypageEvents: MypageEvents, | ||
| val reviewDetailEvents: ReviewDetailEvents | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.spoony.spoony.core.analytics.events | ||
|
|
||
| import com.spoony.spoony.core.analytics.MixPanelTracker | ||
| import jakarta.inject.Inject | ||
|
|
||
| class MixPanelUserProperties @Inject constructor( | ||
| private val tracker: MixPanelTracker | ||
| ) { | ||
| fun setUserProfile(userId: String, properties: Map<String, Any>) { | ||
| tracker.setUserProfile(userId = userId, properties = properties) | ||
| } | ||
|
|
||
| fun resetUserProfile() { | ||
| tracker.resetUserProfile() | ||
| } | ||
| } |
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