From aa186255ba154782f5d8ddbd289796c2ee488e7c Mon Sep 17 00:00:00 2001 From: Nick Date: Tue, 12 Dec 2023 19:00:22 +0000 Subject: [PATCH] Allow passing an object of parameters to the JS enrichment --- .../enrichments/registry/EnrichmentConf.scala | 10 ++++-- .../registry/JavascriptScriptEnrichment.scala | 29 ++++++++++------ .../registry/EnrichmentConfigsSpec.scala | 33 +++++++++++++++++++ .../JavascriptScriptEnrichmentSpec.scala | 14 ++++++++ project/BuildSettings.scala | 5 ++- 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/EnrichmentConf.scala b/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/EnrichmentConf.scala index fd8cd0cb6..528968cbd 100644 --- a/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/EnrichmentConf.scala +++ b/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/EnrichmentConf.scala @@ -20,6 +20,8 @@ import cats.data.EitherT import cats.effect.kernel.{Async, Sync} +import io.circe.JsonObject + import org.joda.money.CurrencyUnit import com.snowplowanalytics.iglu.core.SchemaKey @@ -192,8 +194,12 @@ object EnrichmentConf { ) } - final case class JavascriptScriptConf(schemaKey: SchemaKey, rawFunction: String) extends EnrichmentConf { - def enrichment: JavascriptScriptEnrichment = JavascriptScriptEnrichment(schemaKey, rawFunction) + final case class JavascriptScriptConf( + schemaKey: SchemaKey, + rawFunction: String, + params: JsonObject + ) extends EnrichmentConf { + def enrichment: JavascriptScriptEnrichment = JavascriptScriptEnrichment(schemaKey, rawFunction, params) } final case class RefererParserConf( diff --git a/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/JavascriptScriptEnrichment.scala b/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/JavascriptScriptEnrichment.scala index 99eea9060..4578b047a 100644 --- a/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/JavascriptScriptEnrichment.scala +++ b/modules/common/src/main/scala/com.snowplowanalytics.snowplow.enrich/common/enrichments/registry/JavascriptScriptEnrichment.scala @@ -15,6 +15,7 @@ import cats.implicits._ import io.circe._ import io.circe.parser._ +import io.circe.syntax._ import javax.script._ @@ -52,11 +53,16 @@ object JavascriptScriptEnrichment extends ParseableEnrichment { _ <- isParseable(c, schemaKey) encoded <- CirceUtils.extract[String](c, "parameters", "script").toEither script <- ConversionUtils.decodeBase64Url(encoded) + params <- CirceUtils.extract[Option[JsonObject]](c, "parameters", "config").toEither _ <- if (script.isEmpty) Left("Provided script for JS enrichment is empty") else Right(()) - } yield JavascriptScriptConf(schemaKey, script)).toValidatedNel + } yield JavascriptScriptConf(schemaKey, script, params.getOrElse(JsonObject.empty))).toValidatedNel } -final case class JavascriptScriptEnrichment(schemaKey: SchemaKey, rawFunction: String) extends Enrichment { +final case class JavascriptScriptEnrichment( + schemaKey: SchemaKey, + rawFunction: String, + params: JsonObject = JsonObject.empty +) extends Enrichment { private val enrichmentInfo = FailureDetails.EnrichmentInformation(schemaKey, "Javascript enrichment").some @@ -64,15 +70,18 @@ final case class JavascriptScriptEnrichment(schemaKey: SchemaKey, rawFunction: S .getEngineByMimeType("text/javascript") .asInstanceOf[ScriptEngine with Invocable with Compilable] - private val stringified = rawFunction + """ - function getJavascriptContexts(event) { - var result = process(event); - if (result == null) { - return "[]" - } else { - return JSON.stringify(result); + private val stringified = rawFunction + s""" + var getJavascriptContexts = function() { + const params = ${params.asJson.noSpaces}; + return function(event) { + const result = process(event, params); + if (result == null) { + return "[]" + } else { + return JSON.stringify(result); + } } - } + }() """ private val invocable = diff --git a/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/EnrichmentConfigsSpec.scala b/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/EnrichmentConfigsSpec.scala index 56b591c9d..01c97895a 100644 --- a/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/EnrichmentConfigsSpec.scala +++ b/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/EnrichmentConfigsSpec.scala @@ -298,6 +298,39 @@ class EnrichmentConfigsSpec extends Specification with ValidatedMatchers with Da val result = JavascriptScriptEnrichment.parse(javascriptScriptEnrichmentJson, schemaKey) result must beValid // TODO: check the result's contents by evaluating some JavaScript } + "parse the additional arguments" in { + val params = json"""{"foo": 3, "nested": {"bar": 42}}""".asObject.get + val script = + s"""|function process(event, params) { + | return []; + |} + |""".stripMargin + val javascriptScriptEnrichmentJson = { + val encoder = new Base64(true) + val encoded = new String(encoder.encode(script.getBytes)).trim // Newline being appended by some Base64 versions + parse(s"""{ + "enabled": true, + "parameters": { + "script": "$encoded", + "config": { + "foo": 3, + "nested": { + "bar": 42 + } + } + } + }""").toOption.get + } + val schemaKey = SchemaKey( + "com.snowplowanalytics.snowplow", + "javascript_script_config", + "jsonschema", + SchemaVer.Full(1, 0, 0) + ) + val result = JavascriptScriptEnrichment.parse(javascriptScriptEnrichmentJson, schemaKey) + result must beValid + result.map(_.params).toOption mustEqual Some(params) + } } "Parsing a valid event_fingerprint_config enrichment JSON" should { diff --git a/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/JavascriptScriptEnrichmentSpec.scala b/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/JavascriptScriptEnrichmentSpec.scala index b7a424028..d596b8c86 100644 --- a/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/JavascriptScriptEnrichmentSpec.scala +++ b/modules/common/src/test/scala/com.snowplowanalytics.snowplow.enrich.common/enrichments/registry/JavascriptScriptEnrichmentSpec.scala @@ -35,6 +35,7 @@ class JavascriptScriptEnrichmentSpec extends Specification { Javascript enrichment should be able to proceed without return statement $e9 Javascript enrichment should be able to proceed with return null $e10 Javascript enrichment should be able to update the fields without return statement $e11 + Javascript enrichment should be able to utilize the passed parameters $e12 """ val schemaKey = @@ -168,6 +169,19 @@ class JavascriptScriptEnrichmentSpec extends Specification { enriched.app_id must beEqualTo(newAppId) } + def e12 = { + val appId = "greatApp" + val enriched = buildEnriched(appId) + val params = json"""{"foo": "bar", "nested": {"foo": "newId"}}""".asObject.get + val function = + s""" + function process(event, params) { + event.setApp_id(params.nested.foo) + }""" + JavascriptScriptEnrichment(schemaKey, function, params).process(enriched) + enriched.app_id must beEqualTo("newId") + } + def buildEnriched(appId: String = "my super app"): EnrichedEvent = { val e = new EnrichedEvent() e.platform = "server" diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index 779b91206..9d36b1046 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -215,7 +215,10 @@ object BuildSettings { // Build and publish publishSettings ++ // Tests - scoverageSettings ++ noParallelTestExecution + scoverageSettings ++ noParallelTestExecution ++ Seq( + Test / fork := true, + Test / javaOptions := Seq("-Dnashorn.args=--language=es6") + ) } lazy val commonFs2BuildSettings = {