embulk-parser-twitter_ads_stats 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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
|