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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.avm.yml +3 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +40 -0
  5. data/SelfGemfile +5 -0
  6. data/app/controllers/event_exceptions_controller.rb +32 -0
  7. data/app/controllers/listener_options_controller.rb +12 -0
  8. data/app/models/event_exception.rb +8 -0
  9. data/app/models/listener_option.rb +37 -0
  10. data/assets/stylesheets/application.css +8 -0
  11. data/bin/bundle +5 -0
  12. data/config/initializers/000_dependencies.rb +6 -0
  13. data/config/initializers/assets.rb +0 -0
  14. data/config/locales/pt-BR.yml +7 -0
  15. data/config/routes.rb +11 -0
  16. data/db/migrate/20160509152749_create_delayed_jobs.rb +27 -0
  17. data/db/migrate/20160717222418_create_event_exceptions.rb +20 -0
  18. data/db/migrate/20180331122033_create_listener_options.rb +14 -0
  19. data/db/migrate/20180419202618_add_enabled_to_listener_options.rb +9 -0
  20. data/db/migrate/20180711170315_rename_plugin_events_manager_to_redmine_events_manager.rb +42 -0
  21. data/init.rb +37 -0
  22. data/lib/events_manager.rb +99 -0
  23. data/lib/events_manager/event.rb +29 -0
  24. data/lib/events_manager/hooks/add_assets.rb +11 -0
  25. data/lib/events_manager/patches/issue_patch.rb +32 -0
  26. data/lib/events_manager/patches/issue_relation_patch.rb +32 -0
  27. data/lib/events_manager/patches/journal_patch.rb +29 -0
  28. data/lib/events_manager/patches/repository/git_patch.rb +18 -0
  29. data/lib/events_manager/patches/test_case_patch.rb +17 -0
  30. data/lib/events_manager/patches/time_entry_patch.rb +39 -0
  31. data/lib/events_manager/removed_record.rb +22 -0
  32. data/lib/events_manager/settings.rb +17 -0
  33. data/lib/events_manager/updated_record.rb +14 -0
  34. data/lib/redmine_events_manager/test_config.rb +11 -0
  35. data/lib/redmine_events_manager/version.rb +8 -0
  36. data/lib/tasks/events.rake +46 -0
  37. data/lib/tasks/redmine_events_manager.rake +4 -0
  38. data/redmine_events_manager.gemspec +24 -0
  39. data/spec/controllers/event_exceptions_controller_spec.rb +18 -0
  40. data/spec/lib/events_manager_spec.rb +16 -0
  41. data/spec/models/event_exception_spec.rb +79 -0
  42. metadata +118 -0
@@ -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
@@ -0,0 +1,3 @@
1
+ ruby:
2
+ rubocop:
3
+ gemfile: SelfGemfile
@@ -0,0 +1 @@
1
+ /SelfGemfile.lock
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
@@ -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
@@ -0,0 +1,8 @@
1
+ #top-menu .event-exception-unchecked {
2
+ background-image: url('../../../images/warning.png');
3
+ background-position: 0% 40%;
4
+ background-repeat: no-repeat;
5
+ padding-left: 20px;
6
+ padding-top: 2px;
7
+ padding-bottom: 3px;
8
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../SelfGemfile', __dir__)
5
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ Redmine::Plugin.post_register :redmine_events_manager do
4
+ # Source: https://github.com/esquilo-azul/redmine_nonproject_modules
5
+ requires_redmine_plugin(:redmine_nonproject_modules, version_or_higher: '0.2.1')
6
+ end
File without changes
@@ -0,0 +1,7 @@
1
+ pt-BR:
2
+ activerecord:
3
+ description:
4
+ listener_option:
5
+ delay: Atraso em segundos para o listener ser processado.
6
+ label_event_exception_plural: Exceções em eventos
7
+ label_listener_option_plural: Opções de listeners
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RedmineApp::Application.routes.draw do
4
+ resources(:event_exceptions) do
5
+ as_routes
6
+ member do
7
+ get :download
8
+ end
9
+ end
10
+ resources(:listener_options) { as_routes }
11
+ end
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddEnabledToListenerOptions < (
4
+ Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
5
+ )
6
+ def change
7
+ add_column :listener_options, :enabled, :boolean, null: false, default: true
8
+ end
9
+ 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,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventsManager
4
+ class UpdatedRecord
5
+ attr_reader :record, :changes
6
+
7
+ def initialize(record)
8
+ @record = record
9
+ @changes = record.changes
10
+ end
11
+
12
+ delegate :id, to: :record
13
+ end
14
+ 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RedmineEventsManager
4
+ AUTHOR = 'Eduardo Henrique Bogoni'
5
+ HOMEPAGE = 'https://github.com/esquilo-azul/redmine_events_manager'
6
+ SUMMARY = 'Management for events'
7
+ VERSION = '0.4.8'
8
+ 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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redmine_plugins_helper/plugin_rake_task'
4
+ ::RedminePluginsHelper::PluginRakeTask.register(:redmine_events_manager, :test)
@@ -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