Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass request headers to the JS enrichment (close #901) #902

Merged
merged 1 commit into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,9 @@ object EnrichmentManager {
_ <- getHttpHeaderContexts // Execute header extractor enrichment
_ <- getYauaaContext[F](registry.yauaa, raw.context.headers) // Runs YAUAA enrichment (gets info thanks to user agent)
_ <- extractSchemaFields[F](unstructEvent) // Extract the event vendor/name/format/version
_ <- registry.javascriptScript.traverse(getJsScript[F](_)) // Execute the JavaScript scripting enrichment
_ <- registry.javascriptScript.traverse( // Execute the JavaScript scripting enrichment
getJsScript[F](_, raw.context.headers)
)
_ <- getCurrency[F](raw.context.timestamp, registry.currencyConversion) // Finalize the currency conversion
_ <- getWeatherContext[F](registry.weather) // Fetch weather context
_ <- geoLocation[F](registry.ipLookups) // Execute IP lookup enrichment
Expand Down Expand Up @@ -347,7 +349,9 @@ object EnrichmentManager {
_ <- getYauaaContext[F](registry.yauaa, raw.context.headers) // Runs YAUAA enrichment (gets info thanks to user agent)
_ <- extractSchemaFields[F](unstructEvent) // Extract the event vendor/name/format/version
_ <- geoLocation[F](registry.ipLookups) // Execute IP lookup enrichment
_ <- registry.javascriptScript.traverse(getJsScript[F](_)) // Execute the JavaScript scripting enrichment
_ <- registry.javascriptScript.traverse( // Execute the JavaScript scripting enrichment
getJsScript[F](_, raw.context.headers)
)
_ <- sqlContexts // Derive some contexts with custom SQL Query enrichment
_ <- apiContexts // Derive some contexts with custom API Request enrichment
_ <- anonIp[F](registry.anonIp) // Anonymize the IP
Expand Down Expand Up @@ -788,12 +792,13 @@ object EnrichmentManager {

// Execute the JavaScript scripting enrichment
def getJsScript[F[_]: Applicative](
javascriptScript: JavascriptScriptEnrichment
javascriptScript: JavascriptScriptEnrichment,
headers: List[String]
): EStateT[F, Unit] =
EStateT.fromEither {
case (event, derivedContexts) =>
ME.formatContexts(derivedContexts).foreach(c => event.derived_contexts = c)
javascriptScript.process(event).leftMap(NonEmptyList.one)
javascriptScript.process(event, headers).leftMap(NonEmptyList.one)
}

def headerContexts[F[_]: Applicative, A](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*/
package com.snowplowanalytics.snowplow.enrich.common.enrichments.registry

import scala.collection.JavaConverters._

import cats.data.{NonEmptyList, ValidatedNel}
import cats.implicits._

Expand Down Expand Up @@ -73,8 +75,8 @@ final case class JavascriptScriptEnrichment(
private val stringified = rawFunction + s"""
var getJavascriptContexts = function() {
const params = ${params.asJson.noSpaces};
return function(event) {
const result = process(event, params);
return function(event, headers) {
const result = process(event, params, headers);
if (result == null) {
return "[]"
} else {
Expand All @@ -95,11 +97,11 @@ final case class JavascriptScriptEnrichment(
* The event can be updated in-place by the JS function.
* @return either a JSON array of contexts on Success, or an error String on Failure
*/
def process(event: EnrichedEvent): Either[FailureDetails.EnrichmentFailure, List[SelfDescribingData[Json]]] =
def process(event: EnrichedEvent, headers: List[String]): Either[FailureDetails.EnrichmentFailure, List[SelfDescribingData[Json]]] =
invocable
.flatMap(_ =>
Either
.catchNonFatal(engine.invokeFunction("getJavascriptContexts", event).asInstanceOf[String])
.catchNonFatal(engine.invokeFunction("getJavascriptContexts", event, headers.asJava).asInstanceOf[String])
.leftMap(e => s"Error during execution of JavaScript function: [${e.getMessage}]")
)
.flatMap(contexts =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ class JavascriptScriptEnrichmentSpec extends Specification {
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
Javascript enrichment should be able to utilize the headers $e13
"""

val schemaKey =
SchemaKey("com.snowplowanalytics.snowplow", "javascript_script_config", "jsonschema", SchemaVer.Full(1, 0, 0))

def e1 =
JavascriptScriptEnrichment(schemaKey, "[").process(buildEnriched()) must beLeft(
JavascriptScriptEnrichment(schemaKey, "[").process(buildEnriched(), List.empty) must beLeft(
failureContains(_: FailureDetails.EnrichmentFailure, "Error compiling")
)

Expand All @@ -51,7 +52,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
function process(event) {
return { foo: "bar" }
}"""
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beLeft(
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beLeft(
failureContains(_: FailureDetails.EnrichmentFailure, "not read as an array")
)
}
Expand All @@ -61,7 +62,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
function process(event) {
return [ { foo: "bar" } ]
}"""
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beLeft(
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beLeft(
failureContains(_: FailureDetails.EnrichmentFailure, "not self-desribing")
)
}
Expand All @@ -74,7 +75,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
data: { appId: event.getApp_id() }
} ];
}"""
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(appId)) must beRight.like {
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(appId), List.empty) must beRight.like {
case List(sdj) if sdj.data.noSpaces.contains(appId) => true
case _ => false
}
Expand All @@ -91,7 +92,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
data: { foo: "bar" }
} ];
}"""
JavascriptScriptEnrichment(schemaKey, function).process(enriched)
JavascriptScriptEnrichment(schemaKey, function).process(enriched, List.empty)
enriched.app_id must beEqualTo(newAppId)
}

Expand All @@ -100,7 +101,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
function process(event) {
throw "Error"
}"""
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beLeft(
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beLeft(
failureContains(_: FailureDetails.EnrichmentFailure, "Error during execution")
)
}
Expand All @@ -110,7 +111,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
function process(event) {
return [ ];
}"""
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beRight
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beRight
}

def e8 = {
Expand All @@ -133,7 +134,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
SchemaKey("com.acme", "bar", "jsonschema", SchemaVer.Full(1, 0, 0)),
json"""{"hello":"world"}"""
)
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beRight.like {
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beRight.like {
case List(c1, c2) if c1 == context1 && c2 == context2 => true
case _ => false
}
Expand All @@ -145,7 +146,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
var a = 42 // no-op
}"""

JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beRight(Nil)
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beRight(Nil)
}

def e10 = {
Expand All @@ -154,7 +155,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
return null
}"""

JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched()) must beRight(Nil)
JavascriptScriptEnrichment(schemaKey, function).process(buildEnriched(), List.empty) must beRight(Nil)
}

def e11 = {
Expand All @@ -165,7 +166,7 @@ class JavascriptScriptEnrichmentSpec extends Specification {
function process(event) {
event.setApp_id("$newAppId")
}"""
JavascriptScriptEnrichment(schemaKey, function).process(enriched)
JavascriptScriptEnrichment(schemaKey, function).process(enriched, List.empty)
enriched.app_id must beEqualTo(newAppId)
}

Expand All @@ -178,7 +179,28 @@ class JavascriptScriptEnrichmentSpec extends Specification {
function process(event, params) {
event.setApp_id(params.nested.foo)
}"""
JavascriptScriptEnrichment(schemaKey, function, params).process(enriched)
JavascriptScriptEnrichment(schemaKey, function, params).process(enriched, List.empty)
enriched.app_id must beEqualTo("newId")
}

def e13 = {
val appId = "greatApp"
val enriched = buildEnriched(appId)
val function =
s"""
function process(event, params, headers) {
for (header of headers) {
const jwt = header.match(/X-JWT:(.+)/i)
if (jwt) {
event.setApp_id(jwt[1].trim())
}
}
}"""

JavascriptScriptEnrichment(schemaKey, function).process(enriched, List.empty)
enriched.app_id must beEqualTo("greatApp")

JavascriptScriptEnrichment(schemaKey, function).process(enriched, List("x-jwt: newId"))
enriched.app_id must beEqualTo("newId")
}

Expand Down
Loading