embulk-input-zendesk-all 0.3.7

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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +5 -0
  4. data/CHANGELOG.md +126 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +91 -0
  7. data/build.gradle +123 -0
  8. data/config/checkstyle/checkstyle.xml +128 -0
  9. data/config/checkstyle/default.xml +108 -0
  10. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  11. data/gradle/wrapper/gradle-wrapper.properties +5 -0
  12. data/gradlew +172 -0
  13. data/gradlew.bat +84 -0
  14. data/lib/embulk/guess/zendesk.rb +21 -0
  15. data/lib/embulk/input/zendesk.rb +3 -0
  16. data/src/main/java/org/embulk/input/zendesk/RecordImporter.java +134 -0
  17. data/src/main/java/org/embulk/input/zendesk/ZendeskInputPlugin.java +513 -0
  18. data/src/main/java/org/embulk/input/zendesk/clients/ZendeskRestClient.java +291 -0
  19. data/src/main/java/org/embulk/input/zendesk/models/AuthenticationMethod.java +23 -0
  20. data/src/main/java/org/embulk/input/zendesk/models/Target.java +47 -0
  21. data/src/main/java/org/embulk/input/zendesk/models/ZendeskException.java +25 -0
  22. data/src/main/java/org/embulk/input/zendesk/services/ZendeskCustomObjectService.java +110 -0
  23. data/src/main/java/org/embulk/input/zendesk/services/ZendeskNPSService.java +30 -0
  24. data/src/main/java/org/embulk/input/zendesk/services/ZendeskNormalServices.java +347 -0
  25. data/src/main/java/org/embulk/input/zendesk/services/ZendeskService.java +14 -0
  26. data/src/main/java/org/embulk/input/zendesk/services/ZendeskSupportAPIService.java +63 -0
  27. data/src/main/java/org/embulk/input/zendesk/services/ZendeskUserEventService.java +158 -0
  28. data/src/main/java/org/embulk/input/zendesk/stream/PagingSpliterator.java +40 -0
  29. data/src/main/java/org/embulk/input/zendesk/stream/paginator/sunshine/CustomObjectSpliterator.java +42 -0
  30. data/src/main/java/org/embulk/input/zendesk/stream/paginator/sunshine/SunshineSpliterator.java +66 -0
  31. data/src/main/java/org/embulk/input/zendesk/stream/paginator/sunshine/UserEventSpliterator.java +35 -0
  32. data/src/main/java/org/embulk/input/zendesk/stream/paginator/support/OrganizationSpliterator.java +13 -0
  33. data/src/main/java/org/embulk/input/zendesk/stream/paginator/support/SupportSpliterator.java +44 -0
  34. data/src/main/java/org/embulk/input/zendesk/stream/paginator/support/UserSpliterator.java +13 -0
  35. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskConstants.java +72 -0
  36. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskDateUtils.java +68 -0
  37. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskUtils.java +92 -0
  38. data/src/test/java/org/embulk/input/zendesk/TestRecordImporter.java +114 -0
  39. data/src/test/java/org/embulk/input/zendesk/TestZendeskInputPlugin.java +402 -0
  40. data/src/test/java/org/embulk/input/zendesk/clients/TestZendeskRestClient.java +337 -0
  41. data/src/test/java/org/embulk/input/zendesk/services/TestZendeskCustomObjectService.java +161 -0
  42. data/src/test/java/org/embulk/input/zendesk/services/TestZendeskNPSService.java +56 -0
  43. data/src/test/java/org/embulk/input/zendesk/services/TestZendeskNormalService.java +261 -0
  44. data/src/test/java/org/embulk/input/zendesk/services/TestZendeskSupportAPIService.java +130 -0
  45. data/src/test/java/org/embulk/input/zendesk/services/TestZendeskUserEventService.java +158 -0
  46. data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskDateUtils.java +87 -0
  47. data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskUtil.java +22 -0
  48. data/src/test/java/org/embulk/input/zendesk/utils/ZendeskPluginTestRuntime.java +133 -0
  49. data/src/test/java/org/embulk/input/zendesk/utils/ZendeskTestHelper.java +92 -0
  50. data/src/test/resources/config/base.yml +14 -0
  51. data/src/test/resources/config/base_validator.yml +48 -0
  52. data/src/test/resources/config/incremental.yml +54 -0
  53. data/src/test/resources/config/non-incremental.yml +39 -0
  54. data/src/test/resources/config/nps.yml +31 -0
  55. data/src/test/resources/config/object_records.yml +24 -0
  56. data/src/test/resources/config/relationship_records.yml +23 -0
  57. data/src/test/resources/config/user_events.yml +29 -0
  58. data/src/test/resources/config/util.yml +18 -0
  59. data/src/test/resources/data/client.json +293 -0
  60. data/src/test/resources/data/duplicate_user.json +0 -0
  61. data/src/test/resources/data/empty_result.json +7 -0
  62. data/src/test/resources/data/error_data.json +187 -0
  63. data/src/test/resources/data/expected/ticket_column.json +148 -0
  64. data/src/test/resources/data/expected/ticket_column_with_related_objects.json +152 -0
  65. data/src/test/resources/data/expected/ticket_fields_column.json +92 -0
  66. data/src/test/resources/data/expected/ticket_metrics_column.json +98 -0
  67. data/src/test/resources/data/expected/user_events_column.json +40 -0
  68. data/src/test/resources/data/object_records.json +30 -0
  69. data/src/test/resources/data/organization.json +39 -0
  70. data/src/test/resources/data/relationship_records.json +57 -0
  71. data/src/test/resources/data/scores.json +21 -0
  72. data/src/test/resources/data/scores_share_same_time_with_next_page.json +35 -0
  73. data/src/test/resources/data/scores_share_same_time_without_next_page.json +35 -0
  74. data/src/test/resources/data/simple_organization.json +23 -0
  75. data/src/test/resources/data/simple_user.json +50 -0
  76. data/src/test/resources/data/simple_user_event.json +19 -0
  77. data/src/test/resources/data/ticket_events_share_same_time_with_next_page.json +279 -0
  78. data/src/test/resources/data/ticket_events_share_same_time_without_next_page.json +279 -0
  79. data/src/test/resources/data/ticket_events_updated_by_system_records.json +279 -0
  80. data/src/test/resources/data/ticket_fields.json +225 -0
  81. data/src/test/resources/data/ticket_metrics.json +397 -0
  82. data/src/test/resources/data/ticket_share_same_time_with_next_page.json +232 -0
  83. data/src/test/resources/data/ticket_share_same_time_without_next_page.json +232 -0
  84. data/src/test/resources/data/ticket_with_related_objects.json +67 -0
  85. data/src/test/resources/data/ticket_with_updated_by_system_records.json +187 -0
  86. data/src/test/resources/data/tickets.json +232 -0
  87. data/src/test/resources/data/tickets_continue.json +52 -0
  88. data/src/test/resources/data/user_event.json +19 -0
  89. data/src/test/resources/data/user_event_contain_latter_create_at.json +19 -0
  90. data/src/test/resources/data/user_event_multiple.json +33 -0
  91. data/src/test/resources/data/util.json +19 -0
  92. data/src/test/resources/data/util_page.json +227 -0
  93. metadata +168 -0
@@ -0,0 +1,513 @@
1
+ package org.embulk.input.zendesk;
2
+
3
+ import com.fasterxml.jackson.databind.JsonNode;
4
+ import com.fasterxml.jackson.databind.ObjectMapper;
5
+ import com.fasterxml.jackson.databind.node.ArrayNode;
6
+ import com.fasterxml.jackson.databind.node.ObjectNode;
7
+ import com.google.common.annotations.VisibleForTesting;
8
+ import com.google.common.collect.ImmutableList;
9
+
10
+ import org.embulk.config.Config;
11
+ import org.embulk.config.ConfigDefault;
12
+ import org.embulk.config.ConfigDiff;
13
+ import org.embulk.config.ConfigException;
14
+ import org.embulk.config.ConfigSource;
15
+ import org.embulk.config.Task;
16
+ import org.embulk.config.TaskReport;
17
+ import org.embulk.config.TaskSource;
18
+ import org.embulk.exec.GuessExecutor;
19
+ import org.embulk.input.zendesk.models.AuthenticationMethod;
20
+ import org.embulk.input.zendesk.models.Target;
21
+ import org.embulk.input.zendesk.services.ZendeskCustomObjectService;
22
+ import org.embulk.input.zendesk.services.ZendeskNPSService;
23
+ import org.embulk.input.zendesk.services.ZendeskService;
24
+ import org.embulk.input.zendesk.services.ZendeskSupportAPIService;
25
+ import org.embulk.input.zendesk.services.ZendeskUserEventService;
26
+ import org.embulk.input.zendesk.services.ZendeskNormalServices;
27
+ import org.embulk.input.zendesk.utils.ZendeskConstants;
28
+ import org.embulk.input.zendesk.utils.ZendeskDateUtils;
29
+ import org.embulk.input.zendesk.utils.ZendeskUtils;
30
+ import org.embulk.spi.Buffer;
31
+ import org.embulk.spi.Exec;
32
+ import org.embulk.spi.InputPlugin;
33
+ import org.embulk.spi.PageBuilder;
34
+ import org.embulk.spi.PageOutput;
35
+ import org.embulk.spi.Schema;
36
+ import org.embulk.spi.SchemaConfig;
37
+ import org.embulk.spi.type.Types;
38
+ import org.slf4j.Logger;
39
+
40
+ import javax.validation.constraints.Max;
41
+ import javax.validation.constraints.Min;
42
+
43
+ import java.nio.charset.StandardCharsets;
44
+ import java.time.Instant;
45
+ import java.time.OffsetDateTime;
46
+ import java.time.ZoneOffset;
47
+ import java.time.format.DateTimeFormatter;
48
+ import java.util.Arrays;
49
+ import java.util.Iterator;
50
+ import java.util.List;
51
+ import java.util.Optional;
52
+ import java.util.regex.Pattern;
53
+ import java.util.stream.Collectors;
54
+ import java.util.stream.StreamSupport;
55
+
56
+ public class ZendeskInputPlugin implements InputPlugin
57
+ {
58
+ public interface PluginTask extends Task
59
+ {
60
+ @Config("login_url")
61
+ String getLoginUrl();
62
+
63
+ @Config("auth_method")
64
+ @ConfigDefault("\"basic\"")
65
+ AuthenticationMethod getAuthenticationMethod();
66
+
67
+ @Config("target")
68
+ Target getTarget();
69
+
70
+ @Config("username")
71
+ @ConfigDefault("null")
72
+ Optional<String> getUsername();
73
+
74
+ @Config("password")
75
+ @ConfigDefault("null")
76
+ Optional<String> getPassword();
77
+
78
+ @Config("token")
79
+ @ConfigDefault("null")
80
+ Optional<String> getToken();
81
+
82
+ @Config("access_token")
83
+ @ConfigDefault("null")
84
+ Optional<String> getAccessToken();
85
+
86
+ @Config("start_time")
87
+ @ConfigDefault("null")
88
+ Optional<String> getStartTime();
89
+
90
+ @Min(1)
91
+ @Max(30)
92
+ @Config("retry_limit")
93
+ @ConfigDefault("5")
94
+ int getRetryLimit();
95
+
96
+ @Min(1)
97
+ @Max(3600)
98
+ @Config("retry_initial_wait_sec")
99
+ @ConfigDefault("4")
100
+ int getRetryInitialWaitSec();
101
+
102
+ @Min(30)
103
+ @Max(3600)
104
+ @Config("max_retry_wait_sec")
105
+ @ConfigDefault("60")
106
+ int getMaxRetryWaitSec();
107
+
108
+ @Config("incremental")
109
+ @ConfigDefault("true")
110
+ boolean getIncremental();
111
+
112
+ @Config("includes")
113
+ @ConfigDefault("[]")
114
+ List<String> getIncludes();
115
+
116
+ @Config("dedup")
117
+ @ConfigDefault("true")
118
+ boolean getDedup();
119
+
120
+ @Config("app_marketplace_integration_name")
121
+ @ConfigDefault("null")
122
+ Optional<String> getAppMarketPlaceIntegrationName();
123
+
124
+ @Config("app_marketplace_org_id")
125
+ @ConfigDefault("null")
126
+ Optional<String> getAppMarketPlaceOrgId();
127
+
128
+ @Config("app_marketplace_app_id")
129
+ @ConfigDefault("null")
130
+ Optional<String> getAppMarketPlaceAppId();
131
+
132
+ @Config("object_types")
133
+ @ConfigDefault("[]")
134
+ List<String> getObjectTypes();
135
+
136
+ @Config("relationship_types")
137
+ @ConfigDefault("[]")
138
+ List<String> getRelationshipTypes();
139
+
140
+ @Config("profile_source")
141
+ @ConfigDefault("null")
142
+ Optional<String> getProfileSource();
143
+
144
+ @Config("end_time")
145
+ @ConfigDefault("null")
146
+ Optional<String> getEndTime();
147
+
148
+ @Config("user_event_type")
149
+ @ConfigDefault("null")
150
+ Optional<String> getUserEventType();
151
+
152
+ @Config("user_event_source")
153
+ @ConfigDefault("null")
154
+ Optional<String> getUserEventSource();
155
+
156
+ @Config("columns")
157
+ SchemaConfig getColumns();
158
+ }
159
+
160
+ private ZendeskService zendeskService;
161
+
162
+ private RecordImporter recordImporter;
163
+
164
+ private static final Logger logger = Exec.getLogger(ZendeskInputPlugin.class);
165
+
166
+ @Override
167
+ public ConfigDiff transaction(final ConfigSource config, final Control control)
168
+ {
169
+ final PluginTask task = config.loadConfig(PluginTask.class);
170
+ validateInputTask(task);
171
+
172
+ final Schema schema = task.getColumns().toSchema();
173
+ int taskCount = 1;
174
+
175
+ // For non-incremental target, we will split records based on number of pages. 100 records per page
176
+ // In preview, run with taskCount = 1
177
+ if (!Exec.isPreview() && !getZendeskService(task).isSupportIncremental() && getZendeskService(task) instanceof ZendeskSupportAPIService && !Target.SATISFACTION_RATINGS.equals(task.getTarget())) {
178
+
179
+ final JsonNode result = getZendeskService(task).getDataFromPath("", 0, false, 0);
180
+ if (result.has(ZendeskConstants.Field.COUNT) && !result.get(ZendeskConstants.Field.COUNT).isNull()
181
+ && result.get(ZendeskConstants.Field.COUNT).isInt()) {
182
+ taskCount = ZendeskUtils.numberToSplitWithHintingInTask(result.get(ZendeskConstants.Field.COUNT).asInt());
183
+ }
184
+ }
185
+ return resume(task.dump(), schema, taskCount, control);
186
+ }
187
+
188
+ @Override
189
+ public ConfigDiff resume(final TaskSource taskSource, final Schema schema, final int taskCount, final Control control)
190
+ {
191
+ final PluginTask task = taskSource.loadTask(PluginTask.class);
192
+ final List<TaskReport> taskReports = control.run(taskSource, schema, taskCount);
193
+ return this.buildConfigDiff(task, taskReports);
194
+ }
195
+
196
+ @Override
197
+ public void cleanup(final TaskSource taskSource, final Schema schema, final int taskCount, final List<TaskReport> successTaskReports)
198
+ {
199
+ }
200
+
201
+ @Override
202
+ public TaskReport run(final TaskSource taskSource, final Schema schema, final int taskIndex, final PageOutput output)
203
+ {
204
+ final PluginTask task = taskSource.loadTask(PluginTask.class);
205
+
206
+ if (getZendeskService(task).isSupportIncremental() && !isValidTimeRange(task)) {
207
+ if (Exec.isPreview()) {
208
+ throw new ConfigException("Invalid End time. End time is greater than current time");
209
+ }
210
+
211
+ logger.warn("The end time, '" + task.getEndTime().get() + "', is greater than the current time. No records will be imported");
212
+
213
+ // we just need to store config_diff when incremental_mode is enable
214
+ if (task.getIncremental()) {
215
+ return buildTaskReportKeepOldStartAndEndTime(task);
216
+ }
217
+ return Exec.newTaskReport();
218
+ }
219
+
220
+ try (final PageBuilder pageBuilder = getPageBuilder(schema, output)) {
221
+ final TaskReport taskReport = getZendeskService(task).addRecordToImporter(taskIndex, getRecordImporter(schema, pageBuilder));
222
+ pageBuilder.finish();
223
+ return taskReport;
224
+ }
225
+ }
226
+
227
+ @Override
228
+ public ConfigDiff guess(final ConfigSource config)
229
+ {
230
+ config.set("columns", new ObjectMapper().createArrayNode());
231
+ final PluginTask task = config.loadConfig(PluginTask.class);
232
+ validateInputTask(task);
233
+ if (!isValidTimeRange(task)) {
234
+ throw new ConfigException("Invalid End time. End time is greater than current time");
235
+ }
236
+ return Exec.newConfigDiff().set("columns", buildColumns(task));
237
+ }
238
+
239
+ @VisibleForTesting
240
+ protected PageBuilder getPageBuilder(final Schema schema, final PageOutput output)
241
+ {
242
+ return new PageBuilder(Exec.getBufferAllocator(), schema, output);
243
+ }
244
+
245
+ private ConfigDiff buildConfigDiff(final PluginTask task, final List<TaskReport> taskReports)
246
+ {
247
+ final ConfigDiff configDiff = Exec.newConfigDiff();
248
+
249
+ if (!taskReports.isEmpty() && task.getIncremental()) {
250
+ final TaskReport taskReport = taskReports.get(0);
251
+ if (taskReport.has(ZendeskConstants.Field.START_TIME)) {
252
+ final OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(
253
+ taskReport.get(JsonNode.class, ZendeskConstants.Field.START_TIME).asLong()), ZoneOffset.UTC);
254
+
255
+ configDiff.set(ZendeskConstants.Field.START_TIME,
256
+ offsetDateTime.format(DateTimeFormatter.ofPattern(ZendeskConstants.Misc.RUBY_TIMESTAMP_FORMAT_INPUT)));
257
+ }
258
+
259
+ if (taskReport.has(ZendeskConstants.Field.END_TIME)) {
260
+ final OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(
261
+ taskReport.get(JsonNode.class, ZendeskConstants.Field.END_TIME).asLong()), ZoneOffset.UTC);
262
+
263
+ configDiff.set(ZendeskConstants.Field.END_TIME,
264
+ offsetDateTime.format(DateTimeFormatter.ofPattern(ZendeskConstants.Misc.RUBY_TIMESTAMP_FORMAT_INPUT)));
265
+ }
266
+ }
267
+ return configDiff;
268
+ }
269
+
270
+ private JsonNode buildColumns(final PluginTask task)
271
+ {
272
+ JsonNode jsonNode = getZendeskService(task).getDataFromPath("", 0, true, 0);
273
+
274
+ String targetName = task.getTarget().getJsonName();
275
+
276
+ if (jsonNode.has(targetName) && !jsonNode.get(targetName).isNull() && jsonNode.get(targetName).isArray() && jsonNode.get(targetName).size() > 0) {
277
+ return addAllColumnsToSchema(jsonNode, task.getTarget(), task.getIncludes());
278
+ }
279
+ throw new ConfigException("Could not guess schema due to empty data set");
280
+ }
281
+
282
+ private final Pattern idPattern = Pattern.compile(ZendeskConstants.Regex.ID);
283
+
284
+ private JsonNode addAllColumnsToSchema(final JsonNode jsonNode, final Target target, final List<String> includes)
285
+ {
286
+ final JsonNode sample = new ObjectMapper().valueToTree(StreamSupport.stream(
287
+ jsonNode.get(target.getJsonName()).spliterator(), false).limit(10).collect(Collectors.toList()));
288
+ final Buffer bufferSample = Buffer.copyOf(sample.toString().getBytes(StandardCharsets.UTF_8));
289
+ final JsonNode columns = Exec.getInjector().getInstance(GuessExecutor.class)
290
+ .guessParserConfig(bufferSample, Exec.newConfigSource(), createGuessConfig())
291
+ .get(JsonNode.class, "columns");
292
+
293
+ final Iterator<JsonNode> ite = columns.elements();
294
+
295
+ while (ite.hasNext()) {
296
+ final ObjectNode entry = (ObjectNode) ite.next();
297
+ final String name = entry.get("name").asText();
298
+ final String type = entry.get("type").asText();
299
+
300
+ if (type.equals(Types.TIMESTAMP.getName())) {
301
+ entry.put("format", ZendeskConstants.Misc.RUBY_TIMESTAMP_FORMAT);
302
+ }
303
+
304
+ if (name.equals("id")) {
305
+ if (!type.equals(Types.LONG.getName())) {
306
+ if (type.equals(Types.TIMESTAMP.getName())) {
307
+ entry.remove("format");
308
+ }
309
+ entry.put("type", Types.LONG.getName());
310
+ }
311
+
312
+ // Id of User Events target is more suitable for String
313
+ if (target.equals(Target.USER_EVENTS)) {
314
+ entry.put("type", Types.STRING.getName());
315
+ }
316
+ }
317
+ else if (idPattern.matcher(name).find()) {
318
+ if (type.equals(Types.TIMESTAMP.getName())) {
319
+ entry.remove("format");
320
+ }
321
+ entry.put("type", Types.STRING.getName());
322
+ }
323
+ }
324
+ addIncludedObjectsToSchema((ArrayNode) columns, includes);
325
+ return columns;
326
+ }
327
+
328
+ private void addIncludedObjectsToSchema(final ArrayNode arrayNode, final List<String> includes)
329
+ {
330
+ final ObjectMapper mapper = new ObjectMapper();
331
+
332
+ includes.stream()
333
+ .map((include) -> mapper.createObjectNode()
334
+ .put("name", include)
335
+ .put("type", Types.JSON.getName()))
336
+ .forEach(arrayNode::add);
337
+ }
338
+
339
+ private ConfigSource createGuessConfig()
340
+ {
341
+ return Exec.newConfigSource()
342
+ .set("guess_plugins", ImmutableList.of("zendesk"))
343
+ .set("guess_sample_buffer_bytes", ZendeskConstants.Misc.GUESS_BUFFER_SIZE);
344
+ }
345
+
346
+ private ZendeskService getZendeskService(PluginTask task)
347
+ {
348
+ if (zendeskService == null) {
349
+ zendeskService = dispatchPerTarget(task);
350
+ }
351
+ return zendeskService;
352
+ }
353
+
354
+ @VisibleForTesting
355
+ protected ZendeskService dispatchPerTarget(ZendeskInputPlugin.PluginTask task)
356
+ {
357
+ switch (task.getTarget()) {
358
+ case TICKETS:
359
+ case USERS:
360
+ case ORGANIZATIONS:
361
+ case TICKET_METRICS:
362
+ case TICKET_EVENTS:
363
+ case TICKET_FORMS:
364
+ case TICKET_FIELDS:
365
+ case TICKET_METRIC_EVENTS:
366
+ case SATISFACTION_RATINGS:
367
+ return new ZendeskSupportAPIService(task);
368
+ case RECIPIENTS:
369
+ case SCORES:
370
+ return new ZendeskNPSService(task);
371
+ case OBJECT_RECORDS:
372
+ case RELATIONSHIP_RECORDS:
373
+ return new ZendeskCustomObjectService(task);
374
+ case USER_EVENTS:
375
+ return new ZendeskUserEventService(task);
376
+ default:
377
+ throw new ConfigException("Unsupported " + task.getTarget() + ", supported values: '" + Arrays.toString(Target.values()) + "'");
378
+ }
379
+ }
380
+
381
+ private RecordImporter getRecordImporter(Schema schema, PageBuilder pageBuilder)
382
+ {
383
+ if (recordImporter == null) {
384
+ recordImporter = new RecordImporter(schema, pageBuilder);
385
+ }
386
+ return recordImporter;
387
+ }
388
+
389
+ private void validateInputTask(PluginTask task)
390
+ {
391
+ validateAppMarketPlace(task.getAppMarketPlaceIntegrationName().isPresent(),
392
+ task.getAppMarketPlaceAppId().isPresent(),
393
+ task.getAppMarketPlaceOrgId().isPresent());
394
+ validateCredentials(task);
395
+ validateIncremental(task);
396
+ validateCustomObject(task);
397
+ validateUserEvent(task);
398
+ validateTime(task);
399
+ }
400
+
401
+ private void validateCredentials(PluginTask task)
402
+ {
403
+ switch (task.getAuthenticationMethod()) {
404
+ case OAUTH:
405
+ if (!task.getAccessToken().isPresent()) {
406
+ throw new ConfigException(String.format("access_token is required for authentication method '%s'",
407
+ task.getAuthenticationMethod().name().toLowerCase()));
408
+ }
409
+ break;
410
+ case TOKEN:
411
+ if (!task.getUsername().isPresent() || !task.getToken().isPresent()) {
412
+ throw new ConfigException(String.format("username and token are required for authentication method '%s'",
413
+ task.getAuthenticationMethod().name().toLowerCase()));
414
+ }
415
+ break;
416
+ case BASIC:
417
+ if (!task.getUsername().isPresent() || !task.getPassword().isPresent()) {
418
+ throw new ConfigException(String.format("username and password are required for authentication method '%s'",
419
+ task.getAuthenticationMethod().name().toLowerCase()));
420
+ }
421
+ break;
422
+ default:
423
+ throw new ConfigException("Unknown authentication method");
424
+ }
425
+ }
426
+
427
+ private void validateAppMarketPlace(final boolean isAppMarketIntegrationNamePresent,
428
+ final boolean isAppMarketAppIdPresent,
429
+ final boolean isAppMarketOrgIdPresent)
430
+ {
431
+ final boolean isAllAvailable =
432
+ isAppMarketIntegrationNamePresent && isAppMarketAppIdPresent && isAppMarketOrgIdPresent;
433
+ final boolean isAllUnAvailable =
434
+ !isAppMarketIntegrationNamePresent && !isAppMarketAppIdPresent && !isAppMarketOrgIdPresent;
435
+ // All or nothing needed
436
+ if (!(isAllAvailable || isAllUnAvailable)) {
437
+ throw new ConfigException("All of app_marketplace_integration_name, app_marketplace_org_id, " +
438
+ "app_marketplace_app_id " +
439
+ "are required to fill out for Apps Marketplace API header");
440
+ }
441
+ }
442
+
443
+ private void validateIncremental(PluginTask task)
444
+ {
445
+ if (task.getIncremental() && getZendeskService(task).isSupportIncremental()) {
446
+ if (!task.getDedup()) {
447
+ logger.warn("You've selected to skip de-duplicating records, result may contain duplicated data");
448
+ }
449
+
450
+ if (!getZendeskService(task).isSupportIncremental() && task.getStartTime().isPresent()) {
451
+ logger.warn(String.format("Target: '%s' doesn't support incremental export API. Will be ignored start_time option",
452
+ task.getTarget()));
453
+ }
454
+ }
455
+ }
456
+
457
+ private void validateCustomObject(PluginTask task)
458
+ {
459
+ if (task.getTarget().equals(Target.OBJECT_RECORDS) && task.getObjectTypes().isEmpty()) {
460
+ throw new ConfigException("Should have at least one Object Type");
461
+ }
462
+
463
+ if (task.getTarget().equals(Target.RELATIONSHIP_RECORDS) && task.getRelationshipTypes().isEmpty()) {
464
+ throw new ConfigException("Should have at least one Relationship Type");
465
+ }
466
+ }
467
+
468
+ private void validateUserEvent(PluginTask task)
469
+ {
470
+ if (task.getTarget().equals(Target.USER_EVENTS)) {
471
+ if (!task.getProfileSource().isPresent()) {
472
+ throw new ConfigException("Profile Source is required for User Event Target");
473
+ }
474
+ }
475
+ }
476
+
477
+ private void validateTime(PluginTask task)
478
+ {
479
+ if (getZendeskService(task).isSupportIncremental()) {
480
+ // Can't set end_time to 0, so it should be valid
481
+ task.getEndTime().ifPresent(time -> {
482
+ if (!ZendeskDateUtils.supportedTimeFormat(task.getEndTime().get()).isPresent()) {
483
+ throw new ConfigException("End Time should follow these format " + ZendeskConstants.Misc.SUPPORT_DATE_TIME_FORMAT.toString());
484
+ }
485
+ });
486
+
487
+ if (task.getStartTime().isPresent() && task.getEndTime().isPresent()
488
+ && ZendeskDateUtils.getStartTime(task.getStartTime().get()) > ZendeskDateUtils.isoToEpochSecond(task.getEndTime().get())) {
489
+ throw new ConfigException("End Time should be later or equal than Start Time");
490
+ }
491
+ }
492
+ }
493
+
494
+ private boolean isValidTimeRange(PluginTask task)
495
+ {
496
+ return !task.getEndTime().isPresent() || ZendeskDateUtils.isoToEpochSecond(task.getEndTime().get()) <= Instant.now().getEpochSecond();
497
+ }
498
+
499
+ private TaskReport buildTaskReportKeepOldStartAndEndTime(PluginTask task)
500
+ {
501
+ final TaskReport taskReport = Exec.newTaskReport();
502
+
503
+ if (task.getStartTime().isPresent()) {
504
+ taskReport.set(ZendeskConstants.Field.START_TIME, ZendeskDateUtils.isoToEpochSecond(task.getStartTime().get()));
505
+ }
506
+
507
+ if (task.getEndTime().isPresent()) {
508
+ taskReport.set(ZendeskConstants.Field.END_TIME, ZendeskDateUtils.isoToEpochSecond(task.getEndTime().get()));
509
+ }
510
+
511
+ return taskReport;
512
+ }
513
+ }