Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ buildConfig {
packageName = 'app'
buildConfigField 'int', 'VERSION_CODE', '1'
buildConfigField 'String', 'VERSION', '0.0.1'
buildConfigField "String", "PROFILE_URL", "https://sourcerer.io/"
buildConfigField "String", "API_BASE_URL", "http://localhost:3181"
buildConfigField 'String', 'PROFILE_URL', 'https://sourcerer.io/'
buildConfigField 'String', 'API_BASE_URL', 'http://localhost:3181'
buildConfigField 'String', 'GA_BASE_PATH', 'http://www.google-analytics.com'
buildConfigField 'String', 'GA_TRACKING_ID', 'UA-107129190-2'
buildConfigField 'boolean', 'IS_GA_ENABLED', 'true'
buildConfig
}

junitPlatform {
Expand Down
145 changes: 145 additions & 0 deletions src/main/kotlin/app/Analytics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright 2017 Sourcerer Inc. All Rights Reserved.
// Author: Anatoly Kislov (anatoly@sourcerer.io)

package app
Copy link
Member

Choose a reason for hiding this comment

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

header

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok.


import com.github.kittinunf.fuel.core.FuelError
import com.github.kittinunf.fuel.core.FuelManager
import com.github.kittinunf.fuel.core.Method
import com.github.kittinunf.fuel.core.Request
import com.google.protobuf.InvalidProtocolBufferException
import java.security.InvalidParameterException

typealias Param = Pair<String, String>

/**
* Google Analytics events tracking.
*/
object Analytics {
private val IS_ENABLED = BuildConfig.IS_GA_ENABLED
private val BASE_PATH = BuildConfig.GA_BASE_PATH
private val BASE_URL = "/virtual/app/"
private val PROTOCOL_VERSION = "1"
private val TRACKING_ID = BuildConfig.GA_TRACKING_ID
private val DATA_SOURCE = "app"

private val HIT_PAGEVIEW = "pageview"
private val HIT_EXCEPTION = "exception"

private val fuelManager = FuelManager()

var uuid: String = "" // Should be set on start of the app.
var username: String = "" // Should be set on successful authorization.

init {
fuelManager.basePath = BASE_PATH
}

private fun post(params: List<Param>): Request {
return fuelManager.request(Method.POST, "/collect", params)
}

/**
* Google Analytics Measurement Protocol is used to track events.
* User iteration data is sent to GA endpoint via POST request.
* Events (or hits) mapped to virtual urls with "Data Source" parameter.
* Used parameters:
* - v: Protocol Version (Required)
* - tid: Tracking ID - used to specify GA account (Required)
* - cid: Client ID - anonymous client id (UUID type 4)
* - uid: User ID - username
* - t: Hit Type - type of event
* - dp: Document Path - virtual url
*/
private fun trackEvent(event: String, params: List<Param> = listOf()) {
if (!IS_ENABLED || (username.isEmpty() && uuid.isEmpty())) {
return
}

val idParams = mutableListOf<Param>()
if (uuid.isNotEmpty()) {
idParams.add("cid" to uuid)
}
if (username.isNotEmpty()) {
idParams.add("uid" to username)
}

val defaultParams = listOf("v" to PROTOCOL_VERSION,
"tid" to TRACKING_ID,
"ds" to DATA_SOURCE,
"t" to HIT_PAGEVIEW,
"dp" to BASE_URL + event)

try {
// Send event to GA with united params.
post(params + defaultParams.filter { !params.contains(it) }
+ idParams).responseString()
Copy link
Member

Choose a reason for hiding this comment

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

weird line breakdown

Copy link
Member Author

Choose a reason for hiding this comment

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

Why?

} catch (e: Throwable) {
Logger.error("Error while sending error report", e, logOnly = true)
}
}

fun trackStart() {
trackEvent("start")
}

fun trackAuth() {
trackEvent("auth")
}

fun trackConfigSetup() {
trackEvent("config/setup")
}

fun trackConfigChanged() {
trackEvent("config/changed")
}

fun trackHashingRepoSuccess() {
trackEvent("hashing/repo/success")
}

fun trackHashingSuccess() {
trackEvent("hashing/success")
}

fun trackError(e: Throwable? = null, code: String = "") {
val url = if (e != null) getErrorUrl(e) else code
val separator = if (url.isNotEmpty()) "/" else ""
trackEvent("error" + separator + url, listOf("t" to HIT_EXCEPTION))
}

fun trackExit() {
trackEvent("exit")
}

private fun getErrorUrl(e: Throwable): String {
// Mapping for request exceptions.
when (e) {
is FuelError -> return "request"
is InvalidParameterException -> return "request/parsing"
is InvalidProtocolBufferException -> return "request/parsing"
}

// Get concrete class of exception name removing all common parts.
val name = e.javaClass.simpleName.replace("Exception", "")
.replace("Error", "")
.replace("Throwable", "")

if (name.length == 0 || name.length == 1) {
return name
}

// Divide CamelCased words in class name by dashes.
val nameCapitalized = name.toUpperCase()
var url = name[0].toString()
for (i in 1..name.length - 1) {
if (name[i] == nameCapitalized[i]) {
url += "-"
}
url += name[i]
}

return url.toLowerCase()
}
}
20 changes: 10 additions & 10 deletions src/main/kotlin/app/Logger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ object Logger {

/**
* Log error message with exception info.
*
* @property message the message for user and logs.
* @property e the exception if presented.
* @property code the code of error if exception is not presented.
*/
fun error(message: String) {
fun error(message: String, e: Throwable? = null, code: String = "",
logOnly: Boolean = false) {
if (LEVEL >= ERROR) {
println("[e] $message.")
println("[e] $message" + if (e != null) ": $e" else "")
}
}

/**
* Log error message with exception info.
*/
fun error(message: String, e: Throwable) {
if (LEVEL >= ERROR) {
println("[e] $message: $e")
if (!logOnly) {
Analytics.trackError(e = e, code = code)
//TODO(anatoly): Add error tracking software.
}
}

Expand Down
25 changes: 22 additions & 3 deletions src/main/kotlin/app/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import com.beust.jcommander.JCommander
import com.beust.jcommander.MissingCommandException

fun main(argv : Array<String>) {
Thread.setDefaultUncaughtExceptionHandler { _, e: Throwable? ->
Logger.error("Uncaught exception", e)
}
Main(argv)
}

Expand All @@ -27,6 +30,9 @@ class Main(argv: Array<String>) {
private val api = ServerApi(configurator)

init {
Analytics.uuid = configurator.getUuidPersistent()
Analytics.trackStart()

val options = Options()
val commandAdd = CommandAdd()
val commandConfig = CommandConfig()
Expand Down Expand Up @@ -58,8 +64,13 @@ class Main(argv: Array<String>) {
else -> startUi()
}
} catch (e: MissingCommandException) {
Logger.error("No such command: ${e.unknownCommand}")
Logger.error(
message = "No such command: ${e.unknownCommand}",
code = "no-command"
)
}

Analytics.trackExit()
}

private fun startUi() {
Expand All @@ -74,16 +85,20 @@ class Main(argv: Array<String>) {
configurator.addLocalRepoPersistent(localRepo)
configurator.saveToFile()
println("Added git repository at $path.")

Analytics.trackConfigChanged()
} else {
Logger.error("No valid git repository found at $path.")
Logger.error(message = "No valid git repository found at $path.",
code = "repo-invalid")
}
}

private fun doConfig(commandOptions: CommandConfig) {
val (key, value) = commandOptions.pair

if (!arrayListOf("username", "password").contains(key)) {
Logger.error("No such key $key")
Logger.error(message = "No such key $key",
code = "invalid-params")
return
}

Expand All @@ -93,6 +108,8 @@ class Main(argv: Array<String>) {
}

configurator.saveToFile()

Analytics.trackConfigChanged()
}

private fun doList() {
Expand All @@ -108,6 +125,8 @@ class Main(argv: Array<String>) {
configurator.removeLocalRepoPersistent(LocalRepo(path))
configurator.saveToFile()
println("Repository removed from tracking list.")

Analytics.trackConfigChanged()
} else {
println("Repository not found in tracking list.")
}
Expand Down
49 changes: 30 additions & 19 deletions src/main/kotlin/app/api/ServerApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import app.model.FactGroup
import app.model.Repo
import app.model.User
import app.utils.RequestException
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.FuelManager
import com.github.kittinunf.fuel.core.Method
import com.github.kittinunf.fuel.core.Request
import com.github.kittinunf.fuel.core.Response
import com.google.protobuf.InvalidProtocolBufferException
Expand All @@ -30,6 +30,7 @@ class ServerApi (private val configurator: Configurator) : Api {
private val KEY_TOKEN = "Token="
}

private val fuelManager = FuelManager()
private var token = ""

private fun cookieRequestInterceptor() = { req: Request ->
Expand All @@ -50,7 +51,6 @@ class ServerApi (private val configurator: Configurator) : Api {
}

init {
val fuelManager = FuelManager.instance
fuelManager.basePath = BuildConfig.API_BASE_URL
fuelManager.addRequestInterceptor { cookieRequestInterceptor() }
fuelManager.addResponseInterceptor { cookieResponseInterceptor() }
Expand All @@ -62,38 +62,49 @@ class ServerApi (private val configurator: Configurator) : Api {
private val password
get() = configurator.getPassword()

private fun post(path: String): Request {
return fuelManager.request(Method.POST, path)
}

private fun get(path: String): Request {
return fuelManager.request(Method.GET, path)
}

private fun delete(path: String): Request {
return fuelManager.request(Method.DELETE, path)
}

private fun createRequestGetToken(): Request {
return Fuel.post("/auth").authenticate(username, password)
return post("/auth").authenticate(username, password)
.header(getVersionCodeHeader())
}

private fun createRequestGetUser(): Request {
return Fuel.get("/user")
return get("/user")
}

private fun createRequestGetRepo(repoRehash: String): Request {
return Fuel.get("/repo/$repoRehash")
return get("/repo/$repoRehash")
}

private fun createRequestPostRepo(repo: Repo): Request {
return Fuel.post("/repo").header(getContentTypeHeader())
.body(repo.serialize())
return post("/repo").header(getContentTypeHeader())
.body(repo.serialize())
}

private fun createRequestPostCommits(commits: CommitGroup): Request {
return Fuel.post("/commits").header(getContentTypeHeader())
.body(commits.serialize())
return post("/commits").header(getContentTypeHeader())
.body(commits.serialize())
}

private fun createRequestDeleteCommits(commits: CommitGroup): Request {
return Fuel.delete("/commits").header(getContentTypeHeader())
.body(commits.serialize())
return delete("/commits").header(getContentTypeHeader())
.body(commits.serialize())
}

private fun createRequestPostFacts(facts: FactGroup):
Request {
return Fuel.post("/facts").header(getContentTypeHeader())
.body(facts.serialize())
private fun createRequestPostFacts(facts: FactGroup): Request {
return post("/facts").header(getContentTypeHeader())
.body(facts.serialize())
}

private fun <T> makeRequest(request: Request,
Expand All @@ -102,13 +113,13 @@ class ServerApi (private val configurator: Configurator) : Api {
try {
Logger.debug("Request $requestName initialized")
val (_, res, result) = request.responseString()
val (_, error) = result
if (error == null) {
val (_, e) = result
if (e == null) {
Logger.debug("Request $requestName success")
return parser(res.data)
} else {
Logger.error("Request $requestName error", error)
throw RequestException(error)
Logger.error("Request $requestName error", e)
throw RequestException(e)
}
} catch (e: InvalidProtocolBufferException) {
Logger.error("Request $requestName error while parsing", e)
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/app/config/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import app.utils.Options
* Config data class.
*/
class Config (
var uuid: String = "",
var username: String = "",
var password: String = "",
var localRepos: MutableSet<LocalRepo> = mutableSetOf()
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/app/config/Configurator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface Configurator {
fun getRepos(): List<Repo>
fun setUsernameCurrent(username: String)
fun setPasswordCurrent(password: String)
fun getUuidPersistent(): String
fun setUsernamePersistent(username: String)
fun setPasswordPersistent(password: String)
fun addLocalRepoPersistent(localRepo: LocalRepo)
Expand Down
Loading