Added online notification feature.

This commit is contained in:
Martin Edenhofer 2014-08-26 09:54:12 +02:00
parent 4c8a13d5b2
commit 1daf79041d
17 changed files with 460 additions and 44 deletions

View file

@ -387,6 +387,26 @@ class App.Controller extends Spine.Controller
processData: true processData: true
) )
prepareForObjectList: (items) ->
for item in items
item.link = ''
item.title = '???'
# convert backend name space to local name space
item.object = item.object.replace("::", '')
# lookup real data
if App[item.object]
object = App[item.object].find( item.o_id )
item.link = object.uiUrl()
item.title = object.displayName()
item.object_name = object.objectDisplayName()
item.cssIcon = object.iconActivity( @Session.all() )
item.created_by = App.User.retrieve( item.created_by_id )
items
ws_send: (data) -> ws_send: (data) ->
App.Event.trigger( 'ws:send', JSON.stringify(data) ) App.Event.trigger( 'ws:send', JSON.stringify(data) )

View file

@ -38,24 +38,7 @@ class App.DashboardActivityStream extends App.Controller
@render(items) @render(items)
render: (items) -> render: (items) ->
items = @prepareForObjectList(items)
for item in items
item.link = ''
item.title = '???'
# convert backend name space to local name space
item.object = item.object.replace("::", '')
# lookup real data
if App[item.object]
object = App[item.object].find( item.o_id )
item.link = object.uiUrl()
item.title = object.displayName()
item.object_name = object.objectDisplayName()
item.cssIcon = object.iconActivity( @Session.all() )
item.created_by = App.User.retrieve( item.created_by_id )
html = App.view('dashboard/activity_stream')( html = App.view('dashboard/activity_stream')(
head: 'Activity Stream', head: 'Activity Stream',

View file

@ -216,6 +216,10 @@ class App.Navigation extends App.Controller
@delay( searchFunction, 220, 'search' ) @delay( searchFunction, 220, 'search' )
) )
new App.OnlineNotificationWidget(
el: @el
)
@taskbar = new App.TaskbarWidget( el: @el.find('.tasks') ) @taskbar = new App.TaskbarWidget( el: @el.find('.tasks') )
emptySearch: (event) => emptySearch: (event) =>
@ -318,8 +322,6 @@ class App.Navigation extends App.Controller
App.Store.write( 'update_recent_viewed', data ) App.Store.write( 'update_recent_viewed', data )
items = data.recent_viewed
# load assets # load assets
App.Collection.loadAssets( data.assets ) App.Collection.loadAssets( data.assets )
@ -332,6 +334,8 @@ class App.Navigation extends App.Controller
delete NavBarRight[key] delete NavBarRight[key]
# add new views # add new views
items = data.recent_viewed
items = @prepareForObjectList(items)
prio = 8000 prio = 8000
for item in items for item in items
divider = false divider = false
@ -340,21 +344,6 @@ class App.Navigation extends App.Controller
divider = true divider = true
navheader = 'Recent Viewed' navheader = 'Recent Viewed'
item.link = ''
item.title = '???'
# convert backend name space to local name space
item.object = item.object.replace("::", '')
# lookup real data
if App[item.object]
object = App[item.object].find( item.o_id )
item.link = object.uiUrl()
item.title = object.displayName()
item.object_name = object.objectDisplayName()
item.created_by = App.User.find( item.created_by_id )
prio++ prio++
NavBarRight['RecendViewed::' + item.o_id + item.object + '-' + prio ] = { NavBarRight['RecendViewed::' + item.o_id + item.object + '-' + prio ] = {
prio: prio prio: prio

View file

@ -27,6 +27,7 @@ class App.OrganizationZoom extends App.Controller
'#organization/zoom/' + @organization_id '#organization/zoom/' + @organization_id
activate: => activate: =>
App.OnlineNotification.seen( 'Organization', @organization_id )
@navupdate '#' @navupdate '#'
changed: => changed: =>

View file

@ -53,6 +53,7 @@ class App.TicketZoom extends App.Controller
'#ticket/zoom/' + @ticket_id '#ticket/zoom/' + @ticket_id
activate: => activate: =>
App.OnlineNotification.seen( 'Ticket', @ticket_id )
@navupdate '#' @navupdate '#'
changed: => changed: =>

View file

@ -27,6 +27,7 @@ class App.UserZoom extends App.Controller
'#user/zoom/' + @user_id '#user/zoom/' + @user_id
activate: => activate: =>
App.OnlineNotification.seen( 'User', @user_id )
@navupdate '#' @navupdate '#'
changed: => changed: =>

View file

@ -0,0 +1,85 @@
class App.OnlineNotificationWidget extends App.Controller
constructor: ->
super
@bind 'OnlineNotification::changed', =>
@fetch()
# rebuild widget on auth
@bind 'auth', (user) =>
if !user
@el.find('activity-counter').html('')
else
if !@access()
@el.find('activity-counter').html('')
return
@start()
if @access()
@start()
@start()
@subscribeId = App.OnlineNotification.subscribe( @start )
release: =>
@stop()
App.Model.unsubscribe( @subscribeId )
access: ->
return false if _.isEmpty( @Session.all() )
return true if @isRole('Agent')
return true if @isRole('Admin')
return false
counterUpdate: (count) =>
console.log('counter update', count)
if !count
@el.find('.activity-counter').remove()
return
if @el.find('.logo .activity-counter')[0]
@el.find('.logo .activity-counter').html(count)
else
@el.find('.logo').append('<div class="activity-counter">' + count.toString() + '</div>')
stop: =>
@counterUpdate(0)
@el.find('.logo').popover('destroy')
start: =>
@stop()
# show popover
items = App.OnlineNotification.search(sortBy: 'created_at', order: 'DESC' )
counter = 0
for item in items
if !item.seen
counter = counter + 1
@counterUpdate(counter)
items = @prepareForObjectList(items)
@el.find('.logo').popover(
trigger: 'click'
container: 'body'
html: true
delay: { show: 100, hide: 0 }
placement: 'right'
title: ->
App.i18n.translateInline( 'Notifications' ) + " <span>#{counter}</span>"
content: ->
# insert data
App.view('widget/online_notification')(
items: items
)
).on('shown.bs.popover', =>
# show frontend times
@frontendTimeUpdate()
)
fetch: =>
load = (items) =>
App.OnlineNotification.refresh(items)
@start()
App.OnlineNotification.fetchFull(load)

View file

@ -382,6 +382,37 @@ class App.Model extends Spine.Model
if @SUBSCRIPTION_COLLECTION[subscribeId] if @SUBSCRIPTION_COLLECTION[subscribeId]
delete @SUBSCRIPTION_COLLECTION[subscribeId] delete @SUBSCRIPTION_COLLECTION[subscribeId]
###
fetch full collection (with assets)
App.Model.fetchFull( @callback )
###
@fetchFull: (callback) ->
url = "#{@url}/?full=true"
App.Ajax.request(
type: 'GET'
url: url
processData: true,
success: (data, status, xhr) =>
# full / load assets
if data.assets
App.Collection.loadAssets( data.assets )
# find / load object
else
App[ @className ].refresh( data )
# execute callbacks
callback(data.stream)
error: (xhr, statusText, error) =>
console.log(statusText, error)
)
@_bindsEmpty: -> @_bindsEmpty: ->
if @SUBSCRIPTION_ITEM if @SUBSCRIPTION_ITEM
for id, keys of @SUBSCRIPTION_ITEM for id, keys of @SUBSCRIPTION_ITEM

View file

@ -0,0 +1,18 @@
class App.OnlineNotification extends App.Model
@configure 'OnlineNotification', 'name', 'seen'
@extend Spine.Model.Ajax
@url: @apiPath + '/online_notifications'
###
App.OnlineNotification.seen( 'Ticket', 123 )
###
@seen: (object, o_id) ->
notifications = App.OnlineNotification.all()
for notification in notifications
if notification.object is object && notification.o_id.toString() is o_id.toString()
if notification.seen isnt true
notification.seen = true
notification.save()

View file

@ -5,9 +5,7 @@
<div class="close icon"></div> <div class="close icon"></div>
</div> </div>
</div> </div>
<div class="logo" title="<%- @C( 'product_name' ) %>"> <div class="logo" title="<%- @C( 'product_name' ) %>"></div>
<div class="activity-counter">12</div>
</div>
<ul id="global-search-result" class="custom-dropdown-menu" role="menu"> <ul id="global-search-result" class="custom-dropdown-menu" role="menu">
</ul> </ul>
</form> </form>

View file

@ -0,0 +1,15 @@
<% for item in @items: %>
<div class="activity-entry horizontal">
<a class="activity-avatar" href="<%- item.created_by.uiUrl() %>">
<span class="avatar" style="background-image: url(<%- item.created_by.imageUrl %>)"></span>
</a>
<a href="<%- item.link %>" class="activity-body flex horizontal">
<span class="activity-message flex">
<span class="activity-text <% if item.seen: %>inactive<% end %>">
<%= item.created_by.displayName() %> <%- @T( item.type ) %> <%- @T( item.object_name ) %><% if item.title: %> (<%= item.title %>)<% end %>
</span>
<span class="activity-time <% if item.seen: %>inactive<% end %> humanTimeFromNow" data-time="<%- item.created_at %>">?</span>
</span>
</a>
</div>
<% end %>

View file

@ -0,0 +1,91 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class OnlineNotificationsController < ApplicationController
before_filter :authentication_check
=begin
Format:
JSON
Example:
{
"id":1,
"name":"some template",
"user_id": null,
"options":{"a":1,"b":2},
"updated_at":"2012-09-14T17:51:53Z",
"created_at":"2012-09-14T17:51:53Z",
"updated_by_id":2.
"created_by_id":2,
}
=end
=begin
Resource:
GET /api/v1/templates.json
Response:
[
{
"id": 1,
"name": "some_name1",
...
},
{
"id": 2,
"name": "some_name2",
...
}
]
Test:
curl http://localhost/api/v1/online_notifications.json -v -u #{login}:#{password}
=end
def index
if params[:full]
render :json => OnlineNotification.list_full(current_user, 50)
return
end
notifications = OnlineNotification.list(current_user, 50)
model_index_render_result(notifications)
end
=begin
Resource:
PUT /api/v1/online_notifications/{id}
Payload:
{
"name": "some name",
"options":{"a":1,"b":2},
}
Response:
{
"id": 1,
"name": "some_name",
...
}
Test:
curl http://localhost/api/v1/online_notifications -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def update
notification = OnlineNotification.find(params[:id])
if notification.user_id != current_user.id
response_access_deny
return
end
model_update_render(OnlineNotification, params)
end
end

View file

@ -65,7 +65,8 @@ class Observer::Ticket::Notification < ActiveRecord::Observer
' '
}, },
ticket, ticket,
article article,
'new ticket'
) )
end end
@ -96,7 +97,8 @@ class Observer::Ticket::Notification < ActiveRecord::Observer
' '
}, },
ticket, ticket,
article article,
'new ticket'
) )
end end
@ -131,7 +133,8 @@ class Observer::Ticket::Notification < ActiveRecord::Observer
' '
}, },
ticket, ticket,
article article,
'follow up'
) )
end end
@ -160,14 +163,15 @@ class Observer::Ticket::Notification < ActiveRecord::Observer
' '
}, },
ticket, ticket,
article article,
'follow up',
) )
end end
end end
} }
end end
def self.send_notify(data, ticket, article) def self.send_notify(data, ticket, article, type)
# find recipients # find recipients
recipients = [] recipients = []
@ -202,6 +206,16 @@ class Observer::Ticket::Notification < ActiveRecord::Observer
recipient_list = '' recipient_list = ''
notification_subject = '' notification_subject = ''
recipients.each do |user| recipients.each do |user|
OnlineNotification.add(
:type => type,
:object => 'Ticket',
:o_id => ticket.id,
:seen => false,
:created_by_id => UserInfo.current_user_id || 1,
:user_id => user.id,
)
next if !user.email || user.email == '' next if !user.email || user.email == ''
# add recipient_list # add recipient_list

View file

@ -0,0 +1,144 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class OnlineNotification < ApplicationModel
belongs_to :type_lookup, :class_name => 'TypeLookup'
belongs_to :object_lookup, :class_name => 'ObjectLookup'
after_create :notify_clients_after_change
after_update :notify_clients_after_change
after_destroy :notify_clients_after_change
=begin
add a new online notification for this user
OnlineNotification.add(
:type => 'Assigned to you',
:object => 'Ticket',
:o_id => ticket.id,
:seen => false,
:created_by_id => 1,
:user_id => 2,
)
=end
def self.add(data)
# lookups
if data[:type]
type_id = TypeLookup.by_name( data[:type] )
end
if data[:object]
object_id = ObjectLookup.by_name( data[:object] )
end
record = {
:o_id => data[:o_id],
:object_lookup_id => object_id,
:type_lookup_id => type_id,
:seen => data[:seen],
:user_id => data[:user_id],
:created_by_id => data[:created_by_id]
}
OnlineNotification.create(record)
end
=begin
add a new online notification for this user
OnlineNotification.add(
:type => 'Assigned to you',
:object => 'Ticket',
:o_id => ticket.id,
:seen => 1,
:created_by_id => 1,
:user_id => 2,
)
=end
def self.seen(data)
notification = OnlineNotification.find(data[:id])
notification.seen = true
notification.save
end
=begin
remove whole online notifications of an object
OnlineNotification.remove( 'Ticket', 123 )
=end
def self.remove( object_name, o_id )
object_id = ObjectLookup.by_name( object_name )
OnlineNotification.where(
:object_lookup_id => object_id,
:o_id => o_id,
).destroy_all
end
=begin
return all online notifications of an user
notifications = OnlineNotification.list( 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
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)
return {
:stream => notifications,
:assets => assets
}
end
def notify_clients_after_change
puts "#{ self.class.name } changed " + self.created_at.to_s
Sessions.broadcast(
:event => 'OnlineNotification::changed',
:data => {}
)
end
end

View file

@ -0,0 +1,8 @@
Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
# groups
match api_path + '/online_notifications', :to => 'online_notifications#index', :via => :get
match api_path + '/online_notifications/:id', :to => 'online_notifications#update', :via => :put
end

View file

@ -0,0 +1,17 @@
class CreateOnlineNotification < ActiveRecord::Migration
def up
create_table :online_notifications do |t|
t.column :o_id, :integer, :null => false
t.column :object_lookup_id, :integer, :null => false
t.column :type_lookup_id, :integer, :null => false
t.column :user_id, :integer, :null => false
t.column :seen, :boolean, :null => false, :default => false
t.column :created_by_id, :integer, :null => false
t.timestamps
end
add_index :online_notifications, [:user_id]
end
def down
end
end