embulk-input-marketo 0.6.10 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|