Skip to content
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

Refactor/keyappkit/analyticsmanager #1519

Merged
merged 9 commits into from
Aug 2, 2023
1 change: 1 addition & 0 deletions .github/workflows/key-app-kit-unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
-parallel-testing-worker-count 3 \
-parallel-testing-enabled YES \
-resultBundlePath TestResults \
-only-testing:AnalyticsManagerUnitTests \
-only-testing:CountriesAPIUnitTests \
-only-testing:FeeRelayerSwiftUnitTests \
-only-testing:JSBridgeTests \
Expand Down
9 changes: 8 additions & 1 deletion Packages/KeyAppKit/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ let package = Package(
.package(url: "https://github.com/attaswift/BigInt.git", from: "5.3.0"),
.package(url: "https://github.com/p2p-org/BigDecimal.git", branch: "main"),
.package(url: "https://github.com/vapor/websocket-kit", from: "2.8.0"),
.package(url: "https://github.com/amplitude/Amplitude-iOS.git", from: "8.15.0"),
.package(url: "https://github.com/AppsFlyerSDK/AppsFlyerFramework", from: "6.12.0"),
.package(url: "https://github.com/firebase/firebase-ios-sdk.git", from: "10.7.0"),
],
targets: [
.binaryTarget(
Expand Down Expand Up @@ -167,7 +170,11 @@ let package = Package(
// AnalyticsManager
.target(
name: "AnalyticsManager",
dependencies: []
dependencies: [
.product(name: "Amplitude", package: "Amplitude-iOS"),
.product(name: "AppsFlyerLib", package: "AppsFlyerFramework"),
.product(name: "FirebaseAnalytics", package: "firebase-ios-sdk"),
]
),
.testTarget(
name: "AnalyticsManagerUnitTests",
Expand Down
15 changes: 4 additions & 11 deletions Packages/KeyAppKit/Sources/AnalyticsManager/AnalyticsManager.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
//
// AnalyticsManager .swift
// p2p_wallet
//
// Created by Chung Tran on 11/06/2021.
//

import Foundation

public protocol AnalyticsManager {
Expand All @@ -14,7 +7,7 @@ public protocol AnalyticsManager {

public class AnalyticsManagerImpl: AnalyticsManager {
private let providers: [AnalyticsProvider]

public init(providers: [AnalyticsProvider]) {
self.providers = providers
}
Expand All @@ -24,18 +17,18 @@ public class AnalyticsManagerImpl: AnalyticsManager {
// fillter providers to send
guard event.providerIds.contains(provider.providerId)
else { return }

// log event to provider
provider.logEvent(event)
}
}

public func log(parameter: AnalyticsParameter) {
providers.forEach { provider in
// fillter providers to send
guard parameter.providerIds.contains(provider.providerId)
else { return }

// log event to provider
provider.logParameter(parameter)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Amplitude
import Foundation

extension AmplitudeAnalyticsProvider {
public extension AmplitudeAnalyticsProvider {
func setUserId(_ id: String?) {
guard let id else { return }
Amplitude.instance().setUserId(id)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
//
// AnalyticsService.swift
// p2p_wallet
//
// Created by Ivan on 12.12.2022.
//

import Foundation
import AnalyticsManager
import Foundation

extension AnalyticsManager {
public extension AnalyticsManager {
func log(event: KeyAppAnalyticsEvent) {
log(event: event as AnalyticsEvent)
}

func log(parameter: KeyAppAnalyticsParameter) {
log(parameter: parameter as AnalyticsParameter)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

extension String {
var snakeAndFirstUppercased: String? {
guard let snakeCase = snakeCased() else { return nil }
return snakeCase.prefix(1).uppercased() + snakeCase.dropFirst()
}

private func snakeCased() -> String? {
let pattern = "([a-z0-9])([A-Z])"

let regex = try? NSRegularExpression(pattern: pattern, options: [])
let range = NSRange(location: 0, length: count)
return regex?.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "$1_$2")
.uppercaseFirst
}

private var uppercaseFirst: String {
firstCharacter.uppercased() + String(dropFirst())
}

private var firstCharacter: String {
String(prefix(1))
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Foundation
import AnalyticsManager
import Foundation

extension KeyAppAnalyticsEvent {

public extension KeyAppAnalyticsEvent {
/// The name of the event to send
var name: String? {
// By default, name of the event will be converted from `camelCase` to `Uppercased_Snake_Case` format
// For example: `KeyAppAnalyticsEvent.mainScreenSwapOpen` will be converted to "Main_Screen_Swap_Open" automatically.

// For example: `KeyAppAnalyticsEvent.mainScreenSwapOpen` will be converted to "Main_Screen_Swap_Open"
// automatically.

// Modify the name manually and prevent default behavior
switch self {
case .sellOnlySOLNotification:
Expand All @@ -23,17 +23,18 @@ extension KeyAppAnalyticsEvent {
default:
break
}

// Default converter from `camelCase` to `Uppercased_Snake_Case` format
return mirror.label.snakeAndFirstUppercased
}

/// Params sent with event
var params: [String: Any]? {
guard !mirror.params.isEmpty else { return nil }

// The same for params, params key & value can be customized too, if not, it will be automatically converted to `Uppercased_Snake_Case`


// The same for params, params key & value can be customized too, if not, it will be automatically converted to
// `Uppercased_Snake_Case`

// Modify the key & value manually and prevent default behavior
switch self {
case let .swapChangingTokenAClick(tokenAName):
Expand All @@ -43,19 +44,23 @@ extension KeyAppAnalyticsEvent {
case let .swapChangingValueTokenA(tokenAName, tokenAValue):
return ["Token_A_Name": tokenAName, "Token_A_Value": tokenAValue]
case let .swapChangingValueTokenB(tokenBName, tokenBValue, transactionSimulation):
return ["Token_B_Name": tokenBName, "Token_B_Value": tokenBValue, "Transaction_Simulation": transactionSimulation]
return [
"Token_B_Name": tokenBName,
"Token_B_Value": tokenBValue,
"Transaction_Simulation": transactionSimulation,
]
case let .swapChangingValueTokenAAll(tokenAName, tokenAValue):
return ["Token_A_Name": tokenAName, "Token_A_Value": tokenAValue]
case let .swapSwitchTokens(tokenAName, tokenBName):
return ["Token_A_Name": tokenAName, "Token_B_Name": tokenBName]
default:
break
}

// Default converter from `camelCase` to `Uppercased_Snake_Case` format
let formatted = mirror.params.map {
var key = $0.key.snakeAndFirstUppercased

switch key {
case "Token_BName":
key = "Token_B_Name"
Expand All @@ -68,7 +73,7 @@ extension KeyAppAnalyticsEvent {
default:
break
}

return (key ?? "", $0.value)
}
return Dictionary(uniqueKeysWithValues: formatted)
Expand All @@ -78,33 +83,33 @@ extension KeyAppAnalyticsEvent {
var providerIds: [AnalyticsProviderId] {
// By default, all events will be sent to amplitude only
var ids: [KeyAppAnalyticsProviderId] = [
.amplitude
.amplitude,
]

// for some events, we will sent to appsFlyer and firebaseAnalytics
switch self {
case .onboardingStartButton,
.creationPhoneScreen,
.createSmsValidation,
.createConfirmPin,
.usernameCreationScreen,
.usernameCreationButton,
.restoreSeed,
.onboardingMerged,
.login,
.buyButtonPressed,
.sendNewConfirmButtonClick,
.swapClickApproveButton:
.creationPhoneScreen,
.createSmsValidation,
.createConfirmPin,
.usernameCreationScreen,
.usernameCreationButton,
.restoreSeed,
.onboardingMerged,
.login,
.buyButtonPressed,
.sendNewConfirmButtonClick,
.swapClickApproveButton:
ids.append(contentsOf: [
.appsFlyer,
.firebaseAnalytics
.firebaseAnalytics,
])
default:
break
}
return ids.map(\.rawValue)
}

// MARK: - Helpers

private var mirror: (label: String, params: [String: Any]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import AnalyticsManager
import Foundation

enum KeyAppAnalyticsEvent: AnalyticsEvent {
public enum KeyAppAnalyticsEvent: AnalyticsEvent {
// MARK: - Create wallet

case createPhoneClickButton
Expand Down Expand Up @@ -42,25 +42,25 @@ enum KeyAppAnalyticsEvent: AnalyticsEvent {
case mainScreenTokenDetailsOpen(tokenTicker: String)
case mainScreenBuyToken(tokenName: String)
case mainScreenHiddenTokens

case mainScreenCryptoClick
case mainScreenSendClick
case mainScreenHistoryClick
case mainScreenSettingsClick

case userAggregateBalanceBase(amountUsd: Double, currency: String)
case userHasPositiveBalanceBase(state: Bool)

// MARK: - Crypto

case cryptoScreenOpened
case cryptoAmountClick
case cryptoReceiveClick
case cryptoSwapClick
case cryptoTokenClick(tokenName: String)
case cryptoClaimTransferredViewed(claimCount: Int)
case cryptoClaimTransferredClick

case userAggregateBalanceTokens(amountUsd: Double, currency: String)
case userHasPositiveBalanceTokens(state: Bool)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import Foundation
import AnalyticsManager
import Foundation

extension KeyAppAnalyticsParameter {
public extension KeyAppAnalyticsParameter {
var name: String? {
mirror.label.snakeAndFirstUppercased
}

var value: Any? {
mirror.value
}

var providerIds: [AnalyticsProviderId] {
[KeyAppAnalyticsProviderId.amplitude].map(\.rawValue)
}

// MARK: - Helpers

private var mirror: (label: String, value: Any?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Foundation
import AnalyticsManager
import Foundation

enum KeyAppAnalyticsParameter: AnalyticsParameter {
public enum KeyAppAnalyticsParameter: AnalyticsParameter {
case userHasPositiveBalance(Bool)
case userAggregateBalance(Double)

// Onboarding
case userRestoreMethod(String)
case userDeviceshare(Bool)

case pushAllowed(Bool)
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
import Amplitude
import Foundation
import AnalyticsManager
import Foundation

final class AmplitudeAnalyticsProvider: AnalyticsProvider {
var providerId: AnalyticsProviderId {
public final class AmplitudeAnalyticsProvider: AnalyticsProvider {
public var providerId: AnalyticsProviderId {
KeyAppAnalyticsProviderId.amplitude.rawValue
}

init() {
let apiKey: String
#if !RELEASE
apiKey = .secretConfig("AMPLITUDE_API_KEY_FEATURE")!
#else
apiKey = .secretConfig("AMPLITUDE_API_KEY")!
#endif


public init(apiKey: String) {
Amplitude.instance().trackingSessionEvents = true
Amplitude.instance().initializeApiKey(apiKey)
}

func logEvent(_ event: AnalyticsEvent) {
public func logEvent(_ event: AnalyticsEvent) {
guard let eventName = event.name else { return }
Amplitude.instance().logEvent(eventName, withEventProperties: event.params)
}
func logParameter(_ parameter: AnalyticsParameter) {

public func logParameter(_ parameter: AnalyticsParameter) {
guard
let value = parameter.value as? NSObject,
let identify = AMPIdentify().set(parameter.name, value: value)
Expand Down
Loading
Loading