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.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/db/migrations/026_undo_integration_backfill_cursor.rb +2 -0
  3. data/db/migrations/032_remove_db_defaults.rb +2 -0
  4. data/db/migrations/043_text_search.rb +2 -0
  5. data/db/migrations/047_sync_parallelism.rb +9 -0
  6. data/db/migrations/048_sync_stats.rb +9 -0
  7. data/db/migrations/049_error_handlers.rb +18 -0
  8. data/db/migrations/050_logged_webhook_indices.rb +25 -0
  9. data/db/migrations/051_partitioning.rb +9 -0
  10. data/integration/async_spec.rb +0 -2
  11. data/integration/service_integrations_spec.rb +0 -2
  12. data/lib/amigo/durable_job.rb +2 -2
  13. data/lib/amigo/job_in_context.rb +12 -0
  14. data/lib/webhookdb/api/entities.rb +6 -2
  15. data/lib/webhookdb/api/error_handlers.rb +104 -0
  16. data/lib/webhookdb/api/helpers.rb +8 -1
  17. data/lib/webhookdb/api/icalproxy.rb +22 -0
  18. data/lib/webhookdb/api/install.rb +2 -1
  19. data/lib/webhookdb/api/saved_queries.rb +1 -0
  20. data/lib/webhookdb/api/saved_views.rb +1 -0
  21. data/lib/webhookdb/api/service_integrations.rb +1 -1
  22. data/lib/webhookdb/api/sync_targets.rb +1 -1
  23. data/lib/webhookdb/api/system.rb +5 -0
  24. data/lib/webhookdb/api/webhook_subscriptions.rb +1 -0
  25. data/lib/webhookdb/api.rb +4 -1
  26. data/lib/webhookdb/apps.rb +4 -0
  27. data/lib/webhookdb/async/autoscaler.rb +10 -0
  28. data/lib/webhookdb/async/job.rb +4 -0
  29. data/lib/webhookdb/async/scheduled_job.rb +4 -0
  30. data/lib/webhookdb/async.rb +2 -0
  31. data/lib/webhookdb/backfiller.rb +17 -4
  32. data/lib/webhookdb/concurrent.rb +96 -0
  33. data/lib/webhookdb/connection_cache.rb +29 -8
  34. data/lib/webhookdb/customer.rb +2 -2
  35. data/lib/webhookdb/database_document.rb +1 -1
  36. data/lib/webhookdb/db_adapter/default_sql.rb +1 -14
  37. data/lib/webhookdb/db_adapter/partition.rb +14 -0
  38. data/lib/webhookdb/db_adapter/partitioning.rb +8 -0
  39. data/lib/webhookdb/db_adapter/pg.rb +77 -5
  40. data/lib/webhookdb/db_adapter/snowflake.rb +15 -6
  41. data/lib/webhookdb/db_adapter.rb +24 -2
  42. data/lib/webhookdb/fixtures/logged_webhooks.rb +4 -0
  43. data/lib/webhookdb/fixtures/organization_error_handlers.rb +20 -0
  44. data/lib/webhookdb/http.rb +29 -15
  45. data/lib/webhookdb/icalendar.rb +30 -9
  46. data/lib/webhookdb/jobs/amigo_test_jobs.rb +1 -1
  47. data/lib/webhookdb/jobs/backfill.rb +21 -25
  48. data/lib/webhookdb/jobs/create_mirror_table.rb +3 -4
  49. data/lib/webhookdb/jobs/deprecated_jobs.rb +2 -0
  50. data/lib/webhookdb/jobs/emailer.rb +2 -1
  51. data/lib/webhookdb/jobs/front_signalwire_message_channel_sync_inbound.rb +15 -0
  52. data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +7 -2
  53. data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +74 -11
  54. data/lib/webhookdb/jobs/icalendar_enqueue_syncs_for_urls.rb +22 -0
  55. data/lib/webhookdb/jobs/icalendar_sync.rb +21 -9
  56. data/lib/webhookdb/jobs/increase_event_handler.rb +3 -2
  57. data/lib/webhookdb/jobs/logged_webhooks_replay.rb +5 -3
  58. data/lib/webhookdb/jobs/message_dispatched.rb +1 -0
  59. data/lib/webhookdb/jobs/model_event_system_log_tracker.rb +7 -0
  60. data/lib/webhookdb/jobs/monitor_metrics.rb +1 -1
  61. data/lib/webhookdb/jobs/organization_database_migration_notify.rb +32 -0
  62. data/lib/webhookdb/jobs/organization_database_migration_run.rb +4 -6
  63. data/lib/webhookdb/jobs/organization_error_handler_dispatch.rb +26 -0
  64. data/lib/webhookdb/jobs/prepare_database_connections.rb +1 -0
  65. data/lib/webhookdb/jobs/process_webhook.rb +11 -12
  66. data/lib/webhookdb/jobs/renew_watch_channel.rb +7 -10
  67. data/lib/webhookdb/jobs/replication_migration.rb +5 -2
  68. data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +1 -2
  69. data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -2
  70. data/lib/webhookdb/jobs/send_invite.rb +3 -2
  71. data/lib/webhookdb/jobs/send_test_webhook.rb +1 -3
  72. data/lib/webhookdb/jobs/send_webhook.rb +4 -5
  73. data/lib/webhookdb/jobs/stale_row_deleter.rb +31 -0
  74. data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +3 -0
  75. data/lib/webhookdb/jobs/sync_target_run_sync.rb +9 -15
  76. data/lib/webhookdb/jobs/webhook_subscription_delivery_event.rb +5 -8
  77. data/lib/webhookdb/liquid/expose.rb +1 -1
  78. data/lib/webhookdb/liquid/filters.rb +1 -1
  79. data/lib/webhookdb/liquid/partial.rb +2 -2
  80. data/lib/webhookdb/logged_webhook/resilient.rb +3 -3
  81. data/lib/webhookdb/logged_webhook.rb +16 -2
  82. data/lib/webhookdb/message/email_transport.rb +1 -1
  83. data/lib/webhookdb/message.rb +2 -2
  84. data/lib/webhookdb/messages/error_generic_backfill.rb +2 -0
  85. data/lib/webhookdb/messages/error_icalendar_fetch.rb +2 -0
  86. data/lib/webhookdb/messages/error_signalwire_send_sms.rb +2 -0
  87. data/lib/webhookdb/organization/alerting.rb +50 -4
  88. data/lib/webhookdb/organization/database_migration.rb +1 -1
  89. data/lib/webhookdb/organization/db_builder.rb +4 -3
  90. data/lib/webhookdb/organization/error_handler.rb +141 -0
  91. data/lib/webhookdb/organization.rb +62 -9
  92. data/lib/webhookdb/postgres/model_utilities.rb +2 -0
  93. data/lib/webhookdb/postgres.rb +1 -3
  94. data/lib/webhookdb/replicator/base.rb +136 -29
  95. data/lib/webhookdb/replicator/base_stale_row_deleter.rb +165 -0
  96. data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +0 -1
  97. data/lib/webhookdb/replicator/fake.rb +100 -88
  98. data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +105 -44
  99. data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +17 -0
  100. data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +144 -23
  101. data/lib/webhookdb/replicator/icalendar_event_v1.rb +20 -44
  102. data/lib/webhookdb/replicator/icalendar_event_v1_partitioned.rb +33 -0
  103. data/lib/webhookdb/replicator/intercom_contact_v1.rb +1 -0
  104. data/lib/webhookdb/replicator/intercom_conversation_v1.rb +1 -0
  105. data/lib/webhookdb/replicator/intercom_v1_mixin.rb +24 -2
  106. data/lib/webhookdb/replicator/partitionable_mixin.rb +116 -0
  107. data/lib/webhookdb/replicator/shopify_v1_mixin.rb +1 -1
  108. data/lib/webhookdb/replicator/signalwire_message_v1.rb +1 -2
  109. data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
  110. data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +0 -1
  111. data/lib/webhookdb/replicator.rb +4 -1
  112. data/lib/webhookdb/service/helpers.rb +4 -0
  113. data/lib/webhookdb/service/middleware.rb +6 -2
  114. data/lib/webhookdb/service_integration.rb +5 -0
  115. data/lib/webhookdb/signalwire.rb +1 -1
  116. data/lib/webhookdb/spec_helpers/async.rb +0 -4
  117. data/lib/webhookdb/spec_helpers/sentry.rb +32 -0
  118. data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +87 -1
  119. data/lib/webhookdb/spec_helpers.rb +1 -0
  120. data/lib/webhookdb/sync_target.rb +195 -29
  121. data/lib/webhookdb/tasks/admin.rb +1 -1
  122. data/lib/webhookdb/tasks/annotate.rb +1 -1
  123. data/lib/webhookdb/tasks/db.rb +13 -1
  124. data/lib/webhookdb/tasks/docs.rb +1 -1
  125. data/lib/webhookdb/tasks/fixture.rb +1 -1
  126. data/lib/webhookdb/tasks/message.rb +1 -1
  127. data/lib/webhookdb/tasks/regress.rb +1 -1
  128. data/lib/webhookdb/tasks/release.rb +1 -1
  129. data/lib/webhookdb/tasks/sidekiq.rb +1 -1
  130. data/lib/webhookdb/tasks/specs.rb +1 -1
  131. data/lib/webhookdb/version.rb +1 -1
  132. data/lib/webhookdb/webhook_subscription.rb +2 -3
  133. data/lib/webhookdb.rb +3 -1
  134. metadata +88 -54
  135. data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +0 -21
  136. 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: 1e9c0cec9d89ca341198512d1ae986100a6db250a1d26403529888ec6f71f467
4
- data.tar.gz: 2819c28d8615b488d5e29163f3c9962d3d1b75777b9f9cc6c3540f468864bca9
3
+ metadata.gz: 4fa962b21d64eb6d04e0e55247340e7d07c592c360a56b26227fc3449e55b148
4
+ data.tar.gz: dd97a7e221331c5f15e9a39d6c32d14bbb37ab24943c7af5fbdb7d44c3f55fc3
5
5
  SHA512:
6
- metadata.gz: 024a72c5913586de41647332309b051be213be9cc5aeb54f4269e8ea3d06e671fce56cdf28f38f00895a4ea37aee22cf877be4e5403a619fed82f032aee57671
7
- data.tar.gz: 0ccaeb11294b44a69787c555422f6fc72e573c96e3332a3e2d247858df3c65914a530a62a707a8d8c6d83b6a4ef8820c70e3a40949ceb6ce6b23cd5c2ef409a8
6
+ metadata.gz: efaa0af60718483dbd227c1af6e65583de80315b6161bb267e35a3d9564b806229ed3b90496bd7c0f91f7dcffcc3ec84fe20d2ab5f902650bffaa42f0e9b50fe
7
+ data.tar.gz: 1a2ead61ea98ed53526a8bf9990bf3ecb90c3404380c1c8db42919cb7afd84dc5e1b54e5793987864723a0673cb983efba2c8271135699451ca5a8f6544d2602
@@ -2,8 +2,10 @@
2
2
 
3
3
  Sequel.migration do
4
4
  change do
5
+ # rubocop:disable Sequel/IrreversibleMigration
5
6
  alter_table(:service_integrations) do
6
7
  drop_column :backfill_cursor
7
8
  end
9
+ # rubocop:enable Sequel/IrreversibleMigration
8
10
  end
9
11
  end
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ alter_table(:sync_targets) do
6
+ add_column :parallelism, :integer, null: false, default: 0
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ alter_table(:sync_targets) do
6
+ add_column :sync_stats, :jsonb, null: false, default: "[]"
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ alter_table(:service_integrations) do
6
+ add_column :partition_value, :int, default: 0, null: false
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rspec/eventually"
4
-
5
3
  require "webhookdb/async"
6
4
 
7
5
  RSpec.describe "async workers", :integration do
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rspec/eventually"
4
-
5
3
  RSpec.describe "service integrations", :integration do
6
4
  def catch_missing_db(default)
7
5
  yield
@@ -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: ->(s) { s.to_sym }
355
- setting :table_name, :durable_jobs, convert: ->(s) { s.to_sym }
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amigo
4
+ end
5
+
6
+ module Amigo::JobInContext
7
+ class ServerMiddleware
8
+ def call(worker, job, queue, &)
9
+ Sidekiq::Context.with(worker:, queue:, job_hash: job, &)
10
+ end
11
+ end
12
+ end
@@ -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
- return sints.first if sints.size == 1
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", exception: e
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", error: e
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."
@@ -37,6 +37,11 @@ class Webhookdb::API::System < Webhookdb::Service
37
37
  }
38
38
  end
39
39
 
40
+ post :sink do
41
+ status 204
42
+ body ""
43
+ end
44
+
40
45
  if ["development", "test"].include?(Webhookdb::RACK_ENV)
41
46
  resource :debug do
42
47
  resource :echo do
@@ -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
- return memberships.first.organization
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
 
@@ -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:, **)
@@ -14,5 +14,9 @@ module Webhookdb::Async::Job
14
14
  def with_log_tags(tags, &)
15
15
  Webhookdb::Async::JobLogger.with_log_tags(tags, &)
16
16
  end
17
+
18
+ def set_job_tags(tags)
19
+ Webhookdb::Async::JobLogger.set_job_tags(**tags)
20
+ end
17
21
  end
18
22
  end
@@ -14,5 +14,9 @@ module Webhookdb::Async::ScheduledJob
14
14
  def with_log_tags(tags, &)
15
15
  Webhookdb::Async::JobLogger.with_log_tags(tags, &)
16
16
  end
17
+
18
+ def set_job_tags(**tags)
19
+ Webhookdb::Async::JobLogger.set_job_tags(**tags)
20
+ end
17
21
  end
18
22
  end
@@ -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
@@ -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
- update_where = self.conditional_upsert? ? self.upserting_replicator._update_where_expr : nil
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._remote_key_column.name,
99
- update: self.upserting_replicator._upsert_update_expr(rows_to_insert.first),
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