Performance: Observing every Overview of each Agent Session doesn't scale well on larger systems (60 Overviews per Agent). With this change only the last 5 used Overviews are checked on every iteration. A full check is still performed ever 60 seconds. This reduces the overall load.
This commit is contained in:
parent
f41aa097ce
commit
4e8cf209ca
12 changed files with 215 additions and 48 deletions
|
@ -936,6 +936,10 @@ class Navbar extends App.Controller
|
|||
if item.link is @view
|
||||
@title item.name, true
|
||||
|
||||
# send first view info
|
||||
if !@view && data && data[0] && data[0].link
|
||||
App.WebSocket.send(event:'ticket_overview_select', data: { view: data[0].link })
|
||||
|
||||
# redirect to first view
|
||||
if @activeState && !@view && !@vertical
|
||||
view = data[0].link
|
||||
|
@ -1037,6 +1041,8 @@ class Table extends App.Controller
|
|||
@view_mode = App.LocalStorage.get("mode:#{@view}", @Session.get('id')) || 's'
|
||||
console.log 'notice', 'view:', @view, @view_mode
|
||||
|
||||
App.WebSocket.send(event:'ticket_overview_select', data: { view: @view })
|
||||
|
||||
# get ticket list
|
||||
ticketListShow = []
|
||||
for ticket in tickets
|
||||
|
|
|
@ -894,6 +894,10 @@ class App.TicketZoom extends App.Controller
|
|||
if macro && macro.ux_flow_next_up
|
||||
taskAction = macro.ux_flow_next_up
|
||||
|
||||
nextTicket = undefined
|
||||
if taskAction is 'closeNextInOverview' || taskAction is 'next_from_overview'
|
||||
nextTicket = @getNextTicketInOverview()
|
||||
|
||||
# submit changes
|
||||
@ajax(
|
||||
id: "ticket_update_#{ticket.id}"
|
||||
|
@ -916,8 +920,8 @@ class App.TicketZoom extends App.Controller
|
|||
@sidebarWidget.commit()
|
||||
|
||||
if taskAction is 'closeNextInOverview' || taskAction is 'next_from_overview'
|
||||
@openTicketInOverview(nextTicket)
|
||||
App.Event.trigger('overview:fetch')
|
||||
@taskOpenNextTicketInOverview()
|
||||
return
|
||||
|
||||
if taskAction is 'closeTab' || taskAction is 'next_task'
|
||||
|
|
|
@ -22,22 +22,40 @@ App.TicketNavigable =
|
|||
show: true
|
||||
)
|
||||
|
||||
getNextTicketInOverview: ->
|
||||
return if !@ticket
|
||||
return if !@overview_id
|
||||
|
||||
App.Overview.find(@overview_id).nextTicket(@ticket)
|
||||
|
||||
openTicketInOverview: (nextTicket) ->
|
||||
if nextTicket
|
||||
@taskCloseTicket()
|
||||
@taskLoadTicket(nextTicket.id)
|
||||
return
|
||||
|
||||
@taskCloseTicket(true)
|
||||
|
||||
taskOpenNextTicketInOverview: ->
|
||||
if !(@overview_id? && @ticket?)
|
||||
@taskCloseTicket(true)
|
||||
return
|
||||
next_ticket = App.Overview.find(@overview_id).nextTicket(@ticket)
|
||||
if next_ticket
|
||||
|
||||
nextTicket = @getNextTicketInOverview()
|
||||
if nextTicket
|
||||
@taskCloseTicket()
|
||||
@taskLoadTicket(next_ticket.id)
|
||||
@taskLoadTicket(nextTicket.id)
|
||||
return
|
||||
|
||||
@taskCloseTicket(true)
|
||||
|
||||
taskCloseTicket: (openNext = false) ->
|
||||
App.TaskManager.remove(@taskKey)
|
||||
return if !openNext
|
||||
|
||||
nextTaskUrl = App.TaskManager.nextTaskUrl()
|
||||
if nextTaskUrl
|
||||
@navigate nextTaskUrl
|
||||
return
|
||||
|
||||
@navigate '#'
|
||||
|
|
|
@ -7,6 +7,10 @@ all overviews by user
|
|||
|
||||
result = Ticket::Overviews.all(current_user: User.find(3))
|
||||
|
||||
certain overviews by user
|
||||
|
||||
result = Ticket::Overviews.all(current_user: User.find(3), links: ['all_unassigned', 'my_assigned'])
|
||||
|
||||
returns
|
||||
|
||||
result = [overview1, overview2]
|
||||
|
@ -15,6 +19,7 @@ returns
|
|||
|
||||
def self.all(data)
|
||||
current_user = data[:current_user]
|
||||
links = data[:links]
|
||||
|
||||
# get customer overviews
|
||||
role_ids = User.joins(:roles).where(users: { id: current_user.id, active: true }, roles: { active: true }).pluck('roles.id')
|
||||
|
@ -23,6 +28,9 @@ returns
|
|||
if current_user.organization_id && current_user.organization.shared
|
||||
overview_filter.delete(:organization_shared)
|
||||
end
|
||||
if links.present?
|
||||
overview_filter[:link] = links
|
||||
end
|
||||
overviews = Overview.joins(:roles).left_joins(:users).where(overviews_roles: { role_id: role_ids }, overviews_users: { user_id: nil }, overviews: overview_filter).or(Overview.joins(:roles).left_joins(:users).where(overviews_roles: { role_id: role_ids }, overviews_users: { user_id: current_user.id }, overviews: overview_filter)).distinct('overview.id').order(:prio, :name)
|
||||
return overviews
|
||||
end
|
||||
|
@ -35,12 +43,21 @@ returns
|
|||
if User.where('out_of_office = ? AND out_of_office_start_at <= ? AND out_of_office_end_at >= ? AND out_of_office_replacement_id = ? AND active = ?', true, Time.zone.today, Time.zone.today, current_user.id, true).count.positive?
|
||||
overview_filter_not = {}
|
||||
end
|
||||
if links.present?
|
||||
overview_filter[:link] = links
|
||||
end
|
||||
Overview.joins(:roles).left_joins(:users).where(overviews_roles: { role_id: role_ids }, overviews_users: { user_id: nil }, overviews: overview_filter).or(Overview.joins(:roles).left_joins(:users).where(overviews_roles: { role_id: role_ids }, overviews_users: { user_id: current_user.id }, overviews: overview_filter)).where.not(overview_filter_not).distinct('overview.id').order(:prio, :name)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
result = Ticket::Overviews.index(User.find(123))
|
||||
index of all overviews by user
|
||||
|
||||
result = Ticket::Overviews.index(User.find(3))
|
||||
|
||||
index of certain overviews by user
|
||||
|
||||
result = Ticket::Overviews.index(User.find(3), ['all_unassigned', 'my_assigned'])
|
||||
|
||||
returns
|
||||
|
||||
|
@ -75,9 +92,10 @@ returns
|
|||
|
||||
=end
|
||||
|
||||
def self.index(user)
|
||||
def self.index(user, links = nil)
|
||||
overviews = Ticket::Overviews.all(
|
||||
current_user: user,
|
||||
links: links,
|
||||
)
|
||||
return [] if overviews.blank?
|
||||
|
||||
|
|
|
@ -12,21 +12,17 @@ Rails.application.configure do
|
|||
# Show full error reports.
|
||||
config.consider_all_requests_local = true
|
||||
|
||||
# Commented out to ensure using file cache store as described in config/application.rb
|
||||
# Enable/disable caching. By default caching is disabled.
|
||||
# Run rails dev:cache to toggle caching.
|
||||
# if Rails.root.join('tmp', 'caching-dev.txt').exist?
|
||||
# config.action_controller.perform_caching = true
|
||||
#
|
||||
# config.cache_store = :memory_store
|
||||
# config.public_file_server.headers = {
|
||||
# 'Cache-Control' => "public, max-age=#{2.days.to_i}"
|
||||
# }
|
||||
# else
|
||||
# config.action_controller.perform_caching = false
|
||||
#
|
||||
# config.cache_store = :null_store
|
||||
# end
|
||||
if Rails.root.join('tmp', 'caching-dev.txt').exist?
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
config.public_file_server.headers = {
|
||||
'Cache-Control' => "public, max-age=#{2.days.to_i}"
|
||||
}
|
||||
else
|
||||
config.action_controller.perform_caching = false
|
||||
end
|
||||
|
||||
# Store uploaded files on the local file system (see config/storage.yml for options)
|
||||
# config.active_storage.service = :local
|
||||
|
|
|
@ -4,31 +4,82 @@ class Sessions::Backend::TicketOverviewList < Sessions::Backend::Base
|
|||
Cache.write("TicketOverviewPull::#{user_id}", { needed: true })
|
||||
end
|
||||
|
||||
def initialize(user, asset_lookup, client = nil, client_id = nil, ttl = 8)
|
||||
def initialize(user, asset_lookup, client = nil, client_id = nil, ttl = 7)
|
||||
@user = user
|
||||
@client = client
|
||||
@client_id = client_id
|
||||
@ttl = ttl
|
||||
@asset_lookup = asset_lookup
|
||||
@last_change = nil
|
||||
@last_index_lists = nil
|
||||
@last_overview = {}
|
||||
@last_overview_change = nil
|
||||
@last_ticket_change = nil
|
||||
@last_full_fetch = nil
|
||||
end
|
||||
|
||||
def self.overview_history_append(overview, user_id)
|
||||
key = "TicketOverviewHistory::#{user_id}"
|
||||
history = Cache.get(key) || []
|
||||
|
||||
history.prepend overview
|
||||
history.uniq!
|
||||
if history.count > 4
|
||||
history.pop
|
||||
end
|
||||
|
||||
Cache.write(key, history)
|
||||
end
|
||||
|
||||
def self.overview_history_get(user_id)
|
||||
Cache.get("TicketOverviewHistory::#{user_id}")
|
||||
end
|
||||
|
||||
def load
|
||||
|
||||
# get whole collection
|
||||
index_and_lists = nil
|
||||
local_overview_changed = overview_changed?
|
||||
if !@last_index_lists || !@last_full_fetch || @last_full_fetch < (Time.zone.now.to_i - 60) || local_overview_changed
|
||||
|
||||
# check if min one ticket has changed
|
||||
return if !ticket_changed?(true) && !local_overview_changed
|
||||
|
||||
index_and_lists = Ticket::Overviews.index(@user)
|
||||
@last_full_fetch = Time.zone.now.to_i
|
||||
else
|
||||
|
||||
# check if min one ticket has changed
|
||||
return if !ticket_changed? && !local_overview_changed
|
||||
|
||||
index_and_lists_local = Ticket::Overviews.index(@user, Sessions::Backend::TicketOverviewList.overview_history_get(@user.id))
|
||||
|
||||
# compare index_and_lists_local to index_and_lists_local
|
||||
# return if no changes
|
||||
|
||||
index_and_lists = []
|
||||
@last_index_lists.each do |last_index|
|
||||
found_in_particular_index = false
|
||||
index_and_lists_local.each do |local_index|
|
||||
next if local_index[:overview][:id] != last_index[:overview][:id]
|
||||
|
||||
index_and_lists.push local_index
|
||||
found_in_particular_index = true
|
||||
break
|
||||
end
|
||||
next if found_in_particular_index == true
|
||||
|
||||
index_and_lists.push last_index
|
||||
end
|
||||
end
|
||||
|
||||
# no data exists
|
||||
return if index_and_lists.blank?
|
||||
|
||||
# no change exists
|
||||
return if @last_change == index_and_lists
|
||||
return if @last_index_lists == index_and_lists
|
||||
|
||||
# remember last state
|
||||
@last_change = index_and_lists
|
||||
@last_index_lists = index_and_lists
|
||||
|
||||
index_and_lists
|
||||
end
|
||||
|
@ -36,6 +87,12 @@ class Sessions::Backend::TicketOverviewList < Sessions::Backend::Base
|
|||
def local_to_run?
|
||||
return false if !@time_now
|
||||
|
||||
return true if pull_overview?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def pull_overview?
|
||||
result = Cache.get("TicketOverviewPull::#{@user.id}")
|
||||
Cache.delete("TicketOverviewPull::#{@user.id}") if result
|
||||
return true if result
|
||||
|
@ -48,14 +105,6 @@ class Sessions::Backend::TicketOverviewList < Sessions::Backend::Base
|
|||
|
||||
@time_now = Time.zone.now.to_i
|
||||
|
||||
# check if min one ticket or overview has changed
|
||||
last_overview_change = Overview.latest_change
|
||||
last_ticket_change = Ticket.latest_change
|
||||
return if last_ticket_change == @last_ticket_change && last_overview_change == @last_overview_change
|
||||
|
||||
@last_overview_change = last_overview_change
|
||||
@last_ticket_change = last_ticket_change
|
||||
|
||||
# load current data
|
||||
index_and_lists = load
|
||||
return if !index_and_lists
|
||||
|
@ -134,4 +183,26 @@ class Sessions::Backend::TicketOverviewList < Sessions::Backend::Base
|
|||
nil
|
||||
end
|
||||
|
||||
def overview_changed?
|
||||
|
||||
# check if min one overview has changed
|
||||
last_overview_change = Overview.latest_change
|
||||
return false if last_overview_change == @last_overview_change
|
||||
|
||||
@last_overview_change = last_overview_change
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def ticket_changed?(reset = false)
|
||||
|
||||
# check if min one ticket has changed
|
||||
last_ticket_change = Ticket.latest_change
|
||||
return false if last_ticket_change == @last_ticket_change
|
||||
|
||||
@last_ticket_change = last_ticket_change if reset
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
23
lib/sessions/event/ticket_overview_select.rb
Normal file
23
lib/sessions/event/ticket_overview_select.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
class Sessions::Event::TicketOverviewSelect < Sessions::Event::Base
|
||||
|
||||
=begin
|
||||
|
||||
Event module to serve spool messages and send them to new client connection.
|
||||
|
||||
To execute this manually, just paste the following into the browser console
|
||||
|
||||
App.WebSocket.send({event:'spool'})
|
||||
|
||||
=end
|
||||
|
||||
def run
|
||||
return if @payload['data'].blank?
|
||||
return if @payload['data']['view'].blank?
|
||||
return if @session['id'].blank?
|
||||
|
||||
Sessions::Backend::TicketOverviewList.overview_history_append(@payload['data']['view'], @session['id'])
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
|
@ -82,14 +82,14 @@ RSpec.describe Sessions::Backend::TicketOverviewList do
|
|||
let!(:first_call) { collection.push }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(collection.push).to be(nil)
|
||||
expect(collection.push).to eq(nil)
|
||||
end
|
||||
|
||||
context 'even after the TTL has passed' do
|
||||
before { travel(ttl + 1) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(collection.push).to be(nil)
|
||||
expect(collection.push).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -97,7 +97,7 @@ RSpec.describe Sessions::Backend::TicketOverviewList do
|
|||
before { described_class.reset(admin.id) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(collection.push).to be(nil)
|
||||
expect(collection.push).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -108,14 +108,14 @@ RSpec.describe Sessions::Backend::TicketOverviewList do
|
|||
|
||||
context 'before the TTL has passed' do
|
||||
it 'returns nil' do
|
||||
expect(collection.push).to be(nil)
|
||||
expect(collection.push).to eq(nil)
|
||||
end
|
||||
|
||||
context 'after .reset with the user’s id' do
|
||||
before { described_class.reset(admin.id) }
|
||||
|
||||
it 'returns nil because no ticket and no overview has changed' do
|
||||
expect(collection.push).to be nil
|
||||
expect(collection.push).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -124,7 +124,7 @@ RSpec.describe Sessions::Backend::TicketOverviewList do
|
|||
before { travel(ttl + 1) }
|
||||
|
||||
it 'returns an empty result' do
|
||||
expect(collection.push).to eq nil
|
||||
expect(collection.push).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -132,7 +132,7 @@ RSpec.describe Sessions::Backend::TicketOverviewList do
|
|||
before { travel(2.hours + 1.second) }
|
||||
|
||||
it 'returns an empty result' do
|
||||
expect(collection.push).to eq nil
|
||||
expect(collection.push).to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -101,6 +101,35 @@ class AgentTicketLinkTest < TestCase
|
|||
css: '.content.active .ticketLinks',
|
||||
value: ticket2[:title],
|
||||
)
|
||||
|
||||
# cleanup
|
||||
ticket_open_by_search(
|
||||
browser: browser2,
|
||||
number: ticket1[:number],
|
||||
)
|
||||
sleep 1
|
||||
|
||||
ticket_update(
|
||||
browser: browser2,
|
||||
data: {
|
||||
state: 'closed',
|
||||
}
|
||||
)
|
||||
|
||||
tasks_close_all()
|
||||
|
||||
ticket_open_by_search(
|
||||
browser: browser2,
|
||||
number: ticket2[:number],
|
||||
)
|
||||
sleep 1
|
||||
|
||||
ticket_update(
|
||||
browser: browser2,
|
||||
data: {
|
||||
state: 'closed',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ class AgentTicketMacroTest < TestCase
|
|||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: 'some subject - macro "Close & Tag as Spam" default',
|
||||
title: 'macro "Close & Tag as Spam" default',
|
||||
body: 'some body - macro "Close & Tag as Spam" default',
|
||||
},
|
||||
)
|
||||
|
@ -66,7 +66,7 @@ class AgentTicketMacroTest < TestCase
|
|||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: "some subject - macro #{macro_name}",
|
||||
title: "macro #{macro_name}",
|
||||
body: "some body - macro #{macro_name}",
|
||||
},
|
||||
)
|
||||
|
@ -105,7 +105,7 @@ class AgentTicketMacroTest < TestCase
|
|||
data: {
|
||||
customer: 'nico',
|
||||
group: 'Users',
|
||||
title: "some subject - macro #{macro_name}",
|
||||
title: "macro #{macro_name}",
|
||||
body: "some body - macro #{macro_name}",
|
||||
},
|
||||
)
|
||||
|
@ -134,7 +134,7 @@ class AgentTicketMacroTest < TestCase
|
|||
ux_flow_next_up: ux_flow_next_up,
|
||||
)
|
||||
|
||||
title_prefix = "some subject - macro #{macro_name}"
|
||||
title_prefix = "macro #{macro_name}"
|
||||
ticket1 = ticket_create(
|
||||
data: {
|
||||
customer: 'nico',
|
||||
|
|
|
@ -45,7 +45,6 @@ class AgentTicketOverviewTabTest < TestCase
|
|||
# js: '$(".content.active .sidebar").css("display", "block")',
|
||||
#)
|
||||
#click(text: 'Unassigned & Open')
|
||||
sleep 8 # till overview is rendered
|
||||
|
||||
ticket_open_by_overview(
|
||||
number: ticket1[:number],
|
||||
|
@ -63,9 +62,10 @@ class AgentTicketOverviewTabTest < TestCase
|
|||
task_type: 'closeNextInOverview', # default: stayOnTab / possible: closeTab, closeNextInOverview, stayOnTab
|
||||
)
|
||||
|
||||
match(
|
||||
watch_for(
|
||||
css: '.tasks .task.is-active',
|
||||
value: "overview tab test #2 - #{title}",
|
||||
timeout: 8,
|
||||
)
|
||||
|
||||
assert_equal(1, @browser.find_elements(css: '.tasks .task').count)
|
||||
|
|
|
@ -2721,6 +2721,8 @@ wait untill text in selector disabppears
|
|||
end
|
||||
else
|
||||
6.times do
|
||||
|
||||
# prefere find_elements ofer find_element because of exception handling
|
||||
element = instance.find_elements(partial_link_text: params[:number])[0]
|
||||
break if element
|
||||
|
||||
|
@ -2733,7 +2735,7 @@ wait untill text in selector disabppears
|
|||
end
|
||||
element.click
|
||||
sleep 1
|
||||
number = instance.find_elements(css: '.content.active .ticketZoom-header .ticket-number')[0].text
|
||||
number = instance.find_element(css: '.content.active .ticketZoom-header .ticket-number').text
|
||||
if !number.match?(/#{params[:number]}/)
|
||||
screenshot(browser: instance, comment: 'ticket_open_by_overview_open_failed_failed')
|
||||
raise "unable to open ticket #{params[:number]}!"
|
||||
|
|
Loading…
Reference in a new issue