multiwoven-integrations 0.1.1 → 0.1.6

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/.vscode/settings.json +5 -0
  3. data/lib/multiwoven/integrations/core/base_connector.rb +3 -3
  4. data/lib/multiwoven/integrations/core/constants.rb +2 -0
  5. data/lib/multiwoven/integrations/core/source_connector.rb +17 -0
  6. data/lib/multiwoven/integrations/core/utils.rb +8 -0
  7. data/lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb +123 -0
  8. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json +38 -0
  9. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json +15 -0
  10. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/spec.json +22 -0
  11. data/lib/multiwoven/integrations/destination/klaviyo/client.rb +11 -3
  12. data/lib/multiwoven/integrations/destination/klaviyo/config/catalog.json +86 -88
  13. data/lib/multiwoven/integrations/destination/klaviyo/config/meta.json +1 -1
  14. data/lib/multiwoven/integrations/destination/klaviyo/config/spec.json +2 -2
  15. data/lib/multiwoven/integrations/destination/salesforce_crm/client.rb +127 -0
  16. data/lib/multiwoven/integrations/destination/salesforce_crm/config/catalog.json +317 -0
  17. data/lib/multiwoven/integrations/destination/salesforce_crm/config/meta.json +15 -0
  18. data/lib/multiwoven/integrations/destination/salesforce_crm/config/spec.json +37 -0
  19. data/lib/multiwoven/integrations/destination/slack/client.rb +125 -0
  20. data/lib/multiwoven/integrations/destination/slack/config/catalog.json +71 -0
  21. data/lib/multiwoven/integrations/destination/slack/config/meta.json +14 -0
  22. data/lib/multiwoven/integrations/destination/slack/config/spec.json +22 -0
  23. data/lib/multiwoven/integrations/protocol/protocol.rb +2 -0
  24. data/lib/multiwoven/integrations/rollout.rb +4 -1
  25. data/lib/multiwoven/integrations/source/bigquery/client.rb +6 -0
  26. data/lib/multiwoven/integrations/source/bigquery/config/meta.json +1 -1
  27. data/lib/multiwoven/integrations/source/redshift/client.rb +5 -0
  28. data/lib/multiwoven/integrations/source/redshift/config/meta.json +1 -1
  29. data/lib/multiwoven/integrations/source/snowflake/client.rb +5 -0
  30. data/lib/multiwoven/integrations/source/snowflake/config/meta.json +1 -1
  31. data/lib/multiwoven/integrations.rb +6 -0
  32. data/multiwoven-integrations.gemspec +10 -6
  33. metadata +65 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e33f303a54e624644de0dbaa80056632e61c7834f0b192ba2e041e6c3d131e6
4
- data.tar.gz: dd3e29285e988665a08b6e71b1986810af068916bd2ebdd4f74a3bacab6b1385
3
+ metadata.gz: 2ca3845d4b648bffe8c785fdd1cc0c206099deb7d7414700d528a25ec2fb72f9
4
+ data.tar.gz: 319139931a5c9a723a379c3aa116d3d6a825235fc6b80359abf6c19104fcc815
5
5
  SHA512:
6
- metadata.gz: f538a941eae0f2d00d754d494d70c319f4bf44444ff4463e36010dbd53e25633b114f7399edd5a1199720eb736d20f64c8e956df4b349776e173ed1f6bc537c1
7
- data.tar.gz: 56fe3d7902f215f67403d21bcb857f8d18d87f6be0082dd875489e4d60d74a753178a2436eacbc4405f7c3baa8b1fbf267bfe3ab09cc9a292137c413e5432bd3
6
+ metadata.gz: f5a96c27ef09fb593a0f000bbefa835fb79ec2c7876a7d84635013f757d8a31a52f7f5f32b791a9539dc6494af0092faf28ca8c13cccc1b4465e94eeba49490a
7
+ data.tar.gz: a9ddefdc6259d2d160ea118e1571ed1facc8cb39d533f490692e085a12d160b2d8ec8138ab1801353c0e31eb172111ffa51d1c49af9b1b54c9a1645b7a9315f1
@@ -0,0 +1,5 @@
1
+ {
2
+ "cSpell.words": [
3
+ "multiwoven"
4
+ ]
5
+ }
@@ -30,10 +30,10 @@ module Multiwoven
30
30
  private
31
31
 
32
32
  def read_json(file_path)
33
- # TODO: Rethink. Hack to find connector folder
34
- connector_folder = self.class.to_s.split("::")[2..3].map(&:downcase).join("/")
33
+ path = Object.const_source_location(self.class.to_s)[0]
34
+ connector_folder = File.dirname(path)
35
35
  file_path = File.join(
36
- INTEGRATIONS_PATH, "#{connector_folder}/",
36
+ "#{connector_folder}/",
37
37
  file_path
38
38
  )
39
39
  file_contents = File.read(file_path)
@@ -24,6 +24,8 @@ module Multiwoven
24
24
  }
25
25
  }.freeze
26
26
 
27
+ FACEBOOK_AUDIENCE_GET_ALL_ACCOUNTS = "https://graph.facebook.com/v18.0/me/adaccounts?fields=id,name"
28
+
27
29
  # HTTP
28
30
  HTTP_GET = "GET"
29
31
  HTTP_POST = "POST"
@@ -7,6 +7,23 @@ module Multiwoven
7
7
  raise "Not implemented"
8
8
  # return list of RecordMessage
9
9
  end
10
+
11
+ private
12
+
13
+ def batched_query(sql_query, limit, offset)
14
+ offset = offset.to_i
15
+ limit = limit.to_i
16
+ raise ArgumentError, "Offset and limit must be non-negative" if offset.negative? || limit.negative?
17
+
18
+ # Removing any trailing semicolons
19
+ sql_query.chomp!(";")
20
+
21
+ # Checking if the query already has a LIMIT clause
22
+ raise ArgumentError, "Query already contains a LIMIT clause" if sql_query.match?(/LIMIT \d+/i)
23
+
24
+ # Appending the LIMIT and OFFSET clauses to the SQL query
25
+ "#{sql_query} LIMIT #{limit} OFFSET #{offset}"
26
+ end
10
27
  end
11
28
  end
12
29
  end
@@ -63,6 +63,14 @@ module Multiwoven
63
63
 
64
64
  create_log_message(context, type, exception)
65
65
  end
66
+
67
+ def extract_data(record_object, properties)
68
+ record_object.data.select { |key, _| properties.key?(key.to_s) }
69
+ end
70
+
71
+ def success?(response)
72
+ response && %w[200 201].include?(response.code.to_s)
73
+ end
66
74
  end
67
75
  end
68
76
  end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven::Integrations::Destination
4
+ module FacebookCustomAudience
5
+ include Multiwoven::Integrations::Core
6
+ class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
7
+ def check_connection(connection_config)
8
+ connection_config = connection_config.with_indifferent_access
9
+ access_token = connection_config[:access_token]
10
+ response = Multiwoven::Integrations::Core::HttpClient.request(
11
+ FACEBOOK_AUDIENCE_GET_ALL_ACCOUNTS,
12
+ HTTP_GET,
13
+ headers: auth_headers(access_token)
14
+ )
15
+ if success?(response)
16
+ ad_account_exists?(response, connection_config[:ad_account_id])
17
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
18
+ else
19
+ ConnectionStatus.new(status: ConnectionStatusType["failed"]).to_multiwoven_message
20
+ end
21
+ rescue StandardError => e
22
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: e.message).to_multiwoven_message
23
+ end
24
+
25
+ def discover(_connection_config = nil)
26
+ catalog_json = read_json(CATALOG_SPEC_PATH)
27
+
28
+ streams = catalog_json["streams"].map do |stream|
29
+ Multiwoven::Integrations::Protocol::Stream.new(
30
+ audience_id: stream["audience_id"],
31
+ url: stream["url"],
32
+ name: stream["name"],
33
+ json_schema: stream["json_schema"],
34
+ request_method: stream["method"],
35
+ action: stream["action"]
36
+ )
37
+ end
38
+
39
+ catalog = Multiwoven::Integrations::Protocol::Catalog.new(
40
+ streams: streams
41
+ )
42
+
43
+ catalog.to_multiwoven_message
44
+ rescue StandardError => e
45
+ handle_exception(
46
+ "FACEBOOK AUDIENCE:DISCOVER:EXCEPTION",
47
+ "error",
48
+ e
49
+ )
50
+ end
51
+
52
+ def write(sync_config, records, _action = "insert")
53
+ url = sync_config.stream.url
54
+ connection_config = sync_config.destination.connection_specification.with_indifferent_access
55
+ connection_config = connection_config.with_indifferent_access
56
+ access_token = connection_config[:access_token]
57
+
58
+ write_success = 0
59
+ write_failure = 0
60
+ records.each do |record|
61
+ schema, data = extract_schema_and_data(record.with_indifferent_access[:data])
62
+ payload = {
63
+ "payload" => {
64
+ "schema" => schema,
65
+ "data" => [data]
66
+ }
67
+ }
68
+ response = Multiwoven::Integrations::Core::HttpClient.request(
69
+ url,
70
+ sync_config.stream.request_method,
71
+ payload: payload,
72
+ headers: auth_headers(access_token)
73
+ )
74
+ if success?(response)
75
+ write_success += 1
76
+ else
77
+ write_failure += 1
78
+ end
79
+ rescue StandardError => e
80
+ logger.error(
81
+ "FACEBOOK:RECORD:WRITE:FAILURE: #{e.message}"
82
+ )
83
+ write_failure += 1
84
+ end
85
+ tracker = Multiwoven::Integrations::Protocol::TrackingMessage.new(
86
+ success: write_success,
87
+ failed: write_failure
88
+ )
89
+ tracker.to_multiwoven_message
90
+ rescue StandardError => e
91
+ # TODO: Handle rate limiting seperately
92
+ handle_exception(
93
+ "FACEBOOK:WRITE:EXCEPTION",
94
+ "error",
95
+ e
96
+ )
97
+ end
98
+
99
+ def extract_schema_and_data(data)
100
+ [data.keys, data.values]
101
+ end
102
+
103
+ def auth_headers(access_token)
104
+ {
105
+ "Accept" => "application/json",
106
+ "Authorization" => "Bearer #{access_token}",
107
+ "Content-Type" => "application/json"
108
+ }
109
+ end
110
+
111
+ def ad_account_exists?(response, ad_account_id)
112
+ return if extract_data(response).any? { |ad_account| ad_account["id"] == "act_#{ad_account_id}" }
113
+
114
+ raise ArgumentError, "Ad account not found in business account"
115
+ end
116
+
117
+ def extract_data(response)
118
+ response_body = response.body
119
+ JSON.parse(response_body)["data"] if response_body
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,38 @@
1
+ {
2
+ "streams": [
3
+ {
4
+ "name": "audience",
5
+ "action": "create",
6
+ "method": "POST",
7
+ "audience_id": "audience_id",
8
+ "url": "https://graph.facebook.com/v18.0/{audience_id}/users",
9
+ "json_schema": {
10
+ "type": "object",
11
+ "additionalProperties": true,
12
+ "properties": {
13
+ "CT": { "type": ["string", "null"], "default": null },
14
+ "COUNTRY": { "type": ["string", "null"], "default": null },
15
+ "DOBD": { "type": ["string", "null"], "default": null },
16
+ "DOBM": { "type": ["string", "null"], "default": null },
17
+ "DOBY": { "type": ["string", "null"], "default": null },
18
+ "EMAIL": { "type": ["string", "null"], "default": null },
19
+ "EXTERN_ID": { "type": ["string", "null"], "default": null },
20
+ "FI": { "type": ["string", "null"], "default": null },
21
+ "FN": { "type": ["string", "null"], "default": null },
22
+ "GEN": { "type": ["string", "null"], "default": null },
23
+ "LN": { "type": ["string", "null"], "default": null },
24
+ "LOOKALIKE_VALUE": { "type": ["number", "null"], "default": null },
25
+ "MADID": { "type": ["string", "null"], "default": null },
26
+ "PAGEUID": { "type": ["string", "null"], "default": null },
27
+ "PHONE": { "type": ["string", "null"], "default": null },
28
+ "ST": { "type": ["string", "null"], "default": null },
29
+ "ZIP": { "type": ["string", "null"], "default": null }
30
+ }
31
+ },
32
+ "supported_sync_modes": ["full_refresh", "incremental"],
33
+ "source_defined_cursor": true,
34
+ "default_cursor_field": ["updated"],
35
+ "source_defined_primary_key": [["email"]]
36
+ }
37
+ ]
38
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "data":
3
+ {
4
+ "name": "Facebook",
5
+ "connector_type": "destination",
6
+ "category": "Adtech",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "destination-facebook",
9
+ "icon": "facebook.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/destination/facebook",
3
+ "stream_type": "static",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "Facebook Destination Spec",
7
+ "type": "object",
8
+ "required": ["access_token","ad_account_id"],
9
+ "properties": {
10
+ "access_token": {
11
+ "type": "string",
12
+ "title": "Access Token",
13
+ "order": 0
14
+ },
15
+ "ad_account_id": {
16
+ "type": "string",
17
+ "title": "Ad Account Id",
18
+ "order": 1
19
+ }
20
+ }
21
+ }
22
+ }
@@ -3,8 +3,9 @@
3
3
  module Multiwoven::Integrations::Destination
4
4
  module Klaviyo
5
5
  include Multiwoven::Integrations::Core
6
- class Client < DestinationConnector
6
+ class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
7
7
  def check_connection(connection_config)
8
+ connection_config = connection_config.with_indifferent_access
8
9
  api_key = connection_config[:private_api_key]
9
10
 
10
11
  response = Multiwoven::Integrations::Core::HttpClient.request(
@@ -16,7 +17,7 @@ module Multiwoven::Integrations::Destination
16
17
  parse_response(response)
17
18
  end
18
19
 
19
- def discover
20
+ def discover(_connection_config = nil)
20
21
  catalog_json = read_json(CATALOG_SPEC_PATH)
21
22
 
22
23
  streams = catalog_json["streams"].map do |stream|
@@ -24,7 +25,7 @@ module Multiwoven::Integrations::Destination
24
25
  name: stream["name"],
25
26
  json_schema: stream["json_schema"],
26
27
  url: stream["url"],
27
- method: stream["method"],
28
+ request_method: stream["method"],
28
29
  action: stream["action"]
29
30
  )
30
31
  end
@@ -44,12 +45,19 @@ module Multiwoven::Integrations::Destination
44
45
 
45
46
  def write(sync_config, records, _action = "insert")
46
47
  connection_config = sync_config.destination.connection_specification.with_indifferent_access
48
+ connection_config = connection_config.with_indifferent_access
47
49
  url = sync_config.stream.url
50
+
48
51
  request_method = sync_config.stream.request_method
49
52
 
50
53
  write_success = 0
51
54
  write_failure = 0
52
55
  records.each do |record|
56
+ # pre process payload
57
+ # Add hardcode values into payload
58
+ record["data"] ||= {}
59
+ record["data"]["type"] = sync_config.stream.name
60
+
53
61
  response = Multiwoven::Integrations::Core::HttpClient.request(
54
62
  url,
55
63
  request_method,
@@ -6,103 +6,101 @@
6
6
  "url": "https://a.klaviyo.com/api/profiles",
7
7
  "method": "POST",
8
8
  "json_schema": {
9
+ "$schema": "http://json-schema.org/draft-07/schema#",
9
10
  "type": "object",
10
- "additionalProperties": true,
11
11
  "properties": {
12
- "type": {
13
- "type": [
14
- "null",
15
- "string"
16
- ]
17
- },
18
- "id": {
19
- "type": "string"
20
- },
21
- "updated": {
22
- "type": [
23
- "null",
24
- "string"
25
- ],
26
- "format": "date-time"
27
- },
28
- "attributes": {
29
- "type": [
30
- "null",
31
- "object"
32
- ],
33
- "additionalProperties": true,
12
+ "data": {
13
+ "type": "object",
34
14
  "properties": {
35
- "email": {
36
- "type": [
37
- "null",
38
- "string"
39
- ]
40
- },
41
- "phone_number": {
42
- "type": [
43
- "null",
44
- "string"
45
- ]
46
- },
47
- "first_name": {
48
- "type": [
49
- "null",
50
- "string"
51
- ]
52
- },
53
- "last_name": {
54
- "type": [
55
- "null",
56
- "string"
15
+ "type": {
16
+ "type": "string",
17
+ "enum": [
18
+ "profile"
57
19
  ]
58
20
  },
59
- "properties": {
60
- "type": [
61
- "null",
62
- "object"
63
- ],
64
- "additionalProperties": true
65
- },
66
- "organization": {
67
- "type": [
68
- "null",
69
- "string"
70
- ]
71
- },
72
- "title": {
73
- "type": [
74
- "null",
75
- "string"
21
+ "attributes": {
22
+ "type": "object",
23
+ "properties": {
24
+ "email": {
25
+ "type": "string",
26
+ "format": "email"
27
+ },
28
+ "phone_number": {
29
+ "type": "string"
30
+ },
31
+ "external_id": {
32
+ "type": "string",
33
+ "format": "uuid"
34
+ },
35
+ "first_name": {
36
+ "type": "string"
37
+ },
38
+ "last_name": {
39
+ "type": "string"
40
+ },
41
+ "organization": {
42
+ "type": "string"
43
+ },
44
+ "title": {
45
+ "type": "string"
46
+ },
47
+ "image": {
48
+ "type": "string",
49
+ "format": "uri"
50
+ },
51
+ "location": {
52
+ "type": "object",
53
+ "properties": {
54
+ "address1": {
55
+ "type": "string"
56
+ },
57
+ "address2": {
58
+ "type": "string"
59
+ },
60
+ "city": {
61
+ "type": "string"
62
+ },
63
+ "country": {
64
+ "type": "string"
65
+ },
66
+ "region": {
67
+ "type": "string"
68
+ },
69
+ "zip": {
70
+ "type": "string"
71
+ },
72
+ "timezone": {
73
+ "type": "string"
74
+ },
75
+ "ip": {
76
+ "type": "string",
77
+ "format": "ipv4"
78
+ }
79
+ }
80
+
81
+ },
82
+ "properties": {
83
+ "type": "object",
84
+ "additionalProperties": {
85
+ "type": "string"
86
+ }
87
+ }
88
+ },
89
+ "required": [
90
+ "email",
91
+ "phone_number"
76
92
  ]
77
- },
78
- "last_event_date": {
79
- "type": [
80
- "null",
81
- "string"
82
- ],
83
- "format": "date-time"
84
93
  }
85
- }
86
- },
87
- "links": {
88
- "type": [
89
- "null",
90
- "object"
91
- ]
92
- },
93
- "relationships": {
94
- "type": [
95
- "null",
96
- "object"
97
- ]
98
- },
99
- "segments": {
100
- "type": [
101
- "null",
102
- "object"
94
+ },
95
+ "required": [
96
+ "type",
97
+ "attributes"
103
98
  ]
104
99
  }
105
- }
100
+ },
101
+ "required": [
102
+ "data"
103
+ ]
106
104
  },
107
105
  "supported_sync_modes": [
108
106
  "full_refresh",
@@ -3,7 +3,7 @@
3
3
  {
4
4
  "name": "Klaviyo",
5
5
  "connector_type": "destination",
6
- "connector_subtype": "API",
6
+ "category": "Marketing Automation",
7
7
  "documentation_url": "https://docs.mutliwoven.com",
8
8
  "github_issue_label": "destination-klaviyo",
9
9
  "icon": "klaviyo.svg",
@@ -7,7 +7,7 @@
7
7
  "type": "object",
8
8
  "required": ["public_api_key", "private_api_key"],
9
9
  "properties": {
10
- "public_api_keyhost": {
10
+ "public_api_key": {
11
11
  "type": "string",
12
12
  "title": "Public API Key",
13
13
  "order": 0
@@ -19,4 +19,4 @@
19
19
  }
20
20
  }
21
21
  }
22
- }
22
+ }
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module Multiwoven
6
+ module Integrations
7
+ module Destination
8
+ module SalesforceCrm
9
+ include Multiwoven::Integrations::Core
10
+
11
+ API_VERSION = "59.0"
12
+
13
+ class Client < DestinationConnector
14
+ def check_connection(connection_config)
15
+ connection_config = connection_config.with_indifferent_access
16
+ initialize_client(connection_config)
17
+ authenticate_client
18
+ success_status
19
+ rescue StandardError => e
20
+ failure_status(e)
21
+ end
22
+
23
+ def discover(_connection_config = nil)
24
+ catalog = build_catalog(load_catalog_streams)
25
+ catalog.to_multiwoven_message
26
+ rescue StandardError => e
27
+ handle_exception("SALESFORCE:CRM:DISCOVER:EXCEPTION", "error", e)
28
+ end
29
+
30
+ def write(sync_config, records, _action = "create")
31
+ initialize_client(sync_config[:destination][:connection_specification])
32
+ process_records(records, sync_config[:stream])
33
+ rescue StandardError => e
34
+ handle_exception("SALESFORCE:CRM:WRITE:EXCEPTION", "error", e)
35
+ end
36
+
37
+ private
38
+
39
+ def initialize_client(config)
40
+ config = config.with_indifferent_access
41
+ @client = Restforce.new(oauth_token: config[:access_token],
42
+ refresh_token: config[:refresh_token],
43
+ instance_url: config[:instance_url],
44
+ client_id: config[:client_id],
45
+ client_secret: config[:client_secret],
46
+ authentication_callback: proc { |x| log_debug(x.to_s) },
47
+ api_version: API_VERSION)
48
+ end
49
+
50
+ def process_records(records, stream)
51
+ write_success = 0
52
+ write_failure = 0
53
+ properties = stream[:json_schema][:properties]
54
+ records.each do |record_object|
55
+ record = extract_data(record_object, properties)
56
+ process_record(stream, record)
57
+ write_success += 1
58
+ rescue StandardError => e
59
+ handle_exception("SALESFORCE:CRM:WRITE:EXCEPTION", "error", e)
60
+ write_failure += 1
61
+ end
62
+ tracking_message(write_success, write_failure)
63
+ end
64
+
65
+ def process_record(stream, record)
66
+ send_data_to_salesforce(stream[:action], stream[:name], record)
67
+ end
68
+
69
+ def send_data_to_salesforce(action, stream_name, record = {})
70
+ method_name = "#{action}!"
71
+ args = build_args(action, stream_name, record)
72
+ @client.send(method_name, *args)
73
+ end
74
+
75
+ def build_args(action, stream_name, record)
76
+ case action
77
+ when :upsert
78
+ [stream_name, record[:external_key], record]
79
+ when :destroy
80
+ [stream_name, record[:id]]
81
+ else
82
+ [stream_name, record]
83
+ end
84
+ end
85
+
86
+ def authenticate_client
87
+ @client.authenticate!
88
+ end
89
+
90
+ def success_status
91
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
92
+ end
93
+
94
+ def failure_status(error)
95
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: error.message).to_multiwoven_message
96
+ end
97
+
98
+ def load_catalog_streams
99
+ catalog_json = read_json(CATALOG_SPEC_PATH)
100
+ catalog_json["streams"].map { |stream| build_stream(stream) }
101
+ end
102
+
103
+ def build_stream(stream)
104
+ Multiwoven::Integrations::Protocol::Stream.new(
105
+ name: stream["name"], json_schema: stream["json_schema"],
106
+ action: stream["action"]
107
+ )
108
+ end
109
+
110
+ def build_catalog(streams)
111
+ Multiwoven::Integrations::Protocol::Catalog.new(streams: streams)
112
+ end
113
+
114
+ def tracking_message(success, failure)
115
+ Multiwoven::Integrations::Protocol::TrackingMessage.new(
116
+ success: success, failed: failure
117
+ ).to_multiwoven_message
118
+ end
119
+
120
+ def log_debug(message)
121
+ Multiwoven::Integrations::Service.logger.debug(message)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end