embulk-input-zendesk 0.2.14 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -3
  3. data/.travis.yml +5 -44
  4. data/CHANGELOG.md +3 -0
  5. data/README.md +5 -5
  6. data/build.gradle +123 -0
  7. data/classpath/commons-codec-1.10.jar +0 -0
  8. data/classpath/commons-logging-1.2.jar +0 -0
  9. data/classpath/embulk-input-zendesk-0.3.0.jar +0 -0
  10. data/classpath/httpclient-4.5.6.jar +0 -0
  11. data/classpath/httpcore-4.4.10.jar +0 -0
  12. data/config/checkstyle/checkstyle.xml +128 -0
  13. data/config/checkstyle/default.xml +108 -0
  14. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  15. data/gradle/wrapper/gradle-wrapper.properties +5 -0
  16. data/gradlew +172 -0
  17. data/gradlew.bat +84 -0
  18. data/lib/embulk/guess/zendesk.rb +21 -0
  19. data/lib/embulk/input/zendesk.rb +3 -9
  20. data/src/main/java/org/embulk/input/zendesk/ZendeskInputPlugin.java +471 -0
  21. data/src/main/java/org/embulk/input/zendesk/clients/ZendeskRestClient.java +268 -0
  22. data/src/main/java/org/embulk/input/zendesk/models/AuthenticationMethod.java +23 -0
  23. data/src/main/java/org/embulk/input/zendesk/models/Target.java +46 -0
  24. data/src/main/java/org/embulk/input/zendesk/models/ZendeskException.java +25 -0
  25. data/src/main/java/org/embulk/input/zendesk/services/ZendeskSupportAPIService.java +109 -0
  26. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskConstants.java +61 -0
  27. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskDateUtils.java +51 -0
  28. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskUtils.java +150 -0
  29. data/src/main/java/org/embulk/input/zendesk/utils/ZendeskValidatorUtils.java +92 -0
  30. data/src/test/java/org/embulk/input/zendesk/TestZendeskInputPlugin.java +232 -0
  31. data/src/test/java/org/embulk/input/zendesk/clients/TestZendeskRestClient.java +351 -0
  32. data/src/test/java/org/embulk/input/zendesk/services/TestZendeskSupportAPIService.java +172 -0
  33. data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskDateUtils.java +36 -0
  34. data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskUtil.java +160 -0
  35. data/src/test/java/org/embulk/input/zendesk/utils/TestZendeskValidatorUtils.java +138 -0
  36. data/src/test/java/org/embulk/input/zendesk/utils/ZendeskPluginTestRuntime.java +133 -0
  37. data/src/test/java/org/embulk/input/zendesk/utils/ZendeskTestHelper.java +63 -0
  38. data/src/test/resources/config/base.yml +14 -0
  39. data/src/test/resources/config/base_validator.yml +48 -0
  40. data/src/test/resources/config/incremental.yml +54 -0
  41. data/src/test/resources/config/non-incremental.yml +39 -0
  42. data/src/test/resources/config/util.yml +18 -0
  43. data/src/test/resources/data/client.json +293 -0
  44. data/src/test/resources/data/error_data.json +187 -0
  45. data/src/test/resources/data/expected/ticket_column.json +148 -0
  46. data/src/test/resources/data/expected/ticket_column_with_related_objects.json +152 -0
  47. data/src/test/resources/data/expected/ticket_fields_column.json +92 -0
  48. data/src/test/resources/data/expected/ticket_metrics_column.json +98 -0
  49. data/src/test/resources/data/ticket_fields.json +225 -0
  50. data/src/test/resources/data/ticket_metrics.json +397 -0
  51. data/src/test/resources/data/ticket_with_related_objects.json +67 -0
  52. data/src/test/resources/data/tickets.json +232 -0
  53. data/src/test/resources/data/tickets_continue.json +52 -0
  54. data/src/test/resources/data/util.json +19 -0
  55. data/src/test/resources/data/util_page.json +227 -0
  56. metadata +65 -221
  57. data/.ruby-version +0 -1
  58. data/.travis.yml.erb +0 -43
  59. data/Gemfile +0 -2
  60. data/Rakefile +0 -21
  61. data/embulk-input-zendesk.gemspec +0 -29
  62. data/gemfiles/embulk-0.8.0-latest +0 -4
  63. data/gemfiles/embulk-0.8.1 +0 -4
  64. data/gemfiles/embulk-latest +0 -4
  65. data/gemfiles/template.erb +0 -4
  66. data/lib/embulk/input/zendesk/client.rb +0 -434
  67. data/lib/embulk/input/zendesk/plugin.rb +0 -199
  68. data/test/capture_io.rb +0 -45
  69. data/test/embulk/input/zendesk/test_client.rb +0 -722
  70. data/test/embulk/input/zendesk/test_plugin.rb +0 -628
  71. data/test/fixture_helper.rb +0 -11
  72. data/test/fixtures/invalid_app_marketplace_lack_one_property.yml +0 -13
  73. data/test/fixtures/invalid_app_marketplace_lack_two_property.yml +0 -12
  74. data/test/fixtures/invalid_lack_username.yml +0 -9
  75. data/test/fixtures/invalid_unknown_auth.yml +0 -9
  76. data/test/fixtures/tickets.json +0 -44
  77. data/test/fixtures/valid_app_marketplace.yml +0 -14
  78. data/test/fixtures/valid_auth_basic.yml +0 -11
  79. data/test/fixtures/valid_auth_oauth.yml +0 -10
  80. data/test/fixtures/valid_auth_token.yml +0 -11
  81. data/test/override_assert_raise.rb +0 -21
  82. data/test/run-test.rb +0 -26
@@ -0,0 +1,268 @@
1
+ package org.embulk.input.zendesk.clients;
2
+
3
+ import com.fasterxml.jackson.databind.DeserializationFeature;
4
+ import com.fasterxml.jackson.databind.ObjectMapper;
5
+ import com.google.common.annotations.VisibleForTesting;
6
+ import com.google.common.collect.ImmutableMap;
7
+ import com.google.common.collect.ImmutableMap.Builder;
8
+ import com.google.common.util.concurrent.RateLimiter;
9
+ import com.google.common.util.concurrent.Uninterruptibles;
10
+
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
+ import static org.apache.http.HttpHeaders.AUTHORIZATION;
30
+ import static org.apache.http.protocol.HTTP.CONTENT_TYPE;
31
+ import static org.embulk.spi.util.RetryExecutor.retryExecutor;
32
+
33
+ import java.io.IOException;
34
+
35
+ import java.util.concurrent.TimeUnit;
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
+
43
+ private static RateLimiter rateLimiter;
44
+
45
+ private static final ObjectMapper objectMapper = new ObjectMapper();
46
+
47
+ static {
48
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
49
+ }
50
+
51
+ public ZendeskRestClient()
52
+ {
53
+ }
54
+
55
+ public String doGet(String url, PluginTask task, boolean isPreview)
56
+ {
57
+ try {
58
+ return retryExecutor().withRetryLimit(task.getRetryLimit())
59
+ .withInitialRetryWait(task.getRetryInitialWaitSec() * 1000)
60
+ .withMaxRetryWait(task.getMaxRetryWaitSec() * 1000)
61
+ .runInterruptible(new RetryExecutor.Retryable<String>() {
62
+ @Override
63
+ public String call() throws Exception
64
+ {
65
+ return sendGetRequest(url, task);
66
+ }
67
+
68
+ @Override
69
+ public boolean isRetryableException(Exception exception)
70
+ {
71
+ if (exception instanceof ZendeskException) {
72
+ 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(Exception exception, int retryCount, int retryLimit, int retryWait)
80
+ {
81
+ if (exception instanceof ZendeskException) {
82
+ int retryAfter = ((ZendeskException) exception).getRetryAfter();
83
+ 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
+ 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(Exception firstException, Exception lastException)
114
+ {
115
+ }
116
+ });
117
+ }
118
+ catch (RetryExecutor.RetryGiveupException | InterruptedException e) {
119
+ if (e instanceof RetryExecutor.RetryGiveupException && e.getCause() != null && e.getCause() instanceof ZendeskException) {
120
+ throw new ConfigException(e.getCause().getMessage());
121
+ }
122
+ throw new ConfigException(e);
123
+ }
124
+ }
125
+
126
+ @VisibleForTesting
127
+ protected HttpClient createHttpClient()
128
+ {
129
+ RequestConfig config = RequestConfig.custom()
130
+ .setConnectTimeout(CONNECTION_TIME_OUT)
131
+ .setConnectionRequestTimeout(CONNECTION_TIME_OUT)
132
+ .build();
133
+ return HttpClientBuilder.create().setDefaultRequestConfig(config).build();
134
+ }
135
+
136
+ private String sendGetRequest(final String url, final PluginTask task) throws ZendeskException
137
+ {
138
+ try {
139
+ HttpClient client = createHttpClient();
140
+ HttpRequestBase request = createGetRequest(url, task);
141
+ logger.info(">>> {}{}", request.getURI().getPath(),
142
+ request.getURI().getQuery() != null ? "?" + request.getURI().getQuery() : "");
143
+ HttpResponse response = client.execute(request);
144
+ getRateLimiter(response).acquire();
145
+ int statusCode = response.getStatusLine().getStatusCode();
146
+ if (statusCode != HttpStatus.SC_OK) {
147
+ if (statusCode == ZendeskConstants.HttpStatus.TOO_MANY_REQUEST || statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
148
+ || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE) {
149
+ Header retryHeader = response.getFirstHeader("Retry-After");
150
+ if (retryHeader != null) {
151
+ throw new ZendeskException(statusCode, EntityUtils.toString(response.getEntity()), Integer.parseInt(retryHeader.getValue()));
152
+ }
153
+ }
154
+ throw new ZendeskException(statusCode, EntityUtils.toString(response.getEntity()), 0);
155
+ }
156
+ return EntityUtils.toString(response.getEntity());
157
+ }
158
+ catch (IOException ex) {
159
+ throw new ZendeskException(-1, ex.getMessage(), 0);
160
+ }
161
+ }
162
+
163
+ private boolean isResponseStatusToRetry(final int status, final String message, int retryAfter, final boolean isPreview)
164
+ {
165
+ if (status == HttpStatus.SC_NOT_FOUND) {
166
+ // 404 would be returned e.g. ticket comments are empty (on fetchRelatedObjects method)
167
+ return false;
168
+ }
169
+
170
+ if (status == HttpStatus.SC_CONFLICT) {
171
+ logger.warn(String.format("'%s' temporally failure.", status));
172
+ return true;
173
+ }
174
+
175
+ if (status == HttpStatus.SC_UNPROCESSABLE_ENTITY) {
176
+ if (message.startsWith(ZendeskConstants.Misc.TOO_RECENT_START_TIME)) {
177
+ //That means "No records from start_time". We can recognize it same as 200.
178
+ return false;
179
+ }
180
+ throw new ConfigException("Status: '" + status + "', error message '" + message + "'");
181
+ }
182
+
183
+ if (status == ZendeskConstants.HttpStatus.TOO_MANY_REQUEST || status == HttpStatus.SC_INTERNAL_SERVER_ERROR
184
+ || status == HttpStatus.SC_SERVICE_UNAVAILABLE) {
185
+ if (!isPreview) {
186
+ if (retryAfter > 0) {
187
+ logger.warn("Reached API limitation, wait for at least '{}' '{}'", retryAfter, TimeUnit.SECONDS.name());
188
+ }
189
+ else if (status != ZendeskConstants.HttpStatus.TOO_MANY_REQUEST) {
190
+ logger.warn(String.format("'%s' temporally failure.", status));
191
+ }
192
+ return true;
193
+ }
194
+ throw new DataException("Rate Limited. Waiting '" + retryAfter + "' seconds to re-run");
195
+ }
196
+
197
+ // Won't retry for 4xx range errors except above. Almost they should be ConfigError e.g. 403 Forbidden
198
+ if (status / 100 == 4) {
199
+ throw new ConfigException("Status '" + status + "', message '" + message + "'");
200
+ }
201
+
202
+ logger.warn("Server returns unknown status code '" + status + "' message '" + message + "'");
203
+ return true;
204
+ }
205
+
206
+ private HttpRequestBase createGetRequest(String url, PluginTask task)
207
+ {
208
+ HttpGet request = new HttpGet(url);
209
+ ImmutableMap<String, String> headers = buildAuthHeader(task);
210
+ headers.forEach(request::setHeader);
211
+ return request;
212
+ }
213
+
214
+ private ImmutableMap<String, String> buildAuthHeader(PluginTask task)
215
+ {
216
+ Builder<String, String> builder = new Builder<>();
217
+ builder.put(AUTHORIZATION, buildCredential(task));
218
+ addCommonHeader(builder, task);
219
+ return builder.build();
220
+ }
221
+
222
+ private String buildCredential(PluginTask task)
223
+ {
224
+ switch (task.getAuthenticationMethod()) {
225
+ case BASIC:
226
+ return "Basic " + ZendeskUtils.convertBase64(String.format("%s:%s", task.getUsername().get(), task.getPassword().get()));
227
+ case TOKEN:
228
+ return "Basic " + ZendeskUtils.convertBase64(String.format("%s/token:%s", task.getUsername().get(), task.getToken().get()));
229
+ case OAUTH:
230
+ return "Bearer " + task.getAccessToken().get();
231
+ }
232
+ return "";
233
+ }
234
+
235
+ private void addCommonHeader(final Builder<String, String> builder, PluginTask task)
236
+ {
237
+ task.getAppMarketPlaceIntegrationName().ifPresent(s -> builder.put(ZendeskConstants.Header.ZENDESK_MARKETPLACE_NAME, s));
238
+ task.getAppMarketPlaceAppId().ifPresent(s -> builder.put(ZendeskConstants.Header.ZENDESK_MARKETPLACE_APP_ID, s));
239
+ task.getAppMarketPlaceOrgId().ifPresent(s -> builder.put(ZendeskConstants.Header.ZENDESK_MARKETPLACE_ORGANIZATION_ID, s));
240
+
241
+ builder.put(CONTENT_TYPE, ZendeskConstants.Header.APPLICATION_JSON);
242
+ }
243
+
244
+ private RateLimiter getRateLimiter(final HttpResponse response)
245
+ {
246
+ if (rateLimiter == null) {
247
+ rateLimiter = initRateLimiter(response);
248
+ }
249
+ return rateLimiter;
250
+ }
251
+
252
+ private static synchronized RateLimiter initRateLimiter(final HttpResponse response)
253
+ {
254
+ double permits = 0.0;
255
+ if (response.containsHeader("x-rate-limit")) {
256
+ String rateLimit = response.getFirstHeader("x-rate-limit").getValue();
257
+ try {
258
+ permits = Double.parseDouble(rateLimit);
259
+ }
260
+ catch (NumberFormatException e) {
261
+ throw new DataException("Error when parse x-rate-limit: '" + response.getFirstHeader("x-rate-limit").getValue() + "'");
262
+ }
263
+ permits = permits / 60;
264
+ }
265
+ logger.info("Permits per second " + permits);
266
+ return RateLimiter.create(permits);
267
+ }
268
+ }
@@ -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,46 @@
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"),
14
+ TICKET_EVENTS("ticket_events"), TICKET_METRICS("metric_sets"),
15
+ TICKET_FIELDS("ticket_fields"), TICKET_FORMS("ticket_forms");
16
+
17
+ String jsonName;
18
+
19
+ Target(String jsonName)
20
+ {
21
+ this.jsonName = jsonName;
22
+ }
23
+
24
+ @JsonCreator
25
+ public static Target fromString(final String value)
26
+ {
27
+ try {
28
+ return Target.valueOf(value.trim().toUpperCase());
29
+ }
30
+ catch (IllegalArgumentException e) {
31
+ throw new ConfigException("Unsupported target '" + value + "', supported values: '"
32
+ + Arrays.toString(Target.values()) + "'");
33
+ }
34
+ }
35
+
36
+ @Override
37
+ public String toString()
38
+ {
39
+ return this.name().trim().toLowerCase();
40
+ }
41
+
42
+ public String getJsonName()
43
+ {
44
+ return this.jsonName;
45
+ }
46
+ }
@@ -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(int statusCode, String message, 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,109 @@
1
+ package org.embulk.input.zendesk.services;
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.fasterxml.jackson.databind.node.ObjectNode;
7
+ import com.google.common.annotations.VisibleForTesting;
8
+ import com.google.common.base.Throwables;
9
+ import org.embulk.input.zendesk.ZendeskInputPlugin.PluginTask;
10
+ import org.embulk.input.zendesk.clients.ZendeskRestClient;
11
+ import org.embulk.input.zendesk.models.Target;
12
+ import org.embulk.input.zendesk.utils.ZendeskConstants;
13
+ import org.embulk.input.zendesk.utils.ZendeskUtils;
14
+ import org.embulk.spi.DataException;
15
+
16
+ import java.io.IOException;
17
+
18
+ public class ZendeskSupportAPIService
19
+ {
20
+ private ZendeskRestClient zendeskRestClient;
21
+
22
+ private PluginTask task;
23
+
24
+ private static ObjectMapper mapper = new ObjectMapper();
25
+
26
+ static {
27
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
28
+ mapper.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, false);
29
+ }
30
+
31
+ public ZendeskSupportAPIService(final PluginTask task)
32
+ {
33
+ this.task = task;
34
+ }
35
+
36
+ public void setTask(PluginTask task)
37
+ {
38
+ this.task = task;
39
+ }
40
+
41
+ public JsonNode getData(String path, final int page, final boolean isPreview, long startTime)
42
+ {
43
+ if (path.isEmpty()) {
44
+ path = buildPath(page, startTime);
45
+ }
46
+ try {
47
+ final String response = getZendeskRestClient().doGet(path, task, isPreview);
48
+ return parseJsonObject(response);
49
+ }
50
+ catch (final Exception e) {
51
+ throw Throwables.propagate(e);
52
+ }
53
+ }
54
+
55
+ @VisibleForTesting
56
+ protected ZendeskRestClient getZendeskRestClient()
57
+ {
58
+ if (zendeskRestClient == null) {
59
+ zendeskRestClient = new ZendeskRestClient();
60
+ }
61
+ return zendeskRestClient;
62
+ }
63
+
64
+ private ObjectNode parseJsonObject(final String jsonText)
65
+ {
66
+ JsonNode node = parseJsonNode(jsonText);
67
+ if (node.isObject()) {
68
+ return (ObjectNode) node;
69
+ }
70
+
71
+ throw new DataException("Expected object node to parse but doesn't get");
72
+ }
73
+
74
+ private JsonNode parseJsonNode(final String jsonText)
75
+ {
76
+ try {
77
+ return mapper.readTree(jsonText);
78
+ }
79
+ catch (final IOException e) {
80
+ throw Throwables.propagate(e);
81
+ }
82
+ }
83
+
84
+ private String buildPath(final int page, long startTime)
85
+ {
86
+ boolean isSupportIncremental = ZendeskUtils.isSupportAPIIncremental(task.getTarget());
87
+
88
+ StringBuilder urlBuilder = new StringBuilder(task.getLoginUrl())
89
+ .append(isSupportIncremental
90
+ ? ZendeskConstants.Url.API_INCREMENTAL
91
+ : ZendeskConstants.Url.API)
92
+ .append("/")
93
+ .append(Target.TICKET_METRICS.equals(task.getTarget())
94
+ ? Target.TICKETS.toString()
95
+ : task.getTarget().toString())
96
+ .append(".json?");
97
+
98
+ urlBuilder
99
+ .append(isSupportIncremental
100
+ ? "start_time=" + startTime
101
+ : "sort_by=id&per_page=100&page=" + page);
102
+
103
+ if (Target.TICKET_METRICS.equals(task.getTarget())) {
104
+ urlBuilder.append("&include=metric_sets");
105
+ }
106
+
107
+ return urlBuilder.toString();
108
+ }
109
+ }