embulk-input-zendesk-all 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +126 -0
- data/LICENSE.txt +21 -0
- data/README.md +91 -0
- data/build.gradle +123 -0
- data/config/checkstyle/checkstyle.xml +128 -0
- data/config/checkstyle/default.xml +108 -0
- data/gradle/wrapper/gradle-wrapper.jar +0 -0
- data/gradle/wrapper/gradle-wrapper.properties +5 -0
- data/gradlew +172 -0
- data/gradlew.bat +84 -0
- data/lib/embulk/guess/zendesk.rb +21 -0
- data/lib/embulk/input/zendesk.rb +3 -0
- data/src/main/java/org/embulk/input/zendesk/RecordImporter.java +134 -0
- data/src/main/java/org/embulk/input/zendesk/ZendeskInputPlugin.java +513 -0
- data/src/main/java/org/embulk/input/zendesk/clients/ZendeskRestClient.java +291 -0
- data/src/main/java/org/embulk/input/zendesk/models/AuthenticationMethod.java +23 -0
- data/src/main/java/org/embulk/input/zendesk/models/Target.java +47 -0
- data/src/main/java/org/embulk/input/zendesk/models/ZendeskException.java +25 -0
- data/src/main/java/org/embulk/input/zendesk/services/ZendeskCustomObjectService.java +110 -0
- data/src/main/java/org/embulk/input/zendesk/services/ZendeskNPSService.java +30 -0
- data/src/main/java/org/embulk/input/zendesk/services/ZendeskNormalServices.java +347 -0
- data/src/main/java/org/embulk/input/zendesk/services/ZendeskService.java +14 -0
- data/src/main/java/org/embulk/input/zendesk/services/ZendeskSupportAPIService.java +63 -0
- data/src/main/java/org/embulk/input/zendesk/services/ZendeskUserEventService.java +158 -0
- data/src/main/java/org/embulk/input/zendesk/stream/PagingSpliterator.java +40 -0
- data/src/main/java/org/embulk/input/zendesk/stream/paginator/sunshine/CustomObjectSpliterator.java +42 -0
- data/src/main/java/org/embulk/input/zendesk/stream/paginator/sunshine/SunshineSpliterator.java +66 -0
- data/src/main/java/org/embulk/input/zendesk/stream/paginator/sunshine/UserEventSpliterator.java +35 -0
- data/src/main/java/org/embulk/input/zendesk/stream/paginator/support/OrganizationSpliterator.java +13 -0
- data/src/main/java/org/embulk/input/zendesk/stream/paginator/support/SupportSpliterator.java +44 -0
- data/src/main/java/org/embulk/input/zendesk/stream/paginator/support/UserSpliterator.java +13 -0
- data/src/main/java/org/embulk/input/zendesk/utils/ZendeskConstants.java +72 -0
- data/src/main/java/org/embulk/input/zendesk/utils/ZendeskDateUtils.java +68 -0
- data/src/main/java/org/embulk/input/zendesk/utils/ZendeskUtils.java +92 -0
- data/src/test/java/org/embulk/input/zendesk/TestRecordImporter.java +114 -0
- data/src/test/java/org/embulk/input/zendesk/TestZendeskInputPlugin.java +402 -0
- data/src/test/java/org/embulk/input/zendesk/clients/TestZendeskRestClient.java +337 -0
- data/src/test/java/org/embulk/input/zendesk/services/TestZendeskCustomObjectService.java +161 -0
- data/src/test/java/org/embulk/input/zendesk/services/TestZendeskNPSService.java +56 -0
- data/src/test/java/org/embulk/input/zendesk/services/TestZendeskNormalService.java +261 -0
- data/src/test/java/org/embulk/input/zendesk/services/TestZendeskSupportAPIService.java +130 -0
- data/src/test/java/org/embulk/input/zendesk/services/TestZendeskUserEventService.java +158 -0
- data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskDateUtils.java +87 -0
- data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskUtil.java +22 -0
- data/src/test/java/org/embulk/input/zendesk/utils/ZendeskPluginTestRuntime.java +133 -0
- data/src/test/java/org/embulk/input/zendesk/utils/ZendeskTestHelper.java +92 -0
- data/src/test/resources/config/base.yml +14 -0
- data/src/test/resources/config/base_validator.yml +48 -0
- data/src/test/resources/config/incremental.yml +54 -0
- data/src/test/resources/config/non-incremental.yml +39 -0
- data/src/test/resources/config/nps.yml +31 -0
- data/src/test/resources/config/object_records.yml +24 -0
- data/src/test/resources/config/relationship_records.yml +23 -0
- data/src/test/resources/config/user_events.yml +29 -0
- data/src/test/resources/config/util.yml +18 -0
- data/src/test/resources/data/client.json +293 -0
- data/src/test/resources/data/duplicate_user.json +0 -0
- data/src/test/resources/data/empty_result.json +7 -0
- data/src/test/resources/data/error_data.json +187 -0
- data/src/test/resources/data/expected/ticket_column.json +148 -0
- data/src/test/resources/data/expected/ticket_column_with_related_objects.json +152 -0
- data/src/test/resources/data/expected/ticket_fields_column.json +92 -0
- data/src/test/resources/data/expected/ticket_metrics_column.json +98 -0
- data/src/test/resources/data/expected/user_events_column.json +40 -0
- data/src/test/resources/data/object_records.json +30 -0
- data/src/test/resources/data/organization.json +39 -0
- data/src/test/resources/data/relationship_records.json +57 -0
- data/src/test/resources/data/scores.json +21 -0
- data/src/test/resources/data/scores_share_same_time_with_next_page.json +35 -0
- data/src/test/resources/data/scores_share_same_time_without_next_page.json +35 -0
- data/src/test/resources/data/simple_organization.json +23 -0
- data/src/test/resources/data/simple_user.json +50 -0
- data/src/test/resources/data/simple_user_event.json +19 -0
- data/src/test/resources/data/ticket_events_share_same_time_with_next_page.json +279 -0
- data/src/test/resources/data/ticket_events_share_same_time_without_next_page.json +279 -0
- data/src/test/resources/data/ticket_events_updated_by_system_records.json +279 -0
- data/src/test/resources/data/ticket_fields.json +225 -0
- data/src/test/resources/data/ticket_metrics.json +397 -0
- data/src/test/resources/data/ticket_share_same_time_with_next_page.json +232 -0
- data/src/test/resources/data/ticket_share_same_time_without_next_page.json +232 -0
- data/src/test/resources/data/ticket_with_related_objects.json +67 -0
- data/src/test/resources/data/ticket_with_updated_by_system_records.json +187 -0
- data/src/test/resources/data/tickets.json +232 -0
- data/src/test/resources/data/tickets_continue.json +52 -0
- data/src/test/resources/data/user_event.json +19 -0
- data/src/test/resources/data/user_event_contain_latter_create_at.json +19 -0
- data/src/test/resources/data/user_event_multiple.json +33 -0
- data/src/test/resources/data/util.json +19 -0
- data/src/test/resources/data/util_page.json +227 -0
- 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
|
+
}
|