From 4e7c25e5da73e0ba7eb3cfe0d964830a67d9726d Mon Sep 17 00:00:00 2001 From: Na Lee Ha Date: Mon, 12 Aug 2024 16:29:12 +0100 Subject: [PATCH] #1434 adding test for type ahead request at a view port level. Data is loaded into table at initialisation --- .../vuu/person/manual/PersonProvider.java | 2 +- .../ViewPortTypeAheadRpcHandler.scala | 54 +++++++++++++++++ .../finos/vuu/net/rpc/DefaultRpcHandler.scala | 6 +- .../finos/vuu/viewport/ViewPortAction.scala | 6 +- .../org/finos/vuu/net/TestModuleBuilder.scala | 17 ++++-- .../org/finos/vuu/net/TestProvider.scala | 39 ++++++++++++ .../org/finos/vuu/net/WebSocketApiTest.scala | 60 +++++++++++++++++-- 7 files changed, 167 insertions(+), 17 deletions(-) create mode 100644 vuu/src/main/scala/org/finos/vuu/core/module/typeahead/ViewPortTypeAheadRpcHandler.scala create mode 100644 vuu/src/test/scala/org/finos/vuu/net/TestProvider.scala diff --git a/example/main-java/src/main/java/org/finos/vuu/person/manual/PersonProvider.java b/example/main-java/src/main/java/org/finos/vuu/person/manual/PersonProvider.java index beafbe857..8b11aed83 100644 --- a/example/main-java/src/main/java/org/finos/vuu/person/manual/PersonProvider.java +++ b/example/main-java/src/main/java/org/finos/vuu/person/manual/PersonProvider.java @@ -47,7 +47,7 @@ public void doDestroy() { @Override public String lifecycleId() { - return null; + return "PersonProvider"; } @Override diff --git a/vuu/src/main/scala/org/finos/vuu/core/module/typeahead/ViewPortTypeAheadRpcHandler.scala b/vuu/src/main/scala/org/finos/vuu/core/module/typeahead/ViewPortTypeAheadRpcHandler.scala new file mode 100644 index 000000000..d3747986b --- /dev/null +++ b/vuu/src/main/scala/org/finos/vuu/core/module/typeahead/ViewPortTypeAheadRpcHandler.scala @@ -0,0 +1,54 @@ +package org.finos.vuu.core.module.typeahead + +import com.typesafe.scalalogging.StrictLogging +import org.finos.vuu.core.table.{DataTable, TableContainer} +import org.finos.vuu.net.RequestContext +import org.finos.vuu.net.rpc.{DefaultRpcHandler, RpcMethodCallResult, RpcMethodSuccess, RpcParams} + +class ViewPortTypeAheadRpcHandler(tableContainer: TableContainer) extends DefaultRpcHandler with StrictLogging { + + this.registerRpc("getUniqueFieldValues", params => processGetUniqueFieldValuesRequest(params)) + this.registerRpc("getUniqueFieldValuesStartingWith", params => processGetUniqueFieldValuesStartWithRequest(params)) + + def processGetUniqueFieldValuesRequest(params: RpcParams): RpcMethodCallResult = { + val values = getUniqueFieldValues( + params.namedParams("table").toString, //how to report error when expected param missing or fail to cast to right type + params.namedParams("module").toString, + params.namedParams("column").toString, + null //todo what to do about request context + ) + RpcMethodSuccess(values) + } + + def processGetUniqueFieldValuesStartWithRequest(params: RpcParams): RpcMethodCallResult = { + val values = getUniqueFieldValuesStartingWith( + params.namedParams("table").toString, //how to report error when expected param missing or fail to cast to right type + params.namedParams("module").toString, + params.namedParams("column").toString, + params.namedParams("starts").toString, + null //todo what to do about request context + ) + RpcMethodSuccess(values) //how to control what viewport action to trigger? + } + + def getUniqueFieldValues(tableName: String, moduleName: String, column: String, ctx: RequestContext): Array[String] = { + tableContainer.getTable(tableName) match { + case dataTable: DataTable => + val columValueProvider = dataTable.getColumnValueProvider + columValueProvider.getUniqueValues(column) + case null => + throw new Exception("Could not find table by name:" + tableName) + } + } + + def getUniqueFieldValuesStartingWith(tableName: String, moduleName: String, column: String, starts: String, ctx: RequestContext): Array[String] = { + tableContainer.getTable(tableName) match { + case dataTable: DataTable => + val columValueProvider = dataTable.getColumnValueProvider + columValueProvider.getUniqueValuesStartingWith(column, starts) + case null => + throw new Exception("Could not find table by name:" + tableName) + } + } + +} diff --git a/vuu/src/main/scala/org/finos/vuu/net/rpc/DefaultRpcHandler.scala b/vuu/src/main/scala/org/finos/vuu/net/rpc/DefaultRpcHandler.scala index a40268238..fbd3e82f8 100644 --- a/vuu/src/main/scala/org/finos/vuu/net/rpc/DefaultRpcHandler.scala +++ b/vuu/src/main/scala/org/finos/vuu/net/rpc/DefaultRpcHandler.scala @@ -2,7 +2,7 @@ package org.finos.vuu.net.rpc import com.typesafe.scalalogging.StrictLogging import org.finos.vuu.net.{Error, RequestContext, RpcCall, RpcResponse, ViewServerMessage, VsMsg} -import org.finos.vuu.viewport.{ViewPortAction, ViewPortRpcFailure, ViewPortRpcSuccess} +import org.finos.vuu.viewport.{DisplayResultAction, ViewPortAction, ViewPortRpcFailure, ViewPortRpcSuccess} import java.util.concurrent.ConcurrentHashMap @@ -30,7 +30,9 @@ class DefaultRpcHandler extends RpcHandler with StrictLogging { override def processViewPortRpcCall(methodName: String, params: Array[Any], namedParams: Map[String, Any])(ctx: RequestContext): ViewPortAction = { val result = processRpcMethodHandler(methodName, params, namedParams, ctx) result match { - case RpcMethodSuccess(_) => ViewPortRpcSuccess() + case RpcMethodSuccess(result) => + if(result == null) ViewPortRpcSuccess() + else DisplayResultAction(result) case _: RpcMethodFailure => ViewPortRpcFailure(s"Exception occurred calling rpc $methodName") } } diff --git a/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortAction.scala b/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortAction.scala index effbcfb09..dd5216935 100644 --- a/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortAction.scala +++ b/vuu/src/main/scala/org/finos/vuu/viewport/ViewPortAction.scala @@ -12,8 +12,9 @@ trait ViewPortAction {} case class NoAction() extends ViewPortAction case class OpenDialogViewPortAction(table: ViewPortTable, renderComponent: String = "grid") extends ViewPortAction case class CloseDialogViewPortAction(vpId: String) extends ViewPortAction -case class ViewPortRpcSuccess() extends ViewPortAction {} -case class ViewPortRpcFailure(msg: String) extends ViewPortAction {} +case class DisplayResultAction(result: Any) extends ViewPortAction +case class ViewPortRpcSuccess() extends ViewPortAction +case class ViewPortRpcFailure(msg: String) extends ViewPortAction case class ViewPortCreateSuccess(key:String) extends ViewPortAction @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @@ -21,6 +22,7 @@ case class ViewPortCreateSuccess(key:String) extends ViewPortAction new Type(value = classOf[OpenDialogViewPortAction], name = "OPEN_DIALOG_ACTION"), new Type(value = classOf[CloseDialogViewPortAction], name = "CLOSE_DIALOG_ACTION"), new Type(value = classOf[NoAction], name = "NO_ACTION"), + new Type(value = classOf[DisplayResultAction], name = "DISPLAY_RESULT_ACTION"), new Type(value = classOf[ViewPortEditSuccess], name = "VP_EDIT_SUCCESS"), new Type(value = classOf[ViewPortEditFailure], name = "VP_EDIT_FAILURE"), new Type(value = classOf[ViewPortRpcSuccess], name = "VP_RPC_SUCCESS"), diff --git a/vuu/src/test/scala/org/finos/vuu/net/TestModuleBuilder.scala b/vuu/src/test/scala/org/finos/vuu/net/TestModuleBuilder.scala index cc120a9fe..68ed406bf 100644 --- a/vuu/src/test/scala/org/finos/vuu/net/TestModuleBuilder.scala +++ b/vuu/src/test/scala/org/finos/vuu/net/TestModuleBuilder.scala @@ -3,8 +3,10 @@ package org.finos.vuu.net import org.finos.toolbox.lifecycle.LifecycleContainer import org.finos.toolbox.time.Clock import org.finos.vuu.api.{TableDef, ViewPortDef} +import org.finos.vuu.core.IVuuServer import org.finos.vuu.core.module.{ModuleFactory, ModuleFactoryNode, TableDefContainer, ViewServerModule} -import org.finos.vuu.provider.MockProvider +import org.finos.vuu.core.table.{DataTable, TableContainer} +import org.finos.vuu.provider.{MockProvider, Provider, ProviderContainer} object TestExtension { @@ -17,11 +19,15 @@ object TestExtension { ) } - def addTableForTest(tableDef: TableDef, viewPortDef: ViewPortDef)(implicit clock: Clock, lifecycle: LifecycleContainer): ModuleFactoryNode = { + def addTableForTest( + tableDef: TableDef, + ViewPortDefFactory: (DataTable, Provider, ProviderContainer, TableContainer) => ViewPortDef, + providerFactory: (DataTable, IVuuServer) => Provider + )(implicit clock: Clock, lifecycle: LifecycleContainer): ModuleFactoryNode = { moduleFactoryNode.addTable( tableDef, - (table, _) => new MockProvider(table), - (_, _, _, _) => viewPortDef + providerFactory, + ViewPortDefFactory ) } @@ -29,8 +35,8 @@ object TestExtension { } class TestModuleBuilder(moduleName:String)(implicit clock: Clock, lifecycle: LifecycleContainer, tableDefContainer: TableDefContainer){ - private var moduleFactory = ModuleFactory.withNamespace(moduleName) + def withTable(tableDef: TableDef, viewPortDef: ViewPortDef): TestModuleBuilder = { moduleFactory = moduleFactory.addTable( tableDef, @@ -40,7 +46,6 @@ class TestModuleBuilder(moduleName:String)(implicit clock: Clock, lifecycle: Lif this } - def build(moduleName: String, tableDef: TableDef, viewPortDef: ViewPortDef)(implicit clock: Clock, lifecycle: LifecycleContainer, tableDefContainer: TableDefContainer): ViewServerModule = ModuleFactory.withNamespace(moduleName) .addTable( diff --git a/vuu/src/test/scala/org/finos/vuu/net/TestProvider.scala b/vuu/src/test/scala/org/finos/vuu/net/TestProvider.scala new file mode 100644 index 000000000..e0d81a9bd --- /dev/null +++ b/vuu/src/test/scala/org/finos/vuu/net/TestProvider.scala @@ -0,0 +1,39 @@ +package org.finos.vuu.net + +import com.typesafe.scalalogging.StrictLogging +import org.finos.toolbox.time.Clock +import org.finos.vuu.core.table.{DataTable, RowWithData} +import org.finos.vuu.provider.Provider + +class TestProvider(table: DataTable, fakeDataSource: FakeDataSource)(implicit clock: Clock) extends Provider with StrictLogging { + + override def subscribe(key: String): Unit = {} + + override def doStart(): Unit = { + logger.info(s"Test Provider for ${table.name}- Starting") + } + + override def doStop(): Unit = { + logger.info(s"Test Provider for ${table.name}- Stopping") + } + + override def doInitialize(): Unit = { + logger.info(s"Test Provider for ${table.name}- Initialising") + fakeDataSource.get() + .foreach(row => { + table.processUpdate(row._1, RowWithData(row._1, row._2), clock.now()) + }) + } + + override def doDestroy(): Unit = {} + + override val lifecycleId: String = s"TestProvider ${table.name}" +} +class FakeDataSource(rows: Map[String, Map[String, Any]]) { + type RowKey = String + type ColumnName = String + + def get(): Map[RowKey, Map[ColumnName, Any]] = { + rows + } +} \ No newline at end of file diff --git a/vuu/src/test/scala/org/finos/vuu/net/WebSocketApiTest.scala b/vuu/src/test/scala/org/finos/vuu/net/WebSocketApiTest.scala index 8b9f5d08c..bbde6897e 100644 --- a/vuu/src/test/scala/org/finos/vuu/net/WebSocketApiTest.scala +++ b/vuu/src/test/scala/org/finos/vuu/net/WebSocketApiTest.scala @@ -3,15 +3,18 @@ package org.finos.vuu.net import org.finos.toolbox.jmx.{MetricsProvider, MetricsProviderImpl} import org.finos.toolbox.lifecycle.LifecycleContainer import org.finos.toolbox.time.{Clock, DefaultClock} -import org.finos.vuu.api.{ColumnBuilder, NoRpcHandler, TableDef, ViewPortDef} +import org.finos.vuu.api.{ColumnBuilder, TableDef, ViewPortDef} import org.finos.vuu.core._ +import org.finos.vuu.core.module.typeahead.ViewPortTypeAheadRpcHandler import org.finos.vuu.core.module.{ModuleFactory, TableDefContainer, ViewServerModule} +import org.finos.vuu.core.table.{DataTable, TableContainer} import org.finos.vuu.net.TestExtension.ModuleFactoryExtension import org.finos.vuu.net.auth.AlwaysHappyAuthenticator import org.finos.vuu.net.http.VuuHttp2ServerOptions import org.finos.vuu.net.json.JsonVsSerializer import org.finos.vuu.net.ws.WebSocketClient -import org.finos.vuu.viewport.ViewPortTable +import org.finos.vuu.provider.{Provider, ProviderContainer} +import org.finos.vuu.viewport.{DisplayResultAction, ViewPortRange, ViewPortTable} import org.scalatest.featurespec.AnyFeatureSpec import org.scalatest.matchers.should.Matchers import org.scalatest.{BeforeAndAfterAll, GivenWhenThen} @@ -42,7 +45,6 @@ class WebSocketApiTest extends AnyFeatureSpec with BeforeAndAfterAll with GivenW def testStartUp(): TestVuuClient = { implicit val metrics: MetricsProvider = new MetricsProviderImpl - implicit val tableDefContainer: TableDefContainer = new TableDefContainer(Map()) lifecycle.autoShutdownHook() @@ -97,15 +99,24 @@ class WebSocketApiTest extends AnyFeatureSpec with BeforeAndAfterAll with GivenW .addInt("Account") .build() ) - val viewPortDef = ViewPortDef( + + val viewPortDefFactory = (table: DataTable, provider: Provider, providerContainer: ProviderContainer, tableContainer: TableContainer) => + ViewPortDef( columns = new ColumnBuilder() .addString("Id") .addInt("Account") .build(), - service = NoRpcHandler + service = new ViewPortTypeAheadRpcHandler(tableContainer) ) + val dataSource = new FakeDataSource(Map( + "row1" -> Map("Id" -> "row1", "Name" -> "Becky Thatcher", "Account" -> 1235), + "row2" -> Map("Id" -> "row2", "Name" -> "Tom Sawyer", "Account" -> 45321), + "row3" -> Map("Id" -> "row3", "Name" -> "Huckleberry Finn", "Account" -> 89564), + )) + + val providerFactory = (table: DataTable, vuuServer: IVuuServer) => new TestProvider(table, dataSource) val tableDef2 = TableDef( name = "TableMetaDefaultVPTest", keyField = "Id", @@ -116,7 +127,7 @@ class WebSocketApiTest extends AnyFeatureSpec with BeforeAndAfterAll with GivenW ) ModuleFactory.withNamespace("TEST") - .addTableForTest(tableDef, viewPortDef) + .addTableForTest(tableDef, viewPortDefFactory, providerFactory) .addTableForTest(tableDef2) .asModule() } @@ -167,6 +178,43 @@ class WebSocketApiTest extends AnyFeatureSpec with BeforeAndAfterAll with GivenW assert(response.isDefined) response.get.msg shouldEqual "No such table found with name null in module TEST. Table name and module should not be null" } + + Scenario("Type ahead rcp request for a column") { + + Then("create viewport") + val createViewPortRequest = CreateViewPortRequest(ViewPortTable("TableMetaTest", "TEST"), ViewPortRange(1,100),columns = Array("Id", "Name", "Account")) + vuuClient.send(sessionId, tokenId, createViewPortRequest) + val viewPortCreateResponse = vuuClient.awaitForMsgWithBody[CreateViewPortSuccess] + val viewPortId = viewPortCreateResponse.get.viewPortId + + //todo how to change the table data + //1. get access to provider and update directly - via adding new function to get provider from TableDefs in TableDefContainer? + //2. update the data source but have listener function to update the provider if data source change? + //3. only change when loading table for first time + + val getTypeAheadRequest = ViewPortRpcCall( + viewPortId, + "getUniqueFieldValues", + params = Array(), + namedParams = Map( + "table" -> "TableMetaTest", + "module" -> "TEST", + "column" -> "Account" + )) + vuuClient.send(sessionId, tokenId, getTypeAheadRequest) + + Then("return top 10 values in that column") + val response = vuuClient.awaitForMsgWithBody[ViewPortRpcResponse] + assert(response.isDefined) + + response.get.method shouldEqual "getUniqueFieldValues" + + val action = response.get.action + action shouldBe a [DisplayResultAction] + val displayResultAction = action.asInstanceOf[DisplayResultAction] + displayResultAction.result shouldEqual List("1235", "45321", "89564") + + } } }