Added ticket job feature.

This commit is contained in:
Martin Edenhofer 2016-03-18 03:04:49 +01:00
parent cf2b451d8c
commit 1950f82322
23 changed files with 841 additions and 221 deletions

View file

@ -17,13 +17,13 @@ class App.UiElement.active extends App.UiElement.ApplicationUiElement
attribute.name = '{boolean}' + attribute.name
# build options list based on config
@getConfigOptionList( attribute, params )
@getConfigOptionList(attribute, params)
# sort attribute.options
@sortOptions( attribute, params )
@sortOptions(attribute, params)
# finde selected/checked item of list
@selectedOptions( attribute, params )
@selectedOptions(attribute, params)
# return item
$( App.view('generic/select')( attribute: attribute ) )

View file

@ -14,13 +14,13 @@ class App.UiElement.boolean extends App.UiElement.ApplicationUiElement
attribute.name = '{boolean}' + attribute.name
# build options list based on config
@getConfigOptionList( attribute, params )
@getConfigOptionList(attribute, params)
# sort attribute.options
@sortOptions( attribute, params )
@sortOptions(attribute, params)
# finde selected/checked item of list
@selectedOptions( attribute, params )
@selectedOptions(attribute, params)
# return item
$( App.view('generic/select')( attribute: attribute ) )
$(App.view('generic/select')(attribute: attribute))

View file

@ -3,24 +3,24 @@ class App.UiElement.radio extends App.UiElement.ApplicationUiElement
@render: (attribute, params) ->
# build options list based on config
@getConfigOptionList( attribute, params )
@getConfigOptionList(attribute, params)
# build options list based on relation
@getRelationOptionList( attribute, params )
@getRelationOptionList(attribute, params)
# add null selection if needed
@addNullOption( attribute, params )
@addNullOption(attribute, params)
# sort attribute.options
@sortOptions( attribute, params )
@sortOptions(attribute, params)
# finde selected/checked item of list
@selectedOptions( attribute, params )
@selectedOptions(attribute, params)
# disable item of list
@disabledOptions( attribute, params )
@disabledOptions(attribute, params)
# filter attributes
@filterOption( attribute, params )
@filterOption(attribute, params)
$( App.view('generic/radio')( attribute: attribute ) )

View file

@ -9,25 +9,25 @@ class App.UiElement.select extends App.UiElement.ApplicationUiElement
attribute.multiple = ''
# build options list based on config
@getConfigOptionList( attribute, params )
@getConfigOptionList(attribute, params)
# build options list based on relation
@getRelationOptionList( attribute, params )
@getRelationOptionList(attribute, params)
# add null selection if needed
@addNullOption( attribute, params )
@addNullOption(attribute, params)
# sort attribute.options
@sortOptions( attribute, params )
@sortOptions(attribute, params)
# finde selected/checked item of list
@selectedOptions( attribute, params )
@selectedOptions(attribute, params)
# disable item of list
@disabledOptions( attribute, params )
@disabledOptions(attribute, params)
# filter attributes
@filterOption( attribute, params )
@filterOption(attribute, params)
# return item
$( App.view('generic/select')( attribute: attribute ) )

View file

@ -5,5 +5,5 @@ class App.UiElement.tag
a = ->
$('#' + attribute.id ).tokenfield()
$('#' + attribute.id ).parent().css('height', 'auto')
App.Delay.set( a, 120, undefined, 'tags' )
App.Delay.set(a, 120, undefined, 'tags')
item

View file

@ -13,7 +13,7 @@ class App.UiElement.textarea
if visible && !$( item[0] ).expanding('active')
$( item[0] ).expanding().focus()
)
App.Delay.set( a, 80 )
App.Delay.set(a, 80)
if attribute.upload
@ -39,5 +39,5 @@ class App.UiElement.textarea
fail: ''
debug: false
)
App.Delay.set( u, 100, undefined, 'form_upload' )
App.Delay.set(u, 100, undefined, 'form_upload')
item

View file

@ -15,13 +15,13 @@ class App.UiElement.timezone extends App.UiElement.ApplicationUiElement
attribute.options.push item
# add null selection if needed
@addNullOption( attribute, params )
@addNullOption(attribute, params)
# sort attribute.options
@sortOptions( attribute, params )
@sortOptions(attribute, params)
# finde selected/checked item of list
@selectedOptions( attribute, params )
@selectedOptions(attribute, params)
attribute.tag = 'searchable_select'
attribute.placeholder = App.i18n.translateInline('Enter timzone...')

View file

@ -0,0 +1,29 @@
class Index extends App.ControllerContent
constructor: ->
super
# check authentication
return if !@authenticate(false, 'Admin')
new App.ControllerGenericIndex(
el: @el
id: @id
genericObject: 'Job'
defaultSortBy: 'name'
pageData:
title: 'Scheduler'
home: 'Jobs'
object: 'Scheduler'
objects: 'Schedulers'
navupdate: '#Jobs'
notes: [
'Scheduler are ...'
]
buttons: [
{ name: 'New Scheduler', 'data-type': 'new', class: 'btn--success' }
]
container: @el.closest('.content')
#large: true
)
App.Config.set('Job', { prio: 3400, name: 'Scheduler', parent: '#manage', target: '#manage/job', controller: Index, role: ['Admin'] }, 'NavBarAdmin')

View file

@ -1,88 +1,30 @@
class Index extends App.ControllerTabs
header: 'Trigger'
class Index extends App.ControllerContent
constructor: ->
super
@title 'Trigger', true
# check authentication
return if !@authenticate(false, 'Admin')
@tabs = [
{
name: 'Time Based',
target: 'c-time-based',
controller: App.TriggerTime,
},
{
name: 'Event Based',
target: 'c-event-based',
controller: App.SettingsArea,
params: { area: 'Email::Base' },
},
{
name: 'Notifications',
target: 'c-notification',
controller: App.SettingsArea,
params: { area: 'Email::Base' },
},
{
name: 'Web Hooks',
target: 'c-web-hook',
controller: App.SettingsArea,
params: { area: 'Email::Base' },
},
]
@render()
App.Config.set( 'Trigger', { prio: 3000, name: 'Trigger', parent: '#manage', target: '#manage/triggers', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
class App.TriggerTime extends App.Controller
events:
'click .js-new': 'new'
#'click .js-edit': 'edit'
'click .js-delete': 'delete'
constructor: ->
super
@interval(@load, 30000)
#@load()
load: =>
@startLoading()
@ajax(
id: 'trigger_time_index'
type: 'GET'
url: @apiPath + '/jobs'
processData: true
success: (data, status, xhr) =>
#App.Collection.loadAssets(data.assets)
@stopLoading()
@render(data)
)
render: (data = {}) =>
@html App.view('trigger/time/index')(
triggers: []
)
delete: (e) =>
e.preventDefault()
id = $(e.target).closest('.action').data('id')
item = App.Channel.find(id)
new App.ControllerGenericDestroyConfirm(
item: item
container: @el.closest('.content')
callback: @load
)
new: (e) =>
e.preventDefault()
channel_id = $(e.target).closest('.action').data('id')
new App.ControllerGenericNew(
new App.ControllerGenericIndex(
el: @el
id: @id
genericObject: 'Trigger'
defaultSortBy: 'name'
#groupBy: 'role'
pageData:
object: 'Jobs'
genericObject: 'Job'
title: 'Triggers'
home: 'triggers'
object: 'Trigger'
objects: 'Triggers'
navupdate: '#triggers'
notes: [
'Triggers are ...'
]
buttons: [
{ name: 'New Trigger', 'data-type': 'new', class: 'btn--success' }
]
container: @el.closest('.content')
callback: @load
#large: true
)
App.Config.set('Trigger', { prio: 3300, name: 'Trigger', parent: '#manage', target: '#manage/trigger', controller: Index, role: ['Admin'] }, 'NavBarAdmin')

View file

@ -1,12 +1,13 @@
class App.Job extends App.Model
@configure 'Job', 'name', 'timeplan', 'condition', 'execute', 'note', 'active'
@configure 'Job', 'name', 'timeplan', 'condition', 'perform', 'disable_notiifcation', 'note', 'active'
@extend Spine.Model.Ajax
@url: @apiPath + '/jobs'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
# { name: 'timeplan', display: 'The times where the job should run.', tag: 'timeplan', null: true },
{ name: 'condition', display: 'Conditions for matching objects.', tag: 'ticket_selector', null: true },
{ name: 'execute', display: 'Execute changes on objects.', tag: 'ticket_perform_action', null: true },
{ name: 'timeplan', display: 'When should the job run?', tag: 'timer', null: true },
{ name: 'condition', display: 'Conditions for effected objects', tag: 'ticket_selector', null: true },
{ name: 'perform', display: 'Execute changes on objects', tag: 'ticket_perform_action', null: true },
{ name: 'disable_notiifcation', display: 'Disable Notifications', tag: 'boolean', default: true },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
{ name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'matching', display: 'Matching', readonly: 1 },

View file

@ -0,0 +1,21 @@
class App.Trigger extends App.Model
@configure 'Trigger', 'name', 'condition', 'perform', 'active'
@extend Spine.Model.Ajax
@url: @apiPath + '/triggers'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'condition', display: 'Conditions for effected objects', tag: 'ticket_selector', null: false },
{ name: 'perform', display: 'Execute changes on objects', tag: 'ticket_perform_action', null: true },
{ name: 'disable_notiifcation', display: 'Disable Notifications', tag: 'boolean', default: true },
{ name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
]
@configure_delete = true
@configure_overview = [
'name',
]
@description = '''
Trigger are....
'''

View file

@ -0,0 +1,30 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class TriggersController < ApplicationController
before_action :authentication_check
def index
return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_index_render(Trigger, params)
end
def show
return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_show_render(Trigger, params)
end
def create
return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_create_render(Trigger, params)
end
def update
return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_update_render(Trigger, params)
end
def destroy
return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_destory_render(Trigger, params)
end
end

View file

@ -3,85 +3,118 @@
class Job < ApplicationModel
store :timeplan
store :condition
store :execute
store :perform
validates :name, presence: true
before_create :updated_matching
before_update :updated_matching
before_create :updated_matching
before_update :updated_matching
notify_clients_support
def self.run
time = Time.zone.now
day_map = {
0 => 'sun',
1 => 'mon',
2 => 'tue',
3 => 'wed',
4 => 'thu',
5 => 'fri',
6 => 'sat',
}
jobs = Job.where( active: true )
jobs = Job.where(active: true, running: false)
jobs.each do |job|
logger.debug "Execute job #{job.inspect}"
# only execute jobs, older then 1 min, to give admin posibility to change
next if job.updated_at > Time.zone.now - 1.minute
next if !job.executable?
# check if jobs need to be executed
# ignore if job was running within last 10 min.
next if job.last_run_at && job.last_run_at > Time.zone.now - 10.minutes
# check day
next if !job.timeplan['days'].include?( day_map[time.wday] )
# check hour
next if !job.timeplan['hours'].include?( time.hour.to_s )
# check min
next if !job.timeplan['minutes'].include?( match_minutes(time.min.to_s) )
# find tickets to change
tickets = Ticket.where( job.condition.permit! )
.order( '`tickets`.`created_at` DESC' )
.limit( 1_000 )
job.processed = tickets.count
tickets.each do |ticket|
logger.debug "CHANGE #{job.execute.inspect}"
changed = false
job.execute.each do |key, value|
changed = true
attribute = key.split('.', 2).last
logger.debug "-- #{Ticket.columns_hash[ attribute ].type}"
#value = 4
#if Ticket.columns_hash[ attribute ].type == :integer
# logger.debug "to i #{attribute}/#{value.inspect}/#{value.to_i.inspect}"
# #value = value.to_i
#end
ticket[attribute] = value
logger.debug "set #{attribute} = #{value.inspect}"
end
next if !changed
ticket.updated_by_id = 1
ticket.save
matching = job.matching_count
if job.matching != matching
job.matching = matching
job.save
end
next if !job.in_timeplan?
# find tickets to change
ticket_count, tickets = Ticket.selectors(job.condition, 2_000)
logger.debug "Job #{job.name} with #{ticket_count} tickets"
job.processed = ticket_count || 0
job.running = true
job.save
if tickets
tickets.each do |ticket|
logger.debug "Perform job #{job.perform.inspect} in Ticket.find(#{ticket.id})"
changed = false
job.perform.each do |key, value|
(object_name, attribute) = key.split('.', 2)
raise "Unable to update object #{object_name}.#{attribute}, only can update tickets!" if object_name != 'ticket'
next if ticket[attribute].to_s == value['value'].to_s
changed = true
ticket[attribute] = value['value']
logger.debug "set #{object_name}.#{attribute} = #{value['value'].inspect}"
end
next if !changed
ticket.updated_by_id = 1
ticket.save
end
end
job.running = false
job.last_run_at = Time.zone.now
job.save
end
true
end
def executable?
# only execute jobs, older then 1 min, to give admin posibility to change
return false if updated_at > Time.zone.now - 1.minute
# check if jobs need to be executed
# ignore if job was running within last 10 min.
return false if last_run_at && last_run_at > Time.zone.now - 10.minutes
true
end
def in_timeplan?
time = Time.zone.now
day_map = {
0 => 'Sun',
1 => 'Mon',
2 => 'Tue',
3 => 'Wed',
4 => 'Thu',
5 => 'Fri',
6 => 'Sat',
}
# check day
return false if !timeplan['days']
return false if !timeplan['days'][day_map[time.wday]]
# check hour
return false if !timeplan['hours']
return false if !timeplan['hours'][time.hour.to_s] && !timeplan['hours'][time.hour]
# check min
return false if !timeplan['minutes']
return false if !timeplan['minutes'][match_minutes(time.min).to_s] && !timeplan['minutes'][match_minutes(time.min)]
true
end
def matching_count
ticket_count, tickets = Ticket.selectors(condition, 1)
ticket_count || 0
end
private
def updated_matching
count = Ticket.where( condition.permit! ).count
self.matching = count
self.matching = matching_count
end
def self.match_minutes(minutes)
minutes.gsub!(/(\d)\d/, '\\1')
minutes.to_s + '0'
def match_minutes(minutes)
return 0 if minutes < 10
"#{minutes.to_s.gsub(/(\d)\d/, '\\1')}0".to_i
end
private_class_method :match_minutes
end

View file

@ -468,7 +468,7 @@ condition example
raise "Invalid selector, operator missing #{selector.inspect}" if !selector['operator']
# validate value / allow empty but only if pre_condition exists
if (selector['value'].class == String || selector['value'].class == Array) && (selector['value'].respond_to?(:empty?) && selector['value'].empty?)
if !selector.key?('value') || ((selector['value'].class == String || selector['value'].class == Array) && (selector['value'].respond_to?(:empty?) && selector['value'].empty?))
return nil if selector['pre_condition'].nil? || (selector['pre_condition'].respond_to?(:empty?) && selector['pre_condition'].empty?)
end
@ -679,7 +679,7 @@ result
return if !customer_id
customer = User.find( customer_id )
customer = User.find(customer_id)
return if organization_id == customer.organization_id
self.organization_id = customer.organization_id
@ -691,8 +691,8 @@ result
return if !changes['state_id']
# check if new state isn't pending*
current_state = Ticket::State.lookup( id: state_id )
current_state_type = Ticket::StateType.lookup( id: current_state.state_type_id )
current_state = Ticket::State.lookup(id: state_id)
current_state_type = Ticket::StateType.lookup(id: current_state.state_type_id)
# in case, set pending_time to nil
return if current_state_type.name =~ /^pending/i
@ -706,7 +706,7 @@ result
articles.destroy_all
# destroy online notifications
OnlineNotification.remove( self.class.to_s, id )
OnlineNotification.remove(self.class.to_s, id)
end
end

View file

@ -8,6 +8,7 @@ Zammad::Application.routes.draw do
match '/tests_form_find', to: 'tests#form_find', via: :get
match '/tests_form_trim', to: 'tests#form_trim', via: :get
match '/tests_form_extended', to: 'tests#form_extended', via: :get
match '/tests_form_timer', to: 'tests#form_timer', via: :get
match '/tests_form_validation', to: 'tests#form_validation', via: :get
match '/tests_form_column_select', to: 'tests#form_column_select', via: :get
match '/tests_form_searchable_select', to: 'tests#form_searchable_select', via: :get

11
config/routes/trigger.rb Normal file
View file

@ -0,0 +1,11 @@
Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
# triggers
match api_path + '/triggers', to: 'triggers#index', via: :get
match api_path + '/triggers/:id', to: 'triggers#show', via: :get
match api_path + '/triggers', to: 'triggers#create', via: :post
match api_path + '/triggers/:id', to: 'triggers#update', via: :put
match api_path + '/triggers/:id', to: 'triggers#destroy', via: :delete
end

View file

@ -188,7 +188,6 @@ class CreateTicket < ActiveRecord::Migration
add_index :ticket_counters, [:generator], unique: true
create_table :overviews do |t|
t.references :user, null: true
t.references :role, null: false
t.column :name, :string, limit: 250, null: false
t.column :link, :string, limit: 250, null: false
@ -203,9 +202,15 @@ class CreateTicket < ActiveRecord::Migration
t.column :created_by_id, :integer, null: false
t.timestamps null: false
end
add_index :overviews, [:user_id]
add_index :overviews, [:name]
create_table :overviews_users, id: false do |t|
t.integer :overview_id
t.integer :user_id
end
add_index :overviews_users, [:overview_id]
add_index :overviews_users, [:user_id]
create_table :overviews_groups, id: false do |t|
t.integer :overview_id
t.integer :group_id
@ -214,13 +219,36 @@ class CreateTicket < ActiveRecord::Migration
add_index :overviews_groups, [:group_id]
create_table :triggers do |t|
t.column :name, :string, limit: 250, null: false
t.column :key, :string, limit: 250, null: false
t.column :value, :string, limit: 250, null: false
t.column :name, :string, limit: 250, null: false
t.column :condition, :string, limit: 2500, null: false
t.column :perform, :string, limit: 2500, null: false
t.column :disable_notification, :boolean, null: false, default: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps null: false
end
add_index :triggers, [:name]
add_index :triggers, [:key]
add_index :triggers, [:value]
add_index :triggers, [:name], unique: true
create_table :jobs do |t|
t.column :name, :string, limit: 250, null: false
t.column :timeplan, :string, limit: 500, null: false
t.column :condition, :string, limit: 2500, null: false
t.column :perform, :string, limit: 2500, null: false
t.column :disable_notification, :boolean, null: false, default: true
t.column :last_run_at, :timestamp, null: true
t.column :running, :boolean, null: false, default: false
t.column :processed, :integer, null: false, default: 0
t.column :matching, :integer, null: false
t.column :pid, :string, limit: 250, null: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: false
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps null: false
end
add_index :jobs, [:name], unique: true
create_table :notifications do |t|
t.column :subject, :string, limit: 250, null: false

View file

@ -1,25 +0,0 @@
class CreateJob < ActiveRecord::Migration
def up
create_table :jobs do |t|
t.column :name, :string, limit: 250, null: false
t.column :timeplan, :string, limit: 500, null: false
t.column :condition, :string, limit: 2500, null: false
t.column :execute, :string, limit: 2500, null: false
t.column :last_run_at, :timestamp, null: true
t.column :running, :boolean, null: false, default: false
t.column :processed, :integer, null: false, default: 0
t.column :matching, :integer, null: false
t.column :pid, :string, limit: 250, null: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: false
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps null: false
end
add_index :jobs, [:name], unique: true
end
def down
drop_table :jobs
end
end

View file

@ -1,13 +0,0 @@
class OverviewUserRelation < ActiveRecord::Migration
def up
create_table :overviews_users, id: false do |t|
t.integer :overview_id
t.integer :user_id
end
add_index :overviews_users, [:overview_id]
add_index :overviews_users, [:user_id]
remove_column :overviews, :user_id
end
end

View file

@ -0,0 +1,48 @@
class RenewTriggers < ActiveRecord::Migration
def up
drop_table :triggers
create_table :triggers do |t|
t.column :name, :string, limit: 250, null: false
t.column :condition, :string, limit: 2500, null: false
t.column :perform, :string, limit: 2500, null: false
t.column :disable_notification, :boolean, null: false, default: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps null: false
end
add_index :triggers, [:name], unique: true
drop_table :jobs
create_table :jobs do |t|
t.column :name, :string, limit: 250, null: false
t.column :timeplan, :string, limit: 1000, null: false
t.column :condition, :string, limit: 2500, null: false
t.column :perform, :string, limit: 2500, null: false
t.column :disable_notification, :boolean, null: false, default: true
t.column :last_run_at, :timestamp, null: true
t.column :running, :boolean, null: false, default: false
t.column :processed, :integer, null: false, default: 0
t.column :matching, :integer, null: false, default: 0
t.column :pid, :string, limit: 250, null: true
t.column :note, :string, limit: 250, null: true
t.column :active, :boolean, null: false, default: false
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps null: false
end
add_index :jobs, [:name], unique: true
Scheduler.create_if_not_exists(
name: 'Execute jobs',
method: 'Job.run',
period: 5 * 60,
prio: 2,
active: true,
updated_by_id: 1,
created_by_id: 1,
)
end
end

View file

@ -3367,6 +3367,15 @@ Scheduler.create_if_not_exists(
updated_by_id: 1,
created_by_id: 1,
)
Scheduler.create_if_not_exists(
name: 'Execute jobs',
method: 'Job.run',
period: 5 * 60,
prio: 2,
active: true,
updated_by_id: 1,
created_by_id: 1,
)
Scheduler.create_if_not_exists(
name: 'Cleanup expired sessions',
method: 'SessionHelper.cleanup_expired',

425
test/unit/job_test.rb Normal file
View file

@ -0,0 +1,425 @@
# encoding: utf-8
require 'test_helper'
class JobTest < ActiveSupport::TestCase
test 'case 1' do
# create ticket
group1 = Group.lookup(name: 'Users')
group2 = Group.create_or_update(
name: 'JobTest2',
updated_by_id: 1,
created_by_id: 1,
)
ticket1 = Ticket.create(
title: 'job test 1',
group: group1,
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '2 normal'),
created_at: Time.zone.now - 3.days,
updated_at: Time.zone.now - 3.days,
created_by_id: 1,
updated_by_id: 1,
)
ticket2 = Ticket.create(
title: 'job test 2',
group: group1,
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '2 normal'),
created_at: Time.zone.now - 1.day,
created_by_id: 1,
updated_at: Time.zone.now - 1.day,
updated_by_id: 1,
)
ticket3 = Ticket.create(
title: 'job test 3',
group: group2,
customer_id: 2,
state: Ticket::State.lookup(name: 'open'),
priority: Ticket::Priority.lookup(name: '3 high'),
created_at: Time.zone.now - 1.day,
created_by_id: 1,
updated_at: Time.zone.now - 1.day,
updated_by_id: 1,
)
ticket4 = Ticket.create(
title: 'job test 4',
group: group2,
customer_id: 2,
state: Ticket::State.lookup(name: 'closed'),
priority: Ticket::Priority.lookup(name: '2 normal'),
created_at: Time.zone.now - 3.days,
created_by_id: 1,
updated_at: Time.zone.now - 3.days,
updated_by_id: 1,
)
ticket5 = Ticket.create(
title: 'job test 5',
group: group2,
customer_id: 2,
state: Ticket::State.lookup(name: 'open'),
priority: Ticket::Priority.lookup(name: '2 normal'),
created_at: Time.zone.now - 3.days,
created_by_id: 1,
updated_by_id: 1,
updated_at: Time.zone.now - 3.days,
)
# create jobs
job1 = Job.create_or_update(
name: 'Test Job1',
timeplan: {
days: {
Mon: false,
Tue: false,
Wed: false,
Thu: false,
Fri: false,
Sat: false,
Sun: false,
},
hours: {
0 => false,
1 => false,
2 => false,
3 => false,
4 => false,
5 => false,
6 => false,
7 => false,
8 => false,
9 => false,
10 => false,
11 => false,
12 => false,
13 => false,
14 => false,
15 => false,
16 => false,
17 => false,
18 => false,
19 => false,
20 => false,
21 => false,
22 => false,
23 => false,
},
minutes: {
0 => false,
10 => false,
20 => false,
30 => false,
40 => false,
50 => false,
},
},
condition: {
'ticket.state_id' => { 'operator' => 'is', 'value' => [Ticket::State.lookup(name: 'new').id.to_s, Ticket::State.lookup(name: 'open').id.to_s] },
'ticket.created_at' => { 'operator' => 'before (relative)', 'value' => '2', 'range' => 'day' },
},
perform: {
'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s }
},
disable_notification: true,
last_run_at: nil,
active: true,
created_by_id: 1,
created_at: Time.zone.now,
updated_by_id: 1,
updated_at: Time.zone.now,
)
assert_not(job1.executable?)
job1.last_run_at = Time.zone.now - 15.minutes
job1.save
assert_not(job1.executable?)
job1.updated_at = Time.zone.now - 15.minutes
job1.save
assert(job1.executable?)
assert_not(job1.in_timeplan?)
time = Time.zone.now
day_map = {
0 => 'Sun',
1 => 'Mon',
2 => 'Tue',
3 => 'Wed',
4 => 'Thu',
5 => 'Fri',
6 => 'Sat',
}
job1.timeplan['days'][day_map[time.wday]] = true
job1.save
assert_not(job1.in_timeplan?)
job1.timeplan['hours'][time.hour.to_s] = true
job1.save
assert_not(job1.in_timeplan?)
min = time.min
if min < 9
min = 0
elsif min < 20
min = 10
elsif min < 30
min = 20
elsif min < 40
min = 30
elsif min < 50
min = 40
elsif min < 59
min = 50
end
job1.timeplan['minutes'][min.to_s] = true
job1.save
assert(job1.in_timeplan?)
job1.timeplan['hours'][time.hour] = true
job1.save
job1.timeplan['minutes'][min] = true
job1.save
assert(job1.in_timeplan?)
# execute jobs
job1.updated_at = Time.zone.now - 15.minutes
job1.save
Job.run
assert(job1.executable?)
assert(job1.in_timeplan?)
# verify changes on tickets
ticket1_later = Ticket.find(ticket1.id)
assert_equal('closed', ticket1_later.state.name)
assert_not_equal(ticket1.updated_at.to_s, ticket1_later.updated_at.to_s)
ticket2_later = Ticket.find(ticket2.id)
assert_equal('new', ticket2_later.state.name)
assert_equal(ticket2.updated_at.to_s, ticket2_later.updated_at.to_s)
ticket3_later = Ticket.find(ticket3.id)
assert_equal('open', ticket3_later.state.name)
assert_equal(ticket3.updated_at.to_s, ticket3_later.updated_at.to_s)
ticket4_later = Ticket.find(ticket4.id)
assert_equal('closed', ticket4_later.state.name)
assert_equal(ticket4.updated_at.to_s, ticket4_later.updated_at.to_s)
ticket5_later = Ticket.find(ticket5.id)
assert_equal('closed', ticket5_later.state.name)
assert_not_equal(ticket5.updated_at.to_s, ticket5_later.updated_at.to_s)
# execute jobs again
job1.updated_at = Time.zone.now - 15.minutes
job1.save
Job.run
# verify changes on tickets
ticket1_later_next = Ticket.find(ticket1.id)
assert_equal('closed', ticket1_later_next.state.name)
assert_equal(ticket1_later.updated_at.to_s, ticket1_later_next.updated_at.to_s)
ticket2_later_next = Ticket.find(ticket2.id)
assert_equal('new', ticket2_later_next.state.name)
assert_equal(ticket2_later.updated_at.to_s, ticket2_later_next.updated_at.to_s)
ticket3_later_next = Ticket.find(ticket3.id)
assert_equal('open', ticket3_later_next.state.name)
assert_equal(ticket3_later.updated_at.to_s, ticket3_later_next.updated_at.to_s)
ticket4_later_next = Ticket.find(ticket4.id)
assert_equal('closed', ticket4_later_next.state.name)
assert_equal(ticket4_later.updated_at.to_s, ticket4_later_next.updated_at.to_s)
ticket5_later_next = Ticket.find(ticket5.id)
assert_equal('closed', ticket5_later_next.state.name)
assert_equal(ticket5_later.updated_at.to_s, ticket5_later_next.updated_at.to_s)
end
test 'case 2' do
# create ticket
group1 = Group.lookup(name: 'Users')
group2 = Group.create_or_update(
name: 'JobTest2',
updated_by_id: 1,
created_by_id: 1,
)
ticket1 = Ticket.create(
title: 'job test 1',
group: group1,
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '2 normal'),
created_at: Time.zone.now - 3.days,
updated_at: Time.zone.now - 3.days,
created_by_id: 1,
updated_by_id: 1,
)
ticket2 = Ticket.create(
title: 'job test 2',
group: group1,
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '2 normal'),
created_at: Time.zone.now - 1.day,
created_by_id: 1,
updated_at: Time.zone.now - 1.day,
updated_by_id: 1,
)
# create jobs
job1 = Job.create_or_update(
name: 'Test Job1',
timeplan: {
days: {
Mon: true,
Tue: true,
Wed: true,
Thu: true,
Fri: true,
Sat: true,
Sun: true,
},
hours: {
0 => true,
1 => true,
2 => true,
3 => true,
4 => true,
5 => true,
6 => true,
7 => true,
8 => true,
9 => true,
10 => true,
11 => true,
12 => true,
13 => true,
14 => true,
15 => true,
16 => true,
17 => true,
18 => true,
19 => true,
20 => true,
21 => true,
22 => true,
23 => true,
},
minutes: {
0 => true,
10 => true,
20 => true,
30 => true,
40 => true,
50 => true,
},
},
condition: {
'ticket.state_id' => { 'operator' => 'is', 'value' => '' },
'ticket.created_at' => { 'operator' => 'before (relative)', 'value' => '2', 'range' => 'day' },
},
perform: {
'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s }
},
disable_notification: true,
last_run_at: nil,
updated_at: Time.zone.now - 15.minutes,
active: true,
updated_by_id: 1,
created_by_id: 1,
)
assert(job1.executable?)
assert(job1.in_timeplan?)
Job.run
# verify changes on tickets
ticket1_later = Ticket.find(ticket1.id)
assert_equal('new', ticket1_later.state.name)
assert_equal(ticket1.updated_at.to_s, ticket1_later.updated_at.to_s)
ticket2_later = Ticket.find(ticket2.id)
assert_equal('new', ticket2_later.state.name)
assert_equal(ticket2.updated_at.to_s, ticket2_later.updated_at.to_s)
job1 = Job.create_or_update(
name: 'Test Job1',
timeplan: {
days: {
Mon: true,
Tue: true,
Wed: true,
Thu: true,
Fri: true,
Sat: true,
Sun: true,
},
hours: {
0 => true,
1 => true,
2 => true,
3 => true,
4 => true,
5 => true,
6 => true,
7 => true,
8 => true,
9 => true,
10 => true,
11 => true,
12 => true,
13 => true,
14 => true,
15 => true,
16 => true,
17 => true,
18 => true,
19 => true,
20 => true,
21 => true,
22 => true,
23 => true,
},
minutes: {
0 => true,
10 => true,
20 => true,
30 => true,
40 => true,
50 => true,
},
},
condition: {
'ticket.state_id' => { 'operator' => 'is' },
'ticket.created_at' => { 'operator' => 'before (relative)', 'value' => '2', 'range' => 'day' },
},
perform: {
'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s }
},
disable_notification: true,
last_run_at: nil,
updated_at: Time.zone.now - 15.minutes,
active: true,
updated_by_id: 1,
created_by_id: 1,
)
assert(job1.executable?)
assert(job1.in_timeplan?)
Job.run
# verify changes on tickets
ticket1_later = Ticket.find(ticket1.id)
assert_equal('new', ticket1_later.state.name)
assert_equal(ticket1.updated_at.to_s, ticket1_later.updated_at.to_s)
ticket2_later = Ticket.find(ticket2.id)
assert_equal('new', ticket2_later.state.name)
assert_equal(ticket2.updated_at.to_s, ticket2_later.updated_at.to_s)
end
end

View file

@ -146,6 +146,86 @@ class TicketSelectorTest < ActiveSupport::TestCase
ticket_count, tickets = Ticket.selectors(condition, 10, customer1)
assert_equal(ticket_count, 0)
# search matching with empty value / missing key
condition = {
'ticket.group_id' => {
operator: 'is',
value: group.id,
},
'ticket.state_id' => {
operator: 'is',
},
}
ticket_count, tickets = Ticket.selectors(condition, 10)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, agent1)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, agent2)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, customer1)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, customer2)
assert_equal(ticket_count, nil)
# search matching with empty value []
condition = {
'ticket.group_id' => {
operator: 'is',
value: group.id,
},
'ticket.state_id' => {
operator: 'is',
value: [],
},
}
ticket_count, tickets = Ticket.selectors(condition, 10)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, agent1)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, agent2)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, customer1)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, customer2)
assert_equal(ticket_count, nil)
# search matching with empty value ''
condition = {
'ticket.group_id' => {
operator: 'is',
value: group.id,
},
'ticket.state_id' => {
operator: 'is',
value: '',
},
}
ticket_count, tickets = Ticket.selectors(condition, 10)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, agent1)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, agent2)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, customer1)
assert_equal(ticket_count, nil)
ticket_count, tickets = Ticket.selectors(condition, 10, customer2)
assert_equal(ticket_count, nil)
# search matching
condition = {
'ticket.group_id' => {