embulk-input-zendesk 0.1.2 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eddb031e2e41652c6c825d2caaefaf618125fa07
4
- data.tar.gz: 2b76a1a7a750008b20823c8dbb7f4794ad3d92dd
3
+ metadata.gz: 6ed2bcc09ae824662952c09511b06045ebef3641
4
+ data.tar.gz: 95281d1ac59e524c36356c9c670de71c067fb326
5
5
  SHA512:
6
- metadata.gz: eb4d2dc9a0b128bf2c66f259750b9db6f27f9d67b512e48a94cd1d4c4dfd1aa52b8676c37a9f5449d43f6118605bb820d3b06e5f7b5b2020af147c172d85881f
7
- data.tar.gz: 519e20eebcb4a732174b6b71be9ae01f097b00108b2d47e71299a4adc6b205fbf34a9c160beebdf31acf7609e2ac9103b6b615a2776838e4658aa1fb1bbb41b3
6
+ metadata.gz: 4994efad0da2b8872ceda6c29a0cd5eda72136194a33e71c6e6a418dbf412f95c289463d243c7e812b30c23a56f79dee19c344461667b722e10231c44cdcdfb5
7
+ data.tar.gz: b9b63e1fde1f724cc418e39a529c9bf0f3ebbf831bfdd5f268ef3027b71b25fa7987f3370db058ce51c12e9e704f01a0bac55d40f26305104a3debf92fd60ab1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.1.3 - 2016-03-15
2
+
3
+ * [enhancement] Support more targets [#8](https://github.com/treasure-data/embulk-input-zendesk/pull/8)
4
+ * [enhancement] Enable json type [#7](https://github.com/treasure-data/embulk-input-zendesk/pull/7)
5
+
1
6
  ## 0.1.2 - 2016-01-29
2
7
 
3
8
  * [maintenance] Add authors @muga and @sakama.
data/README.md CHANGED
@@ -22,7 +22,8 @@ Required Embulk version >= 0.8.1.
22
22
 
23
23
  - **login_url**: Login URL for Zendesk (string, required)
24
24
  - **auth_method**: `basic`, `token`, or `oauth`. For more detail on [zendesk document](https://developer.zendesk.com/rest_api/docs/core/introduction#security-and-authentication). (string, required)
25
- - **target**: Which export Zendesk resource. Currently supported are `tickets`, `ticket_events`, `users`, `organizations`, `ticket_fields` or `ticket_forms`. (string, required)
25
+ - **target**: Which export Zendesk resource. Currently supported are `tickets`, `ticket_events`, `users`, `organizations`, `ticket_fields`, `ticket_forms` or `ticket_metrics`. (string, required)
26
+ - **includes**: Will fetch sub resources. For example, ticket has ticket_audits, ticket_comments. See below example config. (array, default: `[]`)
26
27
  - **username**: The user name a.k.a. email. Required if `auth_method` is `basic` or `token`. (string, default: `null`)
27
28
  - **password**: Password. required if `auth_method` is `basic`. (string, default: `null`)
28
29
  - **token**: Token. required if `auth_method` is `token`. (string, default: `null`)
@@ -42,6 +43,9 @@ in:
42
43
  username: jdoe@example.com
43
44
  token: 6wiIBWbGkBMo1mRDMuVwkw1EPsNkeUj95PIz2akv
44
45
  target: tickets
46
+ includes:
47
+ - audits
48
+ - comments
45
49
  start_time: "2015-01-01 00:00:00+0000"
46
50
  ```
47
51
 
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ task default: :test
5
5
 
6
6
  desc "Run tests"
7
7
  task :test do
8
- ruby("test/run-test.rb", "--use-color=yes", "--collector=dir")
8
+ ruby("--debug", "test/run-test.rb", "--use-color=yes", "--collector=dir")
9
9
  end
10
10
 
11
11
  desc "Run tests with coverage"
@@ -1,7 +1,7 @@
1
1
 
2
2
  Gem::Specification.new do |spec|
3
3
  spec.name = "embulk-input-zendesk"
4
- spec.version = "0.1.2"
4
+ spec.version = "0.1.3"
5
5
  spec.authors = ["uu59", "muga", "sakama"]
6
6
  spec.summary = "Zendesk input plugin for Embulk"
7
7
  spec.description = "Loads records from Zendesk."
@@ -7,11 +7,9 @@ module Embulk
7
7
  attr_reader :config
8
8
 
9
9
  PARTIAL_RECORDS_SIZE = 50
10
- AVAILABLE_TARGETS = %w(
11
- tickets ticket_events users organizations
12
- ticket_fields ticket_forms
13
- ).freeze
14
- AVAILABLE_INCREMENTAL_EXPORT = AVAILABLE_TARGETS - %w(ticket_fields ticket_forms)
10
+ AVAILABLE_INCREMENTAL_EXPORT = %w(tickets users organizations ticket_events).freeze
11
+ UNAVAILABLE_INCREMENTAL_EXPORT = %w(ticket_fields ticket_forms ticket_metrics).freeze
12
+ AVAILABLE_TARGETS = AVAILABLE_INCREMENTAL_EXPORT + UNAVAILABLE_INCREMENTAL_EXPORT
15
13
 
16
14
  def initialize(config)
17
15
  @config = config
@@ -47,10 +45,11 @@ module Embulk
47
45
 
48
46
  def validate_target
49
47
  unless AVAILABLE_TARGETS.include?(config[:target])
50
- raise Embulk::ConfigError.new("target: '#{config[:target]}' is not supported.")
48
+ raise Embulk::ConfigError.new("target: '#{config[:target]}' is not supported. Supported targets are #{AVAILABLE_TARGETS.join(", ")}.")
51
49
  end
52
50
  end
53
51
 
52
+ # they have both Incremental API and non-incremental API
54
53
  %w(tickets users organizations).each do |target|
55
54
  define_method(target) do |partial = true, start_time = 0, &block|
56
55
  if partial
@@ -61,22 +60,32 @@ module Embulk
61
60
  end
62
61
  end
63
62
 
64
- def ticket_events(partial = true, start_time = 0, &block)
65
- # NOTE: ticket_events only have incremental export API
66
- path = "/api/v2/incremental/ticket_events"
67
- incremental_export(path, "ticket_events", start_time, [], &block)
63
+ # they have incremental API only
64
+ %w(ticket_events).each do |target|
65
+ define_method(target) do |partial = true, start_time = 0, &block|
66
+ path = "/api/v2/incremental/#{target}"
67
+ incremental_export(path, target, start_time, [], &block)
68
+ end
68
69
  end
69
70
 
70
- def ticket_fields(partial = true, start_time = 0, &block)
71
- # NOTE: ticket_fields only have export API (not incremental)
72
- path = "/api/v2/ticket_fields.json"
73
- export(path, "ticket_fields", 1000, &block)
71
+ # they have non-incremental API only
72
+ UNAVAILABLE_INCREMENTAL_EXPORT.each do |target|
73
+ define_method(target) do |partial = true, start_time = 0, &block|
74
+ path = "/api/v2/#{target}.json"
75
+ export(path, target, partial ? PARTIAL_RECORDS_SIZE : 1000, &block)
76
+ end
74
77
  end
75
78
 
76
- def ticket_forms(partial = true, start_time = 0, &block)
77
- # NOTE: ticket_forms only have export API (not incremental)
78
- path = "/api/v2/ticket_forms.json"
79
- export(path, "ticket_forms", 1000, &block)
79
+ def fetch_subresource(record_id, base, target)
80
+ response = request("/api/v2/#{base}/#{record_id}/#{target}.json")
81
+ return [] if response.status == 404
82
+
83
+ begin
84
+ data = JSON.parse(response.body)
85
+ data[target]
86
+ rescue => e
87
+ raise Embulk::DataError.new(e)
88
+ end
80
89
  end
81
90
 
82
91
  private
@@ -163,12 +172,14 @@ module Embulk
163
172
  u.path = path
164
173
 
165
174
  retryer.with_retry do
175
+ Embulk.logger.debug "Fetching #{u.to_s}"
166
176
  response = httpclient.get(u.to_s, query, follow_redirect: true)
167
177
 
168
178
  # https://developer.zendesk.com/rest_api/docs/core/introduction#response-format
169
179
  status_code = response.status
170
180
  case status_code
171
- when 200
181
+ when 200, 404
182
+ # 404 would be returned e.g. ticket comments are empty (on fetch_subresource method)
172
183
  response
173
184
  when 400, 401
174
185
  raise Embulk::ConfigError.new("[#{status_code}] #{response.body}")
@@ -58,12 +58,16 @@ module Embulk
58
58
  hash[:type] = :json
59
59
  end
60
60
 
61
- # NOTE: current version don't support JSON type
62
- next if hash[:type] == :json
63
-
64
61
  hash
65
62
  end
66
63
 
64
+ task[:includes].each do |ent|
65
+ columns << {
66
+ name: ent,
67
+ type: :json
68
+ }
69
+ end
70
+
67
71
  return {"columns" => columns.compact}
68
72
  end
69
73
 
@@ -81,6 +85,7 @@ module Embulk
81
85
  retry_initial_wait_sec: config.param("retry_initial_wait_sec", :integer, default: 1),
82
86
  incremental: config.param("incremental", :bool, default: true),
83
87
  schema: config.param(:columns, :array, default: []),
88
+ includes: config.param(:includes, :array, default: []),
84
89
  }
85
90
  end
86
91
 
@@ -95,8 +100,8 @@ module Embulk
95
100
  args << @start_time.to_i
96
101
  end
97
102
 
98
- client = Client.new(task)
99
103
  last_data = client.public_send(method, *args) do |record|
104
+ record = fetch_related_object(record)
100
105
  values = extract_values(record)
101
106
  page_builder.add(values)
102
107
  end
@@ -116,6 +121,22 @@ module Embulk
116
121
 
117
122
  private
118
123
 
124
+ def fetch_related_object(record)
125
+ (task[:includes] || []).each do |ent|
126
+ if preview?
127
+ # Fetching subresource consume ~2 sec for each record. it is too long to preview. so the dummy value used.
128
+ record[ent] = [{dummy: "(#{ent}) dummy value for preview"}]
129
+ else
130
+ record[ent] = client.fetch_subresource(record["id"], task[:target], ent)
131
+ end
132
+ end
133
+ record
134
+ end
135
+
136
+ def client
137
+ Client.new(task)
138
+ end
139
+
119
140
  def preview?
120
141
  org.embulk.spi.Exec.isPreview()
121
142
  rescue java.lang.NullPointerException => e
@@ -132,13 +132,162 @@ module Embulk
132
132
  assert actual.include?(name: "id", type: :long)
133
133
  assert actual.include?(name: "created_at", type: :timestamp, format: "%Y-%m-%dT%H:%M:%S%z")
134
134
  assert actual.include?(name: "has_incidents", type: :boolean)
135
+ assert actual.include?(name: "tags", type: :json)
136
+ assert actual.include?(name: "collaborator_ids", type: :json)
137
+ assert actual.include?(name: "custom_fields", type: :json)
138
+ assert actual.include?(name: "satisfaction_rating", type: :json)
139
+ end
140
+ end
141
+
142
+ sub_test_case "include subresources" do
143
+ def page_builder
144
+ @page_builder ||= Class.new do
145
+ def add(_); end
146
+ def finish; end
147
+ end.new
148
+ end
149
+
150
+ sub_test_case "guess" do
151
+ def task
152
+ t = {
153
+ type: "zendesk",
154
+ login_url: "https://example.zendesk.com/",
155
+ auth_method: "token",
156
+ username: "foo@example.com",
157
+ token: "token",
158
+ target: "tickets",
159
+ includes: includes,
160
+ }
161
+ t.delete :includes unless includes
162
+ t
163
+ end
164
+
165
+ def config
166
+ Embulk::DataSource.new(task)
167
+ end
168
+
169
+ def ticket
170
+ JSON.parse(fixture_load("tickets.json"))
171
+ end
172
+
173
+ setup do
174
+ @client = Client.new(task)
175
+ stub(Client).new { @client }
176
+ stub(@client).public_send(anything) do |*args|
177
+ args.last.call(ticket)
178
+ end
179
+ end
180
+
181
+ sub_test_case "includes present" do
182
+ def includes
183
+ %w(audits comments)
184
+ end
185
+
186
+ test "guessed includes fields" do
187
+ actual = Plugin.guess(config)["columns"]
188
+ assert actual.include?(name: "audits", type: :json)
189
+ assert actual.include?(name: "comments", type: :json)
190
+ end
191
+ end
192
+
193
+ sub_test_case "includes blank" do
194
+ def includes
195
+ nil
196
+ end
197
+
198
+ test "not guessed includes fields" do
199
+ actual = Plugin.guess(config)["columns"]
200
+ assert !actual.include?(name: "audits", type: :json)
201
+ assert !actual.include?(name: "comments", type: :json)
202
+ end
203
+ end
204
+ end
205
+
206
+ sub_test_case "#run" do
207
+ def schema
208
+ [
209
+ {"name" => "id", "type" => "long"},
210
+ {"name" => "tags", "type" => "json"},
211
+ ]
212
+ end
213
+
214
+ def run_task
215
+ task.merge({
216
+ schema: schema,
217
+ retry_limit: 1,
218
+ retry_initial_wait_sec: 0,
219
+ includes: includes,
220
+ })
221
+ end
222
+
223
+ setup do
224
+ @client = Client.new(run_task)
225
+ stub(@client).public_send {|*args| args.last.call({}) }
226
+ @plugin = Plugin.new(run_task, nil, nil, page_builder)
227
+ stub(@plugin).client { @client }
228
+ @httpclient = @client.httpclient
229
+ stub(@client).httpclient { @httpclient }
230
+ end
231
+
232
+ sub_test_case "preview" do
233
+ setup do
234
+ stub(@plugin).preview? { true }
235
+ end
236
+
237
+ sub_test_case "includes present" do
238
+ def includes
239
+ %w(foo bar)
240
+ end
241
+
242
+ test "call fetch_subresource" do
243
+ includes.each do |ent|
244
+ mock(@client).fetch_subresource(anything, anything, ent).never
245
+ end
246
+ @plugin.run
247
+ end
248
+ end
249
+
250
+ sub_test_case "includes blank" do
251
+ def includes
252
+ []
253
+ end
254
+
255
+ test "don't call fetch_subresource" do
256
+ mock(@client).fetch_subresource.never
257
+ @plugin.run
258
+ end
259
+ end
260
+ end
135
261
 
136
- # TODO: re-enable these json type tests after this plugin officially support it
137
- # assert actual.include?(name: "tags", type: :json)
138
- # assert actual.include?(name: "collaborator_ids", type: :json)
262
+ sub_test_case "run" do
263
+ setup do
264
+ stub(@plugin).preview? { false }
265
+ end
266
+
267
+ sub_test_case "includes present " do
268
+ def includes
269
+ %w(foo bar)
270
+ end
271
+
272
+ test "call fetch_subresource" do
273
+ includes.each do |ent|
274
+ mock(@client).fetch_subresource(anything, anything, ent).at_least(1)
275
+ end
276
+ @plugin.run
277
+ end
278
+ end
139
279
 
140
- # assert actual.include?(name: "custom_fields", type: :json)
141
- # assert actual.include?(name: "satisfaction_rating", type: :json)
280
+ sub_test_case "includes blank" do
281
+ def includes
282
+ []
283
+ end
284
+
285
+ test "don't call fetch_subresource" do
286
+ mock(@client).fetch_subresource.never
287
+ @plugin.run
288
+ end
289
+ end
290
+ end
142
291
  end
143
292
  end
144
293
 
@@ -149,7 +298,8 @@ module Embulk
149
298
 
150
299
  def schema
151
300
  [
152
- {"name" => "id", "type" => "long"}
301
+ {"name" => "id", "type" => "long"},
302
+ {"name" => "tags", "type" => "json"},
153
303
  ]
154
304
  end
155
305
 
@@ -158,6 +308,7 @@ module Embulk
158
308
  schema: schema,
159
309
  retry_limit: 1,
160
310
  retry_initial_wait_sec: 0,
311
+ includes: [],
161
312
  })
162
313
  end
163
314
 
@@ -184,8 +335,8 @@ module Embulk
184
335
 
185
336
  test "task[:schema] columns passed into page_builder.add" do
186
337
  tickets = [
187
- {"id" => 1, "created_at" => "2000-01-01T00:00:00+0900"},
188
- {"id" => 2, "created_at" => "2000-01-01T00:00:00+0900"},
338
+ {"id" => 1, "created_at" => "2000-01-01T00:00:00+0900", "tags" => ["foo"]},
339
+ {"id" => 2, "created_at" => "2000-01-01T00:00:00+0900", "tags" => ["foo"]},
189
340
  ]
190
341
 
191
342
  @httpclient.test_loopback_http_response << [
@@ -198,7 +349,7 @@ module Embulk
198
349
  ].join("\r\n")
199
350
 
200
351
  tickets.each do |ticket|
201
- mock(page_builder).add([ticket["id"]])
352
+ mock(page_builder).add([ticket["id"], ticket["tags"]])
202
353
  end
203
354
  mock(page_builder).finish
204
355
 
@@ -317,6 +468,7 @@ module Embulk
317
468
  {"name" => "target_str", "type" => "string"},
318
469
  {"name" => "target_bool", "type" => "boolean"},
319
470
  {"name" => "target_time", "type" => "timestamp"},
471
+ {"name" => "target_json", "type" => "json"},
320
472
  ]
321
473
  end
322
474
 
@@ -325,22 +477,25 @@ module Embulk
325
477
  {
326
478
  "id" => 1, "target_l" => "3", "target_f" => "3", "target_str" => "str",
327
479
  "target_bool" => false, "target_time" => "2000-01-01",
480
+ "target_json" => [1,2,3],
328
481
  },
329
482
  {
330
483
  "id" => 2, "target_l" => 4.5, "target_f" => 4.5, "target_str" => 999,
331
484
  "target_bool" => "truthy", "target_time" => Time.parse("1999-01-01"),
485
+ "target_json" => {"foo" => "bar"},
332
486
  },
333
487
  {
334
488
  "id" => 3, "target_l" => nil, "target_f" => nil, "target_str" => nil,
335
489
  "target_bool" => nil, "target_time" => nil,
490
+ "target_json" => nil,
336
491
  },
337
492
  ]
338
493
  end
339
494
 
340
495
  test "cast as given type" do
341
- mock(page_builder).add([3, 3.0, "str", false, Time.parse("2000-01-01")])
342
- mock(page_builder).add([4, 4.5, "999", true, Time.parse("1999-01-01")])
343
- mock(page_builder).add([nil, nil, nil, nil, nil])
496
+ mock(page_builder).add([3, 3.0, "str", false, Time.parse("2000-01-01"), [1,2,3]])
497
+ mock(page_builder).add([4, 4.5, "999", true, Time.parse("1999-01-01"), {"foo" => "bar"}])
498
+ mock(page_builder).add([nil, nil, nil, nil, nil, nil])
344
499
  mock(page_builder).finish
345
500
 
346
501
  @plugin.run
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: embulk-input-zendesk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - uu59
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-01-29 00:00:00.000000000 Z
13
+ date: 2016-03-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  requirement: !ruby/object:Gem::Requirement