Fixes #3372 - Reasoning about Webhooks activity.
This commit is contained in:
parent
1ac83882f7
commit
02417225f7
24 changed files with 495 additions and 84 deletions
|
@ -2,6 +2,7 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
events:
|
||||
'click [data-type=edit]': 'edit'
|
||||
'click [data-type=new]': 'new'
|
||||
'click [data-type=payload]': 'payload'
|
||||
'click [data-type=import]': 'import'
|
||||
'click .js-description': 'description'
|
||||
|
||||
|
@ -152,6 +153,12 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
else
|
||||
@table.update(objects: objects, pagerSelected: @pageData.pagerSelected, pagerTotalCount: @pageData.pagerTotalCount)
|
||||
|
||||
if @pageData.logFacility
|
||||
new App.HttpLog(
|
||||
el: @$('.page-footer')
|
||||
facility: @pageData.logFacility
|
||||
)
|
||||
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = App[ @genericObject ].find(id)
|
||||
|
@ -181,6 +188,13 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
veryLarge: @veryLarge
|
||||
)
|
||||
|
||||
payload: (e) ->
|
||||
e.preventDefault()
|
||||
new App.WidgetPayloadExample(
|
||||
baseUrl: @payloadExampleUrl
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
import: (e) ->
|
||||
e.preventDefault()
|
||||
@importCallback()
|
||||
|
|
|
@ -406,55 +406,73 @@ class App.UiElement.ticket_perform_action
|
|||
|
||||
selectionRecipient = columnSelectRecipient.element()
|
||||
|
||||
elementTemplate = 'notification'
|
||||
if notificationType is 'webhook'
|
||||
elementTemplate = 'webhook'
|
||||
notificationElement = $( App.view('generic/ticket_perform_action/webhook')(
|
||||
attribute: attribute
|
||||
name: name
|
||||
notificationType: notificationType
|
||||
meta: meta || {}
|
||||
))
|
||||
|
||||
notificationElement = $( App.view("generic/ticket_perform_action/#{elementTemplate}")(
|
||||
attribute: attribute
|
||||
name: name
|
||||
notificationType: notificationType
|
||||
meta: meta || {}
|
||||
))
|
||||
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
|
||||
|
||||
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
|
||||
webhookSelection = App.UiElement.select.render(
|
||||
name: "#{name}::webhook_id"
|
||||
multiple: false
|
||||
null: false
|
||||
relation: 'Webhook'
|
||||
value: meta.webhook_id
|
||||
translate: false
|
||||
)
|
||||
|
||||
visibilitySelection = App.UiElement.select.render(
|
||||
name: "#{name}::internal"
|
||||
multiple: false
|
||||
null: false
|
||||
options: { true: 'internal', false: 'public' }
|
||||
value: meta.internal || 'false'
|
||||
translate: true
|
||||
)
|
||||
notificationElement.find('.js-webhooks').html(webhookSelection)
|
||||
|
||||
notificationElement.find('.js-internal').html(visibilitySelection)
|
||||
else
|
||||
notificationElement = $( App.view('generic/ticket_perform_action/notification')(
|
||||
attribute: attribute
|
||||
name: name
|
||||
notificationType: notificationType
|
||||
meta: meta || {}
|
||||
))
|
||||
|
||||
notificationElement.find('.js-body div[contenteditable="true"]').ce(
|
||||
mode: 'richtext'
|
||||
placeholder: 'message'
|
||||
maxlength: messageLength
|
||||
)
|
||||
new App.WidgetPlaceholder(
|
||||
el: notificationElement.find('.js-body div[contenteditable="true"]').parent()
|
||||
objects: [
|
||||
{
|
||||
prefix: 'ticket'
|
||||
object: 'Ticket'
|
||||
display: 'Ticket'
|
||||
},
|
||||
{
|
||||
prefix: 'article'
|
||||
object: 'TicketArticle'
|
||||
display: 'Article'
|
||||
},
|
||||
{
|
||||
prefix: 'user'
|
||||
object: 'User'
|
||||
display: 'Current User'
|
||||
},
|
||||
]
|
||||
)
|
||||
notificationElement.find('.js-recipient select').replaceWith(selectionRecipient)
|
||||
|
||||
visibilitySelection = App.UiElement.select.render(
|
||||
name: "#{name}::internal"
|
||||
multiple: false
|
||||
null: false
|
||||
options: { true: 'internal', false: 'public' }
|
||||
value: meta.internal || 'false'
|
||||
translate: true
|
||||
)
|
||||
|
||||
notificationElement.find('.js-internal').html(visibilitySelection)
|
||||
|
||||
notificationElement.find('.js-body div[contenteditable="true"]').ce(
|
||||
mode: 'richtext'
|
||||
placeholder: 'message'
|
||||
maxlength: messageLength
|
||||
)
|
||||
new App.WidgetPlaceholder(
|
||||
el: notificationElement.find('.js-body div[contenteditable="true"]').parent()
|
||||
objects: [
|
||||
{
|
||||
prefix: 'ticket'
|
||||
object: 'Ticket'
|
||||
display: 'Ticket'
|
||||
},
|
||||
{
|
||||
prefix: 'article'
|
||||
object: 'TicketArticle'
|
||||
display: 'Article'
|
||||
},
|
||||
{
|
||||
prefix: 'user'
|
||||
object: 'User'
|
||||
display: 'Current User'
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
elementRow.find('.js-setNotification').html(notificationElement).removeClass('hide')
|
||||
|
||||
|
|
41
app/assets/javascripts/app/controllers/webhook.coffee
Normal file
41
app/assets/javascripts/app/controllers/webhook.coffee
Normal file
|
@ -0,0 +1,41 @@
|
|||
class Index extends App.ControllerSubContent
|
||||
requiredPermission: 'admin.webhook'
|
||||
header: 'Webhooks'
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
@genericController = new App.ControllerGenericIndex(
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'Webhook'
|
||||
defaultSortBy: 'name'
|
||||
pageData:
|
||||
home: 'webhooks'
|
||||
object: 'Webhook'
|
||||
objects: 'Webhooks'
|
||||
pagerAjax: true
|
||||
pagerBaseUrl: '#manage/webhook/'
|
||||
pagerSelected: ( @page || 1 )
|
||||
pagerPerPage: 150
|
||||
navupdate: '#webhooks'
|
||||
notes: [
|
||||
'Webhooks are ...'
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'Example Payload', 'data-type': 'payload', class: 'btn' }
|
||||
{ name: 'New Webhook', 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
logFacility: 'webhook'
|
||||
payloadExampleUrl: '/api/v1/webhooks/preview'
|
||||
container: @el.closest('.content')
|
||||
veryLarge: true
|
||||
)
|
||||
|
||||
show: (params) =>
|
||||
for key, value of params
|
||||
if key isnt 'el' && key isnt 'shown' && key isnt 'match'
|
||||
@[key] = value
|
||||
|
||||
@genericController.paginate( @page || 1 )
|
||||
|
||||
App.Config.set('Webhook', { prio: 3350, name: 'Webhook', parent: '#manage', target: '#manage/webhook', controller: Index, permission: ['admin.webhook'] }, 'NavBarAdmin')
|
|
@ -0,0 +1,37 @@
|
|||
class App.WidgetPayloadExample extends App.ControllerModal
|
||||
buttonClose: true
|
||||
buttonCancel: true
|
||||
buttonSubmit: false
|
||||
head: 'Example Payload'
|
||||
large: true
|
||||
|
||||
content: =>
|
||||
if !@payloadExample
|
||||
@load()
|
||||
return
|
||||
|
||||
@payloadExample
|
||||
|
||||
load: =>
|
||||
@ajax(
|
||||
id: 'example_payload'
|
||||
type: 'get'
|
||||
url: @baseUrl
|
||||
processData: false
|
||||
contentType: 'text/plain'
|
||||
dataType: 'text'
|
||||
cache: false
|
||||
success: (data, status, xhr) =>
|
||||
@payloadExample = $(App.view('widget/payload_example')(
|
||||
payload: data
|
||||
))
|
||||
|
||||
@update()
|
||||
error: (data) =>
|
||||
details = data.responseJSON || {}
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to load example payload!')
|
||||
timeout: 6000
|
||||
)
|
||||
|
29
app/assets/javascripts/app/models/webhook.coffee
Normal file
29
app/assets/javascripts/app/models/webhook.coffee
Normal file
|
@ -0,0 +1,29 @@
|
|||
class App.Webhook extends App.Model
|
||||
@configure 'Webhook', 'name', 'endpoint', 'signature_token', 'ssl_verify', 'note', 'active'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/webhooks'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'endpoint', display: 'Endpoint', tag: 'input', type: 'text', limit: 300, null: false, placeholder: 'https://target.example.com/webhook' },
|
||||
{ name: 'signature_token', display: 'HMAC SHA1 Signature Token', tag: 'input', type: 'text', limit: 100, null: true },
|
||||
{ name: 'ssl_verify', display: 'SSL Verify', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: '', limit: 250, null: true },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_delete = true
|
||||
@configure_clone = true
|
||||
@configure_overview = [
|
||||
'name',
|
||||
'endpoint',
|
||||
]
|
||||
|
||||
@description = '''
|
||||
Webhooks make it easy to send information about events within Zammad to third party systems via HTTP(S).
|
||||
|
||||
You can use Webhooks in Zammad to send Ticket, Article and Attachment data whenever a Trigger is performed. Just create and configure your Webhook with an HTTP(S) endpoint and relevant security settings, configure a Trigger to perform it.
|
||||
'''
|
||||
|
||||
displayName: ->
|
||||
return @name if !@endpoint
|
||||
"#{@name} (#{@endpoint})"
|
|
@ -16,4 +16,6 @@
|
|||
|
||||
<div class="page-content">
|
||||
<div class="table-overview"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-footer"></div>
|
||||
|
|
|
@ -1,24 +1,6 @@
|
|||
<div class="form-group">
|
||||
<div class="formGroup-label">
|
||||
<label><%- @T('Endpoint') %></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<input type="url" name="<%= @name %>::endpoint" value="<%= @meta.endpoint %>" class="form-control" style="width: 100%;" placeholder="https://target.example.com/webhook">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="formGroup-label">
|
||||
<label><%- @T('%s Signature Token', 'HMAC-SHA1')%></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<input type="text" name="<%= @name %>::token" value="<%= @meta.token %>" class="form-control" style="width: 100%;" placeholder="<%- @T('some token') %>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="formGroup-label">
|
||||
<label><%- @T('Verify SSL')%></label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<input type="checkbox" name="<%= @name %>::verify_ssl" <% if @meta.verify_ssl: %>checked<% end %>>
|
||||
<label><%- @T('Webhooks') %></label>
|
||||
</div>
|
||||
<div class="controls js-webhooks"></div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<div>
|
||||
<%- @T('Header') %>
|
||||
<pre>
|
||||
X-Zammad-Trigger: Name of the Trigger
|
||||
X-Zammad-Delivery: 6d600811-06a3-40af-aebd-a2d8213e85aa
|
||||
X-Hub-Signature: sha1=06007ef23c38e435f49091cdfa3c770b3d85d7be
|
||||
</pre>
|
||||
<%- @T('Body') %>
|
||||
<pre><%= @payload %></pre>
|
||||
</div>
|
37
app/controllers/webhooks_controller.rb
Normal file
37
app/controllers/webhooks_controller.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class WebhooksController < ApplicationController
|
||||
prepend_before_action { authentication_check && authorize! }
|
||||
|
||||
def preview
|
||||
access_condition = Ticket.access_condition(current_user, 'read')
|
||||
|
||||
ticket = Ticket.where(access_condition).last
|
||||
|
||||
render json: JSON.pretty_generate({
|
||||
ticket: TriggerWebhookJob::RecordPayload.generate(ticket),
|
||||
article: TriggerWebhookJob::RecordPayload.generate(ticket.articles.last),
|
||||
}),
|
||||
status: :ok
|
||||
end
|
||||
|
||||
def index
|
||||
model_index_render(Webhook, params)
|
||||
end
|
||||
|
||||
def show
|
||||
model_show_render(Webhook, params)
|
||||
end
|
||||
|
||||
def create
|
||||
model_create_render(Webhook, params)
|
||||
end
|
||||
|
||||
def update
|
||||
model_update_render(Webhook, params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
model_destroy_render(Webhook, params)
|
||||
end
|
||||
end
|
|
@ -27,6 +27,7 @@ class TriggerWebhookJob < ApplicationJob
|
|||
@ticket = ticket
|
||||
@article = article
|
||||
|
||||
return if abort?
|
||||
return if request.success?
|
||||
|
||||
raise TriggerWebhookJob::RequestError
|
||||
|
@ -34,9 +35,42 @@ class TriggerWebhookJob < ApplicationJob
|
|||
|
||||
private
|
||||
|
||||
def abort?
|
||||
if webhook_id.blank?
|
||||
log_wrong_trigger_config
|
||||
return true
|
||||
elsif webhook.blank?
|
||||
log_not_existing_webhook
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def webhook_id
|
||||
@webhook_id ||= trigger.perform.dig('notification.webhook', 'webhook_id')
|
||||
end
|
||||
|
||||
def webhook
|
||||
@webhook ||= begin
|
||||
Webhook.find_by(
|
||||
id: webhook_id,
|
||||
active: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def log_wrong_trigger_config
|
||||
Rails.logger.error "Can't find webhook_id for Trigger '#{trigger.name}' with ID #{trigger.id}"
|
||||
end
|
||||
|
||||
def log_not_existing_webhook
|
||||
Rails.logger.error "Can't find Webhook for ID #{webhook_id} configured in Trigger '#{trigger.name}' with ID #{trigger.id}"
|
||||
end
|
||||
|
||||
def request
|
||||
UserAgent.post(
|
||||
config['endpoint'],
|
||||
webhook.endpoint,
|
||||
payload,
|
||||
{
|
||||
json: true,
|
||||
|
@ -45,8 +79,8 @@ class TriggerWebhookJob < ApplicationJob
|
|||
read_timeout: 30,
|
||||
total_timeout: 60,
|
||||
headers: headers,
|
||||
signature_token: config['token'],
|
||||
verify_ssl: verify_ssl?,
|
||||
signature_token: webhook.signature_token,
|
||||
verify_ssl: webhook.ssl_verify,
|
||||
log: {
|
||||
facility: 'webhook',
|
||||
},
|
||||
|
@ -54,14 +88,6 @@ class TriggerWebhookJob < ApplicationJob
|
|||
)
|
||||
end
|
||||
|
||||
def config
|
||||
@config ||= trigger.perform['notification.webhook']
|
||||
end
|
||||
|
||||
def verify_ssl?
|
||||
config.fetch('verify_ssl', false).present?
|
||||
end
|
||||
|
||||
def headers
|
||||
{
|
||||
'X-Zammad-Trigger' => trigger.name,
|
||||
|
|
|
@ -15,7 +15,7 @@ module ChecksPerformValidation
|
|||
'article.note' => %w[body subject internal],
|
||||
'notification.email' => %w[body recipient subject],
|
||||
'notification.sms' => %w[body recipient],
|
||||
'notification.webhook' => %w[endpoint],
|
||||
'notification.webhook' => %w[webhook_id],
|
||||
}
|
||||
|
||||
check_present.each do |key, values|
|
||||
|
|
|
@ -39,6 +39,12 @@ returns
|
|||
data = calendar.assets(data)
|
||||
end
|
||||
|
||||
app_model_webhook = Webhook.to_app_model
|
||||
data[ app_model_webhook ] ||= {}
|
||||
Webhook.find_each do |webhook|
|
||||
data = webhook.assets(data)
|
||||
end
|
||||
|
||||
app_model_user = User.to_app_model
|
||||
data[ app_model_user ] ||= {}
|
||||
|
||||
|
|
23
app/models/webhook.rb
Normal file
23
app/models/webhook.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Webhook < ApplicationModel
|
||||
include ChecksClientNotification
|
||||
include ChecksLatestChangeObserved
|
||||
include HasCollectionUpdate
|
||||
|
||||
before_create :validate_endpoint
|
||||
before_update :validate_endpoint
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
private
|
||||
|
||||
def validate_endpoint
|
||||
uri = URI.parse(endpoint)
|
||||
raise Exceptions::UnprocessableEntity, 'Invalid endpoint (no http/https)!' if !uri.is_a?(URI::HTTP)
|
||||
raise Exceptions::UnprocessableEntity, 'Invalid endpoint (no hostname)!' if uri.host.nil?
|
||||
rescue URI::InvalidURIError
|
||||
raise Exceptions::UnprocessableEntity, 'Invalid endpoint!'
|
||||
end
|
||||
|
||||
end
|
3
app/policies/controllers/webhooks_controller_policy.rb
Normal file
3
app/policies/controllers/webhooks_controller_policy.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
class Controllers::WebhooksControllerPolicy < Controllers::ApplicationControllerPolicy
|
||||
default_permit!('admin.webhook')
|
||||
end
|
12
config/routes/webhook.rb
Normal file
12
config/routes/webhook.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
Zammad::Application.routes.draw do
|
||||
api_path = Rails.configuration.api_path
|
||||
|
||||
# webhooks
|
||||
match api_path + '/webhooks/preview', to: 'webhooks#preview', via: :get
|
||||
match api_path + '/webhooks', to: 'webhooks#index', via: :get
|
||||
match api_path + '/webhooks/:id', to: 'webhooks#show', via: :get
|
||||
match api_path + '/webhooks', to: 'webhooks#create', via: :post
|
||||
match api_path + '/webhooks/:id', to: 'webhooks#update', via: :put
|
||||
match api_path + '/webhooks/:id', to: 'webhooks#destroy', via: :delete
|
||||
|
||||
end
|
|
@ -586,6 +586,19 @@ class CreateTicket < ActiveRecord::Migration[4.2]
|
|||
add_index :karma_activity_logs, %i[o_id object_lookup_id]
|
||||
add_foreign_key :karma_activity_logs, :users
|
||||
add_foreign_key :karma_activity_logs, :karma_activities, column: :activity_id
|
||||
|
||||
create_table :webhooks do |t|
|
||||
t.column :name, :string, limit: 250, null: false
|
||||
t.column :endpoint, :string, limit: 300, null: false
|
||||
t.column :signature_token, :string, limit: 200, null: true
|
||||
t.column :ssl_verify, :boolean, null: false, default: true
|
||||
t.column :note, :string, limit: 500, null: true
|
||||
t.column :active, :boolean, null: false, default: true
|
||||
t.column :updated_by_id, :integer, null: false
|
||||
t.column :created_by_id, :integer, null: false
|
||||
t.timestamps limit: 3, null: false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def self.down
|
||||
|
@ -623,5 +636,6 @@ class CreateTicket < ActiveRecord::Migration[4.2]
|
|||
drop_table :ticket_priorities
|
||||
drop_table :ticket_states
|
||||
drop_table :ticket_state_types
|
||||
drop_table :webhooks
|
||||
end
|
||||
end
|
||||
|
|
58
db/migrate/20210118095820_issue_3372_webhooks_admin_view.rb
Normal file
58
db/migrate/20210118095820_issue_3372_webhooks_admin_view.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
class Issue3372WebhooksAdminView < ActiveRecord::Migration[5.2]
|
||||
|
||||
def up
|
||||
return if !Setting.exists?(name: 'system_init_done')
|
||||
|
||||
create_webhooks_table
|
||||
|
||||
record_upgrade
|
||||
|
||||
Permission.create_if_not_exists(
|
||||
name: 'admin.webhook',
|
||||
note: 'Manage %s',
|
||||
preferences: {
|
||||
translations: ['Webhooks']
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
def create_webhooks_table
|
||||
create_table :webhooks do |t|
|
||||
t.column :name, :string, limit: 250, null: false
|
||||
t.column :endpoint, :string, limit: 300, null: false
|
||||
t.column :signature_token, :string, limit: 200, null: true
|
||||
t.column :ssl_verify, :boolean, null: false, default: true
|
||||
t.column :note, :string, limit: 500, null: true
|
||||
t.column :active, :boolean, null: false, default: true
|
||||
t.column :updated_by_id, :integer, null: false
|
||||
t.column :created_by_id, :integer, null: false
|
||||
t.timestamps limit: 3, null: false
|
||||
end
|
||||
end
|
||||
|
||||
def record_upgrade
|
||||
Trigger.all.find_each do |trigger|
|
||||
next if trigger.perform.dig('notification.webhook', 'endpoint').blank?
|
||||
|
||||
webhook = webhook_create(
|
||||
source: trigger.name,
|
||||
config: trigger.perform['notification.webhook'],
|
||||
)
|
||||
trigger.perform['notification.webhook'] = { webhook_id: webhook.id }
|
||||
trigger.save!
|
||||
end
|
||||
end
|
||||
|
||||
def webhook_create(source:, config:)
|
||||
Webhook.create!(
|
||||
name: "Webhook '#{source}'",
|
||||
endpoint: config['endpoint'],
|
||||
signature_token: config['token'],
|
||||
ssl_verify: config['verify_ssl'],
|
||||
active: true,
|
||||
created_by_id: 1,
|
||||
updated_by_id: 1,
|
||||
)
|
||||
end
|
||||
|
||||
end
|
|
@ -262,6 +262,13 @@ Permission.create_if_not_exists(
|
|||
translations: ['Sessions']
|
||||
},
|
||||
)
|
||||
Permission.create_if_not_exists(
|
||||
name: 'admin.webhook',
|
||||
note: 'Manage %s',
|
||||
preferences: {
|
||||
translations: ['Webhooks']
|
||||
},
|
||||
)
|
||||
Permission.create_if_not_exists(
|
||||
name: 'user_preferences',
|
||||
note: 'User Preferences',
|
||||
|
|
44
spec/db/migrate/issue_3372_webhooks_admin_view_spec.rb
Normal file
44
spec/db/migrate/issue_3372_webhooks_admin_view_spec.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Issue3372WebhooksAdminView, type: :db_migration do
|
||||
|
||||
let(:trigger_webhook_config) do
|
||||
{
|
||||
'endpoint' => 'https://example.com/webhook',
|
||||
'token' => '53Cr3T',
|
||||
'verify_ssl' => false,
|
||||
}
|
||||
end
|
||||
|
||||
let(:webhook_attributes) do
|
||||
{
|
||||
endpoint: trigger_webhook_config['endpoint'],
|
||||
signature_token: trigger_webhook_config['token'],
|
||||
ssl_verify: trigger_webhook_config['verify_ssl'],
|
||||
}
|
||||
end
|
||||
|
||||
let!(:trigger) do
|
||||
Trigger.without_callback(:create, :before, :validate_perform) do
|
||||
create(:trigger, perform: {
|
||||
'notification.webhook' => trigger_webhook_config
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
it 'Creates Webhook object from mapped Trigger configuration' do
|
||||
migrate do |migration|
|
||||
allow(migration).to receive(:create_webhooks_table)
|
||||
end
|
||||
|
||||
expect(Webhook.last).to have_attributes(**webhook_attributes)
|
||||
end
|
||||
|
||||
it 'Migrates Trigger#perform Webhook configuration to new structure' do
|
||||
migrate do |migration|
|
||||
allow(migration).to receive(:create_webhooks_table)
|
||||
end
|
||||
|
||||
expect(trigger.reload.perform['notification.webhook']['webhook_id']).to eq(Webhook.last.id)
|
||||
end
|
||||
end
|
9
spec/factories/webhook.rb
Normal file
9
spec/factories/webhook.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
FactoryBot.define do
|
||||
factory :webhook do
|
||||
sequence(:name) { |n| "Test webhook #{n}" }
|
||||
ssl_verify { true }
|
||||
active { true }
|
||||
created_by_id { 1 }
|
||||
updated_by_id { 1 }
|
||||
end
|
||||
end
|
|
@ -62,13 +62,12 @@ RSpec.describe TriggerWebhookJob, type: :job do
|
|||
let!(:ticket) { create(:ticket) }
|
||||
let!(:article) { create(:'ticket/article') }
|
||||
|
||||
let(:webhook) { create(:webhook, endpoint: endpoint, signature_token: token) }
|
||||
|
||||
let(:trigger) do
|
||||
create(:trigger,
|
||||
perform: {
|
||||
'notification.webhook' => {
|
||||
endpoint: endpoint,
|
||||
token: token
|
||||
}
|
||||
'notification.webhook' => { 'webhook_id' => webhook.id }
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
@ -502,13 +502,11 @@ RSpec.describe Ticket, type: :model do
|
|||
end
|
||||
|
||||
context 'with a "notification.webhook" trigger', performs_jobs: true do
|
||||
let(:webhook) { create(:webhook, endpoint: 'http://api.example.com/webhook', signature_token: '53CR3t') }
|
||||
let(:trigger) do
|
||||
create(:trigger,
|
||||
perform: {
|
||||
'notification.webhook' => {
|
||||
endpoint: 'http://api.example.com/webhook',
|
||||
token: '53CR3t'
|
||||
}
|
||||
'notification.webhook' => { 'webhook_id' => webhook.id }
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
@ -870,6 +870,7 @@ RSpec.describe User, type: :model do
|
|||
'Channel' => { 'created_by_id' => 0, 'updated_by_id' => 0 },
|
||||
'Role' => { 'created_by_id' => 0, 'updated_by_id' => 0 },
|
||||
'History' => { 'created_by_id' => 1 },
|
||||
'Webhook' => { 'created_by_id' => 0, 'updated_by_id' => 0 },
|
||||
'Overview' => { 'created_by_id' => 1, 'updated_by_id' => 0 },
|
||||
'ActivityStream' => { 'created_by_id' => 0 },
|
||||
'StatsStore' => { 'created_by_id' => 0 },
|
||||
|
|
41
spec/models/webhook_spec.rb
Normal file
41
spec/models/webhook_spec.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Webhook, type: :model do
|
||||
|
||||
describe 'check endpoint' do
|
||||
subject(:webhook) { create(:webhook, endpoint: endpoint) }
|
||||
|
||||
let(:endpoint) { 'example.com' }
|
||||
|
||||
context 'with missing http type' do
|
||||
it 'raise an error' do
|
||||
expect { webhook }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid endpoint (no http/https)!')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with spaces in invalid hostname' do
|
||||
let(:endpoint) { 'http:// example.com' }
|
||||
|
||||
it 'raise an error' do
|
||||
expect { webhook }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid endpoint!')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ? in hostname' do
|
||||
let(:endpoint) { 'http://?example.com' }
|
||||
|
||||
it 'raise an error' do
|
||||
expect { webhook }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid endpoint (no hostname)!')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with nil in endpoint' do
|
||||
let(:endpoint) { nil }
|
||||
|
||||
it 'raise an error' do
|
||||
expect { webhook }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid endpoint!')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue