Skip to content

Commit

Permalink
Scala Common Enrich: converting transactions from given currency to "…
Browse files Browse the repository at this point in the history
…home currency" (closes snowplow#370)

Scala Common Enrich: converting transactions from given currency to home currency

Scala Common Enrich: converting transactions from given currency to home currency

Scala Common Enrich: converting transactions from given currency to home currency

Scala Common Enrich: converting transactions from given currency to home currency (closes snowplow#370)

Scala Common Enrich: converting transactions from given currency to home currency (closes snowplow#370)
  • Loading branch information
AALEKH committed Jan 30, 2015
1 parent dd0ef47 commit 93d3bb0
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 3 deletions.
4 changes: 4 additions & 0 deletions 3-enrich/scala-common-enrich/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ object Dependencies {
// For Snowplow libs
"Snowplow Analytics Maven repo" at "http://maven.snplow.com/releases/",
"Snowplow Analytics Maven snapshot repo" at "http://maven.snplow.com/snapshots/",
//For scala-forex
"Sonatype" at "https://oss.sonatype.org/content/repositories/releases",
// For user-agent-utils
"user-agent-utils repo" at "https://raw.github.com/HaraldWalker/user-agent-utils/mvn-repo/"
)
Expand All @@ -47,6 +49,7 @@ object Dependencies {
val maxmindIplookups = "0.2.0"
val json4s = "3.2.11"
val igluClient = "0.2.0"
val scalaForex = "0.2.0"
// Scala (test only)
val specs2 = "1.14"
val scalazSpecs2 = "0.1.2"
Expand All @@ -67,6 +70,7 @@ object Dependencies {
val jsonValidator = "com.github.fge" % "json-schema-validator" % V.jsonValidator
val mavenArtifact = "org.apache.maven" % "maven-artifact" % V.mavenArtifact
// Scala
val scalaForex = "com.snowplowanalytics" %% "scala-forex" % V.scalaForex
val scalaz7 = "org.scalaz" %% "scalaz-core" % V.scalaz7
val snowplowRawEvent = "com.snowplowanalytics" % "snowplow-thrift-raw-event" % V.snowplowRawEvent
val scalaUtil = "com.snowplowanalytics" % "scala-util" % V.scalaUtil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ object SnowplowCommonEnrichBuild extends Build {
Libraries.json4sScalaz,
Libraries.igluClient,
Libraries.scalaUri,
Libraries.scalaForex,
// Scala (test only)
Libraries.specs2,
Libraries.scalazSpecs2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import enrichments.{MiscEnrichments => ME}
import enrichments.{ClientEnrichments => CE}
import web.{PageEnrichments => WPE}

// For CurrencyConversion Enrichment
import registry.TransactionAmounts
/**
* A module to hold our enrichment process.
*
Expand Down Expand Up @@ -169,6 +171,7 @@ object EnrichmentManager {
("tr_tt" , (CU.stringToDoublelike, "tr_total")),
("tr_tx" , (CU.stringToDoublelike, "tr_tax")),
("tr_sh" , (CU.stringToDoublelike, "tr_shipping")),
("tr_cu" , (CU.stringToDoublelike, "tr_currency")),
("tr_ci" , (ME.toTsvSafe, "tr_city")),
("tr_st" , (ME.toTsvSafe, "tr_state")),
("tr_co" , (ME.toTsvSafe, "tr_country")),
Expand All @@ -179,6 +182,7 @@ object EnrichmentManager {
("ti_ca" , (ME.toTsvSafe, "ti_category")),
("ti_pr" , (CU.stringToDoublelike, "ti_price")),
("ti_qu" , (ME.toTsvSafe, "ti_quantity")),
("ti_cu" , (CU.stringToDoublelike, "ti_currency")),
// Page pings
("pp_mix" , (CU.stringToJInteger, "pp_xoffset_min")),
("pp_max" , (CU.stringToJInteger, "pp_xoffset_max")),
Expand Down Expand Up @@ -231,7 +235,25 @@ object EnrichmentManager {
event.page_urlquery = components.query.orNull
event.page_urlfragment = components.fragment.orNull
}


// For parsing ValidationString (value) to String type
def tryParse(s: scalaz.Validation[String,String]):String = {
s match {
case Success(pass) => {
pass
}
case Failure(failure) => {
failure
}
}
}
// For Currency Conversion Enrichment
val trTotal = tryParse(CU.stringToDoublelike("tr_total", "tr_tt" ))
val trTax = tryParse(CU.stringToDoublelike("tr_tax", "tr_tx" ))
val trShipping = tryParse(CU.stringToDoublelike("tr_shipping", "tr_sh" ))
val tiPrice = tryParse(CU.stringToDoublelike("ti_price", "ti_pr" ))
val tiCurrency = tryParse(CU.stringToDoublelike("ti_currency", "ti_cu" ))
val trCurrency = tryParse(CU.stringToDoublelike("tr_currency", "tr_cu" ))
// If our IpToGeo enrichment is enabled,
// get the geo-location from the IP address
val geoLocation = {
Expand Down Expand Up @@ -270,6 +292,21 @@ object EnrichmentManager {
case None => ip
})

val tuple = (trCurrency,TransactionAmounts(trTotal, trTax, trShipping), tiPrice, tiCurrency)

val currencyConversion = {
registry.getCurrencyConversionEnrichment match {
case Some(currency) => {
Option(tuple) match {
case Some(cuP) =>
var currencyConversion = currency.convertCurrencies(cuP._1, cuP._2, cuP._3, cuP._4)
case None => unitSuccess // No fields updated
}
}
case None => unitSuccess
}
}

// Potentially set the referrer details and URL components
val refererUri = CU.stringToUri(event.page_referrer)
for (uri <- refererUri; u <- uri) {
Expand Down Expand Up @@ -342,3 +379,4 @@ object EnrichmentManager {
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,16 @@ import registry.{
AnonIpEnrichment,
IpLookupsEnrichment,
RefererParserEnrichment,
CampaignAttributionEnrichment
CampaignAttributionEnrichment,
CurrencyConversionEnrichment
}

// CurrencyConversionEnrichmentConfig
import registry.CurrencyConversionEnrichmentConfig

import utils.ScalazJson4sUtils


/**
* Companion which holds a constructor
* for the EnrichmentRegistry.
Expand Down Expand Up @@ -125,7 +131,9 @@ object EnrichmentRegistry {
RefererParserEnrichment.parse(enrichmentConfig, schemaKey).map((nm, _).some)
} else if (nm == "campaign_attribution") {
CampaignAttributionEnrichment.parse(enrichmentConfig, schemaKey).map((nm, _).some)
} else {
} else if (nm == "currency_conversion_config") {
CurrencyConversionEnrichmentConfig.parse(enrichmentConfig, schemaKey).map((nm, _).some)
}else {
None.success // Enrichment is not recognized yet
}
})
Expand Down Expand Up @@ -184,6 +192,15 @@ case class EnrichmentRegistry(private val configs: EnrichmentMap) {
*/
def getCampaignAttributionEnrichment: Option[CampaignAttributionEnrichment] =
getEnrichment[CampaignAttributionEnrichment]("campaign_attribution")

/**
* Returns an Option boxing the CurrencyConversionEnrichment
* config value if present, or None if not
*
* @return Option boxing the CurrencyConversionEnrichment instance
*/
def getCurrencyConversionEnrichment: Option[CurrencyConversionEnrichment] =
getEnrichment[CurrencyConversionEnrichment]("currency_conversion")

/**
* Returns an Option boxing an Enrichment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright (c) 2012-2015 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package com.snowplowanalytics
package snowplow
package enrich
package common
package enrichments
package registry

// Java
import java.net.URI

// Maven Artifact
import org.apache.maven.artifact.versioning.DefaultArtifactVersion

// Scalaz
import scalaz._
import Scalaz._

// json4s
import org.json4s.JValue

// Iglu
import iglu.client.SchemaKey
import iglu.client.validation.ProcessingMessageMethods._

// Scala Forex
import org.joda.money.{Money}
import org.joda.time.{DateTime, DateTimeZone}
import com.snowplowanalytics.forex._
import com.snowplowanalytics.forex.oerclient.{OerClientConfig, DeveloperAccount, AccountType, OerResponseError}
import com.snowplowanalytics.forex.oerclient.OerResponseError._
import com.snowplowanalytics.forex.{Forex, ForexConfig}
import com.snowplowanalytics.forex.ForexLookupWhen._
//import org.joda.money.BigMoney.convertedTo
// This project
import common.utils.ConversionUtils
import utils.ScalazJson4sUtils

/**
* Companion object. Lets us create an CurrencyConversionEnrichment
* instance from a JValue.
*/
object CurrencyConversionEnrichmentConfig extends ParseableEnrichment {

val supportedSchemaKey = SchemaKey("com.snowplowanalytics.snowplow", "currency_conversion_config", "jsonschema", "1-0-0")

//Creates an CurrencyConversionEnrichment instance from a JValue
def parse(config: JValue, schemaKey: SchemaKey): ValidatedNelMessage[CurrencyConversionEnrichment] = {
isParseable(config, schemaKey).flatMap( conf => {
(for {
apiKey <- ScalazJson4sUtils.extract[String](config, "parameters", "apiKey")
baseCurrency <- ScalazJson4sUtils.extract[String](config, "parameters", "baseCurrency")
rateAt <- ScalazJson4sUtils.extract[String](config, "parameters", "rateAt")
enrich = CurrencyConversionEnrichment(apiKey, baseCurrency, rateAt)
} yield enrich).toValidationNel
})
}
}

/**
* Case class to wrap everything we
* can extract as converted Transaction
* Amounts.
*/
case class TransactionAmounts(
total: String,
tax: String,
shipping: String)

// Object and a case object with the same name

case class CurrencyConversionEnrichment(
apiKey: String,
baseCurrency: String,
rateAt: String) extends Enrichment {

val version = new DefaultArtifactVersion("0.1.0")

// To convert string value to double

def toDouble(s: String) = try {
Some(s.toDouble)
} catch {
case _ => None
}

// To provide Validation for org.joda.money.Money variable
def eitherToValidation(input:Either[OerResponseError, Money]): Validation[String, Double]= {
input match {
case Right(l) => (l.getAmount().doubleValue()).success[String]
case Left(l) => (s"Open Exchange Rates error, message: ${l.errorMessage}").failure
}
}

// To retrieve value from Success/Failure and parse them as string
def tryParse(s: Either[OerResponseError, Money]):String = {
eitherToValidation(s) match {
case Success(pass) => {
pass.toString()
}
case Failure(fail) => {
fail
}
}
}

/**
* Convert's currency for a given
* set of currency, using
* Scala-Forex.
*
* @param trCurrency The desired
* currency for a given
* amount
* @param trAmounts Contains
* total amount, tax, shipping
* amount's
* @param tiCurrency Trasaction Item
* Currency
* @param tiPrice Trasaction Item
* Price
* @return the converted currency
* in TransacrionAmounts
* format and Ttansaction
* Item Price
*/
def convertCurrencies(trCurrency: String, trAmounts: TransactionAmounts, tiCurrency: String, tiPrice: String): ValidationNel[String, (TransactionAmounts, String)] = {

try{
val fx = Forex(ForexConfig( nowishCacheSize = 0, nowishSecs = 0, eodCacheSize= 0, baseCurrency = baseCurrency), OerClientConfig(apiKey, DeveloperAccount))
val newCurrencyTr = fx.convert((toDouble(trAmounts.total)).get).to(trCurrency).nowish
val newCurrencyTi = fx.convert((toDouble(tiPrice)).get).to(tiCurrency).nowish
val newTrAmountsTax = fx.convert((toDouble(trAmounts.tax)).get).to(trCurrency).nowish
val newTrAmountsShipping = fx.convert((toDouble(trAmounts.shipping)).get).to(trCurrency).nowish
val newCurrencyList = List(newCurrencyTr, newCurrencyTi, newTrAmountsTax, newTrAmountsShipping)
val tupleArray = newCurrencyList.map(element=>tryParse(element))
val returnTuple = (TransactionAmounts(tupleArray(0), tupleArray(2), tupleArray(3)), tupleArray(1))
returnTuple.success

} catch {
case e => "Exception Converting Currency :[%s]".format( e.getMessage).failureNel
}

}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package com.snowplowanalytics.snowplow.enrich.common
package enrichments
package registry

// Specs2
import org.specs2.Specification
import org.specs2.matcher.DataTables
import org.specs2.scalaz._

// Scalaz
import scalaz._
import Scalaz._


// Scala Forex
import org.joda.time.{DateTime, DateTimeZone}
import com.snowplowanalytics.forex._
import com.snowplowanalytics.forex.oerclient.{OerClientConfig, DeveloperAccount, AccountType}
import com.snowplowanalytics.forex.{Forex, ForexConfig}

import com.snowplowanalytics.snowplow.enrich.common.enrichments.registry._
// This project
//import object com.snowplowanalytics.snowplow.enrich.common.enrichments.registry.CurrencyConversionEnrichment
/**
* Tests the convertCurrencies function
*/

class CurrencyConversionEnrichmentSpec extends Specification with DataTables { def is =

"This is a specification to test convertCurrencies" ^
p^
"Currency Conversion should work" ! e1^
end

val appIdInvalid = (TransactionAmounts("Open Exchange Rates error, message: Invalid App ID provided - please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org. Thanks!","Open Exchange Rates error, message: Invalid App ID provided - please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org. Thanks!","Open Exchange Rates error, message: Invalid App ID provided - please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org. Thanks!"),"Open Exchange Rates error, message: Invalid App ID provided - please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org. Thanks!")
def e1 =
"SPEC NAME" || "TRANSACTION PRICE" | "API Key" | "TRANSACTION AMOUNT" | "TRANSACTION ITEM CURRENCY" | "TRANSACTION ITEM PRICE" |"CONVERTED TUPLE" |
"First Success Test" !! "GBP" ! "" ! TransactionAmounts("12.99", "2.67", "0") ! "GBP" ! "12.99" !(TransactionAmounts("8.65","1.78","0.0"),"8.65").success |
"First Failure Test" !! "RUP" ! "" ! TransactionAmounts("14.99", "1.17", "0") ! "GBP" ! "17.99" ! "Exception Converting Currency :[Either.left.value on Right]".failureNel |
"Second Failure Test" !! "HUL" ! "" ! TransactionAmounts("10.99", "0.7", "0") ! "GBP" ! "1.99" ! "Exception Converting Currency :[Either.left.value on Right]".failureNel |
"Third Failure Test" !! "GBP" ! "89ab8d8dabdj9usd9d8siasjndsajA8Ada8" ! TransactionAmounts("2.99", "3.67", "0") ! "GBP" ! "2.99" ! appIdInvalid.success |
"Second Success Test" !! "USD" ! "" ! TransactionAmounts("11.09", "4.67", "2") ! "GBP" ! "10.99" !(TransactionAmounts("11.09","4.67","2.0"),"7.32").success |
"Third Success Test" !! "GBP" ! "" ! TransactionAmounts("10.00", "2.67", "0") ! "GBP" ! "10.00" !(TransactionAmounts("6.66","1.78","0.0"),"6.66").success |> {
(_, trPrice, apiKey, trAmount, tiCurrency, tiPrice, expected) =>
CurrencyConversionEnrichment(apiKey, "EUR", "EOD_PRIOR").convertCurrencies(trPrice, trAmount, tiCurrency, tiPrice) must_== expected
}

}

0 comments on commit 93d3bb0

Please sign in to comment.