Improved activity stream messages.

This commit is contained in:
Martin Edenhofer 2016-02-22 20:58:23 +01:00
parent 2c6a1c542a
commit 10f13cb618
21 changed files with 163 additions and 62 deletions

View file

@ -480,14 +480,15 @@ class App.Controller extends Spine.Controller
item.object = item.object.replace('::', '') item.object = item.object.replace('::', '')
# lookup real data # lookup real data
if App[item.object] && App[item.object].exists( item.o_id ) if App[item.object] && App[item.object].exists(item.o_id)
object = App[item.object].find( item.o_id ) object = App[item.object].find(item.o_id)
item.objectNative = object
item.link = object.uiUrl() item.link = object.uiUrl()
item.title = object.displayName() item.title = object.displayName()
item.object_name = object.objectDisplayName() item.object_name = object.objectDisplayName()
item.cssIcon = object.iconActivity( @Session.get() ) item.cssIcon = object.iconActivity(@Session.get())
item.created_by = App.User.find( item.created_by_id ) item.created_by = App.User.find(item.created_by_id)
items items
# central method, is getting called on every ticket form change # central method, is getting called on every ticket form change

View file

@ -40,6 +40,11 @@ class App.OnlineNotificationWidget extends App.Controller
@createContainer() @createContainer()
@subscribeId = App.OnlineNotification.subscribe(@updateContent) @subscribeId = App.OnlineNotification.subscribe(@updateContent)
@bind('ui:rerender', =>
@updateContent()
'popover'
)
release: -> release: ->
@removeContainer() @removeContainer()
$(window).off 'click.notifications' $(window).off 'click.notifications'
@ -141,8 +146,10 @@ class App.OnlineNotificationWidget extends App.Controller
if !@alreadyShown[item.id] if !@alreadyShown[item.id]
@alreadyShown[item.id] = true @alreadyShown[item.id] = true
if @fetchedData if @fetchedData
word = "#{item.type}d" if item.objectNative && item.objectNative.activityMessage
title = "#{item.created_by.displayName()} #{App.i18n.translateInline(word)} #{App.i18n.translateInline(item.object_name)} \"#{item.title}\"" title = item.objectNative.activityMessage(item)
else
title = "Need objectNative in item #{item.object}.find(#{item.o_id})"
@notifyDesktop( @notifyDesktop(
url: item.link url: item.link
title: title title: title

View file

@ -174,7 +174,7 @@ class _i18nSingleton extends Spine.Module
translateInline: (string, args) => translateInline: (string, args) =>
return string if !string return string if !string
App.Utils.htmlEscape(@translate(string, args)) @translate(string, args, true)
translateContent: (string, args) => translateContent: (string, args) =>
return string if !string return string if !string
@ -182,19 +182,12 @@ class _i18nSingleton extends Spine.Module
if App.Config.get('translation_inline') if App.Config.get('translation_inline')
return '<span class="translation" onclick="arguments[0].stopPropagation(); return false" contenteditable="true" title="' + App.Utils.htmlEscape(string) + '">' + App.Utils.htmlEscape(@translate(string)) + '</span>' return '<span class="translation" onclick="arguments[0].stopPropagation(); return false" contenteditable="true" title="' + App.Utils.htmlEscape(string) + '">' + App.Utils.htmlEscape(@translate(string)) + '</span>'
translated = App.Utils.htmlEscape(@translate(string, args)) translated = @translate(string, args, true, true)
# apply inline markup
translated
.replace(/\|\|(.+?)\|\|/gm, '<i>$1</i>')
.replace(/\|(.+?)\|/gm, '<b>$1</b>')
.replace(/_(.+?)_/gm, '<u>$1</u>')
.replace(/§(.+?)§/gm, '<kbd>$1</kbd>')
translatePlain: (string, args) => translatePlain: (string, args) =>
@translate(string, args) @translate(string, args)
translate: (string, args) => translate: (string, args, quote, markup) =>
# type convertation # type convertation
if typeof string isnt 'string' if typeof string isnt 'string'
@ -221,10 +214,27 @@ class _i18nSingleton extends Spine.Module
@log 'notice', "translation for '#{string}' in '#{@locale}' is missing" @log 'notice', "translation for '#{string}' in '#{@locale}' is missing"
@_notTranslated[@locale][string] = true @_notTranslated[@locale][string] = true
# apply html quote
if quote
translated = App.Utils.htmlEscape(translated)
# apply inline markup
if markup
translated = translated
.replace(/\|\|(.+?)\|\|/gm, '<i>$1</i>')
.replace(/\|(.+?)\|/gm, '<b>$1</b>')
.replace(/_(.+?)_/gm, '<u>$1</u>')
.replace(/§(.+?)§/gm, '<kbd>$1</kbd>')
# search %s # search %s
if args if args
for arg in args for arg in args
translated = translated.replace(/%s/, arg) if quote
argNew = App.Utils.htmlEscape(arg)
else
argNew = arg
translated = translated.replace(/%s/, argNew)
@log 'debug', 'translate', string, args, translated @log 'debug', 'translate', string, args, translated

View file

@ -643,3 +643,6 @@ class App.Model extends Spine.Model
return return
) )
collection collection
activityMessage: (item) ->
return "Need own activityMessage() in model to generate text (#{@objectDisplayName()}/#{item.type})."

View file

@ -20,3 +20,10 @@ class App.Group extends App.Model
uiUrl: -> uiUrl: ->
'#group/zoom/' + @id '#group/zoom/' + @id
activityMessage: (item) ->
if item.type is 'create'
return App.i18n.translateContent('%s created Group |%s|', item.created_by.displayName(), item.title)
else if item.type is 'update'
return App.i18n.translateContent('%s updated Group |%s|', item.created_by.displayName(), item.title)
return "Unknow action for (#{@objectDisplayName()}/#{item.type}), extend activityMessage() of model."

View file

@ -45,3 +45,10 @@ Mit **Organisationen** können Sie Kunden **gruppieren**. Dies hat u. a. zwei be
class: 'organization organization-popover' class: 'organization organization-popover'
url: @uiUrl() url: @uiUrl()
icon: 'organization' icon: 'organization'
activityMessage: (item) ->
if item.type is 'create'
return App.i18n.translateContent('%s created Organization |%s|', item.created_by.displayName(), item.title)
else if item.type is 'update'
return App.i18n.translateContent('%s updated Organization |%s|', item.created_by.displayName(), item.title)
return "Unknow action for (#{@objectDisplayName()}/#{item.type}), extend activityMessage() of model."

View file

@ -14,3 +14,10 @@ class App.Role extends App.Model
@configure_overview = [ @configure_overview = [
'name', 'name',
] ]
activityMessage: (item) ->
if item.type is 'create'
return App.i18n.translateContent('%s created Role |%s|', item.created_by.displayName(), item.title)
else if item.type is 'update'
return App.i18n.translateContent('%s updated Role |%s|', item.created_by.displayName(), item.title)
return "Unknow action for (#{@objectDisplayName()}/#{item.type}), extend activityMessage() of model."

View file

@ -77,3 +77,14 @@ class App.Ticket extends App.Model
url: @uiUrl() url: @uiUrl()
icon: 'task-state' icon: 'task-state'
iconClass: @getState() iconClass: @getState()
activityMessage: (item) ->
if item.type is 'create'
return App.i18n.translateContent('%s created Ticket |%s|', item.created_by.displayName(), item.title)
else if item.type is 'update'
return App.i18n.translateContent('%s updated Ticket |%s|', item.created_by.displayName(), item.title)
else if item.type is 'reminder_reached'
return App.i18n.translateContent('Pending reminder reached for Ticket |%s|', item.title)
else if item.type is 'escalation'
return App.i18n.translateContent('Ticket |%s| is escalated!', item.title)
return "Unknow action for (#{@objectDisplayName()}/#{item.type}), extend activityMessage() of model."

View file

@ -37,4 +37,11 @@ class App.TicketArticle extends App.Model
ticket = App.Ticket.find(@ticket_id) ticket = App.Ticket.find(@ticket_id)
if ticket.owner_id == user.id if ticket.owner_id == user.id
return 'important' return 'important'
'' ''
activityMessage: (item) ->
if item.type is 'create'
return App.i18n.translateContent('%s created Article for |%s|', item.created_by.displayName(), item.title)
else if item.type is 'update'
return App.i18n.translateContent('%s updated Article for |%s|', item.created_by.displayName(), item.title)
return "Unknow action for (#{@objectDisplayName()}/#{item.type}), extend activityMessage() of model."

View file

@ -142,3 +142,22 @@ class App.User extends App.Model
class: 'user user-popover' class: 'user user-popover'
url: @uiUrl() url: @uiUrl()
icon: 'user' icon: 'user'
activityMessage: (item) ->
if item.type is 'create'
return App.i18n.translateContent('%s created User |%s|', item.created_by.displayName(), item.title)
else if item.type is 'update'
return App.i18n.translateContent('%s updated User |%s|', item.created_by.displayName(), item.title)
else if item.type is 'session started'
return App.i18n.translateContent('%s started a new session', item.created_by.displayName())
else if item.type is 'switch to'
to = item.title
if item.objectNative
to = item.objectNative.displayName()
return App.i18n.translateContent('%s switched to |%s|!', item.created_by.displayName(), to)
else if item.type is 'ended switch to'
to = item.title
if item.objectNative
to = item.objectNative.displayName()
return App.i18n.translateContent('%s ended switch to |%s|!', item.created_by.displayName(), to)
return "Unknow action for (#{@objectDisplayName()}/#{item.type}), extend activityMessage() of model."

View file

@ -3,7 +3,11 @@
<a href="<%- @item.link %>" class="activity-body"> <a href="<%- @item.link %>" class="activity-body">
<span class="activity-message"> <span class="activity-message">
<span class="activity-text"> <span class="activity-text">
<%= @item.created_by.displayName() %> <%- @T( @item.type ) %> <%- @T( @item.object_name ) %><% if @item.title: %> <strong><%= @item.title %></strong><% end %> <% if @item.objectNative && @item.objectNative.activityMessage: %>
<%- @item.objectNative.activityMessage(@item) %>
<% else: %>
Need objectNative in item <%= item.object %>.find(<%= item.o_id %>)
<% end %>
</span> </span>
<%- @humanTime(@item.created_at, false, 'activity-time') %> <%- @humanTime(@item.created_at, false, 'activity-time') %>
</span> </span>

View file

@ -7,7 +7,11 @@
<div class="activity-body"> <div class="activity-body">
<a class="activity-message js-locationVerify" href="<%- item.link %>"> <a class="activity-message js-locationVerify" href="<%- item.link %>">
<span class="activity-text"> <span class="activity-text">
<%= item.created_by.displayName() %> <%- @T( "#{item.type}d" ) %> <%- @T( item.object_name ) %><% if item.title: %> <strong><%= item.title %></strong><% end %> <% if item.objectNative && item.objectNative.activityMessage: %>
<%- item.objectNative.activityMessage(item) %>
<% else: %>
Need objectNative in item <%= item.object %>.find(<%= item.o_id %>)
<% end %>
</span> </span>
<%- @humanTime(item.created_at, false, 'activity-time') %> <%- @humanTime(item.created_at, false, 'activity-time') %>
</a> </a>

View file

@ -10,12 +10,12 @@ class ActivityStream < ApplicationModel
add a new activity entry for an object add a new activity entry for an object
ActivityStream.add( ActivityStream.add(
:type => 'updated', type: 'update',
:object => 'Ticket', object: 'Ticket',
:role => 'Admin', role: 'Admin',
:o_id => ticket.id, o_id: ticket.id,
:created_by_id => 1, created_by_id: 1,
:created_at => '2013-06-04 10:00:00', created_at: '2013-06-04 10:00:00',
) )
=end =end
@ -24,15 +24,15 @@ add a new activity entry for an object
# lookups # lookups
if data[:type] if data[:type]
type_id = TypeLookup.by_name( data[:type] ) type_id = TypeLookup.by_name(data[:type])
end end
if data[:object] if data[:object]
object_id = ObjectLookup.by_name( data[:object] ) object_id = ObjectLookup.by_name(data[:object])
end end
role_id = nil role_id = nil
if data[:role] if data[:role]
role = Role.lookup( name: data[:role] ) role = Role.lookup(name: data[:role])
if !role if !role
fail "No such Role #{data[:role]}" fail "No such Role #{data[:role]}"
end end
@ -49,7 +49,7 @@ add a new activity entry for an object
).order('created_at DESC, id DESC').first ).order('created_at DESC, id DESC').first
# resturn if old entry is really fresh # resturn if old entry is really fresh
return result if result && result.created_at.to_i >= ( data[:created_at].to_i - 12 ) return result if result && result.created_at.to_i >= ( data[:created_at].to_i - 20 )
# create history # create history
record = { record = {
@ -69,12 +69,12 @@ add a new activity entry for an object
remove whole activity entries of an object remove whole activity entries of an object
ActivityStream.remove( 'Ticket', 123 ) ActivityStream.remove('Ticket', 123)
=end =end
def self.remove( object_name, o_id ) def self.remove(object_name, o_id)
object_id = ObjectLookup.by_name( object_name ) object_id = ObjectLookup.by_name(object_name)
ActivityStream.where( ActivityStream.where(
activity_stream_object_id: object_id, activity_stream_object_id: object_id,
o_id: o_id, o_id: o_id,
@ -85,7 +85,7 @@ remove whole activity entries of an object
return all activity entries of an user return all activity entries of an user
activity_stream = ActivityStream.list( user ) activity_stream = ActivityStream.list(user)
=end =end
@ -94,7 +94,7 @@ return all activity entries of an user
group_ids = user.group_ids group_ids = user.group_ids
# do not return an activity stream for custoers # do not return an activity stream for custoers
customer_role = Role.lookup( name: 'Customer' ) customer_role = Role.lookup(name: 'Customer')
return [] if role_ids.include?(customer_role.id) return [] if role_ids.include?(customer_role.id)
stream = if group_ids.empty? stream = if group_ids.empty?

View file

@ -826,7 +826,7 @@ log object create activity stream, if configured - will be executed automaticall
def activity_stream_create def activity_stream_create
return if !self.class.activity_stream_support_config return if !self.class.activity_stream_support_config
activity_stream_log('created', self['created_by_id']) activity_stream_log('create', self['created_by_id'])
end end
=begin =begin
@ -867,7 +867,7 @@ log object update activity stream, if configured - will be executed automaticall
return if !log return if !log
activity_stream_log('updated', self['updated_by_id']) activity_stream_log('update', self['updated_by_id'])
end end
=begin =begin

View file

@ -6,10 +6,10 @@ module ApplicationModel::ActivityStreamBase
log activity for this object log activity for this object
article = Ticket::Article.find(123) article = Ticket::Article.find(123)
result = article.activity_stream_log( 'created', user_id ) result = article.activity_stream_log('create', user_id)
# force log # force log
result = article.activity_stream_log( 'created', user_id, true ) result = article.activity_stream_log('create', user_id, true)
returns returns
@ -17,7 +17,7 @@ returns
=end =end
def activity_stream_log (type, user_id, force = false) def activity_stream_log(type, user_id, force = false)
# return if we run import mode # return if we run import mode
return if Setting.get('import_mode') return if Setting.get('import_mode')

View file

@ -151,9 +151,12 @@ class Observer::Ticket::Notification::BackgroundJob
if channels['online'] if channels['online']
used_channels.push 'online' used_channels.push 'online'
created_by_id = ticket.updated_by_id || 1
# delete old notifications # delete old notifications
if @p[:type] == 'reminder_reached' || @p[:type] == 'escalation' if @p[:type] == 'reminder_reached' || @p[:type] == 'escalation'
seen = false seen = false
created_by_id = 1
OnlineNotification.remove_by_type('Ticket', ticket.id, @p[:type], user) OnlineNotification.remove_by_type('Ticket', ticket.id, @p[:type], user)
# on updates without state changes create unseen messages # on updates without state changes create unseen messages
@ -168,7 +171,7 @@ class Observer::Ticket::Notification::BackgroundJob
object: 'Ticket', object: 'Ticket',
o_id: ticket.id, o_id: ticket.id,
seen: seen, seen: seen,
created_by_id: ticket.updated_by_id || 1, created_by_id: created_by_id,
user_id: user.id, user_id: user.id,
) )
Rails.logger.debug "sent ticket online notifiaction to agent (#{@p[:type]}/#{ticket.id}/#{user.email})" Rails.logger.debug "sent ticket online notifiaction to agent (#{@p[:type]}/#{ticket.id}/#{user.email})"

View file

@ -6,7 +6,7 @@ module Ticket::ActivityStreamLog
log activity for this object log activity for this object
ticket = Ticket.find(123) ticket = Ticket.find(123)
result = ticket.activity_stream_log( 'created', user_id ) result = ticket.activity_stream_log('create', user_id)
returns returns

View file

@ -6,7 +6,7 @@ module Ticket::Article::ActivityStreamLog
log activity for this object log activity for this object
article = Ticket::Article.find(123) article = Ticket::Article.find(123)
result = article.activity_stream_log( 'created', user_id ) result = article.activity_stream_log('create', user_id)
returns returns
@ -14,7 +14,7 @@ returns
=end =end
def activity_stream_log (type, user_id) def activity_stream_log(type, user_id)
# return if we run import mode # return if we run import mode
return if Setting.get('import_mode') return if Setting.get('import_mode')
@ -24,7 +24,7 @@ returns
return if !self.class.activity_stream_support_config return if !self.class.activity_stream_support_config
role = self.class.activity_stream_support_config[:role] role = self.class.activity_stream_support_config[:role]
ticket = Ticket.lookup( id: ticket_id ) ticket = Ticket.lookup(id: ticket_id)
ActivityStream.add( ActivityStream.add(
o_id: self['id'], o_id: self['id'],
type: type, type: type,

View file

@ -0,0 +1,5 @@
class ImprovedActivityMessages < ActiveRecord::Migration
def up
ActivityStream.destroy_all
end
end

View file

@ -271,8 +271,11 @@ test( "i18n", function() {
translated = App.i18n.translateContent('%s ago', 123); translated = App.i18n.translateContent('%s ago', 123);
equal( translated, 'vor 123', 'de-de - %s' ); equal( translated, 'vor 123', 'de-de - %s' );
translated = App.i18n.translateContent('%s %s test', 123, 'xxx'); translated = App.i18n.translateContent('%s ago', '<b>quote</b>');
equal( translated, '123 xxx test', 'de-de - %s %s' ); equal( translated, 'vor &lt;b&gt;quote&lt;/b&gt;', 'de-de - %s - quote' );
translated = App.i18n.translateContent('%s %s test', 123, 'xxx |B|');
equal( translated, '123 xxx |B| test', 'de-de - %s %s' );
translated = App.i18n.translateContent('|%s| %s test', 123, 'xxx'); translated = App.i18n.translateContent('|%s| %s test', 123, 'xxx');
equal( translated, '<b>123</b> xxx test', 'de-de - *%s* %s' ); equal( translated, '<b>123</b> xxx test', 'de-de - *%s* %s' );
@ -311,11 +314,14 @@ test( "i18n", function() {
translated = App.i18n.translateContent('%s ago', 123); translated = App.i18n.translateContent('%s ago', 123);
equal( translated, '123 ago', 'en-us - %s' ); equal( translated, '123 ago', 'en-us - %s' );
translated = App.i18n.translateContent('%s ago', '<b>quote</b>');
equal( translated, '&lt;b&gt;quote&lt;/b&gt; ago', 'en-us - %s - qupte' );
translated = App.i18n.translateContent('%s %s test', 123, 'xxx'); translated = App.i18n.translateContent('%s %s test', 123, 'xxx');
equal( translated, '123 xxx test', 'en-us - %s %s' ); equal( translated, '123 xxx test', 'en-us - %s %s' );
translated = App.i18n.translateContent('|%s| %s test', 123, 'xxx'); translated = App.i18n.translateContent('|%s| %s test', 123, 'xxx |B|');
equal( translated, '<b>123</b> xxx test', 'en-us - *%s* %s' ); equal( translated, '<b>123</b> xxx |B| test', 'en-us - *%s* %s' );
translated = App.i18n.translateContent('||%s|| %s test', 123, 'xxx'); translated = App.i18n.translateContent('||%s|| %s test', 123, 'xxx');
equal( translated, '<i>123</i> xxx test', 'en-us - *%s* %s' ); equal( translated, '<i>123</i> xxx test', 'en-us - *%s* %s' );

View file

@ -61,22 +61,22 @@ class ActivityStreamTest < ActiveSupport::TestCase
{ {
result: true, result: true,
object: 'Ticket', object: 'Ticket',
type: 'updated', type: 'update',
}, },
{ {
result: true, result: true,
object: 'Ticket::Article', object: 'Ticket::Article',
type: 'created', type: 'create',
}, },
{ {
result: true, result: true,
object: 'Ticket', object: 'Ticket',
type: 'created', type: 'create',
}, },
{ {
result: false, result: false,
object: 'User', object: 'User',
type: 'updated', type: 'update',
o_id: current_user.id, o_id: current_user.id,
}, },
] ]
@ -118,7 +118,7 @@ class ActivityStreamTest < ActiveSupport::TestCase
article.update_attributes(test[:update][:article]) article.update_attributes(test[:update][:article])
end end
sleep 15 sleep 21
if test[:update][:ticket] if test[:update][:ticket]
ticket.update_attributes(test[:update][:ticket]) ticket.update_attributes(test[:update][:ticket])
end end
@ -168,12 +168,12 @@ class ActivityStreamTest < ActiveSupport::TestCase
{ {
result: true, result: true,
object: 'Organization', object: 'Organization',
type: 'updated', type: 'update',
}, },
{ {
result: true, result: true,
object: 'Organization', object: 'Organization',
type: 'created', type: 'create',
}, },
] ]
}, },
@ -194,7 +194,7 @@ class ActivityStreamTest < ActiveSupport::TestCase
test[:check][1][:o_id] = organization.id test[:check][1][:o_id] = organization.id
test[:check][1][:updated_at] = organization.updated_at test[:check][1][:updated_at] = organization.updated_at
test[:check][1][:created_by_id] = current_user.id test[:check][1][:created_by_id] = current_user.id
sleep 13 sleep 19
end end
if test[:update2][:organization] if test[:update2][:organization]
@ -240,12 +240,12 @@ class ActivityStreamTest < ActiveSupport::TestCase
{ {
result: true, result: true,
object: 'User', object: 'User',
type: 'created', type: 'create',
}, },
{ {
result: false, result: false,
object: 'User', object: 'User',
type: 'updated', type: 'update',
}, },
] ]
}, },
@ -312,12 +312,12 @@ class ActivityStreamTest < ActiveSupport::TestCase
{ {
result: true, result: true,
object: 'User', object: 'User',
type: 'updated', type: 'update',
}, },
{ {
result: true, result: true,
object: 'User', object: 'User',
type: 'created', type: 'create',
}, },
] ]
}, },
@ -340,7 +340,7 @@ class ActivityStreamTest < ActiveSupport::TestCase
end end
# to verify update which need to be logged # to verify update which need to be logged
sleep 14 sleep 21
if test[:update2][:user] if test[:update2][:user]
user.update_attributes(test[:update2][:user]) user.update_attributes(test[:update2][:user])