multiwoven-integrations 0.1.28 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0972dc10bbb0d6d81f9903d0edb045f22f5d93bfa7d622f1c5758fd3b4fd2692'
4
- data.tar.gz: 50ac2206ed0ba57413cc9ac334bb8d00acd8a263e7a3b78efe7a437983636331
3
+ metadata.gz: de97ff2b2478ae4247a3027282f586bcc57c1d7db9de8aae698e8b83f0553f6a
4
+ data.tar.gz: 861cdab018b60dfb1e8dd36b2f8a433f382eb358aa8a3645845185ac1f578019
5
5
  SHA512:
6
- metadata.gz: 8e9c3f011128710a4502dd51d5dad7934521fa80c923fd7c202721bcc63b206fa0e555b29f5ae5ad2dc4beb0619162b7f160e32a5e6ef78248b58ddb662af0ec
7
- data.tar.gz: 4343882c80e4cfd50e008d6fbcd0636e0e92e87d2e08173f2dd73c3152448ac8c874f27e787f645be5c9e1ee46504c4a3c6b28ce36c63d660f02026471b2ba4b
6
+ metadata.gz: 53487427cd135f107cf333072798a53751e0d2fcc4bb92c5f42f8dafc88dfcd5c36f19c70f70daf4251f6b5c8770ef61992094258faeab0426eb73d22f8bc6ca
7
+ data.tar.gz: 462bf61c0646edbd85ead0ecfc203cca0aa31a14a94bcdacb0a117b46b0c60af79d4ad5d5cc44be48cbe1697d01c23a934ce8ef241a78c6f69df870e4c28cdc7
@@ -3,7 +3,9 @@
3
3
  module Multiwoven
4
4
  module Integrations::Core
5
5
  class DestinationConnector < BaseConnector
6
- # records is transformed json payload send it to the destination
6
+ prepend RateLimiter
7
+
8
+ # Records are transformed json payload send it to the destination
7
9
  # SyncConfig is the Protocol::SyncConfig object
8
10
  def write(_sync_config, _records, _action = "insert")
9
11
  raise "Not implemented"
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven
4
+ module Integrations::Core
5
+ module RateLimiter
6
+ def write(sync_config, records, action = "insert")
7
+ stream = sync_config.stream
8
+
9
+ @queue ||= Limiter::RateQueue.new(stream.request_rate_limit, interval: stream.rate_limit_unit_seconds) do
10
+ Integrations::Service.logger.info("Hit the limit for stream: #{stream.name}, waiting")
11
+ end
12
+
13
+ @queue.shift
14
+
15
+ super(sync_config, records, action)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -72,6 +72,31 @@ module Multiwoven
72
72
  def success?(response)
73
73
  response && %w[200 201].include?(response.code.to_s)
74
74
  end
75
+
76
+ def build_catalog(catalog_json)
77
+ streams = catalog_json["streams"].map { |stream_json| build_stream(stream_json) }
78
+ Multiwoven::Integrations::Protocol::Catalog.new(
79
+ streams: streams,
80
+ request_rate_limit: catalog_json["request_rate_limit"] || 60,
81
+ request_rate_limit_unit: catalog_json["request_rate_limit_unit"] || "minute",
82
+ request_rate_concurrency: catalog_json["request_rate_concurrency"] || 10
83
+ )
84
+ end
85
+
86
+ def build_stream(stream_json)
87
+ Multiwoven::Integrations::Protocol::Stream.new(
88
+ name: stream_json["name"],
89
+ url: stream_json["url"],
90
+ action: stream_json["action"],
91
+ request_method: stream_json["method"],
92
+ batch_support: stream_json["batch_support"] || false,
93
+ batch_size: stream_json["batch_size"] || 1,
94
+ json_schema: stream_json["json_schema"],
95
+ request_rate_limit: stream_json["request_rate_limit"].to_i,
96
+ request_rate_limit_unit: stream_json["request_rate_limit_unit"] || "minute",
97
+ request_rate_concurrency: stream_json["request_rate_concurrency"].to_i
98
+ )
99
+ end
75
100
  end
76
101
  end
77
102
  end
@@ -25,23 +25,7 @@ module Multiwoven::Integrations::Destination
25
25
 
26
26
  def discover(_connection_config = nil)
27
27
  catalog_json = read_json(CATALOG_SPEC_PATH)
28
-
29
- streams = catalog_json["streams"].map do |stream|
30
- Multiwoven::Integrations::Protocol::Stream.new(
31
- url: stream["url"],
32
- name: stream["name"],
33
- json_schema: stream["json_schema"],
34
- request_method: stream["method"],
35
- action: stream["action"],
36
- batch_support: stream["batch_support"],
37
- batch_size: stream["batch_size"]
38
- )
39
- end
40
-
41
- catalog = Multiwoven::Integrations::Protocol::Catalog.new(
42
- streams: streams
43
- )
44
-
28
+ catalog = build_catalog(catalog_json)
45
29
  catalog.to_multiwoven_message
46
30
  rescue StandardError => e
47
31
  handle_exception(
@@ -1,4 +1,7 @@
1
1
  {
2
+ "request_rate_limit": 600,
3
+ "request_rate_limit_unit": "minute",
4
+ "request_rate_concurrency": 10,
2
5
  "streams": [
3
6
  {
4
7
  "name": "audience",
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module Multiwoven
6
+ module Integrations
7
+ module Destination
8
+ module Hubspot
9
+ include Multiwoven::Integrations::Core
10
+
11
+ class Client < DestinationConnector
12
+ def check_connection(connection_config)
13
+ connection_config = connection_config.with_indifferent_access
14
+ initialize_client(connection_config)
15
+ authenticate_client
16
+ success_status
17
+ rescue StandardError => e
18
+ handle_exception("HUBSPOT:CRM:DISCOVER:EXCEPTION", "error", e)
19
+ failure_status(e)
20
+ end
21
+
22
+ def discover(_connection_config = nil)
23
+ catalog = build_catalog(load_catalog)
24
+ catalog.to_multiwoven_message
25
+ rescue StandardError => e
26
+ handle_exception("HUBSPOT:CRM:DISCOVER:EXCEPTION", "error", e)
27
+ end
28
+
29
+ def write(sync_config, records, action = "create")
30
+ @action = sync_config.stream.action || action
31
+ initialize_client(sync_config.destination.connection_specification)
32
+ process_records(records, sync_config.stream)
33
+ rescue StandardError => e
34
+ handle_exception("HUBSPOT:CRM:WRITE:EXCEPTION", "error", e)
35
+ end
36
+
37
+ private
38
+
39
+ def initialize_client(config)
40
+ config = config.with_indifferent_access
41
+ @client = ::Hubspot::Client.new(access_token: config[:access_token])
42
+ end
43
+
44
+ def process_records(records, stream)
45
+ write_success = 0
46
+ write_failure = 0
47
+ properties = stream.json_schema.with_indifferent_access[:properties]
48
+ records.each do |record_object|
49
+ record = extract_data(record_object, properties)
50
+ send_data_to_hubspot(stream.name, record)
51
+ write_success += 1
52
+ rescue StandardError => e
53
+ handle_exception("HUBSPOT:CRM:WRITE:EXCEPTION", "error", e)
54
+ write_failure += 1
55
+ end
56
+ tracking_message(write_success, write_failure)
57
+ end
58
+
59
+ def send_data_to_hubspot(stream_name, record = {})
60
+ args = build_args(@action, stream_name, record)
61
+ hubspot_stream = @client.crm.send(stream_name)
62
+ hubspot_data = { simple_public_object_input_for_create: args }
63
+ hubspot_stream.basic_api.send(@action, hubspot_data)
64
+ end
65
+
66
+ def build_args(action, stream_name, record)
67
+ case action
68
+ when :upsert
69
+ [stream_name, record[:external_key], record]
70
+ when :destroy
71
+ [stream_name, record[:id]]
72
+ else
73
+ record
74
+ end
75
+ end
76
+
77
+ def authenticate_client
78
+ @client.crm.contacts.basic_api.get_page
79
+ end
80
+
81
+ def success_status
82
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
83
+ end
84
+
85
+ def failure_status(error)
86
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: error.message).to_multiwoven_message
87
+ end
88
+
89
+ def load_catalog
90
+ read_json(CATALOG_SPEC_PATH)
91
+ end
92
+
93
+ def tracking_message(success, failure)
94
+ Multiwoven::Integrations::Protocol::TrackingMessage.new(
95
+ success: success, failed: failure
96
+ ).to_multiwoven_message
97
+ end
98
+
99
+ def log_debug(message)
100
+ Multiwoven::Integrations::Service.logger.debug(message)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,351 @@
1
+ {
2
+ "request_rate_limit": 600,
3
+ "request_rate_limit_unit": "minute",
4
+ "request_rate_concurrency": 10,
5
+ "streams": [
6
+ {
7
+ "name": "contacts",
8
+ "action": "create",
9
+ "json_schema": {
10
+ "$schema": "http://json-schema.org/draft-07/schema#",
11
+ "title": "HubSpot CRM",
12
+ "type": "object",
13
+ "required": ["email"],
14
+ "properties": {
15
+ "properties": {
16
+ "type": "object",
17
+ "properties": {
18
+ "email": {
19
+ "type": "string",
20
+ "format": "email",
21
+ "description": "The contact's email address."
22
+ },
23
+ "firstname": {
24
+ "type": "string",
25
+ "description": "The contact's first name."
26
+ },
27
+ "lastname": {
28
+ "type": "string",
29
+ "description": "The contact's last name."
30
+ },
31
+ "phone": {
32
+ "type": "string",
33
+ "description": "The contact's phone number."
34
+ },
35
+ "company": {
36
+ "type": "string",
37
+ "description": "The contact's company name."
38
+ },
39
+ "website": {
40
+ "type": "string",
41
+ "format": "uri",
42
+ "description": "The contact's website URL."
43
+ },
44
+ "lifecyclestage": {
45
+ "type": "string",
46
+ "description": "The contact's lifecycle stage."
47
+ }
48
+ },
49
+ "additionalProperties": {
50
+ "type": ["string", "number", "boolean"],
51
+ "description": "Allows for additional custom properties with basic data types."
52
+ }
53
+ },
54
+ "associations": {
55
+ "type": "array",
56
+ "description": "An array of associated objects for the contact.",
57
+ "items": {
58
+ "type": "object",
59
+ "properties": {
60
+ "to": {
61
+ "type": "object",
62
+ "required": ["id"],
63
+ "properties": {
64
+ "id": {
65
+ "type": "integer",
66
+ "description": "The unique identifier of the associated object."
67
+ }
68
+ }
69
+ },
70
+ "types": {
71
+ "type": "array",
72
+ "items": {
73
+ "type": "object",
74
+ "required": ["associationCategory", "associationTypeId"],
75
+ "properties": {
76
+ "associationCategory": {
77
+ "type": "string",
78
+ "description": "The category of the association.",
79
+ "enum": [
80
+ "HUBSPOT_DEFINED",
81
+ "USER_DEFINED",
82
+ "INTEGRATOR_DEFINED"
83
+ ]
84
+ },
85
+ "associationTypeId": {
86
+ "type": "integer",
87
+ "description": "The unique identifier of the association type."
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ },
97
+ "supported_sync_modes": ["full_refresh", "incremental"],
98
+ "source_defined_cursor": true,
99
+ "default_cursor_field": ["updated"]
100
+ },
101
+ {
102
+ "name": "companies",
103
+ "action": "create",
104
+ "json_schema": {
105
+ "$schema": "http://json-schema.org/draft-07/schema#",
106
+ "type": "object",
107
+ "properties": {
108
+ "inputs": {
109
+ "type": "array",
110
+ "items": {
111
+ "type": "object",
112
+ "properties": {
113
+ "associations": {
114
+ "type": "array",
115
+ "items": {
116
+ "type": "object",
117
+ "properties": {
118
+ "types": {
119
+ "type": "array",
120
+ "items": {
121
+ "type": "object",
122
+ "properties": {
123
+ "associationCategory": {
124
+ "type": "string",
125
+ "enum": [
126
+ "HUBSPOT_DEFINED",
127
+ "USER_DEFINED",
128
+ "INTEGRATOR_DEFINED"
129
+ ]
130
+ },
131
+ "associationTypeId": {
132
+ "type": "integer"
133
+ }
134
+ }
135
+ }
136
+ },
137
+ "to": {
138
+ "type": "object",
139
+ "properties": {
140
+ "id": {
141
+ "type": "string"
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ },
148
+ "properties": {
149
+ "type": "object",
150
+ "properties": {
151
+ "city": {
152
+ "type": "string"
153
+ },
154
+ "name": {
155
+ "type": "string"
156
+ },
157
+ "phone": {
158
+ "type": "string"
159
+ },
160
+ "state": {
161
+ "type": "string"
162
+ },
163
+ "domain": {
164
+ "type": "string"
165
+ },
166
+ "industry": {
167
+ "type": "string"
168
+ }
169
+ },
170
+ "required": ["name", "domain"]
171
+ }
172
+ },
173
+ "required": ["properties"]
174
+ }
175
+ }
176
+ },
177
+ "required": ["inputs"]
178
+ },
179
+ "supported_sync_modes": ["full_refresh", "incremental"],
180
+ "source_defined_cursor": true,
181
+ "default_cursor_field": ["updated"]
182
+ },
183
+ {
184
+ "name": "deals",
185
+ "action": "create",
186
+ "json_schema": {
187
+ "$schema": "http://json-schema.org/draft-07/schema#",
188
+ "type": "object",
189
+ "properties": {
190
+ "properties": {
191
+ "type": "object",
192
+ "properties": {
193
+ "amount": {
194
+ "type": "string",
195
+ "pattern": "^[0-9]+(\\.[0-9]{1,2})?$"
196
+ },
197
+ "closedate": {
198
+ "type": "string",
199
+ "format": "date-time"
200
+ },
201
+ "dealname": {
202
+ "type": "string"
203
+ },
204
+ "pipeline": {
205
+ "type": "string"
206
+ },
207
+ "dealstage": {
208
+ "type": "string",
209
+ "enum": [
210
+ "appointmentscheduled",
211
+ "qualifiedtobuy",
212
+ "presentationscheduled",
213
+ "decisionmakerboughtin",
214
+ "contractsent",
215
+ "closedwon",
216
+ "closedlost"
217
+ ]
218
+ },
219
+ "hubspot_owner_id": {
220
+ "type": "string",
221
+ "pattern": "^[0-9]+$"
222
+ }
223
+ },
224
+ "required": ["dealname", "pipeline", "dealstage"]
225
+ },
226
+ "associations": {
227
+ "type": "array",
228
+ "items": {
229
+ "type": "object",
230
+ "properties": {
231
+ "to": {
232
+ "type": "object",
233
+ "properties": {
234
+ "id": {
235
+ "type": "integer"
236
+ }
237
+ },
238
+ "required": ["id"]
239
+ },
240
+ "types": {
241
+ "type": "array",
242
+ "items": {
243
+ "type": "object",
244
+ "properties": {
245
+ "associationCategory": {
246
+ "type": "string",
247
+ "enum": [
248
+ "HUBSPOT_DEFINED",
249
+ "USER_DEFINED",
250
+ "INTEGRATOR_DEFINED"
251
+ ]
252
+ },
253
+ "associationTypeId": {
254
+ "type": "integer"
255
+ }
256
+ },
257
+ "required": ["associationCategory", "associationTypeId"]
258
+ }
259
+ }
260
+ },
261
+ "required": ["to", "types"]
262
+ }
263
+ }
264
+ },
265
+ "required": ["properties"]
266
+ },
267
+ "supported_sync_modes": ["full_refresh", "incremental"],
268
+ "source_defined_cursor": true,
269
+ "default_cursor_field": ["updated"]
270
+ },
271
+ {
272
+ "name": "line_items",
273
+ "action": "create",
274
+ "json_schema": {
275
+ "$schema": "http://json-schema.org/draft-07/schema#",
276
+ "type": "object",
277
+ "properties": {
278
+ "properties": {
279
+ "type": "object",
280
+ "properties": {
281
+ "name": {
282
+ "type": "string"
283
+ },
284
+ "price": {
285
+ "type": "string",
286
+ "pattern": "^[0-9]+(\\.[0-9]{1,2})?$"
287
+ },
288
+ "quantity": {
289
+ "type": "string",
290
+ "pattern": "^[0-9]+$"
291
+ },
292
+ "hs_product_id": {
293
+ "type": "string",
294
+ "pattern": "^[0-9]+$"
295
+ },
296
+ "recurringbillingfrequency": {
297
+ "type": "string"
298
+ },
299
+ "hs_recurring_billing_period": {
300
+ "type": "string"
301
+ }
302
+ },
303
+ "required": ["name", "price"]
304
+ },
305
+ "associations": {
306
+ "type": "array",
307
+ "items": {
308
+ "type": "object",
309
+ "properties": {
310
+ "to": {
311
+ "type": "object",
312
+ "properties": {
313
+ "id": {
314
+ "type": "integer"
315
+ }
316
+ },
317
+ "required": ["id"]
318
+ },
319
+ "types": {
320
+ "type": "array",
321
+ "items": {
322
+ "type": "object",
323
+ "properties": {
324
+ "associationCategory": {
325
+ "type": "string",
326
+ "enum": [
327
+ "HUBSPOT_DEFINED",
328
+ "USER_DEFINED",
329
+ "INTEGRATOR_DEFINED"
330
+ ]
331
+ },
332
+ "associationTypeId": {
333
+ "type": "integer"
334
+ }
335
+ },
336
+ "required": ["associationCategory", "associationTypeId"]
337
+ }
338
+ }
339
+ },
340
+ "required": ["to", "types"]
341
+ }
342
+ }
343
+ },
344
+ "required": ["properties", "associations"]
345
+ },
346
+ "supported_sync_modes": ["full_refresh", "incremental"],
347
+ "source_defined_cursor": true,
348
+ "default_cursor_field": ["updated"]
349
+ }
350
+ ]
351
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "data": {
3
+ "name": "Hubspot",
4
+ "title": "HubSpot CRM",
5
+ "connector_type": "destination",
6
+ "category": "CRM",
7
+ "documentation_url": "https://docs.multiwoven.com/destinations/crm/hubspot",
8
+ "github_issue_label": "destination-hubspot-crm",
9
+ "icon": "icon.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/destinations/crm/hubspot",
3
+ "stream_type": "static",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "HubSpot CRM",
7
+ "type": "object",
8
+ "required": ["access_token"],
9
+ "properties": {
10
+ "access_token": {
11
+ "type": "string",
12
+ "title": "Access Token",
13
+ "order": 0
14
+ }
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
2
+ <svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
3
+ <circle cx="512" cy="512" r="512" style="fill:#ff7a59"/>
4
+ <path d="M623.8 624.94c-38.23 0-69.24-30.67-69.24-68.51s31-68.52 69.24-68.52 69.26 30.67 69.26 68.52-31 68.51-69.26 68.51m20.74-200.42v-61a46.83 46.83 0 0 0 27.33-42.29v-1.41c0-25.78-21.32-46.86-47.35-46.86h-1.43c-26 0-47.35 21.09-47.35 46.86v1.41a46.85 46.85 0 0 0 27.33 42.29v61a135.08 135.08 0 0 0-63.86 27.79l-169.1-130.17A52.49 52.49 0 0 0 372 309c0-29.21-23.89-52.92-53.4-53s-53.45 23.59-53.48 52.81 23.85 52.88 53.36 52.93a53.29 53.29 0 0 0 26.33-7.09l166.38 128.1a132.14 132.14 0 0 0 2.07 150.3l-50.62 50.1A43.42 43.42 0 1 0 450.1 768c24.24 0 43.9-19.46 43.9-43.45a42.24 42.24 0 0 0-2-12.42l50-49.52a135.28 135.28 0 0 0 81.8 27.47c74.61 0 135.06-59.83 135.06-133.65 0-66.82-49.62-122-114.33-131.91" style="fill:#ffffff;fill-rule:evenodd"/>
5
+ </svg>
@@ -3,7 +3,7 @@
3
3
  module Multiwoven::Integrations::Destination
4
4
  module Klaviyo
5
5
  include Multiwoven::Integrations::Core
6
- class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
6
+ class Client < DestinationConnector
7
7
  def check_connection(connection_config)
8
8
  connection_config = connection_config.with_indifferent_access
9
9
  api_key = connection_config[:private_api_key]
@@ -20,19 +20,7 @@ module Multiwoven::Integrations::Destination
20
20
  def discover(_connection_config = nil)
21
21
  catalog_json = read_json(CATALOG_SPEC_PATH)
22
22
 
23
- streams = catalog_json["streams"].map do |stream|
24
- Multiwoven::Integrations::Protocol::Stream.new(
25
- name: stream["name"],
26
- json_schema: stream["json_schema"],
27
- url: stream["url"],
28
- request_method: stream["method"],
29
- action: stream["action"]
30
- )
31
- end
32
-
33
- catalog = Multiwoven::Integrations::Protocol::Catalog.new(
34
- streams: streams
35
- )
23
+ catalog = build_catalog(catalog_json)
36
24
 
37
25
  catalog.to_multiwoven_message
38
26
  rescue StandardError => e
@@ -1,4 +1,7 @@
1
1
  {
2
+ "request_rate_limit": 600,
3
+ "request_rate_limit_unit": "minute",
4
+ "request_rate_concurrency": 10,
2
5
  "streams": [
3
6
  {
4
7
  "name": "profile",
@@ -21,7 +21,7 @@ module Multiwoven
21
21
  end
22
22
 
23
23
  def discover(_connection_config = nil)
24
- catalog = build_catalog(load_catalog_streams)
24
+ catalog = build_catalog(load_catalog)
25
25
  catalog.to_multiwoven_message
26
26
  rescue StandardError => e
27
27
  handle_exception("SALESFORCE:CRM:DISCOVER:EXCEPTION", "error", e)
@@ -96,20 +96,8 @@ module Multiwoven
96
96
  ConnectionStatus.new(status: ConnectionStatusType["failed"], message: error.message).to_multiwoven_message
97
97
  end
98
98
 
99
- def load_catalog_streams
100
- catalog_json = read_json(CATALOG_SPEC_PATH)
101
- catalog_json["streams"].map { |stream| build_stream(stream) }
102
- end
103
-
104
- def build_stream(stream)
105
- Multiwoven::Integrations::Protocol::Stream.new(
106
- name: stream["name"], json_schema: stream["json_schema"],
107
- action: stream["action"]
108
- )
109
- end
110
-
111
- def build_catalog(streams)
112
- Multiwoven::Integrations::Protocol::Catalog.new(streams: streams)
99
+ def load_catalog
100
+ read_json(CATALOG_SPEC_PATH)
113
101
  end
114
102
 
115
103
  def tracking_message(success, failure)
@@ -1,4 +1,7 @@
1
1
  {
2
+ "request_rate_limit": 100000,
3
+ "request_rate_limit_unit": "day",
4
+ "request_rate_concurrency": 10,
2
5
  "streams": [
3
6
  {
4
7
  "name": "Account",
@@ -19,7 +19,7 @@ module Multiwoven
19
19
  end
20
20
 
21
21
  def discover(_connection_config = nil)
22
- catalog = build_catalog(load_catalog_streams)
22
+ catalog = build_catalog(load_catalog)
23
23
  catalog.to_multiwoven_message
24
24
  rescue StandardError => e
25
25
  handle_exception("SLACK:DISCOVER:EXCEPTION", "error", e)
@@ -97,20 +97,8 @@ module Multiwoven
97
97
  ConnectionStatus.new(status: ConnectionStatusType["failed"], message: error.message).to_multiwoven_message
98
98
  end
99
99
 
100
- def load_catalog_streams
101
- catalog_json = read_json(CATALOG_SPEC_PATH)
102
- catalog_json["streams"].map { |stream| build_stream(stream) }
103
- end
104
-
105
- def build_stream(stream)
106
- Multiwoven::Integrations::Protocol::Stream.new(
107
- name: stream["name"], json_schema: stream["json_schema"],
108
- action: stream["action"]
109
- )
110
- end
111
-
112
- def build_catalog(streams)
113
- Multiwoven::Integrations::Protocol::Catalog.new(streams: streams)
100
+ def load_catalog
101
+ read_json(CATALOG_SPEC_PATH)
114
102
  end
115
103
 
116
104
  def tracking_message(success, failure)
@@ -14,7 +14,9 @@
14
14
  "oneOf": [{ "required": ["text"] }]
15
15
  },
16
16
  "supported_sync_modes": ["full_refresh", "incremental"],
17
- "source_defined_cursor": true
17
+ "request_rate_limit": 60,
18
+ "request_rate_limit_unit": "minute",
19
+ "request_rate_concurrency": 1
18
20
  }
19
21
  ]
20
22
  }
@@ -23,6 +23,7 @@ module Multiwoven
23
23
  "rate_limit", "connection_config"
24
24
  )
25
25
  LogLevel = Types::String.enum("fatal", "error", "warn", "info", "debug", "trace")
26
+ RequestRateLimitingUnit = Types::String.default("minute").enum("minute", "hour", "day")
26
27
 
27
28
  class ProtocolModel < Dry::Struct
28
29
  extend Multiwoven::Integrations::Core::Utils
@@ -108,21 +109,46 @@ module Multiwoven
108
109
  attribute? :action, StreamAction
109
110
  attribute :json_schema, Types::Hash
110
111
  attribute? :supported_sync_modes, Types::Array.of(SyncMode).optional
112
+
111
113
  # Applicable for database streams
112
114
  attribute? :source_defined_cursor, Types::Bool.optional
113
115
  attribute? :default_cursor_field, Types::Array.of(Types::String).optional
114
116
  attribute? :source_defined_primary_key, Types::Array.of(Types::Array.of(Types::String)).optional
117
+
115
118
  attribute? :namespace, Types::String.optional
116
119
  # Applicable for API streams
117
120
  attribute? :url, Types::String.optional
118
121
  attribute? :request_method, Types::String.optional
119
122
  attribute :batch_support, Types::Bool.default(false)
120
123
  attribute :batch_size, Types::Integer.default(1)
124
+
125
+ # Rate limits
126
+ attribute? :request_rate_limit, Types::Integer
127
+ attribute? :request_rate_limit_unit, RequestRateLimitingUnit
128
+ attribute? :request_rate_concurrency, Types::Integer
129
+
130
+ def rate_limit_unit_seconds
131
+ case request_rate_limit_unit
132
+ when "minute"
133
+ 60 # Seconds in a minute
134
+ when "hour"
135
+ 3600 # Seconds in an hour
136
+ when "day"
137
+ 86_400 # Seconds in a day
138
+ else
139
+ 1 # Default case, consider as seconds or handle as error
140
+ end
141
+ end
121
142
  end
122
143
 
123
144
  class Catalog < ProtocolModel
124
145
  attribute :streams, Types::Array.of(Stream)
125
146
 
147
+ # Rate limits
148
+ attribute? :request_rate_limit, Types::Integer.default(60)
149
+ attribute? :request_rate_limit_unit, RequestRateLimitingUnit
150
+ attribute? :request_rate_concurrency, Types::Integer.default(10)
151
+
126
152
  def to_multiwoven_message
127
153
  MultiwovenMessage.new(
128
154
  type: MultiwovenMessageType["catalog"],
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Multiwoven
4
4
  module Integrations
5
- VERSION = "0.1.28"
5
+ VERSION = "0.1.30"
6
6
 
7
7
  ENABLED_SOURCES = %w[
8
8
  Snowflake
@@ -16,6 +16,7 @@ module Multiwoven
16
16
  SalesforceCrm
17
17
  FacebookCustomAudience
18
18
  Slack
19
+ Hubspot
19
20
  ].freeze
20
21
  end
21
22
  end
@@ -3,11 +3,10 @@
3
3
  module Multiwoven
4
4
  module Integrations
5
5
  class Service
6
+ def initialize
7
+ yield(self.class.config) if block_given?
8
+ end
6
9
  class << self
7
- def initialize
8
- yield(config) if block_given?
9
- end
10
-
11
10
  def connectors
12
11
  {
13
12
  source: build_connectors(
@@ -14,6 +14,8 @@ require "restforce"
14
14
  require "logger"
15
15
  require "slack-ruby-client"
16
16
  require "git"
17
+ require "ruby-limiter"
18
+ require "hubspot-api-client"
17
19
 
18
20
  # Service
19
21
  require_relative "integrations/config"
@@ -23,6 +25,7 @@ require_relative "integrations/service"
23
25
  # Core
24
26
  require_relative "integrations/core/constants"
25
27
  require_relative "integrations/core/utils"
28
+ require_relative "integrations/core/rate_limiter"
26
29
  require_relative "integrations/protocol/protocol"
27
30
  require_relative "integrations/core/base_connector"
28
31
  require_relative "integrations/core/source_connector"
@@ -40,6 +43,7 @@ require_relative "integrations/destination/klaviyo/client"
40
43
  require_relative "integrations/destination/salesforce_crm/client"
41
44
  require_relative "integrations/destination/facebook_custom_audience/client"
42
45
  require_relative "integrations/destination/slack/client"
46
+ require_relative "integrations/destination/hubspot/client"
43
47
 
44
48
  module Multiwoven
45
49
  module Integrations
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multiwoven-integrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.28
4
+ version: 0.1.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Subin T P
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-19 00:00:00.000000000 Z
11
+ date: 2024-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: hubspot-api-client
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: pg
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +164,20 @@ dependencies:
150
164
  - - ">="
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: ruby-limiter
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
153
181
  - !ruby/object:Gem::Dependency
154
182
  name: ruby-odbc
155
183
  requirement: !ruby/object:Gem::Requirement
@@ -299,6 +327,7 @@ files:
299
327
  - lib/multiwoven/integrations/core/constants.rb
300
328
  - lib/multiwoven/integrations/core/destination_connector.rb
301
329
  - lib/multiwoven/integrations/core/http_client.rb
330
+ - lib/multiwoven/integrations/core/rate_limiter.rb
302
331
  - lib/multiwoven/integrations/core/source_connector.rb
303
332
  - lib/multiwoven/integrations/core/utils.rb
304
333
  - lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb
@@ -306,6 +335,11 @@ files:
306
335
  - lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json
307
336
  - lib/multiwoven/integrations/destination/facebook_custom_audience/config/spec.json
308
337
  - lib/multiwoven/integrations/destination/facebook_custom_audience/icon.svg
338
+ - lib/multiwoven/integrations/destination/hubspot/client.rb
339
+ - lib/multiwoven/integrations/destination/hubspot/config/catalog.json
340
+ - lib/multiwoven/integrations/destination/hubspot/config/meta.json
341
+ - lib/multiwoven/integrations/destination/hubspot/config/spec.json
342
+ - lib/multiwoven/integrations/destination/hubspot/icon.svg
309
343
  - lib/multiwoven/integrations/destination/klaviyo/client.rb
310
344
  - lib/multiwoven/integrations/destination/klaviyo/config/catalog.json
311
345
  - lib/multiwoven/integrations/destination/klaviyo/config/meta.json