multiwoven-integrations 0.1.36 → 0.1.38

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