Init version.

This commit is contained in:
Martin Edenhofer 2015-10-20 10:48:43 +02:00
parent 5327445d44
commit 76637f55a2
17 changed files with 1457 additions and 5 deletions

View file

@ -0,0 +1,488 @@
class Index extends App.ControllerContent
constructor: ->
super
# check authentication
return if !@authenticate()
@title 'Reporting'
@navupdate '#report'
@startLoading()
@ajax(
type: 'GET',
url: @apiPath + '/reports/config',
processData: true,
success: (data) =>
@stopLoading()
@config = data.config
App.Collection.load( type: 'ReportProfile', data: data.profiles )
@render()
)
getParams: =>
return @params if @params
@params = {}
@params.timeRange = 'year'
current = new Date()
currentDay = current.getDate()
currentMonth = current.getMonth() + 1
currentYear = current.getFullYear()
currentWeek = current.getWeek()
@params.day = currentDay
@params.month = currentMonth
@params.week = currentWeek
@params.year = currentYear
if !@params.metric
for key, config of @config.metric
if config.default
@params.metric = config.name
if !@params.backendSelected
@params.backendSelected = {}
for key, config of @config.metric
for backend in config.backend
if backend.selected
@params.backendSelected[backend.name] = true
if !@params.profileSelected
@params.profileSelected = {}
for profile in App.ReportProfile.all()
if _.isEmpty( @params.profileSelected )
@params.profileSelected[ profile.id ] = true
@params
render: (data = {}) =>
@params = @getParams()
@html App.view('report/main')(
params: @params
)
new TimeRangePicker(
el: @el.find('.js-timeRangePicker')
params: @params
ui: @
)
new TimePicker(
el: @el.find('.js-timePicker')
params: @params
ui: @
)
new Sidebar(
el: @el.find('.sidebar')
config: @config
params: @params
)
new Graph(
el: @el
config: @config
params: @params
ui: @
)
class Graph extends App.ControllerContent
constructor: ->
super
# rerender view
@bind 'ui:report:rerender', =>
@render()
@render()
render: =>
update = (data) =>
@draw(data.data)
t = new Date
@el.find('#download-chart').html(t.toString())
new Download(
el: @el.find('.js-dataDownload')
config: @config
params: @params
ui: @ui
)
url = @apiPath + '/reports/generate'
interval = 60000
if @params.timeRange is 'year'
interval = 30000
if @params.timeRange is 'month'
interval = 20000
if @params.timeRange is 'week'
interval = 20000
if @params.timeRange is 'day'
interval = 20000
if @params.timeRange is 'realtime'
interval = 10000
@ajax(
type: 'POST'
url: url
data: JSON.stringify(
metric: @params.metric
year: @params.year
month: @params.month
week: @params.week
day: @params.day
timeRange: @params.timeRange
profiles: @params.profileSelected
backends: @params.backendSelected
)
processData: true
success: (data) =>
update(data)
@delay( @render, interval, 'report-update', 'page' )
)
draw: (data) =>
@log('draw', data)
$('#placeholder').empty()
# create xaxis
xaxis = []
if @params.timeRange is 'realtime'
for minute in [0..59]
xaxis.push [minute, '']
else if @params.timeRange is 'day'
for hour in [0..23]
xaxis.push [hour, hour]
else if @params.timeRange is 'month'
for day in [1..31]
xaxis.push [day, day]
else if @params.timeRange is 'week'
xaxis = [[1, 'Mon'], [2, 'Tue'], [3, 'Wed'], [4, 'Thr'], [5, 'Fri'], [6, 'Sat'], [7, 'Sun'] ]
else
xaxis = [[1, 'Jan'], [2, 'Feb'], [3, 'Mar'], [4, 'Apr'], [5, 'Mai'], [6, 'Jun'], [7, 'Jul'], [8, 'Aug'], [9, 'Sep'], [10, 'Oct'], [11, 'Nov'], [12, 'Dec']]
dataPlot = []
for key, value of data
dataPlot.push {
data: value
label: key
}
# plot
$.plot( $('#placeholder'), dataPlot, {
yaxis: { min: 0 },
xaxis: { ticks: xaxis }
} )
class Download extends App.Controller
events:
'click .js-dataDownloadBackendSelector': 'tableUpdate'
constructor: (data) ->
# unbind existing click binds
data.el.unbind('click .js-dataDownloadBackendSelector')
super
@render()
render: ->
reports = []
$('.js-backendSelector:checked').each( (index, element) ->
if $(element).hasClass('download')
value = $(element).val()
reports.push value
)
profiles = []
for key, value of @params.profileSelected
profiles.push App.ReportProfile.find(key)
console.log('reports', reports, 'profiles', profiles, @config.metric, @params.metric, @config.metric[@params.metric])
@html App.view('report/download_header')(
reports: reports
profiles: profiles
metric: @config.metric[@params.metric]
)
@profileSelected = ''
@backendSelected = ''
active = false
@el.find('.js-dataDownloadBackendSelector').each( (index, element) ->
if $(element).parent().hasClass('active')
active = true
)
if !active
@el.find('.js-dataDownloadBackendSelector').first().parent().addClass('active')
@el.find('.js-dataDownloadBackendSelector').each( (index, element) =>
if $(element).parent().hasClass('active')
@profileSelected = $(element).data('profile')
@backendSelected = $(element).data('report')
)
@tableUpdate()
tableUpdate: (e) =>
if e
e.preventDefault()
@el.find('.js-dataDownloadBackendSelector').parent().removeClass('active')
$(e.target).parent().addClass('active')
@profileSelected = $(e.target).data('profile')
@backendSelected = $(e.target).data('backend')
table = (tickets, count) =>
url = '#ticket/zoom/'
if App.Config.get('import_mode')
url = App.Config.get('import_otrs_endpoint') + '/index.pl?Action=AgentTicketZoom;TicketID='
if _.isEmpty(tickets)
@el.find('js-dataDownloadTable').html('')
else
html = App.view('report/download_list')(
tickets: tickets
count: count
url: url
download: @apiPath + '/reports/csvforset/' + name
)
@el.find('js-dataDownloadTable').html(html)
@startLoading()
@ajax(
type: 'POST'
url: @apiPath + '/reports/sets'
data: JSON.stringify(
metric: @params.metric
year: @params.year
month: @params.month
week: @params.week
day: @params.day
timeRange: @params.timeRange
profiles: @params.profileSelected
)
processData: true
success: (data) =>
@stopLoading()
# load ticket collection / do not save in localStorage
App.Collection.load( type: 'TicketReport', data: data.tickets, localStorage: true )
ticket_collection = []
if data.tickets
for record in data.tickets
ticket = App.TicketReport.fullLocal( record.id )
ticket_collection.push ticket
table( ticket_collection, data.count )
)
class TimeRangePicker extends App.Controller
events:
'click .js-timeRange': 'select'
constructor: ->
super
# rerender view
@bind 'ui:report:rerender', =>
@render()
@render()
render: =>
@html App.view('report/time_range_picker')()
# select time slot
@el.find('.js-timeRange').removeClass('active')
@el.find('.js-timeRange[data-type="' + @ui.params.timeRange + '"]').addClass('active')
select: (e) =>
console.log('TS click')
e.preventDefault()
@ui.params.timeRange = $(e.target).data('type')
console.log 'SLOT', @ui.params.timeRange
App.Event.trigger( 'ui:report:rerender' )
class TimePicker extends App.Controller
events:
'click .js-timePickerDay': 'selectTimeDay'
'click .js-timePickerYear': 'selectTimeYear'
'click .js-timePickerMonth': 'selectTimeMonth'
'click .js-timePickerWeek': 'selectTimeWeek'
constructor: ->
super
@_timeSlotPicker()
# rerender view
@bind 'ui:report:rerender', =>
@render()
@render()
render: =>
@html App.view('report/time_picker')(
params: @ui.params
timeRangeDay: @timeRangeDay
timeRangeMonth: @timeRangeMonth
timeRangeWeek: @timeRangeWeek
timeRangeYear: @timeRangeYear
)
# select time slot
@el.find('.time-slot').removeClass('active')
@el.find('.time-slot[data-type="' + @ui.params.timeRange + '"]').addClass('active')
selectTimeDay: (e) =>
e.preventDefault()
@ui.params.day = $(e.target).data('type')
$(e.target).parent().parent().find('li').removeClass('active')
$(e.target).parent().addClass('active')
App.Event.trigger( 'ui:report:rerender' )
selectTimeMonth: (e) =>
e.preventDefault()
@ui.params.month = $(e.target).data('type')
$(e.target).parent().parent().find('li').removeClass('active')
$(e.target).parent().addClass('active')
App.Event.trigger( 'ui:report:rerender' )
selectTimeWeek: (e) =>
e.preventDefault()
@ui.params.week = $(e.target).data('type')
$(e.target).parent().parent().find('li').removeClass('active')
$(e.target).parent().addClass('active')
App.Event.trigger( 'ui:report:rerender' )
selectTimeYear: (e) =>
e.preventDefault()
@ui.params.year = $(e.target).data('type')
$(e.target).parent().parent().find('li').removeClass('active')
$(e.target).parent().addClass('active')
App.Event.trigger( 'ui:report:rerender' )
_timeSlotPicker: ->
@timeRangeYear = []
year = new Date().getFullYear()
for item in [year-2..year]
record = {
display: item
value: item
}
@timeRangeYear.push record
@timeRangeMonth = [
{
display: 'Jan'
value: 1
},
{
display: 'Feb'
value: 2
},
{
display: 'Mar'
value: 3
},
{
display: 'Apr'
value: 4,
},
{
display: 'Mai'
value: 5,
},
{
display: 'Jun'
value: 6,
},
{
display: 'Jul'
value: 7,
},
{
display: 'Aug'
value: 8,
},
{
display: 'Sep'
value: 9,
},
{
display: 'Oct'
value: 10,
},
{
display: 'Nov'
value: 11,
},
{
display: 'Dec'
value: 12,
},
]
@timeRangeWeek = []
for item in [1..52]
record = {
display: item
value: item
}
@timeRangeWeek.push record
@timeRangeDay = []
for item in [1..31]
record = {
display: item
value: item
}
@timeRangeDay.push record
class Sidebar extends App.Controller
events:
'click .js-profileSelector': 'selectProfile'
'click .js-backendSelector': 'selectBackend'
'click .panel-title': 'selectMetric'
constructor: ->
super
@render()
render: =>
metrics = @config.metric
profiles = App.ReportProfile.all()
console.log('Si', @params)
@html App.view('report/sidebar')(
metrics: metrics
params: @params
profiles: profiles
)
selectMetric: (e) =>
return if $(e.target).closest('.panel').find('.collapse.in').get(0)
metric = $(e.target).closest('.panel').data('metric')
return if @params.metric is metric
@params.metric = metric
App.Event.trigger( 'ui:report:rerender' )
selectProfile: (e) =>
profile_id = $(e.target).val()
active = $(e.target).prop('checked')
if active
@params.profileSelected[profile_id] = true
else
delete @params.profileSelected[profile_id]
App.Event.trigger( 'ui:report:rerender' )
selectBackend: (e) =>
backend = $(e.target).val()
active = $(e.target).prop('checked')
if active
@params.backendSelected[backend] = true
else
delete @params.backendSelected[backend]
App.Event.trigger( 'ui:report:rerender' )
App.Config.set( 'report', Index, 'Routes' )
App.Config.set( 'Reporting', { prio: 8000, parent: '', name: 'Reporing', translate: true, target: '#report', icon: 'report', role: ['Admin'] }, 'NavBarRight' )

View file

@ -0,0 +1,27 @@
class Index extends App.ControllerContent
constructor: ->
super
# check authentication
return if !@authenticate()
new App.ControllerGenericIndex(
el: @el
id: @id
genericObject: 'ReportProfile'
pageData:
title: 'Report Profile'
home: 'report_profiles'
object: 'Report Profile'
objects: 'Report Profiles'
navupdate: '#report_profiles'
notes: [
# 'Report Profile are ...'
]
buttons: [
{ name: 'New Profile', 'data-type': 'new', class: 'primary' }
]
container: @el.closest('.content')
)
App.Config.set( 'ReportProfile', { prio: 8000, name: 'Report Profiles', parent: '#manage', target: '#manage/report_profiles', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -0,0 +1,14 @@
class App.ReportProfile extends App.Model
@configure 'ReportProfile', 'name', 'condition', 'active'
@extend Spine.Model.Ajax
@url: @apiPath + '/report_profiles'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'condition', display: 'Filter', tag: 'ticket_selector', null: true },
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'active', default: true },
]
@configure_delete = true
@configure_overview = [
'name',
]

View file

@ -0,0 +1,14 @@
<div>
<ul class="nav nav-tabs">
<% for profile in @profiles: %>
<% for backend in @metric.backend: %>
<% if backend.dataDownload: %>
<li><a href="#" class="js-dataDownloadBackendSelector" data-toggle="tab" data-profile="<%= profile.id %>" data-backend="<%= backend.name %>"><%= @T(backend.display) %></a></li>
<% end %>
<% end %>
<% end %>
</ul>
<div class="js-dataDownloadTable"></div>
</div>

View file

@ -0,0 +1,26 @@
<i><%- @T('%s records', @count) %></i>
<a href="<%-@download%>" target="_blank" data-type="attachment" id="downloadsetascsv">
<i class="glyphicon glyphicon-download" title="<%- @Ti('Download') %>"></i>
</a>
<table class="table table-striped table-hover">
<thead>
<tr>
<td><%- @T('Number') %></td>
<td><%- @T('Title') %></td>
<td><%- @T('State') %></td>
<td><%- @T('Queue') %></td>
<td><%- @T('Created') %></td>
</tr>
</thead>
<tbody>
<% for ticket in @tickets: %>
<tr>
<td><a target="_blank" href="<%= @url %><%= ticket.id %>"><%- @P(ticket, 'number') %></a></td>
<td><%- @P(ticket, 'title') %></td>
<td><%- @P(ticket, 'state') %></td>
<td><%- @P(ticket, 'group') %></td>
<td><%- @P(ticket, 'created_at') %></td>
</tr>
<% end %>
</tbody>
</table>

View file

@ -0,0 +1,29 @@
<div class="sidebar"></div>
<div class="main flex">
<div class="page-header clearfix">
<h1 class="pull-left"><%- @T( 'Reporting' ) %> <small></small></h1>
<div class="js-timeRangePicker pull-right" style="margin-top: 20px;"></div>
</div>
<div class="page-content"></div>
<div id="placeholder" class="" style="width:710px;height:450px;"></div>
<br>
<span class=" muted" id="download-chart" style="font-size: 8px;"></span>
<!--
<a href="<%-@download%>" target="_blank" data-type="attachment" class="pull-right" id="download-chart">
<i class="icon-download" title="<%- @Ti('Download') %>"></i>
</a>
-->
<!--
<div id="overview" style="margin-left:50px;margin-top:20px;width:400px;height:50px"></div>
-->
<div class="js-timePicker"></div>
<br>
<div class="js-dataDownload"></div>
</div>
</div>

View file

@ -0,0 +1,33 @@
<div class="panel-group" id="accordion">
<% for key, metric of @metrics: %>
<div class="panel panel-default" data-metric="<%= metric.name %>">
<div class="panel-heading">
<div class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapse-<%= metric.name %>">
<%- @T(metric.display) %>
</a>
</div>
</div>
<div id="collapse-<%= metric.name %>" class="panel-collapse collapse <% if metric.name is @params.metric: %>in<% end %>">
<div class="panel-body">
<ul class="type area_select">
<% for backend in metric.backend: %>
<li style="display: block; margin-left: -12px;"><input class="js-backendSelector" type="checkbox" value="<%= backend.name %>" <% if @params.backendSelected[backend.name]: %>checked<% end %>/>
<%- @T(backend.display) %>
</li>
<% end %>
</ul>
</div>
</div>
</div>
<% end %>
</div>
<h3><%- @T('Profiles') %></h3>
<ul>
<% for profile in @profiles: %>
<li style="display: block; margin-left: -12px;"><input class="js-profileSelector" type="radio" name="profile" value="<%= profile.id %>" <% if @params.profileSelected[profile.id]: %>checked<% end %>/>
<%= profile.name %>
</li>
<% end %>
</ul>

View file

@ -0,0 +1,36 @@
<div class="">
<% if @params.timeRange is 'day': %>
<div class="btn-group" role="group" aria-label="">
<% for item in @timeRangeDay: %>
<button type="button" class="btn btn-default js-timePickerDay <% if @params.day is item.value: %>active<% end %>" data-id="<%= @params.timeRange %>" data-type="<%= item.value %>"><%= item.display %></button>
<% end %>
</div>
<br>
<% end %>
<% if @params.timeRange is 'day' || @params.timeRange is 'month': %>
<div class="btn-group" role="group" aria-label="">
<% for item in @timeRangeMonth: %>
<button type="button" class="btn btn-default js-timePickerMonth <% if @params.month is item.value: %>active<% end %>" data-id="<%= @params.timeRange %>" data-type="<%= item.value %>"><%= item.display %></button>
<% end %>
</div>
<br>
<% end %>
<% if @params.timeRange is 'week': %>
<div class="btn-group" role="group" aria-label="">
<% for item in @timeRangeWeek: %>
<button type="button" class="btn btn-default js-timePickerWeek <% if @params.week is item.value: %>active<% end %>" data-id="<%= @params.timeRange %>" data-type="<%= item.value %>"><%= item.display %></button>
<% end %>
</div>
<br>
<% end %>
<% if @params.timeRange isnt 'realtime': %>
<div class="btn-group" role="group" aria-label="">
<% for item in @timeRangeYear: %>
<button type="button" class="btn btn-default js-timePickerYear <% if @params.year is item.value: %>active<% end %>" data-id="<%= @params.timeRange %>" data-type="<%= item.value %>"><%= item.display %></button>
<% end %>
</div>
<% end %>
</div>

View file

@ -0,0 +1,7 @@
<div class="btn-group">
<button type="button" class="btn js-timeRange" data-type="year"><%- @T('Year') %></button>
<button type="button" class="btn js-timeRange" data-type="month"><%- @T('Month') %></button>
<button type="button" class="btn js-timeRange" data-type="week"><%- @T('Week') %></button>
<button type="button" class="btn js-timeRange" data-type="day"><%- @T('Day') %></button>
<button type="button" class="btn js-timeRange" data-type="realtime"><%- @T('Realtime') %></button>
</div>

View file

@ -151,7 +151,7 @@ class ApplicationController < ActionController::Base
def authentication_check_only(auth_param) def authentication_check_only(auth_param)
logger.debug 'authentication_check' #logger.debug 'authentication_check'
#logger.debug params.inspect #logger.debug params.inspect
#logger.debug session.inspect #logger.debug session.inspect
#logger.debug cookies.inspect #logger.debug cookies.inspect

View file

@ -0,0 +1,141 @@
class ReportProfilesController < ApplicationController
before_action :authentication_check
=begin
Format:
JSON
Example:
{
"id":1,
"name":"some report_profile",
"condition":{"c_a":1,"c_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/report_profiles.json
Response:
[
{
"id": 1,
"name": "some_name1",
...
},
{
"id": 2,
"name": "some_name2",
...
}
]
Test:
curl http://localhost/api/report_profiles.json -v -u #{login}:#{password}
=end
def index
model_index_render(Report::Profile, params)
end
=begin
Resource:
GET /api/report_profiles/#{id}.json
Response:
{
"id": 1,
"name": "name_1",
...
}
Test:
curl http://localhost/api/report_profiles/#{id}.json -v -u #{login}:#{password}
=end
def show
model_show_render(Report::Profile, params)
end
=begin
Resource:
POST /api/report_profiles.json
Payload:
{
"name":"some report_profile",
"condition":{"c_a":1,"c_b":2},
}
Response:
{
"id": 1,
"name": "some_name",
...
}
Test:
curl http://localhost/api/report_profiles.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def create
model_create_render(Report::Profile, params)
end
=begin
Resource:
PUT /api/report_profiles/{id}.json
Payload:
{
"name":"some report_profile",
"condition":{"c_a":1,"c_b":2},
}
Response:
{
"id": 1,
"name": "some_name",
...
}
Test:
curl http://localhost/api/report_profiles.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"name": "some_name","active": true, "note": "some note"}'
=end
def update
model_update_render(Report::Profile, params)
end
=begin
Resource:
DELETE /api/report_profiles/{id}.json
Response:
{}
Test:
curl http://localhost/api/report_profiles.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X DELETE
=end
def destroy
model_destory_render(Report::Profile, params)
end
end

View file

@ -0,0 +1,152 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class ReportsController < ApplicationController
before_action :authentication_check
# GET /api/reports/config
def config
return if deny_if_not_role('Report')
render json: {
config: Report.config,
profiles: Report::Profile.list,
}
end
# GET /api/reports/generate
def generate
return if deny_if_not_role('Report')
#{"metric"=>"count", "year"=>2015, "month"=>10, "week"=>43, "day"=>20, "timeSlot"=>"year", "report"=>{"metric"=>"count", "year"=>2015, "month"=>10, "week"=>43, "day"=>20, "timeSlot"=>"year"}}
if params[:timeRange] == 'realtime'
start = (Time.zone.now - 60.minutes).iso8601
stop = Time.zone.now.iso8601
created = aggs(start, stop, 'minute', 'created_at')
closed = aggs(start, stop, 'minute', 'close_time')
elsif params[:timeRange] == 'day'
start = Date.parse("#{params[:year]}-#{params[:month]}-#{params[:day]}").iso8601
start = "#{start}T00:00:00Z"
stop = "#{start}T23:59:59Z"
created = aggs(start, stop, 'hour', 'created_at')
closed = aggs(start, stop, 'hour', 'close_time')
elsif params[:timeRange] == 'week'
start = Date.commercial(params[:year], params[:week]).iso8601
stop = Date.parse(start).end_of_week
created = aggs(start, stop, 'week', 'created_at')
closed = aggs(start, stop, 'week', 'close_time')
elsif params[:timeRange] == 'month'
start = Date.parse("#{params[:year]}-#{params[:month]}-01}").iso8601
stop = Date.parse(start).end_of_month
created = aggs(start, stop, 'day', 'created_at')
closed = aggs(start, stop, 'day', 'close_time')
else
start = "#{params[:year]}-01-01"
stop = "#{params[:year]}-12-31"
created = aggs(start, stop, 'month', 'created_at')
closed = aggs(start, stop, 'month', 'close_time')
end
render json: {
data: {
created: created,
closed: closed,
}
}
end
# GET /api/reports/sets
def sets
return if deny_if_not_role('Report')
#{"metric"=>"count", "year"=>2015, "month"=>10, "week"=>43, "day"=>20, "timeSlot"=>"year", "report"=>{"metric"=>"count", "year"=>2015, "month"=>10, "week"=>43, "day"=>20, "timeSlot"=>"year"}}
if params[:timeRange] == 'realtime'
start = (Time.zone.now - 60.minutes).iso8601
stop = Time.zone.now.iso8601
elsif params[:timeRange] == 'day'
start = Date.parse("#{params[:year]}-#{params[:month]}-#{params[:day]}").iso8601
start = "#{start}T00:00:00Z"
stop = "#{start}T23:59:59Z"
elsif params[:timeRange] == 'week'
start = Date.commercial(params[:year], params[:week]).iso8601
stop = Date.parse(start).end_of_week
elsif params[:timeRange] == 'month'
start = Date.parse("#{params[:year]}-#{params[:month]}-01}").iso8601
stop = Date.parse(start).end_of_month
else
start = "#{params[:year]}-01-01"
stop = "#{params[:year]}-12-31"
end
# get data
render json: {
data: {
start: start,
stop: stop,
}
}
end
def aggs(range_start, range_end, interval, field)
result = SearchIndexBackend.aggs(
{
},
[range_start, range_end, field, interval],
['Ticket'],
)
data = []
if interval == 'month'
start = Date.parse(range_start)
stop_interval = 12
elsif interval == 'week'
start = Date.parse(range_start)
stop_interval = 7
elsif interval == 'day'
start = Date.parse(range_start)
stop_interval = 31
elsif interval == 'hour'
start = Time.zone.parse(range_start)
stop_interval = 24
elsif interval == 'minute'
start = Time.zone.parse(range_start)
stop_interval = 60
end
(1..stop_interval).each {|counter|
match = false
result['aggregations']['time_buckets']['buckets'].each {|item|
if interval == 'minute'
start_string = start.iso8601.sub(/:\d\d.+?$/, '')
else
start_string = start.iso8601.sub(/:\d\d:\d\d.+?$/, '')
end
next if !item['doc_count']
next if item['key_as_string'] !~ /#{start_string}/
match = true
data.push [counter, item['doc_count']]
if interval == 'month'
start = start.next_month
elsif interval == 'week'
start = start.next_week
elsif interval == 'day'
start = start.next_day
elsif interval == 'hour'
start = start + 1.hour
elsif interval == 'minute'
start = start + 1.minute
end
}
next if match
data.push [counter, 0]
if interval == 'month'
start = start.next_month
elsif interval == 'week'
start = start.next_week
elsif interval == 'day'
start = start + 1.day
elsif interval == 'hour'
start = start + 1.hour
elsif interval == 'minute'
start = start + 1.minute
end
}
data
end
end

278
app/models/report.rb Normal file
View file

@ -0,0 +1,278 @@
class Report
def self.config
config = {}
config[:metric] = {}
config[:metric][:count] = {
name: 'count',
display: 'Ticket Count',
default: true,
prio: 10_000,
}
backend = [
{
name: 'created',
display: 'Created',
selected: true,
dataDownload: true,
},
{
name: 'closed',
display: 'Closed',
selected: true,
dataDownload: true,
},
{
name: 'backlog',
display: 'Backlog',
selected: true,
dataDownload: false,
},
{
name: 'first_solution',
display: 'First Solution',
selected: true,
dataDownload: true,
},
{
name: 'reopen',
display: 'Re-Open',
selected: false,
dataDownload: true,
},
{
name: 'movedin',
display: 'Moved in',
selected: false,
dataDownload: true,
},
{
name: 'movedout',
display: 'Moved out',
selected: false,
dataDownload: true,
},
{
name: 'sla_in',
display: 'SLA in',
selected: false,
dataDownload: true,
},
{
name: 'sla_out',
display: 'SLA out',
selected: false,
dataDownload: true,
},
]
config[:metric][:count][:backend] = backend
config[:metric][:create_channels] = {
name: 'create_channels',
display: 'Create Channels',
prio: 9000,
}
backend = [
{
name: 'phone_in',
display: 'Phone (in)',
selected: true,
dataDownload: true,
},
{
name: 'phone_out',
display: 'Phone (out)',
selected: true,
dataDownload: true,
},
{
name: 'email_in',
display: 'Email (in)',
selected: true,
dataDownload: true,
},
{
name: 'email_out',
display: 'Email (out)',
selected: true,
dataDownload: true,
},
{
name: 'web_in',
display: 'Web (in)',
selected: true,
dataDownload: true,
},
{
name: 'twitter_in',
display: 'Twitter (in)',
selected: true,
dataDownload: true,
},
]
config[:metric][:create_channels][:backend] = backend
config[:metric][:times] = {
name: 'times',
display: 'Times',
prio: 8000,
}
backend = [
{
name: 'first_response_average',
display: 'First Response average',
selected: true,
dataDownload: true,
},
{
name: 'first_response_max',
display: 'First Response max',
selected: true,
dataDownload: true,
},
{
name: 'first_response_min',
display: 'First Response min',
selected: true,
dataDownload: true,
},
{
name: 'solution_time_average',
display: 'Solution Time average',
selected: true,
dataDownload: true,
},
{
name: 'solution_time_max',
display: 'Solution Time max',
selected: true,
dataDownload: true,
},
{
name: 'solution_time_min',
display: 'Solution Time min',
selected: true,
dataDownload: true,
},
]
config[:metric][:times][:backend] = backend
config[:metric][:communication] = {
name: 'communication',
display: 'Communication',
prio: 7000,
}
backend = [
{
name: 'phone_in',
display: 'Phone (in)',
selected: true,
dataDownload: true,
},
{
name: 'phone_out',
display: 'Phone (out)',
selected: true,
dataDownload: true,
},
{
name: 'email_in',
display: 'Email (in)',
selected: true,
dataDownload: true,
},
{
name: 'email_out',
display: 'Email (out)',
selected: true,
dataDownload: true,
},
{
name: 'web_in',
display: 'Web (in)',
selected: true,
dataDownload: true,
},
{
name: 'twitter_in',
display: 'Twitter (in)',
selected: true,
dataDownload: true,
},
{
name: 'twitter_out',
display: 'Twitter (out)',
selected: true,
dataDownload: true,
},
]
config[:metric][:communication][:backend] = backend
config[:metric][:sla] = {
name: 'sla',
display: 'SLAs',
prio: 6000,
}
backend = [
{
name: 'sla_out_1',
display: 'SLA (out) - <1h',
selected: true,
dataDownload: true,
},
{
name: 'sla_out_2',
display: 'SLA (out) - <2h',
selected: true,
dataDownload: true,
},
{
name: 'sla_out_4',
display: 'SLA (out) - <4h',
selected: true,
dataDownload: true,
},
{
name: 'sla_out_8',
display: 'SLA (out) - <8h',
selected: true,
dataDownload: true,
},
{
name: 'sla_in_1',
display: 'SLA (in) - <1h',
selected: true,
dataDownload: true,
},
{
name: 'sla_in_2',
display: 'SLA (in) - <2h',
selected: true,
dataDownload: true,
},
{
name: 'sla_in_4',
display: 'SLA (in) - <4h',
selected: true,
dataDownload: true,
},
{
name: 'sla_in_8',
display: 'SLA (in) - <8h',
selected: true,
dataDownload: true,
},
]
config[:metric][:sla][:backend] = backend
config[:metric].each {|metric_key, metric_value|
metric_value[:backend].each {|metric_backend|
metric_backend[:name] = "#{metric_key}::#{metric_backend[:name]}"
}
}
config
end
end

View file

@ -0,0 +1,10 @@
class Report::Profile < ApplicationModel
self.table_name = 'report_profiles'
validates :name, presence: true
store :condition
def self.list
where(active: true)
end
end

16
config/routes/report.rb Normal file
View file

@ -0,0 +1,16 @@
Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
# reports
match api_path + '/reports/config', to: 'reports#config', via: :get
match api_path + '/reports/generate', to: 'reports#generate', via: :post
match api_path + '/reports/sets', to: 'reports#sets', via: :post
# report_profiles
match api_path + '/report_profiles', to: 'report_profiles#index', via: :get
match api_path + '/report_profiles/:id', to: 'report_profiles#show', via: :get
match api_path + '/report_profiles', to: 'report_profiles#create', via: :post
match api_path + '/report_profiles/:id', to: 'report_profiles#update', via: :put
match api_path + '/report_profiles/:id', to: 'report_profiles#destroy', via: :delete
end

View file

@ -0,0 +1,63 @@
class CreateReport < ActiveRecord::Migration
def up
create_table :report_profiles do |t|
t.column :name, :string, limit: 150, null: true
t.column :condition, :string, limit: 6000, null: true
t.column :active, :boolean, null: false, default: true
t.column :updated_by_id, :integer, null: false
t.column :created_by_id, :integer, null: false
t.timestamps null: false
end
add_index :report_profiles, [:name], unique: true
Report::Profile.create_if_not_exists(
name: '-all-',
condition: {},
active: true,
updated_by_id: 1,
created_by_id: 1,
)
Role.create_if_not_exists( name: 'Report', created_by_id: 1, updated_by_id: 1 )
Translation.create_if_not_exists(
locale: 'de-de',
source: 'Ticket Count',
target: 'Ticket Anzahl',
updated_by_id: 1,
created_by_id: 1,
)
Translation.create_if_not_exists(
locale: 'de-de',
source: 'Ticket Count',
target: 'Ticket Anzahl',
updated_by_id: 1,
created_by_id: 1,
)
Translation.create_if_not_exists(
locale: 'de-de',
source: 'Create Channels',
target: 'Erstellkanäle',
updated_by_id: 1,
created_by_id: 1,
)
Translation.create_if_not_exists(
locale: 'de-de',
source: 'Times',
target: 'Zeiten',
updated_by_id: 1,
created_by_id: 1,
)
Translation.create_if_not_exists(
locale: 'de-de',
source: 'Communication',
target: 'Kommunikation',
updated_by_id: 1,
created_by_id: 1,
)
end
def down
drop_table :report_profiles
end
end

View file

@ -44,7 +44,7 @@ create/update/delete index
end end
Rails.logger.info "# curl -X PUT \"#{url}\" \\" Rails.logger.info "# curl -X PUT \"#{url}\" \\"
#Rails.logger.info "-d '#{data[:data].to_json}'" Rails.logger.debug "-d '#{data[:data].to_json}'"
response = UserAgent.put( response = UserAgent.put(
url, url,
@ -76,7 +76,7 @@ add new object to search index
return if !url return if !url
Rails.logger.info "# curl -X POST \"#{url}\" \\" Rails.logger.info "# curl -X POST \"#{url}\" \\"
#Rails.logger.info "-d '#{data.to_json}'" Rails.logger.debug "-d '#{data.to_json}'"
response = UserAgent.post( response = UserAgent.post(
url, url,
@ -119,7 +119,7 @@ remove whole data from index
password: Setting.get('es_password'), password: Setting.get('es_password'),
} }
) )
#Rails.logger.info "# #{response.code.to_s}" Rails.logger.info "# #{response.code}"
return true if response.success? return true if response.success?
#Rails.logger.info "NOTICE: can't drop index: " + response.inspect #Rails.logger.info "NOTICE: can't drop index: " + response.inspect
false false
@ -194,7 +194,7 @@ return search result
data['query']['bool']['must'].push condition data['query']['bool']['must'].push condition
Rails.logger.info "# curl -X POST \"#{url}\" \\" Rails.logger.info "# curl -X POST \"#{url}\" \\"
#Rails.logger.info " -d'#{data.to_json}'" Rails.logger.debug " -d'#{data.to_json}'"
response = UserAgent.get( response = UserAgent.get(
url, url,
@ -232,6 +232,124 @@ return search result
=begin =begin
return aggregation result
result = SearchIndexBackend.aggs(
{
title: 'test',
state_id: 4,
},
['2014-10-19', '2015-10-19', 'created_at', 'month'],
['Ticket'],
)
# year, quarter, month, week, day, hour, minute, second
result = {
hits:{
total:4819,
},
aggregations:{
time_buckets:{
buckets:[
{
key_as_string:"2014-10-01T00:00:00.000Z",
key:1412121600000,
doc_count:420
},
{
key_as_string:"2014-11-01T00:00:00.000Z",
key:1414800000000,
doc_count:561
},
...
]
}
}
}
=end
def self.aggs(query, range, index = nil)
url = build_url()
return if !url
if index
if index.class == Array
url += "/#{index.join(',')}/_search"
else
url += "/#{index}/_search"
end
else
url += '/_search'
end
and_data = []
if query && !query.empty?
bool = {
bool: {
must: {
term: query,
},
},
}
and_data.push bool
end
range_data = {}
range_data[range[2]] = {
from: range[0],
to: range[1],
}
range_data_and = {
range: range_data,
}
and_data.push range_data_and
data = {
query: {
filtered: {
filter: {
and: and_data,
}
}
},
size: 0,
aggs: {
time_buckets: {
date_histogram: {
field: range[2],
interval: range[3],
}
}
}
}
Rails.logger.info "# curl -X POST \"#{url}\" \\"
Rails.logger.debug " -d'#{data.to_json}'"
response = UserAgent.get(
url,
data,
{
json: true,
open_timeout: 5,
read_timeout: 14,
user: Setting.get('es_user'),
password: Setting.get('es_password'),
}
)
Rails.logger.info "# #{response.code}"
if !response.success?
Rails.logger.error "ERROR: #{response.inspect}"
return []
end
Rails.logger.debug response.data.to_json
response.data
end
=begin
return true if backend is configured return true if backend is configured
result = SearchIndexBackend.enabled? result = SearchIndexBackend.enabled?