multiwoven-integrations 0.26.1 → 0.27.1
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/core/vector_source_connector.rb +14 -0
- data/lib/multiwoven/integrations/protocol/protocol.rb +7 -1
- 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 +3 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbd0d602c8388b27dbcb21828148c5c00fcdf260213c2937e4cd501d16b9fb9e
|
4
|
+
data.tar.gz: d6520c4f7976af01f97b2f8b7e06dbee8e68e94a980aedc9e75b55694542a0ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac660b7f3b5f4c3ac888ebe912aae96f0333a0d5a894875e379ffac898f3f493b56ed1d336bafc41a9b4f804d3d0ea3677828e9f98cd294d4de4cfcbc6955c30
|
7
|
+
data.tar.gz: 9766dfbbcd62e9c665a4c32039170683a0e4fb5c33fdac4fb5de9f6296384d2664b6cff1837dae02da33c1a55482c1e998d25832b2025ae1475b5f9b67a4e3a8
|
@@ -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
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Multiwoven
|
4
|
+
module Integrations::Core
|
5
|
+
class VectorSourceConnector < SourceConnector
|
6
|
+
# This needs to be implemented
|
7
|
+
# for all vector database sources
|
8
|
+
# that will be used in RAG workflows
|
9
|
+
def search(_vector_search_config)
|
10
|
+
raise "Not implemented"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -10,7 +10,7 @@ module Multiwoven
|
|
10
10
|
SyncStatus = Types::String.enum("started", "running", "complete", "incomplete")
|
11
11
|
DestinationSyncMode = Types::String.enum("insert", "upsert")
|
12
12
|
ConnectorType = Types::String.enum("source", "destination")
|
13
|
-
ConnectorQueryType = Types::String.enum("raw_sql", "soql", "ai_ml")
|
13
|
+
ConnectorQueryType = Types::String.enum("raw_sql", "soql", "ai_ml", "vector_search")
|
14
14
|
ModelQueryType = Types::String.enum("raw_sql", "dbt", "soql", "table_selector", "ai_ml", "dynamic_sql", "unstructured", "vector_search")
|
15
15
|
ConnectionStatusType = Types::String.enum("succeeded", "failed")
|
16
16
|
StreamType = Types::String.enum("static", "dynamic", "user_defined")
|
@@ -179,6 +179,12 @@ module Multiwoven
|
|
179
179
|
attribute :sync_id, Types::String.default("unknown")
|
180
180
|
end
|
181
181
|
|
182
|
+
class VectorConfig < ProtocolModel
|
183
|
+
attribute :source, Connector
|
184
|
+
attribute :vector, Types::Array.of(Types::Float)
|
185
|
+
attribute :limit, Types::Integer.default(1)
|
186
|
+
end
|
187
|
+
|
182
188
|
class ControlMessage < ProtocolModel
|
183
189
|
attribute :type, ControlMessageType
|
184
190
|
attribute :emitted_at, Types::Integer
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Multiwoven
|
4
4
|
module Integrations
|
5
|
-
VERSION = "0.
|
5
|
+
VERSION = "0.27.1"
|
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"
|
@@ -59,6 +60,7 @@ require_relative "integrations/core/http_client"
|
|
59
60
|
require_relative "integrations/core/streaming_http_client"
|
60
61
|
require_relative "integrations/core/query_builder"
|
61
62
|
require_relative "integrations/core/unstructured_source_connector"
|
63
|
+
require_relative "integrations/core/vector_source_connector"
|
62
64
|
|
63
65
|
# Source
|
64
66
|
require_relative "integrations/source/snowflake/client"
|
@@ -83,6 +85,7 @@ require_relative "integrations/source/watsonx_data/client"
|
|
83
85
|
require_relative "integrations/source/anthropic/client"
|
84
86
|
require_relative "integrations/source/aws_bedrock_model/client"
|
85
87
|
require_relative "integrations/source/generic_open_ai/client"
|
88
|
+
require_relative "integrations/source/intuit_quick_books/client"
|
86
89
|
|
87
90
|
# Destination
|
88
91
|
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.1
|
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-
|
11
|
+
date: 2025-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -587,6 +587,7 @@ files:
|
|
587
587
|
- lib/multiwoven/integrations/core/streaming_http_client.rb
|
588
588
|
- lib/multiwoven/integrations/core/unstructured_source_connector.rb
|
589
589
|
- lib/multiwoven/integrations/core/utils.rb
|
590
|
+
- lib/multiwoven/integrations/core/vector_source_connector.rb
|
590
591
|
- lib/multiwoven/integrations/destination/airtable/client.rb
|
591
592
|
- lib/multiwoven/integrations/destination/airtable/config/catalog.json
|
592
593
|
- lib/multiwoven/integrations/destination/airtable/config/meta.json
|
@@ -765,6 +766,11 @@ files:
|
|
765
766
|
- lib/multiwoven/integrations/source/http_model/config/meta.json
|
766
767
|
- lib/multiwoven/integrations/source/http_model/config/spec.json
|
767
768
|
- lib/multiwoven/integrations/source/http_model/icon.svg
|
769
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/client.rb
|
770
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/config/catalog.json
|
771
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/config/meta.json
|
772
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/config/spec.json
|
773
|
+
- lib/multiwoven/integrations/source/intuit_quick_books/icon.svg
|
768
774
|
- lib/multiwoven/integrations/source/maria_db/client.rb
|
769
775
|
- lib/multiwoven/integrations/source/maria_db/config/meta.json
|
770
776
|
- lib/multiwoven/integrations/source/maria_db/config/spec.json
|