redmine_events_manager 0.4.8
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 +7 -0
- data/.avm.yml +3 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +40 -0
- data/SelfGemfile +5 -0
- data/app/controllers/event_exceptions_controller.rb +32 -0
- data/app/controllers/listener_options_controller.rb +12 -0
- data/app/models/event_exception.rb +8 -0
- data/app/models/listener_option.rb +37 -0
- data/assets/stylesheets/application.css +8 -0
- data/bin/bundle +5 -0
- data/config/initializers/000_dependencies.rb +6 -0
- data/config/initializers/assets.rb +0 -0
- data/config/locales/pt-BR.yml +7 -0
- data/config/routes.rb +11 -0
- data/db/migrate/20160509152749_create_delayed_jobs.rb +27 -0
- data/db/migrate/20160717222418_create_event_exceptions.rb +20 -0
- data/db/migrate/20180331122033_create_listener_options.rb +14 -0
- data/db/migrate/20180419202618_add_enabled_to_listener_options.rb +9 -0
- data/db/migrate/20180711170315_rename_plugin_events_manager_to_redmine_events_manager.rb +42 -0
- data/init.rb +37 -0
- data/lib/events_manager.rb +99 -0
- data/lib/events_manager/event.rb +29 -0
- data/lib/events_manager/hooks/add_assets.rb +11 -0
- data/lib/events_manager/patches/issue_patch.rb +32 -0
- data/lib/events_manager/patches/issue_relation_patch.rb +32 -0
- data/lib/events_manager/patches/journal_patch.rb +29 -0
- data/lib/events_manager/patches/repository/git_patch.rb +18 -0
- data/lib/events_manager/patches/test_case_patch.rb +17 -0
- data/lib/events_manager/patches/time_entry_patch.rb +39 -0
- data/lib/events_manager/removed_record.rb +22 -0
- data/lib/events_manager/settings.rb +17 -0
- data/lib/events_manager/updated_record.rb +14 -0
- data/lib/redmine_events_manager/test_config.rb +11 -0
- data/lib/redmine_events_manager/version.rb +8 -0
- data/lib/tasks/events.rake +46 -0
- data/lib/tasks/redmine_events_manager.rake +4 -0
- data/redmine_events_manager.gemspec +24 -0
- data/spec/controllers/event_exceptions_controller_spec.rb +18 -0
- data/spec/lib/events_manager_spec.rb +16 -0
- data/spec/models/event_exception_spec.rb +79 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a76fba8e0e80ac0cb125f39d3651721a0239b0e69ef46c818c0125b4eb9af2fd
|
4
|
+
data.tar.gz: 7f9f3742c930fb348878382b06a787077e88563f7d1cc3e430668db752537790
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 936392a8e1096dec12b8f309b32df278e59e511c085937de11f71ef8d88b718e877d5c2ae1c2c3ec2eb52f3553fbed851775b6b71ad70fe20a35e06942e2fe93
|
7
|
+
data.tar.gz: 4015c2642a34f911faa94428d1876a3d69ad1b847d3a974807b7f1df663d24bdc59be02e71fc761f994133733f1d637594bd5a05de850b8c338242d11c07189d
|
data/.avm.yml
ADDED
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/SelfGemfile.lock
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rails
|
3
|
+
- rubocop-rspec
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
TargetRubyVersion: 2.4
|
7
|
+
TargetRailsVersion: 4.2
|
8
|
+
|
9
|
+
Layout/LineLength:
|
10
|
+
Max: 100
|
11
|
+
Exclude:
|
12
|
+
- 'db/migrate/**/*'
|
13
|
+
|
14
|
+
Metrics/AbcSize:
|
15
|
+
Exclude:
|
16
|
+
- 'db/migrate/**/*'
|
17
|
+
- 'test/**/*'
|
18
|
+
|
19
|
+
Metrics/BlockLength:
|
20
|
+
Exclude:
|
21
|
+
- 'db/migrate/**/*'
|
22
|
+
- 'lib/tasks/**/*'
|
23
|
+
- 'spec/**/*.rb'
|
24
|
+
|
25
|
+
Metrics/MethodLength:
|
26
|
+
Exclude:
|
27
|
+
- 'db/migrate/**/*'
|
28
|
+
- 'test/**/*'
|
29
|
+
|
30
|
+
Style/Documentation:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Style/HashEachMethods:
|
34
|
+
Enabled: true
|
35
|
+
|
36
|
+
Style/HashTransformKeys:
|
37
|
+
Enabled: true
|
38
|
+
|
39
|
+
Style/HashTransformValues:
|
40
|
+
Enabled: true
|
data/SelfGemfile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class EventExceptionsController < ApplicationController
|
4
|
+
before_action :require_admin
|
5
|
+
layout 'admin'
|
6
|
+
before_action :clear_event_exception_unchecked, only: :index # rubocop:disable Rails/LexicallyScopedActionFilter
|
7
|
+
|
8
|
+
active_scaffold :event_exception do |conf|
|
9
|
+
conf.actions.exclude :create, :update, :delete
|
10
|
+
conf.list.columns = %i[created_at event_entity event_action listener_class]
|
11
|
+
conf.list.sorting = { created_at: :desc }
|
12
|
+
conf.action_links.add :download, type: :member, label: 'Download', page: true
|
13
|
+
end
|
14
|
+
|
15
|
+
def download
|
16
|
+
ex = find_if_allowed(params[:id], :read)
|
17
|
+
send_data ex.attributes.to_yaml, filename: download_filename(ex)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def download_filename(error)
|
23
|
+
[Setting.host_name, 'exception', error.id.to_s, error.created_at.to_s].map do |s|
|
24
|
+
s.parameterize.strip
|
25
|
+
end.select(&:present?).join('_')
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear_event_exception_unchecked
|
29
|
+
EventsManager::Settings.event_exception_unchecked = false if
|
30
|
+
EventsManager::Settings.event_exception_unchecked
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ListenerOptionsController < ApplicationController
|
4
|
+
before_action :require_admin
|
5
|
+
layout 'admin'
|
6
|
+
|
7
|
+
active_scaffold :listener_option do |conf|
|
8
|
+
conf.columns[:listener_class].form_ui = :select
|
9
|
+
conf.columns[:listener_class].options ||= {}
|
10
|
+
conf.columns[:listener_class].options[:options] = ::ListenerOption.listener_class_options
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class EventException < ActiveRecord::Base
|
4
|
+
self.inheritance_column = nil
|
5
|
+
|
6
|
+
validates :event_entity, :event_action, :event_data, :listener_class, :listener_instance,
|
7
|
+
:exception_class, :exception_message, :exception_stack, presence: true
|
8
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ListenerOption < ActiveRecord::Base
|
4
|
+
DEFAULT_DELAY = 0
|
5
|
+
DEFAULT_ENABLED = true
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def listener_class_list
|
9
|
+
@listener_class_list ||= ::EventsManager.all_listeners
|
10
|
+
end
|
11
|
+
|
12
|
+
def listener_class_options
|
13
|
+
listener_class_list.sort.map { |v| [v, v] }
|
14
|
+
end
|
15
|
+
|
16
|
+
def listener_delay(listener_class)
|
17
|
+
o = ::ListenerOption.find_by(listener_class: listener_class.to_s)
|
18
|
+
o && o.delay.present? && o.delay >= 0 ? o.delay : DEFAULT_DELAY
|
19
|
+
end
|
20
|
+
|
21
|
+
def listener_enabled?(listener_class)
|
22
|
+
o = ::ListenerOption.find_by(listener_class: listener_class.to_s)
|
23
|
+
o.present? ? o.enabled? : DEFAULT_ENABLED
|
24
|
+
end
|
25
|
+
|
26
|
+
def listener_enable(listener_class, enabled)
|
27
|
+
o = ::ListenerOption.find_or_create_by(listener_class: listener_class.to_s)
|
28
|
+
o.enabled = enabled
|
29
|
+
o.save!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
validates :listener_class, presence: true, uniqueness: true,
|
34
|
+
inclusion: { in: listener_class_list }
|
35
|
+
validates :delay, allow_blank: true,
|
36
|
+
numericality: { integer_only: true }
|
37
|
+
end
|
data/bin/bundle
ADDED
File without changes
|
data/config/routes.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateDelayedJobs < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
6
|
+
def self.up
|
7
|
+
create_table :delayed_jobs, force: true do |table|
|
8
|
+
table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of
|
9
|
+
# the queue
|
10
|
+
table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.
|
11
|
+
table.text :handler, null: false # YAML-encoded string of the object that will do work
|
12
|
+
table.text :last_error # reason for last failure (See Note below)
|
13
|
+
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
|
14
|
+
table.datetime :locked_at # Set when a client is working on this object
|
15
|
+
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
|
16
|
+
table.string :locked_by # Who is working on this object (if locked)
|
17
|
+
table.string :queue # The name of the queue this job is in
|
18
|
+
table.timestamps null: true
|
19
|
+
end
|
20
|
+
|
21
|
+
add_index :delayed_jobs, %i[priority run_at], name: 'delayed_jobs_priority'
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.down
|
25
|
+
drop_table :delayed_jobs
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateEventExceptions < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
6
|
+
def change
|
7
|
+
create_table :event_exceptions do |t|
|
8
|
+
t.string :event_entity
|
9
|
+
t.string :event_action
|
10
|
+
t.text :event_data
|
11
|
+
t.string :listener_class
|
12
|
+
t.string :listener_instance
|
13
|
+
t.string :exception_class
|
14
|
+
t.string :exception_message
|
15
|
+
t.text :exception_stack
|
16
|
+
|
17
|
+
t.timestamps null: false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateListenerOptions < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
6
|
+
def change
|
7
|
+
create_table :listener_options do |t|
|
8
|
+
t.string :listener_class
|
9
|
+
t.integer :delay
|
10
|
+
|
11
|
+
t.timestamps null: false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RenamePluginEventsManagerToRedmineEventsManager < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
6
|
+
class Settings < ActiveRecord::Base
|
7
|
+
end
|
8
|
+
|
9
|
+
def up
|
10
|
+
rename_plugin('events_manager', 'redmine_events_manager')
|
11
|
+
end
|
12
|
+
|
13
|
+
def down
|
14
|
+
rename_plugin('redmine_events_manager', 'events_manager')
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def rename_plugin(from, to)
|
20
|
+
old_record = Setting.find_by(name: from)
|
21
|
+
return if old_record.blank?
|
22
|
+
|
23
|
+
new_record = Setting.find_by(name: to)
|
24
|
+
if new_record.present?
|
25
|
+
new_value = plugin_setting_value(old_record).value.merge(plugin_setting_value(new_record))
|
26
|
+
new_record.update!(value: new_value)
|
27
|
+
old_record.destroy!
|
28
|
+
else
|
29
|
+
old_record.update!(name: to)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def plugin_setting_value(record)
|
34
|
+
if record.value.blank?
|
35
|
+
{}.with_indifferent_access
|
36
|
+
elsif record.value.is_a?(Hash)
|
37
|
+
record.value
|
38
|
+
else
|
39
|
+
raise "#{record} value is not Hash or blank"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redmine'
|
4
|
+
|
5
|
+
require 'events_manager/patches/issue_patch'
|
6
|
+
require 'events_manager/patches/issue_relation_patch'
|
7
|
+
require 'events_manager/patches/journal_patch'
|
8
|
+
require 'events_manager/patches/test_case_patch'
|
9
|
+
require 'events_manager/patches/time_entry_patch'
|
10
|
+
require 'events_manager/patches/repository/git_patch'
|
11
|
+
require 'events_manager'
|
12
|
+
require_dependency 'events_manager/hooks/add_assets'
|
13
|
+
require_dependency 'redmine_events_manager/version'
|
14
|
+
|
15
|
+
Redmine::Plugin.register :redmine_events_manager do
|
16
|
+
name 'Events Manager'
|
17
|
+
author ::RedmineEventsManager::AUTHOR
|
18
|
+
description ::RedmineEventsManager::SUMMARY
|
19
|
+
version ::RedmineEventsManager::VERSION
|
20
|
+
|
21
|
+
settings default: { event_exception_unchecked: false }
|
22
|
+
|
23
|
+
Redmine::MenuManager.map :admin_menu do |menu|
|
24
|
+
menu.push :event_exceptions, { controller: 'event_exceptions', action: 'index', id: nil },
|
25
|
+
caption: :label_event_exception_plural
|
26
|
+
menu.push :listener_options, { controller: 'listener_options', action: 'index', id: nil },
|
27
|
+
caption: :label_listener_option_plural
|
28
|
+
end
|
29
|
+
|
30
|
+
Redmine::MenuManager.map :top_menu do |menu|
|
31
|
+
menu.push :event_exception_unchecked,
|
32
|
+
{ controller: 'event_exceptions', action: 'index', id: nil },
|
33
|
+
caption: '', last: true, if: proc {
|
34
|
+
User.current.admin? && EventsManager::Settings.event_exception_unchecked
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
class << self
|
5
|
+
attr_accessor :delay_disabled, :log_exceptions_disabled
|
6
|
+
|
7
|
+
EVENT_EXCEPTION_ATTRIBUTES = {
|
8
|
+
event_entity: proc { |e, _l, _ex| e.entity.name },
|
9
|
+
event_action: proc { |e, _l, _ex| e.action.to_s },
|
10
|
+
event_data: proc { |e, _l, _ex| e.data.to_yaml },
|
11
|
+
listener_class: proc { |_e, l, _ex| l.class.name },
|
12
|
+
listener_instance: proc { |_e, l, _ex| l.to_s },
|
13
|
+
exception_class: proc { |_e, _l, ex| ex.class.name },
|
14
|
+
exception_message: proc { |_e, _l, ex| ex.message },
|
15
|
+
exception_stack: proc { |_e, _l, ex| ex.backtrace.join("\n") }
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def add_listener(entity, action, listener)
|
19
|
+
return if listeners(entity, action).include?(listener)
|
20
|
+
|
21
|
+
listeners(entity, action) << listener.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def trigger(entity, action, data)
|
25
|
+
event = EventsManager::Event.new(entity, action, data)
|
26
|
+
Rails.logger.debug("Event triggered: #{event}")
|
27
|
+
listeners(entity, action).each do |l|
|
28
|
+
Rails.logger.debug("Listener found: #{l}")
|
29
|
+
run_delayed_listener(event, l.constantize.new(event))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def all_listeners
|
34
|
+
r = []
|
35
|
+
@listeners.each_value do |e|
|
36
|
+
e.each_value do |a|
|
37
|
+
r += a
|
38
|
+
end
|
39
|
+
end
|
40
|
+
r.uniq
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def run_delayed_listener(event, listener)
|
46
|
+
return unless ::ListenerOption.listener_enabled?(listener.class)
|
47
|
+
|
48
|
+
if delay_disabled
|
49
|
+
run_listener(event, listener)
|
50
|
+
else
|
51
|
+
delay(run_at: ::ListenerOption.listener_delay(listener.class).seconds.from_now)
|
52
|
+
.run_listener(event, listener)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_listener(event, listener)
|
57
|
+
previous_locale = I18n.locale
|
58
|
+
begin
|
59
|
+
Rails.logger.info("Running listener: #{listener}")
|
60
|
+
I18n.locale = Setting.default_language
|
61
|
+
listener.run
|
62
|
+
rescue StandardError => e
|
63
|
+
on_listener_exception(event, listener, e)
|
64
|
+
ensure
|
65
|
+
I18n.locale = previous_locale
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def on_listener_exception(event, listener, exception)
|
70
|
+
raise exception if log_exceptions_disabled
|
71
|
+
|
72
|
+
Rails.logger.warn(exception)
|
73
|
+
begin
|
74
|
+
EventsManager::Settings.event_exception_unchecked = true
|
75
|
+
EventException.create!(event_exception_data(event, listener, exception))
|
76
|
+
rescue StandardError => e
|
77
|
+
Rails.logger.warn(e)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def event_exception_data(event, listener, exception)
|
82
|
+
data = {}
|
83
|
+
EVENT_EXCEPTION_ATTRIBUTES.each do |a, p|
|
84
|
+
data[a] = begin
|
85
|
+
p.call(event, listener, exception)
|
86
|
+
rescue StandardError => e
|
87
|
+
e.to_s
|
88
|
+
end
|
89
|
+
end
|
90
|
+
data
|
91
|
+
end
|
92
|
+
|
93
|
+
def listeners(entity, action)
|
94
|
+
@listeners ||= {}
|
95
|
+
@listeners[entity] ||= {}
|
96
|
+
@listeners[entity][action] ||= []
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
class Event
|
5
|
+
attr_reader :entity, :action, :data
|
6
|
+
|
7
|
+
def initialize(entity, action, data)
|
8
|
+
@entity = entity
|
9
|
+
@action = action
|
10
|
+
@data = data
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"#{entity}::#{action}|#{data.class}(#{data.id})"
|
15
|
+
end
|
16
|
+
|
17
|
+
def issue_create?
|
18
|
+
entity == ::Issue && action == :create
|
19
|
+
end
|
20
|
+
|
21
|
+
def issue_update?
|
22
|
+
entity == ::Issue && action == :update
|
23
|
+
end
|
24
|
+
|
25
|
+
def issue_relation_create?
|
26
|
+
entity == ::IssueRelation && action == :create
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
module Hooks
|
5
|
+
class AddAssets < Redmine::Hook::ViewListener
|
6
|
+
def view_layouts_base_html_head(_context = {})
|
7
|
+
stylesheet_link_tag(:application, plugin: 'redmine_events_manager') + "\n"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
module Patches
|
5
|
+
module IssuePatch
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
|
9
|
+
base.class_eval do
|
10
|
+
unloadable
|
11
|
+
|
12
|
+
after_create :issue_create_event
|
13
|
+
after_destroy :issue_destroy_event
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def issue_create_event
|
19
|
+
EventsManager.trigger(Issue, :create, self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def issue_destroy_event
|
23
|
+
EventsManager.trigger(Issue, :delete, self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
unless Issue.included_modules.include? EventsManager::Patches::IssuePatch
|
31
|
+
Issue.include EventsManager::Patches::IssuePatch
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
module Patches
|
5
|
+
module IssueRelationPatch
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
|
9
|
+
base.class_eval do
|
10
|
+
unloadable
|
11
|
+
|
12
|
+
after_create :issue_relation_create_event
|
13
|
+
after_destroy :issue_relation_destroy_event
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def issue_relation_create_event
|
19
|
+
EventsManager.trigger(IssueRelation, :create, self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def issue_relation_destroy_event
|
23
|
+
EventsManager.trigger(IssueRelation, :delete, self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
unless IssueRelation.included_modules.include? EventsManager::Patches::IssueRelationPatch
|
31
|
+
IssueRelation.include EventsManager::Patches::IssueRelationPatch
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
module Patches
|
5
|
+
module JournalPatch
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
|
9
|
+
base.class_eval do
|
10
|
+
unloadable
|
11
|
+
|
12
|
+
after_create :journal_create_event
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
def journal_create_event
|
18
|
+
return unless journalized_type == 'Issue'
|
19
|
+
|
20
|
+
EventsManager.trigger(Issue, :update, self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
unless Journal.included_modules.include? EventsManager::Patches::JournalPatch
|
28
|
+
Journal.include EventsManager::Patches::JournalPatch
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
module Patches
|
5
|
+
module Repository
|
6
|
+
module GitPatch
|
7
|
+
def fetch_changesets
|
8
|
+
super
|
9
|
+
EventsManager.trigger(::Repository, :receive, self)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
unless ::Repository::Git.included_modules.include?(EventsManager::Patches::Repository::GitPatch)
|
17
|
+
::Repository::Git.prepend(EventsManager::Patches::Repository::GitPatch)
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
module Patches
|
5
|
+
module TestCasePatch
|
6
|
+
def self.included(base)
|
7
|
+
base.setup { ::RedmineEventsManager::TestConfig.new.before_each }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
if Rails.env.test?
|
14
|
+
unless ::ActiveSupport::TestCase.included_modules.include? EventsManager::Patches::TestCasePatch
|
15
|
+
::ActiveSupport::TestCase.include EventsManager::Patches::TestCasePatch
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
module Patches
|
5
|
+
module TimeEntryPatch
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
|
9
|
+
base.class_eval do
|
10
|
+
unloadable
|
11
|
+
|
12
|
+
after_create :time_entry_create_event
|
13
|
+
after_destroy :time_entry_destroy_event
|
14
|
+
after_update :time_entry_update_event
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods
|
19
|
+
def time_entry_create_event
|
20
|
+
EventsManager.trigger(TimeEntry, :create, self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def time_entry_destroy_event
|
24
|
+
EventsManager.trigger(TimeEntry, :delete,
|
25
|
+
EventsManager::RemovedRecord.new(self))
|
26
|
+
end
|
27
|
+
|
28
|
+
def time_entry_update_event
|
29
|
+
EventsManager.trigger(TimeEntry, :update,
|
30
|
+
EventsManager::UpdatedRecord.new(self))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
unless TimeEntry.included_modules.include? EventsManager::Patches::TimeEntryPatch
|
38
|
+
TimeEntry.include EventsManager::Patches::TimeEntryPatch
|
39
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
class RemovedRecord
|
5
|
+
def initialize(record)
|
6
|
+
@attributes = record.attributes
|
7
|
+
@class = record.class
|
8
|
+
end
|
9
|
+
|
10
|
+
def copy_record
|
11
|
+
r = @class.new
|
12
|
+
@attributes.each do |k, v|
|
13
|
+
r.send("#{k}=", v)
|
14
|
+
end
|
15
|
+
r
|
16
|
+
end
|
17
|
+
|
18
|
+
def id
|
19
|
+
@attributes[:id]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EventsManager
|
4
|
+
class Settings
|
5
|
+
class << self
|
6
|
+
def event_exception_unchecked=(value)
|
7
|
+
s = Setting.plugin_redmine_events_manager.dup || {}
|
8
|
+
s[:event_exception_unchecked] = value ? true : false
|
9
|
+
Setting.plugin_redmine_events_manager = s
|
10
|
+
end
|
11
|
+
|
12
|
+
def event_exception_unchecked
|
13
|
+
Setting.plugin_redmine_events_manager[:event_exception_unchecked] ? true : false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RedmineEventsManager
|
4
|
+
class TestConfig
|
5
|
+
def before_each
|
6
|
+
EventsManager.delay_disabled = true
|
7
|
+
EventsManager.log_exceptions_disabled = true
|
8
|
+
EventsManager::Settings.event_exception_unchecked = false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :events_manager do
|
4
|
+
namespace :events do
|
5
|
+
namespace :issue_relation do
|
6
|
+
desc 'Envia notificações da criação de um IssueRelation'
|
7
|
+
task :create, [:issue_relation_id] => :environment do |_t, args|
|
8
|
+
EventsManager.trigger(
|
9
|
+
IssueRelation,
|
10
|
+
:create,
|
11
|
+
IssueRelation.find(args.issue_relation_id)
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
namespace :issue do
|
16
|
+
desc 'Envia notificações da criação de um Issue'
|
17
|
+
task :create, [:issue_id] => :environment do |_t, args|
|
18
|
+
EventsManager.trigger(
|
19
|
+
Issue,
|
20
|
+
:create,
|
21
|
+
Issue.find(args.issue_id)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Envia notificações da alteração de um Issue'
|
26
|
+
task :update, [:journal_id] => :environment do |_t, args|
|
27
|
+
EventsManager.trigger(
|
28
|
+
Issue,
|
29
|
+
:update,
|
30
|
+
Journal.find(args.journal_id)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
namespace :repository do
|
35
|
+
desc 'Ativa evento de recebimento de conteúdo por repositório'
|
36
|
+
task :receive, [:repository_id] => :environment do |_t, args|
|
37
|
+
EventsManager.delay_disabled = true
|
38
|
+
EventsManager.trigger(
|
39
|
+
Repository,
|
40
|
+
:receive,
|
41
|
+
Repository.find(args.repository_id)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
4
|
+
|
5
|
+
# Maintain your gem's version:
|
6
|
+
require 'redmine_events_manager/version'
|
7
|
+
|
8
|
+
# Describe your gem and declare its dependencies:
|
9
|
+
Gem::Specification.new do |s|
|
10
|
+
s.name = 'redmine_events_manager'
|
11
|
+
s.version = ::RedmineEventsManager::VERSION
|
12
|
+
s.authors = [::RedmineEventsManager::AUTHOR]
|
13
|
+
s.summary = ::RedmineEventsManager::SUMMARY
|
14
|
+
s.homepage = ::RedmineEventsManager::HOMEPAGE
|
15
|
+
|
16
|
+
s.require_paths = ['lib']
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files spec test`.split("\n")
|
19
|
+
|
20
|
+
s.add_dependency 'delayed_job_active_record'
|
21
|
+
|
22
|
+
# Test/development gems
|
23
|
+
s.add_development_dependency 'eac_ruby_gem_support', '~> 0.1', '>= 0.1.2'
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe EventExceptionsController, type: :feature do
|
4
|
+
include_context 'with logged user', 'admin' do
|
5
|
+
before do
|
6
|
+
EventsManager::Settings.event_exception_unchecked = true
|
7
|
+
end
|
8
|
+
|
9
|
+
it { expect(EventsManager::Settings.event_exception_unchecked).to be_truthy }
|
10
|
+
it { expect(::User.current.login).to eq('admin') }
|
11
|
+
|
12
|
+
context 'when index is accessed' do
|
13
|
+
before { visit '/event_exceptions' }
|
14
|
+
|
15
|
+
it { expect(EventsManager::Settings.event_exception_unchecked).to be_falsy }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe(::EventsManager) do
|
4
|
+
let(:stub_entity_class) { ::Class.new }
|
5
|
+
let(:stub_listener_class) { ::Class.new }
|
6
|
+
|
7
|
+
before do
|
8
|
+
described_class.add_listener(stub_entity_class, 'create', stub_listener_class)
|
9
|
+
described_class.add_listener(stub_entity_class, 'update', stub_listener_class)
|
10
|
+
end
|
11
|
+
|
12
|
+
it { expect(described_class.all_listeners).to be_a(Array) }
|
13
|
+
it { expect(described_class.all_listeners.count).to be_positive }
|
14
|
+
it { expect(described_class.all_listeners).to(be_all { |l| l.is_a?(::String) }) }
|
15
|
+
it { expect(described_class.all_listeners.count).to eq(described_class.all_listeners.uniq.count) }
|
16
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class DummyListener
|
4
|
+
def initialize(event)
|
5
|
+
@event = event
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
'dummy_listener_instance'
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
raise 'Dummy failed!' if @event.data.failed
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class DummyEntity
|
18
|
+
attr_reader :failed
|
19
|
+
|
20
|
+
def initialize(failed)
|
21
|
+
@failed = failed
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"dummy_entity_instance_#{failed}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def id
|
29
|
+
1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
RSpec.describe EventException do
|
34
|
+
let(:event_exception_count_start) { described_class.count }
|
35
|
+
|
36
|
+
before do
|
37
|
+
EventsManager.log_exceptions_disabled = false
|
38
|
+
event_exception_count_start
|
39
|
+
EventsManager.add_listener(DummyEntity, :create, 'DummyListener')
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when event triggered is successful' do
|
43
|
+
before do
|
44
|
+
EventsManager.trigger(DummyEntity, :create, DummyEntity.new(false))
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'successful event should not generate event exception' do
|
48
|
+
expect(described_class.count).to eq(event_exception_count_start)
|
49
|
+
end
|
50
|
+
|
51
|
+
it { expect(EventsManager::Settings.event_exception_unchecked).to be_falsy }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when event triggered is fail' do
|
55
|
+
let(:ee) { described_class.last }
|
56
|
+
|
57
|
+
before do
|
58
|
+
EventsManager.trigger(DummyEntity, :create, DummyEntity.new(true))
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'failed event should generate event exception' do
|
62
|
+
expect(described_class.count).to eq(event_exception_count_start + 1)
|
63
|
+
end
|
64
|
+
|
65
|
+
it { expect(ee).to be_truthy }
|
66
|
+
it { expect(ee.event_entity).to eq('DummyEntity') }
|
67
|
+
it { expect(ee.event_action).to eq('create') }
|
68
|
+
it { expect(ee.event_data).to eq(<<~DATA_CONTENT) }
|
69
|
+
--- !ruby/object:DummyEntity
|
70
|
+
failed: true
|
71
|
+
DATA_CONTENT
|
72
|
+
it { expect(ee.listener_class).to eq('DummyListener') }
|
73
|
+
it { expect(ee.listener_instance).to eq('dummy_listener_instance') }
|
74
|
+
it { expect(ee.exception_class).to eq('RuntimeError') }
|
75
|
+
it { expect(ee.exception_message).to eq('Dummy failed!') }
|
76
|
+
it { expect(ee.exception_stack).to be_present }
|
77
|
+
it { expect(EventsManager::Settings.event_exception_unchecked).to be_truthy }
|
78
|
+
end
|
79
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redmine_events_manager
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.8
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eduardo Henrique Bogoni
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-08-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: delayed_job_active_record
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: eac_ruby_gem_support
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 0.1.2
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0.1'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.1.2
|
47
|
+
description:
|
48
|
+
email:
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- ".avm.yml"
|
54
|
+
- ".gitignore"
|
55
|
+
- ".rubocop.yml"
|
56
|
+
- SelfGemfile
|
57
|
+
- app/controllers/event_exceptions_controller.rb
|
58
|
+
- app/controllers/listener_options_controller.rb
|
59
|
+
- app/models/event_exception.rb
|
60
|
+
- app/models/listener_option.rb
|
61
|
+
- assets/stylesheets/application.css
|
62
|
+
- bin/bundle
|
63
|
+
- config/initializers/000_dependencies.rb
|
64
|
+
- config/initializers/assets.rb
|
65
|
+
- config/locales/pt-BR.yml
|
66
|
+
- config/routes.rb
|
67
|
+
- db/migrate/20160509152749_create_delayed_jobs.rb
|
68
|
+
- db/migrate/20160717222418_create_event_exceptions.rb
|
69
|
+
- db/migrate/20180331122033_create_listener_options.rb
|
70
|
+
- db/migrate/20180419202618_add_enabled_to_listener_options.rb
|
71
|
+
- db/migrate/20180711170315_rename_plugin_events_manager_to_redmine_events_manager.rb
|
72
|
+
- init.rb
|
73
|
+
- lib/events_manager.rb
|
74
|
+
- lib/events_manager/event.rb
|
75
|
+
- lib/events_manager/hooks/add_assets.rb
|
76
|
+
- lib/events_manager/patches/issue_patch.rb
|
77
|
+
- lib/events_manager/patches/issue_relation_patch.rb
|
78
|
+
- lib/events_manager/patches/journal_patch.rb
|
79
|
+
- lib/events_manager/patches/repository/git_patch.rb
|
80
|
+
- lib/events_manager/patches/test_case_patch.rb
|
81
|
+
- lib/events_manager/patches/time_entry_patch.rb
|
82
|
+
- lib/events_manager/removed_record.rb
|
83
|
+
- lib/events_manager/settings.rb
|
84
|
+
- lib/events_manager/updated_record.rb
|
85
|
+
- lib/redmine_events_manager/test_config.rb
|
86
|
+
- lib/redmine_events_manager/version.rb
|
87
|
+
- lib/tasks/events.rake
|
88
|
+
- lib/tasks/redmine_events_manager.rake
|
89
|
+
- redmine_events_manager.gemspec
|
90
|
+
- spec/controllers/event_exceptions_controller_spec.rb
|
91
|
+
- spec/lib/events_manager_spec.rb
|
92
|
+
- spec/models/event_exception_spec.rb
|
93
|
+
homepage: https://github.com/esquilo-azul/redmine_events_manager
|
94
|
+
licenses: []
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubygems_version: 3.0.8
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: Management for events
|
115
|
+
test_files:
|
116
|
+
- spec/controllers/event_exceptions_controller_spec.rb
|
117
|
+
- spec/lib/events_manager_spec.rb
|
118
|
+
- spec/models/event_exception_spec.rb
|