multiwoven-integrations 0.7.8 → 0.8.0

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: 5064957ae11ed9958ef813d12fb45ff06bd36f77bcb0b06409664d8564c780cb
4
- data.tar.gz: 577dc47d95729aa81e3f98994945fb1715f9da72b4437bdaa523ce8914cde4ac
3
+ metadata.gz: 0d6c095f758588db92c0232cc771651405956eca4cb6c6ddcd76bf4076347e94
4
+ data.tar.gz: e42faebd684460ed4aeebdc09520c929a68b1757410598ccc0679e0cb1474849
5
5
  SHA512:
6
- metadata.gz: 04ae67e7a763b3e951efea585b4ef96b7226219acb0bd0fdfa2afa057838a847d4617463036b866e95e830862eb440bf43e18210bb62dcc5b4340952802b8234
7
- data.tar.gz: f36f8cbc8b4b9e52c140b8da44e5ac76c5eed33c621268cd5407d0d7c4869902ae10fe4c0047020d1496d8643bcb95452b9376cbff76f79ab2db0bf6da8a2e9a
6
+ metadata.gz: 1d29ac63ac873aa8467ffa10e280353dec803ede7cc670d91233bf70e8e2fdd56b55894c1ca8beca6783b130e75cc68721a4a0e41ea4af75a8bdcaa2b008159c
7
+ data.tar.gz: 9bf9ae850bb8e2b75466e20afb9fa8ca769368af72ec7f6ffe76e8bf7469d744497694ffcf9a2464ae275b2cb1a7fd484d0b08a4017280f03b577a2ec795bb2a
@@ -63,7 +63,8 @@ module Multiwoven
63
63
  end
64
64
 
65
65
  def failure_status(error)
66
- ConnectionStatus.new(status: ConnectionStatusType["failed"], message: error.message).to_multiwoven_message
66
+ message = error&.message || "failed"
67
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: message).to_multiwoven_message
67
68
  end
68
69
  end
69
70
  end
@@ -36,6 +36,17 @@ module Multiwoven
36
36
  AIRTABLE_BASES_ENDPOINT = "https://api.airtable.com/v0/meta/bases"
37
37
  AIRTABLE_GET_BASE_SCHEMA_ENDPOINT = "https://api.airtable.com/v0/meta/bases/{baseId}/tables"
38
38
 
39
+ MS_EXCEL_AUTH_ENDPOINT = "https://graph.microsoft.com/v1.0/me"
40
+ MS_EXCEL_TABLE_ROW_WRITE_API = "https://graph.microsoft.com/v1.0/drives/%<drive_id>s/items/%<item_id>s/"\
41
+ "workbook/worksheets/%<sheet_name>s/tables/%<table_name>s/rows"
42
+ MS_EXCEL_TABLE_API = "https://graph.microsoft.com/v1.0/drives/%<drive_id>s/items/%<item_id>s/workbook/"\
43
+ "worksheets/sheet/tables?$select=name"
44
+ MS_EXCEL_FILES_API = "https://graph.microsoft.com/v1.0/drives/%<drive_id>s/root/children"
45
+ MS_EXCEL_WORKSHEETS_API = "https://graph.microsoft.com/v1.0/drives/%<drive_id>s/items/%<item_id>s/"\
46
+ "workbook/worksheets"
47
+ MS_EXCEL_SHEET_RANGE_API = "https://graph.microsoft.com/v1.0/drives/%<drive_id>s/items/%<item_id>s/"\
48
+ "workbook/worksheets/%<sheet_name>s/range(address='A1:Z1')/usedRange?$select=values"
49
+
39
50
  AWS_ACCESS_KEY_ID = ENV["AWS_ACCESS_KEY_ID"]
40
51
  AWS_SECRET_ACCESS_KEY = ENV["AWS_SECRET_ACCESS_KEY"]
41
52
 
@@ -44,6 +55,7 @@ module Multiwoven
44
55
  HTTP_POST = "POST"
45
56
  HTTP_PUT = "PUT"
46
57
  HTTP_DELETE = "DELETE"
58
+ HTTP_PATCH = "PATCH"
47
59
 
48
60
  # google sheets
49
61
  GOOGLE_SHEETS_SCOPE = "https://www.googleapis.com/auth/drive"
@@ -15,6 +15,14 @@ module Multiwoven
15
15
  success: success, failed: failure, logs: log_message_array
16
16
  ).to_multiwoven_message
17
17
  end
18
+
19
+ def auth_headers(access_token)
20
+ {
21
+ "Accept" => "application/json",
22
+ "Authorization" => "Bearer #{access_token}",
23
+ "Content-Type" => "application/json"
24
+ }
25
+ end
18
26
  end
19
27
  end
20
28
  end
@@ -19,13 +19,14 @@ module Multiwoven
19
19
  when Constants::HTTP_GET then Net::HTTP::Get
20
20
  when Constants::HTTP_POST then Net::HTTP::Post
21
21
  when Constants::HTTP_PUT then Net::HTTP::Put
22
+ when Constants::HTTP_PATCH then Net::HTTP::Patch
22
23
  when Constants::HTTP_DELETE then Net::HTTP::Delete
23
24
  else raise ArgumentError, "Unsupported HTTP method: #{method}"
24
25
  end
25
26
 
26
27
  request = request_class.new(uri)
27
28
  headers.each { |key, value| request[key] = value }
28
- request.body = payload.to_json if payload && %w[POST PUT].include?(method.upcase)
29
+ request.body = payload.to_json if payload && %w[POST PUT PATCH].include?(method.upcase)
29
30
  request
30
31
  end
31
32
  end
@@ -109,14 +109,6 @@ module Multiwoven
109
109
  }
110
110
  end
111
111
 
112
- def auth_headers(access_token)
113
- {
114
- "Accept" => "application/json",
115
- "Authorization" => "Bearer #{access_token}",
116
- "Content-Type" => "application/json"
117
- }
118
- end
119
-
120
112
  def base_id_exists?(bases, base_id)
121
113
  return if extract_bases(bases).any? { |base| base["id"] == base_id }
122
114
 
@@ -110,14 +110,6 @@ module Multiwoven::Integrations::Destination
110
110
  [schema, data]
111
111
  end
112
112
 
113
- def auth_headers(access_token)
114
- {
115
- "Accept" => "application/json",
116
- "Authorization" => "Bearer #{access_token}",
117
- "Content-Type" => "application/json"
118
- }
119
- end
120
-
121
113
  def ad_account_exists?(response, ad_account_id)
122
114
  return if extract_data(response).any? { |ad_account| ad_account["id"] == "act_#{ad_account_id}" }
123
115
 
@@ -38,6 +38,7 @@ module Multiwoven::Integrations::Destination
38
38
 
39
39
  request_method = sync_config.stream.request_method
40
40
 
41
+ log_message_array = []
41
42
  write_success = 0
42
43
  write_failure = 0
43
44
  records.each do |record|
@@ -45,6 +46,7 @@ module Multiwoven::Integrations::Destination
45
46
  # Add hardcode values into payload
46
47
  record["data"] ||= {}
47
48
  record["data"]["type"] = sync_config.stream.name
49
+ args = [request_method, url, record]
48
50
 
49
51
  response = Multiwoven::Integrations::Core::HttpClient.request(
50
52
  url,
@@ -57,6 +59,7 @@ module Multiwoven::Integrations::Destination
57
59
  else
58
60
  write_failure += 1
59
61
  end
62
+ log_message_array << log_request_response("info", args, response)
60
63
  rescue StandardError => e
61
64
  handle_exception(e, {
62
65
  context: "KLAVIYO:RECORD:WRITE:FAILURE",
@@ -65,12 +68,9 @@ module Multiwoven::Integrations::Destination
65
68
  sync_run_id: sync_config.sync_run_id
66
69
  })
67
70
  write_failure += 1
71
+ log_message_array << log_request_response("error", args, e.message)
68
72
  end
69
- tracker = Multiwoven::Integrations::Protocol::TrackingMessage.new(
70
- success: write_success,
71
- failed: write_failure
72
- )
73
- tracker.to_multiwoven_message
73
+ tracking_message(write_success, write_failure, log_message_array)
74
74
  rescue StandardError => e
75
75
  # TODO: Handle rate limiting seperately
76
76
  handle_exception(e, {
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven::Integrations::Destination
4
+ module MicrosoftExcel
5
+ include Multiwoven::Integrations::Core
6
+ class Client < DestinationConnector
7
+ prepend Multiwoven::Integrations::Core::RateLimiter
8
+ def check_connection(connection_config)
9
+ connection_config = connection_config.with_indifferent_access
10
+ drive_id = create_connection(connection_config)
11
+ if drive_id
12
+ success_status
13
+ else
14
+ failure_status(nil)
15
+ end
16
+ rescue StandardError => e
17
+ handle_exception(e, {
18
+ context: "MICROSOFT:EXCEL:CHECK_CONNECTION:EXCEPTION",
19
+ type: "error"
20
+ })
21
+ failure_status(e)
22
+ end
23
+
24
+ def discover(connection_config)
25
+ catalog_json = read_json(CATALOG_SPEC_PATH)
26
+ connection_config = connection_config.with_indifferent_access
27
+ token = connection_config[:token]
28
+ drive_id = create_connection(connection_config)
29
+ records = get_file(token, drive_id)
30
+ records.each do |record|
31
+ file_id = record[:id]
32
+ record[:worksheets] = get_file_data(token, drive_id, file_id)
33
+ end
34
+ catalog = Catalog.new(streams: create_streams(records, catalog_json))
35
+ catalog.to_multiwoven_message
36
+ rescue StandardError => e
37
+ handle_exception(e, {
38
+ context: "MICROSOFT:EXCEL:DISCOVER:EXCEPTION",
39
+ type: "error"
40
+ })
41
+ end
42
+
43
+ def write(sync_config, records, _action = "destination_insert")
44
+ connection_config = sync_config.destination.connection_specification.with_indifferent_access
45
+ token = connection_config[:token]
46
+ file_name = sync_config.stream.name.split(", ").first
47
+ sheet_name = sync_config.stream.name.split(", ").last
48
+ drive_id = create_connection(connection_config)
49
+ excel_files = get_file(token, drive_id)
50
+ worksheet = excel_files.find { |file| file[:name] == file_name }
51
+ item_id = worksheet[:id]
52
+
53
+ table = get_table(token, drive_id, item_id)
54
+ write_url = format(MS_EXCEL_TABLE_ROW_WRITE_API, drive_id: drive_id, item_id: item_id, sheet_name: sheet_name,
55
+ table_name: table["name"])
56
+ payload = { values: records.map(&:values) }
57
+ process_write_request(write_url, payload, token, sync_config)
58
+ end
59
+
60
+ private
61
+
62
+ def create_connection(connection_config)
63
+ token = connection_config[:token]
64
+ response = Multiwoven::Integrations::Core::HttpClient.request(
65
+ MS_EXCEL_AUTH_ENDPOINT,
66
+ HTTP_GET,
67
+ headers: auth_headers(token)
68
+ )
69
+ JSON.parse(response.body)["id"]
70
+ end
71
+
72
+ def get_table(token, drive_id, item_id)
73
+ table_url = format(MS_EXCEL_TABLE_API, drive_id: drive_id, item_id: item_id)
74
+ response = Multiwoven::Integrations::Core::HttpClient.request(
75
+ table_url,
76
+ HTTP_GET,
77
+ headers: auth_headers(token)
78
+ )
79
+ JSON.parse(response.body)["value"].first
80
+ end
81
+
82
+ def get_file(token, drive_id)
83
+ url = format(MS_EXCEL_FILES_API, drive_id: drive_id)
84
+ response = Multiwoven::Integrations::Core::HttpClient.request(
85
+ url,
86
+ HTTP_GET,
87
+ headers: auth_headers(token)
88
+ )
89
+ files = JSON.parse(response.body)["value"]
90
+ excel_files = files.select { |file| file["name"].match(/\.(xlsx|xls|xlsm)$/) }
91
+ excel_files.map { |file| { name: file["name"], id: file["id"] } }
92
+ end
93
+
94
+ def get_all_sheets(token, drive_id, item_id)
95
+ base_url = format(MS_EXCEL_WORKSHEETS_API, drive_id: drive_id, item_id: item_id)
96
+ worksheet_response = Multiwoven::Integrations::Core::HttpClient.request(
97
+ base_url,
98
+ HTTP_GET,
99
+ headers: auth_headers(token)
100
+ )
101
+ JSON.parse(worksheet_response.body)["value"]
102
+ end
103
+
104
+ def get_file_data(token, drive_id, item_id)
105
+ result = []
106
+ worksheets_data = get_all_sheets(token, drive_id, item_id)
107
+ worksheets_data.each do |sheet|
108
+ sheet_name = sheet["name"]
109
+ sheet_url = format(MS_EXCEL_SHEET_RANGE_API, drive_id: drive_id, item_id: item_id, sheet_name: sheet_name)
110
+
111
+ sheet_response = Multiwoven::Integrations::Core::HttpClient.request(
112
+ sheet_url,
113
+ HTTP_GET,
114
+ headers: auth_headers(token)
115
+ )
116
+ sheets_data = JSON.parse(sheet_response.body)
117
+ result << {
118
+ sheet_name: sheet_name,
119
+ column_names: sheets_data["values"].first
120
+ }
121
+ end
122
+ result
123
+ end
124
+
125
+ def create_streams(records, catalog_json)
126
+ group_by_table(records).flat_map do |_, record|
127
+ record.map do |_, r|
128
+ Multiwoven::Integrations::Protocol::Stream.new(
129
+ name: r[:workbook],
130
+ action: StreamAction["fetch"],
131
+ json_schema: convert_to_json_schema(r[:columns]),
132
+ request_rate_limit: catalog_json["request_rate_limit"] || 60,
133
+ request_rate_limit_unit: catalog_json["request_rate_limit_unit"] || "minute",
134
+ request_rate_concurrency: catalog_json["request_rate_concurrency"] || 1
135
+ )
136
+ end
137
+ end
138
+ end
139
+
140
+ def group_by_table(records)
141
+ result = {}
142
+
143
+ records.each_with_index do |entries, entries_index|
144
+ entries[:worksheets].each_with_index do |sheet, entry_index|
145
+ workbook_sheet = "#{entries[:name]}, #{sheet[:sheet_name]}"
146
+ columns = sheet[:column_names].map do |column_name|
147
+ column_name = "empty column" if column_name.empty?
148
+ {
149
+ column_name: column_name,
150
+ data_type: "String",
151
+ is_nullable: true
152
+ }
153
+ end
154
+ result[entries_index] ||= {}
155
+ result[entries_index][entry_index] = { workbook: workbook_sheet, columns: columns }
156
+ end
157
+ end
158
+ result
159
+ end
160
+
161
+ def process_write_request(write_url, payload, token, sync_config)
162
+ write_success = 0
163
+ write_failure = 0
164
+ log_message_array = []
165
+
166
+ begin
167
+ response = Multiwoven::Integrations::Core::HttpClient.request(
168
+ write_url,
169
+ HTTP_POST,
170
+ payload: payload,
171
+ headers: auth_headers(token)
172
+ )
173
+ if success?(response)
174
+ write_success += 1
175
+ else
176
+ write_failure += 1
177
+ end
178
+ log_message_array << log_request_response("info", [HTTP_POST, write_url, payload], response)
179
+ rescue StandardError => e
180
+ handle_exception(e, {
181
+ context: "MICROSOFT:EXCEL:RECORD:WRITE:EXCEPTION",
182
+ type: "error",
183
+ sync_id: sync_config.sync_id,
184
+ sync_run_id: sync_config.sync_run_id
185
+ })
186
+ write_failure += 1
187
+ log_message_array << log_request_response("error", [HTTP_POST, write_url, payload], e.message)
188
+ end
189
+
190
+ tracking_message(write_success, write_failure, log_message_array)
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,7 @@
1
+ {
2
+ "request_rate_limit": 6000,
3
+ "request_rate_limit_unit": "minute",
4
+ "request_rate_concurrency": 10,
5
+ "streams": []
6
+ }
7
+
@@ -0,0 +1,15 @@
1
+ {
2
+ "data": {
3
+ "name": "MicrosoftExcel",
4
+ "title": "Microsoft Excel",
5
+ "connector_type": "destination",
6
+ "category": "Database",
7
+ "documentation_url": "https://docs.squared.ai/guides/data-integration/destination/microsoft_excel",
8
+ "github_issue_label": "destination-microsoft-excel",
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,19 @@
1
+ {
2
+ "documentation_url": "https://docs.squared.ai/guides/data-integration/destination/microsoft_excel",
3
+ "stream_type": "dynamic",
4
+ "connector_query_type": "raw_sql",
5
+ "connection_specification": {
6
+ "$schema": "http://json-schema.org/draft-07/schema#",
7
+ "title": "Microsoft Excel",
8
+ "type": "object",
9
+ "required": ["token"],
10
+ "properties": {
11
+ "token": {
12
+ "description": "Token from Microsoft Graph.",
13
+ "type": "string",
14
+ "title": "Token",
15
+ "order": 0
16
+ }
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,18 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="800" width="1200" viewBox="-343.4625 -532.5 2976.675 3195">
2
+ <path d="M1437.75 1011.75L532.5 852v1180.393c0 53.907 43.7 97.607 97.607 97.607h1562.036c53.907 0 97.607-43.7 97.607-97.607V1597.5z" fill="#185C37"/>
3
+ <path d="M1437.75 0H630.107C576.2 0 532.5 43.7 532.5 97.607V532.5l905.25 532.5L1917 1224.75 2289.75 1065V532.5z" fill="#21A366"/>
4
+ <path d="M532.5 532.5h905.25V1065H532.5z" fill="#107C41"/>
5
+ <path d="M1180.393 426H532.5v1331.25h647.893c53.834-.175 97.432-43.773 97.607-97.607V523.607c-.175-53.834-43.773-97.432-97.607-97.607z" opacity=".1"/>
6
+ <path d="M1127.143 479.25H532.5V1810.5h594.643c53.834-.175 97.432-43.773 97.607-97.607V576.857c-.175-53.834-43.773-97.432-97.607-97.607z" opacity=".2"/>
7
+ <path d="M1127.143 479.25H532.5V1704h594.643c53.834-.175 97.432-43.773 97.607-97.607V576.857c-.175-53.834-43.773-97.432-97.607-97.607z" opacity=".2"/>
8
+ <path d="M1073.893 479.25H532.5V1704h541.393c53.834-.175 97.432-43.773 97.607-97.607V576.857c-.175-53.834-43.773-97.432-97.607-97.607z" opacity=".2"/>
9
+ <linearGradient gradientTransform="matrix(1 0 0 -1 0 2132)" y2="404.982" x2="967.987" y1="1729.018" x1="203.513" gradientUnits="userSpaceOnUse" id="a">
10
+ <stop offset="0" stop-color="#18884f"/>
11
+ <stop offset=".5" stop-color="#117e43"/>
12
+ <stop offset="1" stop-color="#0b6631"/>
13
+ </linearGradient>
14
+ <path d="M97.607 479.25h976.285c53.907 0 97.607 43.7 97.607 97.607v976.285c0 53.907-43.7 97.607-97.607 97.607H97.607C43.7 1650.75 0 1607.05 0 1553.143V576.857c0-53.907 43.7-97.607 97.607-97.607z" fill="url(#a)"/>
15
+ <path d="M302.3 1382.264l205.332-318.169L319.5 747.683h151.336l102.666 202.35c9.479 19.223 15.975 33.494 19.49 42.919h1.331a798.667 798.667 0 0121.3-44.677L725.371 747.79H864.3l-192.925 314.548L869.2 1382.263H721.378L602.79 1160.158a186.298 186.298 0 01-14.164-29.66h-1.757a140.458 140.458 0 01-13.739 28.755l-122.102 223.011z" fill="#FFF"/>
16
+ <path d="M2192.143 0H1437.75v532.5h852V97.607C2289.75 43.7 2246.05 0 2192.143 0z" fill="#33C481"/>
17
+ <path d="M1437.75 1065h852v532.5h-852z" fill="#107C41"/>
18
+ </svg>
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Multiwoven
4
4
  module Integrations
5
- VERSION = "0.7.8"
5
+ VERSION = "0.8.0"
6
6
 
7
7
  ENABLED_SOURCES = %w[
8
8
  Snowflake
@@ -36,6 +36,7 @@ module Multiwoven
36
36
  MariaDB
37
37
  DatabricksLakehouse
38
38
  Oracle
39
+ MicrosoftExcel
39
40
  ].freeze
40
41
  end
41
42
  end
@@ -81,6 +81,7 @@ require_relative "integrations/destination/iterable/client"
81
81
  require_relative "integrations/destination/maria_db/client"
82
82
  require_relative "integrations/destination/databricks_lakehouse/client"
83
83
  require_relative "integrations/destination/oracle_db/client"
84
+ require_relative "integrations/destination/microsoft_excel/client"
84
85
 
85
86
  module Multiwoven
86
87
  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.7.8
4
+ version: 0.8.0
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-08-09 00:00:00.000000000 Z
11
+ date: 2024-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -544,6 +544,11 @@ files:
544
544
  - lib/multiwoven/integrations/destination/maria_db/config/meta.json
545
545
  - lib/multiwoven/integrations/destination/maria_db/config/spec.json
546
546
  - lib/multiwoven/integrations/destination/maria_db/icon.svg
547
+ - lib/multiwoven/integrations/destination/microsoft_excel/client.rb
548
+ - lib/multiwoven/integrations/destination/microsoft_excel/config/catalog.json
549
+ - lib/multiwoven/integrations/destination/microsoft_excel/config/meta.json
550
+ - lib/multiwoven/integrations/destination/microsoft_excel/config/spec.json
551
+ - lib/multiwoven/integrations/destination/microsoft_excel/icon.svg
547
552
  - lib/multiwoven/integrations/destination/oracle_db/client.rb
548
553
  - lib/multiwoven/integrations/destination/oracle_db/config/meta.json
549
554
  - lib/multiwoven/integrations/destination/oracle_db/config/spec.json