webhookdb 1.4.0 → 1.5.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/db/migrations/026_undo_integration_backfill_cursor.rb +2 -0
- data/db/migrations/032_remove_db_defaults.rb +2 -0
- data/db/migrations/043_text_search.rb +2 -0
- data/db/migrations/047_sync_parallelism.rb +9 -0
- data/db/migrations/048_sync_stats.rb +9 -0
- data/db/migrations/049_error_handlers.rb +18 -0
- data/db/migrations/050_logged_webhook_indices.rb +25 -0
- data/db/migrations/051_partitioning.rb +9 -0
- data/integration/async_spec.rb +0 -2
- data/integration/service_integrations_spec.rb +0 -2
- data/lib/amigo/durable_job.rb +2 -2
- data/lib/amigo/job_in_context.rb +12 -0
- data/lib/webhookdb/api/entities.rb +6 -2
- data/lib/webhookdb/api/error_handlers.rb +104 -0
- data/lib/webhookdb/api/helpers.rb +8 -1
- data/lib/webhookdb/api/icalproxy.rb +22 -0
- data/lib/webhookdb/api/install.rb +2 -1
- data/lib/webhookdb/api/saved_queries.rb +1 -0
- data/lib/webhookdb/api/saved_views.rb +1 -0
- data/lib/webhookdb/api/service_integrations.rb +1 -1
- data/lib/webhookdb/api/sync_targets.rb +1 -1
- data/lib/webhookdb/api/system.rb +5 -0
- data/lib/webhookdb/api/webhook_subscriptions.rb +1 -0
- data/lib/webhookdb/api.rb +4 -1
- data/lib/webhookdb/apps.rb +4 -0
- data/lib/webhookdb/async/autoscaler.rb +10 -0
- data/lib/webhookdb/async/job.rb +4 -0
- data/lib/webhookdb/async/scheduled_job.rb +4 -0
- data/lib/webhookdb/async.rb +2 -0
- data/lib/webhookdb/backfiller.rb +17 -4
- data/lib/webhookdb/concurrent.rb +96 -0
- data/lib/webhookdb/connection_cache.rb +29 -8
- data/lib/webhookdb/customer.rb +2 -2
- data/lib/webhookdb/database_document.rb +1 -1
- data/lib/webhookdb/db_adapter/default_sql.rb +1 -14
- data/lib/webhookdb/db_adapter/partition.rb +14 -0
- data/lib/webhookdb/db_adapter/partitioning.rb +8 -0
- data/lib/webhookdb/db_adapter/pg.rb +77 -5
- data/lib/webhookdb/db_adapter/snowflake.rb +15 -6
- data/lib/webhookdb/db_adapter.rb +24 -2
- data/lib/webhookdb/fixtures/logged_webhooks.rb +4 -0
- data/lib/webhookdb/fixtures/organization_error_handlers.rb +20 -0
- data/lib/webhookdb/http.rb +29 -15
- data/lib/webhookdb/icalendar.rb +30 -9
- data/lib/webhookdb/jobs/amigo_test_jobs.rb +1 -1
- data/lib/webhookdb/jobs/backfill.rb +21 -25
- data/lib/webhookdb/jobs/create_mirror_table.rb +3 -4
- data/lib/webhookdb/jobs/deprecated_jobs.rb +2 -0
- data/lib/webhookdb/jobs/emailer.rb +2 -1
- data/lib/webhookdb/jobs/front_signalwire_message_channel_sync_inbound.rb +15 -0
- data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +7 -2
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +74 -11
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs_for_urls.rb +22 -0
- data/lib/webhookdb/jobs/icalendar_sync.rb +21 -9
- data/lib/webhookdb/jobs/increase_event_handler.rb +3 -2
- data/lib/webhookdb/jobs/logged_webhooks_replay.rb +5 -3
- data/lib/webhookdb/jobs/message_dispatched.rb +1 -0
- data/lib/webhookdb/jobs/model_event_system_log_tracker.rb +7 -0
- data/lib/webhookdb/jobs/monitor_metrics.rb +1 -1
- data/lib/webhookdb/jobs/organization_database_migration_notify.rb +32 -0
- data/lib/webhookdb/jobs/organization_database_migration_run.rb +4 -6
- data/lib/webhookdb/jobs/organization_error_handler_dispatch.rb +26 -0
- data/lib/webhookdb/jobs/prepare_database_connections.rb +1 -0
- data/lib/webhookdb/jobs/process_webhook.rb +11 -12
- data/lib/webhookdb/jobs/renew_watch_channel.rb +7 -10
- data/lib/webhookdb/jobs/replication_migration.rb +5 -2
- data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +1 -2
- data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -2
- data/lib/webhookdb/jobs/send_invite.rb +3 -2
- data/lib/webhookdb/jobs/send_test_webhook.rb +1 -3
- data/lib/webhookdb/jobs/send_webhook.rb +4 -5
- data/lib/webhookdb/jobs/stale_row_deleter.rb +31 -0
- data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +3 -0
- data/lib/webhookdb/jobs/sync_target_run_sync.rb +9 -15
- data/lib/webhookdb/jobs/webhook_subscription_delivery_event.rb +5 -8
- data/lib/webhookdb/liquid/expose.rb +1 -1
- data/lib/webhookdb/liquid/filters.rb +1 -1
- data/lib/webhookdb/liquid/partial.rb +2 -2
- data/lib/webhookdb/logged_webhook/resilient.rb +3 -3
- data/lib/webhookdb/logged_webhook.rb +16 -2
- data/lib/webhookdb/message/email_transport.rb +1 -1
- data/lib/webhookdb/message.rb +2 -2
- data/lib/webhookdb/messages/error_generic_backfill.rb +2 -0
- data/lib/webhookdb/messages/error_icalendar_fetch.rb +2 -0
- data/lib/webhookdb/messages/error_signalwire_send_sms.rb +2 -0
- data/lib/webhookdb/organization/alerting.rb +50 -4
- data/lib/webhookdb/organization/database_migration.rb +1 -1
- data/lib/webhookdb/organization/db_builder.rb +4 -3
- data/lib/webhookdb/organization/error_handler.rb +141 -0
- data/lib/webhookdb/organization.rb +62 -9
- data/lib/webhookdb/postgres/model_utilities.rb +2 -0
- data/lib/webhookdb/postgres.rb +1 -3
- data/lib/webhookdb/replicator/base.rb +136 -29
- data/lib/webhookdb/replicator/base_stale_row_deleter.rb +165 -0
- data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +0 -1
- data/lib/webhookdb/replicator/fake.rb +100 -88
- data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +105 -44
- data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +17 -0
- data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +144 -23
- data/lib/webhookdb/replicator/icalendar_event_v1.rb +20 -44
- data/lib/webhookdb/replicator/icalendar_event_v1_partitioned.rb +33 -0
- data/lib/webhookdb/replicator/intercom_contact_v1.rb +1 -0
- data/lib/webhookdb/replicator/intercom_conversation_v1.rb +1 -0
- data/lib/webhookdb/replicator/intercom_v1_mixin.rb +24 -2
- data/lib/webhookdb/replicator/partitionable_mixin.rb +116 -0
- data/lib/webhookdb/replicator/shopify_v1_mixin.rb +1 -1
- data/lib/webhookdb/replicator/signalwire_message_v1.rb +1 -2
- data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
- data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +0 -1
- data/lib/webhookdb/replicator.rb +4 -1
- data/lib/webhookdb/service/helpers.rb +4 -0
- data/lib/webhookdb/service/middleware.rb +6 -2
- data/lib/webhookdb/service_integration.rb +5 -0
- data/lib/webhookdb/signalwire.rb +1 -1
- data/lib/webhookdb/spec_helpers/async.rb +0 -4
- data/lib/webhookdb/spec_helpers/sentry.rb +32 -0
- data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +87 -1
- data/lib/webhookdb/spec_helpers.rb +1 -0
- data/lib/webhookdb/sync_target.rb +195 -29
- data/lib/webhookdb/tasks/admin.rb +1 -1
- data/lib/webhookdb/tasks/annotate.rb +1 -1
- data/lib/webhookdb/tasks/db.rb +13 -1
- data/lib/webhookdb/tasks/docs.rb +1 -1
- data/lib/webhookdb/tasks/fixture.rb +1 -1
- data/lib/webhookdb/tasks/message.rb +1 -1
- data/lib/webhookdb/tasks/regress.rb +1 -1
- data/lib/webhookdb/tasks/release.rb +1 -1
- data/lib/webhookdb/tasks/sidekiq.rb +1 -1
- data/lib/webhookdb/tasks/specs.rb +1 -1
- data/lib/webhookdb/version.rb +1 -1
- data/lib/webhookdb/webhook_subscription.rb +2 -3
- data/lib/webhookdb.rb +3 -1
- metadata +88 -54
- data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +0 -21
- data/lib/webhookdb/jobs/organization_database_migration_notify_started.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fa962b21d64eb6d04e0e55247340e7d07c592c360a56b26227fc3449e55b148
|
4
|
+
data.tar.gz: dd97a7e221331c5f15e9a39d6c32d14bbb37ab24943c7af5fbdb7d44c3f55fc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efaa0af60718483dbd227c1af6e65583de80315b6161bb267e35a3d9564b806229ed3b90496bd7c0f91f7dcffcc3ec84fe20d2ab5f902650bffaa42f0e9b50fe
|
7
|
+
data.tar.gz: 1a2ead61ea98ed53526a8bf9990bf3ecb90c3404380c1c8db42919cb7afd84dc5e1b54e5793987864723a0673cb983efba2c8271135699451ca5a8f6544d2602
|
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
Sequel.migration do
|
4
4
|
change do
|
5
|
+
# rubocop:disable Sequel/IrreversibleMigration
|
5
6
|
alter_table(:sync_targets) do
|
6
7
|
set_column_default :page_size, nil
|
7
8
|
end
|
8
9
|
alter_table(:organizations) do
|
9
10
|
set_column_default :minimum_sync_seconds, nil
|
10
11
|
end
|
12
|
+
# rubocop:enable Sequel/IrreversibleMigration
|
11
13
|
end
|
12
14
|
end
|
@@ -19,10 +19,12 @@ Sequel.migration do
|
|
19
19
|
:webhook_subscription_deliveries,
|
20
20
|
:webhook_subscriptions,
|
21
21
|
]
|
22
|
+
# rubocop:disable Sequel/IrreversibleMigration
|
22
23
|
searchable.each do |tbl|
|
23
24
|
alter_table(tbl) do
|
24
25
|
add_column :text_search, :tsvector
|
25
26
|
end
|
26
27
|
end
|
28
|
+
# rubocop:enable Sequel/IrreversibleMigration
|
27
29
|
end
|
28
30
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Sequel.migration do
|
4
|
+
change do
|
5
|
+
create_table(:organization_error_handlers) do
|
6
|
+
primary_key :id
|
7
|
+
timestamptz :created_at, null: false, default: Sequel.function(:now)
|
8
|
+
timestamptz :updated_at
|
9
|
+
|
10
|
+
text :opaque_id, null: false, unique: true
|
11
|
+
|
12
|
+
foreign_key :organization_id, :organizations, null: false
|
13
|
+
foreign_key :created_by_id, :customers, null: true, on_delete: :set_null
|
14
|
+
|
15
|
+
text :url, null: false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Sequel.migration do
|
4
|
+
no_transaction
|
5
|
+
up do
|
6
|
+
# These are optimized for Webhookdb::LoggedWebhook.trim. See code for details/coupling.
|
7
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_trim_unowned_idx ON logged_webhooks(inserted_at) " \
|
8
|
+
"WHERE organization_id IS NULL"
|
9
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_trim_success_idx ON logged_webhooks(inserted_at) " \
|
10
|
+
"WHERE organization_id IS NOT NULL AND response_status < 400 AND truncated_at IS NULL"
|
11
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_delete_success_idx ON logged_webhooks(inserted_at) " \
|
12
|
+
"WHERE organization_id IS NOT NULL AND response_status < 400 AND truncated_at IS NOT NULL"
|
13
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_trim_failures_idx ON logged_webhooks(inserted_at) " \
|
14
|
+
"WHERE organization_id IS NOT NULL AND response_status >= 400 AND truncated_at IS NULL"
|
15
|
+
run "CREATE INDEX CONCURRENTLY logged_webhooks_delete_failures_idx ON logged_webhooks(inserted_at) " \
|
16
|
+
"WHERE organization_id IS NOT NULL AND response_status >= 400 AND truncated_at IS NOT NULL"
|
17
|
+
end
|
18
|
+
down do
|
19
|
+
run "DROP INDEX logged_webhooks_trim_unowned_idx"
|
20
|
+
run "DROP INDEX logged_webhooks_trim_success_idx"
|
21
|
+
run "DROP INDEX logged_webhooks_delete_success_idx"
|
22
|
+
run "DROP INDEX logged_webhooks_trim_failures_idx"
|
23
|
+
run "DROP INDEX logged_webhooks_delete_failures_idx"
|
24
|
+
end
|
25
|
+
end
|
data/integration/async_spec.rb
CHANGED
data/lib/amigo/durable_job.rb
CHANGED
@@ -351,8 +351,8 @@ module Amigo::DurableJob
|
|
351
351
|
# Space-separate multiple env vars.
|
352
352
|
setting :server_env_vars, ["DATABASE_URL"], convert: ->(s) { s.split.map(&:strip) }
|
353
353
|
|
354
|
-
setting :schema_name, :public, convert:
|
355
|
-
setting :table_name, :durable_jobs, convert:
|
354
|
+
setting :schema_name, :public, convert: lambda(&:to_sym)
|
355
|
+
setting :table_name, :durable_jobs, convert: lambda(&:to_sym)
|
356
356
|
|
357
357
|
after_configured do
|
358
358
|
self.storage_database_urls = self.server_urls.dup
|
@@ -136,10 +136,12 @@ module Webhookdb::API
|
|
136
136
|
expose :opaque_id
|
137
137
|
expose :service_integration, with: ServiceIntegrationEntity
|
138
138
|
expose :period_seconds
|
139
|
+
expose :page_size
|
139
140
|
expose :displaysafe_connection_url, as: :connection_url
|
140
141
|
expose :table
|
141
142
|
expose :schema
|
142
143
|
expose :last_synced_at
|
144
|
+
expose :latency, &self.delegate_to(:latency, :to_i)
|
143
145
|
expose :associated_type
|
144
146
|
expose :associated_id
|
145
147
|
expose :associated_object_display
|
@@ -155,8 +157,9 @@ module Webhookdb::API
|
|
155
157
|
[:associated_object_display, "Associated"],
|
156
158
|
[:schema_and_table_string, "Table"],
|
157
159
|
[:last_synced_at, "Last Synced"],
|
160
|
+
[:latency, "Latency"],
|
158
161
|
[:period_seconds, "Period"],
|
159
|
-
[:page_size, "Page"],
|
162
|
+
[:page_size, "Page Size"],
|
160
163
|
]
|
161
164
|
end
|
162
165
|
end
|
@@ -168,8 +171,9 @@ module Webhookdb::API
|
|
168
171
|
[:connection_url, "URL"],
|
169
172
|
[:associated_object_display, "Associated"],
|
170
173
|
[:last_synced_at, "Last Synced"],
|
174
|
+
[:latency, "Latency"],
|
171
175
|
[:period_seconds, "Period"],
|
172
|
-
[:page_size, "Page"],
|
176
|
+
[:page_size, "Page Size"],
|
173
177
|
]
|
174
178
|
end
|
175
179
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb/api"
|
4
|
+
|
5
|
+
class Webhookdb::API::ErrorHandlers < Webhookdb::API::V1
|
6
|
+
include Webhookdb::Service::Types
|
7
|
+
|
8
|
+
CREATE_PROMPT = "What is the URL that WebhookDB should POST to when a replicator fails? " \
|
9
|
+
"See #{Webhookdb::Organization::ErrorHandler::DOCS_URL} for more information:".freeze
|
10
|
+
|
11
|
+
resource :organizations do
|
12
|
+
route_param :org_identifier do
|
13
|
+
resource :error_handlers do
|
14
|
+
helpers do
|
15
|
+
def lookup!
|
16
|
+
org = lookup_org!
|
17
|
+
eh = org.error_handlers_dataset[opaque_id: params[:opaque_id].strip]
|
18
|
+
merror!(403, "There is no error handler with that id.") if eh.nil?
|
19
|
+
set_request_tags(error_handler_opaque_id: eh.opaque_id)
|
20
|
+
return eh
|
21
|
+
end
|
22
|
+
|
23
|
+
def guard_editable!(customer, org)
|
24
|
+
return if has_admin?(org, customer:)
|
25
|
+
permission_error!("You must be an org admin to modify error handlers.")
|
26
|
+
end
|
27
|
+
|
28
|
+
def url_valid?(u)
|
29
|
+
begin
|
30
|
+
uri = URI(u)
|
31
|
+
rescue URI::InvalidURIError
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
return true if uri.scheme == "sentry"
|
35
|
+
return false unless uri.scheme&.start_with?("http")
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Returns a list of all error handlers associated with the org."
|
41
|
+
get do
|
42
|
+
handlers = lookup_org!.error_handlers
|
43
|
+
message = ""
|
44
|
+
if handlers.empty?
|
45
|
+
message = "This organization doesn't have any error handlers yet.\n" \
|
46
|
+
"Use `webhookdb error-handler create` to set one up."
|
47
|
+
end
|
48
|
+
present_collection handlers, with: ErrorHandlerEntity, message:
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Creates a new error handler."
|
52
|
+
params do
|
53
|
+
optional :url, type: TrimmedString, prompt: CREATE_PROMPT
|
54
|
+
end
|
55
|
+
post :create do
|
56
|
+
cust = current_customer
|
57
|
+
org = lookup_org!
|
58
|
+
guard_editable!(cust, org)
|
59
|
+
unless url_valid?(params[:url])
|
60
|
+
msg = "URL is malformed. It should be a URL like https://foo.bar/path, http://foo.bar:123, etc. " \
|
61
|
+
"See #{Webhookdb::Organization::ErrorHandler::DOCS_URL} for more info."
|
62
|
+
merror!(400, msg)
|
63
|
+
end
|
64
|
+
eh = Webhookdb::Organization::ErrorHandler.create(
|
65
|
+
organization: org,
|
66
|
+
url: params[:url],
|
67
|
+
created_by: cust,
|
68
|
+
)
|
69
|
+
message = "Whenever one of your replicators errors, WebhookDB will alert the given URL. " \
|
70
|
+
"See #{Webhookdb::Organization::ErrorHandler::DOCS_URL} for more information."
|
71
|
+
status 200
|
72
|
+
present eh, with: ErrorHandlerEntity, message:
|
73
|
+
end
|
74
|
+
|
75
|
+
route_param :opaque_id, type: String do
|
76
|
+
get do
|
77
|
+
eh = lookup!
|
78
|
+
status 200
|
79
|
+
present eh, with: ErrorHandlerEntity
|
80
|
+
end
|
81
|
+
|
82
|
+
post :delete do
|
83
|
+
customer = current_customer
|
84
|
+
eh = lookup!
|
85
|
+
guard_editable!(customer, eh.organization)
|
86
|
+
eh.destroy
|
87
|
+
status 200
|
88
|
+
present eh, with: ErrorHandlerEntity,
|
89
|
+
message: "You have successfully deleted the error handler '#{eh.opaque_id}'."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class ErrorHandlerEntity < Webhookdb::API::BaseEntity
|
97
|
+
expose :opaque_id, as: :id
|
98
|
+
expose :url
|
99
|
+
|
100
|
+
def self.display_headers
|
101
|
+
return [[:id, "ID"], [:url, "Url"]]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -122,7 +122,14 @@ module Webhookdb::API::Helpers
|
|
122
122
|
sints = org.service_integrations_dataset.
|
123
123
|
where(Sequel[service_name: identifier] | Sequel[table_name: identifier] | Sequel[opaque_id: identifier]).
|
124
124
|
limit(2).all
|
125
|
-
|
125
|
+
if sints.size == 1
|
126
|
+
sint = sints.first
|
127
|
+
set_request_tags(
|
128
|
+
service_integration_service_name: sint.service_name,
|
129
|
+
service_integration_table_name: sint.table_name,
|
130
|
+
)
|
131
|
+
return sint
|
132
|
+
end
|
126
133
|
merror!(403, "There is no service integration with that identifier.") if sints.empty?
|
127
134
|
dupe_attr = nil
|
128
135
|
alternative = nil
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webhookdb/api"
|
4
|
+
require "webhookdb/jobs/icalendar_enqueue_syncs_for_urls"
|
5
|
+
|
6
|
+
class Webhookdb::API::Icalproxy < Webhookdb::API::V1
|
7
|
+
resource :icalproxy do
|
8
|
+
resource :webhook do
|
9
|
+
post do
|
10
|
+
merror!(402, "Api key not configured") unless Webhookdb::Icalendar.proxy_api_key.present?
|
11
|
+
request_key = request.env["HTTP_AUTHORIZATION"] || ""
|
12
|
+
configured_key = "Apikey #{Webhookdb::Icalendar.proxy_api_key}"
|
13
|
+
verified = request_key && ActiveSupport::SecurityUtils.secure_compare(request_key, configured_key)
|
14
|
+
merror!(401, "Invalid api key") unless verified
|
15
|
+
urls = params.fetch(:urls)
|
16
|
+
Webhookdb::Jobs::IcalendarEnqueueSyncsForUrls.perform_async(urls)
|
17
|
+
status 202
|
18
|
+
present({o: "k"})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -13,6 +13,7 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
13
13
|
def lookup_session!
|
14
14
|
session = Webhookdb::Oauth::Session.usable.where(oauth_state: params[:state]).first
|
15
15
|
error!("Forbidden", 302, {"Location" => "/v1/install/#{oauth_provider.key}/forbidden"}) if session.nil?
|
16
|
+
set_request_tags(session_id: session.id, session_state: session.oauth_state)
|
16
17
|
return session
|
17
18
|
end
|
18
19
|
|
@@ -73,7 +74,7 @@ class Webhookdb::API::Install < Webhookdb::API::V1
|
|
73
74
|
def exchange_authorization_code(code)
|
74
75
|
return oauth_provider.exchange_authorization_code(code:)
|
75
76
|
rescue Webhookdb::Http::Error => e
|
76
|
-
logger.warn "oauth_exchange_error",
|
77
|
+
logger.warn "oauth_exchange_error", e
|
77
78
|
url = "#{Webhookdb.api_url}/v1/install/#{oauth_provider.key}"
|
78
79
|
raise FormError.new(
|
79
80
|
"Something went wrong getting your access token from #{oauth_provider.app_name}. " \
|
@@ -15,6 +15,7 @@ class Webhookdb::API::SavedQueries < Webhookdb::API::V1
|
|
15
15
|
# We can add other identifiers in the future
|
16
16
|
cq = org.saved_queries_dataset[opaque_id: params[:query_identifier]]
|
17
17
|
merror!(403, "There is no saved query with that identifier.") if cq.nil?
|
18
|
+
set_request_tags(saved_query_opaque_id: cq.opaque_id)
|
18
19
|
return cq
|
19
20
|
end
|
20
21
|
|
@@ -13,6 +13,7 @@ class Webhookdb::API::SavedViews < Webhookdb::API::V1
|
|
13
13
|
org = lookup_org!
|
14
14
|
cq = org.saved_views_dataset[name: params[:name].strip]
|
15
15
|
merror!(403, "There is no view with that name.") if cq.nil?
|
16
|
+
set_request_tags(saved_view_name: cq.name)
|
16
17
|
return cq
|
17
18
|
end
|
18
19
|
|
@@ -179,7 +179,7 @@ If the list does not look correct, you can contact support at #{Webhookdb.suppor
|
|
179
179
|
begin
|
180
180
|
svc.upsert_webhook_body(body)
|
181
181
|
rescue KeyError, TypeError => e
|
182
|
-
self.logger.error "immediate_upsert",
|
182
|
+
self.logger.error "immediate_upsert", e
|
183
183
|
err_msg = "Sorry! Looks like something has gone wrong. " \
|
184
184
|
"Check your schema or contact support at #{Webhookdb.support_email}."
|
185
185
|
merror!(400, err_msg)
|
@@ -151,6 +151,7 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
|
|
151
151
|
(stgt = org.all_sync_targets_dataset[Sequel[:sync_targets][:opaque_id] => params[:opaque_id]])
|
152
152
|
merror!(403, "There is no #{fullname} sync target with that id.") if
|
153
153
|
stgt.nil? || !stgt.send(predicate)
|
154
|
+
set_request_tags(stgt.log_tags)
|
154
155
|
return stgt
|
155
156
|
end
|
156
157
|
end
|
@@ -204,7 +205,6 @@ class Webhookdb::API::SyncTargets < Webhookdb::API::V1
|
|
204
205
|
)
|
205
206
|
end
|
206
207
|
|
207
|
-
stgt.logger.warn("destroying_sync_target", sync_target_id: stgt.id, customer_id: current_customer.id)
|
208
208
|
stgt.destroy
|
209
209
|
status 200
|
210
210
|
message = "#{fullname.capitalize} sync target has been removed and will no longer sync."
|
data/lib/webhookdb/api/system.rb
CHANGED
@@ -52,6 +52,7 @@ class Webhookdb::API::WebhookSubscriptions < Webhookdb::API::V1
|
|
52
52
|
org = lookup_org!
|
53
53
|
whsub = org.all_webhook_subscriptions_dataset[opaque_id: params[:opaque_id]]
|
54
54
|
merror!(403, "No webhook subscription with that ID exists in that organization.") if whsub.nil?
|
55
|
+
set_request_tags(webhook_subscription_id: whsub.id)
|
55
56
|
return whsub
|
56
57
|
end
|
57
58
|
end
|
data/lib/webhookdb/api.rb
CHANGED
@@ -52,11 +52,14 @@ module Webhookdb::API
|
|
52
52
|
memberships = customer.verified_memberships_dataset.where(organization: orgs).limit(2).all
|
53
53
|
permission_error!("You don't have permissions with that organization.") if memberships.empty?
|
54
54
|
merror!(500, "ambiguous", alert: true) if memberships.size > 1 # TODO: better message, tests
|
55
|
-
|
55
|
+
org = memberships.first.organization
|
56
|
+
set_request_tags(organization: org.key)
|
57
|
+
return org
|
56
58
|
end
|
57
59
|
raise "something went wrong" unless allow_connstr_auth
|
58
60
|
org = Webhookdb::API::ConnstrAuth.find_authed(orgs, request)
|
59
61
|
unauthenticated! if org.nil?
|
62
|
+
set_request_tags(organization: org.key)
|
60
63
|
return org
|
61
64
|
end
|
62
65
|
|
data/lib/webhookdb/apps.rb
CHANGED
@@ -15,6 +15,8 @@ require "webhookdb/service"
|
|
15
15
|
require "webhookdb/api/auth"
|
16
16
|
require "webhookdb/api/db"
|
17
17
|
require "webhookdb/api/demo"
|
18
|
+
require "webhookdb/api/error_handlers"
|
19
|
+
require "webhookdb/api/icalproxy"
|
18
20
|
require "webhookdb/api/install"
|
19
21
|
require "webhookdb/api/me"
|
20
22
|
require "webhookdb/api/organizations"
|
@@ -77,6 +79,8 @@ module Webhookdb::Apps
|
|
77
79
|
mount Webhookdb::API::Auth
|
78
80
|
mount Webhookdb::API::Db
|
79
81
|
mount Webhookdb::API::Demo
|
82
|
+
mount Webhookdb::API::ErrorHandlers
|
83
|
+
mount Webhookdb::API::Icalproxy
|
80
84
|
mount Webhookdb::API::Install
|
81
85
|
mount Webhookdb::API::Me
|
82
86
|
mount Webhookdb::API::Organizations
|
@@ -28,6 +28,7 @@ module Webhookdb::Async::Autoscaler
|
|
28
28
|
setting :hostname_regex, /^web\.1$/, convert: ->(s) { Regexp.new(s) }
|
29
29
|
setting :heroku_app_id_or_app_name, "", key: "HEROKU_APP_NAME"
|
30
30
|
setting :heroku_formation_id_or_formation_type, "worker"
|
31
|
+
setting :sentry_alert_interval, 180
|
31
32
|
|
32
33
|
after_configured do
|
33
34
|
self._check_provider!
|
@@ -65,6 +66,7 @@ module Webhookdb::Async::Autoscaler
|
|
65
66
|
latency_restored_threshold: self.latency_restored_threshold,
|
66
67
|
latency_restored_handlers: [self.method(:scale_down)],
|
67
68
|
log: ->(level, msg, kw={}) { self.logger.send(level, msg, kw) },
|
69
|
+
on_unhandled_exception: ->(e) { Sentry.capture_exception(e) },
|
68
70
|
)
|
69
71
|
return @instance.start
|
70
72
|
end
|
@@ -78,10 +80,18 @@ module Webhookdb::Async::Autoscaler
|
|
78
80
|
scale_action = @impl.scale_up(names_and_latencies, depth:, duration:, **)
|
79
81
|
kw = {queues: names_and_latencies, depth:, duration:, scale_action:}
|
80
82
|
self.logger.warn("high_latency_queues_event", **kw)
|
83
|
+
self._alert_sentry_latency(kw)
|
84
|
+
end
|
85
|
+
|
86
|
+
def _alert_sentry_latency(kw)
|
87
|
+
call_sentry = @last_called_sentry.nil? ||
|
88
|
+
@last_called_sentry < (Time.now - self.sentry_alert_interval)
|
89
|
+
return unless call_sentry
|
81
90
|
Sentry.with_scope do |scope|
|
82
91
|
scope&.set_extras(**kw)
|
83
92
|
Sentry.capture_message("Some queues have a high latency")
|
84
93
|
end
|
94
|
+
@last_called_sentry = Time.now
|
85
95
|
end
|
86
96
|
|
87
97
|
def scale_down(depth:, duration:, **)
|
data/lib/webhookdb/async/job.rb
CHANGED
data/lib/webhookdb/async.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "amigo/retry"
|
4
4
|
require "amigo/durable_job"
|
5
|
+
require "amigo/job_in_context"
|
5
6
|
require "amigo/rate_limited_error_handler"
|
6
7
|
require "appydays/configurable"
|
7
8
|
require "appydays/loggable"
|
@@ -62,6 +63,7 @@ module Webhookdb::Async
|
|
62
63
|
ttl: self.error_reporting_ttl,
|
63
64
|
)
|
64
65
|
config.death_handlers << Webhookdb::Async::JobLogger.method(:death_handler)
|
66
|
+
config.server_middleware.add(Amigo::JobInContext::ServerMiddleware)
|
65
67
|
config.server_middleware.add(Amigo::DurableJob::ServerMiddleware)
|
66
68
|
# We use the dead set to move jobs that we need to retry manually
|
67
69
|
config.options[:dead_max_jobs] = 999_999_999
|
data/lib/webhookdb/backfiller.rb
CHANGED
@@ -88,16 +88,29 @@ class Webhookdb::Backfiller
|
|
88
88
|
return k, inserting
|
89
89
|
end
|
90
90
|
|
91
|
+
# Return the conditional update expression.
|
92
|
+
# Usually this is:
|
93
|
+
# - +nil+ if +conditional_upsert?+ is false.
|
94
|
+
# - the +_update_where_expr+ if +conditional_upsert?+ is true.
|
95
|
+
# - Can be overridden by a subclass if they need to use a specific conditional update expression
|
96
|
+
# in certain cases (should be rare).
|
97
|
+
def update_where_expr = self.conditional_upsert? ? self.upserting_replicator._update_where_expr : nil
|
98
|
+
|
99
|
+
# The upsert 'UPDATE' expression, calculated using the first row of a multi-row upsert.
|
100
|
+
# Defaults to +_upsert_update_expr+, but may need to be overridden in rare cases.
|
101
|
+
def upsert_update_expr(first_inserting_row) = self.upserting_replicator._upsert_update_expr(first_inserting_row)
|
102
|
+
|
91
103
|
def flush_pending_inserts
|
92
104
|
return if self.dry_run?
|
93
105
|
return if self.pending_inserts.empty?
|
94
106
|
rows_to_insert = self.pending_inserts.values
|
95
|
-
|
107
|
+
update_where_expr = self.update_where_expr
|
108
|
+
update_expr = self.upserting_replicator._upsert_update_expr(rows_to_insert.first)
|
96
109
|
self.upserting_replicator.admin_dataset(timeout: :fast) do |ds|
|
97
110
|
insert_ds = ds.insert_conflict(
|
98
|
-
target: self.upserting_replicator.
|
99
|
-
update:
|
100
|
-
update_where
|
111
|
+
target: self.upserting_replicator._upsert_conflict_target,
|
112
|
+
update: update_expr,
|
113
|
+
update_where: update_where_expr,
|
101
114
|
)
|
102
115
|
insert_ds.multi_insert(rows_to_insert)
|
103
116
|
end
|