From c3025f87c3809c54c96e829ba99373baa3b7dc84 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 10 Nov 2014 00:42:17 +0100 Subject: [PATCH] Init version of user zoom. --- .../_application_controller.js.coffee | 9 +- .../app/controllers/user_zoom.js.coffee | 303 ++++++++++++------ .../javascripts/app/views/user_zoom.jst.eco | 46 ++- .../app/views/user_zoom/ticket_stats.jst.eco | 24 ++ .../user_zoom/ticket_stats_frequency.jst.eco | 16 + .../views/user_zoom/ticket_stats_list.jst.eco | 18 ++ app/controllers/tickets_controller.rb | 151 ++++++++- config/routes/ticket.rb | 1 + 8 files changed, 457 insertions(+), 111 deletions(-) create mode 100644 app/assets/javascripts/app/views/user_zoom/ticket_stats.jst.eco create mode 100644 app/assets/javascripts/app/views/user_zoom/ticket_stats_frequency.jst.eco create mode 100644 app/assets/javascripts/app/views/user_zoom/ticket_stats_list.jst.eco diff --git a/app/assets/javascripts/app/controllers/_application_controller.js.coffee b/app/assets/javascripts/app/controllers/_application_controller.js.coffee index ec847105e..aa072f2c0 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.js.coffee @@ -228,15 +228,14 @@ class App.Controller extends Spine.Controller placement: position title: -> ticket_id = $(@).data('id') - ticket = App.Ticket.fullLocal( ticket_id ) + ticket = App.Ticket.fullLocal( ticket_id ) HTMLEscape( ticket.title ) content: -> - ticket_id = $(@).data('id') - ticket = App.Ticket.fullLocal( ticket_id ) + ticket_id = $(@).data('id') + ticket = App.Ticket.fullLocal( ticket_id ) ticket.humanTime = ui.humanTime(ticket.created_at) - # insert data App.view('popover/ticket')( - ticket: ticket, + ticket: ticket ) ) diff --git a/app/assets/javascripts/app/controllers/user_zoom.js.coffee b/app/assets/javascripts/app/controllers/user_zoom.js.coffee index bdfb836ef..8490c4844 100644 --- a/app/assets/javascripts/app/controllers/user_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/user_zoom.js.coffee @@ -1,6 +1,6 @@ class App.UserZoom extends App.Controller - elements: - '.tabsSidebar' : 'sidebar' + events: + 'focusout [data-type=update]': 'update', constructor: (params) -> super @@ -12,7 +12,11 @@ class App.UserZoom extends App.Controller @navupdate '#' - App.User.full( @user_id, @render ) + # subscribe and reload data / fetch new data if triggered + @subscribeId = App.User.full( @user_id, @render, false, true ) + + release: => + App.User.unsubscribe(@subscribeId) meta: => meta = @@ -35,10 +39,7 @@ class App.UserZoom extends App.Controller @navupdate '#' changed: => - formCurrent = @formParam( @el.find('.ticket-update') ) - diff = difference( @formDefault, formCurrent ) - return false if !diff || _.isEmpty( diff ) - return true + false render: (user) => @@ -46,12 +47,43 @@ class App.UserZoom extends App.Controller @doNotLog = 1 @recentView( 'User', @user_id ) + # get display data + userData = [] + for item2 in App.User.configure_attributes + item = _.clone( item2 ) + + # check if value for _id exists + itemNameValue = item.name + itemNameValueNew = itemNameValue.substr( 0, itemNameValue.length - 3 ) + if itemNameValueNew of user + item.name = itemNameValueNew + + # add to show if value exists + if user[item.name] || item.tag is 'textarea' + + # do not show firstname and lastname / already show via diplayName() + if item.name isnt 'firstname' && item.name isnt 'lastname' && item.name isnt 'organization' + if item.info + userData.push item + @html App.view('user_zoom')( - user: user + user: user + userData: userData ) - new Overviews( - el: @el + @$('[contenteditable]').ce({ + mode: 'textonly' + multiline: true + maxlength: 250 + }) + + #new Overviews( + # el: @el + # user: user + #) + + new TicketStats( + el: @$('.js-ticket-stats') user: user ) @@ -59,31 +91,190 @@ class App.UserZoom extends App.Controller genericObject: user ) - new App.UpdateHeader( - el: @el - genericObject: user - ) - # start action controller showHistory = => new App.UserHistory( user_id: user.id ) + editUser = => + new App.ControllerGenericEdit( + id: user.id + genericObject: 'User' + screen: 'edit' + pageData: + title: 'Users' + object: 'User' + objects: 'Users' + ) + actions = [ + { + name: 'edit' + title: 'Edit' + callback: editUser + } { name: 'history' title: 'History' callback: showHistory } ] + new App.ActionRow( el: @el.find('.action') items: actions ) - new Sidebar( - el: @sidebar - user: user - textModule: @textModule + update: (e) => + console.log('update') + note = $(e.target).ceg() + user = App.User.find( @user_id ) + if user.note isnt note + user.updateAttributes( note: note ) + @log 'notice', 'update', e, note, user + + +class TicketStats extends App.Controller + events: + 'click .js-userTab': 'showUserTab' + 'click .js-orgTab': 'showOrgTab' + + constructor: -> + super + + # subscribe and reload data / fetch new data if triggered + @subscribeId = App.User.full( @user.id, @load, false, true ) + + release: => + App.User.unsubscribe(@subscribeId) + + load: (user) => + @ajax( + id: 'ticket_stats_' + user.id, + type: 'GET', + url: @apiPath + '/ticket_stats/' + user.id, + success: (data) => + # load assets + App.Collection.loadAssets( data.assets ) + + @render(data) + ) + + showOrgTab: => + @$('.js-userTab').removeClass('active') + @$('.js-orgTab').addClass('active') + @$('.js-user').addClass('hide') + @$('.js-org').removeClass('hide') + + showUserTab: => + @$('.js-userTab').addClass('active') + @$('.js-orgTab').removeClass('active') + @$('.js-user').removeClass('hide') + @$('.js-org').addClass('hide') + + render: (data) => + + @html App.view('user_zoom/ticket_stats')( + user: @user + ) + + limit = 5 + new TicketStatsList( + el: @$('.js-user-open-tickets') + user: @user + head: 'Open Ticket' + ticket_ids: data.user_tickets_open_ids + limit: limit + ) + new TicketStatsList( + el: @$('.js-user-closed-tickets') + user: @user + head: 'Closed Ticket' + ticket_ids: data.user_tickets_closed_ids + limit: limit + ) + new TicketStatsFrequency( + el: @$('.js-user-frequency') + user: @user + ticket_volume_by_year: data.user_ticket_volume_by_year + ) + + new TicketStatsList( + el: @$('.js-org-open-tickets') + user: @user + head: 'Open Ticket' + ticket_ids: data.org_tickets_open_ids + limit: limit + ) + new TicketStatsList( + el: @$('.js-org-closed-tickets') + user: @user + head: 'Closed Ticket' + ticket_ids: data.org_tickets_closed_ids + limit: limit + ) + new TicketStatsFrequency( + el: @$('.js-org-frequency') + user: @user + ticket_volume_by_year: data.org_ticket_volume_by_year + ) + +class TicketStatsList extends App.Controller + events: + 'click .js-showAll': 'showAll' + + constructor: -> + super + @render() + + render: => + + ticket_ids_show = [] + if !@all + count = 0 + for ticket_id in @ticket_ids + count += 1 + if count <= @limit + ticket_ids_show.push ticket_id + else + ticket_ids_show = @ticket_ids + + @html App.view('user_zoom/ticket_stats_list')( + user: @user + head: @head + ticket_ids: @ticket_ids + ticket_ids_show: ticket_ids_show + limit: @limit + ) + @frontendTimeUpdate() + @ticketPopups() + + showAll: (e) => + e.preventDefault() + @all = true + @render() + +class TicketStatsFrequency extends App.Controller + constructor: -> + super + @render() + + render: (data) => + + # find 100% + max = 0 + for item in @ticket_volume_by_year + if item.closed > max + max = item.closed + if item.created > max + max = item.created + console.log('MM', max) + for item in @ticket_volume_by_year + item.created_in_percent = 100 / max * item.created + item.closed_in_percent = 100 / max * item.closed + + @html App.view('user_zoom/ticket_stats_frequency')( + user: @user + ticket_volume_by_year: @ticket_volume_by_year.reverse() ) class Overviews extends App.Controller @@ -154,80 +345,6 @@ class Overviews extends App.Controller @el.find( '#sortable' ).sortable( dndOptions ) @el.find( '#sortable-sidebar' ).sortable( dndOptions ) -class Sidebar extends App.Controller - constructor: -> - super - - # render ui - @render() - - render: -> - - items = [] - - showCustomer = (el) => - new App.WidgetUser( - el: el - user_id: @user.id - ) - - editCustomer = (e, el) => - new App.ControllerGenericEdit( - id: @user.id - genericObject: 'User' - screen: 'edit' - pageData: - title: 'Users' - object: 'User' - objects: 'Users' - ) - items.push { - head: 'Customer' - name: 'customer' - icon: 'person' - actions: [ - { - name: 'Edit Customer' - class: 'glyphicon glyphicon-edit' - callback: editCustomer - }, - ] - callback: showCustomer - } - - if @user.organization_id - editOrganization = (e, el) => - new App.ControllerGenericEdit( - id: @user.organization_id - genericObject: 'Organization' - pageData: - title: 'Organizations' - object: 'Organization' - objects: 'Organizations' - ) - showOrganization = (el) => - new App.WidgetOrganization( - el: el - organization_id: @user.organization_id - ) - items.push { - head: 'Organization' - name: 'organization' - icon: 'group' - actions: [ - { - name: 'Edit Organization' - class: 'glyphicon glyphicon-edit' - callback: editOrganization - }, - ] - callback: showOrganization - } - - new App.Sidebar( - el: @el - items: items - ) class Router extends App.ControllerPermanent constructor: (params) -> diff --git a/app/assets/javascripts/app/views/user_zoom.jst.eco b/app/assets/javascripts/app/views/user_zoom.jst.eco index 7f610da3f..37da39b96 100644 --- a/app/assets/javascripts/app/views/user_zoom.jst.eco +++ b/app/assets/javascripts/app/views/user_zoom.jst.eco @@ -1,18 +1,40 @@ -
-
-
-
- +
-
+
+
+ + <%- @user.avatar("80") %> +

<%= @user.displayName() %>

+ <% if @user.organization: %> + + <% end %> +
+ +
+
+ <% for row in @userData: %> + <% if @user[row.name]: %> + <% if row.tag isnt 'textarea': %> +
+ + <%- @L( @P( @user[row.name] ) ) %> +
+ <% end %> + <% end %> + <% end %> + <% for row in @userData: %> + <% if row.tag is 'textarea': %> +
+ +
<%= @user[row.name] %>
+
+ <% end %> + <% end %>
-
+
+
-
-
-
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/user_zoom/ticket_stats.jst.eco b/app/assets/javascripts/app/views/user_zoom/ticket_stats.jst.eco new file mode 100644 index 000000000..7c823d2c5 --- /dev/null +++ b/app/assets/javascripts/app/views/user_zoom/ticket_stats.jst.eco @@ -0,0 +1,24 @@ +<% if @user && @user.organization: %> +
+
<%- @T('Tickets of User') %>
+
<%- @T('Tickets of Organization') %>
+
+<% end %> + +
+
+
+
+
+ +
+
+ +
+
+
+
+
+ +
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/user_zoom/ticket_stats_frequency.jst.eco b/app/assets/javascripts/app/views/user_zoom/ticket_stats_frequency.jst.eco new file mode 100644 index 000000000..4f4e50b0a --- /dev/null +++ b/app/assets/javascripts/app/views/user_zoom/ticket_stats_frequency.jst.eco @@ -0,0 +1,16 @@ +

Frequency

+
+ <% for item in @ticket_volume_by_year: %> +
+
+
+
+
+
<%- @T(item.text.substr(0,3)) %>
+
+ <% end %> +
+
+
<%- @T('Closed') %>
+
<%- @T('Created') %>
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/user_zoom/ticket_stats_list.jst.eco b/app/assets/javascripts/app/views/user_zoom/ticket_stats_list.jst.eco new file mode 100644 index 000000000..1ee3baa19 --- /dev/null +++ b/app/assets/javascripts/app/views/user_zoom/ticket_stats_list.jst.eco @@ -0,0 +1,18 @@ + +
    + <% for ticket_id in @ticket_ids_show: %> + <% ticket = App.Ticket.fullLocal(ticket_id) %> +
  1. +
    +
    +
    + +
  2. + <% end %> +
+<% if @ticket_ids.length > @ticket_ids_show.length: %> + <%- @T('Show all ...') %> +<% end %> \ No newline at end of file diff --git a/app/controllers/tickets_controller.rb b/app/controllers/tickets_controller.rb index ab8dc8fa1..043686a46 100644 --- a/app/controllers/tickets_controller.rb +++ b/app/controllers/tickets_controller.rb @@ -365,10 +365,159 @@ class TicketsController < ApplicationController } end + # GET /api/v1/ticket_stats/1 + def stats + user = User.find(params[:id]) + + # permissin check + #return if !ticket_permission(ticket) + + # lookup open user tickets + limit = 100 + assets = {} + condition = { + 'tickets.state_id' => Ticket::State.by_category('open'), + 'tickets.customer_id' => user.id, + } + user_tickets_open = Ticket.search( + :limit => limit, + #:query => params[:term], + :condition => condition, + :current_user => current_user, + :detail => true, + ) + user_tickets_open_ids = assets_of_tickets(user_tickets_open, assets) + + # lookup closed user tickets + condition = { + 'tickets.state_id' => Ticket::State.by_category('closed'), + 'tickets.customer_id' => user.id, + } + user_tickets_closed = Ticket.search( + :limit => limit, + #:query => params[:term], + :condition => condition, + :current_user => current_user, + :detail => true, + ) + user_tickets_closed_ids = assets_of_tickets(user_tickets_closed, assets) + + + # lookup open org tickets + org_tickets_open_ids = [] + if user.organization_id + condition = { + 'tickets.state_id' => Ticket::State.by_category('open'), + 'tickets.organization_id' => user.organization_id, + } + org_tickets_open = Ticket.search( + :limit => limit, + #:query => params[:term], + :condition => condition, + :current_user => current_user, + :detail => true, + ) + org_tickets_open_ids = assets_of_tickets(org_tickets_open, assets) + end + + # lookup closed org tickets + org_tickets_closed_ids = [] + if user.organization_id + condition = { + 'tickets.state_id' => Ticket::State.by_category('closed'), + 'tickets.organization_id' => user.organization_id, + } + org_tickets_closed = Ticket.search( + :limit => limit, + #:query => params[:term], + :condition => condition, + :current_user => current_user, + :detail => true, + ) + org_tickets_closed_ids = assets_of_tickets(org_tickets_closed, assets) + end + + # generate stats by user + user_ticket_volume_by_year = [] + now = DateTime.now + (0..11).each {|month_back| + date_to_check = DateTime.now - month_back.month + date_start = "#{date_to_check.year}-#{date_to_check.month}-#{01} 00:00:00" + date_end = "#{date_to_check.year}-#{date_to_check.month}-#{date_to_check.end_of_month.day} 00:00:00" + + condition = { + 'tickets.customer_id' => user.id, + } + + # created + created = Ticket.where('created_at > ? AND created_at < ?', date_start, date_end ).where(condition).count + + # closed + closed = Ticket.where('close_time > ? AND close_time < ?', date_start, date_end ).where(condition).count + + data = { + :month => date_to_check.month, + :year => date_to_check.year, + :text => Date::MONTHNAMES[date_to_check.month], + :created => created, + :closed => closed, + } + user_ticket_volume_by_year.push data + } + + # generate stats by org + org_ticket_volume_by_year = [] + now = DateTime.now + (0..11).each {|month_back| + date_to_check = DateTime.now - month_back.month + date_start = "#{date_to_check.year}-#{date_to_check.month}-#{01} 00:00:00" + date_end = "#{date_to_check.year}-#{date_to_check.month}-#{date_to_check.end_of_month.day} 00:00:00" + + condition = { + 'tickets.organization_id' => user.organization_id, + } + + # created + created = Ticket.where('created_at > ? AND created_at < ?', date_start, date_end ).where(condition).count + + # closed + closed = Ticket.where('close_time > ? AND close_time < ?', date_start, date_end ).where(condition).count + + data = { + :month => date_to_check.month, + :year => date_to_check.year, + :text => Date::MONTHNAMES[date_to_check.month], + :created => created, + :closed => closed, + } + org_ticket_volume_by_year.push data + } + + # return result + render :json => { + :user_tickets_open_ids => user_tickets_open_ids, + :user_tickets_closed_ids => user_tickets_closed_ids, + :org_tickets_open_ids => org_tickets_open_ids, + :org_tickets_closed_ids => org_tickets_closed_ids, + :user_ticket_volume_by_year => user_ticket_volume_by_year, + :org_ticket_volume_by_year => org_ticket_volume_by_year, + :assets => assets, + } + end + private + def assets_of_tickets(tickets, assets) + ticket_ids = [] + tickets.each do |ticket| + ticket_ids.push ticket.id + assets = ticket.assets(assets) + end + return ticket_ids + end + def article_create(ticket, params) -puts params.inspect + # create article if given form_id = params[:form_id] params.delete(:form_id) diff --git a/config/routes/ticket.rb b/config/routes/ticket.rb index 0b3257e6d..670f85f85 100644 --- a/config/routes/ticket.rb +++ b/config/routes/ticket.rb @@ -13,6 +13,7 @@ Zammad::Application.routes.draw do match api_path + '/ticket_customer', :to => 'tickets#ticket_customer', :via => :get match api_path + '/ticket_related/:ticket_id', :to => 'tickets#ticket_related', :via => :get match api_path + '/ticket_merge/:slave_ticket_id/:master_ticket_number', :to => 'tickets#ticket_merge', :via => :get + match api_path + '/ticket_stats/:id', :to => 'tickets#stats', :via => :get # ticket overviews match api_path + '/ticket_overviews', :to => 'ticket_overviews#show', :via => :get