Feature #29664 » 0001-Introduce-acts_as_webhookable-to-centralize-webhook-.patch
| app/models/concerns/issue/webhookable.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
module Issue::Webhookable |
|
| 21 |
extend ActiveSupport::Concern |
|
| 22 | ||
| 23 |
def webhook_payload(user, action) |
|
| 24 |
h = super |
|
| 25 |
if action == 'updated' && current_journal.present? |
|
| 26 |
journal = journals.visible(user).find_by_id(current_journal.id) |
|
| 27 |
if journal.present? |
|
| 28 |
h[:data][:journal] = journal_payload(journal, user) |
|
| 29 |
h[:timestamp] = journal.created_on.iso8601 |
|
| 30 |
end |
|
| 31 |
end |
|
| 32 |
h |
|
| 33 |
end |
|
| 34 | ||
| 35 |
private |
|
| 36 | ||
| 37 |
def journal_payload(journal, user) |
|
| 38 |
{
|
|
| 39 |
id: journal.id, |
|
| 40 |
created_on: journal.created_on.iso8601, |
|
| 41 |
notes: journal.notes, |
|
| 42 |
user: {
|
|
| 43 |
id: journal.user.id, |
|
| 44 |
name: journal.user.name, |
|
| 45 |
}, |
|
| 46 |
details: journal.visible_details(user).map do |d| |
|
| 47 |
{
|
|
| 48 |
property: d.property, |
|
| 49 |
prop_key: d.prop_key, |
|
| 50 |
old_value: d.old_value, |
|
| 51 |
value: d.value, |
|
| 52 |
} |
|
| 53 |
end |
|
| 54 |
} |
|
| 55 |
end |
|
| 56 |
end |
|
| app/models/concerns/news/webhookable.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
module News::Webhookable |
|
| 21 |
extend ActiveSupport::Concern |
|
| 22 | ||
| 23 |
# TODO: remove this method once news have the updated_on column |
|
| 24 |
def webhook_payload_timestamp(action) |
|
| 25 |
ts = action == 'created' ? created_on : Time.now |
|
| 26 | ||
| 27 |
ts.iso8601 |
|
| 28 |
end |
|
| 29 |
end |
|
| app/models/concerns/wiki_page/webhookable.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
module WikiPage::Webhookable |
|
| 21 |
extend ActiveSupport::Concern |
|
| 22 | ||
| 23 |
def webhook_payload_ivars |
|
| 24 |
{ page: self, content: content }
|
|
| 25 |
end |
|
| 26 | ||
| 27 |
def webhook_payload_api_template |
|
| 28 |
"app/views/wiki/show.api.rsb" |
|
| 29 |
end |
|
| 30 |
end |
|
| app/models/issue.rb | ||
|---|---|---|
| 21 | 21 |
include Redmine::SafeAttributes |
| 22 | 22 |
include Redmine::Utils::DateCalculation |
| 23 | 23 |
include Redmine::I18n |
| 24 | ||
| 24 | 25 |
before_validation :default_assign, on: :create |
| 25 | 26 |
before_validation :force_default_value_on_noneditable_custom_fields, on: :create |
| 26 | 27 |
before_validation :clear_disabled_fields |
| ... | ... | |
| 59 | 60 |
:author_key => :author_id |
| 60 | 61 | |
| 61 | 62 |
acts_as_mentionable :attributes => ['description'] |
| 63 |
acts_as_webhookable |
|
| 64 |
include Issue::Webhookable |
|
| 62 | 65 | |
| 63 | 66 |
DONE_RATIO_OPTIONS = %w(issue_field issue_status) |
| 64 | 67 | |
| ... | ... | |
| 130 | 133 |
after_create_commit :add_auto_watcher |
| 131 | 134 |
after_commit :create_parent_issue_journal |
| 132 | 135 | |
| 133 |
after_create_commit ->{ Webhook.trigger('issue.created', self) }
|
|
| 134 |
after_update_commit ->{ Webhook.trigger('issue.updated', self) }
|
|
| 135 |
after_destroy_commit ->{ Webhook.trigger('issue.deleted', self) }
|
|
| 136 | ||
| 137 | 136 |
# Returns a SQL conditions string used to find all issues visible by the specified user |
| 138 | 137 |
def self.visible_condition(user, options={})
|
| 139 | 138 |
Project.allowed_to_condition(user, :view_issues, options) do |role, user| |
| app/models/news.rb | ||
|---|---|---|
| 37 | 37 |
acts_as_activity_provider :scope => proc {preload(:project, :author)},
|
| 38 | 38 |
:author_key => :author_id |
| 39 | 39 |
acts_as_watchable |
| 40 |
acts_as_webhookable |
|
| 41 |
include News::Webhookable |
|
| 40 | 42 | |
| 41 | 43 |
after_create :add_author_as_watcher |
| 42 | 44 |
after_create_commit :send_notification |
| 43 | 45 | |
| 44 |
after_create_commit ->{ Webhook.trigger('news.created', self) }
|
|
| 45 |
after_update_commit ->{ Webhook.trigger('news.updated', self) }
|
|
| 46 |
after_destroy_commit ->{ Webhook.trigger('news.deleted', self) }
|
|
| 47 | ||
| 48 | 46 |
scope :visible, (lambda do |*args| |
| 49 | 47 |
joins(:project). |
| 50 | 48 |
where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) |
| app/models/time_entry.rb | ||
|---|---|---|
| 46 | 46 |
acts_as_activity_provider :timestamp => "#{table_name}.created_on",
|
| 47 | 47 |
:author_key => :user_id, |
| 48 | 48 |
:scope => proc {joins(:project).preload(:project)}
|
| 49 |
acts_as_webhookable |
|
| 49 | 50 | |
| 50 | 51 |
validates_presence_of :author_id, :user_id, :activity_id, :project_id, :hours, :spent_on |
| 51 | 52 |
validates_presence_of :issue_id, :if => lambda {Setting.timelog_required_fields.include?('issue_id')}
|
| ... | ... | |
| 58 | 59 |
before_validation :set_author_if_nil |
| 59 | 60 |
validate :validate_time_entry |
| 60 | 61 | |
| 61 |
after_create_commit ->{ Webhook.trigger('time_entry.created', self) }
|
|
| 62 |
after_update_commit ->{ Webhook.trigger('time_entry.updated', self) }
|
|
| 63 |
after_destroy_commit ->{ Webhook.trigger('time_entry.deleted', self) }
|
|
| 64 | ||
| 65 | 62 |
scope :visible, (lambda do |*args| |
| 66 | 63 |
joins(:project). |
| 67 | 64 |
where(TimeEntry.visible_condition(args.shift || User.current, *args)) |
| ... | ... | |
| 82 | 79 |
'issue_id', 'activity_id', 'spent_on', |
| 83 | 80 |
'custom_field_values', 'custom_fields' |
| 84 | 81 | |
| 82 |
def webhook_payload_api_template |
|
| 83 |
"app/views/timelog/show.api.rsb" |
|
| 84 |
end |
|
| 85 | ||
| 85 | 86 |
# Returns a SQL conditions string used to find all time entries visible by the specified user |
| 86 | 87 |
def self.visible_condition(user, options={})
|
| 87 | 88 |
Project.allowed_to_condition(user, :view_time_entries, options) do |role, user| |
| app/models/version.rb | ||
|---|---|---|
| 123 | 123 |
before_destroy :nullify_projects_default_version |
| 124 | 124 |
after_save :update_default_project_version |
| 125 | 125 | |
| 126 |
after_create_commit ->{ Webhook.trigger('version.created', self) }
|
|
| 127 |
after_update_commit ->{ Webhook.trigger('version.updated', self) }
|
|
| 128 |
after_destroy_commit ->{ Webhook.trigger('version.deleted', self) }
|
|
| 129 | ||
| 130 | 126 |
belongs_to :project |
| 131 | 127 |
has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify, :extend => FixedIssuesExtension |
| 132 | 128 | |
| ... | ... | |
| 134 | 130 |
acts_as_attachable :view_permission => :view_files, |
| 135 | 131 |
:edit_permission => :manage_files, |
| 136 | 132 |
:delete_permission => :manage_files |
| 133 |
acts_as_webhookable |
|
| 137 | 134 | |
| 138 | 135 |
VERSION_STATUSES = %w(open locked closed) |
| 139 | 136 |
VERSION_SHARINGS = %w(none descendants hierarchy tree system) |
| app/models/webhook.rb | ||
|---|---|---|
| 94 | 94 |
end |
| 95 | 95 | |
| 96 | 96 |
def setable_events |
| 97 |
WebhookPayload::EVENTS
|
|
| 97 |
WebhookPayload.events
|
|
| 98 | 98 |
end |
| 99 | 99 | |
| 100 | 100 |
def setable_event_names |
| app/models/webhook_payload.rb | ||
|---|---|---|
| 27 | 27 |
self.user = user |
| 28 | 28 |
end |
| 29 | 29 | |
| 30 |
EVENTS = {
|
|
| 31 |
issue: %w[created updated deleted], |
|
| 32 |
wiki_page: %w[created updated deleted], |
|
| 33 |
time_entry: %w[created updated deleted], |
|
| 34 |
news: %w[created updated deleted], |
|
| 35 |
version: %w[created updated deleted], |
|
| 36 |
} |
|
| 30 |
def self.register_model(model, model_events) |
|
| 31 |
raise ArgumentError, "model_events must be Array" unless model_events.is_a?(Array) |
|
| 32 | ||
| 33 |
@events ||= {}
|
|
| 34 |
@events[model.model_name.singular.to_sym] = model_events |
|
| 35 |
end |
|
| 36 | ||
| 37 |
def self.events |
|
| 38 |
@events ||= {}
|
|
| 39 |
end |
|
| 37 | 40 | |
| 38 | 41 |
def to_h |
| 39 | 42 |
type, action = event.split('.')
|
| 40 |
if EVENTS[type.to_sym].include?(action)
|
|
| 41 |
send("#{type}_payload", action)
|
|
| 43 |
if self.class.events[type.to_sym]&.include?(action)
|
|
| 44 |
object.webhook_payload(user, action)
|
|
| 42 | 45 |
else |
| 43 | 46 |
raise ArgumentError, "invalid event: #{event}"
|
| 44 | 47 |
end |
| 45 | 48 |
end |
| 46 | 49 | |
| 47 |
private |
|
| 48 | ||
| 49 |
def issue_payload(action) |
|
| 50 |
issue = object |
|
| 51 |
if issue.current_journal.present? |
|
| 52 |
journal = issue.journals.visible(user).find_by_id(issue.current_journal.id) |
|
| 53 |
end |
|
| 54 |
ts = case action |
|
| 55 |
when 'created' |
|
| 56 |
issue.created_on |
|
| 57 |
when 'deleted' |
|
| 58 |
Time.now |
|
| 59 |
else |
|
| 60 |
journal&.created_on || issue.updated_on |
|
| 61 |
end |
|
| 62 |
h = {
|
|
| 63 |
type: event, |
|
| 64 |
timestamp: ts.iso8601, |
|
| 65 |
data: {
|
|
| 66 |
issue: ApiRenderer.new("app/views/issues/show.api.rsb", user).to_h(issue: issue)
|
|
| 67 |
} |
|
| 68 |
} |
|
| 69 |
if action == 'updated' && journal.present? |
|
| 70 |
h[:data][:journal] = journal_payload(journal) |
|
| 71 |
end |
|
| 72 |
h |
|
| 73 |
end |
|
| 74 | ||
| 75 |
def journal_payload(journal) |
|
| 76 |
{
|
|
| 77 |
id: journal.id, |
|
| 78 |
created_on: journal.created_on.iso8601, |
|
| 79 |
notes: journal.notes, |
|
| 80 |
user: {
|
|
| 81 |
id: journal.user.id, |
|
| 82 |
name: journal.user.name, |
|
| 83 |
}, |
|
| 84 |
details: journal.visible_details(user).map do |d| |
|
| 85 |
{
|
|
| 86 |
property: d.property, |
|
| 87 |
prop_key: d.prop_key, |
|
| 88 |
old_value: d.old_value, |
|
| 89 |
value: d.value, |
|
| 90 |
} |
|
| 91 |
end |
|
| 92 |
} |
|
| 93 |
end |
|
| 94 | ||
| 95 |
def wiki_page_payload(action) |
|
| 96 |
wiki_page = object |
|
| 97 | ||
| 98 |
ts = case action |
|
| 99 |
when 'created' |
|
| 100 |
wiki_page.created_on |
|
| 101 |
when 'deleted' |
|
| 102 |
Time.now |
|
| 103 |
else |
|
| 104 |
wiki_page.updated_on |
|
| 105 |
end |
|
| 106 | ||
| 107 |
{
|
|
| 108 |
type: event, |
|
| 109 |
timestamp: ts.iso8601, |
|
| 110 |
data: {
|
|
| 111 |
wiki_page: ApiRenderer.new("app/views/wiki/show.api.rsb", user).to_h(page: wiki_page, content: wiki_page.content)
|
|
| 112 |
} |
|
| 113 |
} |
|
| 114 |
end |
|
| 115 | ||
| 116 |
def time_entry_payload(action) |
|
| 117 |
time_entry = object |
|
| 118 |
ts = case action |
|
| 119 |
when 'created' |
|
| 120 |
time_entry.created_on |
|
| 121 |
when 'deleted' |
|
| 122 |
Time.now |
|
| 123 |
else |
|
| 124 |
time_entry.updated_on |
|
| 125 |
end |
|
| 126 |
{
|
|
| 127 |
type: event, |
|
| 128 |
timestamp: ts.iso8601, |
|
| 129 |
data: {
|
|
| 130 |
time_entry: ApiRenderer.new("app/views/timelog/show.api.rsb", user).to_h(time_entry: time_entry)
|
|
| 131 |
} |
|
| 132 |
} |
|
| 133 |
end |
|
| 134 | ||
| 135 |
def news_payload(action) |
|
| 136 |
news = object |
|
| 137 |
ts = case action |
|
| 138 |
when 'created' |
|
| 139 |
news.created_on |
|
| 140 |
when 'deleted' |
|
| 141 |
Time.now |
|
| 142 |
else # rubocop:disable Lint/DuplicateBranch |
|
| 143 |
# TODO: fix this by adding a update_on column for news. |
|
| 144 |
Time.now |
|
| 145 |
end |
|
| 146 |
{
|
|
| 147 |
type: event, |
|
| 148 |
timestamp: ts.iso8601, |
|
| 149 |
data: {
|
|
| 150 |
news: ApiRenderer.new("app/views/news/show.api.rsb", user).to_h(news: news)
|
|
| 151 |
} |
|
| 152 |
} |
|
| 153 |
end |
|
| 154 | ||
| 155 |
def version_payload(action) |
|
| 156 |
version = object |
|
| 157 |
ts = case action |
|
| 158 |
when 'created' |
|
| 159 |
version.created_on |
|
| 160 |
when 'deleted' |
|
| 161 |
Time.now |
|
| 162 |
else |
|
| 163 |
version.updated_on |
|
| 164 |
end |
|
| 165 |
{
|
|
| 166 |
type: event, |
|
| 167 |
timestamp: ts.iso8601, |
|
| 168 |
data: {
|
|
| 169 |
version: ApiRenderer.new("app/views/versions/show.api.rsb", user).to_h(version: version)
|
|
| 170 |
} |
|
| 171 |
} |
|
| 172 |
end |
|
| 173 | ||
| 174 | 50 |
# given a path to an API template (relative to RAILS_ROOT), renders it and returns the resulting hash |
| 175 | 51 |
class ApiRenderer |
| 176 | 52 |
include ApplicationHelper |
| app/models/wiki_page.rb | ||
|---|---|---|
| 47 | 47 |
:preload => [:content, {:wiki => :project}],
|
| 48 | 48 |
:permission => :view_wiki_pages, |
| 49 | 49 |
:project_key => "#{Wiki.table_name}.project_id"
|
| 50 |
acts_as_webhookable |
|
| 51 |
include WikiPage::Webhookable |
|
| 50 | 52 | |
| 51 | 53 |
attr_accessor :redirect_existing_links |
| 52 | 54 |
attr_writer :deleted_attachment_ids |
| ... | ... | |
| 62 | 64 |
before_destroy :delete_redirects |
| 63 | 65 |
after_save :handle_children_move, :delete_selected_attachments |
| 64 | 66 | |
| 65 |
after_create_commit ->{ Webhook.trigger('wiki_page.created', self) }
|
|
| 66 |
after_update_commit ->{ Webhook.trigger('wiki_page.updated', self) }
|
|
| 67 |
after_destroy_commit ->{ Webhook.trigger('wiki_page.deleted', self) }
|
|
| 68 | ||
| 69 | 67 |
# eager load information about last updates, without loading text |
| 70 | 68 |
scope :with_updated_on, lambda {preload(:content_without_text)}
|
| 71 | 69 | |
| config/locales/en.yml | ||
|---|---|---|
| 1189 | 1189 |
webhook_event_created: "%{object_name} created"
|
| 1190 | 1190 |
webhook_event_updated: "%{object_name} updated"
|
| 1191 | 1191 |
webhook_event_deleted: "%{object_name} deleted"
|
| 1192 | ||
| 1192 | 1193 |
webhook_url_info: Redmine will send a POST request to this URL whenever one of the selected events occurs in one of the selected projects. |
| 1193 | 1194 |
webhook_secret_info_html: If provided, Redmine will use this to create a hash signature that is sent with each delivery as the value of the X-Redmine-Signature-256 header. |
| 1194 | 1195 | |
| lib/redmine/acts/webhookable.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
module Redmine |
|
| 21 |
module Acts |
|
| 22 |
module Webhookable |
|
| 23 |
def self.included(base) |
|
| 24 |
base.extend ClassMethods |
|
| 25 |
end |
|
| 26 | ||
| 27 |
module ClassMethods |
|
| 28 |
def acts_as_webhookable(events = %w(created updated deleted)) |
|
| 29 |
events = Array(events).map(&:to_s) |
|
| 30 |
WebhookPayload.register_model(self, events) |
|
| 31 | ||
| 32 |
events.each do |event| |
|
| 33 |
case event |
|
| 34 |
when 'created' |
|
| 35 |
after_create_commit ->{ Webhook.trigger(event_name('created'), self) }
|
|
| 36 |
when 'updated' |
|
| 37 |
after_update_commit ->{ Webhook.trigger(event_name('updated'), self) }
|
|
| 38 |
when 'deleted' |
|
| 39 |
after_destroy_commit ->{ Webhook.trigger(event_name('deleted'), self) }
|
|
| 40 |
end |
|
| 41 |
end |
|
| 42 | ||
| 43 |
include Redmine::Acts::Webhookable::InstanceMethods |
|
| 44 |
end |
|
| 45 |
end |
|
| 46 | ||
| 47 |
module InstanceMethods |
|
| 48 |
def event_name(action) |
|
| 49 |
"#{self.class.model_name.singular}.#{action}"
|
|
| 50 |
end |
|
| 51 | ||
| 52 |
def webhook_payload(user, action) |
|
| 53 |
{
|
|
| 54 |
type: event_name(action), |
|
| 55 |
timestamp: webhook_payload_timestamp(action), |
|
| 56 |
data: {
|
|
| 57 |
self.class.model_name.singular.to_sym => |
|
| 58 |
WebhookPayload::ApiRenderer.new(webhook_payload_api_template, user).to_h(**webhook_payload_ivars) |
|
| 59 |
} |
|
| 60 |
} |
|
| 61 |
end |
|
| 62 | ||
| 63 |
def webhook_payload_ivars |
|
| 64 |
{ self.class.model_name.singular.to_sym => self }
|
|
| 65 |
end |
|
| 66 | ||
| 67 |
def webhook_payload_api_template |
|
| 68 |
"app/views/#{self.class.model_name.plural}/show.api.rsb"
|
|
| 69 |
end |
|
| 70 | ||
| 71 |
def webhook_payload_timestamp(action) |
|
| 72 |
ts = case action |
|
| 73 |
when 'created' |
|
| 74 |
created_on |
|
| 75 |
when 'updated' |
|
| 76 |
updated_on |
|
| 77 |
else |
|
| 78 |
Time.now |
|
| 79 |
end |
|
| 80 | ||
| 81 |
ts.iso8601 |
|
| 82 |
end |
|
| 83 |
end |
|
| 84 |
end |
|
| 85 |
end |
|
| 86 |
end |
|
| lib/redmine/preparation.rb | ||
|---|---|---|
| 22 | 22 |
def self.prepare |
| 23 | 23 |
ApplicationRecord.include Redmine::Acts::Positioned |
| 24 | 24 |
ApplicationRecord.include Redmine::Acts::Mentionable |
| 25 |
ApplicationRecord.include Redmine::Acts::Webhookable |
|
| 25 | 26 |
ApplicationRecord.include Redmine::I18n |
| 26 | 27 | |
| 27 | 28 |
Scm::Base.add "Subversion" |
| test/unit/webhook_payload_test.rb | ||
|---|---|---|
| 28 | 28 |
@issue = @project.issues.first |
| 29 | 29 |
end |
| 30 | 30 | |
| 31 |
WebhookPayload.events.each do |type, actions| |
|
| 32 |
actions.each do |action| |
|
| 33 |
test "#{type} #{action} payload should be correct" do
|
|
| 34 |
model_class = type.to_s.classify.constantize |
|
| 35 |
obj = model_class.first || model_class.generate! |
|
| 36 |
p = WebhookPayload.new("#{type}.#{action}", obj, @dlopper)
|
|
| 37 |
assert h = p.to_h |
|
| 38 |
assert_equal "#{type}.#{action}", h[:type]
|
|
| 39 |
assert Time.iso8601(h[:timestamp]) |
|
| 40 |
assert h.dig(:data, type) |
|
| 41 |
end |
|
| 42 |
end |
|
| 43 |
end |
|
| 44 | ||
| 31 | 45 |
test "issue update payload should contain journal" do |
| 32 | 46 |
@issue.init_journal(@dlopper) |
| 33 | 47 |
@issue.subject = "new subject" |
| ... | ... | |
| 35 | 49 |
p = WebhookPayload.new('issue.updated', @issue, @dlopper)
|
| 36 | 50 |
assert h = p.to_h |
| 37 | 51 |
assert_equal 'issue.updated', h[:type] |
| 52 |
assert Time.iso8601(h[:timestamp]) |
|
| 38 | 53 |
assert j = h.dig(:data, :journal) |
| 39 | 54 |
assert_equal 'Dave Lopper', j[:user][:name] |
| 40 | 55 |
assert i = h.dig(:data, :issue) |
| ... | ... | |
| 46 | 61 |
p = WebhookPayload.new('issue.deleted', @issue, @dlopper)
|
| 47 | 62 |
assert h = p.to_h |
| 48 | 63 |
assert_equal 'issue.deleted', h[:type] |
| 64 |
assert Time.iso8601(h[:timestamp]) |
|
| 49 | 65 |
assert_nil h.dig(:data, :journal) |
| 50 | 66 |
assert i = h.dig(:data, :issue) |
| 51 | 67 |
assert_equal @issue.subject, i[:subject], i.inspect |
| ... | ... | |
| 60 | 76 |
p = WebhookPayload.new('wiki_page.created', page, @dlopper)
|
| 61 | 77 |
assert h = p.to_h |
| 62 | 78 |
assert_equal 'wiki_page.created', h[:type] |
| 79 |
assert Time.iso8601(h[:timestamp]) |
|
| 63 | 80 |
assert_equal 'Test_Page', h.dig(:data, :wiki_page, :title) |
| 64 | 81 |
assert_equal 'Test content', h.dig(:data, :wiki_page, :text) |
| 65 | 82 |
assert_equal @dlopper.name, h.dig(:data, :wiki_page, :author, :name) |
| ... | ... | |
| 78 | 95 |
p = WebhookPayload.new('wiki_page.updated', page, @dlopper)
|
| 79 | 96 |
h = p.to_h |
| 80 | 97 |
assert_equal 'wiki_page.updated', h[:type] |
| 98 |
assert Time.iso8601(h[:timestamp]) |
|
| 81 | 99 |
assert_equal 'Updated content', h.dig(:data, :wiki_page, :text) |
| 82 | 100 |
end |
| 83 | 101 | |
| ... | ... | |
| 92 | 110 |
p = WebhookPayload.new('wiki_page.deleted', page, @dlopper)
|
| 93 | 111 |
h = p.to_h |
| 94 | 112 |
assert_equal 'wiki_page.deleted', h[:type] |
| 113 |
assert Time.iso8601(h[:timestamp]) |
|
| 95 | 114 |
assert_equal 'Test_Page', h.dig(:data, :wiki_page, :title) |
| 96 | 115 |
end |
| 97 | 116 | |
| ... | ... | |
| 101 | 120 |
p = WebhookPayload.new('time_entry.created', time_entry, @dlopper)
|
| 102 | 121 |
assert h = p.to_h |
| 103 | 122 |
assert_equal 'time_entry.created', h[:type] |
| 123 |
assert Time.iso8601(h[:timestamp]) |
|
| 104 | 124 |
assert_equal time_entry.hours, h.dig(:data, :time_entry, :hours) |
| 105 | 125 |
end |
| 106 | 126 | |
| ... | ... | |
| 113 | 133 |
p = WebhookPayload.new('time_entry.updated', time_entry, @dlopper)
|
| 114 | 134 |
h = p.to_h |
| 115 | 135 |
assert_equal 'time_entry.updated', h[:type] |
| 136 |
assert Time.iso8601(h[:timestamp]) |
|
| 116 | 137 |
assert_equal 2.5, h.dig(:data, :time_entry, :hours) |
| 117 | 138 |
end |
| 118 | 139 | |
| ... | ... | |
| 123 | 144 |
p = WebhookPayload.new('time_entry.deleted', time_entry, @dlopper)
|
| 124 | 145 |
h = p.to_h |
| 125 | 146 |
assert_equal 'time_entry.deleted', h[:type] |
| 147 |
assert Time.iso8601(h[:timestamp]) |
|
| 126 | 148 |
assert_equal 4.25, h.dig(:data, :time_entry, :hours) |
| 127 | 149 |
end |
| 128 | 150 | |
| ... | ... | |
| 133 | 155 |
p = WebhookPayload.new('news.created', news, @dlopper)
|
| 134 | 156 |
assert h = p.to_h |
| 135 | 157 |
assert_equal 'news.created', h[:type] |
| 158 |
assert_equal news.created_on.iso8601, h[:timestamp] |
|
| 136 | 159 |
assert_equal news.title, h.dig(:data, :news, :title) |
| 137 | 160 |
end |
| 138 | 161 | |
| ... | ... | |
| 145 | 168 |
p = WebhookPayload.new('news.updated', news, @dlopper)
|
| 146 | 169 |
h = p.to_h |
| 147 | 170 |
assert_equal 'news.updated', h[:type] |
| 171 |
assert Time.iso8601(h[:timestamp]) |
|
| 148 | 172 |
assert_equal 'Updated title', h.dig(:data, :news, :title) |
| 149 | 173 |
end |
| 150 | 174 | |
| ... | ... | |
| 155 | 179 |
p = WebhookPayload.new('news.deleted', news, @dlopper)
|
| 156 | 180 |
h = p.to_h |
| 157 | 181 |
assert_equal 'news.deleted', h[:type] |
| 182 |
assert Time.iso8601(h[:timestamp]) |
|
| 158 | 183 |
assert_equal 'eCookbook first release !', h.dig(:data, :news, :title) |
| 159 | 184 |
end |
| 160 | 185 | |
| ... | ... | |
| 164 | 189 |
p = WebhookPayload.new('version.created', version, @dlopper)
|
| 165 | 190 |
assert h = p.to_h |
| 166 | 191 |
assert_equal 'version.created', h[:type] |
| 192 |
assert Time.iso8601(h[:timestamp]) |
|
| 167 | 193 |
assert_equal version.name, h.dig(:data, :version, :name) |
| 168 | 194 |
end |
| 169 | 195 | |
| ... | ... | |
| 176 | 202 |
p = WebhookPayload.new('version.updated', version, @dlopper)
|
| 177 | 203 |
h = p.to_h |
| 178 | 204 |
assert_equal 'version.updated', h[:type] |
| 205 |
assert Time.iso8601(h[:timestamp]) |
|
| 179 | 206 |
assert_equal 'Updated name', h.dig(:data, :version, :name) |
| 180 | 207 |
end |
| 181 | 208 | |
| ... | ... | |
| 186 | 213 |
p = WebhookPayload.new('version.deleted', version, @dlopper)
|
| 187 | 214 |
h = p.to_h |
| 188 | 215 |
assert_equal 'version.deleted', h[:type] |
| 216 |
assert Time.iso8601(h[:timestamp]) |
|
| 189 | 217 |
assert_equal '0.1', h.dig(:data, :version, :name) |
| 190 | 218 |
end |
| 219 | ||
| 220 |
test "should generate payload for custom event" do |
|
| 221 |
# Register a custom event for News |
|
| 222 |
News.acts_as_webhookable %w(created updated deleted commented) |
|
| 223 | ||
| 224 |
news = News.first |
|
| 225 |
p = WebhookPayload.new('news.commented', news, @dlopper)
|
|
| 226 |
assert h = p.to_h |
|
| 227 |
assert_equal 'news.commented', h[:type] |
|
| 228 |
assert Time.iso8601(h[:timestamp]) |
|
| 229 |
end |
|
| 191 | 230 |
end |
- « Previous
- 1
- …
- 15
- 16
- 17
- Next »