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.
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