multiwoven-integrations 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) 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/spec.json +2 -2
  14. data/lib/multiwoven/integrations/destination/salesforce_crm/client.rb +127 -0
  15. data/lib/multiwoven/integrations/destination/salesforce_crm/config/catalog.json +317 -0
  16. data/lib/multiwoven/integrations/destination/salesforce_crm/config/meta.json +15 -0
  17. data/lib/multiwoven/integrations/destination/salesforce_crm/config/spec.json +37 -0
  18. data/lib/multiwoven/integrations/protocol/protocol.rb +2 -0
  19. data/lib/multiwoven/integrations/rollout.rb +3 -1
  20. data/lib/multiwoven/integrations/source/bigquery/client.rb +6 -0
  21. data/lib/multiwoven/integrations/source/redshift/client.rb +5 -0
  22. data/lib/multiwoven/integrations/source/snowflake/client.rb +5 -0
  23. data/lib/multiwoven/integrations.rb +4 -0
  24. data/multiwoven-integrations.gemspec +8 -6
  25. metadata +32 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e33f303a54e624644de0dbaa80056632e61c7834f0b192ba2e041e6c3d131e6
4
- data.tar.gz: dd3e29285e988665a08b6e71b1986810af068916bd2ebdd4f74a3bacab6b1385
3
+ metadata.gz: e21d943bffd7e4ad698ecfc778df04a421e79c9020808e61ef30ac90c622a8ac
4
+ data.tar.gz: 7838b443c4aa37a523369d0b6f1bf919b0b7e110cb16fb3a5fa7f72e274776ff
5
5
  SHA512:
6
- metadata.gz: f538a941eae0f2d00d754d494d70c319f4bf44444ff4463e36010dbd53e25633b114f7399edd5a1199720eb736d20f64c8e956df4b349776e173ed1f6bc537c1
7
- data.tar.gz: 56fe3d7902f215f67403d21bcb857f8d18d87f6be0082dd875489e4d60d74a753178a2436eacbc4405f7c3baa8b1fbf267bfe3ab09cc9a292137c413e5432bd3
6
+ metadata.gz: 83dc8ffa3e34b4ff1d73a0f0b09304ff57765af634b1356f85d8f374e9aaa7acb81accecbfbdbe74ea0e3c96b32cf91ae28adb8fb7885b2a0008b3f59432cd34
7
+ data.tar.gz: 2cbe6766d9729ed2c3505403b0c197c8ae8dbb3d31a107fffcc3bb474032921295724b2446be80df9798d6776d901006a524929b4ae56593c92ae72816879cec
@@ -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
+ "connector_subtype": "API",
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",
@@ -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
@@ -0,0 +1,317 @@
1
+ {
2
+ "streams": [
3
+ {
4
+ "name": "Account",
5
+ "action": "create",
6
+ "json_schema": {
7
+ "type": "object",
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "Id": {
11
+ "type": "string"
12
+ },
13
+ "IsDeleted": {
14
+ "type": "boolean"
15
+ },
16
+ "MasterRecordId": {
17
+ "type": ["string", "null"]
18
+ },
19
+ "Name": {
20
+ "type": "string"
21
+ },
22
+ "Type": {
23
+ "type": ["string", "null"],
24
+ "enum": [
25
+ "Prospect",
26
+ "Customer - Direct",
27
+ "Customer - Channel",
28
+ "Channel Partner / Reseller",
29
+ "Installation Partner",
30
+ "Technology Partner",
31
+ "Other"
32
+ ]
33
+ },
34
+ "ParentId": {
35
+ "type": ["string", "null"]
36
+ },
37
+ "BillingStreet": {
38
+ "type": ["string", "null"]
39
+ },
40
+ "BillingCity": {
41
+ "type": ["string", "null"]
42
+ },
43
+ "BillingState": {
44
+ "type": ["string", "null"]
45
+ },
46
+ "BillingPostalCode": {
47
+ "type": ["string", "null"]
48
+ },
49
+ "BillingCountry": {
50
+ "type": ["string", "null"]
51
+ },
52
+ "BillingLatitude": {
53
+ "type": ["number", "null"]
54
+ },
55
+ "BillingLongitude": {
56
+ "type": ["number", "null"]
57
+ },
58
+ "BillingGeocodeAccuracy": {
59
+ "type": ["string", "null"],
60
+ "enum": [
61
+ "Address",
62
+ "NearAddress",
63
+ "Block",
64
+ "Street",
65
+ "ExtendedZip",
66
+ "Zip",
67
+ "Neighborhood",
68
+ "City",
69
+ "County",
70
+ "State",
71
+ "Unknown"
72
+ ]
73
+ },
74
+ "BillingAddress": {
75
+ "type": ["string", "null"],
76
+ "enum": [
77
+ "Address",
78
+ "NearAddress",
79
+ "Block",
80
+ "Street",
81
+ "ExtendedZip",
82
+ "Zip",
83
+ "Neighborhood",
84
+ "City",
85
+ "County",
86
+ "State",
87
+ "Unknown"
88
+ ]
89
+ },
90
+ "ShippingStreet": {
91
+ "type": ["string", "null"]
92
+ },
93
+ "ShippingCity": {
94
+ "type": ["string", "null"]
95
+ },
96
+ "ShippingState": {
97
+ "type": ["string", "null"]
98
+ },
99
+ "ShippingPostalCode": {
100
+ "type": ["string", "null"]
101
+ },
102
+ "ShippingCountry": {
103
+ "type": ["string", "null"]
104
+ },
105
+ "ShippingLatitude": {
106
+ "type": ["number", "null"]
107
+ },
108
+ "ShippingLongitude": {
109
+ "type": ["number", "null"]
110
+ },
111
+ "ShippingGeocodeAccuracy": {
112
+ "type": ["string", "null"]
113
+ },
114
+ "ShippingAddress": {
115
+ "type": ["string", "null"]
116
+ },
117
+ "Phone": {
118
+ "type": ["string", "null"]
119
+ },
120
+ "Fax": {
121
+ "type": ["string", "null"]
122
+ },
123
+ "AccountNumber": {
124
+ "type": ["string", "null"]
125
+ },
126
+ "Website": {
127
+ "type": ["string", "null"],
128
+ "format": "uri"
129
+ },
130
+ "PhotoUrl": {
131
+ "type": ["string", "null"],
132
+ "format": "uri"
133
+ },
134
+ "Sic": {
135
+ "type": ["string", "null"]
136
+ },
137
+ "Industry": {
138
+ "type": ["string", "null"],
139
+ "enum": [
140
+ "Agriculture",
141
+ "Apparel",
142
+ "Banking",
143
+ "Biotechnology",
144
+ "Chemicals",
145
+ "Communications",
146
+ "Construction",
147
+ "Consulting",
148
+ "Education",
149
+ "Electronics",
150
+ "Energy",
151
+ "Engineering",
152
+ "Entertainment",
153
+ "Environmental",
154
+ "Finance",
155
+ "Food & Beverage",
156
+ "Government",
157
+ "Healthcare",
158
+ "Hospitality",
159
+ "Insurance",
160
+ "Machinery",
161
+ "Manufacturing",
162
+ "Media",
163
+ "Not For Profit",
164
+ "Recreation",
165
+ "Retail",
166
+ "Shipping",
167
+ "Technology",
168
+ "Telecommunications",
169
+ "Transportation",
170
+ "Utilities",
171
+ "Other"
172
+ ]
173
+ },
174
+ "AnnualRevenue": {
175
+ "type": ["number", "null"]
176
+ },
177
+ "NumberOfEmployees": {
178
+ "type": ["integer", "null"]
179
+ },
180
+ "Ownership": {
181
+ "type": ["string", "null"],
182
+ "enum": ["Public", "Private", "Subsidiary", "Other"]
183
+ },
184
+ "TickerSymbol": {
185
+ "type": ["string", "null"]
186
+ },
187
+ "Description": {
188
+ "type": ["string", "null"]
189
+ },
190
+ "Rating": {
191
+ "type": ["string", "null"],
192
+ "enum": ["Hot", "Warm", "Cold"]
193
+ },
194
+ "Site": {
195
+ "type": ["string", "null"]
196
+ },
197
+ "OwnerId": {
198
+ "type": "string"
199
+ },
200
+ "CreatedDate": {
201
+ "type": "string",
202
+ "format": "date-time"
203
+ },
204
+ "CreatedById": {
205
+ "type": "string"
206
+ },
207
+ "LastModifiedDate": {
208
+ "type": "string",
209
+ "format": "date-time"
210
+ },
211
+ "LastModifiedById": {
212
+ "type": "string"
213
+ },
214
+ "SystemModstamp": {
215
+ "type": "string",
216
+ "format": "date-time"
217
+ },
218
+ "LastActivityDate": {
219
+ "type": ["string", "null"],
220
+ "format": "date"
221
+ },
222
+ "LastViewedDate": {
223
+ "type": ["string", "null"],
224
+ "format": "date-time"
225
+ },
226
+ "LastReferencedDate": {
227
+ "type": ["string", "null"],
228
+ "format": "date-time"
229
+ },
230
+ "Jigsaw": {
231
+ "type": ["string", "null"]
232
+ },
233
+ "JigsawCompanyId": {
234
+ "type": ["string", "null"]
235
+ },
236
+ "CleanStatus": {
237
+ "type": ["string", "null"],
238
+ "enum": [
239
+ "In Sync",
240
+ "Different",
241
+ "Reviewed",
242
+ "Not Found",
243
+ "Inactive",
244
+ "Not Compared",
245
+ "Select Match",
246
+ "Skipped"
247
+ ]
248
+ },
249
+ "AccountSource": {
250
+ "type": ["string", "null"],
251
+ "enum": [
252
+ "Web",
253
+ "Phone Inquiry",
254
+ "Partner Referral",
255
+ "Purchased List",
256
+ "Other"
257
+ ]
258
+ },
259
+ "DunsNumber": {
260
+ "type": ["string", "null"]
261
+ },
262
+ "Tradestyle": {
263
+ "type": ["string", "null"]
264
+ },
265
+ "NaicsCode": {
266
+ "type": ["string", "null"]
267
+ },
268
+ "NaicsDesc": {
269
+ "type": ["string", "null"]
270
+ },
271
+ "YearStarted": {
272
+ "type": ["string", "null"]
273
+ },
274
+ "SicDesc": {
275
+ "type": ["string", "null"]
276
+ },
277
+ "DandbCompanyId": {
278
+ "type": ["string", "null"]
279
+ },
280
+ "OperatingHoursId": {
281
+ "type": ["string", "null"]
282
+ },
283
+ "CustomerPriority__c": {
284
+ "type": ["string", "null"],
285
+ "enum": ["High", "Low", "Medium"]
286
+ },
287
+ "SLA__c": {
288
+ "type": ["string", "null"],
289
+ "enum": ["Gold", "Silver", "Platinum", "Bronze"]
290
+ },
291
+ "Active__c": {
292
+ "type": ["string", "null"],
293
+ "enum": ["Yes", "No"]
294
+ },
295
+ "NumberofLocations__c": {
296
+ "type": ["number", "null"]
297
+ },
298
+ "UpsellOpportunity__c": {
299
+ "type": ["string", "null"],
300
+ "enum": ["Yes", "No", "Maybe"]
301
+ },
302
+ "SLASerialNumber__c": {
303
+ "type": ["string", "null"]
304
+ },
305
+ "SLAExpirationDate__c": {
306
+ "type": ["string", "null"],
307
+ "format": "date"
308
+ }
309
+ }
310
+ },
311
+ "supported_sync_modes": ["full_refresh", "incremental"],
312
+ "source_defined_cursor": true,
313
+ "default_cursor_field": ["updated"],
314
+ "source_defined_primary_key": [["Id"]]
315
+ }
316
+ ]
317
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "data":
3
+ {
4
+ "name": "Salesforce CRM",
5
+ "connector_type": "destination",
6
+ "connector_subtype": "API",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "destination-salesforce-crm",
9
+ "icon": "salesforce.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/destination/salesforce_crm",
3
+ "stream_type": "static",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "Salesforce Destination Spec",
7
+ "type": "object",
8
+ "required": ["access_token", "refresh_token", "instance_url", "client_id", "client_secret"],
9
+ "properties": {
10
+ "access_token": {
11
+ "type": "string",
12
+ "title": "Access Token",
13
+ "order": 0
14
+ },
15
+ "refresh_token": {
16
+ "type": "string",
17
+ "title": "Refresh Token",
18
+ "order": 1
19
+ },
20
+ "instance_url": {
21
+ "type": "string",
22
+ "title": "Instance URL",
23
+ "order": 2
24
+ },
25
+ "client_id": {
26
+ "type": "string",
27
+ "title": "Client ID",
28
+ "order": 3
29
+ },
30
+ "client_secret": {
31
+ "type": "string",
32
+ "title": "Client Secret",
33
+ "order": 4
34
+ }
35
+ }
36
+ }
37
+ }
@@ -130,6 +130,8 @@ module Multiwoven
130
130
  end
131
131
 
132
132
  class SyncConfig < ProtocolModel
133
+ attr_accessor :offset, :limit
134
+
133
135
  attribute :source, Connector
134
136
  attribute :destination, Connector
135
137
  attribute :model, Model
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Multiwoven
4
4
  module Integrations
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
 
7
7
  ENABLED_SOURCES = %w[
8
8
  Snowflake
@@ -12,6 +12,8 @@ module Multiwoven
12
12
 
13
13
  ENABLED_DESTINATIONS = %w[
14
14
  Klaviyo
15
+ SalesforceCrm
16
+ FacebookCustomAudience
15
17
  ].freeze
16
18
  end
17
19
  end
@@ -7,6 +7,7 @@ module Multiwoven::Integrations::Source
7
7
  include Multiwoven::Integrations::Core
8
8
  class Client < SourceConnector
9
9
  def check_connection(connection_config)
10
+ connection_config = connection_config.with_indifferent_access
10
11
  bigquery = create_connection(connection_config)
11
12
  bigquery.datasets
12
13
  ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
@@ -15,6 +16,7 @@ module Multiwoven::Integrations::Source
15
16
  end
16
17
 
17
18
  def discover(connection_config)
19
+ connection_config = connection_config.with_indifferent_access
18
20
  bigquery = create_connection(connection_config)
19
21
  target_dataset_id = connection_config["dataset_id"]
20
22
  records = bigquery.datasets.flat_map do |dataset|
@@ -43,7 +45,11 @@ module Multiwoven::Integrations::Source
43
45
 
44
46
  def read(sync_config)
45
47
  connection_config = sync_config.source.connection_specification
48
+ connection_config = connection_config.with_indifferent_access
46
49
  query = sync_config.model.query
50
+
51
+ query = batched_query(query, sync_config.limit, sync_config.offset) unless sync_config.limit.nil? && sync_config.offset.nil?
52
+
47
53
  bigquery = create_connection(connection_config)
48
54
  records = []
49
55
  results = bigquery.query query
@@ -7,6 +7,7 @@ module Multiwoven::Integrations::Source
7
7
  include Multiwoven::Integrations::Core
8
8
  class Client < SourceConnector
9
9
  def check_connection(connection_config)
10
+ connection_config = connection_config.with_indifferent_access
10
11
  create_connection(connection_config)
11
12
  ConnectionStatus.new(
12
13
  status: ConnectionStatusType["succeeded"]
@@ -18,6 +19,7 @@ module Multiwoven::Integrations::Source
18
19
  end
19
20
 
20
21
  def discover(connection_config)
22
+ connection_config = connection_config.with_indifferent_access
21
23
  query = "SELECT table_name, column_name, data_type, is_nullable
22
24
  FROM information_schema.columns
23
25
  WHERE table_schema = '#{connection_config[:schema]}' AND table_catalog = '#{connection_config[:database]}'
@@ -43,7 +45,10 @@ module Multiwoven::Integrations::Source
43
45
 
44
46
  def read(sync_config)
45
47
  connection_config = sync_config.source.connection_specification
48
+ connection_config = connection_config.with_indifferent_access
46
49
  query = sync_config.model.query
50
+ query = batched_query(query, sync_config.limit, sync_config.offset) unless sync_config.limit.nil? && sync_config.offset.nil?
51
+
47
52
  db = create_connection(connection_config)
48
53
  records = db.exec(query) do |result|
49
54
  result.map do |row|
@@ -5,6 +5,7 @@ module Multiwoven::Integrations::Source
5
5
  include Multiwoven::Integrations::Core
6
6
  class Client < SourceConnector
7
7
  def check_connection(connection_config)
8
+ connection_config = connection_config.with_indifferent_access
8
9
  create_connection(connection_config)
9
10
  ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
10
11
  rescue Sequel::DatabaseConnectionError => e
@@ -12,6 +13,7 @@ module Multiwoven::Integrations::Source
12
13
  end
13
14
 
14
15
  def discover(connection_config)
16
+ connection_config = connection_config.with_indifferent_access
15
17
  query = "SELECT table_name, column_name, data_type, is_nullable
16
18
  FROM information_schema.columns
17
19
  WHERE table_schema = \'#{connection_config[:schema]}\' AND table_catalog = \'#{connection_config[:database]}\'
@@ -35,7 +37,10 @@ module Multiwoven::Integrations::Source
35
37
 
36
38
  def read(sync_config)
37
39
  connection_config = sync_config.source.connection_specification
40
+ connection_config = connection_config.with_indifferent_access
38
41
  query = sync_config.model.query
42
+ query = batched_query(query, sync_config.limit, sync_config.offset) unless sync_config.limit.nil? && sync_config.offset.nil?
43
+
39
44
  db = create_connection(connection_config)
40
45
 
41
46
  records = []
@@ -10,6 +10,8 @@ require "byebug"
10
10
  require "net/http"
11
11
  require "uri"
12
12
  require "active_support/core_ext/hash/indifferent_access"
13
+ require "restforce"
14
+ require "logger"
13
15
 
14
16
  # Service
15
17
  require_relative "integrations/config"
@@ -32,6 +34,8 @@ require_relative "integrations/source/bigquery/client"
32
34
 
33
35
  # Destination
34
36
  require_relative "integrations/destination/klaviyo/client"
37
+ require_relative "integrations/destination/salesforce_crm/client"
38
+ require_relative "integrations/destination/facebook_custom_audience/client"
35
39
 
36
40
  module Multiwoven
37
41
  module Integrations
@@ -8,17 +8,18 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Subin T P"]
9
9
  spec.email = ["subin@multiwoven.com"]
10
10
 
11
- spec.summary = "Open source data activation"
12
- spec.description = "Wrapper of connectors"
11
+ spec.summary = "Integration suite for open source reverse ETL platform"
12
+ spec.description = "Multiwoven Integrations is a comprehensive Ruby gem designed to facilitate seamless connectivity between various data sources and SaaS platforms."
13
+
13
14
  spec.homepage = "https://www.multiwoven.com/"
14
15
  spec.license = "MIT"
15
16
  spec.required_ruby_version = ">= 2.6.0"
16
17
 
17
18
  # spec.metadata["allowed_push_host"] = nil
18
-
19
+ spec.metadata["github_repo"] = "https://github.com/Multiwoven/multiwoven-integrations"
19
20
  spec.metadata["homepage_uri"] = spec.homepage
20
- spec.metadata["source_code_uri"] = "https://www.multiwoven.com/"
21
- spec.metadata["changelog_uri"] = "https://www.multiwoven.com/"
21
+ spec.metadata["source_code_uri"] = "https://github.com/Multiwoven/multiwoven-integrations"
22
+ spec.metadata["changelog_uri"] = "https://github.com/Multiwoven/multiwoven-integrations/blob/master/CHANGELOG.md"
22
23
 
23
24
  # Specify which files should be added to the gem when it is released.
24
25
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -39,7 +40,8 @@ Gem::Specification.new do |spec|
39
40
  spec.add_runtime_dependency "google-cloud-bigquery"
40
41
  spec.add_runtime_dependency "pg"
41
42
  spec.add_runtime_dependency "rake"
42
- spec.add_runtime_dependency "mw-ruby-odbc"
43
+ spec.add_runtime_dependency "restforce"
44
+ spec.add_runtime_dependency "ruby-odbc"
43
45
  spec.add_runtime_dependency "sequel"
44
46
 
45
47
  spec.add_development_dependency "byebug"
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.1
4
+ version: 0.1.2
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-01-03 00:00:00.000000000 Z
11
+ date: 2024-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -109,7 +109,21 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: mw-ruby-odbc
112
+ name: restforce
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'
125
+ - !ruby/object:Gem::Dependency
126
+ name: ruby-odbc
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - ">="
@@ -220,7 +234,8 @@ dependencies:
220
234
  - - ">="
221
235
  - !ruby/object:Gem::Version
222
236
  version: '0'
223
- description: Wrapper of connectors
237
+ description: Multiwoven Integrations is a comprehensive Ruby gem designed to facilitate
238
+ seamless connectivity between various data sources and SaaS platforms.
224
239
  email:
225
240
  - subin@multiwoven.com
226
241
  executables: []
@@ -230,6 +245,7 @@ files:
230
245
  - ".rspec"
231
246
  - ".rubocop.yml"
232
247
  - ".ruby-version"
248
+ - ".vscode/settings.json"
233
249
  - CHANGELOG.md
234
250
  - CODE_OF_CONDUCT.md
235
251
  - LICENSE.txt
@@ -243,10 +259,18 @@ files:
243
259
  - lib/multiwoven/integrations/core/http_client.rb
244
260
  - lib/multiwoven/integrations/core/source_connector.rb
245
261
  - lib/multiwoven/integrations/core/utils.rb
262
+ - lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb
263
+ - lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json
264
+ - lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json
265
+ - lib/multiwoven/integrations/destination/facebook_custom_audience/config/spec.json
246
266
  - lib/multiwoven/integrations/destination/klaviyo/client.rb
247
267
  - lib/multiwoven/integrations/destination/klaviyo/config/catalog.json
248
268
  - lib/multiwoven/integrations/destination/klaviyo/config/meta.json
249
269
  - lib/multiwoven/integrations/destination/klaviyo/config/spec.json
270
+ - lib/multiwoven/integrations/destination/salesforce_crm/client.rb
271
+ - lib/multiwoven/integrations/destination/salesforce_crm/config/catalog.json
272
+ - lib/multiwoven/integrations/destination/salesforce_crm/config/meta.json
273
+ - lib/multiwoven/integrations/destination/salesforce_crm/config/spec.json
250
274
  - lib/multiwoven/integrations/protocol/protocol.json
251
275
  - lib/multiwoven/integrations/protocol/protocol.rb
252
276
  - lib/multiwoven/integrations/rollout.rb
@@ -266,9 +290,10 @@ homepage: https://www.multiwoven.com/
266
290
  licenses:
267
291
  - MIT
268
292
  metadata:
293
+ github_repo: https://github.com/Multiwoven/multiwoven-integrations
269
294
  homepage_uri: https://www.multiwoven.com/
270
- source_code_uri: https://www.multiwoven.com/
271
- changelog_uri: https://www.multiwoven.com/
295
+ source_code_uri: https://github.com/Multiwoven/multiwoven-integrations
296
+ changelog_uri: https://github.com/Multiwoven/multiwoven-integrations/blob/master/CHANGELOG.md
272
297
  post_install_message:
273
298
  rdoc_options: []
274
299
  require_paths:
@@ -287,5 +312,5 @@ requirements: []
287
312
  rubygems_version: 3.4.22
288
313
  signing_key:
289
314
  specification_version: 4
290
- summary: Open source data activation
315
+ summary: Integration suite for open source reverse ETL platform
291
316
  test_files: []