Improved fetching activity stream, online notifications and recent viewed items. Improved REST API with full and expaned attributes.

This commit is contained in:
Martin Edenhofer 2018-03-20 13:16:17 +01:00
parent 2df0ddc248
commit 70847f2b41
26 changed files with 585 additions and 308 deletions

View file

@ -1171,7 +1171,7 @@ class App.CollectionController extends App.Controller
class App.ObserverController extends App.Controller
model: 'Ticket'
template: 'ticket_zoom/title'
template: 'tba'
globalRerender: true
###
@ -1251,3 +1251,24 @@ class App.ObserverController extends App.Controller
#console.trace()
@log 'debug', 'release', @object_id, @model, @subscribeId
App[@model].unsubscribe(@subscribeId)
class App.ObserverActionRow extends App.ObserverController
constructor: ->
super
render: (object) =>
return if _.isEmpty(object)
actions = @actions()
@html App.view('generic/actions')(
items: actions
type: @type
)
for item in actions
do (item) =>
@$("[data-type=\"#{item.name}\"]").on(
'click'
(e) ->
e.preventDefault()
item.callback(object)
)

View file

@ -27,7 +27,7 @@ class App.DashboardActivityStream extends App.CollectionController
@ajax(
id: 'dashoard_activity_stream'
type: 'GET'
url: "#{@apiPath}/activity_stream"
url: "#{@apiPath}/activity_stream?full=true"
data:
limit: @limit || 8
processData: true
@ -37,8 +37,9 @@ class App.DashboardActivityStream extends App.CollectionController
load: (data) =>
App.SessionStorage.set('activity_stream', data)
@items = data.activity_stream
App.ActivityStream.refresh([], clear: true)
App.Collection.loadAssets(data.assets)
@items = App.ActivityStream.search(sortBy: 'created_at', order: 'DESC')
@collectionSync(@items)
itemGet: (key) =>
@ -49,7 +50,7 @@ class App.DashboardActivityStream extends App.CollectionController
# nothing
itemsAll: =>
@items
@items || []
onRenderEnd: =>
return if _.isEmpty(@items)

View file

@ -448,10 +448,9 @@ class App.Navigation extends App.ControllerWidgetPermanent
@Config.set('NavBarRight', NavBarRight)
fetchRecentView: =>
load = (data) =>
App.RecentView.refresh(data.stream, clear: true)
load = =>
@renderPersonal()
App.RecentView.fetchFull(load)
App.RecentView.fetchFull(load, clear: true)
toggleNotifications: (e) ->
e.stopPropagation()

View file

@ -73,50 +73,43 @@ class App.OrganizationProfile extends App.Controller
currentPosition: =>
@$('.profile').scrollTop()
class ActionRow extends App.ObserverController
class ActionRow extends App.ObserverActionRow
model: 'Organization'
observe:
member_ids: true
render: (organization) =>
showHistory: (organization) =>
new App.OrganizationHistory(
organization_id: organization.id
container: @el.closest('.content')
)
# start action controller
showHistory = =>
new App.OrganizationHistory(
organization_id: organization.id
container: @el.closest('.content')
)
editOrganization = =>
new App.ControllerGenericEdit(
id: organization.id
genericObject: 'Organization'
screen: 'edit'
pageData:
title: 'Organizations'
object: 'Organization'
objects: 'Organizations'
container: @el.closest('.content')
)
editOrganization: (organization) =>
new App.ControllerGenericEdit(
id: organization.id
genericObject: 'Organization'
screen: 'edit'
pageData:
title: 'Organizations'
object: 'Organization'
objects: 'Organizations'
container: @el.closest('.content')
)
actions: =>
actions = [
{
name: 'edit'
title: 'Edit'
callback: editOrganization
callback: @editOrganization
}
{
name: 'history'
title: 'History'
callback: showHistory
callback: @showHistory
}
]
new App.ActionRow(
el: @el
items: actions
)
class Object extends App.ObserverController
model: 'Organization'
observeNot:

View file

@ -574,11 +574,12 @@ class App.TicketOverview extends App.Controller
@activeFocus = 'nav'
)
@bind 'overview:fetch', =>
@bind('overview:fetch', =>
return if !@view
update = =>
App.OverviewListCollection.fetch(@view)
@delay(update, 2800, 'overview:fetch')
)
renderBatchOverlay: (elLocal) =>
if elLocal

View file

@ -80,58 +80,51 @@ class App.UserProfile extends App.Controller
currentPosition: =>
@$('.profile').scrollTop()
class ActionRow extends App.ObserverController
class ActionRow extends App.ObserverActionRow
model: 'User'
observe:
organization_id: true
render: (user) =>
showHistory: (user) =>
new App.UserHistory(
user_id: user.id
container: @el.closest('.content')
)
# start action controller
showHistory = =>
new App.UserHistory(
user_id: user.id
container: @el.closest('.content')
)
editUser: (user) =>
new App.ControllerGenericEdit(
id: user.id
genericObject: 'User'
screen: 'edit'
pageData:
title: 'Users'
object: 'User'
objects: 'Users'
container: @el.closest('.content')
)
editUser = =>
new App.ControllerGenericEdit(
id: user.id
genericObject: 'User'
screen: 'edit'
pageData:
title: 'Users'
object: 'User'
objects: 'Users'
container: @el.closest('.content')
)
newTicket: (user) =>
@navigate("ticket/create/customer/#{user.id}")
newTicket = =>
@navigate("ticket/create/customer/#{user.id}")
actions = [
actions: =>
[
{
name: 'edit'
title: 'Edit'
callback: editUser
callback: @editUser
}
{
name: 'history'
title: 'History'
callback: showHistory
callback: @showHistory
}
{
name: 'ticket'
title: 'New Ticket'
callback: newTicket
callback: @newTicket
}
]
new App.ActionRow(
el: @el
items: actions
)
class Object extends App.ObserverController
model: 'User'
observeNot:

View file

@ -20,16 +20,17 @@ class App.OnlineNotificationWidget extends App.Controller
super
# at runtime if a online notifiction has changed
@bind 'OnlineNotification::changed', =>
@bind('OnlineNotification::changed', =>
@delay(
=> @fetch()
2200
'online-notification-changed'
)
)
# after new websocket connection has been established
@ignoreInitLogin = false
@bind 'ws:login', =>
@bind('ws:login', =>
if @ignoreInitLogin
@delay(
=> @fetch()
@ -37,15 +38,17 @@ class App.OnlineNotificationWidget extends App.Controller
'online-notification-changed'
)
@ignoreInitLogin = true
)
# rebuild widget on auth
@bind 'auth', (user) =>
@bind('auth', (user) =>
if !user
@counterUpdate(0)
return
if !@access()
@counterUpdate(0)
return
)
$(window).on 'click.notifications', @hide
@ -150,10 +153,9 @@ class App.OnlineNotificationWidget extends App.Controller
)
fetch: =>
load = (data) =>
load = =>
@fetchedData = true
App.OnlineNotification.refresh(data.stream, clear: true)
App.OnlineNotification.fetchFull(load)
App.OnlineNotification.fetchFull(load, clear: true)
toggle: =>
if @shown

View file

@ -273,11 +273,11 @@ set new attributes of model (remove already available attributes)
# subscribe and reload data / fetch new data if triggered
subscribeId = undefined
if bind
subscribeId = App[ @className ].subscribeItem(id, callback)
subscribeId = App[@className].subscribeItem(id, callback)
# execute if object already exists
if !force && App[ @className ].exists(id)
data = App[ @className ].find(id)
if !force && App[@className].exists(id)
data = App[@className].find(id)
data = @_fillUp(data)
if callback
callback(data, 'full')
@ -312,17 +312,18 @@ set new attributes of model (remove already available attributes)
# find / load object
else
App[ @className ].refresh(data)
App[@className].refresh(data)
# execute callbacks
if @FULL_CALLBACK[ data.id ]
for key, callback of @FULL_CALLBACK[ data.id ]
callback( @_fillUp( App[ @className ].find(data.id) ) )
callback( @_fillUp( App[@className].find(data.id) ) )
delete @FULL_CALLBACK[ data.id ][ key ]
if _.isEmpty @FULL_CALLBACK[ data.id ]
delete @FULL_CALLBACK[ data.id ]
error: (xhr, statusText, error) ->
error: (xhr, statusText, error) =>
@FULL_FETCH[ data.id ] = false
App.Log.error('Model', statusText, error, url)
)
subscribeId
@ -455,8 +456,8 @@ set new attributes of model (remove already available attributes)
items = [items]
App.Log.debug('Model', "local change #{@className}", items)
for item in items
for key, callback of App[ @className ].SUBSCRIPTION_ITEM[ item.id ]
callback(App[ @className ]._fillUp(item), 'change')
for key, callback of App[@className].SUBSCRIPTION_ITEM[ item.id ]
callback(App[@className]._fillUp(item), 'change')
)
@bind(
'destroy'
@ -467,8 +468,8 @@ set new attributes of model (remove already available attributes)
items = [items]
App.Log.debug('Model', "local destroy #{@className}", items)
for item in items
for key, callback of App[ @className ].SUBSCRIPTION_ITEM[ item.id ]
callback(App[ @className ]._fillUp(item), 'destroy')
for key, callback of App[@className].SUBSCRIPTION_ITEM[ item.id ]
callback(App[@className]._fillUp(item), 'destroy')
)
@changeTable = {}
@ -481,7 +482,7 @@ set new attributes of model (remove already available attributes)
items = [items]
App.Log.debug('Model', "local refresh #{@className}", items)
for item in items
for key, callback of App[ @className ].SUBSCRIPTION_ITEM[ item.id ]
for key, callback of App[@className].SUBSCRIPTION_ITEM[ item.id ]
# only trigger callbacks if object has changed
if !@changeTable[key] || @changeTable[key] < item.updated_at
@ -498,8 +499,8 @@ set new attributes of model (remove already available attributes)
App.Log.debug('Model', "server change on #{@className}.find(#{item.id}) #{item.updated_at}")
callback = =>
genericObject = undefined
if App[ @className ].exists(item.id)
genericObject = App[ @className ].find(item.id)
if App[@className].exists(item.id)
genericObject = App[@className].find(item.id)
if !genericObject || new Date(item.updated_at) > new Date(genericObject.updated_at)
App.Log.debug('Model', "request #{@className}.find(#{item.id}) from server")
@full(item.id, false, true)
@ -512,8 +513,8 @@ set new attributes of model (remove already available attributes)
events
(item) =>
return if !@SUBSCRIPTION_ITEM || !@SUBSCRIPTION_ITEM[ item.id ]
return if !App[ @className ].exists(item.id)
genericObject = App[ @className ].find(item.id)
return if !App[@className].exists(item.id)
genericObject = App[@className].find(item.id)
App.Log.debug('Model', "server delete on #{@className}.find(#{item.id}) #{item.updated_at}")
callback = ->
genericObject.trigger('destroy', genericObject)
@ -565,16 +566,30 @@ set new attributes of model (remove already available attributes)
@fetchFull: (callback, params = {}) ->
url = "#{@url}/?full=true"
App.Log.debug('Model', "fetchFull collection #{@className}", url)
# request already active, queue callback
queueManagerName = "#{@className}::fetchFull"
if params.force is false && App[@className].count() isnt 0
if callback
callback(App[@className].all())
localCallback = =>
callback(App[@className].all(), 'full')
App.QueueManager.add(queueManagerName, localCallback)
App.QueueManager.run(queueManagerName)
return
if callback
localCallback = =>
callback(App[@className].all())
App.QueueManager.add(queueManagerName, localCallback)
return if @fetchFullActive is true
@fetchFullActive = true
App.Ajax.request(
type: 'GET'
url: url
processData: true,
success: (data, status, xhr) =>
@fetchFullActive = false
App.Log.debug('Model', "got fetchFull collection #{@className}", data)
@ -594,10 +609,10 @@ set new attributes of model (remove already available attributes)
else
App[@className].refresh(data)
if callback
callback(data)
App.QueueManager.run(queueManagerName)
error: (xhr, statusText, error) ->
error: (xhr, statusText, error) =>
@fetchFullActive = false
App.Log.error('Model', statusText, error, url)
)

View file

@ -0,0 +1,4 @@
class App.ActivityStream extends App.Model
@configure 'ActivityStream', 'name'
@extend Spine.Model.Ajax
@url: @apiPath + '/activity_steams'

View file

@ -5,10 +5,36 @@ class ActivityStreamController < ApplicationController
# GET /api/v1/activity_stream
def show
activity_stream = current_user.activity_stream(params[:limit], true)
activity_stream = current_user.activity_stream(params[:limit])
# return result
render json: activity_stream
if response_expand?
list = []
activity_stream.each do |item|
list.push item.attributes_with_association_names
end
render json: list, status: :ok
return
end
if response_full?
assets = {}
item_ids = []
activity_stream.each do |item|
item_ids.push item.id
assets = item.assets(assets)
end
render json: {
record_ids: item_ids,
assets: assets,
}, status: :ok
return
end
all = []
activity_stream.each do |item|
all.push item.attributes_with_association_ids
end
render json: all, status: :ok
end
end

View file

@ -47,13 +47,36 @@ curl http://localhost/api/v1/online_notifications.json -v -u #{login}:#{password
=end
def index
if response_full?
render json: OnlineNotification.list_full(current_user, 200)
online_notifications = OnlineNotification.list(current_user, 200)
if response_expand?
list = []
online_notifications.each do |item|
list.push item.attributes_with_association_names
end
render json: list, status: :ok
return
end
notifications = OnlineNotification.list(current_user, 200)
model_index_render_result(notifications)
if response_full?
assets = {}
item_ids = []
online_notifications.each do |item|
item_ids.push item.id
assets = item.assets(assets)
end
render json: {
record_ids: item_ids,
assets: assets,
}, status: :ok
return
end
all = []
online_notifications.each do |item|
all.push item.attributes_with_association_ids
end
render json: all, status: :ok
end
=begin

View file

@ -19,10 +19,36 @@ curl http://localhost/api/v1/recent_view -v -u #{login}:#{password} -H "Content-
=end
def index
recent_viewed = RecentView.list_full(current_user, 10)
recent_viewed = RecentView.list(current_user, 10)
# return result
render json: recent_viewed
if response_expand?
list = []
recent_viewed.each do |item|
list.push item.attributes_with_association_names
end
render json: list, status: :ok
return
end
if response_full?
assets = {}
item_ids = []
recent_viewed.each do |item|
item_ids.push item.id
assets = item.assets(assets)
end
render json: {
record_ids: item_ids,
assets: assets,
}, status: :ok
return
end
all = []
recent_viewed.each do |item|
all.push item.attributes_with_association_ids
end
render json: all, status: :ok
end
=begin

View file

@ -11,11 +11,15 @@ module ExtraCollection
assets = item.assets(assets)
end
collections[ OnlineNotification.to_app_model ] = OnlineNotification.list(user, 200)
assets = ApplicationModel.assets_of_object_list(collections[ OnlineNotification.to_app_model ], assets)
collections[ OnlineNotification.to_app_model ] = []
OnlineNotification.list(user, 200).each do |item|
assets = item.assets(assets)
end
collections[ RecentView.to_app_model ] = RecentView.list(user, 10)
assets = RecentView.assets_of_object_list(collections[ RecentView.to_app_model ], assets)
collections[ RecentView.to_app_model ] = []
RecentView.list(user, 10).each do |item|
assets = item.assets(assets)
end
collections[ Permission.to_app_model ] = []
Permission.all.each do |item|

View file

@ -335,12 +335,12 @@ class TicketsController < ApplicationController
end
ticket_ids_recent_viewed = []
recent_views = RecentView.list(current_user, 8, 'Ticket').delete_if { |object| object['o_id'] == ticket.id }
recent_views = RecentView.list(current_user, 8, 'Ticket')
recent_views.each do |recent_view|
next if recent_view['object'] != 'Ticket'
ticket_ids_recent_viewed.push recent_view['o_id']
recent_view_ticket = Ticket.find(recent_view['o_id'])
next if recent_view_ticket.state.state_type.name == 'merged'
next if recent_view.object.name != 'Ticket'
next if recent_view.o_id == ticket.id
ticket_ids_recent_viewed.push recent_view.o_id
recent_view_ticket = Ticket.find(recent_view.o_id)
assets = recent_view_ticket.assets(assets)
end

View file

@ -1,9 +1,12 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class ActivityStream < ApplicationModel
load 'activity_stream/assets.rb'
include ActivityStream::Assets
self.table_name = 'activity_streams'
belongs_to :activity_stream_type, class_name: 'TypeLookup'
belongs_to :activity_stream_object, class_name: 'ObjectLookup'
belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'activity_stream_object_id'
belongs_to :type, class_name: 'TypeLookup', foreign_key: 'activity_stream_type_id'
=begin
@ -108,16 +111,7 @@ return all activity entries of an user
.order('created_at DESC, id DESC')
.limit(limit)
end
list = []
stream.each do |item|
data = item.attributes
data['object'] = ObjectLookup.by_id( data['activity_stream_object_id'] )
data['type'] = TypeLookup.by_id( data['activity_stream_type_id'] )
data.delete('activity_stream_object_id')
data.delete('activity_stream_type_id')
list.push data
end
list
stream
end
=begin

View file

@ -0,0 +1,56 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class ActivityStream
module Assets
=begin
get all assets / related models for this activity stream item
activity_stream = ActivityStream.find(123)
result = activity_stream.assets(assets_if_exists)
returns
result = {
:ActivityStream => {
123 => activity_stream_model_123,
1234 => activity_stream_model_1234,
}
}
=end
def assets(data)
app_model = self.class.to_app_model
if !data[ app_model ]
data[ app_model ] = {}
end
if !data[ app_model ][ id ]
local_attributes = attributes_with_association_ids
local_attributes['object'] = ObjectLookup.by_id(local_attributes['activity_stream_object_id'])
local_attributes['type'] = TypeLookup.by_id(local_attributes['activity_stream_type_id'])
# set temp. current attributes to assets pool to prevent
# loops, will be updated with lookup attributes later
data[ app_model ][ id ] = local_attributes
ApplicationModel.assets_of_object_list([local_attributes], data)
end
return data if !self['created_by_id']
app_model_user = User.to_app_model
%w[created_by_id].each do |local_user_id|
next if !self[ local_user_id ]
next if data[ app_model_user ] && data[ app_model_user ][ self[ local_user_id ] ]
user = User.lookup(id: self[ local_user_id ])
next if !user
data = user.assets(data)
end
data
end
end
end

View file

@ -1,8 +1,11 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class OnlineNotification < ApplicationModel
belongs_to :type_lookup, class_name: 'TypeLookup'
belongs_to :object_lookup, class_name: 'ObjectLookup'
load 'online_notification/assets.rb'
include OnlineNotification::Assets
belongs_to :type, class_name: 'TypeLookup', foreign_key: 'type_lookup_id'
belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'object_lookup_id'
belongs_to :user
after_create :notify_clients_after_change
@ -49,7 +52,7 @@ add a new online notification for this user
updated_at: data[:updated_at] || Time.zone.now,
}
OnlineNotification.create(record)
OnlineNotification.create!(record)
end
=begin
@ -92,9 +95,9 @@ remove whole online notifications of an object by type
=end
def self.remove_by_type(object_name, o_id, type, user)
def self.remove_by_type(object_name, o_id, type_name, user)
object_id = ObjectLookup.by_name(object_name)
type_id = TypeLookup.by_name(type)
type_id = TypeLookup.by_name(type_name)
OnlineNotification.where(
object_lookup_id: object_id,
type_lookup_id: type_id,
@ -112,20 +115,9 @@ return all online notifications of an user
=end
def self.list(user, limit)
notifications = OnlineNotification.where(user_id: user.id)
.order('created_at DESC, id DESC')
.limit(limit)
list = []
notifications.each do |item|
data = item.attributes
data['object'] = ObjectLookup.by_id(data['object_lookup_id'])
data['type'] = TypeLookup.by_id(data['type_lookup_id'])
data.delete('object_lookup_id')
data.delete('type_lookup_id')
list.push data
end
list
OnlineNotification.where(user_id: user.id)
.order('created_at DESC, id DESC')
.limit(limit)
end
=begin
@ -169,31 +161,6 @@ mark online notification as seen by object
true
end
=begin
return all online notifications of an user with assets
OnlineNotification.list_full(user)
returns:
list = {
stream: notifications,
assets: assets,
}
=end
def self.list_full(user, limit)
notifications = OnlineNotification.list(user, limit)
assets = ApplicationModel.assets_of_object_list(notifications)
{
stream: notifications,
assets: assets
}
end
def notify_clients_after_change
Sessions.send_to(
user_id,
@ -216,8 +183,8 @@ returns:
=end
def self.all_seen?(object, o_id)
notifications = OnlineNotification.list_by_object(object, o_id)
def self.all_seen?(object_name, o_id)
notifications = OnlineNotification.list_by_object(object_name, o_id)
notifications.each do |onine_notification|
return false if !onine_notification['seen']
end
@ -237,15 +204,17 @@ returns:
=end
# rubocop:disable Metrics/ParameterLists
def self.exists?(user, object, o_id, type, created_by_user, seen)
def self.exists?(user, object_name, o_id, type_name, created_by_user, seen)
# rubocop:enable Metrics/ParameterLists
object_id = ObjectLookup.by_name(object_name)
type_id = TypeLookup.by_name(type_name)
notifications = OnlineNotification.list(user, 10)
notifications.each do |notification|
next if notification['o_id'] != o_id
next if notification['object'] != object
next if notification['type'] != type
next if notification['created_by_id'] != created_by_user.id
next if notification['seen'] != seen
next if notification.o_id != o_id
next if notification.object_lookup_id != object_id
next if notification.type_lookup_id != type_id
next if notification.created_by_id != created_by_user.id
next if notification.seen != seen
return true
end
false

View file

@ -0,0 +1,56 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class OnlineNotification
module Assets
=begin
get all assets / related models for this online notification item
online_notification = OnlineNotification.find(123)
result = online_notification.assets(assets_if_exists)
returns
result = {
:OnlineNotification => {
123 => online_notification_model_123,
1234 => online_notification_model_1234,
}
}
=end
def assets(data)
app_model = self.class.to_app_model
if !data[ app_model ]
data[ app_model ] = {}
end
if !data[ app_model ][ id ]
local_attributes = attributes_with_association_ids
local_attributes['object'] = ObjectLookup.by_id(local_attributes['object_lookup_id'])
local_attributes['type'] = TypeLookup.by_id(local_attributes['type_lookup_id'])
# set temp. current attributes to assets pool to prevent
# loops, will be updated with lookup attributes later
data[ app_model ][ id ] = local_attributes
ApplicationModel.assets_of_object_list([local_attributes], data)
end
return data if !self['created_by_id'] && !self['updated_by_id']
app_model_user = User.to_app_model
%w[created_by_id updated_by_id].each do |local_user_id|
next if !self[ local_user_id ]
next if data[ app_model_user ] && data[ app_model_user ][ self[ local_user_id ] ]
user = User.lookup(id: self[ local_user_id ])
next if !user
data = user.assets(data)
end
data
end
end
end

View file

@ -1,8 +1,10 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class RecentView < ApplicationModel
belongs_to :object_lookup, class_name: 'ObjectLookup'
belongs_to :ticket, class_name: 'Ticket', foreign_key: 'o_id'
load 'recent_view/assets.rb'
include RecentView::Assets
belongs_to :object, class_name: 'ObjectLookup', foreign_key: 'recent_view_object_id'
after_create :notify_clients
after_update :notify_clients
@ -36,53 +38,46 @@ class RecentView < ApplicationModel
RecentView.where(created_by_id: user.id).destroy_all
end
def self.list(user, limit = 10, type = nil)
recent_views = if !type
RecentView.select('o_id, recent_view_object_id, MAX(created_at) as created_at, MAX(id) as id')
.group(:o_id, :recent_view_object_id)
def self.list(user, limit = 10, object_name = nil)
recent_views = if !object_name
RecentView.select('o_id, recent_view_object_id, MAX(created_at) as created_at, MAX(id) as id, created_by_id')
.group(:o_id, :recent_view_object_id, :created_by_id)
.where(created_by_id: user.id)
.limit(limit)
elsif type == 'Ticket'
elsif object_name == 'Ticket'
state_ids = Ticket::State.by_category(:viewable_agent_new).pluck(:id)
RecentView.joins(:ticket)
.select('recent_views.o_id as o_id, recent_views.recent_view_object_id as recent_view_object_id, MAX(recent_views.created_at) as created_at, MAX(recent_views.id) as id')
.group(:o_id, :recent_view_object_id)
.where('recent_views.created_by_id = ? AND recent_views.recent_view_object_id = ? AND tickets.state_id IN (?)', user.id, ObjectLookup.by_name('Ticket'), state_ids )
.limit(limit)
local_recent_views = RecentView.select('o_id, recent_view_object_id, MAX(created_at) as created_at, MAX(id) as id, created_by_id')
.group(:o_id, :recent_view_object_id, :created_by_id)
.where(created_by_id: user.id, recent_view_object_id: ObjectLookup.by_name(object_name))
.limit(limit + 10)
clear_list = []
local_recent_views.each do |item|
ticket = Ticket.find_by(id: item.o_id)
next if !ticket
next if !state_ids.include?(ticket.state_id)
clear_list.push item
break if clear_list.count == limit
end
clear_list
else
RecentView.select('o_id, recent_view_object_id, MAX(created_at) as created_at, MAX(id) as id')
.group(:o_id, :recent_view_object_id)
.where(created_by_id: user.id, recent_view_object_id: ObjectLookup.by_name(type))
RecentView.select('o_id, recent_view_object_id, MAX(created_at) as created_at, MAX(id) as id, created_by_id')
.group(:o_id, :recent_view_object_id, :created_by_id)
.where(created_by_id: user.id, recent_view_object_id: ObjectLookup.by_name(object_name))
.limit(limit)
end
list = []
recent_views.each do |item|
data = item.attributes
data['object'] = ObjectLookup.by_id(data['recent_view_object_id'])
data.delete('recent_view_object_id')
# access check
next if !access(data['object'], data['o_id'], user)
next if !access(ObjectLookup.by_id(item['recent_view_object_id']), item['o_id'], user)
# add to result list
list.push data
list.push item
end
list
end
def self.list_full(user, limit = 10)
recent_viewed = list(user, limit)
# get related object
assets = ApplicationModel.assets_of_object_list(recent_viewed)
{
stream: recent_viewed,
assets: assets,
}
end
def notify_clients
Sessions.send_to(
created_by_id,
@ -117,11 +112,11 @@ cleanup old entries
optional you can put the max oldest entries as argument
RecentView.cleanup(1.month)
RecentView.cleanup(3.month)
=end
def self.cleanup(diff = 1.month)
def self.cleanup(diff = 3.months)
RecentView.where('created_at < ?', Time.zone.now - diff).delete_all
true
end

View file

@ -0,0 +1,55 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
class RecentView
module Assets
=begin
get all assets / related models for this recend view item
recent_view = RecentView.find(123)
result = recent_view.assets(assets_if_exists)
returns
result = {
:RecentView => {
123 => recent_view_model_123,
1234 => recent_view_model_1234,
}
}
=end
def assets(data)
app_model = self.class.to_app_model
if !data[ app_model ]
data[ app_model ] = {}
end
if !data[ app_model ][ id ]
local_attributes = attributes_with_association_ids
local_attributes['object'] = ObjectLookup.by_id(local_attributes['recent_view_object_id'])
# set temp. current attributes to assets pool to prevent
# loops, will be updated with lookup attributes later
data[ app_model ][ id ] = local_attributes
ApplicationModel.assets_of_object_list([local_attributes], data)
end
return data if !self['created_by_id']
app_model_user = User.to_app_model
%w[created_by_id].each do |local_user_id|
next if !self[ local_user_id ]
next if data[ app_model_user ] && data[ app_model_user ][ self[ local_user_id ] ]
user = User.lookup(id: self[ local_user_id ])
next if !user
data = user.assets(data)
end
data
end
end
end

View file

@ -44,7 +44,7 @@ class User < ApplicationModel
before_update :check_preferences_default, :validate_ooo, :reset_login_failed, :validate_agent_limit_by_attributes, :last_admin_check_by_attribute
after_create :avatar_for_email_check
after_update :avatar_for_email_check
after_destroy :avatar_destroy, :user_device_destroy
before_destroy :avatar_destroy, :user_device_destroy, :cit_caller_id_destroy, :task_destroy
has_and_belongs_to_many :roles, after_add: %i[cache_update check_notifications], after_remove: :cache_update, before_add: %i[validate_agent_limit_by_role validate_roles], before_remove: :last_admin_check_by_role, class_name: 'Role'
has_and_belongs_to_many :organizations, after_add: :cache_update, after_remove: :cache_update, class_name: 'Organization'
@ -257,14 +257,17 @@ returns
=end
def activity_stream(limit, fulldata = false)
activity_stream = ActivityStream.list(self, limit)
return activity_stream if !fulldata
stream = ActivityStream.list(self, limit)
return stream if !fulldata
# get related objects
assets = ApplicationModel.assets_of_object_list(activity_stream)
assets = {}
stream.each do |item|
assets = item.assets(assets)
end
{
activity_stream: activity_stream,
stream: stream,
assets: assets,
}
end
@ -1132,6 +1135,14 @@ raise 'Minimum one user need to have admin permissions'
UserDevice.remove(id)
end
def cit_caller_id_destroy
Cti::CallerId.where(user_id: id).destroy_all
end
def task_destroy
Taskbar.where(user_id: id).destroy_all
end
def ensure_password
return true if password_empty?
return true if PasswordHash.crypted?(password)

View file

@ -26,7 +26,17 @@ class Sessions::Backend::ActivityStream
@last_change = activity_stream.first['created_at']
end
@user.activity_stream(25, true)
assets = {}
item_ids = []
activity_stream.each do |item|
item_ids.push item.id
assets = item.assets(assets)
end
{
record_ids: item_ids,
assets: assets,
}
end
def client_key

View file

@ -235,18 +235,18 @@ test( "model search tests", function() {
},
] )
priorities = App.TicketPriority.search({sortBy:'created_at', order: 'ASC'})
equal( '2 normal', priorities[0].name, 'check 1 entry')
equal( '3 high', priorities[1].name, 'check 2 entry')
equal( '4 very high', priorities[2].name, 'check 3 entry')
equal( '1 low', priorities[3].name, 'check 4 entry')
equal( undefined, priorities[4], 'check 5 entry')
equal('2 normal', priorities[0].name, 'check 1 entry')
equal('3 high', priorities[1].name, 'check 2 entry')
equal('4 very high', priorities[2].name, 'check 3 entry')
equal('1 low', priorities[3].name, 'check 4 entry')
equal(undefined, priorities[4], 'check 5 entry')
priorities = App.TicketPriority.search({sortBy:'created_at', order: 'DESC'})
equal( '1 low', priorities[0].name, 'check 4 entry')
equal( '4 very high', priorities[1].name, 'check 3 entry')
equal( '3 high', priorities[2].name, 'check 2 entry')
equal( '2 normal', priorities[3].name, 'check 1 entry')
equal( undefined, priorities[4], 'check 5 entry')
equal('1 low', priorities[0].name, 'check 4 entry')
equal('4 very high', priorities[1].name, 'check 3 entry')
equal('3 high', priorities[2].name, 'check 2 entry')
equal('2 normal', priorities[3].name, 'check 1 entry')
equal(undefined, priorities[4], 'check 5 entry')
});
@ -255,12 +255,12 @@ test( "model loadAssets tests - 1", function() {
window.refreshCounter1 = 0
var callback1 = function(state, triggerType) {
window.refreshCounter1 = window.refreshCounter1 + 1
equal( state.id, 9999, 'id check')
equal(state.id, 9999, 'id check')
if (window.refreshCounter1 == 1) {
equal( 'full', triggerType, 'trigger type check')
equal('full', triggerType, 'trigger type check')
}
else {
equal( 'refresh', triggerType, 'trigger type check')
equal('refresh', triggerType, 'trigger type check')
}
if ( window.refreshCounter1 == 1 ) {
@ -298,7 +298,7 @@ test( "model loadAssets tests - 1", function() {
App.Delay.set( function() {
test( "model loadAssets tests - 1 / check refresh counter", function() {
equal( window.refreshCounter1, 2, 'check refresh counter')
equal(window.refreshCounter1, 2, 'check refresh counter')
});
},
1000
@ -308,12 +308,12 @@ test( "model loadAssets tests - 2", function() {
window.refreshCounter2 = 0
var callback2 = function(state, triggerType) {
window.refreshCounter2 = window.refreshCounter2 + 1
equal( state.id, 10000, 'id check')
equal(state.id, 10000, 'id check')
if (window.refreshCounter2 == 1) {
equal( 'full', triggerType, 'trigger type check')
equal('full', triggerType, 'trigger type check')
}
else {
equal( 'refresh', triggerType, 'trigger type check')
equal('refresh', triggerType, 'trigger type check')
}
if ( window.refreshCounter2 == 1 ) {
App.Collection.loadAssets({
@ -349,7 +349,7 @@ test( "model loadAssets tests - 2", function() {
App.Delay.set( function() {
test( "model loadAssets tests - 2 / check refresh counter", function() {
equal( window.refreshCounter2, 2, 'check refresh counter')
equal(window.refreshCounter2, 2, 'check refresh counter')
});
},
1200
@ -359,12 +359,12 @@ test( "model loadAssets tests - 3", function() {
window.refreshCounter3 = 0
var callback3 = function(state, triggerType) {
window.refreshCounter3 = window.refreshCounter3 + 1
equal( state.id, 10001, 'id check')
equal(state.id, 10001, 'id check')
if (window.refreshCounter3 == 1) {
equal( 'full', triggerType, 'trigger type check')
equal('full', triggerType, 'trigger type check')
}
else {
equal( 'refresh', triggerType, 'trigger type check')
equal('refresh', triggerType, 'trigger type check')
}
if ( window.refreshCounter3 == 1 ) {
@ -401,7 +401,7 @@ test( "model loadAssets tests - 3", function() {
App.Delay.set( function() {
test( "model loadAssets tests - 3 / check refresh counter", function() {
equal( window.refreshCounter3, 3, 'check refresh counter')
equal(window.refreshCounter3, 3, 'check refresh counter')
});
},
1400

View file

@ -91,15 +91,38 @@ class ApiAuthControllerTest < ActionDispatch::IntegrationTest
assert_equal(Hash, result_ticket_create.class)
assert_equal(result_ticket_create['created_by_id'], @customer.id)
get '/api/v1/activity_stream', params: {}, headers: admin_headers
get '/api/v1/activity_stream?full=true', params: {}, headers: admin_headers
assert_response(200)
result_activity_stream = JSON.parse(@response.body)
assert_equal(Hash, result_activity_stream.class)
ticket_created = result_activity_stream['activity_stream'].find { |activity| activity['object'] == 'Ticket' && activity['o_id'] == result_ticket_create['id'] }
ticket_created = nil
result_activity_stream['record_ids'].each do |record_id|
activity_stream = ActivityStream.find(record_id)
next if activity_stream.object.name != 'Ticket'
next if activity_stream.o_id != result_ticket_create['id']
ticket_created = activity_stream
end
assert(ticket_created)
assert_equal(ticket_created.created_by_id, @customer.id)
get '/api/v1/activity_stream', params: {}, headers: admin_headers
assert_response(200)
result_activity_stream = JSON.parse(@response.body)
assert_equal(Array, result_activity_stream.class)
ticket_created = nil
result_activity_stream.each do |record|
activity_stream = ActivityStream.find(record['id'])
next if activity_stream.object.name != 'Ticket'
next if activity_stream.o_id != result_ticket_create['id']
ticket_created = activity_stream
end
assert(ticket_created)
assert_equal(ticket_created.created_by_id, @customer.id)
assert_equal(Hash, ticket_created.class)
assert_equal(ticket_created['created_by_id'], @customer.id)
end
test 'X-On-Behalf-Of auth - ticket create admin for customer by email' do

View file

@ -62,24 +62,24 @@ class ActivityStreamTest < ActiveSupport::TestCase
# check activity_stream
stream = @admin_user.activity_stream(4)
assert_equal(stream[0]['group_id'], ticket.group_id)
assert_equal(stream[0]['o_id'], ticket.id)
assert_equal(stream[0]['created_by_id'], @current_user.id)
assert_equal(stream[0]['created_at'].to_s, updated_at.to_s)
assert_equal(stream[0]['object'], 'Ticket')
assert_equal(stream[0]['type'], 'update')
assert_equal(stream[1]['group_id'], ticket.group_id)
assert_equal(stream[1]['o_id'], article.id)
assert_equal(stream[1]['created_by_id'], @current_user.id)
assert_equal(stream[1]['created_at'].to_s, article.created_at.to_s)
assert_equal(stream[1]['object'], 'Ticket::Article')
assert_equal(stream[1]['type'], 'create')
assert_equal(stream[2]['group_id'], ticket.group_id)
assert_equal(stream[2]['o_id'], ticket.id)
assert_equal(stream[2]['created_by_id'], @current_user.id)
assert_equal(stream[2]['created_at'].to_s, ticket.created_at.to_s)
assert_equal(stream[2]['object'], 'Ticket')
assert_equal(stream[2]['type'], 'create')
assert_equal(stream[0].group_id, ticket.group_id)
assert_equal(stream[0].o_id, ticket.id)
assert_equal(stream[0].created_by_id, @current_user.id)
assert_equal(stream[0].created_at.to_s, updated_at.to_s)
assert_equal(stream[0].object.name, 'Ticket')
assert_equal(stream[0].type.name, 'update')
assert_equal(stream[1].group_id, ticket.group_id)
assert_equal(stream[1].o_id, article.id)
assert_equal(stream[1].created_by_id, @current_user.id)
assert_equal(stream[1].created_at.to_s, article.created_at.to_s)
assert_equal(stream[1].object.name, 'Ticket::Article')
assert_equal(stream[1].type.name, 'create')
assert_equal(stream[2].group_id, ticket.group_id)
assert_equal(stream[2].o_id, ticket.id)
assert_equal(stream[2].created_by_id, @current_user.id)
assert_equal(stream[2].created_at.to_s, ticket.created_at.to_s)
assert_equal(stream[2].object.name, 'Ticket')
assert_equal(stream[2].type.name, 'create')
assert_not(stream[3])
stream = @current_user.activity_stream(4)
@ -90,18 +90,18 @@ class ActivityStreamTest < ActiveSupport::TestCase
# check activity_stream
stream = @admin_user.activity_stream(4)
assert_equal(stream[0]['group_id'], ticket.group_id)
assert_equal(stream[0]['o_id'], ticket.id)
assert_equal(stream[0]['created_by_id'], @current_user.id)
assert_equal(stream[0]['created_at'].to_s, updated_at.to_s)
assert_equal(stream[0]['object'], 'Ticket')
assert_equal(stream[0]['type'], 'update')
assert_equal(stream[1]['group_id'], ticket.group_id)
assert_equal(stream[1]['o_id'], ticket.id)
assert_equal(stream[1]['created_by_id'], @current_user.id)
assert_equal(stream[1]['created_at'].to_s, ticket.created_at.to_s)
assert_equal(stream[1]['object'], 'Ticket')
assert_equal(stream[1]['type'], 'create')
assert_equal(stream[0].group_id, ticket.group_id)
assert_equal(stream[0].o_id, ticket.id)
assert_equal(stream[0].created_by_id, @current_user.id)
assert_equal(stream[0].created_at.to_s, updated_at.to_s)
assert_equal(stream[0].object.name, 'Ticket')
assert_equal(stream[0].type.name, 'update')
assert_equal(stream[1].group_id, ticket.group_id)
assert_equal(stream[1].o_id, ticket.id)
assert_equal(stream[1].created_by_id, @current_user.id)
assert_equal(stream[1].created_at.to_s, ticket.created_at.to_s)
assert_equal(stream[1].object.name, 'Ticket')
assert_equal(stream[1].type.name, 'create')
assert_not(stream[2])
stream = @current_user.activity_stream(4)
@ -129,18 +129,18 @@ class ActivityStreamTest < ActiveSupport::TestCase
# check activity_stream
stream = @admin_user.activity_stream(3)
assert_not(stream[0]['group_id'])
assert_equal(stream[0]['o_id'], organization.id)
assert_equal(stream[0]['created_by_id'], @current_user.id)
assert_equal(stream[0]['created_at'].to_s, updated_at.to_s)
assert_equal(stream[0]['object'], 'Organization')
assert_equal(stream[0]['type'], 'update')
assert_not(stream[1]['group_id'])
assert_equal(stream[1]['o_id'], organization.id)
assert_equal(stream[1]['created_by_id'], @current_user.id)
assert_equal(stream[1]['created_at'].to_s, organization.created_at.to_s)
assert_equal(stream[1]['object'], 'Organization')
assert_equal(stream[1]['type'], 'create')
assert_not(stream[0].group_id)
assert_equal(stream[0].o_id, organization.id)
assert_equal(stream[0].created_by_id, @current_user.id)
assert_equal(stream[0].created_at.to_s, updated_at.to_s)
assert_equal(stream[0].object.name, 'Organization')
assert_equal(stream[0].type.name, 'update')
assert_not(stream[1].group_id)
assert_equal(stream[1].o_id, organization.id)
assert_equal(stream[1].created_by_id, @current_user.id)
assert_equal(stream[1].created_at.to_s, organization.created_at.to_s)
assert_equal(stream[1].object.name, 'Organization')
assert_equal(stream[1].type.name, 'create')
assert_not(stream[2])
stream = @current_user.activity_stream(4)
@ -167,12 +167,12 @@ class ActivityStreamTest < ActiveSupport::TestCase
# check activity_stream
stream = @admin_user.activity_stream(3)
assert_not(stream[0]['group_id'])
assert_equal(stream[0]['o_id'], user.id)
assert_equal(stream[0]['created_by_id'], @current_user.id)
assert_equal(stream[0]['created_at'].to_s, user.created_at.to_s)
assert_equal(stream[0]['object'], 'User')
assert_equal(stream[0]['type'], 'create')
assert_not(stream[0].group_id)
assert_equal(stream[0].o_id, user.id)
assert_equal(stream[0].created_by_id, @current_user.id)
assert_equal(stream[0].created_at.to_s, user.created_at.to_s)
assert_equal(stream[0].object.name, 'User')
assert_equal(stream[0].type.name, 'create')
assert_not(stream[1])
stream = @current_user.activity_stream(4)
@ -208,18 +208,18 @@ class ActivityStreamTest < ActiveSupport::TestCase
# check activity_stream
stream = @admin_user.activity_stream(3)
assert_not(stream[0]['group_id'])
assert_equal(stream[0]['o_id'], user.id)
assert_equal(stream[0]['created_by_id'], @current_user.id)
assert_equal(stream[0]['created_at'].to_s, updated_at.to_s)
assert_equal(stream[0]['object'], 'User')
assert_equal(stream[0]['type'], 'update')
assert_not(stream[1]['group_id'])
assert_equal(stream[1]['o_id'], user.id)
assert_equal(stream[1]['created_by_id'], @current_user.id)
assert_equal(stream[1]['created_at'].to_s, user.created_at.to_s)
assert_equal(stream[1]['object'], 'User')
assert_equal(stream[1]['type'], 'create')
assert_not(stream[0].group_id)
assert_equal(stream[0].o_id, user.id)
assert_equal(stream[0].created_by_id, @current_user.id)
assert_equal(stream[0].created_at.to_s, updated_at.to_s)
assert_equal(stream[0].object.name, 'User')
assert_equal(stream[0].type.name, 'update')
assert_not(stream[1].group_id)
assert_equal(stream[1].o_id, user.id)
assert_equal(stream[1].created_by_id, @current_user.id)
assert_equal(stream[1].created_at.to_s, user.created_at.to_s)
assert_equal(stream[1].object.name, 'User')
assert_equal(stream[1].type.name, 'create')
assert_not(stream[2])
stream = @current_user.activity_stream(4)

View file

@ -37,11 +37,11 @@ class RecentViewTest < ActiveSupport::TestCase
RecentView.log(ticket1.class.to_s, ticket1.id, user1)
list = RecentView.list(user1)
assert(list[0]['o_id'], ticket1.id)
assert(list[0]['object'], 'Ticket')
assert(list[0].o_id, ticket1.id)
assert(list[0].object.name, 'Ticket')
assert(list[1]['o_id'], ticket2.id)
assert(list[1]['object'], 'Ticket')
assert(list[1].o_id, ticket2.id)
assert(list[1].object.name, 'Ticket')
assert_equal(2, list.count)
ticket1.destroy
@ -156,8 +156,8 @@ class RecentViewTest < ActiveSupport::TestCase
# check if list is empty
list = RecentView.list(customer)
assert(list[0]['o_id'], ticket1.id)
assert(list[0]['object'], 'Ticket')
assert(list[0].o_id, ticket1.id)
assert(list[0].object.name, 'Ticket')
assert_not(list[1], 'check if recent view list is empty')
# log entry
@ -178,8 +178,8 @@ class RecentViewTest < ActiveSupport::TestCase
# check if list is empty
list = RecentView.list(agent)
assert(list[0]['o_id'], organization1.id)
assert(list[0]['object'], 'Organization')
assert(list[0].o_id, organization1.id)
assert(list[0].object.name, 'Organization')
assert_not(list[1], 'check if recent view list is empty')
organization2.destroy