Improved history and activity stream.

This commit is contained in:
Martin Edenhofer 2012-07-10 10:09:58 +02:00
parent 3b3ecd279d
commit 5cd1012d88
10 changed files with 433 additions and 135 deletions

View file

@ -40,6 +40,9 @@ class App.DashboardActivityStream extends App.Controller
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
# load article collection
@loadCollection( type: 'TicketArticle', data: data.articles )
# set cache
window.LastRefresh[ 'dashboard_activity_stream' ] = items
@ -49,11 +52,19 @@ class App.DashboardActivityStream extends App.Controller
# load user data
for item in items
item.created_by = App.User.find(item.created_by_id)
item.created_by = App.User.find( item.created_by_id )
# load ticket data
for item in items
item.ticket = App.Ticket.find(item.o_id)
item.data = {}
if item.history_object is 'Ticket'
item.data.title = App.Ticket.find( item.o_id ).title
if item.history_object is 'Ticket::Article'
article = App.TicketArticle.find( item.o_id )
item.history_object = 'Article'
item.sub_o_id = article.id
item.o_id = article.ticket_id
item.data.title = article.subject
html = App.view('dashboard/activity_stream')(
head: 'Activity Stream',
@ -69,5 +80,9 @@ class App.DashboardActivityStream extends App.Controller
zoom: (e) =>
e.preventDefault()
id = $(e.target).parents('[data-id]').data('id')
@log 'goto zoom!'
@navigate 'ticket/zoom/' + id
subid = $(e.target).parents('[data-subid]').data('subid')
@log 'goto zoom!', id, subid
if subid
@navigate 'ticket/zoom/' + id + '/' + subid
else
@navigate 'ticket/zoom/' + id

View file

@ -9,10 +9,6 @@ class App.TicketHistory extends App.ControllerModal
@ajax.ajax(
type: 'GET',
url: '/ticket_history/' + ticket_id,
data: {
# view: @view
}
# processData: true,
success: (data, status, xhr) =>
# remember ticket
@ticket = data.ticket
@ -42,12 +38,11 @@ class App.TicketHistory extends App.ControllerModal
render: ->
# create table/overview
table = @table(
overview_extended: [
{ name: 'type', },
{ name: 'attribute', },
{ name: 'history_type', },
{ name: 'history_attribute', },
{ name: 'value_from', },
{ name: 'value_to', },
{ name: 'created_by', class: 'user-data', data: { id: 1 } },
@ -57,11 +52,7 @@ class App.TicketHistory extends App.ControllerModal
objects: App.History.all(),
)
@html App.view('agent_ticket_history')(
# head: 'New User',
# form: @formGen( model: App.User, required: 'quick' ),
)
@html App.view('agent_ticket_history')()
@el.find('.table_history').append(table)
@modalShow()

View file

@ -4,7 +4,7 @@
<dl>
<% for item in @items: %>
<dt><span class="user-data" data-id="<%= item.created_by_id %>">"<%= item.created_by.realname %>"</span></dt>
<dd><%- T(item.history_type.name) %> <span data-id="<%= item.o_id %>"><a data-type="edit" href="#"><%= item.history_object.name %> (<%= item.ticket.title %>)</a></span>.</dd>
<dd><%- T(item.history_type) %> <span data-id="<%= item.o_id %>" data-subid="<%= item.sub_o_id %>"><a data-type="edit" href="#"><%= item.history_object %><% if item.data.title: %> (<%= item.data.title %>)<% end %></a></span>.</dd>
<% end %>
</dl>
</div>

View file

@ -413,14 +413,17 @@ class TicketOverviewsController < ApplicationController
ticket = Ticket.find(params[:id])
# get history of ticket
history = History.history_list(['Ticket', 'Ticket::Article'], params[:id])
history = History.history_list( 'Ticket', params[:id], 'Ticket::Article' )
# get related users
users = {}
history.each do |item|
users[item.created_by_id] = user_data_full(item.created_by_id)
# item['history_attribute'] = item.history_attribute
# item['history_type'] = item.history_type
users[ item['created_by_id'] ] = user_data_full( item['created_by_id'] )
if item['history_object'] == 'Ticket::Article'
item['type'] = 'Article ' + item['type'].to_s
else
item['type'] = 'Ticket ' + item['type'].to_s
end
end
# fetch meta relations
@ -495,18 +498,23 @@ class TicketOverviewsController < ApplicationController
# get related users
users = {}
tickets = []
articles = []
activity_stream.each {|item|
# load article ids
# if item.history_object == 'Ticket'
tickets.push Ticket.find( item['o_id'] )
# end
# if item.history_object 'Ticket::Article'
# tickets.push Ticket::Article.find(item.o_id)
# end
# if item.history_object 'User'
# tickets.push User.find(item.o_id)
# end
if item['history_object'] == 'Ticket'
tickets.push Ticket.find( item['o_id'] ).attributes
end
if item['history_object'] == 'Ticket::Article'
article = Ticket::Article.find( item['o_id'] ).attributes
if !article['subject'] || article['subject'] == ''
article['subject'] = Ticket.find( article['ticket_id'] ).title
end
articles.push article
end
if item['history_object'] == 'User'
users[ item['o_id'] ] = user_data_full( item['o_id'] )
end
# load users
if !users[ item['created_by_id'] ]
@ -518,6 +526,7 @@ class TicketOverviewsController < ApplicationController
render :json => {
:activity_stream => activity_stream,
:tickets => tickets,
:articles => articles,
:users => users,
}
end
@ -534,7 +543,7 @@ class TicketOverviewsController < ApplicationController
# load article ids
# if item.history_object == 'Ticket'
tickets.push Ticket.find( item['o_id'] )
tickets.push Ticket.find( item['o_id'] ).attributes
# end
# if item.history_object 'Ticket::Article'
# tickets.push Ticket::Article.find(item.o_id)

View file

@ -6,15 +6,113 @@ class History < ActiveRecord::Base
# before_validation :check_type, :check_object
# attr_writer :history_type, :history_object
# def history_type(a)
# end
def self.history_list(requested_object, requested_object_id)
history = History.where( :history_object_id => History::Object.where( :name => requested_object ) ).
def self.history_create(data)
# lookups
history_type = History::Type.where( :name => data[:history_type] ).first
if !history_type || !history_type.id
history_type = History::Type.create(
:name => data[:history_type]
)
end
history_object = History::Object.where( :name => data[:history_object] ).first
if !history_object || !history_object.id
history_object = History::Object.create(
:name => data[:history_object]
)
end
related_history_object_id = nil
if data[:related_history_object]
related_history_object = History::Object.where( :name => data[:related_history_object] ).first
if !related_history_object || !related_history_object.id
related_history_object = History::Object.create(
:name => data[:related_history_object]
)
end
related_history_object_id = related_history_object.id
end
history_attribute_id = nil
if data[:history_attribute]
history_attribute = History::Attribute.where( :name => data[:history_attribute] ).first
if !history_attribute || !history_attribute.object_id
history_attribute = History::Attribute.create(
:name => data[:history_attribute]
)
end
history_attribute_id = history_attribute.id
end
# create history
History.create(
:o_id => data[:o_id],
:history_type_id => history_type.id,
:history_object_id => history_object.id,
:history_attribute_id => history_attribute_id,
:related_history_object_id => related_history_object_id,
:related_o_id => data[:related_o_id],
:value_from => data[:value_from],
:value_to => data[:value_to],
:id_from => data[:id_from],
:id_to => data[:id_to],
:created_by_id => data[:created_by_id]
)
end
def self.history_destroy(requested_object, requested_object_id)
History.where( :history_object_id => History::Object.where( :name => requested_object ) ).
where( :o_id => requested_object_id ).
where( :history_type_id => History::Type.where( :name => ['created', 'updated']) ).
order('created_at ASC, id ASC')
return history
destroy_all
end
def self.history_list(requested_object, requested_object_id, related_history_object = nil)
if !related_history_object
history = History.where( :history_object_id => History::Object.where( :name => requested_object ) ).
where( :o_id => requested_object_id ).
where( :history_type_id => History::Type.where( :name => ['created', 'updated']) ).
order('created_at ASC, id ASC')
else
history = History.where(
'((history_object_id = ? AND o_id = ?) OR (history_object_id = ? AND related_o_id = ? )) AND history_type_id IN (?)',
History::Object.where( :name => requested_object ).first.id,
requested_object_id,
History::Object.where( :name => related_history_object ).first.id,
requested_object_id,
History::Type.where( :name => ['created', 'updated'] )
).
order('created_at ASC, id ASC')
end
list = []
history.each { |item|
item_tmp = item.attributes
item_tmp['history_type'] = item.history_type.name
item_tmp['history_object'] = item.history_object.name
if item.history_attribute
item_tmp['history_attribute'] = item.history_attribute.name
end
item_tmp.delete( 'history_attribute_id' )
item_tmp.delete( 'history_object_id' )
item_tmp.delete( 'history_type_id' )
item_tmp.delete( 'o_id' )
item_tmp.delete( 'updated_at' )
if item_tmp['id_to'] == nil && item_tmp['id_from'] == nil
item_tmp.delete( 'id_to' )
item_tmp.delete( 'id_from' )
end
if item_tmp['value_to'] == nil && item_tmp['value_from'] == nil
item_tmp.delete( 'value_to' )
item_tmp.delete( 'value_from' )
end
if item_tmp['related_history_object_id'] == nil
item_tmp.delete( 'related_history_object_id' )
end
if item_tmp['related_o_id'] == nil
item_tmp.delete( 'related_o_id' )
end
list.push item_tmp
}
return list
end
def self.activity_stream(user, limit = 10)
@ -22,15 +120,17 @@ class History < ActiveRecord::Base
# stream = History.select("distinct(histories.o_id), created_by_id, history_attribute_id, history_type_id, history_object_id, value_from, value_to").
# where( :history_type_id => History::Type.where( :name => ['created', 'updated']) ).
stream = History.select("distinct(histories.o_id), created_by_id, history_type_id, history_object_id").
where( :history_object_id => History::Object.where( :name => 'Ticket').first.id ).
where( :history_type_id => History::Type.where( :name => ['updated']) ).
where( :history_object_id => History::Object.where( :name => [ 'Ticket', 'Ticket::Article' ] ) ).
where( :history_type_id => History::Type.where( :name => [ 'created', 'updated' ]) ).
order('created_at DESC, id DESC').
limit(limit)
datas = []
stream.each do |item|
data = item.attributes
data['history_object'] = item.history_object
data['history_type'] = item.history_type
data['history_object'] = item.history_object.name
data['history_type'] = item.history_type.name
data.delete('history_object_id')
data.delete('history_type_id')
datas.push data
# item['history_attribute'] = item.history_attribute
end

View file

@ -5,29 +5,20 @@ class HistoryObserver < ActiveRecord::Observer
def after_create(record)
puts 'HISTORY OBSERVER CREATE !!!!' + record.class.name
puts record.inspect
history_type = History::Type.where( :name => 'created' ).first
if !history_type || !history_type.id
history_type = History::Type.create(
:name => 'created'
)
related_o_id = nil
related_history_object_id = nil
if record.class.name == 'Ticket::Article'
related_o_id = record.ticket_id
related_history_object = 'Ticket'
end
history_object = History::Object.where( :name => record.class.name ).first
if !history_object || !history_object.id
history_object = History::Object.create(
:name => record.class.name
)
end
History.create(
:o_id => record.id,
:history_type_id => history_type.id,
:history_object_id => history_object.id,
:created_by_id => current_user_id || record.created_by_id || 1
History.history_create(
:o_id => record.id,
:history_type => 'created',
:history_object => record.class.name,
:related_o_id => related_o_id,
:related_history_object => related_history_object,
:created_by_id => current_user_id || record.created_by_id || 1
)
# :name => record.class.name,
# :type => 'create',
# :data => record
end
def before_update(record)
@ -53,61 +44,50 @@ class HistoryObserver < ActiveRecord::Observer
puts 'CURRENT USER ID'
puts current_user_id
history_type = History::Type.where( :name => 'updated' ).first
if !history_type || !history_type.id
history_type = History::Type.create(
:name => 'updated'
)
end
history_object = History::Object.where( :name => record.class.name ).first
if !history_object || !history_object.id
history_object = History::Object.create(
:name => record.class.name
)
end
map = {
:group_id => {
:attribute => 'Group',
:lookup_object => Group,
:lookup_name => 'name',
},
:title => {
:attribute => 'Title',
},
:number => {
:attribute => 'Number',
},
:owner_id => {
:attribute => 'Owner',
:lookup_object => User,
:lookup_name => ['firstname', 'lastname'],
:lookup_object => User,
:lookup_method => 'fullname',
},
:ticket_state_id => {
:attribute => 'State',
:lookup_object => Ticket::State,
:lookup_name => 'name',
:lookup_object => Ticket::State,
:lookup_name => 'name',
},
:ticket_priority_id => {
:attribute => 'Priority',
:lookup_object => Ticket::Priority,
:lookup_name => 'name',
:lookup_object => Ticket::Priority,
:lookup_name => 'name',
}
}
diff.each do |key, value_ids|
puts "#{key} is #{value_ids}"
# do not log created_at and updated_at attributes
next if key.to_s == 'created_at'
next if key.to_s == 'updated_at'
puts "#{key} is #{value_ids.inspect}"
# check if diff are ids, if yes do lookup
if value_ids[0].to_s == value_ids[1].to_s
puts 'NEXT!!'
next
end
# check if diff are ids, if yes do lookup
value = []
if map[key.to_sym] && map[key.to_sym][:lookup_object]
value[0] = ''
value[1] = ''
if map[key.to_sym][:lookup_name].class == Array
# name base
if map[key.to_sym][:lookup_name]
if map[key.to_sym][:lookup_name].class != Array
map[key.to_sym][:lookup_name] = [ map[key.to_sym][:lookup_name] ]
end
map[key.to_sym][:lookup_name].each do |item|
if value[0] != ''
value[0] = value[0] + ' '
@ -118,9 +98,12 @@ class HistoryObserver < ActiveRecord::Observer
end
value[1] = value[1] + map[key.to_sym][:lookup_object].find(value_ids[1])[item.to_sym].to_s
end
else
value[0] = map[key.to_sym][:lookup_object].find(value_ids[0])[map[key.to_sym][:lookup_name]]
value[1] = map[key.to_sym][:lookup_object].find(value_ids[1])[map[key.to_sym][:lookup_name]]
end
# method base
if map[key.to_sym][:lookup_method]
value[0] = map[key.to_sym][:lookup_object].find( value_ids[0] ).send( map[key.to_sym][:lookup_method] )
value[1] = map[key.to_sym][:lookup_object].find( value_ids[1] ).send( map[key.to_sym][:lookup_method] )
end
# if not, fill diff data to value, empty value_ids
@ -128,33 +111,25 @@ class HistoryObserver < ActiveRecord::Observer
value = value_ids
value_ids = []
end
attribute_name = ''
if map[key.to_sym] && map[key.to_sym][:attribute].to_s
attribute_name = map[key.to_sym][:attribute].to_s
else
attribute_name = key
# get attribute name
attribute_name = key.to_s
if attribute_name.scan(/^(.*)_id$/).first
attribute_name = attribute_name.scan(/^(.*)_id$/).first.first
end
puts 'LLLLLLLLLLLLLLLLLLLLLLLL' + attribute_name.to_s
attribute = History::Attribute.where( :name => attribute_name.to_s ).first
if !attribute || !attribute.object_id
attribute = History::Attribute.create(
:name => attribute_name
)
end
# puts '9999999'
# puts current.object_id
# puts current.id
History.create(
:o_id => current.id,
:history_type_id => history_type.id,
:history_object_id => history_object.id,
:history_attribute_id => attribute.id,
:value_from => value[0],
:value_to => value[1],
:id_from => value_ids[0],
:id_to => value_ids[1],
:created_by_id => current_user_id || 1 || self['created_by_id'] || 1
History.history_create(
:o_id => current.id,
:history_type => 'updated',
:history_object => record.class.name,
:history_attribute => attribute_name,
:value_from => value[0],
:value_to => value[1],
:id_from => value_ids[0],
:id_to => value_ids[1],
:created_by_id => current_user_id || 1 || self['created_by_id'] || 1
)
end

View file

@ -16,7 +16,7 @@ class Ticket::Observer::LastContact < ActiveRecord::Observer
# check if last communication is done by agent, else do not set last_contact_customer
if record.ticket.last_contact_customer == nil ||
record.ticket.last_contact_agent == nil ||
record.ticket.last_contact_agent > record.ticket.last_contact_customer
record.ticket.last_contact_agent.to_i > record.ticket.last_contact_customer.to_i
record.ticket.last_contact_customer = Time.now
# set last_contact

View file

@ -16,6 +16,20 @@ class User < ApplicationModel
store :preferences
def fullname
fullname = ''
if self.firstname
fullname = fullname + self.firstname
end
if self.lastname
if fullname != ''
fullname = fullname + ' '
end
fullname = fullname + self.lastname
end
return fullname
end
def self.authenticate( username, password )
# do not authenticate with nothing

View file

@ -2,15 +2,17 @@ class CreateHistory < ActiveRecord::Migration
def up
create_table :histories do |t|
t.references :history_type, :null => false
t.references :history_object, :null => false
t.references :history_attribute, :null => true
t.column :o_id, :integer, :null => false
t.column :id_to, :integer, :null => true
t.column :id_from, :integer, :null => true
t.column :value_from, :string, :limit => 250, :null => true
t.column :value_to, :string, :limit => 250, :null => true
t.column :created_by_id, :integer, :null => false
t.references :history_type, :null => false
t.references :history_object, :null => false
t.references :history_attribute, :null => true
t.column :o_id, :integer, :null => false
t.column :related_o_id, :integer, :null => true
t.column :related_history_object_id, :integer, :null => true
t.column :id_to, :integer, :null => true
t.column :id_from, :integer, :null => true
t.column :value_from, :string, :limit => 250, :null => true
t.column :value_to, :string, :limit => 250, :null => true
t.column :created_by_id, :integer, :null => false
t.timestamps
end
add_index :histories, [:o_id]

192
test/unit/history_test.rb Normal file
View file

@ -0,0 +1,192 @@
# encoding: utf-8
require 'test_helper'
class HistoryTest < ActiveSupport::TestCase
test 'ticket' do
tests = [
# test 1
{
:ticket_create => {
:ticket => {
:group_id => Group.where( :name => 'Users' ).first.id,
:customer_id => User.where( :login => 'nicole.braun@zammad.org' ).first.id,
:owner_id => User.where( :login => '-' ).first.id,
:title => 'Unit Test 1 (äöüß)!',
:ticket_state_id => Ticket::State.where( :name => 'new' ).first.id,
:ticket_priority_id => Ticket::Priority.where( :name => '2 normal' ).first.id,
:created_by_id => User.where( :login => 'nicole.braun@zammad.org' ).first.id
},
:article => {
:created_by_id => User.where( :login => 'nicole.braun@zammad.org' ).first.id,
:ticket_article_type_id => Ticket::Article::Type.where(:name => 'phone' ).first.id,
:ticket_article_sender_id => Ticket::Article::Sender.where(:name => 'Customer' ).first.id,
:from => 'Unit Test <unittest@example.com>',
:body => 'Unit Test 123',
:internal => false
},
},
:ticket_update => {
:ticket => {
:title => 'Unit Test 1 (äöüß) - update!',
:ticket_state_id => Ticket::State.where( :name => 'open' ).first.id,
:ticket_priority_id => Ticket::Priority.where( :name => '1 low' ).first.id,
},
},
:history_check => [
{
:history_object => 'Ticket',
:history_type => 'created',
},
{
:history_object => 'Ticket',
:history_type => 'updated',
:history_attribute => 'title',
:value_from => 'Unit Test 1 (äöüß)!',
:value_to => 'Unit Test 1 (äöüß) - update!',
},
{
:history_object => 'Ticket',
:history_type => 'updated',
:history_attribute => 'ticket_state',
:value_from => 'new',
:value_to => 'open',
:id_from => Ticket::State.where( :name => 'new' ).first.id,
:id_to => Ticket::State.where( :name => 'open' ).first.id,
},
{
:history_object => 'Ticket::Article',
:history_type => 'created',
},
]
},
# test 2
{
:ticket_create => {
:ticket => {
:group_id => Group.where( :name => 'Users' ).first.id,
:customer_id => User.where( :login => 'nicole.braun@zammad.org' ).first.id,
:owner_id => User.where( :login => '-' ).first.id,
:title => 'Unit Test 2 (äöüß)!',
:ticket_state_id => Ticket::State.where( :name => 'new' ).first.id,
:ticket_priority_id => Ticket::Priority.where( :name => '2 normal' ).first.id,
:created_by_id => User.where( :login => 'nicole.braun@zammad.org' ).first.id
},
:article => {
:created_by_id => User.where( :login => 'nicole.braun@zammad.org' ).first.id,
:ticket_article_type_id => Ticket::Article::Type.where(:name => 'phone' ).first.id,
:ticket_article_sender_id => Ticket::Article::Sender.where(:name => 'Customer' ).first.id,
:from => 'Unit Test <unittest@example.com>',
:body => 'Unit Test 123',
:internal => false
},
},
:ticket_update => {
:ticket => {
:title => 'Unit Test 2 (äöüß) - update!',
:ticket_state_id => Ticket::State.where( :name => 'open' ).first.id,
:owner_id => User.where( :login => 'nicole.braun@zammad.org' ).first.id,
},
},
:history_check => [
{
:history_object => 'Ticket',
:history_type => 'created',
},
{
:history_object => 'Ticket',
:history_type => 'updated',
:history_attribute => 'title',
:value_from => 'Unit Test 2 (äöüß)!',
:value_to => 'Unit Test 2 (äöüß) - update!',
},
{
:history_object => 'Ticket',
:history_type => 'updated',
:history_attribute => 'owner',
:value_from => '-',
:value_to => 'Nicole Braun',
:id_from => User.where( :login => '-' ).first.id,
:id_to => User.where( :login => 'nicole.braun@zammad.org' ).first.id,
},
{
:history_object => 'Ticket::Article',
:history_type => 'created',
},
]
},
]
tickets = []
tests.each { |test|
ticket = nil
article = nil
# use transaction
ActiveRecord::Base.transaction do
ticket = Ticket.create( test[:ticket_create][:ticket])
test[:ticket_create][:article][:ticket_id] = ticket.id
article = Ticket::Article.create( test[:ticket_create][:article] )
assert_equal( ticket.class.to_s, 'Ticket' )
assert_equal( article.class.to_s, 'Ticket::Article' )
ticket.update_attributes( test[:ticket_update][:ticket] )
end
# execute ticket events
Ticket::Observer::Notification.transaction
# remember ticket
tickets.push ticket
# get history
history_list = History.history_list( 'Ticket', ticket.id, 'Ticket::Article' )
puts history_list.inspect
test[:history_check].each { |check_item|
# puts '+++++++++++'
# puts check_item.inspect
match = false
history_list.each { |history_item|
next if match
# puts '--------'
# puts history_item.inspect
next if history_item['history_object'] != check_item[:history_object]
next if history_item['history_type'] != check_item[:history_type]
next if check_item[:history_attribute] != history_item['history_attribute']
match = true
if history_item['history_type'] == check_item[:history_type]
assert( true, "History type #{history_item['history_type']} found!")
end
if check_item[:history_attribute]
assert_equal( check_item[:history_attribute], history_item['history_attribute'], "check history attribute #{check_item[:history_attribute]}")
end
if check_item[:value_from]
assert_equal( check_item[:value_from], history_item['value_from'], "check history :value_from #{history_item['value_from']} ok")
end
if check_item[:value_to]
assert_equal( check_item[:value_to], history_item['value_to'], "check history :value_to #{history_item['value_to']} ok")
end
if check_item[:id_from]
assert_equal( check_item[:id_from], history_item['id_from'], "check history :id_from #{history_item['id_from']} ok")
end
if check_item[:id_to]
assert_equal( check_item[:id_to], history_item['id_to'], "check history :id_to #{history_item['id_to']} ok")
end
}
assert( match, "history check not matched!")
}
}
# delete tickets
tickets.each { |ticket|
ticket_id = ticket.id
ticket.destroy
found = Ticket.where( :id => ticket_id ).first
assert( !found, "Ticket destroyed")
}
end
end