embulk-input-zendesk-all 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
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
+ }