From 1daf79041d35110066979d6000af796aa00c5168 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 26 Aug 2014 09:54:12 +0200 Subject: [PATCH] Added online notification feature. --- .../_application_controller.js.coffee | 20 +++ .../_dashboard/activity_stream.js.coffee | 19 +-- .../app/controllers/navigation.js.coffee | 23 +-- .../controllers/organization_zoom.js.coffee | 1 + .../app/controllers/ticket_zoom.js.coffee | 1 + .../app/controllers/user_zoom.js.coffee | 1 + .../widget/online_notification.js.coffee | 85 +++++++++++ .../app/models/_application_model.js.coffee | 31 ++++ .../app/models/online_notification.js.coffee | 18 +++ .../javascripts/app/views/navigation.jst.eco | 4 +- .../views/widget/online_notification.jst.eco | 15 ++ .../online_notifications_controller.rb | 91 +++++++++++ app/models/observer/ticket/notification.rb | 24 ++- app/models/online_notification.rb | 144 ++++++++++++++++++ config/routes/online_notification.rb | 8 + db/migrate/20130201071513_create_sla.rb | 2 +- ...140824000002_create_online_notification.rb | 17 +++ 17 files changed, 460 insertions(+), 44 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/widget/online_notification.js.coffee create mode 100644 app/assets/javascripts/app/models/online_notification.js.coffee create mode 100644 app/assets/javascripts/app/views/widget/online_notification.jst.eco create mode 100644 app/controllers/online_notifications_controller.rb create mode 100644 app/models/online_notification.rb create mode 100644 config/routes/online_notification.rb create mode 100644 db/migrate/20140824000002_create_online_notification.rb diff --git a/app/assets/javascripts/app/controllers/_application_controller.js.coffee b/app/assets/javascripts/app/controllers/_application_controller.js.coffee index 8bd408bce..17b73780a 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.js.coffee @@ -387,6 +387,26 @@ class App.Controller extends Spine.Controller 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) -> App.Event.trigger( 'ws:send', JSON.stringify(data) ) diff --git a/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee b/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee index 9f3c12835..931927d0b 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee @@ -38,24 +38,7 @@ class App.DashboardActivityStream extends App.Controller @render(items) render: (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 = @prepareForObjectList(items) html = App.view('dashboard/activity_stream')( head: 'Activity Stream', diff --git a/app/assets/javascripts/app/controllers/navigation.js.coffee b/app/assets/javascripts/app/controllers/navigation.js.coffee index e9c1ded07..fd2758962 100644 --- a/app/assets/javascripts/app/controllers/navigation.js.coffee +++ b/app/assets/javascripts/app/controllers/navigation.js.coffee @@ -216,6 +216,10 @@ class App.Navigation extends App.Controller @delay( searchFunction, 220, 'search' ) ) + new App.OnlineNotificationWidget( + el: @el + ) + @taskbar = new App.TaskbarWidget( el: @el.find('.tasks') ) emptySearch: (event) => @@ -318,8 +322,6 @@ class App.Navigation extends App.Controller App.Store.write( 'update_recent_viewed', data ) - items = data.recent_viewed - # load assets App.Collection.loadAssets( data.assets ) @@ -332,6 +334,8 @@ class App.Navigation extends App.Controller delete NavBarRight[key] # add new views + items = data.recent_viewed + items = @prepareForObjectList(items) prio = 8000 for item in items divider = false @@ -340,21 +344,6 @@ class App.Navigation extends App.Controller divider = true 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++ NavBarRight['RecendViewed::' + item.o_id + item.object + '-' + prio ] = { prio: prio diff --git a/app/assets/javascripts/app/controllers/organization_zoom.js.coffee b/app/assets/javascripts/app/controllers/organization_zoom.js.coffee index b06dc9f4f..6303dc42e 100644 --- a/app/assets/javascripts/app/controllers/organization_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/organization_zoom.js.coffee @@ -27,6 +27,7 @@ class App.OrganizationZoom extends App.Controller '#organization/zoom/' + @organization_id activate: => + App.OnlineNotification.seen( 'Organization', @organization_id ) @navupdate '#' changed: => diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index 530eb5b6b..2b822c219 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -53,6 +53,7 @@ class App.TicketZoom extends App.Controller '#ticket/zoom/' + @ticket_id activate: => + App.OnlineNotification.seen( 'Ticket', @ticket_id ) @navupdate '#' changed: => diff --git a/app/assets/javascripts/app/controllers/user_zoom.js.coffee b/app/assets/javascripts/app/controllers/user_zoom.js.coffee index 2834c5513..5bb26fcdc 100644 --- a/app/assets/javascripts/app/controllers/user_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/user_zoom.js.coffee @@ -27,6 +27,7 @@ class App.UserZoom extends App.Controller '#user/zoom/' + @user_id activate: => + App.OnlineNotification.seen( 'User', @user_id ) @navupdate '#' changed: => diff --git a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee new file mode 100644 index 000000000..669e83f57 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee @@ -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('
' + count.toString() + '
') + + 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' ) + " #{counter}" + 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) \ No newline at end of file diff --git a/app/assets/javascripts/app/models/_application_model.js.coffee b/app/assets/javascripts/app/models/_application_model.js.coffee index ba15e918d..1cf797746 100644 --- a/app/assets/javascripts/app/models/_application_model.js.coffee +++ b/app/assets/javascripts/app/models/_application_model.js.coffee @@ -382,6 +382,37 @@ class App.Model extends Spine.Model if @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: -> if @SUBSCRIPTION_ITEM for id, keys of @SUBSCRIPTION_ITEM diff --git a/app/assets/javascripts/app/models/online_notification.js.coffee b/app/assets/javascripts/app/models/online_notification.js.coffee new file mode 100644 index 000000000..e30f9a2dc --- /dev/null +++ b/app/assets/javascripts/app/models/online_notification.js.coffee @@ -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() \ No newline at end of file diff --git a/app/assets/javascripts/app/views/navigation.jst.eco b/app/assets/javascripts/app/views/navigation.jst.eco index 47ec01b53..32bb26f56 100644 --- a/app/assets/javascripts/app/views/navigation.jst.eco +++ b/app/assets/javascripts/app/views/navigation.jst.eco @@ -5,9 +5,7 @@
- + diff --git a/app/assets/javascripts/app/views/widget/online_notification.jst.eco b/app/assets/javascripts/app/views/widget/online_notification.jst.eco new file mode 100644 index 000000000..3b9791b07 --- /dev/null +++ b/app/assets/javascripts/app/views/widget/online_notification.jst.eco @@ -0,0 +1,15 @@ +<% for item in @items: %> +
+ + + + + + + <%= item.created_by.displayName() %> <%- @T( item.type ) %> <%- @T( item.object_name ) %><% if item.title: %> (<%= item.title %>)<% end %> + + ? + + +
+<% end %> \ No newline at end of file diff --git a/app/controllers/online_notifications_controller.rb b/app/controllers/online_notifications_controller.rb new file mode 100644 index 000000000..20a60efb1 --- /dev/null +++ b/app/controllers/online_notifications_controller.rb @@ -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 diff --git a/app/models/observer/ticket/notification.rb b/app/models/observer/ticket/notification.rb index f80218257..b9ed322b9 100644 --- a/app/models/observer/ticket/notification.rb +++ b/app/models/observer/ticket/notification.rb @@ -65,7 +65,8 @@ class Observer::Ticket::Notification < ActiveRecord::Observer ' }, ticket, - article + article, + 'new ticket' ) end @@ -96,7 +97,8 @@ class Observer::Ticket::Notification < ActiveRecord::Observer ' }, ticket, - article + article, + 'new ticket' ) end @@ -131,7 +133,8 @@ class Observer::Ticket::Notification < ActiveRecord::Observer ' }, ticket, - article + article, + 'follow up' ) end @@ -160,14 +163,15 @@ class Observer::Ticket::Notification < ActiveRecord::Observer ' }, ticket, - article + article, + 'follow up', ) end end } end - def self.send_notify(data, ticket, article) + def self.send_notify(data, ticket, article, type) # find recipients recipients = [] @@ -202,6 +206,16 @@ class Observer::Ticket::Notification < ActiveRecord::Observer recipient_list = '' notification_subject = '' 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 == '' # add recipient_list diff --git a/app/models/online_notification.rb b/app/models/online_notification.rb new file mode 100644 index 000000000..10b7610b4 --- /dev/null +++ b/app/models/online_notification.rb @@ -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 \ No newline at end of file diff --git a/config/routes/online_notification.rb b/config/routes/online_notification.rb new file mode 100644 index 000000000..5ecfe3303 --- /dev/null +++ b/config/routes/online_notification.rb @@ -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 \ No newline at end of file diff --git a/db/migrate/20130201071513_create_sla.rb b/db/migrate/20130201071513_create_sla.rb index e70b50af4..0d570f249 100644 --- a/db/migrate/20130201071513_create_sla.rb +++ b/db/migrate/20130201071513_create_sla.rb @@ -17,4 +17,4 @@ class CreateSla < ActiveRecord::Migration def down end -end +end \ No newline at end of file diff --git a/db/migrate/20140824000002_create_online_notification.rb b/db/migrate/20140824000002_create_online_notification.rb new file mode 100644 index 000000000..101564a20 --- /dev/null +++ b/db/migrate/20140824000002_create_online_notification.rb @@ -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