embulk-parser-twitter_ads_stats 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +21 -0
- data/README.md +8 -3
- data/build.gradle +1 -1
- data/build.sbt +10 -6
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/Column.scala +10 -11
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/MetricElementNames.scala +6 -3
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/MetricsGroupJson.scala +1 -1
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/ParseException.scala +9 -11
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/TwitterAdsStatsParserPlugin.scala +9 -9
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/Data.scala +13 -10
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/IDData.scala +27 -20
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/Request.scala +11 -10
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/Root.scala +14 -12
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/RootJson.scala +3 -8
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/StatsDateTime.scala +25 -0
- data/src/main/scala/org/embulk/parser/twitter_ads_stats/package.scala +1 -1
- data/src/test/scala/org/embulk/parser/twitter_ads_stats/MetricElementNamesSpec.scala +1 -1
- data/src/test/scala/org/embulk/parser/twitter_ads_stats/MetricsGroupJsonSpec.scala +3 -3
- data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/MetricsJsonSpec.scala +2 -2
- data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/ParamsSpec.scala +3 -3
- data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/RootJsonSpec.scala +1 -1
- data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/RootSpec.scala +47 -47
- data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/StatsDateTimeSpec.scala +21 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b01ca4d919c7b6a4b43fd5becbd2b2606a522a68
|
4
|
+
data.tar.gz: 3b013591fcb20ca7a2b3a0dd1bf45ce4e6687a0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00d138b86ec59902fc4cf150807af1343732d04541a6fe18d1915d8453226a37e21ee6f745e5ddd4df0384b8ab7f27151e486b00c5c437b0747f1551cd30bc58
|
7
|
+
data.tar.gz: 936209667f89677725f4d7b19c7efb86b7b4cf70ba57239b0abaee4cb3b7ad762ccb3fe4e210407744ea0c720289d061bc3b44ef447b079240d4de7a505422ad
|
data/.travis.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
language: scala
|
2
|
+
|
3
|
+
scala:
|
4
|
+
- 2.12.3
|
5
|
+
|
6
|
+
script:
|
7
|
+
- >-
|
8
|
+
sbt
|
9
|
+
++$TRAVIS_SCALA_VERSION
|
10
|
+
scalafmt::test
|
11
|
+
test:scalafmt::test
|
12
|
+
sbt:scalafmt::test
|
13
|
+
test
|
14
|
+
|
15
|
+
cache:
|
16
|
+
directories:
|
17
|
+
- $HOME/.ivy2/cache
|
18
|
+
- $HOME/.sbt/launchers
|
19
|
+
before_cache:
|
20
|
+
- find $HOME/.sbt -name "*.lock" | xargs rm
|
21
|
+
- find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm
|
data/README.md
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
# Twitter Ads Stats parser plugin for Embulk
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/septeni-original/embulk-parser-twitter_ads_stats.svg?branch=master)](https://travis-ci.org/septeni-original/embulk-parser-twitter_ads_stats)
|
4
|
+
|
3
5
|
This plugin parse [Twitter Ads Stats](https://developer.twitter.com/en/docs/ads/analytics/overview/metrics-and-segmentation) json file.
|
4
6
|
|
5
|
-
##
|
7
|
+
## Notice
|
8
|
+
This plugin is an EXPERIMENTAL and support only Java8.
|
9
|
+
|
10
|
+
## Parse Logic
|
6
11
|
1. Flatten to metrics by date.
|
7
12
|
2. Group Metrics into Metrics Group by json type.
|
8
13
|
|
@@ -28,7 +33,7 @@ I suppose this kind of [input file](https://github.com/septeni-original/embulk-p
|
|
28
33
|
in:
|
29
34
|
type: any file input plugin type
|
30
35
|
parser:
|
31
|
-
type:
|
36
|
+
type: twitter_ads_stats
|
32
37
|
stop_on_invalid_record: true
|
33
38
|
```
|
34
39
|
|
@@ -75,5 +80,5 @@ sbt test
|
|
75
80
|
|
76
81
|
## Acknowledgement
|
77
82
|
|
78
|
-
I developed this library with reference to [embulk-parser-firebase_avro](https://github.com/smdmts/embulk-parser-firebase_avro)
|
83
|
+
I developed this library with reference to [embulk-parser-firebase_avro](https://github.com/smdmts/embulk-parser-firebase_avro).
|
79
84
|
Thank you very much.
|
data/build.gradle
CHANGED
data/build.sbt
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
-
lazy val core = (project in file(".")).
|
2
|
-
|
3
|
-
|
1
|
+
lazy val core = (project in file(".")).settings(
|
2
|
+
inThisBuild(
|
3
|
+
List(
|
4
4
|
organization := "jp.co.septeni-original",
|
5
5
|
scalaVersion := "2.12.3",
|
6
6
|
version := "0.1.0-SNAPSHOT"
|
7
|
-
)
|
8
|
-
|
9
|
-
|
7
|
+
)
|
8
|
+
),
|
9
|
+
name := "embulk-parser-twitter_ads_stats"
|
10
|
+
)
|
10
11
|
|
11
12
|
enablePlugins(ScalafmtPlugin)
|
12
13
|
|
13
14
|
resolvers += Resolver.jcenterRepo
|
14
15
|
libraryDependencies ++= Dependencies.value
|
15
16
|
scalacOptions += "-Xexperimental"
|
17
|
+
|
18
|
+
scalafmtVersion in ThisBuild := "1.2.0"
|
19
|
+
scalafmtOnCompile in ThisBuild := true
|
@@ -9,10 +9,10 @@ object Column {
|
|
9
9
|
def createEmbulkColumns(metricElementNames: MetricElementNames): Seq[EmbulkColumn] = {
|
10
10
|
@scala.annotation.tailrec
|
11
11
|
def loop(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
curIndex: Int,
|
13
|
+
curNames: List[String],
|
14
|
+
acc: Seq[EmbulkColumn]
|
15
|
+
): Seq[EmbulkColumn] = {
|
16
16
|
curNames match {
|
17
17
|
case Nil => acc.reverse
|
18
18
|
case x :: xs =>
|
@@ -24,7 +24,6 @@ object Column {
|
|
24
24
|
}
|
25
25
|
}
|
26
26
|
|
27
|
-
|
28
27
|
val baseColumns = Seq(
|
29
28
|
new EmbulkColumn(0, "id", Types.STRING),
|
30
29
|
new EmbulkColumn(1, "date", Types.STRING),
|
@@ -42,9 +41,9 @@ object Column {
|
|
42
41
|
}
|
43
42
|
|
44
43
|
case class Column(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
id: String,
|
45
|
+
date: LocalDate,
|
46
|
+
segment: Option[String],
|
47
|
+
placement: String,
|
48
|
+
metricsGroup: Map[String, MetricsGroup]
|
49
|
+
)
|
@@ -18,9 +18,12 @@ case class MetricElementNames(names: Map[String, Seq[String]]) {
|
|
18
18
|
|
19
19
|
import MetricElementNames._
|
20
20
|
|
21
|
-
def getSortedMetricsGroupNames:List[String] = names.keys.toList.sorted
|
21
|
+
def getSortedMetricsGroupNames: List[String] = names.keys.toList.sorted
|
22
22
|
|
23
|
-
private[twitter_ads_stats] def resolveMetrics(
|
23
|
+
private[twitter_ads_stats] def resolveMetrics(
|
24
|
+
resolveMetricTimeSeries: (List[String], Option[JsValue]) => MetricTimeSeries,
|
25
|
+
json: JsObject
|
26
|
+
): Metrics =
|
24
27
|
Metrics(
|
25
28
|
names.flatMap { v =>
|
26
29
|
v._2.map { value =>
|
@@ -44,4 +47,4 @@ object MetricElementNames {
|
|
44
47
|
def replaceSeparator(name: String): String = {
|
45
48
|
name.replaceAll("[.]", separator)
|
46
49
|
}
|
47
|
-
}
|
50
|
+
}
|
@@ -1,25 +1,23 @@
|
|
1
1
|
package org.embulk.parser.twitter_ads_stats
|
2
2
|
|
3
3
|
sealed abstract class ParseException(
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
message: String,
|
5
|
+
cause: Throwable
|
6
|
+
) extends Throwable
|
8
7
|
|
9
8
|
case class InvalidMetricTimeSeriesException(
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
message: String,
|
10
|
+
cause: Throwable
|
11
|
+
) extends ParseException(message, cause) {
|
13
12
|
def this(cause: Throwable, index: Int) = {
|
14
13
|
this(s"Not Found index: $index", cause)
|
15
14
|
}
|
16
15
|
}
|
17
16
|
|
18
17
|
case class InvalidInputFileException(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
extends ParseException(message, cause) {
|
18
|
+
message: String,
|
19
|
+
cause: Throwable
|
20
|
+
) extends ParseException(message, cause) {
|
23
21
|
|
24
22
|
def this(cause: Throwable) = {
|
25
23
|
this(s"Input file can't parse", cause)
|
@@ -26,13 +26,13 @@ class TwitterAdsStatsParserPlugin extends ParserPlugin {
|
|
26
26
|
|
27
27
|
override def run(taskSource: TaskSource, schema: Schema, input: FileInput, output: PageOutput): Unit = {
|
28
28
|
|
29
|
-
val task
|
29
|
+
val task = taskSource.loadTask(classOf[PluginTask])
|
30
30
|
val stopOnInvalidRecord = task.getStopOnInvalidRecord
|
31
31
|
|
32
32
|
LoanPattern(new PageBuilder(Exec.getBufferAllocator, schema, output)) { pb =>
|
33
33
|
while (input.nextFile()) {
|
34
34
|
(for {
|
35
|
-
root
|
35
|
+
root <- createRootFrom(input)
|
36
36
|
columns <- root.resolveColumns(metricElementNames)
|
37
37
|
} yield addRecord(pb, columns, root)) match {
|
38
38
|
case Right(_) =>
|
@@ -54,15 +54,15 @@ class TwitterAdsStatsParserPlugin extends ParserPlugin {
|
|
54
54
|
(column, embulkColumn.getName) match {
|
55
55
|
case (Column(id, _, _, _, _), "id") =>
|
56
56
|
pb.setString(embulkColumn, id)
|
57
|
-
case (Column(_, date, _, _,_), "date") =>
|
57
|
+
case (Column(_, date, _, _, _), "date") =>
|
58
58
|
pb.setString(embulkColumn, date.toString)
|
59
|
-
case (Column(_, _, Some(segment),_, _), "segment") =>
|
59
|
+
case (Column(_, _, Some(segment), _, _), "segment") =>
|
60
60
|
pb.setString(embulkColumn, segment)
|
61
|
-
case (Column(_, _, None,_, _), "segment") =>
|
61
|
+
case (Column(_, _, None, _, _), "segment") =>
|
62
62
|
pb.setNull(embulkColumn)
|
63
|
-
case (Column(_, _, _,placement, _), "placement") =>
|
63
|
+
case (Column(_, _, _, placement, _), "placement") =>
|
64
64
|
pb.setString(embulkColumn, placement)
|
65
|
-
case (Column(_, _, _, _,metricsGroup), key) =>
|
65
|
+
case (Column(_, _, _, _, metricsGroup), key) =>
|
66
66
|
metricsGroup.get(key) match {
|
67
67
|
case Some(m) =>
|
68
68
|
pb.setJson(
|
@@ -84,7 +84,7 @@ class TwitterAdsStatsParserPlugin extends ParserPlugin {
|
|
84
84
|
val stream = new FileInputInputStream(input)
|
85
85
|
try {
|
86
86
|
val jsValue = scala.io.Source.fromInputStream(stream).mkString.parseJson
|
87
|
-
val root
|
87
|
+
val root = new RootJson(metricElementNames).RootReader.read(jsValue)
|
88
88
|
Right(root)
|
89
89
|
} catch {
|
90
90
|
case NonFatal(e) => Left(new InvalidInputFileException(e))
|
@@ -94,5 +94,5 @@ class TwitterAdsStatsParserPlugin extends ParserPlugin {
|
|
94
94
|
|
95
95
|
object TwitterAdsStatsParserPlugin {
|
96
96
|
val logger: Logger = Exec.getLogger(classOf[TwitterAdsStatsParserPlugin])
|
97
|
-
val jsonParser
|
97
|
+
val jsonParser = new JsonParser
|
98
98
|
}
|
@@ -3,17 +3,20 @@ package org.embulk.parser.twitter_ads_stats.define
|
|
3
3
|
import org.embulk.parser.twitter_ads_stats.{Column, MetricElementNames, ParseException}
|
4
4
|
|
5
5
|
case class Data(id: String, id_data: Seq[IDData]) {
|
6
|
-
private[define] def resolveColumns(metricElementNames: MetricElementNames,
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
private[define] def resolveColumns(metricElementNames: MetricElementNames,
|
7
|
+
request: Request): Either[ParseException, Seq[Column]] = {
|
8
|
+
id_data
|
9
|
+
.map { idData =>
|
10
|
+
idData.resolveColumns(id, metricElementNames, request)
|
11
|
+
}
|
12
|
+
.foldRight[Either[ParseException, Seq[Column]]](Right(Nil)) {
|
13
|
+
case (Left(e), _) => Left(e)
|
14
|
+
case (Right(_), Left(e)) => Left(e)
|
15
|
+
case (Right(seq1), Right(seq2)) => Right(seq1 ++: seq2)
|
16
|
+
}
|
14
17
|
}
|
15
18
|
}
|
16
19
|
|
17
20
|
object Data {
|
18
|
-
val fieldNames:Array[String] = FieldNameUtil.fieldList[Data]
|
19
|
-
}
|
21
|
+
val fieldNames: Array[String] = FieldNameUtil.fieldList[Data]
|
22
|
+
}
|
@@ -6,28 +6,35 @@ import org.embulk.parser.twitter_ads_stats._
|
|
6
6
|
|
7
7
|
case class IDData(metrics: Metrics, segment: Option[String]) {
|
8
8
|
private def resolveColumn(
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
metricElementNames.names
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
9
|
+
id: String,
|
10
|
+
metricElementNames: MetricElementNames,
|
11
|
+
date: (LocalDate, Int),
|
12
|
+
placement: String
|
13
|
+
): Either[ParseException, Column] = {
|
14
|
+
metricElementNames.names
|
15
|
+
.map { name =>
|
16
|
+
(name._1, metrics.findMetricsGroup(date._2, name._2))
|
17
|
+
}
|
18
|
+
.foldRight[Either[ParseException, Map[String, MetricsGroup]]](Right(Map.empty)) {
|
19
|
+
case ((_, Left(e)), _) => Left(e)
|
20
|
+
case ((_, Right(_)), Left(e)) => Left(e)
|
21
|
+
case ((s, Right(r)), Right(acc)) => Right(acc + (s -> r))
|
22
|
+
}
|
23
|
+
.map(Column(id, date._1, segment, placement, _))
|
21
24
|
}
|
22
25
|
|
23
|
-
private[define] def resolveColumns(id: String,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
private[define] def resolveColumns(id: String,
|
27
|
+
metricElementNames: MetricElementNames,
|
28
|
+
request: Request): Either[ParseException, Seq[Column]] = {
|
29
|
+
request.params.targetDates.zipWithIndex
|
30
|
+
.map { date =>
|
31
|
+
resolveColumn(id, metricElementNames, date, request.params.placement)
|
32
|
+
}
|
33
|
+
.foldRight[Either[ParseException, Seq[Column]]](Right(Nil)) {
|
34
|
+
case (Left(e), _) => Left(e)
|
35
|
+
case (Right(_), Left(e)) => Left(e)
|
36
|
+
case (Right(a), Right(seq)) => Right(a +: seq)
|
37
|
+
}
|
31
38
|
}
|
32
39
|
}
|
33
40
|
|
@@ -1,24 +1,25 @@
|
|
1
1
|
package org.embulk.parser.twitter_ads_stats.define
|
2
2
|
|
3
|
-
import java.time.
|
3
|
+
import java.time.LocalDate
|
4
4
|
|
5
5
|
case class Request(
|
6
|
-
|
7
|
-
|
6
|
+
params: Params
|
7
|
+
)
|
8
8
|
|
9
9
|
object Request {
|
10
10
|
val fieldNames: Array[String] = FieldNameUtil.fieldList[Request]
|
11
11
|
}
|
12
12
|
|
13
13
|
case class Params(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
start_time: StatsDateTime,
|
15
|
+
end_time: StatsDateTime,
|
16
|
+
placement: String
|
17
|
+
) {
|
18
18
|
require(!start_time.isAfter(end_time))
|
19
|
+
require(start_time.isSameOffsetTime(end_time))
|
19
20
|
|
20
|
-
val startDate = start_time.
|
21
|
-
val endDate
|
21
|
+
val startDate = start_time.adAccountLocalDate
|
22
|
+
val endDate = end_time.adAccountLocalDate
|
22
23
|
|
23
24
|
/**
|
24
25
|
* MetricTimeSeries の期間
|
@@ -29,7 +30,7 @@ case class Params(
|
|
29
30
|
def loop(curDate: LocalDate, acc: List[LocalDate]): List[LocalDate] = {
|
30
31
|
acc match {
|
31
32
|
case x :: _ if !x.isBefore(endDate.minusDays(1)) => acc.reverse
|
32
|
-
case _
|
33
|
+
case _ => loop(curDate.plusDays(1), curDate :: acc)
|
33
34
|
}
|
34
35
|
}
|
35
36
|
|
@@ -3,19 +3,21 @@ package org.embulk.parser.twitter_ads_stats.define
|
|
3
3
|
import org.embulk.parser.twitter_ads_stats.{Column, MetricElementNames, ParseException}
|
4
4
|
|
5
5
|
case class Root(
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
data: Seq[Data],
|
7
|
+
request: Request
|
8
|
+
) {
|
9
9
|
def resolveColumns(metricElementNames: MetricElementNames): Either[ParseException, Seq[Column]] = {
|
10
|
-
data
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
data
|
11
|
+
.map { d =>
|
12
|
+
d.resolveColumns(metricElementNames, request)
|
13
|
+
}
|
14
|
+
.foldRight[Either[ParseException, Seq[Column]]](Right(Nil)) {
|
15
|
+
case (Left(e), _) => Left(e)
|
16
|
+
case (Right(_), Left(e)) => Left(e)
|
17
|
+
case (Right(seq1), Right(seq2)) => Right(seq1 ++: seq2)
|
18
|
+
}
|
17
19
|
}
|
18
20
|
}
|
19
21
|
object Root {
|
20
|
-
val fieldNames:Array[String] = FieldNameUtil.fieldList[Root]
|
21
|
-
}
|
22
|
+
val fieldNames: Array[String] = FieldNameUtil.fieldList[Root]
|
23
|
+
}
|
@@ -1,8 +1,5 @@
|
|
1
1
|
package org.embulk.parser.twitter_ads_stats.define
|
2
2
|
|
3
|
-
import java.time.LocalDateTime
|
4
|
-
import java.time.format.DateTimeFormatter
|
5
|
-
|
6
3
|
import org.embulk.parser.twitter_ads_stats.{MetricElementNames, MetricTimeSeries}
|
7
4
|
import spray.json.{DefaultJsonProtocol, DeserializationException, JsArray, JsString, JsValue, RootJsonReader}
|
8
5
|
|
@@ -13,7 +10,7 @@ class RootJson(metricElementNames: MetricElementNames) extends DefaultJsonProtoc
|
|
13
10
|
private def readMetricTimeSeries(jsValue: JsValue): MetricTimeSeries = {
|
14
11
|
jsValue match {
|
15
12
|
case JsArray(arr) => Some(arr.map(_.convertTo[Long]))
|
16
|
-
case _
|
13
|
+
case _ => None
|
17
14
|
}
|
18
15
|
}
|
19
16
|
|
@@ -68,11 +65,9 @@ class RootJson(metricElementNames: MetricElementNames) extends DefaultJsonProtoc
|
|
68
65
|
val fieldNames = Params.fieldNames.toList
|
69
66
|
json.asJsObject.getFields(fieldNames: _*) match {
|
70
67
|
case Seq(JsString(a), JsString(b), c) =>
|
71
|
-
val formatter = DateTimeFormatter.ISO_DATE_TIME
|
72
|
-
//todo DateTimeFormatterでフォーマットが出来ない場合の例外処理
|
73
68
|
Params(
|
74
|
-
|
75
|
-
|
69
|
+
StatsDateTime(a),
|
70
|
+
StatsDateTime(b),
|
76
71
|
c.convertTo[String]
|
77
72
|
)
|
78
73
|
case x => throw DeserializationException(msg = s"params can't deserialize json: $x", fieldNames = fieldNames)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
package org.embulk.parser.twitter_ads_stats.define
|
2
|
+
|
3
|
+
import java.time.{LocalDate, LocalDateTime}
|
4
|
+
import java.time.format.DateTimeFormatter
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @param iso8601DateTime This datetime represents midnight in the timezone of the advertiser's account.
|
8
|
+
*/
|
9
|
+
case class StatsDateTime(iso8601DateTime: String) {
|
10
|
+
|
11
|
+
private val utcDateTime: LocalDateTime = LocalDateTime.parse(iso8601DateTime, DateTimeFormatter.ISO_DATE_TIME)
|
12
|
+
|
13
|
+
def adAccountLocalDate: LocalDate = utcDateTime.plusHours(StatsDateTime.DateLineOffsetHours).toLocalDate
|
14
|
+
|
15
|
+
def isAfter(that: StatsDateTime): Boolean = this.utcDateTime.isAfter(that.utcDateTime)
|
16
|
+
|
17
|
+
def isSameOffsetTime(that: StatsDateTime): Boolean = this.utcDateTime.toLocalTime == that.utcDateTime.toLocalTime
|
18
|
+
|
19
|
+
}
|
20
|
+
|
21
|
+
object StatsDateTime {
|
22
|
+
|
23
|
+
val DateLineOffsetHours = 12
|
24
|
+
|
25
|
+
}
|
@@ -6,7 +6,7 @@ class MetricElementNamesSpec extends UnitSpec {
|
|
6
6
|
val names = MetricElementNames(
|
7
7
|
Map("b" -> emptySeq, "a" -> emptySeq, "c" -> emptySeq)
|
8
8
|
)
|
9
|
-
val actual
|
9
|
+
val actual = names.getSortedMetricsGroupNames
|
10
10
|
val expected = List("a", "b", "c")
|
11
11
|
|
12
12
|
assert(actual == expected)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
package org.embulk.parser.twitter_ads_stats
|
2
2
|
|
3
|
-
import spray.json.{JsNull, JsNumber, JsObject
|
3
|
+
import spray.json.{pimpAny, JsNull, JsNumber, JsObject}
|
4
4
|
|
5
5
|
class MetricsGroupJsonSpec extends UnitSpec {
|
6
6
|
"json write" in {
|
@@ -11,8 +11,8 @@ class MetricsGroupJsonSpec extends UnitSpec {
|
|
11
11
|
val actual = v.toJson
|
12
12
|
|
13
13
|
val expected = JsObject(
|
14
|
-
"a"
|
15
|
-
"b"
|
14
|
+
"a" -> JsNumber(3),
|
15
|
+
"b" -> JsNumber(6),
|
16
16
|
"c_e" -> JsNull
|
17
17
|
)
|
18
18
|
assert(actual == expected)
|
@@ -44,8 +44,8 @@ class MetricsJsonSpec extends UnitSpec {
|
|
44
44
|
val actual = new RootJson(metricElementNames).MetricsReader.read(jsValue)
|
45
45
|
val expected = Metrics(
|
46
46
|
Map(
|
47
|
-
"a"
|
48
|
-
"b"
|
47
|
+
"a" -> Some(Vector(510, 494, 364)),
|
48
|
+
"b" -> Some(Vector(1, 2, 3)),
|
49
49
|
"c_e" -> Some(Vector(1, 2, 3)),
|
50
50
|
"c_f" -> None,
|
51
51
|
"d_e" -> Some(Vector(2, 3, 4)),
|
@@ -1,12 +1,12 @@
|
|
1
1
|
package org.embulk.parser.twitter_ads_stats.define
|
2
2
|
|
3
|
-
import java.time.
|
3
|
+
import java.time.LocalDate
|
4
4
|
|
5
5
|
import org.embulk.parser.twitter_ads_stats.UnitSpec
|
6
6
|
|
7
7
|
class ParamsSpec extends UnitSpec {
|
8
8
|
"開始日~(終了日-1)の日程を取得する" in {
|
9
|
-
val period = Params(
|
9
|
+
val period = Params(StatsDateTime("2016-12-31T15:00:00Z"), StatsDateTime("2017-01-03T15:00:00Z"), "")
|
10
10
|
val actual = period.targetDates
|
11
11
|
val expected = List(
|
12
12
|
LocalDate.of(2017, 1, 1),
|
@@ -16,7 +16,7 @@ class ParamsSpec extends UnitSpec {
|
|
16
16
|
assert(actual == expected)
|
17
17
|
}
|
18
18
|
"開始日から終了日までの全日程は、開始日から終了日が同一な場合は同一な日程となる" in {
|
19
|
-
val period = Params(
|
19
|
+
val period = Params(StatsDateTime("2016-12-31T15:00:00Z"), StatsDateTime("2017-01-01T15:00:00Z"), "")
|
20
20
|
val actual = period.targetDates
|
21
21
|
val expected = List(
|
22
22
|
LocalDate.of(2017, 1, 1)
|
@@ -20,11 +20,11 @@ class RootSpec extends UnitSpec {
|
|
20
20
|
"",
|
21
21
|
Map(
|
22
22
|
"media" -> Map(
|
23
|
-
"media_views"
|
23
|
+
"media_views" -> Some(1),
|
24
24
|
"media_engagements" -> None
|
25
25
|
),
|
26
26
|
"billing" -> Map(
|
27
|
-
"billed_engagements"
|
27
|
+
"billed_engagements" -> Some(1),
|
28
28
|
"billed_charge_local_micro" -> Some(1)
|
29
29
|
),
|
30
30
|
"web_conversion" -> Map(
|
@@ -39,11 +39,11 @@ class RootSpec extends UnitSpec {
|
|
39
39
|
"",
|
40
40
|
Map(
|
41
41
|
"media" -> Map(
|
42
|
-
"media_views"
|
42
|
+
"media_views" -> Some(2),
|
43
43
|
"media_engagements" -> None
|
44
44
|
),
|
45
45
|
"billing" -> Map(
|
46
|
-
"billed_engagements"
|
46
|
+
"billed_engagements" -> Some(2),
|
47
47
|
"billed_charge_local_micro" -> Some(2)
|
48
48
|
),
|
49
49
|
"web_conversion" -> Map(
|
@@ -58,13 +58,13 @@ class RootSpec extends UnitSpec {
|
|
58
58
|
"",
|
59
59
|
Map(
|
60
60
|
"media" -> Map(
|
61
|
-
"media_views"
|
61
|
+
"media_views" -> Some(10),
|
62
62
|
"media_engagements" -> None
|
63
63
|
),
|
64
64
|
"billing" -> Map(
|
65
|
-
|
66
|
-
|
67
|
-
|
65
|
+
"billed_engagements" -> Some(10),
|
66
|
+
"billed_charge_local_micro" -> Some(10)
|
67
|
+
),
|
68
68
|
"web_conversion" -> Map(
|
69
69
|
"conversion_purchases_assisted" -> Some(10)
|
70
70
|
)
|
@@ -77,11 +77,11 @@ class RootSpec extends UnitSpec {
|
|
77
77
|
"",
|
78
78
|
Map(
|
79
79
|
"media" -> Map(
|
80
|
-
"media_views"
|
80
|
+
"media_views" -> Some(20),
|
81
81
|
"media_engagements" -> None
|
82
82
|
),
|
83
83
|
"billing" -> Map(
|
84
|
-
"billed_engagements"
|
84
|
+
"billed_engagements" -> Some(20),
|
85
85
|
"billed_charge_local_micro" -> Some(20)
|
86
86
|
),
|
87
87
|
"web_conversion" -> Map(
|
@@ -96,13 +96,13 @@ class RootSpec extends UnitSpec {
|
|
96
96
|
"",
|
97
97
|
Map(
|
98
98
|
"media" -> Map(
|
99
|
-
"media_views"
|
99
|
+
"media_views" -> Some(1),
|
100
100
|
"media_engagements" -> None
|
101
101
|
),
|
102
102
|
"billing" -> Map(
|
103
|
-
|
104
|
-
|
105
|
-
|
103
|
+
"billed_engagements" -> Some(1),
|
104
|
+
"billed_charge_local_micro" -> Some(1)
|
105
|
+
),
|
106
106
|
"web_conversion" -> Map(
|
107
107
|
"conversion_purchases_assisted" -> Some(1)
|
108
108
|
)
|
@@ -115,11 +115,11 @@ class RootSpec extends UnitSpec {
|
|
115
115
|
"",
|
116
116
|
Map(
|
117
117
|
"media" -> Map(
|
118
|
-
"media_views"
|
118
|
+
"media_views" -> Some(2),
|
119
119
|
"media_engagements" -> None
|
120
120
|
),
|
121
121
|
"billing" -> Map(
|
122
|
-
"billed_engagements"
|
122
|
+
"billed_engagements" -> Some(2),
|
123
123
|
"billed_charge_local_micro" -> Some(2)
|
124
124
|
),
|
125
125
|
"web_conversion" -> Map(
|
@@ -134,13 +134,13 @@ class RootSpec extends UnitSpec {
|
|
134
134
|
"",
|
135
135
|
Map(
|
136
136
|
"media" -> Map(
|
137
|
-
"media_views"
|
137
|
+
"media_views" -> Some(10),
|
138
138
|
"media_engagements" -> None
|
139
139
|
),
|
140
140
|
"billing" -> Map(
|
141
|
-
|
142
|
-
|
143
|
-
|
141
|
+
"billed_engagements" -> Some(10),
|
142
|
+
"billed_charge_local_micro" -> Some(10)
|
143
|
+
),
|
144
144
|
"web_conversion" -> Map(
|
145
145
|
"conversion_purchases_assisted" -> Some(10)
|
146
146
|
)
|
@@ -153,11 +153,11 @@ class RootSpec extends UnitSpec {
|
|
153
153
|
"",
|
154
154
|
Map(
|
155
155
|
"media" -> Map(
|
156
|
-
"media_views"
|
156
|
+
"media_views" -> Some(20),
|
157
157
|
"media_engagements" -> None
|
158
158
|
),
|
159
159
|
"billing" -> Map(
|
160
|
-
"billed_engagements"
|
160
|
+
"billed_engagements" -> Some(20),
|
161
161
|
"billed_charge_local_micro" -> Some(20)
|
162
162
|
),
|
163
163
|
"web_conversion" -> Map(
|
@@ -175,8 +175,8 @@ class RootSpec extends UnitSpec {
|
|
175
175
|
val actual = createRoot(
|
176
176
|
Request(
|
177
177
|
params = Params(
|
178
|
-
start_time =
|
179
|
-
end_time =
|
178
|
+
start_time = StatsDateTime("2017-01-01T01:01:01Z"),
|
179
|
+
end_time = StatsDateTime("2017-01-04T01:01:01Z"),
|
180
180
|
placement = ""
|
181
181
|
)
|
182
182
|
)
|
@@ -192,17 +192,17 @@ class RootSpec extends UnitSpec {
|
|
192
192
|
val names = MetricElementNames(
|
193
193
|
Map(
|
194
194
|
"media" ->
|
195
|
-
|
196
|
-
|
197
|
-
|
195
|
+
Seq(
|
196
|
+
"media_views"
|
197
|
+
)
|
198
198
|
)
|
199
199
|
)
|
200
200
|
|
201
201
|
val actual = createRoot(
|
202
202
|
Request(
|
203
203
|
params = Params(
|
204
|
-
start_time =
|
205
|
-
end_time =
|
204
|
+
start_time = StatsDateTime("2017-01-01T01:01:01Z"),
|
205
|
+
end_time = StatsDateTime("2017-01-01T01:01:01Z"),
|
206
206
|
placement = ""
|
207
207
|
)
|
208
208
|
)
|
@@ -265,8 +265,8 @@ object RootSpec {
|
|
265
265
|
val defaultRoot: Root = createRoot(
|
266
266
|
Request(
|
267
267
|
params = Params(
|
268
|
-
start_time =
|
269
|
-
end_time =
|
268
|
+
start_time = StatsDateTime("2017-01-01T01:01:01Z"),
|
269
|
+
end_time = StatsDateTime("2017-01-03T01:01:01Z"),
|
270
270
|
placement = ""
|
271
271
|
)
|
272
272
|
)
|
@@ -281,10 +281,10 @@ object RootSpec {
|
|
281
281
|
IDData(
|
282
282
|
metrics = Metrics(
|
283
283
|
Map(
|
284
|
-
"billed_engagements"
|
285
|
-
"billed_charge_local_micro"
|
286
|
-
"media_views"
|
287
|
-
"media_engagements"
|
284
|
+
"billed_engagements" -> Some(Vector(1, 2)),
|
285
|
+
"billed_charge_local_micro" -> Some(Vector(1, 2)),
|
286
|
+
"media_views" -> Some(Vector(1, 2)),
|
287
|
+
"media_engagements" -> None,
|
288
288
|
"conversion_purchases_assisted" -> Some(Vector(1, 2))
|
289
289
|
)
|
290
290
|
),
|
@@ -293,10 +293,10 @@ object RootSpec {
|
|
293
293
|
IDData(
|
294
294
|
metrics = Metrics(
|
295
295
|
Map(
|
296
|
-
"billed_engagements"
|
297
|
-
"billed_charge_local_micro"
|
298
|
-
"media_views"
|
299
|
-
"media_engagements"
|
296
|
+
"billed_engagements" -> Some(Vector(10, 20)),
|
297
|
+
"billed_charge_local_micro" -> Some(Vector(10, 20)),
|
298
|
+
"media_views" -> Some(Vector(10, 20)),
|
299
|
+
"media_engagements" -> None,
|
300
300
|
"conversion_purchases_assisted" -> Some(Vector(10, 20))
|
301
301
|
)
|
302
302
|
),
|
@@ -310,10 +310,10 @@ object RootSpec {
|
|
310
310
|
IDData(
|
311
311
|
metrics = Metrics(
|
312
312
|
Map(
|
313
|
-
"billed_engagements"
|
314
|
-
"billed_charge_local_micro"
|
315
|
-
"media_views"
|
316
|
-
"media_engagements"
|
313
|
+
"billed_engagements" -> Some(Vector(1, 2)),
|
314
|
+
"billed_charge_local_micro" -> Some(Vector(1, 2)),
|
315
|
+
"media_views" -> Some(Vector(1, 2)),
|
316
|
+
"media_engagements" -> None,
|
317
317
|
"conversion_purchases_assisted" -> Some(Vector(1, 2))
|
318
318
|
)
|
319
319
|
),
|
@@ -322,10 +322,10 @@ object RootSpec {
|
|
322
322
|
IDData(
|
323
323
|
metrics = Metrics(
|
324
324
|
Map(
|
325
|
-
"billed_engagements"
|
326
|
-
"billed_charge_local_micro"
|
327
|
-
"media_views"
|
328
|
-
"media_engagements"
|
325
|
+
"billed_engagements" -> Some(Vector(10, 20)),
|
326
|
+
"billed_charge_local_micro" -> Some(Vector(10, 20)),
|
327
|
+
"media_views" -> Some(Vector(10, 20)),
|
328
|
+
"media_engagements" -> None,
|
329
329
|
"conversion_purchases_assisted" -> Some(Vector(10, 20))
|
330
330
|
)
|
331
331
|
),
|
@@ -0,0 +1,21 @@
|
|
1
|
+
package org.embulk.parser.twitter_ads_stats.define
|
2
|
+
|
3
|
+
import java.time.LocalDate
|
4
|
+
|
5
|
+
import org.embulk.parser.twitter_ads_stats.UnitSpec
|
6
|
+
|
7
|
+
class StatsDateTimeSpec extends UnitSpec {
|
8
|
+
|
9
|
+
"Twitter APIの日時パラメータをアドアカウントでのローカル日付に変換できる" should {
|
10
|
+
"2017-08-20T15:00:00Z は 2017/08/21になる(JST)" in {
|
11
|
+
assert(StatsDateTime("2017-08-20T15:00:00Z").adAccountLocalDate == LocalDate.of(2017, 8, 21))
|
12
|
+
}
|
13
|
+
"2017-08-20T08:00:00Z は 2017/08/20になる(PST)" in {
|
14
|
+
assert(StatsDateTime("2017-08-20T08:00:00Z").adAccountLocalDate == LocalDate.of(2017, 8, 20))
|
15
|
+
}
|
16
|
+
"2017-08-20T10:00:00Z は 2017/08/20になる(Pacific/Honolulu)" in {
|
17
|
+
assert(StatsDateTime("2017-08-20T10:00:00Z").adAccountLocalDate == LocalDate.of(2017, 8, 20))
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-parser-twitter_ads_stats
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kimutyam
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -47,6 +47,7 @@ extra_rdoc_files: []
|
|
47
47
|
files:
|
48
48
|
- .gitignore
|
49
49
|
- .scalafmt.conf
|
50
|
+
- .travis.yml
|
50
51
|
- LICENSE.txt
|
51
52
|
- README.md
|
52
53
|
- build.gradle
|
@@ -74,6 +75,7 @@ files:
|
|
74
75
|
- src/main/scala/org/embulk/parser/twitter_ads_stats/define/Request.scala
|
75
76
|
- src/main/scala/org/embulk/parser/twitter_ads_stats/define/Root.scala
|
76
77
|
- src/main/scala/org/embulk/parser/twitter_ads_stats/define/RootJson.scala
|
78
|
+
- src/main/scala/org/embulk/parser/twitter_ads_stats/define/StatsDateTime.scala
|
77
79
|
- src/main/scala/org/embulk/parser/twitter_ads_stats/package.scala
|
78
80
|
- src/test/resources/test.json
|
79
81
|
- src/test/scala/org/embulk/parser/twitter_ads_stats/ColumnSpec.scala
|
@@ -84,7 +86,8 @@ files:
|
|
84
86
|
- src/test/scala/org/embulk/parser/twitter_ads_stats/define/ParamsSpec.scala
|
85
87
|
- src/test/scala/org/embulk/parser/twitter_ads_stats/define/RootJsonSpec.scala
|
86
88
|
- src/test/scala/org/embulk/parser/twitter_ads_stats/define/RootSpec.scala
|
87
|
-
-
|
89
|
+
- src/test/scala/org/embulk/parser/twitter_ads_stats/define/StatsDateTimeSpec.scala
|
90
|
+
- classpath/embulk-parser-twitter_ads_stats-0.1.2.jar
|
88
91
|
- classpath/scala-library-2.12.3.jar
|
89
92
|
- classpath/spray-json_2.12-1.3.3.jar
|
90
93
|
homepage: https://github.com/septeni-original/embulk-parser-twitter_ads_stats
|