Class: Webhookdb::ServiceIntegration

Inherits:
Object
  • Object
show all
Defined in:
lib/webhookdb/service_integration.rb

Defined Under Namespace

Classes: Stats, TableRenameError

Constant Summary collapse

INTEGRATION_INFO_FIELDS =

We limit the information that a user can access through the CLI to these fields.

{
  "id" => :opaque_id,
  "service" => :service_name,
  "table" => :table_name,
  "url" => :unauthed_webhook_endpoint,
  "webhook_secret" => :webhook_secret,
  "webhookdb_api_key" => :webhookdb_api_key,
  "api_url" => :api_url,
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#api_urlString



# File 'lib/webhookdb/service_integration.rb', line 302


#backfill_keyString



# File 'lib/webhookdb/service_integration.rb', line 305


#backfill_secretString



# File 'lib/webhookdb/service_integration.rb', line 308


#data_encryption_secretString



# File 'lib/webhookdb/service_integration.rb', line 324


#depends_onWebhookdb::ServiceIntegration



# File 'lib/webhookdb/service_integration.rb', line 321


#opaque_idString



# File 'lib/webhookdb/service_integration.rb', line 299


#organizationWebhookdb::Organization



# File 'lib/webhookdb/service_integration.rb', line 290


#service_nameString



# File 'lib/webhookdb/service_integration.rb', line 296


#skip_webhook_verificationBoolean



# File 'lib/webhookdb/service_integration.rb', line 329


#table_nameString



# File 'lib/webhookdb/service_integration.rb', line 293


#webhook_secretString



# File 'lib/webhookdb/service_integration.rb', line 311


#webhookdb_api_keyString



# File 'lib/webhookdb/service_integration.rb', line 314


Class Method Details

.create_disambiguated(service_name, **kwargs) ⇒ Webhookdb::ServiceIntegration



77
78
79
80
# File 'lib/webhookdb/service_integration.rb', line 77

def self.create_disambiguated(service_name, **kwargs)
  kwargs[:table_name] ||= "#{service_name}_#{SecureRandom.hex(2)}"
  return self.create(service_name:, **kwargs)
end

.for_api_key(key) ⇒ Webhookdb::ServiceIntegration



83
84
85
# File 'lib/webhookdb/service_integration.rb', line 83

def self.for_api_key(key)
  return self.with_encrypted_value(:webhookdb_api_key, key).first
end

Instance Method Details

#authed_api_pathObject



105
106
107
# File 'lib/webhookdb/service_integration.rb', line 105

def authed_api_path
  return "/v1/organizations/#{self.organization_id}/service_integrations/#{self.opaque_id}"
end

#before_createObject

:Sequel Hooks:



286
287
288
# File 'lib/webhookdb/service_integration.rb', line 286

def before_create
  self.ensure_opaque_id
end

#can_be_modified_by?(customer) ⇒ Boolean



87
88
89
# File 'lib/webhookdb/service_integration.rb', line 87

def can_be_modified_by?(customer)
  return customer.verified_member_of?(self.organization)
end

#dependency_candidatesArray<Webhookdb::ServiceIntegration>

Return service integrations that can be used as the dependency for this integration.



135
136
137
138
139
140
# File 'lib/webhookdb/service_integration.rb', line 135

def dependency_candidates
  dep_descr = self.replicator.descriptor.dependency_descriptor
  return [] if dep_descr.nil?
  return self.organization.service_integrations.
      select { |si| si.service_name == dep_descr.name }
end

#destroy_self_and_all_dependentsObject



146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/webhookdb/service_integration.rb', line 146

def destroy_self_and_all_dependents
  self.dependents.each(&:destroy_self_and_all_dependents)

  if self.organization.admin_connection_url.present?
    begin
      self.replicator.admin_dataset(timeout: :fast) { |ds| ds.db << "DROP TABLE #{self.table_name}" }
    rescue Sequel::DatabaseError => e
      raise e unless e.wrapped_exception.is_a?(PG::UndefinedTable)
    end
  end
  self.destroy
end

#ensure_opaque_idObject



272
# File 'lib/webhookdb/service_integration.rb', line 272

def ensure_opaque_id = self[:opaque_id] ||= self.new_opaque_id

#ensure_sequence(skip_check: false) ⇒ Object



256
257
258
# File 'lib/webhookdb/service_integration.rb', line 256

def ensure_sequence(skip_check: false)
  self.db << self.ensure_sequence_sql(skip_check:)
end

#ensure_sequence_sql(skip_check: false) ⇒ Object



260
261
262
263
264
# File 'lib/webhookdb/service_integration.rb', line 260

def ensure_sequence_sql(skip_check: false)
  raise Webhookdb::InvalidPrecondition, "#{self.service_name} does not require sequence" if
    !skip_check && !self.requires_sequence?
  return "CREATE SEQUENCE IF NOT EXISTS #{self.sequence_name}"
end

#log_tagsObject



96
97
98
99
100
101
102
103
# File 'lib/webhookdb/service_integration.rb', line 96

def log_tags
  return {
    service_integration_id: self.id,
    service_integration_name: self.service_name,
    service_integration_table: self.table_name,
    **self.organization.log_tags,
  }
end

#new_api_keyObject



274
275
276
277
278
279
280
# File 'lib/webhookdb/service_integration.rb', line 274

def new_api_key
  k = +"sk/"
  k << self.ensure_opaque_id
  k << "/"
  k << Webhookdb::Id.rand_enc(24)
  return k
end

#new_opaque_idObject



270
# File 'lib/webhookdb/service_integration.rb', line 270

def new_opaque_id = Webhookdb::Id.new_opaque_id("svi")

#plan_supports_integration?Boolean



117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/webhookdb/service_integration.rb', line 117

def plan_supports_integration?
  # if the sint's organization has an active subscription, return true
  return true if self.organization.active_subscription?
  # if there is no active subscription, check whether the integration is one of the first two
  # created by the organization
  limit = Webhookdb::Subscription.max_free_integrations
  free_integrations = Webhookdb::ServiceIntegration.
    where(organization: self.organization).order(:created_at, :id).limit(limit).all
  free_integrations.each do |sint|
    return true if sint.id == self.id
  end
  # if not, the integration is not supported
  return false
end

#recursive_dependentsObject



142
143
144
# File 'lib/webhookdb/service_integration.rb', line 142

def recursive_dependents
  return self.dependents + self.dependents.flat_map(&:recursive_dependents)
end

#rename_table(to:) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/webhookdb/service_integration.rb', line 226

def rename_table(to:)
  Webhookdb::Organization::DatabaseMigration.guard_ongoing!(self.organization)
  Webhookdb::DBAdapter.validate_identifier!(to, type: "table")
  self.db.transaction do
    begin
      self.organization.admin_connection { |db| db << "ALTER TABLE #{self.table_name} RENAME TO #{to}" }
    rescue Sequel::DatabaseError => e
      case e.wrapped_exception
        when PG::DuplicateTable
          raise TableRenameError,
                "There is already a table named \"#{to}\". Run `webhookdb db tables` to see available tables."
        when PG::SyntaxError
          raise TableRenameError,
                "Please try again with double quotes around '#{to}' since it contains invalid identifier characters."
        else
          raise e
      end
    end
    self.update(table_name: to)
  end
end

#replicatorWebhookdb::Replicator::Base



92
93
94
# File 'lib/webhookdb/service_integration.rb', line 92

def replicator
  return Webhookdb::Replicator.create(self)
end

#requires_sequence?Boolean



248
249
250
# File 'lib/webhookdb/service_integration.rb', line 248

def requires_sequence?
  return self.replicator.requires_sequence?
end

#sequence_nameObject



252
253
254
# File 'lib/webhookdb/service_integration.rb', line 252

def sequence_name
  return "replicator_seq_org_#{self.organization_id}_#{self.service_name}_#{self.id}_seq"
end

#sequence_nextvalObject



266
267
268
# File 'lib/webhookdb/service_integration.rb', line 266

def sequence_nextval
  return self.db.select(Sequel.function(:nextval, self.sequence_name)).single_value
end

#statsWebhookdb::ServiceIntegration::Stats



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/webhookdb/service_integration.rb', line 185

def stats
  all_logged_webhooks = Webhookdb::LoggedWebhook.where(
    service_integration_opaque_id: self.opaque_id,
  ).where { inserted_at > 7.days.ago }

  if all_logged_webhooks.empty?
    return Stats.new(
      "We have no record of receiving webhooks for that integration in the past seven days.",
      {},
    )
  end

  # rubocop:disable Naming/VariableNumber
  count_last_7_days = all_logged_webhooks.count
  rejected_last_7_days = all_logged_webhooks.where { response_status >= 400 }.count
  success_last_7_days = (count_last_7_days - rejected_last_7_days)
  rejected_last_7_days_percent = (rejected_last_7_days.to_f / count_last_7_days)
  success_last_7_days_percent = (success_last_7_days.to_f / count_last_7_days)
  last_10 = Webhookdb::LoggedWebhook.order_by(Sequel.desc(:inserted_at)).limit(10).select_map(:response_status)
  last_10_success, last_10_rejected = last_10.partition { |rs| rs < 400 }

  data = {
    count_last_7_days:,
    count_last_7_days_formatted: count_last_7_days.to_s,
    success_last_7_days:,
    success_last_7_days_formatted: success_last_7_days.to_s,
    success_last_7_days_percent:,
    success_last_7_days_percent_formatted: "%.1f%%" % (success_last_7_days_percent * 100),
    rejected_last_7_days:,
    rejected_last_7_days_formatted: rejected_last_7_days.to_s,
    rejected_last_7_days_percent:,
    rejected_last_7_days_percent_formatted: "%.1f%%" % (rejected_last_7_days_percent * 100),
    successful_of_last_10: last_10_success.size,
    successful_of_last_10_formatted: last_10_success.size.to_s,
    rejected_of_last_10: last_10_rejected.size,
    rejected_of_last_10_formatted: last_10_rejected.size.to_s,
  }
  # rubocop:enable Naming/VariableNumber
  return Stats.new("", data)
end

#unauthed_webhook_endpointObject



113
114
115
# File 'lib/webhookdb/service_integration.rb', line 113

def unauthed_webhook_endpoint
  return Webhookdb.api_url + self.unauthed_webhook_path
end

#unauthed_webhook_pathObject



109
110
111
# File 'lib/webhookdb/service_integration.rb', line 109

def unauthed_webhook_path
  return "/v1/service_integrations/#{self.opaque_id}"
end