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,291 @@
1
+ package org.embulk.input.zendesk.clients;
2
+
3
+ import com.fasterxml.jackson.databind.DeserializationFeature;
4
+ import com.fasterxml.jackson.databind.JsonNode;
5
+ import com.fasterxml.jackson.databind.ObjectMapper;
6
+ import com.google.common.annotations.VisibleForTesting;
7
+ import com.google.common.collect.ImmutableMap;
8
+ import com.google.common.collect.ImmutableMap.Builder;
9
+ import com.google.common.util.concurrent.RateLimiter;
10
+ import com.google.common.util.concurrent.Uninterruptibles;
11
+ import org.apache.http.Header;
12
+ import org.apache.http.HttpResponse;
13
+ import org.apache.http.HttpStatus;
14
+ import org.apache.http.client.HttpClient;
15
+ import org.apache.http.client.config.RequestConfig;
16
+ import org.apache.http.client.methods.HttpGet;
17
+ import org.apache.http.client.methods.HttpRequestBase;
18
+ import org.apache.http.impl.client.HttpClientBuilder;
19
+ import org.apache.http.util.EntityUtils;
20
+ import org.embulk.config.ConfigException;
21
+ import org.embulk.input.zendesk.ZendeskInputPlugin.PluginTask;
22
+ import org.embulk.input.zendesk.models.ZendeskException;
23
+ import org.embulk.input.zendesk.utils.ZendeskConstants;
24
+ import org.embulk.input.zendesk.utils.ZendeskUtils;
25
+ import org.embulk.spi.DataException;
26
+ import org.embulk.spi.Exec;
27
+ import org.embulk.spi.util.RetryExecutor;
28
+ import org.slf4j.Logger;
29
+
30
+ import java.io.IOException;
31
+ import java.util.concurrent.TimeUnit;
32
+
33
+ import static org.apache.http.HttpHeaders.AUTHORIZATION;
34
+ import static org.apache.http.protocol.HTTP.CONTENT_TYPE;
35
+ import static org.embulk.spi.util.RetryExecutor.retryExecutor;
36
+
37
+ public class ZendeskRestClient
38
+ {
39
+ private static final int CONNECTION_TIME_OUT = 240000;
40
+
41
+ private static final Logger logger = Exec.getLogger(ZendeskRestClient.class);
42
+ private static final ObjectMapper objectMapper = new ObjectMapper();
43
+ private static RateLimiter rateLimiter;
44
+
45
+ static {
46
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
47
+ }
48
+
49
+ public ZendeskRestClient()
50
+ {
51
+ }
52
+
53
+ public String doGet(final String url, final PluginTask task, final boolean isPreview)
54
+ {
55
+ try {
56
+ return retryExecutor().withRetryLimit(task.getRetryLimit())
57
+ .withInitialRetryWait(task.getRetryInitialWaitSec() * 1000)
58
+ .withMaxRetryWait(task.getMaxRetryWaitSec() * 1000)
59
+ .runInterruptible(new RetryExecutor.Retryable<String>()
60
+ {
61
+ @Override
62
+ public String call()
63
+ throws Exception
64
+ {
65
+ return sendGetRequest(url, task);
66
+ }
67
+
68
+ @Override
69
+ public boolean isRetryableException(final Exception exception)
70
+ {
71
+ if (exception instanceof ZendeskException) {
72
+ final int statusCode = ((ZendeskException) exception).getStatusCode();
73
+ return isResponseStatusToRetry(statusCode, exception.getMessage(), ((ZendeskException) exception).getRetryAfter(), isPreview);
74
+ }
75
+ return false;
76
+ }
77
+
78
+ @Override
79
+ public void onRetry(final Exception exception, final int retryCount, final int retryLimit, final int retryWait)
80
+ {
81
+ if (exception instanceof ZendeskException) {
82
+ final int retryAfter = ((ZendeskException) exception).getRetryAfter();
83
+ final String message;
84
+ if (retryAfter > 0 && retryAfter > (retryWait / 1000)) {
85
+ message = String
86
+ .format("Retrying '%d'/'%d' after '%d' seconds. HTTP status code: '%s'",
87
+ retryCount, retryLimit,
88
+ retryAfter,
89
+ ((ZendeskException) exception).getStatusCode());
90
+ logger.warn(message);
91
+ Uninterruptibles.sleepUninterruptibly(retryAfter - (retryWait / 1000), TimeUnit.SECONDS);
92
+ }
93
+ else {
94
+ message = String
95
+ .format("Retrying '%d'/'%d' after '%d' seconds. HTTP status code: '%s'",
96
+ retryCount, retryLimit,
97
+ retryWait / 1000,
98
+ ((ZendeskException) exception).getStatusCode());
99
+ logger.warn(message);
100
+ }
101
+ }
102
+ else {
103
+ final String message = String
104
+ .format("Retrying '%d'/'%d' after '%d' seconds. Message: '%s'",
105
+ retryCount, retryLimit,
106
+ retryWait / 1000,
107
+ exception.getMessage());
108
+ logger.warn(message, exception);
109
+ }
110
+ }
111
+
112
+ @Override
113
+ public void onGiveup(final Exception firstException, final Exception lastException)
114
+ {
115
+ }
116
+ });
117
+ }
118
+ catch (final RetryExecutor.RetryGiveupException | InterruptedException e) {
119
+ if (e instanceof RetryExecutor.RetryGiveupException && e.getCause() != null && e.getCause() instanceof ZendeskException) {
120
+ throw new ConfigException("Status: '" + ((ZendeskException) (e.getCause())).getStatusCode() + "', error message: '" + e.getCause().getMessage() + "'",
121
+ e.getCause());
122
+ }
123
+ throw new ConfigException(e);
124
+ }
125
+ }
126
+
127
+ @VisibleForTesting
128
+ protected HttpClient createHttpClient()
129
+ {
130
+ final RequestConfig config = RequestConfig.custom()
131
+ .setConnectTimeout(CONNECTION_TIME_OUT)
132
+ .setConnectionRequestTimeout(CONNECTION_TIME_OUT)
133
+ .build();
134
+ return HttpClientBuilder.create().setDefaultRequestConfig(config).build();
135
+ }
136
+
137
+ private String sendGetRequest(final String url, final PluginTask task)
138
+ throws ZendeskException
139
+ {
140
+ try {
141
+ final HttpClient client = createHttpClient();
142
+ final HttpRequestBase request = createGetRequest(url, task);
143
+
144
+ if (rateLimiter != null) {
145
+ rateLimiter.acquire();
146
+ }
147
+ logger.info(">>> {}{}", request.getURI().getPath(),
148
+ request.getURI().getQuery() != null ? "?" + request.getURI().getQuery() : "");
149
+ final HttpResponse response = client.execute(request);
150
+ if (rateLimiter == null) {
151
+ initRateLimiter(response);
152
+ }
153
+ final int statusCode = response.getStatusLine().getStatusCode();
154
+ if (statusCode != HttpStatus.SC_OK) {
155
+ if (statusCode == ZendeskConstants.HttpStatus.TOO_MANY_REQUEST || statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
156
+ || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE) {
157
+ final Header retryHeader = response.getFirstHeader("Retry-After");
158
+ if (retryHeader != null) {
159
+ throw new ZendeskException(statusCode, EntityUtils.toString(response.getEntity()), Integer.parseInt(retryHeader.getValue()));
160
+ }
161
+ }
162
+ throw new ZendeskException(statusCode, EntityUtils.toString(response.getEntity()), 0);
163
+ }
164
+ return EntityUtils.toString(response.getEntity());
165
+ }
166
+ catch (final IOException ex) {
167
+ throw new ZendeskException(-1, ex.getMessage(), 0);
168
+ }
169
+ }
170
+
171
+ private boolean isResponseStatusToRetry(final int status, final String message, final int retryAfter, final boolean isPreview)
172
+ {
173
+ if (status == HttpStatus.SC_NOT_FOUND) {
174
+ ObjectMapper objectMapper = new ObjectMapper();
175
+ try {
176
+ JsonNode jsonNode = objectMapper.readTree(message);
177
+ if (jsonNode.has("error") && jsonNode.get("error").has("title") && jsonNode.get("error").get("title").asText().startsWith("No help desk at ")) {
178
+ throw new ConfigException("This address is not registered in Zendesk. Please check the login_url again");
179
+ }
180
+ }
181
+ catch (IOException e) {
182
+ // In case we can't parse the message, error should not be show here
183
+ }
184
+
185
+ // 404 would be returned e.g. ticket comments are empty (on fetchRelatedObjects method)
186
+ return false;
187
+ }
188
+
189
+ if (status == HttpStatus.SC_CONFLICT) {
190
+ logger.warn(String.format("'%s' temporally failure.", status));
191
+ return true;
192
+ }
193
+
194
+ if (status == HttpStatus.SC_UNPROCESSABLE_ENTITY) {
195
+ if (message.startsWith(ZendeskConstants.Misc.TOO_RECENT_START_TIME)) {
196
+ //That means "No records from start_time". We can recognize it same as 200.
197
+ return false;
198
+ }
199
+ throw new ConfigException("Status: '" + status + "', error message: '" + message + "'");
200
+ }
201
+
202
+ if (status == ZendeskConstants.HttpStatus.TOO_MANY_REQUEST || status == HttpStatus.SC_INTERNAL_SERVER_ERROR
203
+ || status == HttpStatus.SC_SERVICE_UNAVAILABLE) {
204
+ if (!isPreview) {
205
+ if (retryAfter > 0) {
206
+ logger.warn("Reached API limitation, wait for at least '{}' '{}'", retryAfter, TimeUnit.SECONDS.name());
207
+ }
208
+ else if (status != ZendeskConstants.HttpStatus.TOO_MANY_REQUEST) {
209
+ logger.warn(String.format("'%s' temporally failure.", status));
210
+ }
211
+ return true;
212
+ }
213
+ throw new DataException("Rate Limited. Waiting '" + retryAfter + "' seconds to re-run");
214
+ }
215
+
216
+ // Won't retry for 4xx range errors except above. Almost they should be ConfigError e.g. 403 Forbidden
217
+ if (status / 100 == 4) {
218
+ if (status == HttpStatus.SC_UNAUTHORIZED) {
219
+ throw new ConfigException("Cannot authenticate due to invalid login credentials");
220
+ }
221
+
222
+ if (status == HttpStatus.SC_FORBIDDEN) {
223
+ throw new ConfigException("You do not have access to this resource. Please contact the account owner of this help desk for further help.");
224
+ }
225
+
226
+ throw new ConfigException("Status '" + status + "', message '" + message + "'");
227
+ }
228
+
229
+ logger.warn("Server returns unknown status code '" + status + "' message '" + message + "'");
230
+ return true;
231
+ }
232
+
233
+ private HttpRequestBase createGetRequest(final String url, final PluginTask task)
234
+ {
235
+ final HttpGet request = new HttpGet(url);
236
+ final ImmutableMap<String, String> headers = buildAuthHeader(task);
237
+ headers.forEach(request::setHeader);
238
+ return request;
239
+ }
240
+
241
+ private ImmutableMap<String, String> buildAuthHeader(final PluginTask task)
242
+ {
243
+ final Builder<String, String> builder = new Builder<>();
244
+ builder.put(AUTHORIZATION, buildCredential(task));
245
+ addCommonHeader(builder, task);
246
+ return builder.build();
247
+ }
248
+
249
+ private String buildCredential(final PluginTask task)
250
+ {
251
+ switch (task.getAuthenticationMethod()) {
252
+ case BASIC:
253
+ return "Basic " + ZendeskUtils.convertBase64(String.format("%s:%s", task.getUsername().get(), task.getPassword().get()));
254
+ case TOKEN:
255
+ return "Basic " + ZendeskUtils.convertBase64(String.format("%s/token:%s", task.getUsername().get(), task.getToken().get()));
256
+ case OAUTH:
257
+ return "Bearer " + task.getAccessToken().get();
258
+ }
259
+ return "";
260
+ }
261
+
262
+ private void addCommonHeader(final Builder<String, String> builder, final PluginTask task)
263
+ {
264
+ task.getAppMarketPlaceIntegrationName().ifPresent(s -> builder.put(ZendeskConstants.Header.ZENDESK_MARKETPLACE_NAME, s));
265
+ task.getAppMarketPlaceAppId().ifPresent(s -> builder.put(ZendeskConstants.Header.ZENDESK_MARKETPLACE_APP_ID, s));
266
+ task.getAppMarketPlaceOrgId().ifPresent(s -> builder.put(ZendeskConstants.Header.ZENDESK_MARKETPLACE_ORGANIZATION_ID, s));
267
+
268
+ builder.put(CONTENT_TYPE, ZendeskConstants.Header.APPLICATION_JSON);
269
+ }
270
+
271
+ private synchronized void initRateLimiter(final HttpResponse response)
272
+ {
273
+ if (response.containsHeader("x-rate-limit")) {
274
+ double permits = 0.0;
275
+ final String rateLimit = response.getFirstHeader("x-rate-limit").getValue();
276
+ try {
277
+ permits = Double.parseDouble(rateLimit);
278
+ }
279
+ catch (final NumberFormatException e) {
280
+ throw new DataException("Error when parse x-rate-limit: '" + response.getFirstHeader("x-rate-limit").getValue() + "'");
281
+ }
282
+
283
+ if (permits > 0) {
284
+ permits = permits / 60;
285
+ logger.info("Permits per second " + permits);
286
+
287
+ rateLimiter = RateLimiter.create(permits);
288
+ }
289
+ }
290
+ }
291
+ }
@@ -0,0 +1,23 @@
1
+ package org.embulk.input.zendesk.models;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonCreator;
4
+ import org.embulk.config.ConfigException;
5
+
6
+ import java.util.Arrays;
7
+
8
+ public enum AuthenticationMethod
9
+ {
10
+ BASIC, OAUTH, TOKEN;
11
+
12
+ @JsonCreator
13
+ public static AuthenticationMethod fromString(final String value)
14
+ {
15
+ try {
16
+ return AuthenticationMethod.valueOf(value.trim().toUpperCase());
17
+ }
18
+ catch (IllegalArgumentException e) {
19
+ throw new ConfigException("Unsupported Authentication mode '" + value + "', supported values: '"
20
+ + Arrays.toString(Target.values()) + "'");
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,47 @@
1
+ package org.embulk.input.zendesk.models;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonCreator;
4
+ import org.embulk.config.ConfigException;
5
+
6
+ import java.util.Arrays;
7
+
8
+ public enum Target
9
+ {
10
+ /** For ticket_metrics - we fetch by using include metric_sets with ticket target
11
+ * so the jsonName is different comparing to the target name
12
+ */
13
+ TICKETS("tickets"), USERS("users"), ORGANIZATIONS("organizations"), TICKET_EVENTS("ticket_events"),
14
+ TICKET_METRICS("metric_sets"), TICKET_FIELDS("ticket_fields"), TICKET_FORMS("ticket_forms"),
15
+ TICKET_METRIC_EVENTS("ticket_metric_events"), SATISFACTION_RATINGS("satisfaction_ratings"),
16
+ RECIPIENTS("recipients"), SCORES("responses"), OBJECT_RECORDS("data"), RELATIONSHIP_RECORDS("data"), USER_EVENTS("data");
17
+
18
+ String jsonName;
19
+
20
+ Target(String jsonName)
21
+ {
22
+ this.jsonName = jsonName;
23
+ }
24
+
25
+ @JsonCreator
26
+ public static Target fromString(final String value)
27
+ {
28
+ try {
29
+ return Target.valueOf(value.trim().toUpperCase());
30
+ }
31
+ catch (IllegalArgumentException e) {
32
+ throw new ConfigException("Unsupported target '" + value + "', supported values: '"
33
+ + Arrays.toString(Target.values()) + "'");
34
+ }
35
+ }
36
+
37
+ @Override
38
+ public String toString()
39
+ {
40
+ return this.name().trim().toLowerCase();
41
+ }
42
+
43
+ public String getJsonName()
44
+ {
45
+ return this.jsonName;
46
+ }
47
+ }
@@ -0,0 +1,25 @@
1
+ package org.embulk.input.zendesk.models;
2
+
3
+ public class ZendeskException extends Exception
4
+ {
5
+ private static final long serialVersionUID = -256731723520584046L;
6
+ private final int statusCode;
7
+ private final int retryAfter;
8
+
9
+ public ZendeskException(final int statusCode, final String message, final int retryAfter)
10
+ {
11
+ super(message);
12
+ this.statusCode = statusCode;
13
+ this.retryAfter = retryAfter;
14
+ }
15
+
16
+ public int getStatusCode()
17
+ {
18
+ return statusCode;
19
+ }
20
+
21
+ public int getRetryAfter()
22
+ {
23
+ return retryAfter;
24
+ }
25
+ }
@@ -0,0 +1,110 @@
1
+ package org.embulk.input.zendesk.services;
2
+
3
+ import com.fasterxml.jackson.databind.JsonNode;
4
+ import com.fasterxml.jackson.databind.ObjectMapper;
5
+ import com.google.common.annotations.VisibleForTesting;
6
+ import com.google.common.base.Preconditions;
7
+ import org.apache.http.HttpStatus;
8
+ import org.embulk.config.ConfigException;
9
+ import org.embulk.config.TaskReport;
10
+ import org.embulk.input.zendesk.RecordImporter;
11
+ import org.embulk.input.zendesk.ZendeskInputPlugin;
12
+ import org.embulk.input.zendesk.clients.ZendeskRestClient;
13
+ import org.embulk.input.zendesk.models.Target;
14
+ import org.embulk.input.zendesk.models.ZendeskException;
15
+ import org.embulk.input.zendesk.stream.paginator.sunshine.CustomObjectSpliterator;
16
+ import org.embulk.input.zendesk.utils.ZendeskConstants;
17
+ import org.embulk.input.zendesk.utils.ZendeskUtils;
18
+ import org.embulk.spi.Exec;
19
+
20
+ import java.util.Collections;
21
+ import java.util.List;
22
+ import java.util.Optional;
23
+ import java.util.stream.Collectors;
24
+ import java.util.stream.StreamSupport;
25
+
26
+ public class ZendeskCustomObjectService implements ZendeskService
27
+ {
28
+ protected ZendeskInputPlugin.PluginTask task;
29
+
30
+ private ZendeskRestClient zendeskRestClient;
31
+
32
+ public ZendeskCustomObjectService(final ZendeskInputPlugin.PluginTask task)
33
+ {
34
+ this.task = task;
35
+ }
36
+
37
+ public boolean isSupportIncremental()
38
+ {
39
+ return false;
40
+ }
41
+
42
+ @Override
43
+ public TaskReport addRecordToImporter(final int taskIndex, final RecordImporter recordImporter)
44
+ {
45
+ final List<String> paths = getListPathByTarget();
46
+
47
+ paths.parallelStream().forEach(path -> StreamSupport.stream(new CustomObjectSpliterator(path, getZendeskRestClient(), task, Exec.isPreview()), !Exec.isPreview())
48
+ .forEach(recordImporter::addRecord));
49
+
50
+ return Exec.newTaskReport();
51
+ }
52
+
53
+ @Override
54
+ public JsonNode getDataFromPath(final String path, final int page, final boolean isPreview, final long startTime)
55
+ {
56
+ Preconditions.checkArgument(isPreview, "IsPreview should be true to use this method");
57
+
58
+ Optional<String> response = Optional.empty();
59
+ final List<String> paths = path.isEmpty()
60
+ ? getListPathByTarget()
61
+ : Collections.singletonList(path);
62
+
63
+ for (final String temp : paths) {
64
+ try {
65
+ response = Optional.ofNullable(getZendeskRestClient().doGet(temp, task, true));
66
+
67
+ if (response.isPresent()) {
68
+ break;
69
+ }
70
+ }
71
+ catch (final ConfigException e) {
72
+ // Sometimes we get 404 when having invalid endpoint, so ignore when we get 404
73
+ if (!(e.getCause() instanceof ZendeskException && ((ZendeskException) e.getCause()).getStatusCode() == HttpStatus.SC_NOT_FOUND)) {
74
+ throw e;
75
+ }
76
+ }
77
+ }
78
+
79
+ return response.map(ZendeskUtils::parseJsonObject).orElse(new ObjectMapper().createObjectNode());
80
+ }
81
+
82
+ @VisibleForTesting
83
+ protected ZendeskRestClient getZendeskRestClient()
84
+ {
85
+ if (zendeskRestClient == null) {
86
+ zendeskRestClient = new ZendeskRestClient();
87
+ }
88
+ return zendeskRestClient;
89
+ }
90
+
91
+ private List<String> getListPathByTarget()
92
+ {
93
+ return task.getTarget().equals(Target.OBJECT_RECORDS)
94
+ ? task.getObjectTypes().stream().map(this::buildPath).collect(Collectors.toList())
95
+ : task.getRelationshipTypes().stream().map(this::buildPath).collect(Collectors.toList());
96
+ }
97
+
98
+ private String buildPath(final String value)
99
+ {
100
+ final String perPage = Exec.isPreview() ? "1" : "1000";
101
+
102
+ return ZendeskUtils.getURIBuilder(task.getLoginUrl())
103
+ .setPath(task.getTarget().equals(Target.OBJECT_RECORDS)
104
+ ? ZendeskConstants.Url.API_OBJECT_RECORD
105
+ : ZendeskConstants.Url.API_RELATIONSHIP_RECORD)
106
+ .setParameter("type", value)
107
+ .setParameter("per_page", perPage)
108
+ .toString();
109
+ }
110
+ }