embulk-input-zendesk 0.3.5 → 0.3.6

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: 2dab4909dba68349742e03eef6be0ff02014caf1
4
- data.tar.gz: 05bee8746fae83639eeb0daacb4b38ac5f765868
3
+ metadata.gz: 59c7819359a24e229366275644b6d543809d7c08
4
+ data.tar.gz: 818fe70947171c2c96d7951f54316f8b13a357e9
5
5
  SHA512:
6
- metadata.gz: 0b0831ce1c1a40d1d9a3b2b3e8a73d8a2fa14a6e5144d264e1e210f2289c092fed6778c7ccea823fcf4464cc8b8ef61182306426730f4a82c85a50b262783bdf
7
- data.tar.gz: e1706af5499ff07076761b0aa591920abf867a2a95d38c4b5e18a47dd61e73ecb653cf5f3d4cca8beb631ddf3f6b3de9018ebb92fecf8558a4f6e3713c4b0715
6
+ metadata.gz: 477172f0826c449a67af48191f29796887c9c8a8b9d8e9550cbfd613b194c406f8047c80e2ab31d0cdcff70410e3f8b4feda7e94316ffbaad3db050033b90b9a
7
+ data.tar.gz: 7a24e2b1bbe4ad103bf5e4c86055e1c069b9f0ca38898b2a1bbbcafc2afa9dad6ccb25bc0d5e45136adf7f8a669c16df3b293fb5a65f32d7799b1f42989d4f9e
@@ -1,3 +1,7 @@
1
+ ## 0.3.6 - 2019-07-02
2
+ * [enhancement] Improve error message #61 [#61](https://github.com/treasure-data/embulk-input-zendesk/pull/61)
3
+ * [enhancement] Support `end_time` field for incremental #60 [#60](https://github.com/treasure-data/embulk-input-zendesk/pull/60)
4
+
1
5
  ## 0.3.5 - 2019-06-03
2
6
  * [enhancement] Add new targets #58 [#58](https://github.com/treasure-data/embulk-input-zendesk/pull/58)
3
7
 
@@ -15,7 +15,7 @@ configurations {
15
15
  provided
16
16
  }
17
17
 
18
- version = "0.3.5"
18
+ version = "0.3.6"
19
19
 
20
20
  sourceCompatibility = 1.8
21
21
  targetCompatibility = 1.8
@@ -167,6 +167,7 @@ public class ZendeskInputPlugin implements InputPlugin
167
167
  {
168
168
  final PluginTask task = config.loadConfig(PluginTask.class);
169
169
  validateInputTask(task);
170
+
170
171
  final Schema schema = task.getColumns().toSchema();
171
172
  int taskCount = 1;
172
173
 
@@ -199,6 +200,21 @@ public class ZendeskInputPlugin implements InputPlugin
199
200
  public TaskReport run(final TaskSource taskSource, final Schema schema, final int taskIndex, final PageOutput output)
200
201
  {
201
202
  final PluginTask task = taskSource.loadTask(PluginTask.class);
203
+
204
+ if (getZendeskService(task).isSupportIncremental() && !isValidTimeRange(task)) {
205
+ if (Exec.isPreview()) {
206
+ throw new ConfigException("Invalid End time. End time is greater than current time");
207
+ }
208
+
209
+ logger.warn("The end time, '" + task.getEndTime().get() + "', is greater than the current time. No records will be imported");
210
+
211
+ // we just need to store config_diff when incremental_mode is enable
212
+ if (task.getIncremental()) {
213
+ return buildTaskReportKeepOldStartAndEndTime(task);
214
+ }
215
+ return Exec.newTaskReport();
216
+ }
217
+
202
218
  try (final PageBuilder pageBuilder = getPageBuilder(schema, output)) {
203
219
  final TaskReport taskReport = getZendeskService(task).addRecordToImporter(taskIndex, getRecordImporter(schema, pageBuilder));
204
220
  pageBuilder.finish();
@@ -212,6 +228,9 @@ public class ZendeskInputPlugin implements InputPlugin
212
228
  config.set("columns", new ObjectMapper().createArrayNode());
213
229
  final PluginTask task = config.loadConfig(PluginTask.class);
214
230
  validateInputTask(task);
231
+ if (!isValidTimeRange(task)) {
232
+ throw new ConfigException("Invalid End time. End time is greater than current time");
233
+ }
215
234
  return Exec.newConfigDiff().set("columns", buildColumns(task));
216
235
  }
217
236
 
@@ -234,6 +253,14 @@ public class ZendeskInputPlugin implements InputPlugin
234
253
  configDiff.set(ZendeskConstants.Field.START_TIME,
235
254
  offsetDateTime.format(DateTimeFormatter.ofPattern(ZendeskConstants.Misc.RUBY_TIMESTAMP_FORMAT_INPUT)));
236
255
  }
256
+
257
+ if (taskReport.has(ZendeskConstants.Field.END_TIME)) {
258
+ final OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(
259
+ taskReport.get(JsonNode.class, ZendeskConstants.Field.END_TIME).asLong()), ZoneOffset.UTC);
260
+
261
+ configDiff.set(ZendeskConstants.Field.END_TIME,
262
+ offsetDateTime.format(DateTimeFormatter.ofPattern(ZendeskConstants.Misc.RUBY_TIMESTAMP_FORMAT_INPUT)));
263
+ }
237
264
  }
238
265
  return configDiff;
239
266
  }
@@ -364,6 +391,7 @@ public class ZendeskInputPlugin implements InputPlugin
364
391
  validateIncremental(task);
365
392
  validateCustomObject(task);
366
393
  validateUserEvent(task);
394
+ validateTime(task);
367
395
  }
368
396
 
369
397
  private void validateCredentials(PluginTask task)
@@ -439,7 +467,12 @@ public class ZendeskInputPlugin implements InputPlugin
439
467
  if (!task.getProfileSource().isPresent()) {
440
468
  throw new ConfigException("Profile Source is required for User Event Target");
441
469
  }
470
+ }
471
+ }
442
472
 
473
+ private void validateTime(PluginTask task)
474
+ {
475
+ if (getZendeskService(task).isSupportIncremental()) {
443
476
  // Can't set end_time to 0, so it should be valid
444
477
  task.getEndTime().ifPresent(time -> {
445
478
  if (!ZendeskDateUtils.supportedTimeFormat(task.getEndTime().get()).isPresent()) {
@@ -453,4 +486,24 @@ public class ZendeskInputPlugin implements InputPlugin
453
486
  }
454
487
  }
455
488
  }
489
+
490
+ private boolean isValidTimeRange(PluginTask task)
491
+ {
492
+ return !task.getEndTime().isPresent() || ZendeskDateUtils.isoToEpochSecond(task.getEndTime().get()) <= Instant.now().getEpochSecond();
493
+ }
494
+
495
+ private TaskReport buildTaskReportKeepOldStartAndEndTime(PluginTask task)
496
+ {
497
+ final TaskReport taskReport = Exec.newTaskReport();
498
+
499
+ if (task.getStartTime().isPresent()) {
500
+ taskReport.set(ZendeskConstants.Field.START_TIME, ZendeskDateUtils.isoToEpochSecond(task.getStartTime().get()));
501
+ }
502
+
503
+ if (task.getEndTime().isPresent()) {
504
+ taskReport.set(ZendeskConstants.Field.END_TIME, ZendeskDateUtils.isoToEpochSecond(task.getEndTime().get()));
505
+ }
506
+
507
+ return taskReport;
508
+ }
456
509
  }
@@ -1,6 +1,7 @@
1
1
  package org.embulk.input.zendesk.clients;
2
2
 
3
3
  import com.fasterxml.jackson.databind.DeserializationFeature;
4
+ import com.fasterxml.jackson.databind.JsonNode;
4
5
  import com.fasterxml.jackson.databind.ObjectMapper;
5
6
  import com.google.common.annotations.VisibleForTesting;
6
7
  import com.google.common.collect.ImmutableMap;
@@ -170,6 +171,17 @@ public class ZendeskRestClient
170
171
  private boolean isResponseStatusToRetry(final int status, final String message, final int retryAfter, final boolean isPreview)
171
172
  {
172
173
  if (status == HttpStatus.SC_NOT_FOUND) {
174
+ ObjectMapper objectMapper = new ObjectMapper();
175
+ try {
176
+ JsonNode jsonNode = objectMapper.readTree(message);
177
+ if (jsonNode.has("error") && jsonNode.get("error").has("title") && jsonNode.get("error").get("title").asText().startsWith("No help desk at ")) {
178
+ throw new ConfigException("This address is not registered in Zendesk. Please check the login_url again");
179
+ }
180
+ }
181
+ catch (IOException e) {
182
+ // In case we can't parse the message, error should not be show here
183
+ }
184
+
173
185
  // 404 would be returned e.g. ticket comments are empty (on fetchRelatedObjects method)
174
186
  return false;
175
187
  }
@@ -203,6 +215,14 @@ public class ZendeskRestClient
203
215
 
204
216
  // Won't retry for 4xx range errors except above. Almost they should be ConfigError e.g. 403 Forbidden
205
217
  if (status / 100 == 4) {
218
+ if (status == HttpStatus.SC_UNAUTHORIZED) {
219
+ throw new ConfigException("Cannot authenticate due to invalid login credentials");
220
+ }
221
+
222
+ if (status == HttpStatus.SC_FORBIDDEN) {
223
+ throw new ConfigException("You do not have access to this resource. Please contact the account owner of this help desk for further help.");
224
+ }
225
+
206
226
  throw new ConfigException("Status '" + status + "', message '" + message + "'");
207
227
  }
208
228
 
@@ -11,6 +11,7 @@ import org.embulk.config.TaskReport;
11
11
  import org.embulk.input.zendesk.RecordImporter;
12
12
  import org.embulk.input.zendesk.ZendeskInputPlugin;
13
13
  import org.embulk.input.zendesk.clients.ZendeskRestClient;
14
+ import org.embulk.input.zendesk.models.Target;
14
15
  import org.embulk.input.zendesk.models.ZendeskException;
15
16
  import org.embulk.input.zendesk.utils.ZendeskConstants;
16
17
  import org.embulk.input.zendesk.utils.ZendeskDateUtils;
@@ -77,10 +78,17 @@ public abstract class ZendeskNormalServices implements ZendeskService
77
78
 
78
79
  private void importDataForIncremental(final ZendeskInputPlugin.PluginTask task, final RecordImporter recordImporter, final TaskReport taskReport)
79
80
  {
81
+ long initStartTime = 0;
80
82
  long startTime = 0;
83
+ long endTime = Long.MAX_VALUE;
81
84
 
82
85
  if (task.getStartTime().isPresent()) {
83
86
  startTime = ZendeskDateUtils.getStartTime(task.getStartTime().get());
87
+ initStartTime = startTime;
88
+ }
89
+
90
+ if (task.getEndTime().isPresent()) {
91
+ endTime = ZendeskDateUtils.isoToEpochSecond(task.getEndTime().get());
84
92
  }
85
93
 
86
94
  // For incremental target, we will run in one task but split in multiple threads inside for data deduplication.
@@ -92,12 +100,14 @@ public abstract class ZendeskNormalServices implements ZendeskService
92
100
  10, 100, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()
93
101
  );
94
102
 
103
+ long apiEndTime = 0;
95
104
  while (true) {
96
105
  int recordCount = 0;
97
106
 
98
107
  // Page argument isn't used in incremental API so we just set it to 0
99
108
  final JsonNode result = getDataFromPath("", 0, false, startTime);
100
109
  final Iterator<JsonNode> iterator = ZendeskUtils.getListRecords(result, task.getTarget().getJsonName());
110
+ apiEndTime = result.get(ZendeskConstants.Field.END_TIME).asLong();
101
111
 
102
112
  int numberOfRecords = 0;
103
113
  if (result.has(ZendeskConstants.Field.COUNT)) {
@@ -111,6 +121,29 @@ public abstract class ZendeskNormalServices implements ZendeskService
111
121
  continue;
112
122
  }
113
123
 
124
+ // Contain some records that later than end_time. Checked and don't add.
125
+ // Because the api already sorted by updated_at or timestamp for ticket_events, we just need to break no need to check further.
126
+ if (apiEndTime > endTime) {
127
+ long checkedTime = 0;
128
+ if (recordJsonNode.has(ZendeskConstants.Field.UPDATED_AT) && !recordJsonNode.get(ZendeskConstants.Field.UPDATED_AT).isNull()) {
129
+ checkedTime = ZendeskDateUtils.isoToEpochSecond(recordJsonNode.get(ZendeskConstants.Field.UPDATED_AT).textValue());
130
+ }
131
+
132
+ // ticket events is updated by system not user's action so it only has timestamp field
133
+ if (task.getTarget().equals(Target.TICKET_EVENTS) && recordJsonNode.has("timestamp") && !recordJsonNode.get("timestamp").isNull()) {
134
+ checkedTime = recordJsonNode.get("timestamp").asLong();
135
+ }
136
+
137
+ // scores (or response) is only store rated_at time
138
+ if (task.getTarget().equals(Target.SCORES) && recordJsonNode.has("rated_at") && !recordJsonNode.get("rated_at").isNull()) {
139
+ checkedTime = ZendeskDateUtils.isoToEpochSecond(recordJsonNode.get("rated_at").textValue());
140
+ }
141
+
142
+ if (checkedTime > endTime) {
143
+ break;
144
+ }
145
+ }
146
+
114
147
  if (task.getDedup()) {
115
148
  final String recordID = recordJsonNode.get(ZendeskConstants.Field.ID).asText();
116
149
 
@@ -131,18 +164,17 @@ public abstract class ZendeskNormalServices implements ZendeskService
131
164
 
132
165
  // https://developer.zendesk.com/rest_api/docs/support/incremental_export#pagination
133
166
  // When there are more than 1000 records share the same time stamp, the count > 1000
134
- long apiEndTime = result.get(ZendeskConstants.Field.END_TIME).asLong();
135
167
  startTime = startTime == apiEndTime
136
168
  ? apiEndTime + 1
137
169
  : apiEndTime;
138
170
 
139
- if (numberOfRecords < ZendeskConstants.Misc.MAXIMUM_RECORDS_INCREMENTAL) {
171
+ if (numberOfRecords < ZendeskConstants.Misc.MAXIMUM_RECORDS_INCREMENTAL || startTime > endTime) {
140
172
  break;
141
173
  }
142
174
  }
143
175
 
144
176
  if (!Exec.isPreview()) {
145
- storeStartTimeForConfigDiff(taskReport, startTime);
177
+ storeStartTimeForConfigDiff(taskReport, initStartTime, startTime);
146
178
  }
147
179
  }
148
180
  finally {
@@ -159,19 +191,34 @@ public abstract class ZendeskNormalServices implements ZendeskService
159
191
  }
160
192
  }
161
193
 
162
- private void storeStartTimeForConfigDiff(final TaskReport taskReport, final long resultEndTime)
194
+ private void storeStartTimeForConfigDiff(final TaskReport taskReport, final long initStartTime, final long resultEndTime)
163
195
  {
164
196
  if (task.getIncremental()) {
165
- // resultEndTime = 0 mean no records, we should store now time for next run
197
+ long nextStartTime;
198
+ long now = Instant.now().getEpochSecond();
199
+ // no record to add
166
200
  if (resultEndTime == 0) {
167
- taskReport.set(ZendeskConstants.Field.START_TIME, Instant.now().getEpochSecond());
201
+ nextStartTime = now;
168
202
  }
169
203
  else {
170
- // NOTE: start_time compared as "=>", not ">".
171
- // If we will use end_time for next start_time, we got the same records that are fetched
172
- // end_time + 1 is workaround for that
173
- taskReport.set(ZendeskConstants.Field.START_TIME, resultEndTime + 1);
204
+ if (task.getEndTime().isPresent()) {
205
+ long endTime = ZendeskDateUtils.isoToEpochSecond(task.getEndTime().get());
206
+ nextStartTime = endTime + 1;
207
+ }
208
+ else {
209
+ // NOTE: start_time compared as "=>", not ">".
210
+ // If we will use end_time for next start_time, we got the same records that are fetched
211
+ // end_time + 1 is workaround for that
212
+ nextStartTime = resultEndTime + 1;
213
+ }
214
+ }
215
+
216
+ if (task.getEndTime().isPresent()) {
217
+ long endTime = ZendeskDateUtils.isoToEpochSecond(task.getEndTime().get());
218
+ taskReport.set(ZendeskConstants.Field.END_TIME, nextStartTime + endTime - initStartTime);
174
219
  }
220
+
221
+ taskReport.set(ZendeskConstants.Field.START_TIME, nextStartTime);
175
222
  }
176
223
  }
177
224
 
@@ -43,11 +43,11 @@ public class ZendeskConstants
43
43
  public static class Misc
44
44
  {
45
45
  public static final String RUBY_TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S%z";
46
- public static final String RUBY_TIMESTAMP_FORMAT_INPUT = "yyyy-MM-dd HH:mm:ss Z";
47
- public static final String JAVA_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
48
- public static final String ISO_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX";
49
- public static final String ISO_INSTANT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
50
- public static final String RUBY_TIMESTAMP_FORMAT_INPUT_NO_SPACE = "yyyy-MM-dd HH:mm:ssZ";
46
+ public static final String RUBY_TIMESTAMP_FORMAT_INPUT = "uuuu-MM-dd HH:mm:ss Z";
47
+ public static final String JAVA_TIMESTAMP_FORMAT = "uuuu-MM-dd'T'HH:mm:ss.SSS'Z'";
48
+ public static final String ISO_TIMESTAMP_FORMAT = "uuuu-MM-dd'T'HH:mm:ssXXX";
49
+ public static final String ISO_INSTANT = "uuuu-MM-dd'T'HH:mm:ss'Z'";
50
+ public static final String RUBY_TIMESTAMP_FORMAT_INPUT_NO_SPACE = "uuuu-MM-dd HH:mm:ssZ";
51
51
  public static final String TOO_RECENT_START_TIME = "Too recent start_time.";
52
52
  public static final int RECORDS_SIZE_PER_PAGE = 100;
53
53
  public static final int MAXIMUM_RECORDS_INCREMENTAL = 1000;
@@ -9,6 +9,7 @@ import java.time.ZoneOffset;
9
9
  import java.time.format.DateTimeFormatter;
10
10
  import java.time.format.DateTimeParseException;
11
11
 
12
+ import java.time.format.ResolverStyle;
12
13
  import java.util.Optional;
13
14
 
14
15
  public class ZendeskDateUtils
@@ -21,9 +22,14 @@ public class ZendeskDateUtils
21
22
  {
22
23
  final Optional<String> pattern = supportedTimeFormat(time);
23
24
  if (pattern.isPresent()) {
24
- final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern.get()).withZone(ZoneOffset.UTC);
25
- final OffsetDateTime offsetDateTime = LocalDateTime.parse(time, formatter).atOffset(ZoneOffset.UTC);
26
- return offsetDateTime.toInstant().getEpochSecond();
25
+ final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern.get()).withZone(ZoneOffset.UTC).withResolverStyle(ResolverStyle.STRICT);
26
+ try {
27
+ final OffsetDateTime offsetDateTime = LocalDateTime.parse(time, formatter).atOffset(ZoneOffset.UTC);
28
+ return offsetDateTime.toInstant().getEpochSecond();
29
+ }
30
+ catch (DateTimeParseException e) {
31
+ throw new DataException(e.getMessage());
32
+ }
27
33
  }
28
34
 
29
35
  throw new DataException("Fail to parse value '" + time + "' follow formats " + ZendeskConstants.Misc.SUPPORT_DATE_TIME_FORMAT.toString());
@@ -46,8 +52,7 @@ public class ZendeskDateUtils
46
52
 
47
53
  public static String convertToDateTimeFormat(String datetime, String dateTimeFormat)
48
54
  {
49
- return OffsetDateTime.ofInstant(Instant.ofEpochSecond(ZendeskDateUtils.isoToEpochSecond(datetime)), ZoneOffset.UTC)
50
- .format(DateTimeFormatter.ofPattern(dateTimeFormat));
55
+ return Instant.ofEpochSecond(ZendeskDateUtils.isoToEpochSecond(datetime)).atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern(dateTimeFormat));
51
56
  }
52
57
 
53
58
  // start_time should be start from 0
@@ -14,6 +14,7 @@ import org.embulk.input.zendesk.services.ZendeskService;
14
14
  import org.embulk.input.zendesk.services.ZendeskSupportAPIService;
15
15
  import org.embulk.input.zendesk.services.ZendeskUserEventService;
16
16
  import org.embulk.input.zendesk.utils.ZendeskConstants;
17
+ import org.embulk.input.zendesk.utils.ZendeskDateUtils;
17
18
  import org.embulk.input.zendesk.utils.ZendeskPluginTestRuntime;
18
19
  import org.embulk.input.zendesk.utils.ZendeskTestHelper;
19
20
 
@@ -143,19 +144,23 @@ public class TestZendeskInputPlugin
143
144
  }
144
145
 
145
146
  @Test
146
- public void testRunIncrementalStoreStartTime()
147
+ public void testRunIncrementalStoreStartTimeAndEndTime()
147
148
  {
148
- final ConfigSource src = ZendeskTestHelper.getConfigSource("incremental.yml");
149
+ final ConfigSource src = ZendeskTestHelper.getConfigSource("incremental.yml")
150
+ .set("end_time", "2019-04-12 06:51:50 +0000");
149
151
  TaskReport taskReport = Exec.newTaskReport();
150
152
  taskReport.set(ZendeskConstants.Field.START_TIME, 1557026576);
153
+ taskReport.set(ZendeskConstants.Field.END_TIME, 1560309776);
151
154
 
152
155
  when(zendeskSupportAPIService.isSupportIncremental()).thenReturn(true);
153
156
  when(zendeskSupportAPIService.addRecordToImporter(anyInt(), any())).thenReturn(taskReport);
154
157
 
155
158
  ConfigDiff configDiff = zendeskInputPlugin.transaction(src, new Control());
156
159
  String nextStartTime = configDiff.get(String.class, ZendeskConstants.Field.START_TIME);
160
+ String nextEndTime = configDiff.get(String.class, ZendeskConstants.Field.END_TIME);
157
161
  verify(pageBuilder, times(1)).finish();
158
162
  assertEquals("2019-05-05 03:22:56 +0000", nextStartTime);
163
+ assertEquals("2019-06-12 03:22:56 +0000", nextEndTime);
159
164
  }
160
165
 
161
166
  @Test
@@ -266,8 +271,14 @@ public class TestZendeskInputPlugin
266
271
  ConfigSource configSource = ZendeskTestHelper.getConfigSource("base_validator.yml");
267
272
  configSource.set("target", Target.USER_EVENTS.name().toLowerCase());
268
273
  assertValidation(configSource, "Profile Source is required for User Event Target");
274
+ }
269
275
 
270
- configSource.set("profile_source", "");
276
+ @Test
277
+ public void validateTimeShouldThrowException()
278
+ {
279
+ ConfigSource configSource = ZendeskTestHelper.getConfigSource("base_validator.yml");
280
+ when(zendeskSupportAPIService.isSupportIncremental()).thenReturn(true);
281
+ configSource.set("target", Target.TICKETS.name().toLowerCase());
271
282
  configSource.set("start_time", OffsetDateTime.ofInstant(Instant.ofEpochSecond(Instant.now().getEpochSecond()), ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
272
283
  configSource.set("end_time", "2019-12-2 22-12-22");
273
284
  assertValidation(configSource, "End Time should follow these format " + ZendeskConstants.Misc.SUPPORT_DATE_TIME_FORMAT.toString());
@@ -277,6 +288,43 @@ public class TestZendeskInputPlugin
277
288
  assertValidation(configSource, "End Time should be later or equal than Start Time");
278
289
  }
279
290
 
291
+ @Test
292
+ public void isValidTimeRangeShouldThrowException()
293
+ {
294
+ ZendeskTestHelper.setPreviewMode(embulk, true);
295
+ String expectedMessage = "Invalid End time. End time is greater than current time";
296
+ ConfigSource configSource = ZendeskTestHelper.getConfigSource("base_validator.yml");
297
+ when(zendeskSupportAPIService.isSupportIncremental()).thenReturn(true);
298
+ configSource.set("start_time", OffsetDateTime.ofInstant(Instant.ofEpochSecond(Instant.now().getEpochSecond()), ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
299
+ configSource.set("end_time", OffsetDateTime.ofInstant(Instant.ofEpochSecond(Instant.now().getEpochSecond() + 1000), ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
300
+
301
+ assertValidation(configSource, expectedMessage);
302
+
303
+ try {
304
+ zendeskInputPlugin.transaction(configSource, new Control());
305
+ fail("Should not reach here");
306
+ }
307
+ catch (final Exception e) {
308
+ assertEquals(expectedMessage, e.getMessage());
309
+ }
310
+ }
311
+
312
+ @Test
313
+ public void runShouldKeepOldStartTimeAndEndTimeInConfigDiff()
314
+ {
315
+ ConfigSource configSource = ZendeskTestHelper.getConfigSource("base_validator.yml");
316
+ ZendeskTestHelper.setPreviewMode(embulk, false);
317
+ when(zendeskSupportAPIService.isSupportIncremental()).thenReturn(true);
318
+ configSource.set("start_time", OffsetDateTime.ofInstant(Instant.ofEpochSecond(Instant.now().getEpochSecond()), ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
319
+ configSource.set("end_time", OffsetDateTime.ofInstant(Instant.ofEpochSecond(Instant.now().getEpochSecond() + 1000), ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
320
+
321
+ ConfigDiff configDiff = zendeskInputPlugin.transaction(configSource, new Control());
322
+ assertEquals(ZendeskDateUtils.convertToDateTimeFormat(configSource.get(String.class, ZendeskConstants.Field.START_TIME), ZendeskConstants.Misc.RUBY_TIMESTAMP_FORMAT_INPUT),
323
+ configDiff.get(String.class, ZendeskConstants.Field.START_TIME));
324
+ assertEquals(ZendeskDateUtils.convertToDateTimeFormat(configSource.get(String.class, ZendeskConstants.Field.END_TIME), ZendeskConstants.Misc.RUBY_TIMESTAMP_FORMAT_INPUT),
325
+ configDiff.get(String.class, ZendeskConstants.Field.END_TIME));
326
+ }
327
+
280
328
  private void assertValidation(final ConfigSource configSource, final String message)
281
329
  {
282
330
  try {
@@ -153,7 +153,7 @@ public class TestZendeskNormalService
153
153
  }
154
154
 
155
155
  @Test
156
- public void testTicketEventsAddRecordToImporterIncrementaAndAllRecordsShareTheSameTime()
156
+ public void testTicketEventsAddRecordToImporterIncrementalAndAllRecordsShareTheSameTime()
157
157
  {
158
158
  setupSupportAPIService("incremental.yml");
159
159
  ZendeskInputPlugin.PluginTask task = ZendeskTestHelper.getConfigSource("incremental.yml")
@@ -168,6 +168,78 @@ public class TestZendeskNormalService
168
168
  Assert.assertEquals(1550645444, taskReport.get(JsonNode.class, ZendeskConstants.Field.START_TIME).asLong());
169
169
  }
170
170
 
171
+ @Test
172
+ public void executeIncrementalContainEndTime()
173
+ {
174
+ ConfigSource src = ZendeskTestHelper.getConfigSource("incremental.yml");
175
+ // same updated_at time of last record
176
+ src.set("end_time", "2019-02-20T07:17:32Z");
177
+ ZendeskInputPlugin.PluginTask task = src.loadConfig(ZendeskInputPlugin.PluginTask.class);
178
+ setupZendeskSupportAPIService(task);
179
+ loadData("data/tickets.json");
180
+
181
+ TaskReport taskReport = zendeskSupportAPIService.addRecordToImporter(0, recordImporter);
182
+ verify(recordImporter, times(3)).addRecord(any());
183
+ Assert.assertFalse(taskReport.isEmpty());
184
+ // start_time = end_time + 1
185
+ Assert.assertEquals(1550647053, taskReport.get(JsonNode.class, ZendeskConstants.Field.START_TIME).asLong());
186
+ }
187
+
188
+ @Test
189
+ public void executeIncrementalContainEndTimeFilterOutLastRecord()
190
+ {
191
+ ConfigSource src = ZendeskTestHelper.getConfigSource("incremental.yml");
192
+ // earlier than updated_at time of last record
193
+ src.set("end_time", "2019-02-20T07:17:32Z");
194
+ ZendeskInputPlugin.PluginTask task = src.loadConfig(ZendeskInputPlugin.PluginTask.class);
195
+ setupZendeskSupportAPIService(task);
196
+ loadData("data/tickets.json");
197
+
198
+ TaskReport taskReport = zendeskSupportAPIService.addRecordToImporter(0, recordImporter);
199
+ verify(recordImporter, times(3)).addRecord(any());
200
+ Assert.assertFalse(taskReport.isEmpty());
201
+ //
202
+ Assert.assertEquals(1550647053, taskReport.get(JsonNode.class, ZendeskConstants.Field.START_TIME).asLong());
203
+ }
204
+
205
+ @Test
206
+ public void executeIncrementalContainEndTimeFilterOutLastRecordTicketEvents()
207
+ {
208
+ ConfigSource src = ZendeskTestHelper.getConfigSource("incremental.yml");
209
+ src.set("target", Target.TICKET_EVENTS.toString());
210
+ // earlier than updated_at time of last record
211
+ // 1550645520
212
+ src.set("end_time", "2019-02-20T06:52:00Z");
213
+ ZendeskInputPlugin.PluginTask task = src.loadConfig(ZendeskInputPlugin.PluginTask.class);
214
+ setupZendeskSupportAPIService(task);
215
+ loadData("data/ticket_events_updated_by_system_records.json");
216
+
217
+ TaskReport taskReport = zendeskSupportAPIService.addRecordToImporter(0, recordImporter);
218
+ verify(recordImporter, times(3)).addRecord(any());
219
+ Assert.assertFalse(taskReport.isEmpty());
220
+ // end_time + 1
221
+ Assert.assertEquals(1550645521, taskReport.get(JsonNode.class, ZendeskConstants.Field.START_TIME).asLong());
222
+ }
223
+
224
+ @Test
225
+ public void executeIncrementalContainEndTimeFilterOutLastRecordNPSScore()
226
+ {
227
+ ConfigSource src = ZendeskTestHelper.getConfigSource("nps.yml");
228
+ src.set("target", Target.SCORES.toString());
229
+ // earlier than updated_at time of last record
230
+ // 1550645520
231
+ src.set("end_time", "2019-02-20T06:52:00Z");
232
+ ZendeskInputPlugin.PluginTask task = src.loadConfig(ZendeskInputPlugin.PluginTask.class);
233
+ setupZendeskSupportAPIService(task);
234
+ loadData("data/scores.json");
235
+
236
+ TaskReport taskReport = zendeskSupportAPIService.addRecordToImporter(0, recordImporter);
237
+ verify(recordImporter, times(1)).addRecord(any());
238
+ Assert.assertFalse(taskReport.isEmpty());
239
+ // end_time + 1
240
+ Assert.assertEquals(1550645521, taskReport.get(JsonNode.class, ZendeskConstants.Field.START_TIME).asLong());
241
+ }
242
+
171
243
  private void setupSupportAPIService(String file)
172
244
  {
173
245
  ZendeskInputPlugin.PluginTask task = ZendeskTestHelper.getConfigSource(file)
@@ -79,7 +79,7 @@ public class TestZendeskDateUtils
79
79
  assertEquals(0, actualValue);
80
80
 
81
81
  actualValue = ZendeskDateUtils.getStartTime("2019-02-30 06:50:45 +0000");
82
- assertEquals(ZendeskDateUtils.getStartTime("2019-02-28 06:50:45 +0000"), actualValue);
82
+ assertEquals(0, actualValue);
83
83
 
84
84
  actualValue = ZendeskDateUtils.getStartTime("2019-02-20 06:50:45 +0000");
85
85
  assertEquals(expectedValue, actualValue);
@@ -76,4 +76,17 @@ public final class ZendeskTestHelper
76
76
  Assert.fail(e.getMessage());
77
77
  }
78
78
  }
79
+
80
+ public static void setPreviewMode(final ZendeskPluginTestRuntime runtime, final boolean isPreview)
81
+ {
82
+ // A small hack to make the plugin executed in preview mode so
83
+ try {
84
+ final Field previewField = ExecSession.class.getDeclaredField("preview");
85
+ previewField.setAccessible(true);
86
+ previewField.set(runtime.getExec(), isPreview);
87
+ }
88
+ catch (NoSuchFieldException | IllegalAccessException e) {
89
+ Assert.fail(e.getMessage());
90
+ }
91
+ }
79
92
  }
@@ -13,6 +13,8 @@ incremental: true
13
13
  retry_initial_wait_sec: 1
14
14
  max_retry_wait_sec: 30
15
15
  retry_limit: 2
16
+ start_time: '2019-01-20T07:14:50Z'
17
+ end_time: '2019-01-20T07:14:53Z'
16
18
  columns:
17
19
  - {name: id, type: long}
18
20
  - {name: survey_id, type: string}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: embulk-input-zendesk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - hieu.duong
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-04 00:00:00.000000000 Z
11
+ date: 2019-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -138,7 +138,7 @@ files:
138
138
  - src/test/resources/data/util_page.json
139
139
  - classpath/commons-logging-1.2.jar
140
140
  - classpath/httpcore-4.4.10.jar
141
- - classpath/embulk-input-zendesk-0.3.5.jar
141
+ - classpath/embulk-input-zendesk-0.3.6.jar
142
142
  - classpath/httpclient-4.5.6.jar
143
143
  - classpath/commons-codec-1.10.jar
144
144
  homepage: https://github.com/treasure-data/embulk-input-zendesk