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.
- 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
|
+
}
|