multiwoven-integrations 0.26.1 → 0.27.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 +4 -4
- data/lib/multiwoven/integrations/core/constants.rb +5 -0
- data/lib/multiwoven/integrations/rollout.rb +2 -1
- data/lib/multiwoven/integrations/source/intuit_quick_books/client.rb +215 -0
- data/lib/multiwoven/integrations/source/intuit_quick_books/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/source/intuit_quick_books/config/meta.json +16 -0
- data/lib/multiwoven/integrations/source/intuit_quick_books/config/spec.json +44 -0
- data/lib/multiwoven/integrations/source/intuit_quick_books/icon.svg +1 -0
- data/lib/multiwoven/integrations.rb +2 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc21bbb426fb6d928bd910e3ce1d02b8dfa64c8b81d2f229cee83144a6c2fa2b
|
4
|
+
data.tar.gz: b6568f1feb3ad15f14c2092ac4299838d680d0b275c0ba7ea06327d566dec12e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34a9f069e6164ea242ea583a9456b3f84cf77cb0ded8ebae69df728277a6b5d985ed475b490cc7233744167e2a5e098e69d3b8768131e0dc8c96f75305ca684e
|
7
|
+
data.tar.gz: 942d74b686bdb030165662d7b76317842ecd2e35e16494fd28480a37f1fc2073670b5a1f14720e424dd1ac98672cf76a3e5686f03d1c23fbe570f69bd40e6601
|
@@ -83,6 +83,11 @@ module Multiwoven
|
|
83
83
|
mistral.mixtral-8x7b-instruct-v0:1
|
84
84
|
mistral.mistral-small-2402-v1:0
|
85
85
|
].freeze
|
86
|
+
|
87
|
+
# Intuit QuickBooks
|
88
|
+
QUICKBOOKS_SANDBOX_QUERY_URL = "https://sandbox-quickbooks.api.intuit.com/v3/company/%<realm_id>s/query?query=%<query>s"
|
89
|
+
QUICKBOOKS_PRODUCTION_QUERY_URL = "https://quickbooks.api.intuit.com/v3/company/%<realm_id>s/query?query=%<query>s"
|
90
|
+
QUICKBOOKS_REDIRECT_URL = "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl"
|
86
91
|
end
|
87
92
|
end
|
88
93
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Multiwoven
|
4
4
|
module Integrations
|
5
|
-
VERSION = "0.
|
5
|
+
VERSION = "0.27.0"
|
6
6
|
|
7
7
|
ENABLED_SOURCES = %w[
|
8
8
|
Snowflake
|
@@ -27,6 +27,7 @@ module Multiwoven
|
|
27
27
|
Anthropic
|
28
28
|
AwsBedrockModel
|
29
29
|
GenericOpenAI
|
30
|
+
IntuitQuickBooks
|
30
31
|
].freeze
|
31
32
|
|
32
33
|
ENABLED_DESTINATIONS = %w[
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Multiwoven::Integrations::Source
|
4
|
+
module IntuitQuickBooks
|
5
|
+
include Multiwoven::Integrations::Core
|
6
|
+
|
7
|
+
QUICKBOOKS_OBJECTS = %w[Account Customer Employee Invoice TimeActivity].freeze
|
8
|
+
MAX_PER_PAGE = 1000
|
9
|
+
|
10
|
+
class Client < SourceConnector
|
11
|
+
def check_connection(connection_config)
|
12
|
+
connection_config = connection_config.with_indifferent_access
|
13
|
+
access_token = create_connection(connection_config)
|
14
|
+
query = "SELECT * FROM Customer STARTPOSITION 1 MAXRESULTS 1"
|
15
|
+
response = query_quickbooks(access_token, query)
|
16
|
+
if success?(response)
|
17
|
+
success_status
|
18
|
+
else
|
19
|
+
failure_status(nil)
|
20
|
+
end
|
21
|
+
rescue StandardError => e
|
22
|
+
handle_exception(e, { context: "INTUIT_QUICKBOOKS:CHECK_CONNECTION:EXCEPTION", type: "error" })
|
23
|
+
failure_status(e)
|
24
|
+
end
|
25
|
+
|
26
|
+
def discover(connection_config)
|
27
|
+
connection_config = connection_config.with_indifferent_access
|
28
|
+
access_token = create_connection(connection_config)
|
29
|
+
catalog = build_catalog(load_catalog.with_indifferent_access)
|
30
|
+
streams = catalog[:streams]
|
31
|
+
QUICKBOOKS_OBJECTS.each do |object|
|
32
|
+
query = "SELECT * FROM #{object}"
|
33
|
+
response = query_quickbooks(access_token, query)
|
34
|
+
streams << create_streams(JSON.parse(response.body)["QueryResponse"])[0]
|
35
|
+
rescue StandardError => e
|
36
|
+
handle_exception(e, { context: "INTUIT_QUICKBOOKS:DISCOVER:LOOP_EXCEPTION", type: "error" })
|
37
|
+
next
|
38
|
+
end
|
39
|
+
catalog.to_multiwoven_message
|
40
|
+
rescue StandardError => e
|
41
|
+
handle_exception(e, { context: "INTUIT_QUICKBOOKS:DISCOVER:EXCEPTION", type: "error" })
|
42
|
+
end
|
43
|
+
|
44
|
+
def read(sync_config)
|
45
|
+
connection_config = sync_config.source.connection_specification
|
46
|
+
connection_config = connection_config.with_indifferent_access
|
47
|
+
query = sync_config.model.query
|
48
|
+
query = batched_query(query, sync_config.limit, sync_config.offset) unless sync_config.limit.nil? && sync_config.offset.nil?
|
49
|
+
access_token = create_connection(connection_config)
|
50
|
+
query(access_token, query)
|
51
|
+
rescue StandardError => e
|
52
|
+
handle_exception(e, {
|
53
|
+
context: "INTUIT_QUICKBOOKS:READ:EXCEPTION",
|
54
|
+
type: "error",
|
55
|
+
sync_id: sync_config.sync_id,
|
56
|
+
sync_run_id: sync_config.sync_run_id
|
57
|
+
})
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def query(access_token, query)
|
63
|
+
parsed = batched_query_for_quickbooks(query)
|
64
|
+
base_query = parsed[:base_query]
|
65
|
+
limit = parsed[:limit]
|
66
|
+
offset = parsed[:offset]
|
67
|
+
execute_query(access_token, base_query, limit, offset).map do |r|
|
68
|
+
flat_data = flatten_hash(r)
|
69
|
+
RecordMessage.new(data: flat_data, emitted_at: Time.now.to_i).to_multiwoven_message
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def execute_query(access_token, base_query, limit, offset)
|
74
|
+
total_fetched = 0
|
75
|
+
current_offset = offset
|
76
|
+
result = []
|
77
|
+
|
78
|
+
while total_fetched < limit
|
79
|
+
batch_limit = [MAX_PER_PAGE, limit - total_fetched].min
|
80
|
+
paginated_query = "#{base_query} STARTPOSITION #{current_offset + 1} MAXRESULTS #{batch_limit}"
|
81
|
+
|
82
|
+
response = query_quickbooks(access_token, paginated_query)
|
83
|
+
records = JSON.parse(response.body)["QueryResponse"] || {}
|
84
|
+
|
85
|
+
break if records.empty?
|
86
|
+
|
87
|
+
records.each_value do |rows|
|
88
|
+
next unless rows.is_a?(Array)
|
89
|
+
|
90
|
+
rows.each do |row|
|
91
|
+
result << row
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
fetched_count = result.size - total_fetched
|
96
|
+
break if fetched_count < batch_limit
|
97
|
+
|
98
|
+
total_fetched += fetched_count
|
99
|
+
current_offset += fetched_count
|
100
|
+
end
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
def query_quickbooks(access_token, query)
|
105
|
+
encoded_query = URI.encode_www_form_component(query)
|
106
|
+
query_url = @environment == "sandbox" ? QUICKBOOKS_SANDBOX_QUERY_URL : QUICKBOOKS_PRODUCTION_QUERY_URL
|
107
|
+
send_request(
|
108
|
+
url: build_url(query_url, encoded_query),
|
109
|
+
http_method: HTTP_GET,
|
110
|
+
payload: {},
|
111
|
+
headers: auth_headers(access_token),
|
112
|
+
config: {}
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_connection(connection_config)
|
117
|
+
cache = defined?(Rails) && Rails.respond_to?(:cache) ? Rails.cache : ActiveSupport::Cache::MemoryStore.new
|
118
|
+
load_connection_config(connection_config)
|
119
|
+
get_access_token(cache)
|
120
|
+
end
|
121
|
+
|
122
|
+
def load_connection_config(connection_config)
|
123
|
+
@client_id = connection_config[:client_id]
|
124
|
+
@client_secret = connection_config[:client_secret]
|
125
|
+
@realm_id = connection_config[:realm_id]
|
126
|
+
@environment = connection_config[:environment]
|
127
|
+
@refresh_token = connection_config[:refresh_token]
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_access_token(cache)
|
131
|
+
cache_key = "intuit_quickbooks_#{@client_id}_#{@client_secret}_#{@realm_id}}"
|
132
|
+
cached_token = cache.read(cache_key)
|
133
|
+
if cached_token
|
134
|
+
cached_token
|
135
|
+
else
|
136
|
+
new_token = refresh_access_token
|
137
|
+
# max expiration is 3 minutes. No way to make it higher
|
138
|
+
cache.write(cache_key, new_token, expires_in: 180)
|
139
|
+
new_token
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def refresh_access_token
|
144
|
+
oauth2_client = IntuitOAuth::Client.new(@client_id, @client_secret, QUICKBOOKS_REDIRECT_URL, @environment)
|
145
|
+
oauth2_client.token.refresh_tokens(@refresh_token).access_token
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_streams(records)
|
149
|
+
group_by_table(records).map do |r|
|
150
|
+
Multiwoven::Integrations::Protocol::Stream.new(name: r[:table_name], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def group_by_table(records)
|
155
|
+
records.filter_map do |table_name, rows|
|
156
|
+
if rows.is_a?(Array) && rows.all? { |row| row.is_a?(Hash) }
|
157
|
+
row_sample = rows.first || {}
|
158
|
+
columns = row_sample.map do |key, value|
|
159
|
+
{
|
160
|
+
column_name: key,
|
161
|
+
data_type: normalize_type(value),
|
162
|
+
is_nullable: rows.any? { |row| row[key].nil? }
|
163
|
+
}
|
164
|
+
end
|
165
|
+
{ table_name: table_name, columns: columns }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def batched_query_for_quickbooks(query)
|
171
|
+
query = query.strip.chomp(";")
|
172
|
+
limit = query[/LIMIT\s+(\d+)/i, 1] || 1000
|
173
|
+
offset = query[/OFFSET\s+(\d+)/i, 1]
|
174
|
+
|
175
|
+
base_query = query.gsub(/LIMIT\s+\d+/i, "").gsub(/OFFSET\s+\d+/i, "").strip
|
176
|
+
{
|
177
|
+
base_query: base_query,
|
178
|
+
limit: limit.to_i,
|
179
|
+
offset: offset.to_i
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
def flatten_hash(hash, parent_key = "", result = {})
|
184
|
+
hash.each do |key, value|
|
185
|
+
full_key = parent_key.empty? ? key.to_s : "#{parent_key}.#{key}"
|
186
|
+
|
187
|
+
case value
|
188
|
+
when Hash
|
189
|
+
flatten_hash(value, full_key, result)
|
190
|
+
when Array
|
191
|
+
next
|
192
|
+
else
|
193
|
+
result[full_key] = value.is_a?(Integer) || value.is_a?(Float) ? value : value.to_s
|
194
|
+
end
|
195
|
+
end
|
196
|
+
result
|
197
|
+
end
|
198
|
+
|
199
|
+
def normalize_type(value)
|
200
|
+
case value
|
201
|
+
when Integer, Float then "NUMBER"
|
202
|
+
else "string"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def load_catalog
|
207
|
+
read_json(CATALOG_SPEC_PATH)
|
208
|
+
end
|
209
|
+
|
210
|
+
def build_url(url, query)
|
211
|
+
format(url, realm_id: @realm_id, query: query)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
{
|
2
|
+
"data": {
|
3
|
+
"name": "IntuitQuickBooks",
|
4
|
+
"title": "Intuit QuickBooks",
|
5
|
+
"connector_type": "source",
|
6
|
+
"category": "Data Warehouse",
|
7
|
+
"documentation_url": "https://docs.squared.ai/guides/sources/data-sources/intuit_quickbooks",
|
8
|
+
"github_issue_label": "source-intuit-quickbooks",
|
9
|
+
"icon": "icon.svg",
|
10
|
+
"license": "MIT",
|
11
|
+
"release_stage": "alpha",
|
12
|
+
"support_level": "community",
|
13
|
+
"tags": ["language:ruby", "multiwoven"]
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
{
|
2
|
+
"documentation_url": "https://docs.squared.ai/guides/sources/data-sources/intuit_quickbooks",
|
3
|
+
"stream_type": "dynamic",
|
4
|
+
"connector_query_type": "raw_sql",
|
5
|
+
"connection_specification": {
|
6
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
7
|
+
"title": "Intuit QuickBooks",
|
8
|
+
"type": "object",
|
9
|
+
"required": ["environment","client_id","client_secret","realm_id","refresh_token"],
|
10
|
+
"properties": {
|
11
|
+
"environment": {
|
12
|
+
"type": "string",
|
13
|
+
"title": "Environment",
|
14
|
+
"enum": ["sandbox", "production"],
|
15
|
+
"order": 0
|
16
|
+
},
|
17
|
+
"client_id": {
|
18
|
+
"type": "string",
|
19
|
+
"multiwoven_secret": true,
|
20
|
+
"title": "Client Id",
|
21
|
+
"order": 1
|
22
|
+
},
|
23
|
+
"client_secret": {
|
24
|
+
"type": "string",
|
25
|
+
"multiwoven_secret": true,
|
26
|
+
"title": "Client Secret",
|
27
|
+
"order": 2
|
28
|
+
},
|
29
|
+
"realm_id": {
|
30
|
+
"type": "string",
|
31
|
+
"multiwoven_secret": true,
|
32
|
+
"title": "Realm Id",
|
33
|
+
"order": 3
|
34
|
+
},
|
35
|
+
"refresh_token": {
|
36
|
+
"type": "string",
|
37
|
+
"multiwoven_secret": true,
|
38
|
+
"title": "Refresh Token",
|
39
|
+
"order": 4
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg enable-background="new 0 0 2500 2500" viewBox="0 0 2500 2500" xmlns="http://www.w3.org/2000/svg"><circle cx="1250" cy="1250" fill="#2ca01c" r="1250"/><path d="m301.3 1249.6c.1 282.6 228 512.4 510.6 514.9h72.3v-188.9h-72.3c-175.2 47.8-355.9-55.5-403.6-230.7-.4-1.4-.7-2.8-1.1-4.2-49.1-177.5 53.7-361.4 230.6-412.5h36.1c45.3-9.9 92.2-9.9 137.5 0h175.6v1002.9c-.9 106.1 84.4 192.9 190.5 193.9v-1395.4h-364.5c-284.6 1.5-514 233.4-512.5 518v.1zm1387.5-519.8h-72.3v198.9h72.3c174.8-47.7 355.1 55.3 402.8 230 .4 1.3.7 2.7 1.1 4 48.8 176.9-53.7 360.1-229.9 411.1h-36.1c-45.3 9.9-92.2 9.9-137.5 0h-175.6v-1002.8c.9-106.1-84.4-192.9-190.5-193.9v1397.4h364.5c287.1-4.5 516.2-240.8 511.8-527.9-4.4-280.8-230.9-507.4-511.8-511.8z" fill="#fff"/></svg>
|
@@ -39,6 +39,7 @@ require "grpc"
|
|
39
39
|
require "MailchimpMarketing"
|
40
40
|
require "aws-sdk-bedrockruntime"
|
41
41
|
require "pinecone"
|
42
|
+
require "intuit-oauth"
|
42
43
|
|
43
44
|
# Service
|
44
45
|
require_relative "integrations/config"
|
@@ -83,6 +84,7 @@ require_relative "integrations/source/watsonx_data/client"
|
|
83
84
|
require_relative "integrations/source/anthropic/client"
|
84
85
|
require_relative "integrations/source/aws_bedrock_model/client"
|
85
86
|
require_relative "integrations/source/generic_open_ai/client"
|
87
|
+
require_relative "integrations/source/intuit_quick_books/client"
|
86
88
|
|
87
89
|
# Destination
|
88
90
|
require_relative "integrations/destination/klaviyo/client"
|
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.
|
4
|
+
version: 0.27.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: 2025-05-
|
11
|
+
date: 2025-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -765,6 +765,11 @@ files:
|
|
765
765
|
- lib/multiwoven/integrations/source/http_model/config/meta.json
|
766
766
|
- lib/multiwoven/integrations/source/http_model/config/spec.json
|
767
767
|
- lib/multiwoven/integrations/source/http_model/icon.svg
|
768
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/client.rb
|
769
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/config/catalog.json
|
770
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/config/meta.json
|
771
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/config/spec.json
|
772
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/icon.svg
|
768
773
|
- lib/multiwoven/integrations/source/maria_db/client.rb
|
769
774
|
- lib/multiwoven/integrations/source/maria_db/config/meta.json
|
770
775
|
- lib/multiwoven/integrations/source/maria_db/config/spec.json
|