Skip to content

Commit 02b1bab

Browse files
authored
Merge pull request #29 from TelemetryDeck/send-navigation-signals
Add support for navigation signals
2 parents 08fe243 + 4dd42df commit 02b1bab

File tree

9 files changed

+249
-26
lines changed

9 files changed

+249
-26
lines changed

.idea/.name

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.telemetrydeck.sdk
2+
3+
interface NavigationStatus {
4+
/**
5+
* Apply the provided path as a visited destination.
6+
*/
7+
fun applyDestination(path: String)
8+
9+
/**
10+
* Returns the last destination path or an empty string if none has been provided.
11+
*/
12+
fun getLastDestination(): String
13+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.telemetrydeck.sdk
2+
3+
class MemoryNavigationStatus(private var previousNavigationPath: String? = null) :
4+
NavigationStatus {
5+
6+
/**
7+
* Apply the provided path as a visited destination.
8+
*/
9+
override fun applyDestination(path: String) {
10+
previousNavigationPath = path
11+
}
12+
13+
/**
14+
* Returns the last destination path or an empty string if none has been provided.
15+
*/
16+
override fun getLastDestination(): String {
17+
return previousNavigationPath ?: ""
18+
}
19+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.telemetrydeck.sdk
2+
3+
enum class PayloadParameters(val type: String) {
4+
TelemetryDeckNavigationSchemaVersion("TelemetryDeck.Navigation.schemaVersion"),
5+
TelemetryDeckNavigationIdentifier("TelemetryDeck.Navigation.identifier"),
6+
TelemetryDeckNavigationSourcePath("TelemetryDeck.Navigation.sourcePath"),
7+
TelemetryDeckNavigationDestinationPath("TelemetryDeck.Navigation.destinationPath"),
8+
}

lib/src/main/java/com/telemetrydeck/sdk/PersistentSignalCache.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package com.telemetrydeck.sdk
22

3-
import kotlinx.serialization.json.Json
4-
import kotlinx.serialization.decodeFromString
53
import kotlinx.serialization.encodeToString
4+
import kotlinx.serialization.json.Json
65
import java.io.File
7-
import java.lang.Exception
86

9-
class PersistentSignalCache(private var signalQueue: MutableList<Signal> = mutableListOf()): SignalCache {
7+
class PersistentSignalCache(private var signalQueue: MutableList<Signal> = mutableListOf()) :
8+
SignalCache {
109
val cacheFileName: String = "telemetrydeck.json"
11-
var file: File? = null
10+
private var file: File? = null
1211

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

59-
fun saveSignals() {
58+
private fun saveSignals() {
6059
file?.createNewFile()
6160
val json = Json.encodeToString(signalQueue)
6261
file?.writeText(json)
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package com.telemetrydeck.sdk
22

33
enum class SignalType(val type: String) {
4-
ActivityCreated("ActivityCreated"),
5-
ActivityStarted("ActivityStarted"),
6-
ActivityResumed("ActivityResumed"),
7-
ActivityPaused("ActivityPaused"),
8-
ActivityStopped("ActivityStopped"),
9-
ActivitySaveInstanceState("ActivitySaveInstanceState"),
10-
ActivityDestroyed("ActivityDestroyed"),
11-
AppBackground("AppBackground"),
12-
AppForeground("AppForeground"),
13-
NewSessionBegan("NewSessionBegan"),
4+
ActivityCreated("ActivityCreated"), ActivityStarted("ActivityStarted"), ActivityResumed("ActivityResumed"), ActivityPaused(
5+
"ActivityPaused"
6+
),
7+
ActivityStopped("ActivityStopped"), ActivitySaveInstanceState("ActivitySaveInstanceState"), ActivityDestroyed(
8+
"ActivityDestroyed"
9+
),
10+
AppBackground("AppBackground"), AppForeground("AppForeground"), NewSessionBegan("NewSessionBegan"), TelemetryDeckNavigationPathChanged(
11+
"TelemetryDeck.Navigation.pathChanged"
12+
)
1413
}

lib/src/main/java/com/telemetrydeck/sdk/TelemetryManager.kt

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import android.content.pm.ApplicationInfo
66
import java.lang.ref.WeakReference
77
import java.net.URL
88
import java.security.MessageDigest
9-
import java.util.*
9+
import java.util.UUID
1010
import kotlin.Result.Companion.failure
1111
import kotlin.Result.Companion.success
1212

@@ -20,6 +20,7 @@ class TelemetryManager(
2020

2121
var cache: SignalCache? = null
2222
var logger: DebugLogger? = null
23+
private val navigationStatus: NavigationStatus = MemoryNavigationStatus()
2324

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

49+
override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) {
50+
navigationStatus.applyDestination(destinationPath)
51+
52+
val payload: Map<String, String> = mapOf(
53+
PayloadParameters.TelemetryDeckNavigationSchemaVersion.type to "1",
54+
PayloadParameters.TelemetryDeckNavigationIdentifier.type to "$sourcePath -> $destinationPath",
55+
PayloadParameters.TelemetryDeckNavigationSourcePath.type to sourcePath,
56+
PayloadParameters.TelemetryDeckNavigationDestinationPath.type to destinationPath
57+
)
58+
59+
queue(SignalType.TelemetryDeckNavigationPathChanged, clientUser, payload)
60+
}
61+
62+
override fun navigate(destinationPath: String, clientUser: String?) {
63+
navigate(navigationStatus.getLastDestination(), destinationPath, clientUser)
64+
}
65+
4866
override suspend fun send(
4967
signalType: String,
5068
clientUser: String?,
@@ -79,7 +97,7 @@ class TelemetryManager(
7997
)
8098
client.send(signals)
8199
success(Unit)
82-
} catch(e: Exception) {
100+
} catch (e: Exception) {
83101
logger?.error("Failed to send signals due to an error ${e} ${e.stackTraceToString()}")
84102
failure(e)
85103
}
@@ -104,7 +122,7 @@ class TelemetryManager(
104122
enrichedPayload = provider.enrich(signalType, clientUser, enrichedPayload)
105123
}
106124
val userValue = clientUser ?: configuration.defaultUser ?: ""
107-
val userValueWithSalt = userValue +( configuration.salt ?: "")
125+
val userValueWithSalt = userValue + (configuration.salt ?: "")
108126
val hashedUser = hashString(userValue, "SHA-256")
109127
val payload = SignalPayload(additionalPayload = enrichedPayload)
110128
val signal = Signal(
@@ -207,6 +225,14 @@ class TelemetryManager(
207225
getInstance()?.queue(signalType, clientUser, additionalPayload)
208226
}
209227

228+
override fun navigate(sourcePath: String, destinationPath: String, clientUser: String?) {
229+
getInstance()?.navigate(sourcePath, destinationPath, clientUser = clientUser)
230+
}
231+
232+
override fun navigate(destinationPath: String, clientUser: String?) {
233+
getInstance()?.navigate(destinationPath, clientUser = clientUser)
234+
}
235+
210236
override suspend fun send(
211237
signalType: String,
212238
clientUser: String?,
@@ -364,7 +390,7 @@ class TelemetryManager(
364390
}
365391
}
366392

367-
var salt = this.salt
393+
val salt = this.salt
368394
if (salt != null) {
369395
config.salt = salt
370396
}

lib/src/main/java/com/telemetrydeck/sdk/TelemetryManagerSignals.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.telemetrydeck.sdk
22

3-
import java.util.*
3+
import java.util.UUID
44

55
interface TelemetryManagerSignals {
66

@@ -38,6 +38,20 @@ interface TelemetryManagerSignals {
3838
additionalPayload: Map<String, String> = emptyMap()
3939
)
4040

41+
/**
42+
* Send a signal that represents a navigation event with a source and a destination.
43+
*
44+
* @see <a href="https://telemetrydeck.com/docs/articles/navigation-signals/">Navigation Signals</a>
45+
* */
46+
fun navigate(sourcePath: String, destinationPath: String, clientUser: String? = null)
47+
48+
/**
49+
* Send a signal that represents a navigation event with a destination and a default source.
50+
*
51+
* @see <a href="https://telemetrydeck.com/docs/articles/navigation-signals/">Navigation Signals</a>
52+
* */
53+
fun navigate(destinationPath: String, clientUser: String? = null)
54+
4155

4256
/**
4357
* Send a signal immediately

lib/src/test/java/com/telemetrydeck/sdk/TelemetryManagerTest.kt

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import org.junit.Assert
66
import org.junit.Rule
77
import org.junit.Test
88
import java.net.URL
9-
import java.util.*
9+
import java.util.Calendar
10+
import java.util.UUID
1011

1112
class TelemetryManagerTest {
1213

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

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

@@ -27,7 +28,10 @@ class TelemetryManagerTest {
2728
Assert.assertEquals(UUID.fromString(appID), queuedSignal!!.appID)
2829
Assert.assertEquals(config.sessionID, UUID.fromString(queuedSignal.sessionID))
2930
Assert.assertEquals("type", queuedSignal.type)
30-
Assert.assertEquals("6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149", queuedSignal.clientUser)
31+
Assert.assertEquals(
32+
"6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149",
33+
queuedSignal.clientUser
34+
)
3135
Assert.assertEquals("false", queuedSignal.isTestMode)
3236
}
3337

@@ -246,9 +250,150 @@ class TelemetryManagerTest {
246250
Assert.assertEquals(1, filteredSignals.count())
247251
Assert.assertEquals("okSignal", filteredSignals[0].type)
248252
}
253+
254+
@Test
255+
fun telemetryManager_navigate_source_destination_sets_default_parameters() {
256+
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
257+
val manager = TelemetryManager.Builder().configuration(config).build(null)
258+
259+
manager.navigate("source", "destination")
260+
261+
val queuedSignal = manager.cache?.empty()?.first()
262+
263+
Assert.assertNotNull(queuedSignal)
264+
265+
// validate the signal type
266+
Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged")
267+
268+
// validate the navigation status payload
269+
// https://github.com/TelemetryDeck/KotlinSDK/issues/28
270+
Assert.assertEquals(
271+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") },
272+
"TelemetryDeck.Navigation.schemaVersion:1"
273+
)
274+
Assert.assertEquals(
275+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") },
276+
"TelemetryDeck.Navigation.identifier:source -> destination"
277+
)
278+
Assert.assertEquals(
279+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") },
280+
"TelemetryDeck.Navigation.sourcePath:source"
281+
)
282+
Assert.assertEquals(
283+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") },
284+
"TelemetryDeck.Navigation.destinationPath:destination"
285+
)
286+
}
287+
288+
@Test
289+
fun telemetryManager_navigate_source_destination_sets_clientUser() {
290+
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
291+
config.defaultUser = "user"
292+
val manager = TelemetryManager.Builder().configuration(config).build(null)
293+
294+
manager.navigate("source", "destination", "clientUser")
295+
296+
val queuedSignal = manager.cache?.empty()?.first()
297+
298+
Assert.assertNotNull(queuedSignal)
299+
300+
// validate that the provided user was used and not default
301+
Assert.assertEquals(
302+
queuedSignal?.clientUser,
303+
"6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149"
304+
)
305+
}
306+
307+
@Test
308+
fun telemetryManager_navigate_source_destination_uses_default_user() {
309+
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
310+
config.defaultUser = "clientUser"
311+
val manager = TelemetryManager.Builder().configuration(config).build(null)
312+
313+
manager.navigate("source", "destination")
314+
315+
val queuedSignal = manager.cache?.empty()?.first()
316+
317+
Assert.assertNotNull(queuedSignal)
318+
319+
// validate that the default user was used
320+
Assert.assertEquals(
321+
queuedSignal?.clientUser,
322+
"6721870580401922549fe8fdb09a064dba5b8792fa018d3bd9ffa90fe37a0149"
323+
)
324+
}
325+
326+
@Test
327+
fun telemetryManager_navigate_destination_no_previous_source() {
328+
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
329+
val manager = TelemetryManager.Builder().configuration(config).build(null)
330+
331+
manager.navigate("destination")
332+
333+
val queuedSignal = manager.cache?.empty()?.first()
334+
335+
Assert.assertNotNull(queuedSignal)
336+
337+
// validate the signal type
338+
Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged")
339+
340+
// validate the navigation status payload
341+
// https://github.com/TelemetryDeck/KotlinSDK/issues/28
342+
Assert.assertEquals(
343+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") },
344+
"TelemetryDeck.Navigation.schemaVersion:1"
345+
)
346+
Assert.assertEquals(
347+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") },
348+
"TelemetryDeck.Navigation.identifier: -> destination"
349+
)
350+
Assert.assertEquals(
351+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") },
352+
"TelemetryDeck.Navigation.sourcePath:"
353+
)
354+
Assert.assertEquals(
355+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") },
356+
"TelemetryDeck.Navigation.destinationPath:destination"
357+
)
358+
}
359+
360+
@Test
361+
fun telemetryManager_navigate_destination_uses_previous_destination_as_source() {
362+
val config = TelemetryManagerConfiguration("32CB6574-6732-4238-879F-582FEBEB6536")
363+
val manager = TelemetryManager.Builder().configuration(config).build(null)
364+
365+
manager.navigate("destination1")
366+
manager.navigate("destination2")
367+
368+
val queuedSignal = manager.cache?.empty()?.last()
369+
370+
Assert.assertNotNull(queuedSignal)
371+
372+
// validate the signal type
373+
Assert.assertEquals(queuedSignal?.type, "TelemetryDeck.Navigation.pathChanged")
374+
375+
// validate the navigation status payload
376+
// https://github.com/TelemetryDeck/KotlinSDK/issues/28
377+
Assert.assertEquals(
378+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.schemaVersion") },
379+
"TelemetryDeck.Navigation.schemaVersion:1"
380+
)
381+
Assert.assertEquals(
382+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.identifier") },
383+
"TelemetryDeck.Navigation.identifier:destination1 -> destination2"
384+
)
385+
Assert.assertEquals(
386+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.sourcePath") },
387+
"TelemetryDeck.Navigation.sourcePath:destination1"
388+
)
389+
Assert.assertEquals(
390+
queuedSignal?.payload?.single { it.startsWith("TelemetryDeck.Navigation.destinationPath") },
391+
"TelemetryDeck.Navigation.destinationPath:destination2"
392+
)
393+
}
249394
}
250395

251-
open class TestProvider: TelemetryProvider {
396+
open class TestProvider : TelemetryProvider {
252397
var registered = false
253398
override fun register(ctx: Application?, manager: TelemetryManager) {
254399
registered = true

0 commit comments

Comments
 (0)