Init version.
This commit is contained in:
parent
5327445d44
commit
76637f55a2
17 changed files with 1457 additions and 5 deletions
488
app/assets/javascripts/app/controllers/report.js.coffee
Normal file
488
app/assets/javascripts/app/controllers/report.js.coffee
Normal 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' )
|
|
@ -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' )
|
14
app/assets/javascripts/app/models/report_profile.js.coffee
Normal file
14
app/assets/javascripts/app/models/report_profile.js.coffee
Normal 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',
|
||||||
|
]
|
|
@ -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>
|
|
@ -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>
|
29
app/assets/javascripts/app/views/report/main.jst.eco
Normal file
29
app/assets/javascripts/app/views/report/main.jst.eco
Normal 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>
|
33
app/assets/javascripts/app/views/report/sidebar.jst.eco
Normal file
33
app/assets/javascripts/app/views/report/sidebar.jst.eco
Normal 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>
|
36
app/assets/javascripts/app/views/report/time_picker.jst.eco
Normal file
36
app/assets/javascripts/app/views/report/time_picker.jst.eco
Normal 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>
|
|
@ -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>
|
|
@ -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
|
||||||
|
|
141
app/controllers/report_profiles_controller.rb
Normal file
141
app/controllers/report_profiles_controller.rb
Normal 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
|
152
app/controllers/reports_controller.rb
Normal file
152
app/controllers/reports_controller.rb
Normal 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
278
app/models/report.rb
Normal 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
|
10
app/models/report/profile.rb
Normal file
10
app/models/report/profile.rb
Normal 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
16
config/routes/report.rb
Normal 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
|
63
db/migrate/20151019000001_create_report.rb
Normal file
63
db/migrate/20151019000001_create_report.rb
Normal 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
|
|
@ -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?
|
||||||
|
|
Loading…
Reference in a new issue