etl-integrations 0.1.81
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +5 -0
- data/CHANGELOG.md +38 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +105 -0
- data/Rakefile +12 -0
- data/lib/multiwoven/integrations/config.rb +13 -0
- data/lib/multiwoven/integrations/core/base_connector.rb +70 -0
- data/lib/multiwoven/integrations/core/constants.rb +46 -0
- data/lib/multiwoven/integrations/core/destination_connector.rb +14 -0
- data/lib/multiwoven/integrations/core/fullrefresher.rb +19 -0
- data/lib/multiwoven/integrations/core/http_client.rb +34 -0
- data/lib/multiwoven/integrations/core/query_builder.rb +27 -0
- data/lib/multiwoven/integrations/core/rate_limiter.rb +19 -0
- data/lib/multiwoven/integrations/core/source_connector.rb +38 -0
- data/lib/multiwoven/integrations/core/utils.rb +104 -0
- data/lib/multiwoven/integrations/destination/airtable/client.rb +153 -0
- data/lib/multiwoven/integrations/destination/airtable/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/destination/airtable/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/airtable/config/spec.json +22 -0
- data/lib/multiwoven/integrations/destination/airtable/icon.svg +6 -0
- data/lib/multiwoven/integrations/destination/airtable/schema_helper.rb +141 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb +124 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json +42 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/spec.json +27 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/icon.svg +23 -0
- data/lib/multiwoven/integrations/destination/google_sheets/client.rb +231 -0
- data/lib/multiwoven/integrations/destination/google_sheets/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/destination/google_sheets/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/google_sheets/config/spec.json +74 -0
- data/lib/multiwoven/integrations/destination/google_sheets/icon.svg +1 -0
- data/lib/multiwoven/integrations/destination/hubspot/client.rb +107 -0
- data/lib/multiwoven/integrations/destination/hubspot/config/catalog.json +351 -0
- data/lib/multiwoven/integrations/destination/hubspot/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/hubspot/config/spec.json +17 -0
- data/lib/multiwoven/integrations/destination/hubspot/icon.svg +5 -0
- data/lib/multiwoven/integrations/destination/klaviyo/client.rb +116 -0
- data/lib/multiwoven/integrations/destination/klaviyo/config/catalog.json +103 -0
- data/lib/multiwoven/integrations/destination/klaviyo/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/klaviyo/config/spec.json +22 -0
- data/lib/multiwoven/integrations/destination/klaviyo/icon.svg +6 -0
- data/lib/multiwoven/integrations/destination/postgresql/client.rb +123 -0
- data/lib/multiwoven/integrations/destination/postgresql/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/postgresql/config/spec.json +68 -0
- data/lib/multiwoven/integrations/destination/postgresql/icon.svg +20 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/client.rb +114 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/meta.json +16 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/spec.json +49 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/icon.svg +16 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/schema_helper.rb +132 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/client.rb +117 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/config/catalog.json +320 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/config/spec.json +43 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/icon.svg +16 -0
- data/lib/multiwoven/integrations/destination/sftp/client.rb +133 -0
- data/lib/multiwoven/integrations/destination/sftp/config/catalog.json +16 -0
- data/lib/multiwoven/integrations/destination/sftp/config/meta.json +16 -0
- data/lib/multiwoven/integrations/destination/sftp/config/spec.json +50 -0
- data/lib/multiwoven/integrations/destination/sftp/icon.svg +1 -0
- data/lib/multiwoven/integrations/destination/slack/client.rb +114 -0
- data/lib/multiwoven/integrations/destination/slack/config/catalog.json +22 -0
- data/lib/multiwoven/integrations/destination/slack/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/slack/config/spec.json +22 -0
- data/lib/multiwoven/integrations/destination/slack/icon.svg +26 -0
- data/lib/multiwoven/integrations/destination/stripe/client.rb +82 -0
- data/lib/multiwoven/integrations/destination/stripe/config/catalog.json +128 -0
- data/lib/multiwoven/integrations/destination/stripe/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/stripe/config/spec.json +17 -0
- data/lib/multiwoven/integrations/destination/stripe/icon.svg +10 -0
- data/lib/multiwoven/integrations/destination/tally/client.rb +151 -0
- data/lib/multiwoven/integrations/destination/tally/config/catalog.json +62 -0
- data/lib/multiwoven/integrations/destination/tally/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/tally/config/spec.json +45 -0
- data/lib/multiwoven/integrations/destination/tally/icon.svg +7 -0
- data/lib/multiwoven/integrations/protocol/protocol.json +189 -0
- data/lib/multiwoven/integrations/protocol/protocol.rb +216 -0
- data/lib/multiwoven/integrations/rollout.rb +32 -0
- data/lib/multiwoven/integrations/service.rb +79 -0
- data/lib/multiwoven/integrations/source/bigquery/client.rb +98 -0
- data/lib/multiwoven/integrations/source/bigquery/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/bigquery/config/spec.json +82 -0
- data/lib/multiwoven/integrations/source/bigquery/icon.svg +1 -0
- data/lib/multiwoven/integrations/source/databricks/client.rb +98 -0
- data/lib/multiwoven/integrations/source/databricks/config/meta.json +16 -0
- data/lib/multiwoven/integrations/source/databricks/config/spec.json +56 -0
- data/lib/multiwoven/integrations/source/databricks/icon.svg +19 -0
- data/lib/multiwoven/integrations/source/postgresql/client.rb +109 -0
- data/lib/multiwoven/integrations/source/postgresql/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/postgresql/config/spec.json +69 -0
- data/lib/multiwoven/integrations/source/postgresql/icon.svg +20 -0
- data/lib/multiwoven/integrations/source/redshift/client.rb +109 -0
- data/lib/multiwoven/integrations/source/redshift/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/redshift/config/spec.json +71 -0
- data/lib/multiwoven/integrations/source/redshift/icon.svg +15 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/client.rb +123 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/meta.json +17 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/spec.json +50 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/icon.svg +16 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/schema_helper.rb +130 -0
- data/lib/multiwoven/integrations/source/snowflake/client.rb +92 -0
- data/lib/multiwoven/integrations/source/snowflake/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/snowflake/config/spec.json +82 -0
- data/lib/multiwoven/integrations/source/snowflake/icon.svg +10 -0
- data/lib/multiwoven/integrations/source/zoho_books/client.rb +155 -0
- data/lib/multiwoven/integrations/source/zoho_books/config/meta.json +16 -0
- data/lib/multiwoven/integrations/source/zoho_books/config/spec.json +43 -0
- data/lib/multiwoven/integrations/source/zoho_books/icon.svg +16 -0
- data/lib/multiwoven/integrations.rb +71 -0
- data/multiwoven-integrations-0.1.68.gem +0 -0
- data/sig/multiwoven/integrations.rbs +6 -0
- metadata +515 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1365.3333 1365.3333" height="1365.3333" width="1365.3333" xml:space="preserve" id="svg2" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
|
2
|
+
<metadata id="metadata8">
|
3
|
+
<rdf:RDF>
|
4
|
+
<cc:Work rdf:about="">
|
5
|
+
<dc:format>
|
6
|
+
image/svg+xml
|
7
|
+
</dc:format>
|
8
|
+
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage">
|
9
|
+
</dc:type>
|
10
|
+
</cc:Work>
|
11
|
+
</rdf:RDF>
|
12
|
+
</metadata>
|
13
|
+
<defs id="defs6">
|
14
|
+
</defs>
|
15
|
+
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1365.3333)" id="g10">
|
16
|
+
<g transform="scale(0.1)" id="g12">
|
17
|
+
<path id="path14" style="fill:#1877f2;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 10240,5120 c 0,2827.7 -2292.3,5120 -5120,5120 C 2292.3,10240 0,7947.7 0,5120 0,2564.46 1872.31,446.301 4320,62.1992 V 3640 H 3020 v 1480 h 1300 v 1128 c 0,1283.2 764.38,1992 1933.9,1992 560.17,0 1146.1,-100 1146.1,-100 V 6880 H 6754.38 C 6118.35,6880 5920,6485.33 5920,6080.43 V 5120 H 7340 L 7113,3640 H 5920 V 62.1992 C 8367.69,446.301 10240,2564.46 10240,5120">
|
18
|
+
</path>
|
19
|
+
<path id="path16" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 7113,3640 227,1480 H 5920 v 960.43 c 0,404.9 198.35,799.57 834.38,799.57 H 7400 v 1260 c 0,0 -585.93,100 -1146.1,100 C 5084.38,8240 4320,7531.2 4320,6248 V 5120 H 3020 V 3640 H 4320 V 62.1992 C 4580.67,21.3008 4847.84,0 5120,0 c 272.16,0 539.33,21.3008 800,62.1992 V 3640 h 1193">
|
20
|
+
</path>
|
21
|
+
</g>
|
22
|
+
</g>
|
23
|
+
</svg>
|
@@ -0,0 +1,231 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Multiwoven
|
4
|
+
module Integrations
|
5
|
+
module Destination
|
6
|
+
module GoogleSheets
|
7
|
+
include Multiwoven::Integrations::Core
|
8
|
+
|
9
|
+
class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
|
10
|
+
prepend Multiwoven::Integrations::Core::Fullrefresher
|
11
|
+
prepend Multiwoven::Integrations::Core::RateLimiter
|
12
|
+
MAX_CHUNK_SIZE = 10_000
|
13
|
+
|
14
|
+
def check_connection(connection_config)
|
15
|
+
connection_config = connection_config.with_indifferent_access
|
16
|
+
authorize_client(connection_config)
|
17
|
+
fetch_google_spread_sheets(connection_config)
|
18
|
+
success_status
|
19
|
+
rescue StandardError => e
|
20
|
+
handle_exception("GOOGLE_SHEETS:CRM:DISCOVER:EXCEPTION", "error", e)
|
21
|
+
failure_status(e)
|
22
|
+
end
|
23
|
+
|
24
|
+
def discover(connection_config)
|
25
|
+
connection_config = connection_config.with_indifferent_access
|
26
|
+
authorize_client(connection_config)
|
27
|
+
spreadsheets = fetch_google_spread_sheets(connection_config)
|
28
|
+
catalog = build_catalog_from_spreadsheets(spreadsheets, connection_config)
|
29
|
+
catalog.to_multiwoven_message
|
30
|
+
rescue StandardError => e
|
31
|
+
handle_exception("GOOGLE_SHEETS:CRM:DISCOVER:EXCEPTION", "error", e)
|
32
|
+
end
|
33
|
+
|
34
|
+
def write(sync_config, records, action = "create")
|
35
|
+
setup_write_environment(sync_config, action)
|
36
|
+
process_record_chunks(records, sync_config)
|
37
|
+
rescue StandardError => e
|
38
|
+
handle_exception("GOOGLE_SHEETS:CRM:WRITE:EXCEPTION", "error", e)
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear_all_records(sync_config)
|
42
|
+
setup_write_environment(sync_config, "clear")
|
43
|
+
connection_specification = sync_config.destination.connection_specification.with_indifferent_access
|
44
|
+
spreadsheet = fetch_google_spread_sheets(connection_specification)
|
45
|
+
sheet_ids = spreadsheet.sheets.map(&:properties).map(&:sheet_id)
|
46
|
+
|
47
|
+
delete_extra_sheets(sheet_ids)
|
48
|
+
|
49
|
+
unless sheet_ids.empty?
|
50
|
+
clear_response = clear_sheet_data(spreadsheet.sheets.first.properties.title)
|
51
|
+
return control_message("Successfully cleared data.", "succeeded") if clear_response&.cleared_range
|
52
|
+
end
|
53
|
+
|
54
|
+
control_message("Failed to clear data.", "failed")
|
55
|
+
rescue StandardError => e
|
56
|
+
control_message(e.message, "failed")
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# To define the level of access granted to your app, you need to identify and declare authorization scopes which is provided by google scopse https://developers.google.com/sheets/api/scopes
|
62
|
+
def authorize_client(config)
|
63
|
+
credentials = config[:credentials_json]
|
64
|
+
@client = Google::Apis::SheetsV4::SheetsService.new
|
65
|
+
@client.authorization = Google::Auth::ServiceAccountCredentials.make_creds(
|
66
|
+
json_key_io: StringIO.new(credentials.to_json),
|
67
|
+
scope: GOOGLE_SHEETS_SCOPE
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Extract spreadsheet id from the spreadsheet link and return the metadata for all the sheets
|
72
|
+
def fetch_google_spread_sheets(connection_config)
|
73
|
+
spreadsheet_id = extract_spreadsheet_id(connection_config[:spreadsheet_link])
|
74
|
+
@client.get_spreadsheet(spreadsheet_id)
|
75
|
+
end
|
76
|
+
|
77
|
+
# dynamically builds catalog based on spreadsheet metadata
|
78
|
+
def build_catalog_from_spreadsheets(spreadsheet, connection_config)
|
79
|
+
catalog = build_catalog(load_catalog)
|
80
|
+
@spreadsheet_id = extract_spreadsheet_id(connection_config[:spreadsheet_link])
|
81
|
+
|
82
|
+
spreadsheet.sheets.each do |sheet|
|
83
|
+
process_sheet_for_catalog(sheet, catalog)
|
84
|
+
end
|
85
|
+
|
86
|
+
catalog
|
87
|
+
end
|
88
|
+
|
89
|
+
# Builds catalog for the single spreadsheet based on column name
|
90
|
+
def process_sheet_for_catalog(sheet, catalog)
|
91
|
+
sheet_name, last_column_index = extract_sheet_properties(sheet)
|
92
|
+
column_names = fetch_column_names(sheet_name, last_column_index)
|
93
|
+
catalog.streams << generate_json_schema(column_names, sheet_name) if column_names
|
94
|
+
end
|
95
|
+
|
96
|
+
def extract_sheet_properties(sheet)
|
97
|
+
[sheet.properties.title, sheet.properties.grid_properties.column_count]
|
98
|
+
end
|
99
|
+
|
100
|
+
def fetch_column_names(sheet_name, last_column_index)
|
101
|
+
header_range = generate_header_range(sheet_name, last_column_index)
|
102
|
+
spread_sheet_value(header_range)&.flatten
|
103
|
+
end
|
104
|
+
|
105
|
+
def spread_sheet_value(header_range)
|
106
|
+
@spread_sheet_value ||= @client.get_spreadsheet_values(@spreadsheet_id, header_range).values
|
107
|
+
end
|
108
|
+
|
109
|
+
def generate_header_range(sheet_name, last_column_index)
|
110
|
+
"#{sheet_name}!A1:#{column_index_to_letter(last_column_index)}1"
|
111
|
+
end
|
112
|
+
|
113
|
+
def column_index_to_letter(index)
|
114
|
+
("A".."ZZZ").to_a[index - 1]
|
115
|
+
end
|
116
|
+
|
117
|
+
def generate_json_schema(column_names, sheet_name)
|
118
|
+
{
|
119
|
+
name: sheet_name,
|
120
|
+
action: "create",
|
121
|
+
batch_support: true,
|
122
|
+
batch_size: 10_000,
|
123
|
+
json_schema: generate_properties_schema(column_names),
|
124
|
+
supported_sync_modes: %w[incremental full_refresh]
|
125
|
+
}.with_indifferent_access
|
126
|
+
end
|
127
|
+
|
128
|
+
def generate_properties_schema(column_names)
|
129
|
+
properties = column_names.each_with_object({}) do |field, props|
|
130
|
+
props[field] = { "type" => "string" }
|
131
|
+
end
|
132
|
+
|
133
|
+
{ "$schema" => JSON_SCHEMA_URL, "type" => "object", "properties" => properties }
|
134
|
+
end
|
135
|
+
|
136
|
+
def setup_write_environment(sync_config, action)
|
137
|
+
@action = sync_config.stream.action || action
|
138
|
+
connection_specification = sync_config.destination.connection_specification.with_indifferent_access
|
139
|
+
@spreadsheet_id = extract_spreadsheet_id(connection_specification[:spreadsheet_link])
|
140
|
+
authorize_client(connection_specification)
|
141
|
+
end
|
142
|
+
|
143
|
+
def extract_spreadsheet_id(link)
|
144
|
+
link[GOOGLE_SPREADSHEET_ID_REGEX, 1] || link
|
145
|
+
end
|
146
|
+
|
147
|
+
# Batch has a limit of sending 2MB data. So creating a chunk of records to meet that limit
|
148
|
+
def process_record_chunks(records, sync_config)
|
149
|
+
write_success = 0
|
150
|
+
write_failure = 0
|
151
|
+
|
152
|
+
records.each_slice(MAX_CHUNK_SIZE) do |chunk|
|
153
|
+
values = prepare_chunk_values(chunk, sync_config.stream)
|
154
|
+
update_sheet_values(values, sync_config.stream.name)
|
155
|
+
write_success += values.size
|
156
|
+
rescue StandardError => e
|
157
|
+
handle_exception("GOOGLE_SHEETS:RECORD:WRITE:EXCEPTION", "error", e)
|
158
|
+
write_failure += chunk.size
|
159
|
+
end
|
160
|
+
|
161
|
+
tracking_message(write_success, write_failure)
|
162
|
+
end
|
163
|
+
|
164
|
+
# We need to format the data to adhere to google sheets API format. This converts the sync mapped data to 2D array format expected by google sheets API
|
165
|
+
def prepare_chunk_values(chunk, stream)
|
166
|
+
last_column_index = spread_sheet_value(stream.name).count
|
167
|
+
fields = fetch_column_names(stream.name, last_column_index)
|
168
|
+
|
169
|
+
chunk.map do |row|
|
170
|
+
row_values = Array.new(fields.size, nil)
|
171
|
+
row.each do |key, value|
|
172
|
+
index = fields.index(key.to_s)
|
173
|
+
row_values[index] = value if index
|
174
|
+
end
|
175
|
+
row_values
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def update_sheet_values(values, stream_name)
|
180
|
+
row_count = spread_sheet_value(stream_name).count
|
181
|
+
range = "#{stream_name}!A#{row_count + 1}"
|
182
|
+
value_range = Google::Apis::SheetsV4::ValueRange.new(range: range, values: values)
|
183
|
+
|
184
|
+
batch_update_request = Google::Apis::SheetsV4::BatchUpdateValuesRequest.new(
|
185
|
+
value_input_option: "RAW",
|
186
|
+
data: [value_range]
|
187
|
+
)
|
188
|
+
|
189
|
+
# TODO: Remove & this is added for the test to pass we need
|
190
|
+
@client&.batch_update_values(@spreadsheet_id, batch_update_request)
|
191
|
+
end
|
192
|
+
|
193
|
+
def load_catalog
|
194
|
+
read_json(CATALOG_SPEC_PATH)
|
195
|
+
end
|
196
|
+
|
197
|
+
def tracking_message(success, failure)
|
198
|
+
Multiwoven::Integrations::Protocol::TrackingMessage.new(
|
199
|
+
success: success, failed: failure
|
200
|
+
).to_multiwoven_message
|
201
|
+
end
|
202
|
+
|
203
|
+
def delete_extra_sheets(sheet_ids)
|
204
|
+
# Leave one sheet intact as a spreadsheet must have at least one sheet.
|
205
|
+
# Delete all other sheets.
|
206
|
+
(sheet_ids.length - 1).times do |i|
|
207
|
+
request = Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest.new(
|
208
|
+
requests: [{ delete_sheet: { sheet_id: sheet_ids[i + 1] } }]
|
209
|
+
)
|
210
|
+
@client.batch_update_spreadsheet(@spreadsheet_id, request)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def clear_sheet_data(sheet_title)
|
215
|
+
clear_request = Google::Apis::SheetsV4::ClearValuesRequest.new
|
216
|
+
@client&.clear_values(@spreadsheet_id, "#{sheet_title}!A2:Z", clear_request)
|
217
|
+
end
|
218
|
+
|
219
|
+
def control_message(message, status)
|
220
|
+
ControlMessage.new(
|
221
|
+
type: "full_refresh",
|
222
|
+
emitted_at: Time.now.to_i,
|
223
|
+
status: ConnectionStatusType[status],
|
224
|
+
meta: { detail: message }
|
225
|
+
).to_multiwoven_message
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"data": {
|
3
|
+
"name": "GoogleSheets",
|
4
|
+
"title": "Google Sheets",
|
5
|
+
"connector_type": "destination",
|
6
|
+
"category": "Productivity Tools",
|
7
|
+
"documentation_url": "https://docs.multiwoven.com/destinations/productivity-tools/google-sheets-service-account",
|
8
|
+
"github_issue_label": "destination-google-sheets",
|
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,74 @@
|
|
1
|
+
{
|
2
|
+
"documentation_url": "https://docs.multiwoven.com/destinations/productivity-tools/google-sheets-service-account",
|
3
|
+
"stream_type": "dynamic",
|
4
|
+
"connection_specification": {
|
5
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
6
|
+
"title": "Google Sheets",
|
7
|
+
"type": "object",
|
8
|
+
"required": ["credentials_json"],
|
9
|
+
"properties": {
|
10
|
+
"spreadsheet_link": {
|
11
|
+
"type": "string"
|
12
|
+
},
|
13
|
+
"credentials_json": {
|
14
|
+
"type": "object",
|
15
|
+
"description": "You can get the keys from the Google Cloud web console. First, go to the IAM page and select Service Accounts from the left menu. Next, locate your service account in the list, click on its Keys tab, and then click Add Key. Lastly, click Create new key and select JSON.",
|
16
|
+
"title": "",
|
17
|
+
"properties": {
|
18
|
+
"type": {
|
19
|
+
"type": "string",
|
20
|
+
"enum": ["service_account"]
|
21
|
+
},
|
22
|
+
"project_id": {
|
23
|
+
"type": "string"
|
24
|
+
},
|
25
|
+
"private_key_id": {
|
26
|
+
"type": "string"
|
27
|
+
},
|
28
|
+
"private_key": {
|
29
|
+
"type": "string"
|
30
|
+
},
|
31
|
+
"client_email": {
|
32
|
+
"type": "string",
|
33
|
+
"format": "email"
|
34
|
+
},
|
35
|
+
"client_id": {
|
36
|
+
"type": "string"
|
37
|
+
},
|
38
|
+
"auth_uri": {
|
39
|
+
"type": "string",
|
40
|
+
"format": "uri"
|
41
|
+
},
|
42
|
+
"token_uri": {
|
43
|
+
"type": "string",
|
44
|
+
"format": "uri"
|
45
|
+
},
|
46
|
+
"auth_provider_x509_cert_url": {
|
47
|
+
"type": "string",
|
48
|
+
"format": "uri"
|
49
|
+
},
|
50
|
+
"client_x509_cert_url": {
|
51
|
+
"type": "string",
|
52
|
+
"format": "uri"
|
53
|
+
},
|
54
|
+
"universe_domain": {
|
55
|
+
"type": "string"
|
56
|
+
}
|
57
|
+
},
|
58
|
+
"required": [
|
59
|
+
"type",
|
60
|
+
"project_id",
|
61
|
+
"private_key_id",
|
62
|
+
"private_key",
|
63
|
+
"client_email",
|
64
|
+
"client_id",
|
65
|
+
"auth_uri",
|
66
|
+
"token_uri",
|
67
|
+
"auth_provider_x509_cert_url",
|
68
|
+
"client_x509_cert_url",
|
69
|
+
"universe_domain"
|
70
|
+
]
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 242423 333333" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"><defs><mask id="c"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="200294" y1="91174.8" x2="200294" y2="176113"><stop offset="0" stop-opacity=".02" stop-color="#fff"/><stop offset="1" stop-opacity=".2" stop-color="#fff"/></linearGradient><path fill="url(#a)" d="M158015 84111h84558v99065h-84558z"/></mask><mask id="e"><radialGradient id="b" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="0" fx="0" fy="0"><stop offset="0" stop-opacity="0" stop-color="#fff"/><stop offset="1" stop-opacity=".098" stop-color="#fff"/></radialGradient><path fill="url(#b)" d="M-150-150h242723v333633H-150z"/></mask><radialGradient id="f" gradientUnits="userSpaceOnUse" cx="9696.85" cy="10000.4" r="166667" fx="9696.85" fy="10000.4"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#fff"/></radialGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="200294" y1="95125.2" x2="200294" y2="172162"><stop offset="0" stop-color="#263138"/><stop offset="1" stop-color="#263138"/></linearGradient></defs><g fill-rule="nonzero"><path d="M151513 0H22729C10227 0 1 10227 1 22728v287877c0 12505 10227 22728 22728 22728h196966c12505 0 22728-10224 22728-22728V90911l-53028-37880L151513 0z" fill="#0f9c57"/><path d="M60606 162880v109853h121216V162880H60606zm53032 94698H75757v-18938h37881v18938zm0-30301H75757v-18946h37881v18946zm0-30310H75757v-18936h37881v18936zm53030 60611h-37884v-18938h37884v18938zm0-30301h-37884v-18946h37884v18946zm0-30310h-37884v-18936h37884v18936z" fill="#f0f0f0"/><path mask="url(#c)" fill="url(#d)" d="M158165 84261l84258 84245V90911z"/><path d="M151513 0v68184c0 12557 10173 22727 22727 22727h68183L151513 0z" fill="#87cdac"/><path d="M22728 0C10226 0 0 10227 0 22729v1893C0 12123 10227 1894 22728 1894h128784V1H22728z" fill="#fff" fill-opacity=".2"/><path d="M219694 331443H22728C10226 331443 0 321213 0 308715v1890c0 12505 10227 22728 22728 22728h196966c12505 0 22728-10224 22728-22728v-1890c0 12499-10224 22728-22728 22728z" fill="#263138" fill-opacity=".2"/><path d="M174239 90911c-12554 0-22727-10170-22727-22727v1893c0 12557 10173 22727 22727 22727h68183v-1893h-68183z" fill="#263138" fill-opacity=".102"/><path d="M151513 0H22729C10227 0 1 10227 1 22729v287876c0 12505 10227 22728 22728 22728h196966c12505 0 22728-10224 22728-22728V90911L151513 0z" mask="url(#e)" fill="url(#f)"/></g></svg>
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module Multiwoven
|
6
|
+
module Integrations
|
7
|
+
module Destination
|
8
|
+
module Hubspot
|
9
|
+
include Multiwoven::Integrations::Core
|
10
|
+
|
11
|
+
class Client < DestinationConnector
|
12
|
+
prepend Multiwoven::Integrations::Core::RateLimiter
|
13
|
+
def check_connection(connection_config)
|
14
|
+
connection_config = connection_config.with_indifferent_access
|
15
|
+
initialize_client(connection_config)
|
16
|
+
authenticate_client
|
17
|
+
success_status
|
18
|
+
rescue StandardError => e
|
19
|
+
handle_exception("HUBSPOT:CRM:DISCOVER:EXCEPTION", "error", e)
|
20
|
+
failure_status(e)
|
21
|
+
end
|
22
|
+
|
23
|
+
def discover(_connection_config = nil)
|
24
|
+
catalog = build_catalog(load_catalog)
|
25
|
+
catalog.to_multiwoven_message
|
26
|
+
rescue StandardError => e
|
27
|
+
handle_exception("HUBSPOT:CRM:DISCOVER:EXCEPTION", "error", e)
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(sync_config, records, action = "create")
|
31
|
+
@action = sync_config.stream.action || action
|
32
|
+
initialize_client(sync_config.destination.connection_specification)
|
33
|
+
process_records(records, sync_config.stream)
|
34
|
+
rescue StandardError => e
|
35
|
+
handle_exception("HUBSPOT:CRM:WRITE:EXCEPTION", "error", e)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def initialize_client(config)
|
41
|
+
config = config.with_indifferent_access
|
42
|
+
@client = ::Hubspot::Client.new(access_token: config[:access_token])
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_records(records, stream)
|
46
|
+
write_success = 0
|
47
|
+
write_failure = 0
|
48
|
+
properties = stream.json_schema.with_indifferent_access[:properties]
|
49
|
+
records.each do |record_object|
|
50
|
+
record = extract_data(record_object, properties)
|
51
|
+
send_data_to_hubspot(stream.name, record)
|
52
|
+
write_success += 1
|
53
|
+
rescue StandardError => e
|
54
|
+
handle_exception("HUBSPOT:CRM:WRITE:EXCEPTION", "error", e)
|
55
|
+
write_failure += 1
|
56
|
+
end
|
57
|
+
tracking_message(write_success, write_failure)
|
58
|
+
end
|
59
|
+
|
60
|
+
def send_data_to_hubspot(stream_name, record = {})
|
61
|
+
args = build_args(@action, stream_name, record)
|
62
|
+
hubspot_stream = @client.crm.send(stream_name)
|
63
|
+
hubspot_data = { simple_public_object_input_for_create: args }
|
64
|
+
hubspot_stream.basic_api.send(@action, hubspot_data)
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_args(action, stream_name, record)
|
68
|
+
case action
|
69
|
+
when :upsert
|
70
|
+
[stream_name, record[:external_key], record]
|
71
|
+
when :destroy
|
72
|
+
[stream_name, record[:id]]
|
73
|
+
else
|
74
|
+
record
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def authenticate_client
|
79
|
+
@client.crm.contacts.basic_api.get_page
|
80
|
+
end
|
81
|
+
|
82
|
+
def success_status
|
83
|
+
ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
|
84
|
+
end
|
85
|
+
|
86
|
+
def failure_status(error)
|
87
|
+
ConnectionStatus.new(status: ConnectionStatusType["failed"], message: error.message).to_multiwoven_message
|
88
|
+
end
|
89
|
+
|
90
|
+
def load_catalog
|
91
|
+
read_json(CATALOG_SPEC_PATH)
|
92
|
+
end
|
93
|
+
|
94
|
+
def tracking_message(success, failure)
|
95
|
+
Multiwoven::Integrations::Protocol::TrackingMessage.new(
|
96
|
+
success: success, failed: failure
|
97
|
+
).to_multiwoven_message
|
98
|
+
end
|
99
|
+
|
100
|
+
def log_debug(message)
|
101
|
+
Multiwoven::Integrations::Service.logger.debug(message)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|