embulk-input-marketo 0.6.10 → 0.6.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +3 -0
- data/README.md +51 -6
- data/build.gradle +1 -1
- data/src/main/java/org/embulk/input/marketo/CsvTokenizer.java +18 -0
- data/src/main/java/org/embulk/input/marketo/MarketoInputPluginDelegate.java +7 -2
- data/src/main/java/org/embulk/input/marketo/MarketoService.java +6 -0
- data/src/main/java/org/embulk/input/marketo/MarketoServiceImpl.java +18 -0
- data/src/main/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPlugin.java +1 -0
- data/src/main/java/org/embulk/input/marketo/delegate/LeadWithListInputPlugin.java +1 -2
- data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPlugin.java +0 -4
- data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseInputPluginDelegate.java +9 -4
- data/src/main/java/org/embulk/input/marketo/delegate/ProgramInputPlugin.java +229 -0
- data/src/main/java/org/embulk/input/marketo/rest/MarketoBaseRestClient.java +1 -0
- data/src/main/java/org/embulk/input/marketo/rest/MarketoRESTEndpoint.java +2 -1
- data/src/main/java/org/embulk/input/marketo/rest/MarketoRestClient.java +24 -0
- data/src/test/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPluginTest.java +0 -3
- data/src/test/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPluginTest.java +1 -2
- data/src/test/java/org/embulk/input/marketo/delegate/ProgramInputPluginTest.java +345 -0
- data/src/test/java/org/embulk/input/marketo/rest/MarketoRestClientTest.java +101 -0
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b375807158e07d67abc81e6319ef6ec9a087f9b
|
4
|
+
data.tar.gz: 596f26cb4804ce01819b27499ead25b35c567100
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5743056090cbad09a2a75a6f460f2d843e721e254b2e898e7e809faad8f0309732244282c91f5dd6bbed9cafb95d0be519bba6305e8bee2726e49b0905867f8
|
7
|
+
data.tar.gz: ab9fe2a479f4ccc842efb62e80d14dc4bac8941b64dc6c7d589c35a412a6f782fc902edd75a014e48b410889dca3e63c99a184dd533675c0707c44f520da0696
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -12,6 +12,7 @@ embulk-input-marketo is the gem preparing Embulk input plugins for [Marketo](htt
|
|
12
12
|
- Lead by list(all_lead_with_list_id)
|
13
13
|
- Lead by program(all_lead_with_program_id)
|
14
14
|
- Campaign(campaign)
|
15
|
+
- Assets Programs (program)
|
15
16
|
|
16
17
|
This plugin uses Marketo REST API.
|
17
18
|
|
@@ -57,7 +58,7 @@ All bulk extract target use this configuration parameter
|
|
57
58
|
| name | required | default value | description |
|
58
59
|
|-----------------------------|----------|---------------|-------------------------------------------------------------------------------------------------------------------------------|
|
59
60
|
| **from_date** | true | | Import data since this date. Example: 2017-10-11T06:43:24+00:00 |
|
60
|
-
| **fetch_days** | false | 1 |
|
61
|
+
| **fetch_days** | false | 1 | Amount of days to fetch since from_date |
|
61
62
|
| **polling_interval_second** | false | 60 | Amount of time to wait between pooling job status in second |
|
62
63
|
| **bulk_job_timeout_second** | false | 3600 | Amount of time to wait for bulk job to complete in second |
|
63
64
|
| **incremental** | false | true | If incremental is set to true, next run will have from_date set to the previous to_date(calculated by from_date + fetch_days) |
|
@@ -74,7 +75,7 @@ Configuration:
|
|
74
75
|
| name | required | default value | description |
|
75
76
|
|---------------------|----------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
76
77
|
| **use_updated_at** | false | false | Support filter with `updateAt` column, but not all Marketo Account have the feature to filter by updatedAt, updatedAt don't support incremental ingestion |
|
77
|
-
| **included_fields** | false |
|
78
|
+
| **included_fields** | false | null | List of lead fields to included in export request sent to Marketo, can be used to reduce the size of BulkExtract file |
|
78
79
|
|
79
80
|
Schema type: Dynamic via describe lead endpoint.
|
80
81
|
|
@@ -84,7 +85,7 @@ Range ingestion: yes
|
|
84
85
|
|
85
86
|
### Activity
|
86
87
|
|
87
|
-
Activity target extract all Marketo
|
88
|
+
Activity target extract all Marketo activity log. Configuration include all bulk extract configuration
|
88
89
|
|
89
90
|
`target: activity`
|
90
91
|
|
@@ -116,7 +117,7 @@ Configuration:
|
|
116
117
|
|
117
118
|
| name | required | default value | description |
|
118
119
|
|---------------------|----------|---------------|-----------------------------------------------------------------------------------------------------------------|
|
119
|
-
| **included_fields** | false |
|
120
|
+
| **included_fields** | false | null | List of lead fields to included in export request sent to Marketo, can be used to reduce request, response size |
|
120
121
|
|
121
122
|
Schema type: Dynamic via describe leads. Schema will have 1 addition column name listId that contain the id of the list the lead belong to
|
122
123
|
|
@@ -124,7 +125,7 @@ Incremental support: no
|
|
124
125
|
|
125
126
|
Range ingestion: no
|
126
127
|
|
127
|
-
###
|
128
|
+
### Lead by program
|
128
129
|
|
129
130
|
Extract all Lead data including lead's program id
|
130
131
|
|
@@ -134,7 +135,7 @@ Configuration:
|
|
134
135
|
|
135
136
|
| name | required | default value | description |
|
136
137
|
|---------------------|----------|---------------|-----------------------------------------------------------------------------------------------------------------|
|
137
|
-
| **included_fields** | false |
|
138
|
+
| **included_fields** | false | null | List of lead fields to included in export request sent to Marketo, can be used to reduce request, response size |
|
138
139
|
|
139
140
|
Schema type: Dynamic via describe leads. Schema will have 1 addition column name listId that contain the id of the list the lead belong to
|
140
141
|
|
@@ -142,6 +143,32 @@ Incremental support: no
|
|
142
143
|
|
143
144
|
Range ingestion: no
|
144
145
|
|
146
|
+
### Assets programs
|
147
|
+
|
148
|
+
Get Assets Programs by Query Tag type, Date range or all if no query by specified.
|
149
|
+
|
150
|
+
`target: program`
|
151
|
+
|
152
|
+
Configuration:
|
153
|
+
|
154
|
+
| name | required | default value | description |
|
155
|
+
|-----------------------------|----------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
156
|
+
| **query_by** | false | null | Get assets programs by query, supported values `date_range`, `tag_type` leave unset to fetch all programs |
|
157
|
+
| **earliest_updated_at** | false | null | Required if query by `date_range` is selected. Exclude programs prior to this date. Must be valid ISO-8601 string |
|
158
|
+
| **latest_updated_at** | false | null | Required if query by `date_range` is selected. Exclude programs after this date. Must be valid ISO-8601 string |
|
159
|
+
| **filter_type** | false | null | Optional value send with query by `date_range` is selected to filter out the result from Marketo. Supported values `id`, `programId`, `folderId`, `workspace`|
|
160
|
+
| **filter_values** | false | null | Set the values associated with `filter_type` |
|
161
|
+
| **tag_type** | false | null | Required if query by `tag_type` is selected. Type of program tag |
|
162
|
+
| **tag_value** | false | null | Required if query by `tag_type` is selected. Value of the tag |
|
163
|
+
| **report_duration** | false | null | Amount of milliseconds to fetch from `earliest_updated_at`. If `incremental = true` this value will automatically calculated for the first run by `latest_updated_at` - `earliest_updated_at` |
|
164
|
+
| **incremental** | false | true | If incremental is set to true, next run will have `earliest_updated_at` set to the previous `latest_updated_at` + `report_duration`. Incremental import only support by query `date_range` |
|
165
|
+
|
166
|
+
Schema type: Static schema
|
167
|
+
|
168
|
+
Incremental support: yes (Query by `date_range` only)
|
169
|
+
|
170
|
+
Range ingestion: yes
|
171
|
+
|
145
172
|
## Example
|
146
173
|
|
147
174
|
For lead, you have `partial-config.yml` like below:
|
@@ -163,3 +190,21 @@ You can run `embulk guess partial-config.yml -o lead-config.yml` and got `lead-c
|
|
163
190
|
|
164
191
|
Next, you can run `embulk preview lead-config.yml` for preview and `embulk run lead-config.yml` for run.
|
165
192
|
|
193
|
+
Example of Assets Programs config
|
194
|
+
```yaml
|
195
|
+
in:
|
196
|
+
account_id: ACCOUNT_ID
|
197
|
+
client_id: CLIENT_ID
|
198
|
+
client_secret: CLIENT_SECRET
|
199
|
+
target: program
|
200
|
+
type: marketo
|
201
|
+
query_by: date_range
|
202
|
+
filter_type: folderId
|
203
|
+
filter_values:
|
204
|
+
- 2598
|
205
|
+
- 1001
|
206
|
+
earliest_updated_at: 2018-08-20T00:00:00.000Z
|
207
|
+
latest_updated_at: 2018-08-31T00:00:00.000Z
|
208
|
+
incremental: true
|
209
|
+
```
|
210
|
+
|
data/build.gradle
CHANGED
@@ -612,6 +612,15 @@ public class CsvTokenizer
|
|
612
612
|
return new String(new char[] { character });
|
613
613
|
}
|
614
614
|
|
615
|
+
@Override
|
616
|
+
public int hashCode()
|
617
|
+
{
|
618
|
+
final int prime = 31;
|
619
|
+
int result = 1;
|
620
|
+
result = prime * result + character;
|
621
|
+
return result;
|
622
|
+
}
|
623
|
+
|
615
624
|
@Override
|
616
625
|
public boolean equals(Object obj)
|
617
626
|
{
|
@@ -673,5 +682,14 @@ public class CsvTokenizer
|
|
673
682
|
EscapeCharacter o = (EscapeCharacter) obj;
|
674
683
|
return character == o.character;
|
675
684
|
}
|
685
|
+
|
686
|
+
@Override
|
687
|
+
public int hashCode()
|
688
|
+
{
|
689
|
+
final int prime = 31;
|
690
|
+
int result = 1;
|
691
|
+
result = prime * result + character;
|
692
|
+
return result;
|
693
|
+
}
|
676
694
|
}
|
677
695
|
}
|
@@ -3,6 +3,7 @@ package org.embulk.input.marketo;
|
|
3
3
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
4
4
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
5
5
|
import com.google.common.base.Optional;
|
6
|
+
|
6
7
|
import org.embulk.base.restclient.DispatchingRestClientInputPluginDelegate;
|
7
8
|
import org.embulk.base.restclient.RestClientInputPluginDelegate;
|
8
9
|
import org.embulk.config.Config;
|
@@ -13,6 +14,7 @@ import org.embulk.input.marketo.delegate.CampaignInputPlugin;
|
|
13
14
|
import org.embulk.input.marketo.delegate.LeadBulkExtractInputPlugin;
|
14
15
|
import org.embulk.input.marketo.delegate.LeadWithListInputPlugin;
|
15
16
|
import org.embulk.input.marketo.delegate.LeadWithProgramInputPlugin;
|
17
|
+
import org.embulk.input.marketo.delegate.ProgramInputPlugin;
|
16
18
|
import org.embulk.input.marketo.rest.MarketoRestClient;
|
17
19
|
|
18
20
|
import java.util.Date;
|
@@ -25,7 +27,9 @@ public class MarketoInputPluginDelegate
|
|
25
27
|
LeadBulkExtractInputPlugin.PluginTask,
|
26
28
|
LeadWithProgramInputPlugin.PluginTask,
|
27
29
|
ActivityBulkExtractInputPlugin.PluginTask,
|
28
|
-
CampaignInputPlugin.PluginTask,
|
30
|
+
CampaignInputPlugin.PluginTask,
|
31
|
+
ProgramInputPlugin.PluginTask,
|
32
|
+
MarketoRestClient.PluginTask
|
29
33
|
{
|
30
34
|
@Config("target")
|
31
35
|
Target getTarget();
|
@@ -68,7 +72,8 @@ public class MarketoInputPluginDelegate
|
|
68
72
|
ACTIVITY(new ActivityBulkExtractInputPlugin()),
|
69
73
|
CAMPAIGN(new CampaignInputPlugin()),
|
70
74
|
ALL_LEAD_WITH_LIST_ID(new LeadWithListInputPlugin()),
|
71
|
-
ALL_LEAD_WITH_PROGRAM_ID(new LeadWithProgramInputPlugin())
|
75
|
+
ALL_LEAD_WITH_PROGRAM_ID(new LeadWithProgramInputPlugin()),
|
76
|
+
PROGRAM(new ProgramInputPlugin());
|
72
77
|
|
73
78
|
private RestClientInputPluginDelegate restClientInputPluginDelegate;
|
74
79
|
|
@@ -23,4 +23,10 @@ public interface MarketoService
|
|
23
23
|
Iterable<ObjectNode> getAllProgramLead(List<String> extractFields);
|
24
24
|
|
25
25
|
Iterable<ObjectNode> getCampaign();
|
26
|
+
|
27
|
+
Iterable<ObjectNode> getPrograms();
|
28
|
+
|
29
|
+
Iterable<ObjectNode> getProgramsByTag(String tagType, String tagValue);
|
30
|
+
|
31
|
+
Iterable<ObjectNode> getProgramsByDateRange(Date earliestUpdatedAt, Date latestUpdatedAt, String filterType, List<String> filterValues);
|
26
32
|
}
|
@@ -206,4 +206,22 @@ public class MarketoServiceImpl implements MarketoService
|
|
206
206
|
return byteWritten;
|
207
207
|
}
|
208
208
|
}
|
209
|
+
|
210
|
+
@Override
|
211
|
+
public Iterable<ObjectNode> getPrograms()
|
212
|
+
{
|
213
|
+
return marketoRestClient.getPrograms();
|
214
|
+
}
|
215
|
+
|
216
|
+
@Override
|
217
|
+
public Iterable<ObjectNode> getProgramsByTag(String tagType, String tagValue)
|
218
|
+
{
|
219
|
+
return marketoRestClient.getProgramsByTag(tagType, tagValue);
|
220
|
+
}
|
221
|
+
|
222
|
+
@Override
|
223
|
+
public Iterable<ObjectNode> getProgramsByDateRange(Date earliestUpdatedAt, Date latestUpdatedAt, String filterType, List<String> filterValues)
|
224
|
+
{
|
225
|
+
return marketoRestClient.getProgramsByDateRange(earliestUpdatedAt, latestUpdatedAt, filterType, filterValues);
|
226
|
+
}
|
209
227
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
package org.embulk.input.marketo.delegate;
|
2
2
|
|
3
3
|
import com.google.common.collect.FluentIterable;
|
4
|
+
|
4
5
|
import org.embulk.base.restclient.ServiceResponseMapper;
|
5
6
|
import org.embulk.base.restclient.record.ServiceRecord;
|
6
7
|
import org.embulk.base.restclient.record.ValueLocator;
|
@@ -10,7 +11,6 @@ import org.embulk.input.marketo.MarketoUtils;
|
|
10
11
|
import org.embulk.input.marketo.model.MarketoField;
|
11
12
|
import org.embulk.input.marketo.rest.MarketoRestClient;
|
12
13
|
|
13
|
-
import java.util.ArrayList;
|
14
14
|
import java.util.Iterator;
|
15
15
|
import java.util.List;
|
16
16
|
|
@@ -48,7 +48,6 @@ public class LeadWithListInputPlugin extends MarketoBaseInputPluginDelegate<Lead
|
|
48
48
|
|
49
49
|
private static class LeadWithListServiceResponseMapper extends LeadServiceResponseMapperBuilder<PluginTask>
|
50
50
|
{
|
51
|
-
|
52
51
|
public LeadWithListServiceResponseMapper(LeadWithListInputPlugin.PluginTask task, MarketoService marketoService)
|
53
52
|
{
|
54
53
|
super(task, marketoService);
|
@@ -79,10 +79,6 @@ public abstract class MarketoBaseBulkExtractInputPlugin<T extends MarketoBaseBul
|
|
79
79
|
@ConfigDefault("3600")
|
80
80
|
Integer getBulkJobTimeoutSecond();
|
81
81
|
|
82
|
-
@Config("incremental")
|
83
|
-
@ConfigDefault("true")
|
84
|
-
Boolean getIncremental();
|
85
|
-
|
86
82
|
@Config("to_date")
|
87
83
|
@ConfigDefault("null")
|
88
84
|
Optional<Date> getToDate();
|
@@ -1,22 +1,23 @@
|
|
1
1
|
package org.embulk.input.marketo.delegate;
|
2
2
|
|
3
3
|
import com.google.common.annotations.VisibleForTesting;
|
4
|
-
|
4
|
+
|
5
5
|
import org.embulk.base.restclient.DefaultServiceDataSplitter;
|
6
6
|
import org.embulk.base.restclient.RestClientInputPluginDelegate;
|
7
7
|
import org.embulk.base.restclient.RestClientInputTaskBase;
|
8
8
|
import org.embulk.base.restclient.ServiceDataSplitter;
|
9
9
|
import org.embulk.base.restclient.record.RecordImporter;
|
10
10
|
import org.embulk.base.restclient.record.ServiceRecord;
|
11
|
-
import org.embulk.config
|
11
|
+
import org.embulk.config.Config;
|
12
|
+
import org.embulk.config.ConfigDefault;
|
13
|
+
import org.embulk.config.ConfigDiff;
|
14
|
+
import org.embulk.config.TaskReport;
|
12
15
|
import org.embulk.input.marketo.MarketoService;
|
13
16
|
import org.embulk.input.marketo.MarketoServiceImpl;
|
14
17
|
import org.embulk.input.marketo.rest.MarketoRestClient;
|
15
18
|
import org.embulk.spi.Exec;
|
16
19
|
import org.embulk.spi.PageBuilder;
|
17
20
|
import org.embulk.spi.Schema;
|
18
|
-
import org.embulk.util.retryhelper.jetty92.DefaultJetty92ClientCreator;
|
19
|
-
import org.embulk.util.retryhelper.jetty92.Jetty92RetryHelper;
|
20
21
|
import org.joda.time.DateTime;
|
21
22
|
|
22
23
|
import java.util.Iterator;
|
@@ -35,6 +36,10 @@ public abstract class MarketoBaseInputPluginDelegate<T extends MarketoBaseInputP
|
|
35
36
|
@ConfigDefault("\"mk\"")
|
36
37
|
String getSchemaColumnPrefix();
|
37
38
|
|
39
|
+
@Config("incremental")
|
40
|
+
@ConfigDefault("true")
|
41
|
+
Boolean getIncremental();
|
42
|
+
|
38
43
|
DateTime getJobStartTime();
|
39
44
|
|
40
45
|
void setJobStartTime(DateTime dateTime);
|
@@ -0,0 +1,229 @@
|
|
1
|
+
package org.embulk.input.marketo.delegate;
|
2
|
+
|
3
|
+
import com.fasterxml.jackson.annotation.JsonCreator;
|
4
|
+
import com.fasterxml.jackson.databind.node.ObjectNode;
|
5
|
+
import com.google.common.base.Optional;
|
6
|
+
import com.google.common.collect.FluentIterable;
|
7
|
+
|
8
|
+
import org.embulk.base.restclient.ServiceResponseMapper;
|
9
|
+
import org.embulk.base.restclient.jackson.JacksonServiceResponseMapper;
|
10
|
+
import org.embulk.base.restclient.record.RecordImporter;
|
11
|
+
import org.embulk.base.restclient.record.ServiceRecord;
|
12
|
+
import org.embulk.base.restclient.record.ValueLocator;
|
13
|
+
import org.embulk.config.Config;
|
14
|
+
import org.embulk.config.ConfigDefault;
|
15
|
+
import org.embulk.config.ConfigDiff;
|
16
|
+
import org.embulk.config.ConfigException;
|
17
|
+
import org.embulk.config.TaskReport;
|
18
|
+
import org.embulk.input.marketo.MarketoService;
|
19
|
+
import org.embulk.input.marketo.MarketoUtils;
|
20
|
+
import org.embulk.spi.Exec;
|
21
|
+
import org.embulk.spi.PageBuilder;
|
22
|
+
import org.embulk.spi.Schema;
|
23
|
+
import org.embulk.spi.type.Types;
|
24
|
+
import org.joda.time.DateTime;
|
25
|
+
import org.joda.time.Duration;
|
26
|
+
import org.joda.time.format.DateTimeFormat;
|
27
|
+
import org.joda.time.format.DateTimeFormatter;
|
28
|
+
import org.slf4j.Logger;
|
29
|
+
|
30
|
+
import java.util.Date;
|
31
|
+
import java.util.Iterator;
|
32
|
+
import java.util.List;
|
33
|
+
|
34
|
+
public class ProgramInputPlugin extends MarketoBaseInputPluginDelegate<ProgramInputPlugin.PluginTask>
|
35
|
+
{
|
36
|
+
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
|
37
|
+
private final Logger logger = Exec.getLogger(getClass());
|
38
|
+
|
39
|
+
public interface PluginTask extends MarketoBaseInputPluginDelegate.PluginTask
|
40
|
+
{
|
41
|
+
@Config("query_by")
|
42
|
+
@ConfigDefault("null")
|
43
|
+
Optional<QueryBy> getQueryBy();
|
44
|
+
|
45
|
+
@Config("tag_type")
|
46
|
+
@ConfigDefault("null")
|
47
|
+
Optional<String> getTagType();
|
48
|
+
|
49
|
+
@Config("tag_value")
|
50
|
+
@ConfigDefault("null")
|
51
|
+
Optional<String> getTagVallue();
|
52
|
+
|
53
|
+
@Config("earliest_updated_at")
|
54
|
+
@ConfigDefault("null")
|
55
|
+
Optional<Date> getEarliestUpdatedAt();
|
56
|
+
|
57
|
+
@Config("latest_updated_at")
|
58
|
+
@ConfigDefault("null")
|
59
|
+
Optional<Date> getLatestUpdatedAt();
|
60
|
+
|
61
|
+
@Config("filter_type")
|
62
|
+
@ConfigDefault("null")
|
63
|
+
Optional<String> getFilterType();
|
64
|
+
|
65
|
+
@Config("filter_values")
|
66
|
+
@ConfigDefault("null")
|
67
|
+
Optional<List<String>> getFilterValues();
|
68
|
+
|
69
|
+
@Config("report_duration")
|
70
|
+
@ConfigDefault("null")
|
71
|
+
Optional<Long> getReportDuration();
|
72
|
+
|
73
|
+
void setLatestUpdatedAt(Optional<Date> latestUpdatedAt);
|
74
|
+
}
|
75
|
+
|
76
|
+
public ProgramInputPlugin()
|
77
|
+
{
|
78
|
+
}
|
79
|
+
|
80
|
+
@Override
|
81
|
+
public void validateInputTask(PluginTask task)
|
82
|
+
{
|
83
|
+
super.validateInputTask(task);
|
84
|
+
// validate if query_by is selected
|
85
|
+
if (task.getQueryBy().isPresent()) {
|
86
|
+
switch(task.getQueryBy().get()) {
|
87
|
+
case TAG_TYPE:
|
88
|
+
//make sure tag type and tag value are not empty
|
89
|
+
if (!task.getTagType().isPresent() || !task.getTagVallue().isPresent()) {
|
90
|
+
throw new ConfigException("tag_type and tag_value are required when query by Tag Type");
|
91
|
+
}
|
92
|
+
break;
|
93
|
+
case DATE_RANGE:
|
94
|
+
// make sure earliest_updated_at is not empty
|
95
|
+
if (!task.getEarliestUpdatedAt().isPresent()) {
|
96
|
+
throw new ConfigException("`earliest_updated_at` is required when query by Date Range");
|
97
|
+
}
|
98
|
+
|
99
|
+
DateTime earliest = new DateTime(task.getEarliestUpdatedAt().get());
|
100
|
+
if (task.getReportDuration().isPresent()) {
|
101
|
+
logger.info("`report_duration` is present, Prefer `report_duration` over `latest_updated_at`");
|
102
|
+
// Update the latestUpdatedAt for the config
|
103
|
+
DateTime latest = earliest.plus(task.getReportDuration().get());
|
104
|
+
task.setLatestUpdatedAt(Optional.of(latest.toDate()));
|
105
|
+
}
|
106
|
+
|
107
|
+
// latest_updated_at is required calculate time range
|
108
|
+
if (!task.getLatestUpdatedAt().isPresent()) {
|
109
|
+
throw new ConfigException("`latest_updated_at` is required when query by Date Range");
|
110
|
+
}
|
111
|
+
|
112
|
+
DateTime latest = new DateTime(task.getLatestUpdatedAt().get());
|
113
|
+
if (earliest.isAfter(DateTime.now())) {
|
114
|
+
throw new ConfigException(String.format("`earliest_updated_at` (%s) cannot precede the current date (%s)",
|
115
|
+
earliest.toString(DATE_FORMATTER),
|
116
|
+
(DateTime.now().toString(DATE_FORMATTER))));
|
117
|
+
}
|
118
|
+
|
119
|
+
if (earliest.isAfter(latest)) {
|
120
|
+
throw new ConfigException(String.format("Invalid date range. `earliest_updated_at` (%s) cannot precede the `latest_updated_at` (%s).",
|
121
|
+
earliest.toString(DATE_FORMATTER),
|
122
|
+
latest.toString(DATE_FORMATTER)));
|
123
|
+
}
|
124
|
+
// if filter type is selected, filter value must be presented
|
125
|
+
if (task.getFilterType().isPresent() && (!task.getFilterValues().isPresent() || task.getFilterValues().get().isEmpty())) {
|
126
|
+
throw new ConfigException("filter_value is required when selected filter_type");
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
@Override
|
133
|
+
public TaskReport ingestServiceData(PluginTask task, RecordImporter recordImporter, int taskIndex, PageBuilder pageBuilder)
|
134
|
+
{
|
135
|
+
// query by date range and incremental import and not preview
|
136
|
+
if (task.getQueryBy().isPresent() && task.getQueryBy().get() == QueryBy.DATE_RANGE && task.getIncremental() && !Exec.isPreview()) {
|
137
|
+
DateTime latestUpdateAt = new DateTime(task.getLatestUpdatedAt().get());
|
138
|
+
DateTime now = DateTime.now();
|
139
|
+
// Do not run incremental import if latest_updated_at precede current time
|
140
|
+
if (latestUpdateAt.isAfter(now)) {
|
141
|
+
logger.warn("`latest_updated_at` ({}) preceded current time ({}). Will try to import next run", latestUpdateAt.toString(DATE_FORMATTER), now.toString(DATE_FORMATTER));
|
142
|
+
|
143
|
+
TaskReport taskReport = Exec.newTaskReport();
|
144
|
+
taskReport.set("earliest_updated_at", new DateTime(task.getEarliestUpdatedAt().get()).toString(DATE_FORMATTER));
|
145
|
+
if (task.getReportDuration().isPresent()) {
|
146
|
+
taskReport.set("report_duration", task.getReportDuration().get());
|
147
|
+
}
|
148
|
+
return taskReport;
|
149
|
+
}
|
150
|
+
}
|
151
|
+
return super.ingestServiceData(task, recordImporter, taskIndex, pageBuilder);
|
152
|
+
}
|
153
|
+
|
154
|
+
@Override
|
155
|
+
protected Iterator<ServiceRecord> getServiceRecords(MarketoService marketoService, PluginTask task)
|
156
|
+
{
|
157
|
+
Iterable<ObjectNode> nodes = null;
|
158
|
+
if (task.getQueryBy().isPresent()) {
|
159
|
+
switch (task.getQueryBy().get()) {
|
160
|
+
case TAG_TYPE:
|
161
|
+
nodes = marketoService.getProgramsByTag(task.getTagType().get(), task.getTagVallue().get());
|
162
|
+
break;
|
163
|
+
case DATE_RANGE:
|
164
|
+
nodes = marketoService.getProgramsByDateRange(task.getEarliestUpdatedAt().get(),
|
165
|
+
task.getLatestUpdatedAt().get(),
|
166
|
+
task.getFilterType().orNull(),
|
167
|
+
task.getFilterValues().orNull());
|
168
|
+
}
|
169
|
+
}
|
170
|
+
else {
|
171
|
+
nodes = marketoService.getPrograms();
|
172
|
+
}
|
173
|
+
return FluentIterable.from(nodes).transform(MarketoUtils.TRANSFORM_OBJECT_TO_JACKSON_SERVICE_RECORD_FUNCTION).iterator();
|
174
|
+
}
|
175
|
+
|
176
|
+
@Override
|
177
|
+
public ConfigDiff buildConfigDiff(PluginTask task, Schema schema, int taskCount, List<TaskReport> taskReports)
|
178
|
+
{
|
179
|
+
ConfigDiff configDiff = super.buildConfigDiff(task, schema, taskCount, taskReports);
|
180
|
+
// set next next earliestUpdatedAt, latestUpdatedAt
|
181
|
+
if (task.getQueryBy().isPresent() && task.getQueryBy().get() == QueryBy.DATE_RANGE && task.getIncremental()) {
|
182
|
+
|
183
|
+
DateTime earliest = new DateTime(task.getEarliestUpdatedAt().get());
|
184
|
+
DateTime latest = new DateTime(task.getLatestUpdatedAt().get());
|
185
|
+
|
186
|
+
Duration d = new Duration(earliest, latest);
|
187
|
+
DateTime nextEarliestUpdatedAt = latest.plusSeconds(1);
|
188
|
+
|
189
|
+
configDiff.set("earliest_updated_at", nextEarliestUpdatedAt.toString(DATE_FORMATTER));
|
190
|
+
configDiff.set("report_duration", task.getReportDuration().or(d.getMillis()));
|
191
|
+
}
|
192
|
+
return configDiff;
|
193
|
+
}
|
194
|
+
|
195
|
+
@Override
|
196
|
+
public ServiceResponseMapper<? extends ValueLocator> buildServiceResponseMapper(PluginTask task)
|
197
|
+
{
|
198
|
+
JacksonServiceResponseMapper.Builder builder = JacksonServiceResponseMapper.builder();
|
199
|
+
builder.add("id", Types.LONG)
|
200
|
+
.add("name", Types.STRING)
|
201
|
+
.add("sfdcId", Types.STRING)
|
202
|
+
.add("sfdcName", Types.STRING)
|
203
|
+
.add("description", Types.STRING)
|
204
|
+
.add("createdAt", Types.TIMESTAMP, MarketoUtils.MARKETO_DATE_TIME_FORMAT)
|
205
|
+
.add("updatedAt", Types.TIMESTAMP, MarketoUtils.MARKETO_DATE_TIME_FORMAT)
|
206
|
+
.add("startDate", Types.TIMESTAMP, MarketoUtils.MARKETO_DATE_TIME_FORMAT)
|
207
|
+
.add("endDate", Types.TIMESTAMP, MarketoUtils.MARKETO_DATE_TIME_FORMAT)
|
208
|
+
.add("url", Types.STRING)
|
209
|
+
.add("type", Types.STRING)
|
210
|
+
.add("channel", Types.STRING)
|
211
|
+
.add("folder", Types.JSON)
|
212
|
+
.add("status", Types.STRING)
|
213
|
+
.add("costs", Types.JSON)
|
214
|
+
.add("tags", Types.JSON)
|
215
|
+
.add("workspace", Types.STRING);
|
216
|
+
return builder.build();
|
217
|
+
}
|
218
|
+
|
219
|
+
public enum QueryBy {
|
220
|
+
TAG_TYPE,
|
221
|
+
DATE_RANGE;
|
222
|
+
|
223
|
+
@JsonCreator
|
224
|
+
public static QueryBy of(String value)
|
225
|
+
{
|
226
|
+
return QueryBy.valueOf(value.toUpperCase());
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
@@ -23,7 +23,8 @@ public enum MarketoRESTEndpoint
|
|
23
23
|
GET_LEADS_BY_LIST("/rest/v1/lists/${list_id}/leads.json"),
|
24
24
|
GET_PROGRAMS("/rest/asset/v1/programs.json"),
|
25
25
|
GET_LEADS_BY_PROGRAM("/rest/v1/leads/programs/${program_id}.json"),
|
26
|
-
GET_CAMPAIGN("/rest/v1/campaigns.json")
|
26
|
+
GET_CAMPAIGN("/rest/v1/campaigns.json"),
|
27
|
+
GET_PROGRAMS_BY_TAG("/rest/asset/v1/program/byTag.json");
|
27
28
|
|
28
29
|
private String endpoint;
|
29
30
|
|
@@ -6,6 +6,7 @@ import com.google.common.collect.ArrayListMultimap;
|
|
6
6
|
import com.google.common.collect.ImmutableListMultimap;
|
7
7
|
import com.google.common.collect.ImmutableMap;
|
8
8
|
import com.google.common.collect.Multimap;
|
9
|
+
|
9
10
|
import org.eclipse.jetty.client.util.FormContentProvider;
|
10
11
|
import org.eclipse.jetty.util.Fields;
|
11
12
|
import org.embulk.config.Config;
|
@@ -341,6 +342,7 @@ public class MarketoRestClient extends MarketoBaseRestClient
|
|
341
342
|
{
|
342
343
|
return getRecordWithTokenPagination(endPoint + MarketoRESTEndpoint.GET_CAMPAIGN.getEndpoint(), null, ObjectNode.class);
|
343
344
|
}
|
345
|
+
|
344
346
|
private <T> RecordPagingIterable<T> getRecordWithOffsetPagination(final String endPoint, final Multimap<String, String> parameters, final Class<T> recordClass)
|
345
347
|
{
|
346
348
|
return new RecordPagingIterable<>(new RecordPagingIterable.PagingFunction<RecordPagingIterable.OffsetPage<T>>()
|
@@ -411,4 +413,26 @@ public class MarketoRestClient extends MarketoBaseRestClient
|
|
411
413
|
}
|
412
414
|
});
|
413
415
|
}
|
416
|
+
|
417
|
+
public Iterable<ObjectNode> getProgramsByTag(String tagType, String tagValue)
|
418
|
+
{
|
419
|
+
Multimap<String, String> multimap = ArrayListMultimap.create();
|
420
|
+
multimap.put("tagType", tagType);
|
421
|
+
multimap.put("tagValue", tagValue);
|
422
|
+
return getRecordWithOffsetPagination(endPoint + MarketoRESTEndpoint.GET_PROGRAMS_BY_TAG.getEndpoint(), multimap, ObjectNode.class);
|
423
|
+
}
|
424
|
+
|
425
|
+
public Iterable<ObjectNode> getProgramsByDateRange(Date earliestUpdatedAt, Date latestUpdatedAt, String filterType, List<String> filterValues)
|
426
|
+
{
|
427
|
+
SimpleDateFormat timeFormat = new SimpleDateFormat(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
|
428
|
+
Multimap<String, String> multimap = ArrayListMultimap.create();
|
429
|
+
multimap.put("earliestUpdatedAt", timeFormat.format(earliestUpdatedAt));
|
430
|
+
multimap.put("latestUpdatedAt", timeFormat.format(latestUpdatedAt));
|
431
|
+
// put filter params if exist.
|
432
|
+
if (filterType != null) {
|
433
|
+
multimap.put("filterType", filterType);
|
434
|
+
multimap.put("filterValues", String.join(",", filterValues));
|
435
|
+
}
|
436
|
+
return getRecordWithOffsetPagination(endPoint + MarketoRESTEndpoint.GET_PROGRAMS.getEndpoint(), multimap, ObjectNode.class);
|
437
|
+
}
|
414
438
|
}
|
data/src/test/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPluginTest.java
CHANGED
@@ -26,10 +26,7 @@ import java.text.DateFormat;
|
|
26
26
|
import java.text.SimpleDateFormat;
|
27
27
|
import java.util.Arrays;
|
28
28
|
import java.util.Date;
|
29
|
-
import java.util.List;
|
30
|
-
import java.util.Set;
|
31
29
|
|
32
|
-
import static org.junit.Assert.assertEquals;
|
33
30
|
import static org.mockito.ArgumentMatchers.any;
|
34
31
|
|
35
32
|
/**
|
data/src/test/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPluginTest.java
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
package org.embulk.input.marketo.delegate;
|
2
2
|
|
3
3
|
import com.google.common.base.Optional;
|
4
|
-
|
4
|
+
|
5
5
|
import org.embulk.EmbulkTestRuntime;
|
6
6
|
import org.embulk.config.ConfigDiff;
|
7
7
|
import org.embulk.config.ConfigException;
|
@@ -23,7 +23,6 @@ import java.text.DateFormat;
|
|
23
23
|
import java.text.SimpleDateFormat;
|
24
24
|
import java.util.Arrays;
|
25
25
|
import java.util.Date;
|
26
|
-
import java.util.Set;
|
27
26
|
|
28
27
|
import static org.junit.Assert.assertEquals;
|
29
28
|
import static org.junit.Assert.fail;
|
@@ -0,0 +1,345 @@
|
|
1
|
+
package org.embulk.input.marketo.delegate;
|
2
|
+
|
3
|
+
import com.fasterxml.jackson.core.JsonParseException;
|
4
|
+
import com.fasterxml.jackson.databind.JavaType;
|
5
|
+
import com.fasterxml.jackson.databind.JsonMappingException;
|
6
|
+
import com.fasterxml.jackson.databind.ObjectMapper;
|
7
|
+
import com.fasterxml.jackson.databind.node.ObjectNode;
|
8
|
+
import com.google.common.base.Optional;
|
9
|
+
import com.google.common.base.Predicate;
|
10
|
+
|
11
|
+
import org.embulk.EmbulkTestRuntime;
|
12
|
+
import org.embulk.base.restclient.ServiceResponseMapper;
|
13
|
+
import org.embulk.base.restclient.record.RecordImporter;
|
14
|
+
import org.embulk.base.restclient.record.ValueLocator;
|
15
|
+
import org.embulk.config.ConfigDiff;
|
16
|
+
import org.embulk.config.ConfigException;
|
17
|
+
import org.embulk.config.ConfigLoader;
|
18
|
+
import org.embulk.config.ConfigSource;
|
19
|
+
import org.embulk.config.TaskReport;
|
20
|
+
import org.embulk.input.marketo.MarketoUtils;
|
21
|
+
import org.embulk.input.marketo.delegate.ProgramInputPlugin.PluginTask;
|
22
|
+
import org.embulk.input.marketo.delegate.ProgramInputPlugin.QueryBy;
|
23
|
+
import org.embulk.input.marketo.rest.MarketoRestClient;
|
24
|
+
import org.embulk.input.marketo.rest.RecordPagingIterable;
|
25
|
+
import org.embulk.spi.PageBuilder;
|
26
|
+
import org.embulk.spi.Schema;
|
27
|
+
import org.joda.time.DateTime;
|
28
|
+
import org.joda.time.Duration;
|
29
|
+
import org.joda.time.format.DateTimeFormat;
|
30
|
+
import org.joda.time.format.DateTimeFormatter;
|
31
|
+
import org.junit.Before;
|
32
|
+
import org.junit.Rule;
|
33
|
+
import org.junit.Test;
|
34
|
+
import org.junit.rules.ExpectedException;
|
35
|
+
import org.junit.rules.RuleChain;
|
36
|
+
import org.junit.rules.TestRule;
|
37
|
+
import org.mockito.ArgumentCaptor;
|
38
|
+
import org.mockito.Mockito;
|
39
|
+
|
40
|
+
import java.io.IOException;
|
41
|
+
import java.util.Arrays;
|
42
|
+
import java.util.Date;
|
43
|
+
import java.util.List;
|
44
|
+
|
45
|
+
import static org.junit.Assert.assertArrayEquals;
|
46
|
+
import static org.junit.Assert.assertEquals;
|
47
|
+
|
48
|
+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
49
|
+
|
50
|
+
public class ProgramInputPluginTest
|
51
|
+
{
|
52
|
+
private static final DateTimeFormatter DATE_FORMATER = DateTimeFormat.forPattern(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
|
53
|
+
|
54
|
+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
55
|
+
|
56
|
+
@Rule
|
57
|
+
public EmbulkTestRuntime runtime = new EmbulkTestRuntime();
|
58
|
+
|
59
|
+
private ExpectedException thrown = ExpectedException.none();
|
60
|
+
|
61
|
+
@Rule
|
62
|
+
@SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
|
63
|
+
public TestRule chain = RuleChain.outerRule(runtime).around(thrown);
|
64
|
+
|
65
|
+
private ConfigSource baseConfig;
|
66
|
+
|
67
|
+
private ProgramInputPlugin mockPlugin;
|
68
|
+
|
69
|
+
private MarketoRestClient mockRestClient;
|
70
|
+
|
71
|
+
@Before
|
72
|
+
public void setUp() throws Exception
|
73
|
+
{
|
74
|
+
mockPlugin = Mockito.spy(new ProgramInputPlugin());
|
75
|
+
baseConfig = config();
|
76
|
+
mockRestClient = Mockito.mock(MarketoRestClient.class);
|
77
|
+
Mockito.doReturn(mockRestClient).when(mockPlugin).createMarketoRestClient(Mockito.any(ProgramInputPlugin.PluginTask.class));
|
78
|
+
}
|
79
|
+
|
80
|
+
// -----------Verify configs --------------
|
81
|
+
@Test
|
82
|
+
public void testQueryByTagTypeConfigMissingTagType()
|
83
|
+
{
|
84
|
+
thrown.expect(ConfigException.class);
|
85
|
+
thrown.expectMessage("tag_type and tag_value are required when query by Tag Type");
|
86
|
+
ConfigSource config = baseConfig.set("query_by", Optional.of(QueryBy.TAG_TYPE)).set("tag_value", Optional.of("dummy"));
|
87
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
88
|
+
}
|
89
|
+
|
90
|
+
@Test
|
91
|
+
public void testQueryByTagTypeConfigMissingTagValue()
|
92
|
+
{
|
93
|
+
thrown.expect(ConfigException.class);
|
94
|
+
thrown.expectMessage("tag_type and tag_value are required when query by Tag Type");
|
95
|
+
ConfigSource config = baseConfig.set("query_by", Optional.of(QueryBy.TAG_TYPE)).set("tag_type", Optional.of("dummy"));
|
96
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
97
|
+
}
|
98
|
+
|
99
|
+
@Test
|
100
|
+
public void testQueryByDateRangeConfigMissingEarliestUpatedAt()
|
101
|
+
{
|
102
|
+
thrown.expect(ConfigException.class);
|
103
|
+
thrown.expectMessage("`earliest_updated_at` is required when query by Date Range");
|
104
|
+
ConfigSource config = baseConfig.set("query_by", Optional.of(QueryBy.DATE_RANGE)).set("latest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
|
105
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
106
|
+
}
|
107
|
+
|
108
|
+
@Test
|
109
|
+
public void testQueryByDateRangeConfigMissingLatestUpdatedAt()
|
110
|
+
{
|
111
|
+
thrown.expect(ConfigException.class);
|
112
|
+
thrown.expectMessage("`latest_updated_at` is required when query by Date Range");
|
113
|
+
ConfigSource config = baseConfig.set("query_by", Optional.of(QueryBy.DATE_RANGE)).set("earliest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
|
114
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
115
|
+
}
|
116
|
+
|
117
|
+
@Test
|
118
|
+
public void testQueryByDateRangeConfigMissingLatestUpdatedAtNonIncremental()
|
119
|
+
{
|
120
|
+
thrown.expect(ConfigException.class);
|
121
|
+
thrown.expectMessage("`latest_updated_at` is required when query by Date Range");
|
122
|
+
ConfigSource config = baseConfig
|
123
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
124
|
+
.set("incremental", Boolean.FALSE)
|
125
|
+
.set("earliest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
|
126
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
127
|
+
}
|
128
|
+
|
129
|
+
@Test
|
130
|
+
public void testNoErrorQueryByDateRangeConfigHasReportDurationNonIncremental()
|
131
|
+
{
|
132
|
+
ConfigSource config = baseConfig
|
133
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
134
|
+
.set("report_duration", Optional.of(60L * 1000))
|
135
|
+
.set("incremental", Boolean.FALSE)
|
136
|
+
.set("earliest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
|
137
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
138
|
+
}
|
139
|
+
|
140
|
+
@Test
|
141
|
+
public void testNoErrorQueryByDateRangeConfigHasReportDuration()
|
142
|
+
{
|
143
|
+
ConfigSource config = baseConfig
|
144
|
+
.set("report_duration", Optional.of(60L * 1000))
|
145
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
146
|
+
.set("earliest_updated_at", Optional.of(DateTime.now().minusDays(10).toDate()));
|
147
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
148
|
+
}
|
149
|
+
|
150
|
+
@Test
|
151
|
+
public void testQueryByDateRangeConfigHasEarliestUpdatedAtExceededNow()
|
152
|
+
{
|
153
|
+
DateTime earliestUpdatedAt = DateTime.now().plusDays(1);
|
154
|
+
DateTime latestUpdatedAt = DateTime.now().minusDays(2);
|
155
|
+
thrown.expect(ConfigException.class);
|
156
|
+
thrown.expectMessage(String.format("`earliest_updated_at` (%s) cannot precede the current date ", earliestUpdatedAt.toString(DATE_FORMATER)));
|
157
|
+
ConfigSource config = baseConfig
|
158
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
159
|
+
.set("earliest_updated_at", Optional.of(earliestUpdatedAt))
|
160
|
+
.set("latest_updated_at", Optional.of(latestUpdatedAt));
|
161
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
162
|
+
}
|
163
|
+
|
164
|
+
@Test
|
165
|
+
public void testQueryByDateRangeConfigHasEarliestUpdatedAtExceededLatestUpdatedAt()
|
166
|
+
{
|
167
|
+
DateTime earliestUpdatedAt = DateTime.now().minusDays(10);
|
168
|
+
DateTime latestUpdatedAt = DateTime.now().minusDays(20);
|
169
|
+
thrown.expect(ConfigException.class);
|
170
|
+
thrown.expectMessage(String.format("Invalid date range. `earliest_updated_at` (%s) cannot precede the `latest_updated_at` (%s).",
|
171
|
+
earliestUpdatedAt.toString(DATE_FORMATER),
|
172
|
+
latestUpdatedAt.toString(DATE_FORMATER)));
|
173
|
+
|
174
|
+
ConfigSource config = baseConfig
|
175
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
176
|
+
.set("earliest_updated_at", Optional.of(earliestUpdatedAt))
|
177
|
+
.set("latest_updated_at", Optional.of(latestUpdatedAt));
|
178
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
179
|
+
}
|
180
|
+
|
181
|
+
@Test
|
182
|
+
public void testHasFilterTypeButMissingFilterValue()
|
183
|
+
{
|
184
|
+
DateTime earliestUpdatedAt = DateTime.now().minusDays(20);
|
185
|
+
DateTime latestUpdatedAt = DateTime.now().minusDays(10);
|
186
|
+
thrown.expect(ConfigException.class);
|
187
|
+
thrown.expectMessage("filter_value is required when selected filter_type");
|
188
|
+
|
189
|
+
ConfigSource config = baseConfig
|
190
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
191
|
+
.set("earliest_updated_at", Optional.of(earliestUpdatedAt))
|
192
|
+
.set("latest_updated_at", Optional.of(latestUpdatedAt))
|
193
|
+
.set("filter_type", Optional.of("dummy"));
|
194
|
+
mockPlugin.validateInputTask(config.loadConfig(PluginTask.class));
|
195
|
+
}
|
196
|
+
|
197
|
+
@Test
|
198
|
+
public void testSkipIncrementalRunIfLastUpdatedAtExceedsNow()
|
199
|
+
{
|
200
|
+
DateTime earliestUpdatedAt = DateTime.now().minusDays(20);
|
201
|
+
DateTime latestUpdatedAt = earliestUpdatedAt.plusDays(21);
|
202
|
+
//21 days
|
203
|
+
long reportDuration = 21 * 24 * 60 * 60 * 1000;
|
204
|
+
|
205
|
+
ConfigSource config = baseConfig
|
206
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
207
|
+
.set("earliest_updated_at", Optional.of(earliestUpdatedAt))
|
208
|
+
.set("latest_updated_at", Optional.of(latestUpdatedAt))
|
209
|
+
.set("report_duration", reportDuration)
|
210
|
+
.set("incremental", true);
|
211
|
+
ServiceResponseMapper<? extends ValueLocator> mapper = mockPlugin.buildServiceResponseMapper(config.loadConfig(PluginTask.class));
|
212
|
+
RecordImporter recordImporter = mapper.createRecordImporter();
|
213
|
+
PageBuilder mockPageBuilder = Mockito.mock(PageBuilder.class);
|
214
|
+
TaskReport taskReport = mockPlugin.ingestServiceData(config.loadConfig(PluginTask.class), recordImporter, 1, mockPageBuilder);
|
215
|
+
// page builder object should never get called.
|
216
|
+
Mockito.verify(mockPageBuilder, Mockito.never()).addRecord();
|
217
|
+
|
218
|
+
String earliestUpdatedAtStr = taskReport.get(String.class, "earliest_updated_at");
|
219
|
+
long duration = taskReport.get(Long.class, "report_duration");
|
220
|
+
assertEquals(duration, reportDuration);
|
221
|
+
assertEquals(earliestUpdatedAtStr, earliestUpdatedAt.toString(DATE_FORMATER));
|
222
|
+
}
|
223
|
+
|
224
|
+
@Test
|
225
|
+
public void testBuildConfigDiff() throws Exception
|
226
|
+
{
|
227
|
+
TaskReport taskReport1 = Mockito.mock(TaskReport.class);
|
228
|
+
DateTime earliestUpdatedAt = DateTime.now().minusDays(20);
|
229
|
+
DateTime latestUpdatedAt = DateTime.now().minusDays(10);
|
230
|
+
ConfigSource config = baseConfig
|
231
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
232
|
+
.set("earliest_updated_at", Optional.of(earliestUpdatedAt))
|
233
|
+
.set("latest_updated_at", Optional.of(latestUpdatedAt))
|
234
|
+
.set("incremental", true);
|
235
|
+
|
236
|
+
ConfigDiff diff = mockPlugin.buildConfigDiff(config.loadConfig(PluginTask.class), Mockito.mock(Schema.class), 1, Arrays.asList(taskReport1));
|
237
|
+
|
238
|
+
long reportDuration = diff.get(Long.class, "report_duration");
|
239
|
+
String nextErliestUpdatedAt = diff.get(String.class, "earliest_updated_at");
|
240
|
+
|
241
|
+
assertEquals(reportDuration, new Duration(earliestUpdatedAt, latestUpdatedAt).getMillis());
|
242
|
+
assertEquals(nextErliestUpdatedAt, latestUpdatedAt.plusSeconds(1).toString(DATE_FORMATER));
|
243
|
+
}
|
244
|
+
|
245
|
+
@SuppressWarnings("unchecked")
|
246
|
+
private void testRun(ConfigSource config, Predicate<MarketoRestClient> expectedCall) throws JsonParseException, JsonMappingException, IOException
|
247
|
+
{
|
248
|
+
// Mock response data
|
249
|
+
RecordPagingIterable<ObjectNode> mockRecordPagingIterable = Mockito.mock(RecordPagingIterable.class);
|
250
|
+
JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametrizedType(List.class, List.class, ObjectNode.class);
|
251
|
+
List<ObjectNode> objectNodeList = OBJECT_MAPPER.readValue(this.getClass().getResourceAsStream("/fixtures/all_program_full.json"), javaType);
|
252
|
+
Mockito.when(mockRecordPagingIterable.iterator()).thenReturn(objectNodeList.iterator());
|
253
|
+
Mockito.when(mockRestClient.getProgramsByTag(Mockito.anyString(), Mockito.anyString())).thenReturn(mockRecordPagingIterable);
|
254
|
+
Mockito.when(mockRestClient.getPrograms()).thenReturn(mockRecordPagingIterable);
|
255
|
+
Mockito.when(mockRestClient.getProgramsByDateRange(
|
256
|
+
Mockito.any(Date.class),
|
257
|
+
Mockito.any(Date.class),
|
258
|
+
Mockito.nullable(String.class),
|
259
|
+
Mockito.nullable(List.class))).thenReturn(mockRecordPagingIterable);
|
260
|
+
|
261
|
+
ServiceResponseMapper<? extends ValueLocator> mapper = mockPlugin.buildServiceResponseMapper(config.loadConfig(PluginTask.class));
|
262
|
+
RecordImporter recordImporter = mapper.createRecordImporter();
|
263
|
+
PageBuilder mockPageBuilder = Mockito.mock(PageBuilder.class);
|
264
|
+
mockPlugin.ingestServiceData(config.loadConfig(PluginTask.class), recordImporter, 1, mockPageBuilder);
|
265
|
+
|
266
|
+
// The method getProgramByTag is called 1 time
|
267
|
+
// Mockito.verify(mockRestClient, Mockito.times(1)).getProgramsByTag(Mockito.anyString(), Mockito.anyString());
|
268
|
+
expectedCall.apply(mockRestClient);
|
269
|
+
|
270
|
+
Schema embulkSchema = mapper.getEmbulkSchema();
|
271
|
+
// 17 columns
|
272
|
+
assertEquals(embulkSchema.size(), 17);
|
273
|
+
// verify 3 times the method setLong for column id has been called
|
274
|
+
ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class);
|
275
|
+
Mockito.verify(mockPageBuilder, Mockito.times(3)).setLong(Mockito.eq(embulkSchema.lookupColumn("id")), longArgumentCaptor.capture());
|
276
|
+
List<Long> allValues = longArgumentCaptor.getAllValues();
|
277
|
+
assertArrayEquals(new Long[]{1004L, 1001L, 1003L}, allValues.toArray());
|
278
|
+
}
|
279
|
+
|
280
|
+
@Test
|
281
|
+
public void testRunQueryByTagType() throws JsonParseException, JsonMappingException, IOException
|
282
|
+
{
|
283
|
+
ConfigSource config = baseConfig
|
284
|
+
.set("query_by", Optional.of(QueryBy.TAG_TYPE))
|
285
|
+
.set("tag_type", Optional.of("dummy"))
|
286
|
+
.set("tag_value", Optional.of("dummy"));
|
287
|
+
Predicate<MarketoRestClient> expectedCall = new Predicate<MarketoRestClient>()
|
288
|
+
{
|
289
|
+
@Override
|
290
|
+
public boolean apply(MarketoRestClient mockRest)
|
291
|
+
{
|
292
|
+
Mockito.verify(mockRest, Mockito.times(1)).getProgramsByTag(Mockito.anyString(), Mockito.anyString());
|
293
|
+
return true;
|
294
|
+
}
|
295
|
+
};
|
296
|
+
testRun(config, expectedCall);
|
297
|
+
}
|
298
|
+
|
299
|
+
@Test
|
300
|
+
public void testRunWithoutQueryBy() throws JsonParseException, JsonMappingException, IOException
|
301
|
+
{
|
302
|
+
Predicate<MarketoRestClient> expectedCall = new Predicate<MarketoRestClient>()
|
303
|
+
{
|
304
|
+
@Override
|
305
|
+
public boolean apply(MarketoRestClient input)
|
306
|
+
{
|
307
|
+
Mockito.verify(input, Mockito.times(1)).getPrograms();
|
308
|
+
return true;
|
309
|
+
}
|
310
|
+
};
|
311
|
+
testRun(baseConfig, expectedCall);
|
312
|
+
}
|
313
|
+
|
314
|
+
@Test
|
315
|
+
public void testRunQueryByDateRange() throws JsonParseException, JsonMappingException, IOException
|
316
|
+
{
|
317
|
+
DateTime earliestUpdatedAt = DateTime.now().minusDays(20);
|
318
|
+
DateTime latestUpdatedAt = DateTime.now().minusDays(10);
|
319
|
+
ConfigSource config = baseConfig
|
320
|
+
.set("query_by", Optional.of(QueryBy.DATE_RANGE))
|
321
|
+
.set("earliest_updated_at", Optional.of(earliestUpdatedAt))
|
322
|
+
.set("latest_updated_at", Optional.of(latestUpdatedAt));
|
323
|
+
Predicate<MarketoRestClient> expectedCall = new Predicate<MarketoRestClient>()
|
324
|
+
{
|
325
|
+
@SuppressWarnings("unchecked")
|
326
|
+
@Override
|
327
|
+
public boolean apply(MarketoRestClient input)
|
328
|
+
{
|
329
|
+
Mockito.verify(input, Mockito.times(1)).getProgramsByDateRange(
|
330
|
+
Mockito.any(Date.class),
|
331
|
+
Mockito.any(Date.class),
|
332
|
+
Mockito.nullable(String.class),
|
333
|
+
Mockito.nullable(List.class));
|
334
|
+
return true;
|
335
|
+
}
|
336
|
+
};
|
337
|
+
testRun(config, expectedCall);
|
338
|
+
}
|
339
|
+
|
340
|
+
public ConfigSource config() throws IOException
|
341
|
+
{
|
342
|
+
ConfigLoader configLoader = runtime.getInjector().getInstance(ConfigLoader.class);
|
343
|
+
return configLoader.fromYaml(this.getClass().getResourceAsStream("/config/rest_config.yaml"));
|
344
|
+
}
|
345
|
+
}
|
@@ -20,11 +20,15 @@ import org.embulk.input.marketo.model.MarketoResponse;
|
|
20
20
|
import org.embulk.spi.DataException;
|
21
21
|
import org.embulk.util.retryhelper.jetty92.Jetty92ResponseReader;
|
22
22
|
import org.embulk.util.retryhelper.jetty92.Jetty92RetryHelper;
|
23
|
+
import org.joda.time.DateTime;
|
24
|
+
import org.joda.time.format.DateTimeFormat;
|
25
|
+
import org.joda.time.format.DateTimeFormatter;
|
23
26
|
import org.junit.Assert;
|
24
27
|
import org.junit.Before;
|
25
28
|
import org.junit.Rule;
|
26
29
|
import org.junit.Test;
|
27
30
|
import org.mockito.ArgumentCaptor;
|
31
|
+
import org.mockito.ArgumentMatchers;
|
28
32
|
import org.mockito.Mockito;
|
29
33
|
|
30
34
|
import java.io.IOException;
|
@@ -443,4 +447,101 @@ public class MarketoRestClientTest
|
|
443
447
|
Assert.assertEquals("GET", params1.get("_method").iterator().next());
|
444
448
|
Assert.assertEquals("nextPageToken=z4MgsIiC5C%3D%3D%3D%3D%3D%3D&batchSize=300", content);
|
445
449
|
}
|
450
|
+
|
451
|
+
@Test
|
452
|
+
public void testGetProgramsByTagType() throws Exception
|
453
|
+
{
|
454
|
+
ArrayNode listPages = (ArrayNode) OBJECT_MAPPER.readTree(new String(ByteStreams.toByteArray(this.getClass().getResourceAsStream("/fixtures/program_response.json")))).get("responses");
|
455
|
+
MarketoResponse<ObjectNode> page1 = OBJECT_MAPPER.readValue(listPages.get(0).toString(), RESPONSE_TYPE);
|
456
|
+
MarketoResponse<ObjectNode> page2 = OBJECT_MAPPER.readValue(listPages.get(1).toString(), RESPONSE_TYPE);
|
457
|
+
|
458
|
+
final String tagType = "dummy_tag";
|
459
|
+
final String tagValue = "dummy_value";
|
460
|
+
|
461
|
+
Mockito.doReturn(page1).doReturn(page2).when(marketoRestClient).doGet(
|
462
|
+
Mockito.eq(END_POINT + MarketoRESTEndpoint.GET_PROGRAMS_BY_TAG.getEndpoint()),
|
463
|
+
(Map) ArgumentMatchers.isNull(),
|
464
|
+
Mockito.any(Multimap.class),
|
465
|
+
Mockito.any(MarketoResponseJetty92EntityReader.class));
|
466
|
+
Iterable<ObjectNode> lists = marketoRestClient.getProgramsByTag(tagType, tagValue);
|
467
|
+
Iterator<ObjectNode> iterator = lists.iterator();
|
468
|
+
ObjectNode program1 = iterator.next();
|
469
|
+
ObjectNode program2 = iterator.next();
|
470
|
+
ObjectNode program3 = iterator.next();
|
471
|
+
Assert.assertFalse(iterator.hasNext());
|
472
|
+
Assert.assertEquals("MB_Sep_25_test_program", program1.get("name").asText());
|
473
|
+
Assert.assertEquals("TD Output Test Program", program2.get("name").asText());
|
474
|
+
Assert.assertEquals("Bill_progream", program3.get("name").asText());
|
475
|
+
|
476
|
+
ArgumentCaptor<ImmutableListMultimap> immutableListMultimapArgumentCaptor = ArgumentCaptor.forClass(ImmutableListMultimap.class);
|
477
|
+
Mockito.verify(marketoRestClient, Mockito.times(2)).doGet(
|
478
|
+
Mockito.eq(END_POINT + MarketoRESTEndpoint.GET_PROGRAMS_BY_TAG.getEndpoint()),
|
479
|
+
(Map) ArgumentMatchers.isNull(),
|
480
|
+
immutableListMultimapArgumentCaptor.capture(),
|
481
|
+
Mockito.any(MarketoResponseJetty92EntityReader.class));
|
482
|
+
List<ImmutableListMultimap> params = immutableListMultimapArgumentCaptor.getAllValues();
|
483
|
+
|
484
|
+
ImmutableListMultimap params1 = params.get(0);
|
485
|
+
Assert.assertEquals("0", params1.get("offset").get(0));
|
486
|
+
Assert.assertEquals("2", params1.get("maxReturn").get(0));
|
487
|
+
Assert.assertEquals("dummy_tag", params1.get("tagType").get(0));
|
488
|
+
Assert.assertEquals("dummy_value", params1.get("tagValue").get(0));
|
489
|
+
|
490
|
+
ImmutableListMultimap params2 = params.get(1);
|
491
|
+
Assert.assertEquals("2", params2.get("offset").get(0));
|
492
|
+
Assert.assertEquals("dummy_tag", params2.get("tagType").get(0));
|
493
|
+
Assert.assertEquals("dummy_value", params2.get("tagValue").get(0));
|
494
|
+
}
|
495
|
+
|
496
|
+
@Test
|
497
|
+
public void TestProgramsByDateRange() throws Exception
|
498
|
+
{
|
499
|
+
ArrayNode listPages = (ArrayNode) OBJECT_MAPPER.readTree(new String(ByteStreams.toByteArray(this.getClass().getResourceAsStream("/fixtures/program_response.json")))).get("responses");
|
500
|
+
MarketoResponse<ObjectNode> page1 = OBJECT_MAPPER.readValue(listPages.get(0).toString(), RESPONSE_TYPE);
|
501
|
+
MarketoResponse<ObjectNode> page2 = OBJECT_MAPPER.readValue(listPages.get(1).toString(), RESPONSE_TYPE);
|
502
|
+
|
503
|
+
Mockito.doReturn(page1).doReturn(page2).when(marketoRestClient).doGet(
|
504
|
+
Mockito.eq(END_POINT + MarketoRESTEndpoint.GET_PROGRAMS.getEndpoint()),
|
505
|
+
(Map) ArgumentMatchers.isNull(),
|
506
|
+
Mockito.any(Multimap.class),
|
507
|
+
Mockito.any(MarketoResponseJetty92EntityReader.class));
|
508
|
+
DateTime earliestUpdatedAt = DateTime.now().minusDays(10);
|
509
|
+
DateTime latestUpdatedAt = earliestUpdatedAt.plusDays(5);
|
510
|
+
String filterType = "filter1";
|
511
|
+
List<String> filterValues = Arrays.asList("value1", "value2");
|
512
|
+
|
513
|
+
Iterable<ObjectNode> lists = marketoRestClient.getProgramsByDateRange(earliestUpdatedAt.toDate(), latestUpdatedAt.toDate(), filterType, filterValues);
|
514
|
+
Iterator<ObjectNode> iterator = lists.iterator();
|
515
|
+
ObjectNode program1 = iterator.next();
|
516
|
+
ObjectNode program2 = iterator.next();
|
517
|
+
ObjectNode program3 = iterator.next();
|
518
|
+
Assert.assertFalse(iterator.hasNext());
|
519
|
+
Assert.assertEquals("MB_Sep_25_test_program", program1.get("name").asText());
|
520
|
+
Assert.assertEquals("TD Output Test Program", program2.get("name").asText());
|
521
|
+
Assert.assertEquals("Bill_progream", program3.get("name").asText());
|
522
|
+
|
523
|
+
ArgumentCaptor<ImmutableListMultimap> immutableListMultimapArgumentCaptor = ArgumentCaptor.forClass(ImmutableListMultimap.class);
|
524
|
+
Mockito.verify(marketoRestClient, Mockito.times(2)).doGet(
|
525
|
+
Mockito.eq(END_POINT + MarketoRESTEndpoint.GET_PROGRAMS.getEndpoint()),
|
526
|
+
(Map) ArgumentMatchers.isNull(),
|
527
|
+
immutableListMultimapArgumentCaptor.capture(),
|
528
|
+
Mockito.any(MarketoResponseJetty92EntityReader.class));
|
529
|
+
List<ImmutableListMultimap> params = immutableListMultimapArgumentCaptor.getAllValues();
|
530
|
+
DateTimeFormatter fmt = DateTimeFormat.forPattern(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
|
531
|
+
|
532
|
+
ImmutableListMultimap params1 = params.get(0);
|
533
|
+
Assert.assertEquals("0", params1.get("offset").get(0));
|
534
|
+
Assert.assertEquals("2", params1.get("maxReturn").get(0));
|
535
|
+
Assert.assertEquals(earliestUpdatedAt.toString(fmt), params1.get("earliestUpdatedAt").get(0));
|
536
|
+
Assert.assertEquals(latestUpdatedAt.toString(fmt), params1.get("latestUpdatedAt").get(0));
|
537
|
+
Assert.assertEquals("filter1", params1.get("filterType").get(0));
|
538
|
+
Assert.assertEquals(String.join(",", filterValues), params1.get("filterValues").get(0));
|
539
|
+
|
540
|
+
ImmutableListMultimap params2 = params.get(1);
|
541
|
+
Assert.assertEquals("2", params2.get("offset").get(0));
|
542
|
+
Assert.assertEquals(earliestUpdatedAt.toString(fmt), params2.get("earliestUpdatedAt").get(0));
|
543
|
+
Assert.assertEquals(latestUpdatedAt.toString(fmt), params2.get("latestUpdatedAt").get(0));
|
544
|
+
Assert.assertEquals("filter1", params2.get("filterType").get(0));
|
545
|
+
Assert.assertEquals(String.join(",", filterValues), params2.get("filterValues").get(0));
|
546
|
+
}
|
446
547
|
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-input-marketo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- uu59
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2018-
|
13
|
+
date: 2018-09-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- src/main/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPlugin.java
|
79
79
|
- src/main/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPlugin.java
|
80
80
|
- src/main/java/org/embulk/input/marketo/delegate/MarketoBaseInputPluginDelegate.java
|
81
|
+
- src/main/java/org/embulk/input/marketo/delegate/ProgramInputPlugin.java
|
81
82
|
- src/main/java/org/embulk/input/marketo/exception/MarketoAPIException.java
|
82
83
|
- src/main/java/org/embulk/input/marketo/model/BulkExtractRangeHeader.java
|
83
84
|
- src/main/java/org/embulk/input/marketo/model/MarketoAccessTokenResponse.java
|
@@ -102,6 +103,7 @@ files:
|
|
102
103
|
- src/test/java/org/embulk/input/marketo/delegate/LeadWithListInputPluginTest.java
|
103
104
|
- src/test/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPluginTest.java
|
104
105
|
- src/test/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPluginTest.java
|
106
|
+
- src/test/java/org/embulk/input/marketo/delegate/ProgramInputPluginTest.java
|
105
107
|
- src/test/java/org/embulk/input/marketo/rest/MarketoBaseRestClientTest.java
|
106
108
|
- src/test/java/org/embulk/input/marketo/rest/MarketoRestClientTest.java
|
107
109
|
- src/test/resources/config/activity_bulk_extract_config.yaml
|
@@ -124,13 +126,13 @@ files:
|
|
124
126
|
- src/test/resources/fixtures/list_reponse_full.json
|
125
127
|
- src/test/resources/fixtures/lists_response.json
|
126
128
|
- src/test/resources/fixtures/program_response.json
|
129
|
+
- classpath/jetty-io-9.2.14.v20151106.jar
|
130
|
+
- classpath/jetty-util-9.2.14.v20151106.jar
|
127
131
|
- classpath/jetty-http-9.2.14.v20151106.jar
|
128
|
-
- classpath/embulk-base-restclient-0.5.3.jar
|
129
132
|
- classpath/jetty-client-9.2.14.v20151106.jar
|
130
|
-
- classpath/
|
131
|
-
- classpath/embulk-input-marketo-0.6.
|
133
|
+
- classpath/embulk-base-restclient-0.5.3.jar
|
134
|
+
- classpath/embulk-input-marketo-0.6.11.jar
|
132
135
|
- classpath/embulk-util-retryhelper-jetty92-0.5.3.jar
|
133
|
-
- classpath/jetty-io-9.2.14.v20151106.jar
|
134
136
|
homepage: https://github.com/treasure-data/embulk-input-marketo
|
135
137
|
licenses:
|
136
138
|
- Apache2
|