|
| 1 | +// Copyright 2017 Sourcerer Inc. All Rights Reserved. |
| 2 | +// Author: Anatoly Kislov ([email protected]) |
| 3 | + |
| 4 | +package app |
| 5 | + |
| 6 | +import com.github.kittinunf.fuel.core.FuelError |
| 7 | +import com.github.kittinunf.fuel.core.FuelManager |
| 8 | +import com.github.kittinunf.fuel.core.Method |
| 9 | +import com.github.kittinunf.fuel.core.Request |
| 10 | +import com.google.protobuf.InvalidProtocolBufferException |
| 11 | +import java.security.InvalidParameterException |
| 12 | + |
| 13 | +typealias Param = Pair<String, String> |
| 14 | + |
| 15 | +/** |
| 16 | + * Google Analytics events tracking. |
| 17 | + */ |
| 18 | +object Analytics { |
| 19 | + private val IS_ENABLED = BuildConfig.IS_GA_ENABLED |
| 20 | + private val BASE_PATH = BuildConfig.GA_BASE_PATH |
| 21 | + private val BASE_URL = "/virtual/app/" |
| 22 | + private val PROTOCOL_VERSION = "1" |
| 23 | + private val TRACKING_ID = BuildConfig.GA_TRACKING_ID |
| 24 | + private val DATA_SOURCE = "app" |
| 25 | + |
| 26 | + private val HIT_PAGEVIEW = "pageview" |
| 27 | + private val HIT_EXCEPTION = "exception" |
| 28 | + |
| 29 | + private val fuelManager = FuelManager() |
| 30 | + |
| 31 | + var uuid: String = "" // Should be set on start of the app. |
| 32 | + var username: String = "" // Should be set on successful authorization. |
| 33 | + |
| 34 | + init { |
| 35 | + fuelManager.basePath = BASE_PATH |
| 36 | + } |
| 37 | + |
| 38 | + private fun post(params: List<Param>): Request { |
| 39 | + return fuelManager.request(Method.POST, "/collect", params) |
| 40 | + } |
| 41 | + |
| 42 | + /** |
| 43 | + * Google Analytics Measurement Protocol is used to track events. |
| 44 | + * User iteration data is sent to GA endpoint via POST request. |
| 45 | + * Events (or hits) mapped to virtual urls with "Data Source" parameter. |
| 46 | + * Used parameters: |
| 47 | + * - v: Protocol Version (Required) |
| 48 | + * - tid: Tracking ID - used to specify GA account (Required) |
| 49 | + * - cid: Client ID - anonymous client id (UUID type 4) |
| 50 | + * - uid: User ID - username |
| 51 | + * - t: Hit Type - type of event |
| 52 | + * - dp: Document Path - virtual url |
| 53 | + */ |
| 54 | + private fun trackEvent(event: String, params: List<Param> = listOf()) { |
| 55 | + if (!IS_ENABLED || (username.isEmpty() && uuid.isEmpty())) { |
| 56 | + return |
| 57 | + } |
| 58 | + |
| 59 | + val idParams = mutableListOf<Param>() |
| 60 | + if (uuid.isNotEmpty()) { |
| 61 | + idParams.add("cid" to uuid) |
| 62 | + } |
| 63 | + if (username.isNotEmpty()) { |
| 64 | + idParams.add("uid" to username) |
| 65 | + } |
| 66 | + |
| 67 | + val defaultParams = listOf("v" to PROTOCOL_VERSION, |
| 68 | + "tid" to TRACKING_ID, |
| 69 | + "ds" to DATA_SOURCE, |
| 70 | + "t" to HIT_PAGEVIEW, |
| 71 | + "dp" to BASE_URL + event) |
| 72 | + |
| 73 | + try { |
| 74 | + // Send event to GA with united params. |
| 75 | + post(params + defaultParams.filter { !params.contains(it) } |
| 76 | + + idParams).responseString() |
| 77 | + } catch (e: Throwable) { |
| 78 | + Logger.error("Error while sending error report", e, logOnly = true) |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + fun trackStart() { |
| 83 | + trackEvent("start") |
| 84 | + } |
| 85 | + |
| 86 | + fun trackAuth() { |
| 87 | + trackEvent("auth") |
| 88 | + } |
| 89 | + |
| 90 | + fun trackConfigSetup() { |
| 91 | + trackEvent("config/setup") |
| 92 | + } |
| 93 | + |
| 94 | + fun trackConfigChanged() { |
| 95 | + trackEvent("config/changed") |
| 96 | + } |
| 97 | + |
| 98 | + fun trackHashingRepoSuccess() { |
| 99 | + trackEvent("hashing/repo/success") |
| 100 | + } |
| 101 | + |
| 102 | + fun trackHashingSuccess() { |
| 103 | + trackEvent("hashing/success") |
| 104 | + } |
| 105 | + |
| 106 | + fun trackError(e: Throwable? = null, code: String = "") { |
| 107 | + val url = if (e != null) getErrorUrl(e) else code |
| 108 | + val separator = if (url.isNotEmpty()) "/" else "" |
| 109 | + trackEvent("error" + separator + url, listOf("t" to HIT_EXCEPTION)) |
| 110 | + } |
| 111 | + |
| 112 | + fun trackExit() { |
| 113 | + trackEvent("exit") |
| 114 | + } |
| 115 | + |
| 116 | + private fun getErrorUrl(e: Throwable): String { |
| 117 | + // Mapping for request exceptions. |
| 118 | + when (e) { |
| 119 | + is FuelError -> return "request" |
| 120 | + is InvalidParameterException -> return "request/parsing" |
| 121 | + is InvalidProtocolBufferException -> return "request/parsing" |
| 122 | + } |
| 123 | + |
| 124 | + // Get concrete class of exception name removing all common parts. |
| 125 | + val name = e.javaClass.simpleName.replace("Exception", "") |
| 126 | + .replace("Error", "") |
| 127 | + .replace("Throwable", "") |
| 128 | + |
| 129 | + if (name.length == 0 || name.length == 1) { |
| 130 | + return name |
| 131 | + } |
| 132 | + |
| 133 | + // Divide CamelCased words in class name by dashes. |
| 134 | + val nameCapitalized = name.toUpperCase() |
| 135 | + var url = name[0].toString() |
| 136 | + for (i in 1..name.length - 1) { |
| 137 | + if (name[i] == nameCapitalized[i]) { |
| 138 | + url += "-" |
| 139 | + } |
| 140 | + url += name[i] |
| 141 | + } |
| 142 | + |
| 143 | + return url.toLowerCase() |
| 144 | + } |
| 145 | +} |
0 commit comments