redmine_events_manager 0.4.9
Sign up to get free protection for your applications and to get access to all the features.
- 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/001_requires.rb +12 -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 +28 -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 +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1b54a70e2373c4cf5eef6b70b467952e3f78b4a3540f5e7c2f85938de5940431
|
4
|
+
data.tar.gz: e25f7b2504e9c60daf8de44edb97e45e7f2492fe17d148609a64ce6bb4c9fa37
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: db965edd92a6d2e212d9d5c680536b439c2d4af7a1c138a2cb7208b2c26c1acbbe94a98350b85d9f0bcf3188773fcc878c3ea8b1f1006a0084e431880dafb15b
|
7
|
+
data.tar.gz: a660f3ee17f0e6bc30e8d9d8d8e9d555e6ffcd8c36782d5f5c0dd4a2ea0d3b6df23fd8523c34f30bfd42dce0554b67bf961f826743da747b868883bf00fadc53
|
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
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'delayed_job_active_record'
|
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'
|
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,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redmine'
|
4
|
+
require_dependency 'redmine_events_manager/version'
|
5
|
+
|
6
|
+
Redmine::Plugin.register :redmine_events_manager do
|
7
|
+
name 'Events Manager'
|
8
|
+
author ::RedmineEventsManager::AUTHOR
|
9
|
+
description ::RedmineEventsManager::SUMMARY
|
10
|
+
version ::RedmineEventsManager::VERSION
|
11
|
+
|
12
|
+
settings default: { event_exception_unchecked: false }
|
13
|
+
|
14
|
+
Redmine::MenuManager.map :admin_menu do |menu|
|
15
|
+
menu.push :event_exceptions, { controller: 'event_exceptions', action: 'index', id: nil },
|
16
|
+
caption: :label_event_exception_plural
|
17
|
+
menu.push :listener_options, { controller: 'listener_options', action: 'index', id: nil },
|
18
|
+
caption: :label_listener_option_plural
|
19
|
+
end
|
20
|
+
|
21
|
+
Redmine::MenuManager.map :top_menu do |menu|
|
22
|
+
menu.push :event_exception_unchecked,
|
23
|
+
{ controller: 'event_exceptions', action: 'index', id: nil },
|
24
|
+
caption: '', last: true, if: proc {
|
25
|
+
User.current.admin? && EventsManager::Settings.event_exception_unchecked
|
26
|
+
}
|
27
|
+
end
|
28
|
+
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,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redmine_events_manager
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eduardo Henrique Bogoni
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-08-17 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/001_requires.rb
|
65
|
+
- config/initializers/assets.rb
|
66
|
+
- config/locales/pt-BR.yml
|
67
|
+
- config/routes.rb
|
68
|
+
- db/migrate/20160509152749_create_delayed_jobs.rb
|
69
|
+
- db/migrate/20160717222418_create_event_exceptions.rb
|
70
|
+
- db/migrate/20180331122033_create_listener_options.rb
|
71
|
+
- db/migrate/20180419202618_add_enabled_to_listener_options.rb
|
72
|
+
- db/migrate/20180711170315_rename_plugin_events_manager_to_redmine_events_manager.rb
|
73
|
+
- init.rb
|
74
|
+
- lib/events_manager.rb
|
75
|
+
- lib/events_manager/event.rb
|
76
|
+
- lib/events_manager/hooks/add_assets.rb
|
77
|
+
- lib/events_manager/patches/issue_patch.rb
|
78
|
+
- lib/events_manager/patches/issue_relation_patch.rb
|
79
|
+
- lib/events_manager/patches/journal_patch.rb
|
80
|
+
- lib/events_manager/patches/repository/git_patch.rb
|
81
|
+
- lib/events_manager/patches/test_case_patch.rb
|
82
|
+
- lib/events_manager/patches/time_entry_patch.rb
|
83
|
+
- lib/events_manager/removed_record.rb
|
84
|
+
- lib/events_manager/settings.rb
|
85
|
+
- lib/events_manager/updated_record.rb
|
86
|
+
- lib/redmine_events_manager/test_config.rb
|
87
|
+
- lib/redmine_events_manager/version.rb
|
88
|
+
- lib/tasks/events.rake
|
89
|
+
- lib/tasks/redmine_events_manager.rake
|
90
|
+
- redmine_events_manager.gemspec
|
91
|
+
- spec/controllers/event_exceptions_controller_spec.rb
|
92
|
+
- spec/lib/events_manager_spec.rb
|
93
|
+
- spec/models/event_exception_spec.rb
|
94
|
+
homepage: https://github.com/esquilo-azul/redmine_events_manager
|
95
|
+
licenses: []
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubygems_version: 3.0.8
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Management for events
|
116
|
+
test_files:
|
117
|
+
- spec/controllers/event_exceptions_controller_spec.rb
|
118
|
+
- spec/lib/events_manager_spec.rb
|
119
|
+
- spec/models/event_exception_spec.rb
|