Skip to content

Commit

Permalink
Merge pull request #29 from TelemetryDeck/send-navigation-signals
Browse files Browse the repository at this point in the history
Add support for navigation signals
  • Loading branch information
winsmith authored Jun 25, 2024
2 parents 08fe243 + 4dd42df commit 02b1bab
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions lib/src/main/java/com/telemetrydeck/sdk/NavigationStatus.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.telemetrydeck.sdk

interface NavigationStatus {
/**
* Apply the provided path as a visited destination.
*/
fun applyDestination(path: String)

/**
* Returns the last destination path or an empty string if none has been provided.
*/
fun getLastDestination(): String
}
19 changes: 19 additions & 0 deletions lib/src/main/java/com/telemetrydeck/sdk/NavigationStatusCache.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.telemetrydeck.sdk

class MemoryNavigationStatus(private var previousNavigationPath: String? = null) :
NavigationStatus {

/**
* Apply the provided path as a visited destination.
*/
override fun applyDestination(path: String) {
previousNavigationPath = path
}

/**
* Returns the last destination path or an empty string if none has been provided.
*/
override fun getLastDestination(): String {
return previousNavigationPath ?: ""
}
}
8 changes: 8 additions & 0 deletions lib/src/main/java/com/telemetrydeck/sdk/PayloadParameters.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.telemetrydeck.sdk

enum class PayloadParameters(val type: String) {
TelemetryDeckNavigationSchemaVersion("TelemetryDeck.Navigation.schemaVersion"),
TelemetryDeckNavigationIdentifier("TelemetryDeck.Navigation.identifier"),
TelemetryDeckNavigationSourcePath("TelemetryDeck.Navigation.sourcePath"),
TelemetryDeckNavigationDestinationPath("TelemetryDeck.Navigation.destinationPath"),
}
11 changes: 5 additions & 6 deletions lib/src/main/java/com/telemetrydeck/sdk/PersistentSignalCache.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.telemetrydeck.sdk

import kotlinx.serialization.json.Json
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import java.lang.Exception

class PersistentSignalCache(private var signalQueue: MutableList<Signal> = mutableListOf()): SignalCache {
class PersistentSignalCache(private var signalQueue: MutableList<Signal> = mutableListOf()) :
SignalCache {
val cacheFileName: String = "telemetrydeck.json"
var file: File? = null
private var file: File? = null

constructor(cacheDir: File, logger: DebugLogger?) : this() {
if (!cacheDir.isDirectory) {
Expand Down Expand Up @@ -56,7 +55,7 @@ class PersistentSignalCache(private var signalQueue: MutableList<Signal> = mutab
}
}

fun saveSignals() {
private fun saveSignals() {
file?.createNewFile()
val json = Json.encodeToString(signalQueue)
file?.writeText(json)
Expand Down
19 changes: 9 additions & 10 deletions lib/src/main/java/com/telemetrydeck/sdk/SignalType.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.telemetrydeck.sdk

enum class SignalType(val type: String) {
ActivityCreated("ActivityCreated"),
ActivityStarted("ActivityStarted"),
ActivityResumed("ActivityResumed"),
ActivityPaused("ActivityPaused"),
ActivityStopped("ActivityStopped"),
ActivitySaveInstanceState("ActivitySaveInstanceState"),
ActivityDestroyed("ActivityDestroyed"),
AppBackground("AppBackground"),
AppForeground("AppForeground"),
NewSessionBegan("NewSessionBegan"),
ActivityCreated("ActivityCreated"), ActivityStarted("ActivityStarted"), ActivityResumed("ActivityResumed"), ActivityPaused(
"ActivityPaused"
),
ActivityStopped("ActivityStopped"), ActivitySaveInstanceState("ActivitySaveInstanceState"), ActivityDestroyed(
"ActivityDestroyed"
),
AppBackground("AppBackground"), AppForeground("AppForeground"), NewSessionBegan("NewSessionBegan"), TelemetryDeckNavigationPathChanged(
"TelemetryDeck.Navigation.pathChanged"
)
}
34 changes: 30 additions & 4 deletions lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import android.content.pm.ApplicationInfo
import java.lang.ref.WeakReference
import java.net.URL
import java.security.MessageDigest
import java.util.*
import java.util.UUID
import kotlin.Result.Companion.failure
import kotlin.Result.Companion.success

Expand All @@ -20,6 +20,7 @@ class TelemetryManager(

var cache: SignalCache? = null
var logger: DebugLogger? = null
private val navigationStatus: NavigationStatus = MemoryNavigationStatus()

override fun newSession(sessionID: UUID) {
this.configuration.sessionID = sessionID
Expand All @@ -45,6 +46,23 @@ class TelemetryManager(
queue(signalType.type, clientUser, additionalPayload)
}

override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) {
navigationStatus.applyDestination(destinationPath)

val payload: Map<String, String> = mapOf(
PayloadParameters.TelemetryDeckNavigationSchemaVersion.type to "1",
PayloadParameters.TelemetryDeckNavigationIdentifier.type to "$sourcePath -> $destinationPath",
PayloadParameters.TelemetryDeckNavigationSourcePath.type to sourcePath,
PayloadParameters.TelemetryDeckNavigationDestinationPath.type to destinationPath
)

queue(SignalType.TelemetryDeckNavigationPathChanged, clientUser, payload)
}

override fun navigate(destinationPath: String, clientUser: String?) {
navigate(navigationStatus.getLastDestination(), destinationPath, clientUser)
}

override suspend fun send(
signalType: String,
clientUser: String?,
Expand Down Expand Up @@ -79,7 +97,7 @@ class TelemetryManager(
)
client.send(signals)
success(Unit)
} catch(e: Exception) {
} catch (e: Exception) {
logger?.error("Failed to send signals due to an error ${e} ${e.stackTraceToString()}")
failure(e)
}
Expand All @@ -104,7 +122,7 @@ class TelemetryManager(
enrichedPayload = provider.enrich(signalType, clientUser, enrichedPayload)
}
val userValue = clientUser ?: configuration.defaultUser ?: ""
val userValueWithSalt = userValue +( configuration.salt ?: "")
val userValueWithSalt = userValue + (configuration.salt ?: "")
val hashedUser = hashString(userValue, "SHA-256")
val payload = SignalPayload(additionalPayload = enrichedPayload)
val signal = Signal(
Expand Down Expand Up @@ -207,6 +225,14 @@ class TelemetryManager(
getInstance()?.queue(signalType, clientUser, additionalPayload)
}

override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) {
getInstance()?.navigate(sourcePath, destinationPath, clientUser = clientUser)
}

override fun navigate(destinationPath: String, clientUser: String?) {
getInstance()?.navigate(destinationPath, clientUser = clientUser)
}

override suspend fun send(
signalType: String,
clientUser: String?,
Expand Down Expand Up @@ -364,7 +390,7 @@ class TelemetryManager(
}
}

var salt = this.salt
val salt = this.salt
if (salt != null) {
config.salt = salt
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.telemetrydeck.sdk

import java.util.*
import java.util.UUID

interface TelemetryManagerSignals {

Expand Down Expand Up @@ -38,6 +38,20 @@ interface TelemetryManagerSignals {
additionalPayload: Map<String, String> = emptyMap()
)

/**
* Send a signal that represents a navigation event with a source and a destination.
*
* @see <a href="https://telemetrydeck.com/docs/articles/navigation-signals/">Navigation Signals</a>
* */
fun navigate(sourcePath: String, destinationPath: String, clientUser: String? = null)

/**
* Send a signal that represents a navigation event with a destination and a default source.
*
* @see <a href="https://telemetrydeck.com/docs/articles/navigation-signals/">Navigation Signals</a>
* */
fun navigate(destinationPath: String, clientUser: String? = null)


/**
* Send a signal immediately
Expand Down
153 changes: 149 additions & 4 deletions lib/src/test/java/com/telemetrydeck/sdk/TelemetryManagerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import java.net.URL
import java.util.*
import java.util.Calendar
import java.util.UUID

class TelemetryManagerTest {

Expand All @@ -17,7 +18,7 @@ class TelemetryManagerTest {
fun telemetryManager_sets_signal_properties() {
val appID = "32CB6574-6732-4238-879F-582FEBEB6536"
val config = TelemetryManagerConfiguration(appID)
val manager = TelemetryManager.Builder().configuration(config).build(null)
val manager = TelemetryManager.Builder().configuration(config).build(null)

manager.queue("type", "clientUser", emptyMap())

Expand All @@ -27,7 +28,10 @@ class TelemetryManagerTest {
Assert.assertEquals(UUID.fromString(appID), queuedSignal!!.appID)
Assert.assertEquals(config.sessionID, UUID.fromString(queuedSignal.sessionID))
Assert.assertEquals("type", queuedSignal.type)
Assert.assertEquals("6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149", queuedSignal.clientUser)
Assert.assertEquals(
"6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149",
queuedSignal.clientUser
)
Assert.assertEquals("false", queuedSignal.isTestMode)
}

Expand Down Expand Up @@ -246,9 +250,150 @@ class TelemetryManagerTest {
Assert.assertEquals(1, filteredSignals.count())
Assert.assertEquals("okSignal", filteredSignals[0].type)
}

@Test
fun telemetryManager_navigate_source_destination_sets_default_parameters() {
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
val manager = TelemetryManager.Builder().configuration(config).build(null)

manager.navigate("source", "destination")

val queuedSignal = manager.cache?.empty()?.first()

Assert.assertNotNull(queuedSignal)

// validate the signal type
Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged")

// validate the navigation status payload
// https://github.com/TelemetryDeck/KotlinSDK/issues/28
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") },
"TelemetryDeck.Navigation.schemaVersion:1"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") },
"TelemetryDeck.Navigation.identifier:source -> destination"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") },
"TelemetryDeck.Navigation.sourcePath:source"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") },
"TelemetryDeck.Navigation.destinationPath:destination"
)
}

@Test
fun telemetryManager_navigate_source_destination_sets_clientUser() {
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
config.defaultUser = "user"
val manager = TelemetryManager.Builder().configuration(config).build(null)

manager.navigate("source", "destination", "clientUser")

val queuedSignal = manager.cache?.empty()?.first()

Assert.assertNotNull(queuedSignal)

// validate that the provided user was used and not default
Assert.assertEquals(
queuedSignal?.clientUser,
"6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149"
)
}

@Test
fun telemetryManager_navigate_source_destination_uses_default_user() {
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
config.defaultUser = "clientUser"
val manager = TelemetryManager.Builder().configuration(config).build(null)

manager.navigate("source", "destination")

val queuedSignal = manager.cache?.empty()?.first()

Assert.assertNotNull(queuedSignal)

// validate that the default user was used
Assert.assertEquals(
queuedSignal?.clientUser,
"6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149"
)
}

@Test
fun telemetryManager_navigate_destination_no_previous_source() {
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
val manager = TelemetryManager.Builder().configuration(config).build(null)

manager.navigate("destination")

val queuedSignal = manager.cache?.empty()?.first()

Assert.assertNotNull(queuedSignal)

// validate the signal type
Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged")

// validate the navigation status payload
// https://github.com/TelemetryDeck/KotlinSDK/issues/28
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") },
"TelemetryDeck.Navigation.schemaVersion:1"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") },
"TelemetryDeck.Navigation.identifier: -> destination"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") },
"TelemetryDeck.Navigation.sourcePath:"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") },
"TelemetryDeck.Navigation.destinationPath:destination"
)
}

@Test
fun telemetryManager_navigate_destination_uses_previous_destination_as_source() {
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
val manager = TelemetryManager.Builder().configuration(config).build(null)

manager.navigate("destination1")
manager.navigate("destination2")

val queuedSignal = manager.cache?.empty()?.last()

Assert.assertNotNull(queuedSignal)

// validate the signal type
Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged")

// validate the navigation status payload
// https://github.com/TelemetryDeck/KotlinSDK/issues/28
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") },
"TelemetryDeck.Navigation.schemaVersion:1"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") },
"TelemetryDeck.Navigation.identifier:destination1 -> destination2"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") },
"TelemetryDeck.Navigation.sourcePath:destination1"
)
Assert.assertEquals(
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") },
"TelemetryDeck.Navigation.destinationPath:destination2"
)
}
}

open class TestProvider: TelemetryProvider {
open class TestProvider : TelemetryProvider {
var registered = false
override fun register(ctx: Application?, manager: TelemetryManager) {
registered = true
Expand Down

0 comments on commit 02b1bab

Please sign in to comment.