multiwoven-integrations 0.1.36 → 0.1.38

Sign up to get free protection for your applications and to get access to all the features.
Files changed (21) hide show
  1. checksums.yaml +4 -4
  2. data/lib/multiwoven/integrations/core/destination_connector.rb +0 -2
  3. data/lib/multiwoven/integrations/core/fullrefresher.rb +19 -0
  4. data/lib/multiwoven/integrations/destination/airtable/client.rb +1 -0
  5. data/lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb +1 -0
  6. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json +1 -1
  7. data/lib/multiwoven/integrations/destination/google_sheets/client.rb +51 -3
  8. data/lib/multiwoven/integrations/destination/hubspot/client.rb +1 -0
  9. data/lib/multiwoven/integrations/destination/klaviyo/client.rb +1 -0
  10. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/client.rb +117 -0
  11. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/catalog.json +2042 -0
  12. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/meta.json +16 -0
  13. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/spec.json +43 -0
  14. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/icon.svg +16 -0
  15. data/lib/multiwoven/integrations/destination/salesforce_crm/client.rb +1 -0
  16. data/lib/multiwoven/integrations/destination/slack/client.rb +1 -0
  17. data/lib/multiwoven/integrations/destination/stripe/client.rb +1 -0
  18. data/lib/multiwoven/integrations/protocol/protocol.rb +2 -1
  19. data/lib/multiwoven/integrations/rollout.rb +2 -1
  20. data/lib/multiwoven/integrations.rb +2 -0
  21. metadata +36 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 818c8606d1ef1b50bd14765b3ed85f687ef5e16cdd975324bfbf28ed2b371070
4
- data.tar.gz: 0bd41ef2fe575ff51d5ea5878522509de3aa6ae0b3592074c15ce3de5be97c51
3
+ metadata.gz: be7368d653a73843a7d204b4dd7793c2ce7d5ba7f7e1e3b49818e15aff85367a
4
+ data.tar.gz: 21c792560111def1edad270dfcba87627818fbf4763c159053abf0114438e035
5
5
  SHA512:
6
- metadata.gz: dfaa4c53e72fbf3fc64d2d746bf4fd632c19be834af900a93eb88060fc1a84156b1849b645768ca41ebd3037f205a7b4f79430738eddb7f607b526fb9424c08b
7
- data.tar.gz: dd7dc877f6ad86082975cb9dbdc714bc5e9dc623196178e6c9c11b601deab967fa107d1d218d5dc03e43b6b6c580403d45bc470fd364a109a8a1cb6e8a793520
6
+ metadata.gz: b294140a41825ef76941958c46ec86a976e9073813b8a641663d249d518dd77d208d1c229bc20b7e76336196aa8abef7504ee92ac8a4100681b76fef05a2251e
7
+ data.tar.gz: f72981b9d03124e508d4cc1087ef13b876ee731d037bfaf4c4142bbc13c9a3227f248b0bdf07579f95591549c0800ce6439fdcd94a36510537589186512f9018
@@ -3,8 +3,6 @@
3
3
  module Multiwoven
4
4
  module Integrations::Core
5
5
  class DestinationConnector < BaseConnector
6
- prepend RateLimiter
7
-
8
6
  # Records are transformed json payload send it to the destination
9
7
  # SyncConfig is the Protocol::SyncConfig object
10
8
  def write(_sync_config, _records, _action = "insert")
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven
4
+ module Integrations::Core
5
+ module Fullrefresher
6
+ def write(sync_config, records, action = "insert")
7
+ if sync_config && sync_config.sync_mode == "full_refresh" && !@full_refreshed
8
+ response = clear_all_records(sync_config)
9
+ return response unless response &&
10
+ response.control.status == Multiwoven::Integrations::Protocol::ConnectionStatusType["succeeded"]
11
+
12
+ @full_refreshed = true
13
+ end
14
+
15
+ super(sync_config, records, action)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -7,6 +7,7 @@ module Multiwoven
7
7
  module Airtable
8
8
  include Multiwoven::Integrations::Core
9
9
  class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
10
+ prepend Multiwoven::Integrations::Core::RateLimiter
10
11
  MAX_CHUNK_SIZE = 10
11
12
  def check_connection(connection_config)
12
13
  connection_config = connection_config.with_indifferent_access
@@ -4,6 +4,7 @@ module Multiwoven::Integrations::Destination
4
4
  module FacebookCustomAudience
5
5
  include Multiwoven::Integrations::Core
6
6
  class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
7
+ prepend Multiwoven::Integrations::Core::RateLimiter
7
8
  MAX_CHUNK_SIZE = 10_000
8
9
  def check_connection(connection_config)
9
10
  connection_config = connection_config.with_indifferent_access
@@ -33,7 +33,7 @@
33
33
  "PAGEUID": { "type": ["string", "null"], "default": null, "title": "Page-Scoped ID", "x-hashRequired": false }
34
34
  }
35
35
  },
36
- "supported_sync_modes": ["full_refresh", "incremental"],
36
+ "supported_sync_modes": ["incremental"],
37
37
  "source_defined_cursor": true,
38
38
  "default_cursor_field": ["updated"],
39
39
  "source_defined_primary_key": [["email"]]
@@ -7,9 +7,12 @@ module Multiwoven
7
7
  include Multiwoven::Integrations::Core
8
8
 
9
9
  class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
10
+ prepend Multiwoven::Integrations::Core::Fullrefresher
11
+ prepend Multiwoven::Integrations::Core::RateLimiter
10
12
  MAX_CHUNK_SIZE = 10_000
11
13
 
12
14
  def check_connection(connection_config)
15
+ connection_config = connection_config.with_indifferent_access
13
16
  authorize_client(connection_config)
14
17
  fetch_google_spread_sheets(connection_config)
15
18
  success_status
@@ -19,6 +22,7 @@ module Multiwoven
19
22
  end
20
23
 
21
24
  def discover(connection_config)
25
+ connection_config = connection_config.with_indifferent_access
22
26
  authorize_client(connection_config)
23
27
  spreadsheets = fetch_google_spread_sheets(connection_config)
24
28
  catalog = build_catalog_from_spreadsheets(spreadsheets, connection_config)
@@ -34,6 +38,24 @@ module Multiwoven
34
38
  handle_exception("GOOGLE_SHEETS:CRM:WRITE:EXCEPTION", "error", e)
35
39
  end
36
40
 
41
+ def clear_all_records(sync_config)
42
+ setup_write_environment(sync_config, "clear")
43
+ connection_specification = sync_config.destination.connection_specification.with_indifferent_access
44
+ spreadsheet = fetch_google_spread_sheets(connection_specification)
45
+ sheet_ids = spreadsheet.sheets.map(&:properties).map(&:sheet_id)
46
+
47
+ delete_extra_sheets(sheet_ids)
48
+
49
+ unless sheet_ids.empty?
50
+ clear_response = clear_sheet_data(spreadsheet.sheets.first.properties.title)
51
+ return control_message("Successfully cleared data.", "succeeded") if clear_response&.cleared_range
52
+ end
53
+
54
+ control_message("Failed to clear data.", "failed")
55
+ rescue StandardError => e
56
+ control_message(e.message, "failed")
57
+ end
58
+
37
59
  private
38
60
 
39
61
  # To define the level of access granted to your app, you need to identify and declare authorization scopes which is provided by google scopse https://developers.google.com/sheets/api/scopes
@@ -99,7 +121,7 @@ module Multiwoven
99
121
  batch_support: true,
100
122
  batch_size: 10_000,
101
123
  json_schema: generate_properties_schema(column_names),
102
- supported_sync_modes: %w[incremental]
124
+ supported_sync_modes: %w[incremental full_refresh]
103
125
  }.with_indifferent_access
104
126
  end
105
127
 
@@ -113,8 +135,9 @@ module Multiwoven
113
135
 
114
136
  def setup_write_environment(sync_config, action)
115
137
  @action = sync_config.stream.action || action
116
- @spreadsheet_id = extract_spreadsheet_id(sync_config.destination.connection_specification[:spreadsheet_link])
117
- authorize_client(sync_config.destination.connection_specification)
138
+ connection_specification = sync_config.destination.connection_specification.with_indifferent_access
139
+ @spreadsheet_id = extract_spreadsheet_id(connection_specification[:spreadsheet_link])
140
+ authorize_client(connection_specification)
118
141
  end
119
142
 
120
143
  def extract_spreadsheet_id(link)
@@ -176,6 +199,31 @@ module Multiwoven
176
199
  success: success, failed: failure
177
200
  ).to_multiwoven_message
178
201
  end
202
+
203
+ def delete_extra_sheets(sheet_ids)
204
+ # Leave one sheet intact as a spreadsheet must have at least one sheet.
205
+ # Delete all other sheets.
206
+ (sheet_ids.length - 1).times do |i|
207
+ request = Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest.new(
208
+ requests: [{ delete_sheet: { sheet_id: sheet_ids[i + 1] } }]
209
+ )
210
+ @client.batch_update_spreadsheet(@spreadsheet_id, request)
211
+ end
212
+ end
213
+
214
+ def clear_sheet_data(sheet_title)
215
+ clear_request = Google::Apis::SheetsV4::ClearValuesRequest.new
216
+ @client&.clear_values(@spreadsheet_id, "#{sheet_title}!A2:Z", clear_request)
217
+ end
218
+
219
+ def control_message(message, status)
220
+ ControlMessage.new(
221
+ type: "full_refresh",
222
+ emitted_at: Time.now.to_i,
223
+ status: ConnectionStatusType[status],
224
+ meta: { detail: message }
225
+ ).to_multiwoven_message
226
+ end
179
227
  end
180
228
  end
181
229
  end
@@ -9,6 +9,7 @@ module Multiwoven
9
9
  include Multiwoven::Integrations::Core
10
10
 
11
11
  class Client < DestinationConnector
12
+ prepend Multiwoven::Integrations::Core::RateLimiter
12
13
  def check_connection(connection_config)
13
14
  connection_config = connection_config.with_indifferent_access
14
15
  initialize_client(connection_config)
@@ -4,6 +4,7 @@ module Multiwoven::Integrations::Destination
4
4
  module Klaviyo
5
5
  include Multiwoven::Integrations::Core
6
6
  class Client < DestinationConnector
7
+ prepend Multiwoven::Integrations::Core::RateLimiter
7
8
  def check_connection(connection_config)
8
9
  connection_config = connection_config.with_indifferent_access
9
10
  api_key = connection_config[:private_api_key]
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module Multiwoven
6
+ module Integrations
7
+ module Destination
8
+ module SalesforceConsumerGoodsCloud
9
+ include Multiwoven::Integrations::Core
10
+
11
+ API_VERSION = "59.0"
12
+
13
+ class Client < DestinationConnector
14
+ prepend Multiwoven::Integrations::Core::RateLimiter
15
+ def check_connection(connection_config)
16
+ connection_config = connection_config.with_indifferent_access
17
+ initialize_client(connection_config)
18
+ authenticate_client
19
+ success_status
20
+ rescue StandardError => e
21
+ failure_status(e)
22
+ end
23
+
24
+ def discover(_connection_config = nil)
25
+ catalog = build_catalog(load_catalog)
26
+ catalog.to_multiwoven_message
27
+ rescue StandardError => e
28
+ handle_exception("SALESFORCE:CONSUMER:GOODS:ClOUD:DISCOVER:EXCEPTION", "error", e)
29
+ end
30
+
31
+ def write(sync_config, records, action = "create")
32
+ @action = sync_config.stream.action || action
33
+ initialize_client(sync_config.destination.connection_specification)
34
+ process_records(records, sync_config.stream)
35
+ rescue StandardError => e
36
+ handle_exception("SALESFORCE:CONSUMER:GOODS:ClOUD:WRITE:EXCEPTION", "error", e)
37
+ end
38
+
39
+ private
40
+
41
+ def initialize_client(config)
42
+ config = config.with_indifferent_access
43
+ @client = Restforce.new(oauth_token: config[:access_token],
44
+ refresh_token: config[:refresh_token],
45
+ instance_url: config[:instance_url],
46
+ client_id: config[:client_id],
47
+ client_secret: config[:client_secret],
48
+ authentication_callback: proc { |x| log_debug(x.to_s) },
49
+ api_version: API_VERSION)
50
+ end
51
+
52
+ def process_records(records, stream)
53
+ write_success = 0
54
+ write_failure = 0
55
+ properties = stream.json_schema[:properties]
56
+ records.each do |record_object|
57
+ record = extract_data(record_object, properties)
58
+ process_record(stream, record)
59
+ write_success += 1
60
+ rescue StandardError => e
61
+ handle_exception("SALESFORCE:CONSUMER:GOODS:ClOUD:WRITE:EXCEPTION", "error", e)
62
+ write_failure += 1
63
+ end
64
+ tracking_message(write_success, write_failure)
65
+ end
66
+
67
+ def process_record(stream, record)
68
+ send_data_to_salesforce(stream.name, record)
69
+ end
70
+
71
+ def send_data_to_salesforce(stream_name, record = {})
72
+ method_name = "#{@action}!"
73
+ args = build_args(@action, stream_name, record)
74
+ @client.send(method_name, *args)
75
+ end
76
+
77
+ def build_args(action, stream_name, record)
78
+ case action
79
+ when :upsert
80
+ [stream_name, record[:external_key], record]
81
+ when :destroy
82
+ [stream_name, record[:id]]
83
+ else
84
+ [stream_name, record]
85
+ end
86
+ end
87
+
88
+ def authenticate_client
89
+ @client.authenticate!
90
+ end
91
+
92
+ def success_status
93
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
94
+ end
95
+
96
+ def failure_status(error)
97
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: error.message).to_multiwoven_message
98
+ end
99
+
100
+ def load_catalog
101
+ read_json(CATALOG_SPEC_PATH)
102
+ end
103
+
104
+ def tracking_message(success, failure)
105
+ Multiwoven::Integrations::Protocol::TrackingMessage.new(
106
+ success: success, failed: failure
107
+ ).to_multiwoven_message
108
+ end
109
+
110
+ def log_debug(message)
111
+ Multiwoven::Integrations::Service.logger.debug(message)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end