embulk-input-mixpanel 0.5.15 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,745 @@
1
+ require "prepare_embulk"
2
+ require "override_assert_raise"
3
+ require "embulk/input/mixpanel"
4
+ require "embulk/input/service/base_service"
5
+ require "embulk/input/service/jql_service"
6
+ require "active_support/core_ext/time"
7
+ require "json"
8
+
9
+ module Embulk
10
+ module Input
11
+ class JQLServiceTest < Test::Unit::TestCase
12
+ include OverrideAssertRaise
13
+
14
+ API_SECRET = "api_secret".freeze
15
+ FROM_DATE = "2015-02-22".freeze
16
+ TO_DATE = "2015-03-02".freeze
17
+ DAYS = 8
18
+ SLICE_RANGE = 10
19
+ DATES = Date.parse(FROM_DATE)..(Date.parse(FROM_DATE) + DAYS - 1)
20
+ TIMEZONE = "Asia/Tokyo".freeze
21
+ SMALL_NUM_OF_RECORDS = 10
22
+ DURATIONS = [
23
+ {from_date: FROM_DATE, to_date: "2015-02-28"}, # It has 7 days between 2015-02-22 and 2015-02-28
24
+ {from_date: "2015-03-01", to_date: TO_DATE},
25
+ ]
26
+ JQL_SCRIPT = 'function main() { return Events({ from_date: "2015-01-01", to_date:"2015-01-02"}'
27
+ JQL_SCRIPT_WITH_PARAMS = 'function main() { return Events({ from_date: params.from_date, to_date: to_date}'
28
+
29
+ def setup
30
+ setup_client
31
+ setup_logger
32
+ stub(Embulk::Input::MixpanelApi::Client).mixpanel_available? {true}
33
+ end
34
+
35
+ def setup_client
36
+ params = {
37
+ params: nil,
38
+ script: nil,
39
+ }
40
+
41
+ any_instance_of(MixpanelApi::Client) do |klass|
42
+ DURATIONS.each do |duration|
43
+ from_date = duration[:from_date]
44
+ to_date = duration[:to_date]
45
+
46
+ params = params.merge(from_date: from_date, to_date: to_date)
47
+ stub(klass).send_jql_script_small_dataset(anything) {records}
48
+ end
49
+ end
50
+ end
51
+
52
+ def satisfy_task_ignore_start_time(expected_task)
53
+ satisfy {|input_task|
54
+ assert_equal(expected_task, input_task)
55
+ true
56
+ }
57
+ end
58
+
59
+ def setup_logger
60
+ stub(Embulk).logger {::Logger.new(IO::NULL)}
61
+ end
62
+
63
+ class GuessTest < self
64
+ def setup
65
+ # Do nothing from parent
66
+ mute_warn
67
+ stub(Embulk::Input::MixpanelApi::Client).mixpanel_available? {true}
68
+ end
69
+
70
+ def test_from_date_old_date
71
+ config = {
72
+ type: "mixpanel",
73
+ jql_mode: true,
74
+ incremental: false,
75
+ jql_script: JQL_SCRIPT,
76
+ api_secret: API_SECRET,
77
+ from_date: FROM_DATE,
78
+ timezone: TIMEZONE,
79
+ }
80
+
81
+ stub_export_all
82
+ mock(Embulk.logger).info(/^Guessing.*#{Regexp.escape FROM_DATE}\.\./)
83
+
84
+ actual = Mixpanel.guess(embulk_config(config))
85
+ assert_equal(expected, actual)
86
+ end
87
+
88
+ def test_from_date_future
89
+ config = {
90
+ type: "mixpanel",
91
+ api_secret: API_SECRET,
92
+ jql_mode: true,
93
+ incremental: false,
94
+ jql_script: JQL_SCRIPT,
95
+ timezone: TIMEZONE,
96
+ from_date: (today + 1).to_s
97
+ }
98
+
99
+ stub_export_all
100
+ mock(Embulk.logger).info(/Guessing.*#{Regexp.escape Embulk::Input::Service::JqlService.new(config).default_guess_start_date(TIMEZONE).to_s}/)
101
+
102
+ Mixpanel.guess(embulk_config(config))
103
+ end
104
+
105
+ def test_from_date_yesterday
106
+ from_date = (today - 1).to_s
107
+ config = {
108
+ type: "mixpanel",
109
+ api_secret: API_SECRET,
110
+ from_date: from_date,
111
+ timezone: TIMEZONE,
112
+ incremental: false,
113
+ jql_mode: true,
114
+ jql_script: JQL_SCRIPT,
115
+ }
116
+
117
+ stub_export_all
118
+ mock(Embulk.logger).info(/Guessing.*#{Regexp.escape from_date}/)
119
+
120
+ Mixpanel.guess(embulk_config(config))
121
+ end
122
+
123
+ def test_no_from_date
124
+ config = {
125
+ type: "mixpanel",
126
+ api_secret: API_SECRET,
127
+ timezone: TIMEZONE,
128
+ jql_mode: true,
129
+ incremental: false,
130
+ jql_script: JQL_SCRIPT,
131
+ }
132
+
133
+ stub_export_all
134
+ mock(Embulk.logger).info(/Guessing.*#{Regexp.escape Embulk::Input::Service::JqlService.new(config).default_guess_start_date(TIMEZONE).to_s}/)
135
+
136
+ Mixpanel.guess(embulk_config(config))
137
+ end
138
+
139
+ def test_json_type
140
+ sample_records = records.map do |r|
141
+ r.merge("properties"=>{"time"=>1, "array"=>[1, 2], "hash"=>{foo: "FOO"}})
142
+ end
143
+
144
+ config = {
145
+ type: "mixpanel",
146
+ api_secret: API_SECRET,
147
+ timezone: TIMEZONE,
148
+ jql_mode: true,
149
+ incremental: false,
150
+ jql_script: JQL_SCRIPT,
151
+ }
152
+
153
+ actual = Embulk::Input::Service::JqlService.new(config).guess_from_records(sample_records)
154
+
155
+ assert actual.include?(name: "properties", type: :json)
156
+ end
157
+
158
+ def test_mixpanel_is_down
159
+ stub(Embulk::Input::MixpanelApi::Client).mixpanel_available? {false}
160
+ config = {
161
+ type: "mixpanel",
162
+ api_secret: API_SECRET,
163
+ timezone: TIMEZONE,
164
+ jql_mode: true,
165
+ incremental: false,
166
+ jql_script: JQL_SCRIPT,
167
+ }
168
+
169
+ assert_raise(Embulk::DataError) do
170
+ Mixpanel.guess(embulk_config(config))
171
+ end
172
+ end
173
+
174
+ private
175
+
176
+ def stub_export_all
177
+ any_instance_of(MixpanelApi::Client) do |klass|
178
+ stub(klass).send_brief_checked_jql_script(anything) {}
179
+ stub(klass).send_jql_script_small_dataset(anything) {records}
180
+ end
181
+ end
182
+
183
+ def mute_warn
184
+ stub(Embulk.logger).warn(anything) {}
185
+ end
186
+
187
+ def embulk_config(config)
188
+ DataSource[*config.to_a.flatten(1)]
189
+ end
190
+
191
+ def expected
192
+ {"columns"=>
193
+ [{:name=>:name, :type=>:string},
194
+ {:name=>:distinct_id, :type=>:string},
195
+ {:name=>:labels, :type=>:json},
196
+ {:name=>:time, :type=>:long},
197
+ {:name=>:sampling_factor, :type=>:long},
198
+ {:name=>:dataset, :type=>:string},
199
+ {:name=>:properties, :type=>:json}]
200
+ }
201
+ end
202
+ end
203
+
204
+ class TransactionDateTest < self
205
+ def test_valid_from_date
206
+ from_date = "2015-08-14"
207
+ mock(Mixpanel).resume(anything, anything, 1)
208
+
209
+ Mixpanel.transaction(transaction_config(from_date))
210
+ end
211
+
212
+ def test_invalid_from_date
213
+ from_date = "2015-08-41"
214
+
215
+ assert_raise(Embulk::ConfigError) do
216
+ Mixpanel.transaction(transaction_config(from_date))
217
+ end
218
+ end
219
+
220
+ def test_future
221
+ from_date = (today + 10).to_s
222
+ mock(Mixpanel).resume(anything, anything, 1)
223
+
224
+ Mixpanel.transaction(transaction_config(from_date))
225
+ end
226
+
227
+ def test_negative_days
228
+ assert_raise(Embulk::ConfigError) do
229
+ Mixpanel.transaction(transaction_config((today - 1).to_s).merge(fetch_days: -1))
230
+ end
231
+ end
232
+
233
+ def test_default_configuration
234
+ stub(Mixpanel).resume {|task|
235
+ assert_true(task[:jql_mode])
236
+ assert_true(task[:incremental])
237
+ }
238
+ Mixpanel.transaction(transaction_config(today))
239
+ end
240
+
241
+ private
242
+
243
+ def transaction_config(from_date)
244
+ _config = config.merge(
245
+ from_date: from_date,
246
+ timezone: TIMEZONE,
247
+ columns: schema,
248
+ jql_mode: true,
249
+ jql_script: JQL_SCRIPT,
250
+ )
251
+ DataSource[*_config.to_a.flatten(1)]
252
+ end
253
+ end
254
+
255
+ class TransactionTest < self
256
+ class FromDateTest < self
257
+ def setup
258
+ any_instance_of(MixpanelApi::Client) do |klass|
259
+ stub(klass).send_brief_checked_jql_script(anything) {}
260
+ end
261
+ end
262
+
263
+ def test_info
264
+ stub(Mixpanel).resume(satisfy_task_ignore_start_time(task.merge(dates: target_dates)), columns, 1, &control)
265
+
266
+ info_message_regexp = /#{Regexp.escape(target_dates.first)}.+#{Regexp.escape(target_dates.last)}/
267
+ mock(Embulk.logger).info(info_message_regexp)
268
+ stub(Embulk.logger).warn
269
+
270
+ Mixpanel.transaction(transaction_config, &control)
271
+ end
272
+
273
+ def test_warn
274
+ stub(Mixpanel).resume(satisfy_task_ignore_start_time(task.merge(dates: target_dates)), columns, 1, &control)
275
+ stub(Embulk.logger).info
276
+
277
+ ignore_dates = dates.map {|date| date.to_s}.to_a - target_dates
278
+ warn_message_regexp = /#{Regexp.escape(ignore_dates.first)}.+#{Regexp.escape(ignore_dates.last)}/
279
+ mock(Embulk.logger).warn(warn_message_regexp)
280
+
281
+ Mixpanel.transaction(transaction_config, &control)
282
+ end
283
+
284
+ private
285
+
286
+ def dates
287
+ (today - 10)..(today + 10)
288
+ end
289
+
290
+ def target_dates
291
+ dates.find_all {|d| d <= today}.map {|date| date.to_s}
292
+ end
293
+
294
+ def transaction_config
295
+ _config = config.merge(
296
+ from_date: dates.first.to_s,
297
+ fetch_days: dates.to_a.size,
298
+ timezone: TIMEZONE,
299
+ columns: schema,
300
+ jql_mode: true,
301
+ jql_script: JQL_SCRIPT,
302
+ )
303
+ DataSource[*_config.to_a.flatten(1)]
304
+ end
305
+ end
306
+
307
+ class TimezoneTest < self
308
+ def test_valid_timezone
309
+ timezone = TIMEZONE
310
+ mock(Mixpanel).resume(satisfy_task_ignore_start_time(transaction_task(timezone)), columns, 1, &control)
311
+ Mixpanel.transaction(transaction_config(timezone), &control)
312
+ end
313
+
314
+ def test_invalid_timezone
315
+ timezone = "#{TIMEZONE}ooooo"
316
+
317
+ assert_raise(Embulk::ConfigError) do
318
+ Mixpanel.transaction(transaction_config(timezone), &control)
319
+ end
320
+ end
321
+
322
+ private
323
+
324
+ def transaction_task(timezone)
325
+ task.merge(
326
+ dates: DATES.map {|date| date.to_s},
327
+ api_secret: API_SECRET,
328
+ incremental: true,
329
+ timezone: timezone,
330
+ schema: schema
331
+ )
332
+ end
333
+
334
+ def transaction_config(timezone)
335
+ _config = config.merge(
336
+ timezone: timezone,
337
+ columns: schema,
338
+ )
339
+ DataSource[*_config.to_a.flatten(1)]
340
+ end
341
+ end
342
+
343
+ class DaysTest < self
344
+ def test_valid_days
345
+ days = 5
346
+
347
+ mock(Mixpanel).resume(satisfy_task_ignore_start_time(transaction_task(days)), columns, 1, &control)
348
+ Mixpanel.transaction(transaction_config(days), &control)
349
+ end
350
+
351
+ def test_next_to_date
352
+ next_config_diff = Mixpanel.resume(transaction_task(1).merge(incremental: true), columns, 1) do
353
+ [{to_date: today.to_s, latest_fetched_time: 1502707247000}]
354
+ end
355
+ assert_equal((today).to_s, next_config_diff[:from_date])
356
+ end
357
+
358
+ def test_invalid_days
359
+ days = 0
360
+
361
+ assert_raise(Embulk::ConfigError) do
362
+ Mixpanel.transaction(transaction_config(days), &control)
363
+ end
364
+ end
365
+
366
+ def test_valid_days_with_backfill
367
+ days = 5
368
+
369
+ stub(Mixpanel).resume() do |task|
370
+ assert_equal(["2015-02-17", "2015-02-18", "2015-02-19", "2015-02-20", "2015-02-21", "2015-02-22", "2015-02-23", "2015-02-24", "2015-02-25", "2015-02-26"], task[:dates])
371
+ end
372
+ config=transaction_config(days).merge("back_fill_days" => 5, "incremental_column" => "test_column", "latest_fetched_time" => 1501599491000)
373
+ Mixpanel.transaction(config, &control)
374
+ end
375
+
376
+ def test_valid_days_with_backfill_first_run
377
+ days = 5
378
+ stub(Mixpanel).resume() do |task|
379
+ assert_equal(transaction_task(days)[:dates], task[:dates])
380
+ end
381
+ config=transaction_config(days).merge("back_fill_days" => 5, "incremental_column" => "test_column")
382
+ Mixpanel.transaction(config, &control)
383
+ end
384
+
385
+ private
386
+
387
+ def transaction_task(days)
388
+ from_date = Date.parse(FROM_DATE)
389
+ task.merge(
390
+ dates: (from_date..(from_date + days - 1)).map {|date| date.to_s},
391
+ api_secret: API_SECRET,
392
+ timezone: TIMEZONE,
393
+ schema: schema,
394
+ jql_mode: true,
395
+ jql_script: JQL_SCRIPT,
396
+ )
397
+ end
398
+
399
+ def transaction_config(days)
400
+ _config = config.merge(
401
+ fetch_days: days,
402
+ columns: schema,
403
+ timezone: TIMEZONE,
404
+ jql_mode: true,
405
+ jql_script: JQL_SCRIPT,
406
+ )
407
+ DataSource[*_config.to_a.flatten(1)]
408
+ end
409
+ end
410
+
411
+ class ValidateTest < self
412
+ def setup_client
413
+ any_instance_of(MixpanelApi::Client) do |klass|
414
+ stub(klass).send_jql_script(anything) {[1]}
415
+ stub(klass).send_jql_script_small_dataset(anything) {[1]}
416
+ end
417
+ end
418
+
419
+ def setup
420
+ super
421
+ @page_builder = Object.new
422
+ @plugin = Mixpanel.new(task, nil, nil, @page_builder)
423
+ end
424
+
425
+ def test_unsupport_data_format
426
+ assert_raise(Embulk::ConfigError) do
427
+ Mixpanel.guess(embulk_config)
428
+ end
429
+ end
430
+ end
431
+
432
+ def control
433
+ proc {} # dummy
434
+ end
435
+
436
+ def transaction_task
437
+ task.merge(
438
+ dates: DATES.map {|date| date.to_s},
439
+ api_secret: API_SECRET,
440
+ timezone: TIMEZONE,
441
+ schema: schema,
442
+ jql_mode: true,
443
+ jql_script: JQL_SCRIPT,
444
+ )
445
+ end
446
+
447
+ def columns
448
+ schema.map do |col|
449
+ Column.new(nil, col["name"], col["type"].to_sym)
450
+ end
451
+ end
452
+ end
453
+
454
+ def test_export_params
455
+ config_params = [
456
+ :type, "mixpanel",
457
+ :api_secret, API_SECRET,
458
+ :from_date, FROM_DATE,
459
+ :to_date, TO_DATE,
460
+ :jql_mode, true,
461
+ :jql_script, JQL_SCRIPT,
462
+ ]
463
+
464
+ config = DataSource[*config_params]
465
+
466
+ expected = {
467
+ params: {:from_date=>:from_date, :to_date=>:today},
468
+ script: "function main() { return Events({ from_date: \"2015-01-01\", to_date:\"2015-01-02\"}",
469
+ }
470
+ actual = Embulk::Input::Service::JqlService.new(config).parameters(JQL_SCRIPT, :from_date, :today)
471
+
472
+ assert_equal(expected, actual)
473
+ end
474
+
475
+ sub_test_case "retry" do
476
+ def setup
477
+ @page_builder = Object.new
478
+ @plugin = Mixpanel.new(task, nil, nil, @page_builder)
479
+ @plugin.init
480
+ @httpclient = HTTPClient.new
481
+ stub(HTTPClient).new {@httpclient}
482
+ stub(@page_builder).add {}
483
+ stub(@page_builder).finish {}
484
+ stub(Embulk.logger).warn {}
485
+ stub(Embulk.logger).info {}
486
+ stub(Embulk::Input::MixpanelApi::Client).mixpanel_available? {true}
487
+ end
488
+
489
+ test "200 and don't support format" do
490
+ stub_response(200)
491
+ mock(Embulk.logger).warn(/Retrying/).never
492
+ assert_raise(Embulk::DataError) do
493
+ @plugin.run
494
+ end
495
+ end
496
+
497
+ test "400" do
498
+ stub_response(400)
499
+ mock(Embulk.logger).warn(/Retrying/).never
500
+ assert_raise(Embulk::ConfigError) do
501
+ @plugin.run
502
+ end
503
+ end
504
+
505
+ test "401" do
506
+ stub_response(401)
507
+ mock(Embulk.logger).warn(/Retrying/).never
508
+ assert_raise(Embulk::ConfigError) do
509
+ @plugin.run
510
+ end
511
+ end
512
+
513
+ test "500" do
514
+ stub_response(500)
515
+ mock(Embulk.logger).warn(/Retrying/).times(task[:retry_limit])
516
+ assert_raise(PerfectRetry::TooManyRetry) do
517
+ @plugin.run
518
+ end
519
+ end
520
+
521
+ test "timeout" do
522
+ stub(@httpclient).post {raise HTTPClient::TimeoutError, "timeout"}
523
+ mock(Embulk.logger).warn(/Retrying/).times(task[:retry_limit])
524
+
525
+ assert_raise(PerfectRetry::TooManyRetry) do
526
+ @plugin.run
527
+ end
528
+ end
529
+
530
+ test "Mixpanel is down" do
531
+ stub(Embulk::Input::MixpanelApi::Client).mixpanel_available? {false}
532
+
533
+ assert_raise(Embulk::ConfigError) do
534
+ @plugin.run
535
+ end
536
+ end
537
+
538
+ def stub_response(code)
539
+ stub(@httpclient.test_loopback_http_response).shift {"HTTP/1.1 #{code} \r\n\r\n"}
540
+ end
541
+
542
+ def task
543
+ {
544
+ api_secret: API_SECRET,
545
+ jql_endpoint: "https://mixpanel.com/api/2.0/jql/",
546
+ timezone: TIMEZONE,
547
+ incremental: true,
548
+ schema: schema,
549
+ dates: DATES.to_a.map(&:to_s),
550
+ retry_initial_wait_sec: 2,
551
+ retry_limit: 3,
552
+ jql_mode: true,
553
+ jql_script: JQL_SCRIPT,
554
+ slice_range: SLICE_RANGE,
555
+ }
556
+ end
557
+ end
558
+
559
+ class RunTest < self
560
+ def setup_client
561
+ any_instance_of(MixpanelApi::Client) do |klass|
562
+ stub(klass).send_jql_script(anything) {data}
563
+ end
564
+ end
565
+
566
+ def setup
567
+ super
568
+ @page_builder = Object.new
569
+ @plugin = Mixpanel.new(DataSource[task.to_a], nil, nil, @page_builder)
570
+ end
571
+
572
+ def test_run
573
+ any_instance_of(Embulk::Input::Service::JqlService) do |klass|
574
+ stub(klass).preview? {false}
575
+ end
576
+ mock(@page_builder).add(anything).times(data.length)
577
+ mock(@page_builder).finish
578
+ task_report = @plugin.run
579
+ assert_equal("2015-03-01", task_report[:to_date])
580
+ end
581
+
582
+ def test_run_with_incremental_column
583
+ any_instance_of(Embulk::Input::Service::JqlService) do |klass|
584
+ stub(klass).preview? {false}
585
+ end
586
+ mock(@page_builder).add(anything).times(data.length)
587
+ mock(@page_builder).finish
588
+ plugin = Mixpanel.new(DataSource[task.to_a].merge({"incremental_column"=>"time", "latest_fetched_time"=>1452027551999}), nil, nil, @page_builder)
589
+ task_report = plugin.run
590
+ assert_equal("2015-03-01", task_report[:to_date])
591
+ assert_equal("1452027552000", task_report[:latest_fetched_time])
592
+ end
593
+
594
+ def test_run_with_incremental_column_skip
595
+ any_instance_of(Embulk::Input::Service::JqlService) do |klass|
596
+ stub(klass).preview? {false}
597
+ end
598
+
599
+ mock(@page_builder).add(anything).times(0)
600
+ mock(@page_builder).finish
601
+ plugin = Mixpanel.new(DataSource[task.to_a].merge({"incremental_column"=>"time", "latest_fetched_time"=>1452027552001}), nil, nil, @page_builder)
602
+ task_report = plugin.run
603
+ assert_equal("2015-03-01", task_report[:to_date])
604
+ assert_equal("1452027552001", task_report[:latest_fetched_time])
605
+ end
606
+
607
+ class SliceRangeRunTest < self
608
+ def test_default_slice_range
609
+ plugin = Mixpanel.new(task.merge(slice_range: 4), nil, nil, @page_builder)
610
+ any_instance_of(Embulk::Input::Service::JqlService) do |klass|
611
+ stub(klass).preview? {false}
612
+ end
613
+
614
+ mock(@page_builder).add(anything).times(data.length * 2)
615
+ mock(@page_builder).finish
616
+ plugin.run
617
+ end
618
+ end
619
+
620
+ def test_preview
621
+ any_instance_of(MixpanelApi::Client) do |klass|
622
+ stub(klass).request_jql(anything) {Struct.new(:code, :body).new(200, records.to_json)}
623
+ end
624
+ any_instance_of(Embulk::Input::Service::JqlService) do |klass|
625
+ stub(klass).preview? {true}
626
+ end
627
+ mock(@page_builder).add(anything).times(SMALL_NUM_OF_RECORDS)
628
+ mock(@page_builder).finish
629
+ @plugin.run
630
+ end
631
+ end
632
+
633
+ private
634
+
635
+ def schema
636
+ [
637
+ {"name"=>"name", "type"=>"string"},
638
+ {"name"=>"distinct_id", "type"=>"string"},
639
+ {"name"=>"labels", "type"=>"json"},
640
+ {"name"=>"time", "type"=>"long"},
641
+ {"name"=>"sampling_factor", "type"=>"long"},
642
+ {"name"=>"dataset", "type"=>"string"},
643
+ {"name"=>"properties", "type"=>"json"}
644
+ ]
645
+ end
646
+
647
+ def task
648
+ {
649
+ api_secret: API_SECRET,
650
+ jql_endpoint: "https://mixpanel.com/api/2.0/jql/",
651
+ timezone: TIMEZONE,
652
+ incremental: true,
653
+ schema: schema,
654
+ dates: DATES.to_a.map(&:to_s),
655
+ retry_initial_wait_sec: 2,
656
+ retry_limit: 3,
657
+ jql_mode: true,
658
+ jql_script: JQL_SCRIPT,
659
+ slice_range: SLICE_RANGE,
660
+ incremental_column: nil,
661
+ latest_fetched_time: 0,
662
+ }
663
+ end
664
+
665
+ def records
666
+ [
667
+ {
668
+ "name": "pageview",
669
+ "distinct_id": "02a99746-0f52-4acd-9a53-7deb763803ca",
670
+ "labels": [],
671
+ "time": 1452027552000,
672
+ "sampling_factor": 1,
673
+ "dataset": "$mixpanel",
674
+ "properties": {
675
+ "$email": "Alexander.Davidson@hotmailx.com",
676
+ "$import": true,
677
+ "country": "UK",
678
+ "load_time_ms": 4
679
+ }
680
+ },
681
+ ] * 30
682
+ end
683
+
684
+ def data
685
+ [
686
+ {
687
+ "name"=> "pageview",
688
+ "distinct_id"=> "02a99746-0f52-4acd-9a53-7deb763803ca",
689
+ "labels"=> [],
690
+ "time"=> 1452027552000,
691
+ "sampling_factor"=> 1,
692
+ "dataset"=> "$mixpanel",
693
+ "properties"=> {
694
+ "$email"=> "Alexander.Davidson@hotmailx.com",
695
+ "$import"=> true,
696
+ "country"=> "UK",
697
+ "load_time_ms"=> 4
698
+ }
699
+ },
700
+ ] * 30
701
+ end
702
+
703
+ def record
704
+ {
705
+ "name": "pageview",
706
+ "distinct_id": "02a99746-0f52-4acd-9a53-7deb763803ca",
707
+ "labels": [],
708
+ "time": 1452027552000,
709
+ "sampling_factor": 1,
710
+ "dataset": "$mixpanel",
711
+ "properties": {
712
+ "$email": "Alexander.Davidson@hotmailx.com",
713
+ "$import": true,
714
+ "country": "UK",
715
+ "load_time_ms": 4
716
+ }
717
+ }
718
+
719
+ end
720
+
721
+ def config
722
+ {
723
+ type: "mixpanel",
724
+ api_secret: API_SECRET,
725
+ from_date: FROM_DATE,
726
+ timezone: TIMEZONE,
727
+ fetch_days: DAYS,
728
+ retry_initial_wait_sec: 2,
729
+ retry_limit: 3,
730
+ jql_mode: true,
731
+ jql_script: JQL_SCRIPT,
732
+ slice_range: SLICE_RANGE,
733
+ }
734
+ end
735
+
736
+ def embulk_config
737
+ DataSource[*config.to_a.flatten(1)]
738
+ end
739
+
740
+ def today
741
+ ActiveSupport::TimeZone[TIMEZONE].today
742
+ end
743
+ end
744
+ end
745
+ end