diff --git a/.travis.yml b/.travis.yml index 3f94d8f9..11360975 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,23 @@ language: scala scala: - - 2.10.4 - - 2.11.5 +- 2.10.4 +- 2.11.5 jdk: - - oraclejdk7 - - openjdk6 - - openjdk7 +- oraclejdk7 +- openjdk6 +- openjdk7 script: - - sbt test +- sbt test +deploy: + skip_cleanup: true + provider: script + script: ./.travis/deploy.sh $TRAVIS_TAG + on: + condition: '"${TRAVIS_SCALA_VERSION}" == "2.11.5" && "${TRAVIS_JDK_VERSION}" == "oraclejdk7"' + tags: true +env: + global: + - secure: CMQoRlhW/ejJjiTIryucBgLXpbMOPx4tP9fzEKDpcltjQMZDIRyAezilraVXdI6Nap4BwbR+JuBXaG0wnfIR7hmEJe2EiWJoTWDXaHM2XnbPMsr44lXvDFPBm2H56jTf/yVTpp2HJO/iaeg+wMODPs9NqK974NY90WlwXuqIbZY= + - secure: WuGBdY3IXh9RP2q0ffVy/Q/qDGTv/DOxjW8dG1BK6F21Itz+9O3cnqHlQtNsoVMB2FmpU3Gb66sOyOYsbE4FDNB+RkFZ+bQ5qlYwNtqea6IgyODWFYMf/6+BxlyEKyFo45BHF33TIZet37vVWx9qdDPaN0tZ8fV5+gwtUa4BJ44= + - secure: "YTyO7algll9BM6I3RA5qMmfjgDTv7Al+oCZQ1SXewNV9jZQYy3DdQjwFDMvhSI2tqTlJs9rWb2pr0yE12YE/JRQDEJqf0XIZYRF6skkqSJy7BcOcbTqseRbY8DThaVtW9HqIHJq2WzoJMEU9KtYsU4ZX3MlthNgPH8hqoFLjKyg=" + - secure: "hKu4UPezvg5GCUyOwtnQ0ZOUZA6bLLC1coodfnSKhTGZTvT4kS5ckbLqMtQTMpbW3+TZLbAuhzIemrxWbvPKJVbw6YEl9ScrsPuJ7ovyRBoIuFp66+BH4apQig7YQ8Lpt2f3Vhq74X0x4NLSfVTgnC2TzzE3wc5T8X/f03rbKzU=" diff --git a/.travis/deploy.sh b/.travis/deploy.sh new file mode 100755 index 00000000..c47c08da --- /dev/null +++ b/.travis/deploy.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +tag_version=$1 + +mkdir ~/.bintray/ +FILE=$HOME/.bintray/.credentials +cat <$FILE +realm = Bintray API Realm +host = api.bintray.com +user = $BINTRAY_USER +password = $BINTRAY_API_KEY +EOF + +cd $TRAVIS_BUILD_DIR +pwd + +project_version=$(sbt version -Dsbt.log.noformat=true | perl -ne 'print $1 if /(\d+\.\d+[^\r\n]*)/') +if [ "${project_version}" == "${tag_version}" ]; then + sbt +publish + sbt +bintraySyncMavenCentral +else + echo "Tag version '${tag_version}' doesn't match version in scala project ('${project_version}'). Aborting!" + exit 1 +fi \ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG index 2811afef..ef1b1174 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +Version 0.3.0 (2016-05-14) +-------------------------- +Added support for true timestamp (#28) +Added HTTPS support to all emitters (#33) +Added missing setters for lang, page and useragent (#34) +Added trackSelfDescribingEvent method (#30) +Added Bintray credentials to .travis.yml (#41) +Fixed incorrect `instance_identity_document` jsonschema (#29) +Updated build configuration to publish to JCenter and Maven Central (#31) +Removed Snowplow Maven Repo and scala-util dependency (#42) + Version 0.2.0 (2015-10-14) -------------------------- Added a custom context based on EC2 instance metadata (#2) diff --git a/README.md b/README.md index 0fe8134c..587f1242 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,6 @@ guest$ cd /vagrant guest$ sbt test ``` -## Publishing - -```bash - host$ sbt publish-local -``` - ## Find out more | Technical Docs | Setup Guide | Roadmap | Contributing | @@ -35,7 +29,7 @@ guest$ sbt test ## Copyright and license -The Snowplow Scala Tracker is copyright 2015 Snowplow Analytics Ltd. +The Snowplow Scala Tracker is copyright 2015-2016 Snowplow Analytics Ltd. Licensed under the **[Apache License, Version 2.0] [license]** (the "License"); you may not use this software except in compliance with the License. @@ -52,7 +46,7 @@ limitations under the License. [license-image]: http://img.shields.io/badge/license-Apache--2-blue.svg?style=flat [license]: http://www.apache.org/licenses/LICENSE-2.0 -[release-image]: http://img.shields.io/badge/release-0.2.0-blue.svg?style=flat +[release-image]: http://img.shields.io/badge/release-0.3.0-blue.svg?style=flat [releases]: https://github.com/snowplow/snowplow-scala-tracker/releases [snowplow]: http://snowplowanalytics.com diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index e08a4880..c1ccb6ba 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -10,6 +10,8 @@ * "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. */ +import bintray.BintrayPlugin._ +import bintray.BintrayKeys._ import sbt._ import Keys._ @@ -18,7 +20,7 @@ object BuildSettings { // Basic settings for our app lazy val basicSettings = Seq[Setting[_]]( organization := "com.snowplowanalytics", - version := "0.2.0", + version := "0.3.0", description := "Scala tracker for Snowplow", scalaVersion := "2.10.6", crossScalaVersions := Seq("2.10.6", "2.11.5"), @@ -40,19 +42,28 @@ object BuildSettings { Seq(file) }) - // Publish settings - // TODO: update with ivy credentials etc when we start using Nexus - lazy val publishSettings = Seq[Setting[_]]( - // Enables publishing to maven repo - publishMavenStyle := true, + // Bintray publishing settings + lazy val publishSettings = bintraySettings ++ Seq[Setting[_]]( + licenses += ("Apache-2.0", url("http://www.apache.org/licenses/LICENSE-2.0.html")), + bintrayOrganization := Some("snowplow"), + bintrayRepository := "snowplow-maven" + ) - publishTo <<= version { version => - val basePath = "target/repo/%s".format { - if (version.trim.endsWith("SNAPSHOT")) "snapshots/" else "releases/" - } - Some(Resolver.file("Local Maven repository", file(basePath)) transactional()) - } + // Maven Central publishing settings + lazy val mavenCentralExtras = Seq[Setting[_]]( + pomIncludeRepository := { x => false }, + homepage := Some(url("http://snowplowanalytics.com")), + scmInfo := Some(ScmInfo(url("https://github.com/snowplow/snowplow-scala-tracker"), "scm:git@github.com:snowplow/snowplow-scala-tracker.git")), + pomExtra := ( + + + Snowplow Analytics Ltd + support@snowplowanalytics.com + Snowplow Analytics Ltd + http://snowplowanalytics.com + + ) ) - lazy val buildSettings = basicSettings ++ scalifySettings ++ publishSettings + lazy val buildSettings = basicSettings ++ scalifySettings ++ publishSettings ++ mavenCentralExtras } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 5c151666..929e7451 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -16,8 +16,6 @@ import Keys._ object Dependencies { val resolutionRepos = Seq( - // For scala-util - "Snowplow Analytics" at "http://maven.snplow.com/releases/", // For Twitter's LRU cache "Twitter Maven Repo" at "http://maven.twttr.com/", "Sonatype" at "https://oss.sonatype.org/content/repositories/releases" @@ -33,7 +31,6 @@ object Dependencies { val jackson = "1.9.7" // Scala - val scalaUtil = "0.1.0" val json4s = "3.2.11" val spray = "1.3.3" val akka = "2.3.14" @@ -64,7 +61,6 @@ object Dependencies { val jackson = "org.codehaus.jackson" % "jackson-mapper-asl" % V.jackson // Scala - val scalaUtil = "com.snowplowanalytics" % "scala-util" % V.scalaUtil val sprayClient = "io.spray" %% "spray-client" % V.spray val akka = "com.typesafe.akka" %% "akka-actor" % V.akka val json4sJackson = "org.json4s" %% "json4s-jackson" % V.json4s @@ -94,4 +90,4 @@ object Dependencies { } else { on211 })) -} \ No newline at end of file +} diff --git a/project/SnowplowTrackerBuild.scala b/project/SnowplowTrackerBuild.scala index 50bb42e6..6758f3f3 100644 --- a/project/SnowplowTrackerBuild.scala +++ b/project/SnowplowTrackerBuild.scala @@ -35,7 +35,6 @@ object SnowplowTrackerBuild extends Build { Libraries.jodaConvert, Libraries.jodaMoney, Libraries.jackson, - Libraries.scalaUtil, Libraries.json4sJackson, Libraries.sprayClient, Libraries.akka, diff --git a/project/build.properties b/project/build.properties index 8ac605a3..43b8278c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.2 +sbt.version=0.13.11 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 00000000..40c4ee2d --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0") \ No newline at end of file diff --git a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Ec2Metadata.scala b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Ec2Metadata.scala index 8c8b216a..cd8f60b6 100644 --- a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Ec2Metadata.scala +++ b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Ec2Metadata.scala @@ -43,7 +43,7 @@ object Ec2Metadata { implicit val timeout = Timeout(shortTimeout) val pipeline: HttpRequest => Future[HttpResponse] = sendReceive - val instanceIdentitySchema = "iglu:com.amazon.aws.ec2/instance_identity_document" + val instanceIdentitySchema = "iglu:com.amazon.aws.ec2/instance_identity_document/jsonschema/1-0-0" val instanceIdentityUri = "http://169.254.169.254/latest/dynamic/instance-identity/document/" private var contextSlot: Option[SelfDescribingJson] = None diff --git a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Subject.scala b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Subject.scala index f19ba15a..001d7615 100644 --- a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Subject.scala +++ b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Subject.scala @@ -51,6 +51,11 @@ class Subject { this } + def setLang(lang: String): Subject = { + standardNvPairs += ("lang" -> lang) + this + } + def setDomainUserId(domainUserId: String): Subject = { standardNvPairs += ("duid" -> domainUserId) this @@ -61,6 +66,11 @@ class Subject { this } + def setUseragent(useragent: String): Subject = { + standardNvPairs += ("ua" -> useragent) + this + } + def setNetworkUserId(nuid: String): Subject = { standardNvPairs += ("tnuid" -> nuid) this diff --git a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Tracker.scala b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Tracker.scala index 3d04bd26..ff335b7e 100644 --- a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Tracker.scala +++ b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/Tracker.scala @@ -18,6 +18,7 @@ import java.util.UUID // Scala import scala.concurrent.Promise import scala.concurrent.ExecutionContext.Implicits.global +import scala.language.implicitConversions import scala.util.{ Failure, Success } import emitters.TEmitter @@ -31,6 +32,7 @@ import emitters.TEmitter * @param encodeBase64 Whether to encode JSONs */ class Tracker(emitters: Seq[TEmitter], namespace: String, appId: String, encodeBase64: Boolean = true) { + import Tracker._ private val Version = s"scala-${generated.ProjectSettings.version}" @@ -78,14 +80,15 @@ class Tracker(emitters: Seq[TEmitter], namespace: String, appId: String, encodeB private def completePayload( payload: Payload, contexts: Seq[SelfDescribingJson], - timestamp: Option[Long]): Payload = { + timestamp: Option[Timestamp]): Payload = { payload.add("eid", UUID.randomUUID().toString) if (!payload.nvPairs.contains("dtm")) { timestamp match { - case Some(dtm) => payload.add("dtm", dtm.toString) - case None => payload.add("dtm", System.currentTimeMillis().toString) + case Some(DeviceCreatedTimestamp(dtm)) => payload.add("dtm", dtm.toString) + case Some(TrueTimestamp(ttm)) => payload.add("ttm", ttm.toString) + case None => payload.add("dtm", System.currentTimeMillis().toString) } } @@ -130,7 +133,7 @@ class Tracker(emitters: Seq[TEmitter], namespace: String, appId: String, encodeB def trackUnstructEvent( unstructEvent: SelfDescribingJson, contexts: Seq[SelfDescribingJson] = Nil, - timestamp: Option[Long] = None): Tracker = { + timestamp: Option[Timestamp] = None): Tracker = { val payload = new Payload() @@ -147,6 +150,21 @@ class Tracker(emitters: Seq[TEmitter], namespace: String, appId: String, encodeB this } + /** + * Track a Snowplow unstructured event + * Alias for [[trackUnstructEvent]] + * + * @param unstructEvent self-describing JSON for the event + * @param contexts list of additional contexts + * @param timestamp optional user-provided timestamp (ms) for the event + * @return The tracker instance + */ + def trackSelfDescribingEvent( + unstructEvent: SelfDescribingJson, + contexts: Seq[SelfDescribingJson] = Nil, + timestamp: Option[Timestamp] = None): Tracker = + trackUnstructEvent(unstructEvent, contexts, timestamp) + /** * Track a Snowplow structured event * @@ -166,7 +184,7 @@ class Tracker(emitters: Seq[TEmitter], namespace: String, appId: String, encodeB property: Option[String] = None, value: Option[Double] = None, contexts: Seq[SelfDescribingJson] = Nil, - timestamp: Option[Long] = None): Tracker = { + timestamp: Option[Timestamp] = None): Tracker = { val payload = new Payload() @@ -197,7 +215,7 @@ class Tracker(emitters: Seq[TEmitter], namespace: String, appId: String, encodeB pageTitle: Option[String] = None, referrer: Option[String] = None, contexts: Seq[SelfDescribingJson] = Nil, - timestamp: Option[Long] = None): Tracker = { + timestamp: Option[Timestamp] = None): Tracker = { val payload = new Payload() @@ -235,3 +253,19 @@ class Tracker(emitters: Seq[TEmitter], namespace: String, appId: String, encodeB } } +object Tracker { + + /** + * Tag-type for timestamp, allowing to set ttm/dtm + */ + sealed trait Timestamp { val value: Long } + case class TrueTimestamp(value: Long) extends Timestamp + case class DeviceCreatedTimestamp(value: Long) extends Timestamp + + /** + * Implicit conversion of Long values to [[DeviceCreatedTimestamp]] as default + */ + implicit def longToTimestamp(value: Long): Timestamp = + DeviceCreatedTimestamp(value) +} + diff --git a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/AsyncBatchEmitter.scala b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/AsyncBatchEmitter.scala index ac3b9a94..ff0d9f3c 100644 --- a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/AsyncBatchEmitter.scala +++ b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/AsyncBatchEmitter.scala @@ -27,10 +27,11 @@ object AsyncBatchEmitter { * @param host collector host * @param port collector port * @param bufferSize quantity of events in batch request + * @param https should this use the https scheme * @return emitter */ - def createAndStart(host: String, port: Int = 80, bufferSize: Int = 50): AsyncBatchEmitter = { - val emitter = new AsyncBatchEmitter(host, port, bufferSize) + def createAndStart(host: String, port: Int = 80, bufferSize: Int = 50, https: Boolean = false): AsyncBatchEmitter = { + val emitter = new AsyncBatchEmitter(host, port, bufferSize, https = https) emitter.startWorker() emitter } @@ -43,8 +44,9 @@ object AsyncBatchEmitter { * @param host collector host * @param port collector port * @param bufferSize quantity of events in a batch request + * @param https should this use the https scheme */ -class AsyncBatchEmitter private(host: String, port: Int, bufferSize: Int) extends TEmitter { +class AsyncBatchEmitter private(host: String, port: Int, bufferSize: Int, https: Boolean = false) extends TEmitter { val queue = new LinkedBlockingQueue[Seq[Map[String, String]]]() @@ -58,7 +60,7 @@ class AsyncBatchEmitter private(host: String, port: Int, bufferSize: Int) extend override def run { while (true) { val batch = queue.take() - RequestUtils.retryPostUntilSuccessful(host, batch, port, initialBackoffPeriod) + RequestUtils.retryPostUntilSuccessful(host, batch, port, initialBackoffPeriod, https = https) } } } diff --git a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/AsyncEmitter.scala b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/AsyncEmitter.scala index d203636e..88513cb1 100644 --- a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/AsyncEmitter.scala +++ b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/AsyncEmitter.scala @@ -23,10 +23,11 @@ object AsyncEmitter { * * @param host collector host * @param port collector port + * @param https should this use the https scheme * @return emitter */ - def createAndStart(host: String, port: Int = 80): AsyncEmitter = { - val emitter = new AsyncEmitter(host, port) + def createAndStart(host: String, port: Int = 80, https: Boolean = false): AsyncEmitter = { + val emitter = new AsyncEmitter(host, port, https) emitter.startWorker() emitter } @@ -37,8 +38,9 @@ object AsyncEmitter { * * @param host collector host * @param port collector port + * @param https should this use the https scheme */ -class AsyncEmitter private(host: String, port: Int) extends TEmitter { +class AsyncEmitter private(host: String, port: Int, https: Boolean = false) extends TEmitter { val queue = new LinkedBlockingQueue[Map[String, String]]() @@ -50,7 +52,7 @@ class AsyncEmitter private(host: String, port: Int) extends TEmitter { override def run { while (true) { val event = queue.take() - RequestUtils.retryGetUntilSuccessful(host, event, port, initialBackoffPeriod) + RequestUtils.retryGetUntilSuccessful(host, event, port, initialBackoffPeriod, https = https) } } } diff --git a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/RequestUtils.scala b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/RequestUtils.scala index de241485..13f67cc6 100644 --- a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/RequestUtils.scala +++ b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/RequestUtils.scala @@ -48,7 +48,7 @@ import com.typesafe.config.ConfigFactory */ object RequestUtils { // JSON object with Iglu URI to Schema for payload - private val payloadBatchStub: JObject = ("schema", "iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-3") + private val payloadBatchStub: JObject = ("schema", "iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4") /** * Transform List of Map[String, String] to JSON array of objects @@ -87,11 +87,12 @@ object RequestUtils { * @param host URL host (not header) * @param payload map of event keys * @param port URL port (not header) + * @param https should this request use the https scheme * @return HTTP request with event */ - private[emitters] def constructGetRequest(host: String, payload: Map[String, String], port: Int): HttpRequest = { + private[emitters] def constructGetRequest(host: String, payload: Map[String, String], port: Int, https: Boolean = false): HttpRequest = { val uri = Uri() - .withScheme("http") + .withScheme(Uri.httpScheme(https)) .withPath(Uri.Path("/i")) .withAuthority(Uri.Authority(Uri.Host(host), port)) .withQuery(payload) @@ -104,11 +105,12 @@ object RequestUtils { * @param host URL host (not header) * @param payload list of events * @param port URL port (not header) + * @param https should this request use the https scheme * @return HTTP request with event */ - private[emitters] def constructPostRequest(host: String, payload: Seq[Map[String, String]], port: Int): HttpRequest = { + private[emitters] def constructPostRequest(host: String, payload: Seq[Map[String, String]], port: Int, https: Boolean = false): HttpRequest = { val uri = Uri() - .withScheme("http") + .withScheme(Uri.httpScheme(https)) .withPath(Uri.Path("/com.snowplowanalytics.snowplow/tp2")) .withAuthority(Uri.Authority(Uri.Host(host), port)) Post(uri, payload) @@ -120,11 +122,12 @@ object RequestUtils { * @param host collector host * @param payload event map * @param port collector port + * @param https should this request use the https scheme * @return Whether the request succeeded */ - def attemptGet(host: String, payload: Map[String, String], port: Int = 80): Boolean = { + def attemptGet(host: String, payload: Map[String, String], port: Int = 80, https: Boolean = false): Boolean = { val payloadWithStm = payload ++ Map("stm" -> System.currentTimeMillis().toString) - val req = constructGetRequest(host, payloadWithStm, port) + val req = constructGetRequest(host, payloadWithStm, port, https) val future = pipeline(req) val result = Await.ready(future, longTimeout).value.get result match { @@ -142,17 +145,25 @@ object RequestUtils { * @param port collector port * @param backoffPeriod How long to wait after first failed request * @param attempt accumulated value of tries + * @param https should this request use the https scheme */ - def retryGetUntilSuccessful(host: String, payload: Map[String, String], port: Int = 80, backoffPeriod: Long, attempt: Int = 1) { + def retryGetUntilSuccessful( + host: String, + payload: Map[String, String], + port: Int = 80, + backoffPeriod: Long, + attempt: Int = 1, + https: Boolean = false) { + val getSuccessful = try { - attemptGet(host, payload, port) + attemptGet(host, payload, port, https) } catch { case NonFatal(f) => false } if (!getSuccessful && attempt < 10) { Thread.sleep(backoffPeriod) - retryGetUntilSuccessful(host, payload, port, backoffPeriod * 2, attempt + 1) + retryGetUntilSuccessful(host, payload, port, backoffPeriod * 2, attempt + 1, https) } } @@ -162,12 +173,13 @@ object RequestUtils { * @param host collector host * @param payload event map * @param port collector port + * @param https should this request use the https scheme * @return Whether the request succeeded */ - def attemptPost(host: String, payload: Seq[Map[String, String]], port: Int = 80): Boolean = { + def attemptPost(host: String, payload: Seq[Map[String, String]], port: Int = 80, https: Boolean = false): Boolean = { val stm = System.currentTimeMillis().toString val payloadWithStm = payload.map(_ ++ Map("stm" -> stm)) - val req = constructPostRequest(host, payloadWithStm, port) + val req = constructPostRequest(host, payloadWithStm, port, https) val future = pipeline(req) val result = Await.ready(future, longTimeout).value.get result match { @@ -185,17 +197,25 @@ object RequestUtils { * @param port collector port * @param backoffPeriod How long to wait after first failed request * @param attempt accumulated value of tries + * @param https should this request use the https scheme */ - def retryPostUntilSuccessful(host: String, payload: Seq[Map[String, String]], port: Int = 80, backoffPeriod: Long, attempt: Int = 1) { + def retryPostUntilSuccessful( + host: String, + payload: Seq[Map[String, String]], + port: Int = 80, + backoffPeriod: Long, + attempt: Int = 1, + https: Boolean = false) { + val getSuccessful = try { - attemptPost(host, payload, port) + attemptPost(host, payload, port, https) } catch { case NonFatal(f) => false } if (!getSuccessful && attempt < 10) { Thread.sleep(backoffPeriod) - retryPostUntilSuccessful(host, payload, port, backoffPeriod * 2, attempt + 1) + retryPostUntilSuccessful(host, payload, port, backoffPeriod * 2, attempt + 1, https) } } diff --git a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/SyncEmitter.scala b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/SyncEmitter.scala index 1646b9ea..ef3e932c 100644 --- a/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/SyncEmitter.scala +++ b/src/main/scala/com.snowplowanalytics.snowplow/scalatracker/emitters/SyncEmitter.scala @@ -17,10 +17,11 @@ package com.snowplowanalytics.snowplow.scalatracker.emitters * * @param host * @param port + * @param https */ -class SyncEmitter(host: String, port: Int = 80) extends TEmitter { +class SyncEmitter(host: String, port: Int = 80, https: Boolean = false) extends TEmitter { def input(event: Map[String, String]): Unit = { - RequestUtils.attemptGet(host, event, port) + RequestUtils.attemptGet(host, event, port, https) } } diff --git a/src/test/scala/com.snowplowanalytics.snowplow.scalatracker/TrackerSpec.scala b/src/test/scala/com.snowplowanalytics.snowplow.scalatracker/TrackerSpec.scala index 656aa024..f3cd7666 100644 --- a/src/test/scala/com.snowplowanalytics.snowplow.scalatracker/TrackerSpec.scala +++ b/src/test/scala/com.snowplowanalytics.snowplow.scalatracker/TrackerSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Snowplow Analytics Ltd. All rights reserved. + * Copyright (c) 2012-2016 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. @@ -85,8 +85,10 @@ class TrackerSpec extends Specification { .setViewport(50,100) .setColorDepth(24) .setTimezone("Europe London") + .setLang("en") .setDomainUserId("17") .setIpAddress("255.255.255.255") + .setUseragent("Mozilla/5.0 (Windows NT 5.1; rv:24.0) Gecko/20100101 Firefox/24.0") .setNetworkUserId("id") tracker.setSubject(subject) @@ -101,10 +103,11 @@ class TrackerSpec extends Specification { event("vp") must_== "50x100" event("cd") must_== "24" event("tz") must_== "Europe London" + event("lang") must_== "en" event("duid") must_== "17" event("ip") must_== "255.255.255.255" + event("ua") must_== "Mozilla/5.0 (Windows NT 5.1; rv:24.0) Gecko/20100101 Firefox/24.0" event("tnuid") must_== "id" - } } @@ -123,5 +126,40 @@ class TrackerSpec extends Specification { event("co") must_== """{"schema":"iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-1","data":[{"schema":"iglu:com.snowplowanalytics.snowplow/context1/jsonschema/1-0-0","data":{"number":20}},{"schema":"iglu:com.snowplowanalytics.snowplow/context1/jsonschema/1-0-0","data":{"letters":["a","b","c"]}}]}""" } + + "implicitly (and without additional imports) assume device_timestamp when no data constructor specified for timestamp" in { + + val emitter = new TestEmitter + + val tracker = new Tracker(List(emitter), "mytracker", "myapp", false) + + tracker.trackStructEvent("e-commerce", "buy", property=Some("book"), timestamp=Some(1459778142000L)) // Long + + val event = emitter.lastInput + + (event("dtm") must_== "1459778142000").and( + event.get("ttm") must beNone + ) + + } + + "set true_timestamp when data constructor applied explicitly" in { + + val emitter = new TestEmitter + + val tracker = new Tracker(List(emitter), "mytracker", "myapp", false) + + val timestamp = Tracker.TrueTimestamp(1459778542000L) + + tracker.trackStructEvent("e-commerce", "buy", property=Some("book"), timestamp=Some(timestamp)) + + val event = emitter.lastInput + + (event("ttm") must_== "1459778542000").and( + event.get("dtm") must beNone + ) + + } + } }