diff --git a/Gemfile b/Gemfile index 154816e..3416bef 100644 --- a/Gemfile +++ b/Gemfile @@ -25,6 +25,8 @@ gem 'jbuilder', '~> 2.7' # Use Active Storage variant # gem 'image_processing', '~> 1.2' +gem 'chronic' + gem 'nokogiri', "1.11.0" # rexml is no longer default gem in Ruby 3.0 diff --git a/Gemfile.lock b/Gemfile.lock index 9852873..3bc7d56 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,6 +77,7 @@ GEM regexp_parser (~> 1.5) xpath (~> 3.2) childprocess (3.0.0) + chronic (0.10.2) coderay (1.1.3) concurrent-ruby (1.1.7) connection_pool (2.2.3) @@ -257,6 +258,7 @@ DEPENDENCIES bootsnap (>= 1.4.2) byebug capybara (>= 2.15) + chronic devise elasticsearch-persistence elasticsearch-rails diff --git a/app/models/email.rb b/app/models/email.rb index d25604f..22946d0 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -21,6 +21,7 @@ class Email attribute :organisations, default: [] attribute :servers, default: [] attribute :issues, default: [] + attribute :postponed_until, :datetime attribute :created_at, :datetime, default: DateTime.now attribute :updated_at, :datetime, default: DateTime.now @@ -47,6 +48,10 @@ class Email mailing_list end + def postponed? + postponed_until.present? && postponed_until > DateTime.now + end + def header_values(header_name) headers.select { |header| header["name"] == header_name diff --git a/app/services/email_action/base.rb b/app/services/email_action/base.rb index 40faa2c..307a581 100644 --- a/app/services/email_action/base.rb +++ b/app/services/email_action/base.rb @@ -9,7 +9,7 @@ module EmailAction attr_reader :action - def initialize(action) + def initialize(action:) @action = action end diff --git a/app/services/email_action/postpone.rb b/app/services/email_action/postpone.rb new file mode 100644 index 0000000..5daeb29 --- /dev/null +++ b/app/services/email_action/postpone.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module EmailAction + class Postpone < Base + + def process(email) + if date = Chronic.parse(action.argument) + email.postponed_until = date + else + Rails.logger.warn "Skipped action##{action.id} '#{action.name}' - Unparsable argument for Chronic : '#{action.argument}'" + end + + email + end + + end +end diff --git a/app/services/rule_set_processor.rb b/app/services/rule_set_processor.rb index 9488167..f3e30db 100644 --- a/app/services/rule_set_processor.rb +++ b/app/services/rule_set_processor.rb @@ -50,7 +50,7 @@ class RuleSetProcessor email = email_action.process(email) rescue NameError => ex Rails.logger.error "Skipped action##{action.id} '#{action.name}' - #{ex.inspect}" - raise InvalidRule, ex.message + raise InvalidRule, ex.inspect end email @@ -60,6 +60,8 @@ class RuleSetProcessor case rule.subject_type.downcase when "header" Array(email.header_values(rule.subject_value)) + when "subject" + Array(email.subject) when "body" Array(email.plain_body) else @@ -74,7 +76,7 @@ class RuleSetProcessor when "match", "matches" subjects.any? { |subject| pattern = Regexp.new(rule.condition_value) - subject.match? + subject.match? pattern } when "equal", "equals" subjects.any? { |subject| diff --git a/db/migrate/20210118132809_create_actions.rb b/db/migrate/20210118132809_create_actions.rb index dc293ed..aa73121 100644 --- a/db/migrate/20210118132809_create_actions.rb +++ b/db/migrate/20210118132809_create_actions.rb @@ -5,6 +5,7 @@ class CreateActions < ActiveRecord::Migration[6.1] t.string :name, null: false t.boolean :enabled, default: true, null: false t.string :class_name, null: false + t.string :argument t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index c011125..442fa33 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -23,9 +23,10 @@ ActiveRecord::Schema.define(version: 2021_01_18_132809) do create_table "actions", force: :cascade do |t| t.integer "rule_set_id", null: false - t.string "name" - t.boolean "enabled", default: true - t.string "class_name" + t.string "name", null: false + t.boolean "enabled", default: true, null: false + t.string "class_name", null: false + t.string "argument" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["rule_set_id"], name: "index_actions_on_rule_set_id" @@ -70,9 +71,9 @@ ActiveRecord::Schema.define(version: 2021_01_18_132809) do t.text "raw_headers" t.boolean "cron" t.boolean "mailing_list" - t.string "organisations" + t.string "clients" t.string "servers" - t.string "issues" + t.string "tickets" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end diff --git a/test/fixtures/actions.yml b/test/fixtures/actions.yml index 0ec7ab1..e3e8525 100644 --- a/test/fixtures/actions.yml +++ b/test/fixtures/actions.yml @@ -35,3 +35,24 @@ metadata: name: Add metadata to mail enabled: true class_name: EmailAction::MetadataMapping + +postpone_future_valid: + rule_set: postpone_future_valid + name: Postpone for 5 days + enabled: true + class_name: EmailAction::Postpone + argument: <%= Chronic.parse "5 days from now" %> + +postpone_past_valid: + rule_set: postpone_past_valid + name: Postpone in the past + enabled: true + class_name: EmailAction::Postpone + argument: <%= Chronic.parse "2 days ago" %> + +postpone_invalid: + rule_set: postpone_invalid + name: Postpone to invalid date + enabled: true + class_name: EmailAction::Postpone + argument: Foo Bar Baz diff --git a/test/fixtures/rule_sets.yml b/test/fixtures/rule_sets.yml index df59850..e4f863c 100644 --- a/test/fixtures/rule_sets.yml +++ b/test/fixtures/rule_sets.yml @@ -58,3 +58,15 @@ mailing_list: name: FromMailingList description: Is this mail from amailing list? enabled: true + +postpone_future_valid: + name: Postpone to valid date in the future + enabled: true + +postpone_past_valid: + name: Postpone to valid date in the past + enabled: true + +postpone_invalid: + name: Postpone to invalid date + enabled: true diff --git a/test/fixtures/rules.yml b/test/fixtures/rules.yml index 920f8f8..c9411bb 100644 --- a/test/fixtures/rules.yml +++ b/test/fixtures/rules.yml @@ -49,3 +49,30 @@ invalid_operator: condition_type: contain condition_value: MyString inverted: false + +postpone_future_valid: + rule_set: postpone_future_valid + name: Match Postponable in Subject + enabled: true + subject_type: Subject + condition_type: match + condition_value: Postponable + inverted: false + +postpone_past_valid: + rule_set: postpone_past_valid + name: Match Postponable in Subject + enabled: true + subject_type: Subject + condition_type: match + condition_value: Postponable + inverted: false + +postpone_invalid: + rule_set: postpone_invalid + name: Match Postponable in Subject + enabled: true + subject_type: Subject + condition_type: match + condition_value: Postponable + inverted: false diff --git a/test/services/rule_set_processor_test.rb b/test/services/rule_set_processor_test.rb index 3585ff1..11accd2 100644 --- a/test/services/rule_set_processor_test.rb +++ b/test/services/rule_set_processor_test.rb @@ -80,4 +80,30 @@ class RuleSetProcessorTest < ActiveSupport::TestCase assert_not_predicate email, :changed? end + test "postponed to valid future date" do + email = Email.new(subject: "Postponable") + processor = RuleSetProcessor.new + email = processor.process(rule_sets(:postpone_future_valid), email) + + assert_not_nil email.postponed_until + assert_predicate email, :postponed? + end + + test "postponed to valid past date" do + email = Email.new(subject: "Postponable") + processor = RuleSetProcessor.new + email = processor.process(rule_sets(:postpone_past_valid), email) + + assert_not_nil email.postponed_until + assert_not_predicate email, :postponed? + end + + test "postponed to invalid date" do + email = Email.new(subject: "Postponable") + processor = RuleSetProcessor.new + email = processor.process(rule_sets(:postpone_invalid), email) + + assert_nil email.postponed_until + assert_not_predicate email, :postponed? + end end