New user and org zoom.

This commit is contained in:
Martin Edenhofer 2014-11-10 08:34:20 +01:00
parent c3025f87c3
commit 7b4015179f
12 changed files with 413 additions and 457 deletions

View file

@ -1,7 +1,4 @@
class App.OrganizationZoom extends App.Controller
elements:
'.tabsSidebar' : 'sidebar'
constructor: (params) ->
super
@ -12,7 +9,11 @@ class App.OrganizationZoom extends App.Controller
@navupdate '#'
App.Organization.full( @organization_id, @render )
# subscribe and reload data / fetch new data if triggered
@subscribeId = App.Organization.full( @organization_id, @render, false, true )
release: =>
App.Organization.unsubscribe(@subscribeId)
meta: =>
meta =
@ -36,10 +37,7 @@ class App.OrganizationZoom 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: (organization) =>
@ -47,12 +45,38 @@ class App.OrganizationZoom extends App.Controller
@doNotLog = 1
@recentView( 'Organization', @organization_id )
# get display data
organizationData = []
for item2 in App.Organization.configure_attributes
item = _.clone( item2 )
# check if value for _id exists
itemNameValue = item.name
itemNameValueNew = itemNameValue.substr( 0, itemNameValue.length - 3 )
if itemNameValueNew of organization
item.name = itemNameValueNew
# add to show if value exists
if organization[item.name] || item.tag is 'textarea'
# do not show firstname and lastname / already show via diplayName()
if item.name isnt 'name'
if item.info
organizationData.push item
@html App.view('organization_zoom')(
organization: organization
organizationData: organizationData
)
new Overviews(
el: @el
@$('[contenteditable]').ce({
mode: 'textonly'
multiline: true
maxlength: 250
})
new App.TicketStats(
el: @$('.js-ticket-stats')
organization: organization
)
@ -60,125 +84,38 @@ class App.OrganizationZoom extends App.Controller
genericObject: organization
)
new App.UpdateHeader(
el: @el
genericObject: organization
)
# start action controller
showHistory = =>
new App.OrganizationHistory( organization_id: organization.id )
editOrganization = =>
new App.ControllerGenericEdit(
id: organization.id
genericObject: 'Organization'
screen: 'edit'
pageData:
title: 'Organizations'
object: 'Organization'
objects: 'Organizations'
)
actions = [
{
name: 'edit'
title: 'Edit'
callback: editOrganization
}
{
name: 'history'
title: 'History'
callback: showHistory
}
]
new App.ActionRow(
el: @el.find('.action')
items: actions
)
new Sidebar(
el: @sidebar
organization: organization
)
class Overviews extends App.Controller
constructor: ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = App.Organization.full( @organization.id, @render, false, true )
release: =>
App.Organization.unsubscribe(@subscribeId)
render: (organization) =>
plugins =
main:
my_organization:
controller: App.DashboardTicketSearch,
params:
name: 'Tickets of Organization'
condition:
'tickets.state_id': [ 1,2,3,4,6 ]
'tickets.organization_id': organization.id
order:
by: 'created_at'
direction: 'DESC'
view:
d: [ 'number', 'title', 'customer', 'state', 'priority', 'created_at' ]
view_mode_default: 'd'
for area, plugins of plugins
for name, plugin of plugins
target = area + '_' + name
@el.find('.' + area + '-overviews').append('<div class="" id="' + target + '"></div>')
if plugin.controller
params = plugin.params || {}
params.el = @el.find( '#' + target )
new plugin.controller( params )
dndOptions =
handle: 'h2.can-move'
placeholder: 'can-move-plcaeholder'
tolerance: 'pointer'
distance: 15
opacity: 0.6
forcePlaceholderSize: true
@el.find( '#sortable' ).sortable( dndOptions )
@el.find( '#sortable-sidebar' ).sortable( dndOptions )
class Sidebar extends App.Controller
constructor: ->
super
# render ui
@render()
render: ->
items = []
editOrganization = (e, el) =>
new App.ControllerGenericEdit(
id: @organization.id
genericObject: 'Organization'
pageData:
title: 'Organizations'
object: 'Organization'
objects: 'Organizations'
)
showOrganization = (el) =>
new App.WidgetOrganization(
el: el
organization_id: @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) ->
super

View file

@ -77,12 +77,7 @@ class App.UserZoom extends App.Controller
maxlength: 250
})
#new Overviews(
# el: @el
# user: user
#)
new TicketStats(
new App.TicketStats(
el: @$('.js-ticket-stats')
user: user
)
@ -132,220 +127,6 @@ class App.UserZoom extends App.Controller
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
constructor: ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = App.User.full( @user.id, @render, false, true )
release: =>
App.User.unsubscribe(@subscribeId)
render: (user) =>
plugins = {
main: {
my_assigned: {
controller: App.DashboardTicketSearch,
params: {
name: 'Tickets of User'
condition:
'tickets.state_id': [ 1,2,3,4,6 ]
'tickets.customer_id': user.id
order:
by: 'created_at'
direction: 'DESC'
view:
d: [ 'number', 'title', 'state', 'priority', 'created_at' ]
view_mode_default: 'd'
},
},
},
}
if user.organization_id
plugins.main.my_organization = {
controller: App.DashboardTicketSearch,
params: {
name: 'Tickets of Organization'
condition:
'tickets.state_id': [ 1,2,3,4,6 ]
'tickets.organization_id': user.organization_id
order:
by: 'created_at'
direction: 'DESC'
view:
d: [ 'number', 'title', 'customer', 'state', 'priority', 'created_at' ]
view_mode_default: 'd'
},
}
for area, plugins of plugins
for name, plugin of plugins
target = area + '_' + name
@el.find('.' + area + '-overviews').append('<div class="" id="' + target + '"></div>')
if plugin.controller
params = plugin.params || {}
params.el = @el.find( '#' + target )
new plugin.controller( params )
dndOptions =
handle: 'h2.can-move'
placeholder: 'can-move-plcaeholder'
tolerance: 'pointer'
distance: 15
opacity: 0.6
forcePlaceholderSize: true
@el.find( '#sortable' ).sortable( dndOptions )
@el.find( '#sortable-sidebar' ).sortable( dndOptions )
class Router extends App.ControllerPermanent
constructor: (params) ->
super

View file

@ -0,0 +1,161 @@
class App.TicketStats extends App.Controller
events:
'click .js-userTab': 'showUserTab'
'click .js-orgTab': 'showOrgTab'
constructor: ->
super
# subscribe and reload data / fetch new data if triggered
if @user
@subscribeId = App.User.full( @user.id, @load, false, true )
if @organization
@subscribeId = App.Organization.full( @organization.id, @load, false, true )
release: =>
App.User.unsubscribe(@subscribeId)
load: (object) =>
if @organization
ajaxKey = "org_" + @organization.id
data =
organization_id: @organization.id
else
ajaxKey = "user_" + @user.id
data =
user_id: @user.id
organization_id: @user.organization_id
@ajax(
id: 'ticket_stats_' + ajaxKey
type: 'GET'
url: @apiPath + '/ticket_stats'
data: data
processData: true
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('widget/ticket_stats')(
user: @user
organization: @organization
)
if @organization
@showOrgTab()
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('widget/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
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('widget/ticket_stats_frequency')(
user: @user
ticket_volume_by_year: @ticket_volume_by_year.reverse()
)

View file

@ -1,18 +1,49 @@
<div class="flex vertical">
<div class="flex u-positionOrigin horizontal">
<div class="main flex tabsSidebar-sidebarSpacer tabsSidebar-tabsSpacer">
<div class="organizationZoom">
<div class="page-header">
<h1><%- @T( @head ) %></h1>
</div>
<div class="flex userZoom">
<div class="main-overviews" id="sortable"></div>
<div class="userZoom-window">
<div class="userZoom-section vertical centered">
<div class="align-right userZoom-action dropdown dropdown--actions action"></div>
<h1><%= @organization.displayName() %></h1>
</div>
<div class="userZoom-section">
<div class="userZoom-details horizontal wrap">
<% for row in @organizationData: %>
<% if @organization[row.name]: %>
<% if row.tag isnt 'textarea': %>
<div class="userZoom-detailsEntry">
<label><%- @Ti( row.display ) %></label>
<%- @L( @P( @organization[row.name] ) ) %>
</div>
<% end %>
<% end %>
<% end %>
<% for row in @organizationData: %>
<% if row.tag is 'textarea': %>
<div class="userZoom-detailsEntry" style="width: 100%;">
<label><%- @Ti( row.display ) %></label>
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%= @organization[row.name] %></div>
</div>
<% end %>
<% end %>
</div>
</div>
<div class="tabsSidebar vertical"></div>
<% if @organization.members: %>
<div class="userZoom-section">
<label><%- @T('Member') %></label>
<div class="userZoom-details horizontal wrap">
<% for user in @organization.members: %>
<div class="userZoom-detailsEntry">
<a href="<%- user.uiUrl() %>" class="user-popover" data-id="<%- user.id %>"><%= user.displayName() %></a>
</div>
<% end %>
</div>
</div>
<% end %>
<div class="userZoom-section js-ticket-stats"></div>
</div>
<form class="bottom-form form-inline horizontal" role="form">
<div class="action"></div>
</form>
</div>

View file

@ -24,7 +24,7 @@
<div class="u-textTruncate"><%- @T( @ticket.priority.name ) %></div>
</div>
<div class="column">
<h3><%- @T( 'Age' ) %></h3>
<h3><%- @T( 'Created' ) %></h3>
<div class="u-textTruncate"><%- @P( @ticket.humanTime ) %></div>
</div>
<div class="column">

View file

@ -365,9 +365,12 @@ class TicketsController < ApplicationController
}
end
# GET /api/v1/ticket_stats/1
# GET /api/v1/ticket_stats
def stats
user = User.find(params[:id])
if !params[:user_id] && !params[:organization_id]
raise "Need user_id or organization_id as param"
end
# permissin check
#return if !ticket_permission(ticket)
@ -375,6 +378,13 @@ class TicketsController < ApplicationController
# lookup open user tickets
limit = 100
assets = {}
access_condition = Ticket.access_condition( current_user )
now = DateTime.now
user_tickets_open_ids = []
user_tickets_closed_ids = []
user_ticket_volume_by_year = []
if params[:user_id]
user = User.find( params[:user_id] )
condition = {
'tickets.state_id' => Ticket::State.by_category('open'),
'tickets.customer_id' => user.id,
@ -402,44 +412,7 @@ class TicketsController < ApplicationController
)
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"
@ -450,10 +423,16 @@ class TicketsController < ApplicationController
}
# created
created = Ticket.where('created_at > ? AND created_at < ?', date_start, date_end ).where(condition).count
created = Ticket.where('created_at > ? AND created_at < ?', date_start, date_end ).
where(access_condition).
where(condition).
count
# closed
closed = Ticket.where('close_time > ? AND close_time < ?', date_start, date_end ).where(condition).count
closed = Ticket.where('close_time > ? AND close_time < ?', date_start, date_end ).
where(access_condition).
where(condition).
count
data = {
:month => date_to_check.month,
@ -464,17 +443,50 @@ class TicketsController < ApplicationController
}
user_ticket_volume_by_year.push data
}
end
# lookup open org tickets
org_tickets_open_ids = []
org_tickets_closed_ids = []
org_ticket_volume_by_year = []
if params[:organization_id] && !params[:organization_id].empty?
organization = Organization.find( params[:organization_id] )
condition = {
'tickets.state_id' => Ticket::State.by_category('open'),
'tickets.organization_id' => params[: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)
# lookup closed org tickets
condition = {
'tickets.state_id' => Ticket::State.by_category('closed'),
'tickets.organization_id' => params[: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)
# 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,
'tickets.organization_id' => params[:organization_id],
}
# created
@ -492,6 +504,7 @@ class TicketsController < ApplicationController
}
org_ticket_volume_by_year.push data
}
end
# return result
render :json => {

View file

@ -79,6 +79,36 @@ returns
=begin
get user access conditions
connditions = Ticket.access_condition( User.find(1) )
returns
result = [user1, user2, ...]
=end
def self.access_condition(user)
access_condition = []
if user.is_role('Agent')
group_ids = Group.select( 'groups.id' ).joins(:users).
where( 'groups_users.user_id = ?', user.id ).
where( 'groups.active = ?', true ).
map( &:id )
access_condition = [ 'group_id IN (?)', group_ids ]
else
if !user.organization || ( !user.organization.shared || user.organization.shared == false )
access_condition = [ 'customer_id = ?', user.id ]
else
access_condition = [ '( customer_id = ? OR organization_id = ? )', user.id, user.organization.id ]
end
end
access_condition
end
=begin
merge tickets
ticket = Ticket.find(123)

View file

@ -4,7 +4,7 @@ module Ticket::Search
=begin
search tickets
search tickets via search index
result = Ticket.search(
:current_user => User.find(123),
@ -17,7 +17,7 @@ returns
result = [ticket_model1, ticket_model2]
search tickets
search tickets via search index
result = Ticket.search(
:current_user => User.find(123),
@ -30,6 +30,22 @@ returns
result = [1,3,5,6,7]
search tickets via database
result = Ticket.search(
:current_user => User.find(123),
:condition => '',
:detail => true,
:limit => 15,
:full => 0
)
returns
result = [1,3,5,6,7]
=end
def search (params)
@ -94,20 +110,7 @@ returns
end
# fallback do sql query
access_condition = []
if current_user.is_role('Agent')
group_ids = Group.select( 'groups.id' ).joins(:users).
where( 'groups_users.user_id = ?', current_user.id ).
where( 'groups.active = ?', true ).
map( &:id )
access_condition = [ 'group_id IN (?)', group_ids ]
else
if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
access_condition = [ 'customer_id = ?', current_user.id ]
else
access_condition = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
end
end
access_condition = Ticket.access_condition( current_user )
# do query
# - stip out * we already search for *query* -

View file

@ -13,7 +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
match api_path + '/ticket_stats', :to => 'tickets#stats', :via => :get
# ticket overviews
match api_path + '/ticket_overviews', :to => 'ticket_overviews#show', :via => :get