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.
Files changed (22) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +3 -0
  4. data/README.md +51 -6
  5. data/build.gradle +1 -1
  6. data/src/main/java/org/embulk/input/marketo/CsvTokenizer.java +18 -0
  7. data/src/main/java/org/embulk/input/marketo/MarketoInputPluginDelegate.java +7 -2
  8. data/src/main/java/org/embulk/input/marketo/MarketoService.java +6 -0
  9. data/src/main/java/org/embulk/input/marketo/MarketoServiceImpl.java +18 -0
  10. data/src/main/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPlugin.java +1 -0
  11. data/src/main/java/org/embulk/input/marketo/delegate/LeadWithListInputPlugin.java +1 -2
  12. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPlugin.java +0 -4
  13. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseInputPluginDelegate.java +9 -4
  14. data/src/main/java/org/embulk/input/marketo/delegate/ProgramInputPlugin.java +229 -0
  15. data/src/main/java/org/embulk/input/marketo/rest/MarketoBaseRestClient.java +1 -0
  16. data/src/main/java/org/embulk/input/marketo/rest/MarketoRESTEndpoint.java +2 -1
  17. data/src/main/java/org/embulk/input/marketo/rest/MarketoRestClient.java +24 -0
  18. data/src/test/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPluginTest.java +0 -3
  19. data/src/test/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPluginTest.java +1 -2
  20. data/src/test/java/org/embulk/input/marketo/delegate/ProgramInputPluginTest.java +345 -0
  21. data/src/test/java/org/embulk/input/marketo/rest/MarketoRestClientTest.java +101 -0
  22. metadata +8 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 368e0b80c346570f82c7c16c2e07135f0b73b543
4
- data.tar.gz: e1edfbc1bf62ed3624cb7fc25cf317cc8664dd0a
3
+ metadata.gz: 1b375807158e07d67abc81e6319ef6ec9a087f9b
4
+ data.tar.gz: 596f26cb4804ce01819b27499ead25b35c567100
5
5
  SHA512:
6
- metadata.gz: dd5b2dea8460522bf57c8a92a38a47f7030d3675a28a4bffb4cb48153af6d8679be4c28783ca7757265a3923cdb9197876dad87bac228c9a652e476531d539d2
7
- data.tar.gz: 17475bdc7f4f3f89e9bb9e568ab85f74d0d38f4ec4a00afa5dbba58fb4999113061de2ebcbb9c485ebea7cedfe77d649851cd418a7da6fbee92f6af9dc9009c5
6
+ metadata.gz: d5743056090cbad09a2a75a6f460f2d843e721e254b2e898e7e809faad8f0309732244282c91f5dd6bbed9cafb95d0be519bba6305e8bee2726e49b0905867f8
7
+ data.tar.gz: ab9fe2a479f4ccc842efb62e80d14dc4bac8941b64dc6c7d589c35a412a6f782fc902edd75a014e48b410889dca3e63c99a184dd533675c0707c44f520da0696
data/.gitignore CHANGED
@@ -12,3 +12,4 @@ build/
12
12
  .classpath
13
13
  .project
14
14
 
15
+ /bin/
@@ -1,3 +1,6 @@
1
+ ## 0.6.11 - 2018-09-10
2
+ * [enhance] Implement Assets Programs [#89](https://github.com/treasure-data/embulk-input-marketo/pull/89)
3
+
1
4
  ## 0.6.10 - 2018-05-28
2
5
  * [fixed] Add included column option [#87](https://github.com/treasure-data/embulk-input-marketo/pull/87)
3
6
 
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 | Ammount of days to fetch since from_date |
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 | empty | List of lead fields to included in export request sent to Marketo, can be used to reduce the size of BulkExtract file |
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 actvity log. Configuration include all bulk extract configuraiont
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 | empty | List of lead fields to included in export request sent to Marketo, can be used to reduce request, response size |
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
- ### Laed by program
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 | empty | List of lead fields to included in export request sent to Marketo, can be used to reduce request, response size |
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
+
@@ -16,7 +16,7 @@ repositories {
16
16
  configurations {
17
17
  provided
18
18
  }
19
- version = "0.6.10"
19
+ version = "0.6.11"
20
20
  sourceCompatibility = 1.7
21
21
  targetCompatibility = 1.7
22
22
 
@@ -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, MarketoRestClient.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.base.Optional;
4
+
4
5
  import org.embulk.base.restclient.ServiceResponseMapper;
5
6
  import org.embulk.base.restclient.record.ValueLocator;
6
7
  import org.embulk.config.Config;
@@ -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
- import com.google.common.base.Optional;
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
+ }
@@ -184,6 +184,7 @@ public class MarketoBaseRestClient implements AutoCloseable
184
184
  }
185
185
  }
186
186
  }
187
+ LOGGER.info("CALLING {} -> {} - params: {}", method, target, params);
187
188
  if (contentProvider != null) {
188
189
  request.content(contentProvider);
189
190
  }
@@ -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
  }
@@ -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
  /**
@@ -1,7 +1,7 @@
1
1
  package org.embulk.input.marketo.delegate;
2
2
 
3
3
  import com.google.common.base.Optional;
4
- import com.google.common.collect.Sets;
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.10
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-05-28 00:00:00.000000000 Z
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/jetty-util-9.2.14.v20151106.jar
131
- - classpath/embulk-input-marketo-0.6.10.jar
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