From 0e18423c90b5311ee5e4b7d1984ecdd31d410e3b Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Wed, 14 Aug 2024 11:17:13 -0400 Subject: [PATCH 1/4] Android/JVM - expose constantController --- .../intuit/playerui/android/AndroidPlayer.kt | 11 +++ .../core/constants/ConstantsController.kt | 46 ++++++++++ .../playerui/core/player/HeadlessPlayer.kt | 3 + .../core/player/HeadlessPlayerTest.kt | 90 +++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt diff --git a/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt index 76ab00b2c..c2de7bb8d 100644 --- a/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt @@ -18,6 +18,7 @@ import com.intuit.playerui.core.bridge.Completable import com.intuit.playerui.core.bridge.format import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeConfig import com.intuit.playerui.core.bridge.serialization.format.registerContextualSerializer +import com.intuit.playerui.core.constants.ConstantsController import com.intuit.playerui.core.logger.TapableLogger import com.intuit.playerui.core.player.HeadlessPlayer import com.intuit.playerui.core.player.Player @@ -89,6 +90,16 @@ public class AndroidPlayer private constructor( override val logger: TapableLogger by player::logger + public fun AndroidPlayer.addConstants(data: Map, namespace: String) = player.constantsController.addConstants(data, namespace) + + public fun AndroidPlayer.getConstants(key: Any, namespace: String, fallback: Any? = null) = player.constantsController.getConstants(key, namespace) + + public fun AndroidPlayer.setTemporaryValues(data: Any, namespace: String) = player.constantsController.setTemporaryValues(data, namespace) + + public fun AndroidPlayer.clearTemporaryValues() = player.constantsController.clearTemporaryValues() + + public val constantsController: ConstantsController by player::constantsController + public class Hooks internal constructor(hooks: Player.Hooks) : Player.Hooks by hooks { public class ContextHook : SyncWaterfallHook<(HookContext, Context) -> Context, Context>() { public fun call(context: Context): Context = super.call( diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt new file mode 100644 index 000000000..8479e2e35 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt @@ -0,0 +1,46 @@ +package com.intuit.playerui.core.constants +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import kotlinx.serialization.Serializable + +@Serializable(with = ConstantsController.Serializer::class) +public class ConstantsController(override val node: Node) : NodeWrapper { + /** + * Function to add constants to the providers store + * @param data values to add to the constants store + * @param namespace namespace to add the constants under + */ + public fun addConstants(data: Map, namespace: String) { + node.getInvokable("addConstants")?.invoke(data, namespace) + } + + /** + * Function to retrieve constants from the providers store + * @param key Key used for the store access + * @param namespace namespace values were loaded under (defined in the plugin) + * @param fallback Optional - if key doesn't exist in namespace what to return (will return unknown if not provided) + */ + public fun getConstants(key: Any, namespace: String, fallback: Any? = null): Any? { + return node.getInvokable("getConstants")?.invoke(key, namespace, fallback) + } + + /** + * Function to set values to temporarily override certain keys in the permanent store + * @param data values to override store with + * @param namespace namespace to override + */ + public fun setTemporaryValues(data: Any, namespace: String) { + node.getInvokable("setTemporaryValues")?.invoke(data, namespace) + } + + /** + * Clears any temporary values that were previously set + */ + public fun clearTemporaryValues() { + node.getInvokable("clearTemporaryValues")?.invoke() + } + + internal object Serializer : NodeWrapperSerializer(::ConstantsController) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt index 04d239891..d7a7a4aa7 100644 --- a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt @@ -11,6 +11,7 @@ import com.intuit.playerui.core.bridge.runtime.ScriptContext import com.intuit.playerui.core.bridge.runtime.add import com.intuit.playerui.core.bridge.runtime.runtimeFactory import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.constants.ConstantsController import com.intuit.playerui.core.experimental.ExperimentalPlayerApi import com.intuit.playerui.core.logger.TapableLogger import com.intuit.playerui.core.player.HeadlessPlayer.Companion.bundledSource @@ -77,6 +78,8 @@ public constructor( override val hooks: Hooks by NodeSerializableField(Hooks.serializer(), NodeSerializableField.CacheStrategy.Full) + public val constantsController: ConstantsController by NodeSerializableField(ConstantsController.serializer(), NodeSerializableField.CacheStrategy.Full) + override val state: PlayerFlowState get() = if (player.isReleased()) { ReleasedState } else { diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt index 74d043093..5391228ab 100644 --- a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt @@ -455,4 +455,94 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { assertTrue(player.state is ErrorState) } + + @TestTemplate + fun `test constantsController get and set`() = runBlockingTest { + player.start(simpleFlowString) + val constantsController = player.constantsController + + val data = mapOf( + "firstname" to "john", + "lastname" to "doe", + "favorite" to mapOf("color" to "red"), + "age" to 1, + ) + + constantsController.addConstants(data = data, namespace = "constants") + + val firstname = constantsController.getConstants(key = "firstname", namespace = "constants") + assertEquals("john", firstname) + + val middleName = constantsController.getConstants(key = "middlename", namespace = "constants") + assertNull(middleName) + + val middleNameSafe = constantsController.getConstants(key = "middlename", namespace = "constants", fallback = "A") + assertEquals("A", middleNameSafe) + + val favoriteColor = constantsController.getConstants(key = "favorite.color", namespace = "constants") + assertEquals("red", favoriteColor) + + val age = constantsController.getConstants(key = "age", namespace = "constants") + assertEquals(1, age) + + val nonExistentNamespace = constantsController.getConstants(key = "test", namespace = "foo") + assertNull(nonExistentNamespace) + + val nonExistentNamespaceWithFallback = constantsController.getConstants(key = "test", namespace = "foo", fallback = "B") + assertEquals("B", nonExistentNamespaceWithFallback) + + // Test and make sure keys override properly + val newData = mapOf( + "favorite" to mapOf("color" to "blue"), + ) + + constantsController.addConstants(data = newData, namespace = "constants") + + val newFavoriteColor = constantsController.getConstants(key = "favorite.color", namespace = "constants") + assertEquals("blue", newFavoriteColor) + } + + @TestTemplate + fun `test constantsController temp override functionality`() = runBlockingTest { + player.start(simpleFlowString) + val constantsController = player.constantsController + + // Add initial constants + val data = mapOf( + "firstname" to "john", + "lastname" to "doe", + "favorite" to mapOf("color" to "red"), + ) + constantsController.addConstants(data = data, namespace = "constants") + + // Override with temporary values + val tempData = mapOf( + "firstname" to "jane", + "favorite" to mapOf("color" to "blue"), + ) + constantsController.setTemporaryValues(data = tempData, namespace = "constants") + + // Test temporary override + val firstnameTemp = constantsController.getConstants(key = "firstname", namespace = "constants") + assertEquals("jane", firstnameTemp) + + val favoriteColorTemp = constantsController.getConstants(key = "favorite.color", namespace = "constants") + assertEquals("blue", favoriteColorTemp) + + // Test fallback to original values when temporary values are not present + val lastnameTemp = constantsController.getConstants(key = "lastname", namespace = "constants") + assertEquals("doe", lastnameTemp) + + // Reset temp and values should be the same as the original data + constantsController.clearTemporaryValues() + + val firstname = constantsController.getConstants(key = "firstname", namespace = "constants") + assertEquals("john", firstname) + + val favoriteColor = constantsController.getConstants(key = "favorite.color", namespace = "constants") + assertEquals("red", favoriteColor) + + val lastname = constantsController.getConstants(key = "lastname", namespace = "constants") + assertEquals("doe", lastname) + } } From d03eb2eb4a72798e14504daa57299bf5d690c27f Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Wed, 14 Aug 2024 11:43:36 -0400 Subject: [PATCH 2/4] fix test build --- .../com/intuit/playerui/core/constants/ConstantsController.kt | 2 +- .../kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt | 2 +- .../src/main/kotlin/com/intuit/playerui/core/player/Player.kt | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt index 8479e2e35..9a22a4417 100644 --- a/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt @@ -22,7 +22,7 @@ public class ConstantsController(override val node: Node) : NodeWrapper { * @param namespace namespace values were loaded under (defined in the plugin) * @param fallback Optional - if key doesn't exist in namespace what to return (will return unknown if not provided) */ - public fun getConstants(key: Any, namespace: String, fallback: Any? = null): Any? { + public fun getConstants(key: String, namespace: String, fallback: Any? = null): Any? { return node.getInvokable("getConstants")?.invoke(key, namespace, fallback) } diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt index d7a7a4aa7..e49ceaed2 100644 --- a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt @@ -78,7 +78,7 @@ public constructor( override val hooks: Hooks by NodeSerializableField(Hooks.serializer(), NodeSerializableField.CacheStrategy.Full) - public val constantsController: ConstantsController by NodeSerializableField(ConstantsController.serializer(), NodeSerializableField.CacheStrategy.Full) + override val constantsController: ConstantsController by NodeSerializableField(ConstantsController.serializer(), NodeSerializableField.CacheStrategy.Full) override val state: PlayerFlowState get() = if (player.isReleased()) { ReleasedState diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt index 3cb518777..e13bfb750 100644 --- a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt @@ -6,6 +6,7 @@ import com.intuit.playerui.core.bridge.NodeWrapper import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.constants.ConstantsController import com.intuit.playerui.core.data.DataController import com.intuit.playerui.core.experimental.ExperimentalPlayerApi import com.intuit.playerui.core.expressions.ExpressionController @@ -33,6 +34,8 @@ public abstract class Player : Pluggable { public abstract val logger: TapableLogger + public abstract val constantsController: ConstantsController + /** * Expose [PlayerHooks] which allow consumers to plug * into the flow and subscribe to different events. From cf8eac4f83320b40a11ccc1489adf25ed19e2292 Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Wed, 14 Aug 2024 12:28:39 -0400 Subject: [PATCH 3/4] remove start player in tests --- .../java/com/intuit/playerui/android/AndroidPlayer.kt | 10 ---------- .../intuit/playerui/core/player/HeadlessPlayerTest.kt | 2 -- 2 files changed, 12 deletions(-) diff --git a/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt index c2de7bb8d..cad5c5948 100644 --- a/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt @@ -90,16 +90,6 @@ public class AndroidPlayer private constructor( override val logger: TapableLogger by player::logger - public fun AndroidPlayer.addConstants(data: Map, namespace: String) = player.constantsController.addConstants(data, namespace) - - public fun AndroidPlayer.getConstants(key: Any, namespace: String, fallback: Any? = null) = player.constantsController.getConstants(key, namespace) - - public fun AndroidPlayer.setTemporaryValues(data: Any, namespace: String) = player.constantsController.setTemporaryValues(data, namespace) - - public fun AndroidPlayer.clearTemporaryValues() = player.constantsController.clearTemporaryValues() - - public val constantsController: ConstantsController by player::constantsController - public class Hooks internal constructor(hooks: Player.Hooks) : Player.Hooks by hooks { public class ContextHook : SyncWaterfallHook<(HookContext, Context) -> Context, Context>() { public fun call(context: Context): Context = super.call( diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt index 5391228ab..7bf1f925f 100644 --- a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt @@ -458,7 +458,6 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { @TestTemplate fun `test constantsController get and set`() = runBlockingTest { - player.start(simpleFlowString) val constantsController = player.constantsController val data = mapOf( @@ -504,7 +503,6 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { @TestTemplate fun `test constantsController temp override functionality`() = runBlockingTest { - player.start(simpleFlowString) val constantsController = player.constantsController // Add initial constants From 16dbb918310d9bbc35e6e460340e1c0a81dace63 Mon Sep 17 00:00:00 2001 From: Chloe Han Date: Wed, 14 Aug 2024 12:59:46 -0400 Subject: [PATCH 4/4] fix test build --- .../src/main/java/com/intuit/playerui/android/AndroidPlayer.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt index cad5c5948..dfae5a8b0 100644 --- a/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt @@ -90,6 +90,8 @@ public class AndroidPlayer private constructor( override val logger: TapableLogger by player::logger + override val constantsController: ConstantsController by player::constantsController + public class Hooks internal constructor(hooks: Player.Hooks) : Player.Hooks by hooks { public class ContextHook : SyncWaterfallHook<(HookContext, Context) -> Context, Context>() { public fun call(context: Context): Context = super.call(