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