embulk-parser-twitter_ads_stats 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.scalafmt.conf +7 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +79 -0
  6. data/build.gradle +79 -0
  7. data/build.sbt +15 -0
  8. data/classpath/embulk-parser-twitter_ads_stats-0.1.0.jar +0 -0
  9. data/classpath/scala-library-2.12.3.jar +0 -0
  10. data/classpath/spray-json_2.12-1.3.3.jar +0 -0
  11. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  12. data/gradle/wrapper/gradle-wrapper.properties +5 -0
  13. data/gradlew +172 -0
  14. data/gradlew.bat +84 -0
  15. data/lib/embulk/guess/twitter_ads_stats.rb +61 -0
  16. data/lib/embulk/parser/twitter_ads_stats.rb +3 -0
  17. data/project/Dependencies.scala +11 -0
  18. data/project/build.properties +1 -0
  19. data/project/plugins.sbt +2 -0
  20. data/src/main/scala/org/embulk/parser/twitter_ads_stats/Column.scala +50 -0
  21. data/src/main/scala/org/embulk/parser/twitter_ads_stats/LoanPattern.scala +19 -0
  22. data/src/main/scala/org/embulk/parser/twitter_ads_stats/MetricElementNames.scala +47 -0
  23. data/src/main/scala/org/embulk/parser/twitter_ads_stats/MetricsGroupJson.scala +19 -0
  24. data/src/main/scala/org/embulk/parser/twitter_ads_stats/ParseException.scala +27 -0
  25. data/src/main/scala/org/embulk/parser/twitter_ads_stats/PluginTask.scala +9 -0
  26. data/src/main/scala/org/embulk/parser/twitter_ads_stats/TwitterAdsStatsParserPlugin.scala +98 -0
  27. data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/Data.scala +19 -0
  28. data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/FieldNameUtil.scala +9 -0
  29. data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/IDData.scala +36 -0
  30. data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/Metrics.scala +23 -0
  31. data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/Request.scala +42 -0
  32. data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/Root.scala +21 -0
  33. data/src/main/scala/org/embulk/parser/twitter_ads_stats/define/RootJson.scala +107 -0
  34. data/src/main/scala/org/embulk/parser/twitter_ads_stats/package.scala +192 -0
  35. data/src/test/resources/test.json +759 -0
  36. data/src/test/scala/org/embulk/parser/twitter_ads_stats/ColumnSpec.scala +33 -0
  37. data/src/test/scala/org/embulk/parser/twitter_ads_stats/MetricElementNamesSpec.scala +14 -0
  38. data/src/test/scala/org/embulk/parser/twitter_ads_stats/MetricsGroupJsonSpec.scala +20 -0
  39. data/src/test/scala/org/embulk/parser/twitter_ads_stats/UnitSpec.scala +5 -0
  40. data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/MetricsJsonSpec.scala +57 -0
  41. data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/ParamsSpec.scala +26 -0
  42. data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/RootJsonSpec.scala +20 -0
  43. data/src/test/scala/org/embulk/parser/twitter_ads_stats/define/RootSpec.scala +356 -0
  44. metadata +114 -0
@@ -0,0 +1,33 @@
1
+ package org.embulk.parser.twitter_ads_stats
2
+
3
+ import org.embulk.spi.`type`.Types
4
+ import org.embulk.spi.{Column => EmbulkColumn}
5
+
6
+ class ColumnSpec extends UnitSpec {
7
+ "メトリクスグループのJSONがアルファベット順で JSON 型で生成される" in {
8
+ val metricElementNames = MetricElementNames(
9
+ Map(
10
+ "video" -> Seq(
11
+ "video_total_views",
12
+ "video_views_25",
13
+ ),
14
+ "billing" -> Seq(
15
+ "billed_engagements",
16
+ "billed_charge_local_micro"
17
+ )
18
+ )
19
+ )
20
+ val actual = Column.createEmbulkColumns(metricElementNames)
21
+
22
+ val expected = Seq(
23
+ new EmbulkColumn(0, "id", Types.STRING),
24
+ new EmbulkColumn(1, "date", Types.STRING),
25
+ new EmbulkColumn(2, "segment", Types.STRING),
26
+ new EmbulkColumn(3, "placement", Types.STRING),
27
+ new EmbulkColumn(4, "billing", Types.JSON),
28
+ new EmbulkColumn(5, "video", Types.JSON),
29
+ )
30
+
31
+ assert(actual == expected)
32
+ }
33
+ }
@@ -0,0 +1,14 @@
1
+ package org.embulk.parser.twitter_ads_stats
2
+
3
+ class MetricElementNamesSpec extends UnitSpec {
4
+ "ソートされたメトリクスグループの名前を取得する" in {
5
+ val emptySeq = Seq.empty
6
+ val names = MetricElementNames(
7
+ Map("b" -> emptySeq, "a" -> emptySeq, "c" -> emptySeq)
8
+ )
9
+ val actual = names.getSortedMetricsGroupNames
10
+ val expected = List("a", "b", "c")
11
+
12
+ assert(actual == expected)
13
+ }
14
+ }
@@ -0,0 +1,20 @@
1
+ package org.embulk.parser.twitter_ads_stats
2
+
3
+ import spray.json.{JsNull, JsNumber, JsObject, pimpAny}
4
+
5
+ class MetricsGroupJsonSpec extends UnitSpec {
6
+ "json write" in {
7
+ import MetricsGroupJson._
8
+
9
+ val v: MetricsGroup = Map("a" -> Some(3), "b" -> Some(6), "c_e" -> None)
10
+
11
+ val actual = v.toJson
12
+
13
+ val expected = JsObject(
14
+ "a" -> JsNumber(3),
15
+ "b" -> JsNumber(6),
16
+ "c_e" -> JsNull
17
+ )
18
+ assert(actual == expected)
19
+ }
20
+ }
@@ -0,0 +1,5 @@
1
+ package org.embulk.parser.twitter_ads_stats
2
+
3
+ import org.scalatest.{DiagrammedAssertions, WordSpec}
4
+
5
+ abstract class UnitSpec extends WordSpec with DiagrammedAssertions
@@ -0,0 +1,57 @@
1
+ package org.embulk.parser.twitter_ads_stats.define
2
+
3
+ import org.embulk.parser.twitter_ads_stats.{MetricElementNames, UnitSpec}
4
+ import spray.json._
5
+
6
+ class MetricsJsonSpec extends UnitSpec {
7
+
8
+ "メトリクスのネストしたJSON構造を .つなぎの要素名を指定してRead できる" in {
9
+
10
+ val metricElementNames = MetricElementNames(
11
+ Map(
12
+ "x" -> Seq("a", "b"),
13
+ "y" -> Seq("c.e", "c.f", "d.e", "d.f")
14
+ )
15
+ )
16
+
17
+ val jsValue =
18
+ """
19
+ |{
20
+ | "a": [
21
+ | 510,
22
+ | 494,
23
+ | 364
24
+ | ],
25
+ | "b": [1,2,3],
26
+ | "c": {
27
+ | "e": [1,2,3],
28
+ | "f": null
29
+ | },
30
+ | "d": {
31
+ | "e": [
32
+ | 2,
33
+ | 3,
34
+ | 4
35
+ | ],
36
+ | "f": [
37
+ | 12,
38
+ | 19,
39
+ | 13
40
+ | ]
41
+ | }
42
+ |}
43
+ """.stripMargin.parseJson
44
+ val actual = new RootJson(metricElementNames).MetricsReader.read(jsValue)
45
+ val expected = Metrics(
46
+ Map(
47
+ "a" -> Some(Vector(510, 494, 364)),
48
+ "b" -> Some(Vector(1, 2, 3)),
49
+ "c_e" -> Some(Vector(1, 2, 3)),
50
+ "c_f" -> None,
51
+ "d_e" -> Some(Vector(2, 3, 4)),
52
+ "d_f" -> Some(Vector(12, 19, 13))
53
+ )
54
+ )
55
+ assert(actual == expected)
56
+ }
57
+ }
@@ -0,0 +1,26 @@
1
+ package org.embulk.parser.twitter_ads_stats.define
2
+
3
+ import java.time.{LocalDate, LocalDateTime}
4
+
5
+ import org.embulk.parser.twitter_ads_stats.UnitSpec
6
+
7
+ class ParamsSpec extends UnitSpec {
8
+ "開始日~(終了日-1)の日程を取得する" in {
9
+ val period = Params(LocalDateTime.of(2017, 1, 1, 0, 0, 0), LocalDateTime.of(2017, 1, 4, 23, 23, 23), "")
10
+ val actual = period.targetDates
11
+ val expected = List(
12
+ LocalDate.of(2017, 1, 1),
13
+ LocalDate.of(2017, 1, 2),
14
+ LocalDate.of(2017, 1, 3)
15
+ )
16
+ assert(actual == expected)
17
+ }
18
+ "開始日から終了日までの全日程は、開始日から終了日が同一な場合は同一な日程となる" in {
19
+ val period = Params(LocalDateTime.of(2017, 1, 1, 0, 0, 0), LocalDateTime.of(2017, 1, 1, 23, 23, 23), "")
20
+ val actual = period.targetDates
21
+ val expected = List(
22
+ LocalDate.of(2017, 1, 1)
23
+ )
24
+ assert(actual == expected)
25
+ }
26
+ }
@@ -0,0 +1,20 @@
1
+ package org.embulk.parser.twitter_ads_stats.define
2
+
3
+ import org.embulk.parser.twitter_ads_stats
4
+ import org.embulk.parser.twitter_ads_stats.UnitSpec
5
+ import spray.json._
6
+
7
+ class RootJsonSpec extends UnitSpec {
8
+ "JSONをパースできる" in {
9
+
10
+ val largeJsonSource = scala.io.Source
11
+ .fromInputStream(
12
+ getClass.getResourceAsStream("/test.json")
13
+ )
14
+ .mkString
15
+ .parseJson
16
+
17
+ val root = new RootJson(twitter_ads_stats.metricElementNames).RootReader.read(largeJsonSource)
18
+ // println(root)
19
+ }
20
+ }
@@ -0,0 +1,356 @@
1
+ package org.embulk.parser.twitter_ads_stats.define
2
+
3
+ import java.time.{LocalDate, LocalDateTime}
4
+
5
+ import org.embulk.parser.twitter_ads_stats.{Column, InvalidMetricTimeSeriesException, MetricElementNames, UnitSpec}
6
+ import org.scalatest.EitherValues._
7
+
8
+ class RootSpec extends UnitSpec {
9
+
10
+ "メトリクス要素名から複数のカラムを解決する" in {
11
+ import RootSpec._
12
+
13
+ val actual = defaultRoot.resolveColumns(metricElementNames)
14
+
15
+ val expected = List(
16
+ Column(
17
+ "123",
18
+ LocalDate.of(2017, 1, 1),
19
+ None,
20
+ "",
21
+ Map(
22
+ "media" -> Map(
23
+ "media_views" -> Some(1),
24
+ "media_engagements" -> None
25
+ ),
26
+ "billing" -> Map(
27
+ "billed_engagements" -> Some(1),
28
+ "billed_charge_local_micro" -> Some(1)
29
+ ),
30
+ "web_conversion" -> Map(
31
+ "conversion_purchases_assisted" -> Some(1)
32
+ )
33
+ )
34
+ ),
35
+ Column(
36
+ "123",
37
+ LocalDate.of(2017, 1, 2),
38
+ None,
39
+ "",
40
+ Map(
41
+ "media" -> Map(
42
+ "media_views" -> Some(2),
43
+ "media_engagements" -> None
44
+ ),
45
+ "billing" -> Map(
46
+ "billed_engagements" -> Some(2),
47
+ "billed_charge_local_micro" -> Some(2)
48
+ ),
49
+ "web_conversion" -> Map(
50
+ "conversion_purchases_assisted" -> Some(2)
51
+ )
52
+ )
53
+ ),
54
+ Column(
55
+ "123",
56
+ LocalDate.of(2017, 1, 1),
57
+ None,
58
+ "",
59
+ Map(
60
+ "media" -> Map(
61
+ "media_views" -> Some(10),
62
+ "media_engagements" -> None
63
+ ),
64
+ "billing" -> Map(
65
+ "billed_engagements" -> Some(10),
66
+ "billed_charge_local_micro" -> Some(10)
67
+ ),
68
+ "web_conversion" -> Map(
69
+ "conversion_purchases_assisted" -> Some(10)
70
+ )
71
+ )
72
+ ),
73
+ Column(
74
+ "123",
75
+ LocalDate.of(2017, 1, 2),
76
+ None,
77
+ "",
78
+ Map(
79
+ "media" -> Map(
80
+ "media_views" -> Some(20),
81
+ "media_engagements" -> None
82
+ ),
83
+ "billing" -> Map(
84
+ "billed_engagements" -> Some(20),
85
+ "billed_charge_local_micro" -> Some(20)
86
+ ),
87
+ "web_conversion" -> Map(
88
+ "conversion_purchases_assisted" -> Some(20)
89
+ )
90
+ )
91
+ ),
92
+ Column(
93
+ "456",
94
+ LocalDate.of(2017, 1, 1),
95
+ None,
96
+ "",
97
+ Map(
98
+ "media" -> Map(
99
+ "media_views" -> Some(1),
100
+ "media_engagements" -> None
101
+ ),
102
+ "billing" -> Map(
103
+ "billed_engagements" -> Some(1),
104
+ "billed_charge_local_micro" -> Some(1)
105
+ ),
106
+ "web_conversion" -> Map(
107
+ "conversion_purchases_assisted" -> Some(1)
108
+ )
109
+ )
110
+ ),
111
+ Column(
112
+ "456",
113
+ LocalDate.of(2017, 1, 2),
114
+ None,
115
+ "",
116
+ Map(
117
+ "media" -> Map(
118
+ "media_views" -> Some(2),
119
+ "media_engagements" -> None
120
+ ),
121
+ "billing" -> Map(
122
+ "billed_engagements" -> Some(2),
123
+ "billed_charge_local_micro" -> Some(2)
124
+ ),
125
+ "web_conversion" -> Map(
126
+ "conversion_purchases_assisted" -> Some(2)
127
+ )
128
+ )
129
+ ),
130
+ Column(
131
+ "456",
132
+ LocalDate.of(2017, 1, 1),
133
+ None,
134
+ "",
135
+ Map(
136
+ "media" -> Map(
137
+ "media_views" -> Some(10),
138
+ "media_engagements" -> None
139
+ ),
140
+ "billing" -> Map(
141
+ "billed_engagements" -> Some(10),
142
+ "billed_charge_local_micro" -> Some(10)
143
+ ),
144
+ "web_conversion" -> Map(
145
+ "conversion_purchases_assisted" -> Some(10)
146
+ )
147
+ )
148
+ ),
149
+ Column(
150
+ "456",
151
+ LocalDate.of(2017, 1, 2),
152
+ None,
153
+ "",
154
+ Map(
155
+ "media" -> Map(
156
+ "media_views" -> Some(20),
157
+ "media_engagements" -> None
158
+ ),
159
+ "billing" -> Map(
160
+ "billed_engagements" -> Some(20),
161
+ "billed_charge_local_micro" -> Some(20)
162
+ ),
163
+ "web_conversion" -> Map(
164
+ "conversion_purchases_assisted" -> Some(20)
165
+ )
166
+ )
167
+ )
168
+ )
169
+ val rightValue = actual.right.value
170
+ assert(rightValue == expected)
171
+ }
172
+ "メトリクス時系列のコレクション要素数以上の期間が指定された時、 InvalidMetricTimeSeriesException となる" in {
173
+ import RootSpec._
174
+
175
+ val actual = createRoot(
176
+ Request(
177
+ params = Params(
178
+ start_time = LocalDateTime.of(2017, 1, 1, 1, 1, 1),
179
+ end_time = LocalDateTime.of(2017, 1, 4, 1, 1, 1),
180
+ placement = ""
181
+ )
182
+ )
183
+ ).resolveColumns(metricElementNames)
184
+
185
+ intercept[InvalidMetricTimeSeriesException] {
186
+ throw actual.left.get
187
+ }
188
+ }
189
+ "指定したメトリクス名のみを解決する" in {
190
+ import RootSpec._
191
+
192
+ val names = MetricElementNames(
193
+ Map(
194
+ "media" ->
195
+ Seq(
196
+ "media_views"
197
+ )
198
+ )
199
+ )
200
+
201
+ val actual = createRoot(
202
+ Request(
203
+ params = Params(
204
+ start_time = LocalDateTime.of(2017, 1, 1, 1, 1, 1),
205
+ end_time = LocalDateTime.of(2017, 1, 1, 1, 1, 1),
206
+ placement = ""
207
+ )
208
+ )
209
+ ).resolveColumns(names)
210
+
211
+ val expected = Seq(
212
+ Column(
213
+ "123",
214
+ LocalDate.of(2017, 1, 1),
215
+ None,
216
+ "",
217
+ Map(
218
+ "media" -> Map(
219
+ "media_views" -> Some(1)
220
+ )
221
+ )
222
+ ),
223
+ Column(
224
+ "123",
225
+ LocalDate.of(2017, 1, 1),
226
+ None,
227
+ "",
228
+ Map(
229
+ "media" -> Map(
230
+ "media_views" -> Some(10)
231
+ )
232
+ )
233
+ ),
234
+ Column(
235
+ "456",
236
+ LocalDate.of(2017, 1, 1),
237
+ None,
238
+ "",
239
+ Map(
240
+ "media" -> Map(
241
+ "media_views" -> Some(1)
242
+ )
243
+ )
244
+ ),
245
+ Column(
246
+ "456",
247
+ LocalDate.of(2017, 1, 1),
248
+ None,
249
+ "",
250
+ Map(
251
+ "media" -> Map(
252
+ "media_views" -> Some(10)
253
+ )
254
+ )
255
+ )
256
+ )
257
+ val rightValue = actual.right.value
258
+
259
+ assert(rightValue == expected)
260
+ }
261
+ }
262
+
263
+ object RootSpec {
264
+
265
+ val defaultRoot: Root = createRoot(
266
+ Request(
267
+ params = Params(
268
+ start_time = LocalDateTime.of(2017, 1, 1, 1, 1, 1),
269
+ end_time = LocalDateTime.of(2017, 1, 3, 1, 1, 1),
270
+ placement = ""
271
+ )
272
+ )
273
+ )
274
+
275
+ def createRoot(request: Request): Root = {
276
+ Root(
277
+ data = Seq(
278
+ Data(
279
+ id = "123",
280
+ id_data = Seq(
281
+ IDData(
282
+ metrics = Metrics(
283
+ Map(
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
+ "conversion_purchases_assisted" -> Some(Vector(1, 2))
289
+ )
290
+ ),
291
+ segment = None
292
+ ),
293
+ IDData(
294
+ metrics = Metrics(
295
+ Map(
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
+ "conversion_purchases_assisted" -> Some(Vector(10, 20))
301
+ )
302
+ ),
303
+ segment = None
304
+ )
305
+ )
306
+ ),
307
+ Data(
308
+ id = "456",
309
+ id_data = Seq(
310
+ IDData(
311
+ metrics = Metrics(
312
+ Map(
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
+ "conversion_purchases_assisted" -> Some(Vector(1, 2))
318
+ )
319
+ ),
320
+ segment = None
321
+ ),
322
+ IDData(
323
+ metrics = Metrics(
324
+ Map(
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
+ "conversion_purchases_assisted" -> Some(Vector(10, 20))
330
+ )
331
+ ),
332
+ segment = None
333
+ )
334
+ )
335
+ )
336
+ ),
337
+ request = request
338
+ )
339
+ }
340
+
341
+ val metricElementNames = MetricElementNames(
342
+ Map(
343
+ "billing" -> Seq(
344
+ "billed_engagements",
345
+ "billed_charge_local_micro"
346
+ ),
347
+ "media" -> Seq(
348
+ "media_views",
349
+ "media_engagements"
350
+ ),
351
+ "web_conversion" -> Seq(
352
+ "conversion_purchases.assisted"
353
+ )
354
+ )
355
+ )
356
+ }