From 39bdbf22db74b61eb78f0dc535c1f9cb3988e903 Mon Sep 17 00:00:00 2001 From: Denny Bresch Date: Thu, 24 Jan 2019 09:58:58 +0100 Subject: [PATCH] Refactoring: Migrated static dashboard widgets to extendable backend structure. --- .gitlab-ci.yml | 26 ++-- .../app/controllers/_dashboard/stats.coffee | 114 +++--------------- .../stats/ticket_channel_distribution.coffee | 40 ++++++ .../_dashboard/stats/ticket_escalation.coffee | 27 +++++ .../_dashboard/stats/ticket_in_process.coffee | 28 +++++ .../stats/ticket_load_measure.coffee | 29 +++++ .../_dashboard/stats/ticket_reopen.coffee | 26 ++++ .../stats/ticket_waiting_time.coffee | 72 +++++++++++ .../app/views/dashboard/stats.jst.eco | 77 ------------ .../stats/ticket_channel_distribution.jst.eco | 17 +++ .../dashboard/stats/ticket_escalation.jst.eco | 10 ++ .../dashboard/stats/ticket_in_process.jst.eco | 10 ++ .../stats/ticket_load_measure.jst.eco | 16 +++ .../dashboard/stats/ticket_reopen.jst.eco | 10 ++ .../stats/ticket_waiting_time.jst.eco | 14 +++ .../20181023163804_add_stats_backends.rb | 98 +++++++++++++++ db/seeds/settings.rb | 90 ++++++++++++++ lib/stats.rb | 28 +++-- spec/lib/stats_spec.rb | 29 +++++ spec/system/dashboard_spec.rb | 16 +++ 20 files changed, 580 insertions(+), 197 deletions(-) create mode 100644 app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee create mode 100644 app/assets/javascripts/app/controllers/_dashboard/stats/ticket_escalation.coffee create mode 100644 app/assets/javascripts/app/controllers/_dashboard/stats/ticket_in_process.coffee create mode 100644 app/assets/javascripts/app/controllers/_dashboard/stats/ticket_load_measure.coffee create mode 100644 app/assets/javascripts/app/controllers/_dashboard/stats/ticket_reopen.coffee create mode 100644 app/assets/javascripts/app/controllers/_dashboard/stats/ticket_waiting_time.coffee delete mode 100644 app/assets/javascripts/app/views/dashboard/stats.jst.eco create mode 100644 app/assets/javascripts/app/views/dashboard/stats/ticket_channel_distribution.jst.eco create mode 100644 app/assets/javascripts/app/views/dashboard/stats/ticket_escalation.jst.eco create mode 100644 app/assets/javascripts/app/views/dashboard/stats/ticket_in_process.jst.eco create mode 100644 app/assets/javascripts/app/views/dashboard/stats/ticket_load_measure.jst.eco create mode 100644 app/assets/javascripts/app/views/dashboard/stats/ticket_reopen.jst.eco create mode 100644 app/assets/javascripts/app/views/dashboard/stats/ticket_waiting_time.jst.eco create mode 100644 db/migrate/20181023163804_add_stats_backends.rb create mode 100644 spec/lib/stats_spec.rb create mode 100644 spec/system/dashboard_spec.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7598542fa..304e5c1d4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -359,11 +359,22 @@ browser:build: - name: registry.znuny.com/docker/docker-imap-devel:latest alias: mail +## Browser core tests + +.variables_browser_template: &variables_browser_definition + RAILS_ENV: "production" + APP_RESTART_CMD: "bundle exec rake zammad:ci:app:restart" + +.test_browser_core_template: &test_browser_core_definition + <<: *base_env + stage: browser-core + dependencies: + - browser:build + ## Capybara .test_capybara_template: &test_capybara_definition - <<: *base_env - stage: browser-core + <<: *test_browser_core_definition script: - bundle exec rake zammad:ci:test:prepare[with_elasticsearch] - bundle exec rspec --fail-fast -t type:system @@ -396,17 +407,6 @@ test:browser:core:capybara_ff_mysql: <<: *variables_capybara_ff_definition <<: *services_browser_mysql_definition -## Browser core tests - -.variables_browser_template: &variables_browser_definition - RAILS_ENV: "production" - APP_RESTART_CMD: "bundle exec rake zammad:ci:app:restart" - -.test_browser_core_template: &test_browser_core_definition - <<: *base_env - stage: browser-core - dependencies: - - browser:build ### API clients diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats.coffee index 3c138bc06..d4a672c16 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/stats.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/stats.coffee @@ -5,103 +5,23 @@ class App.DashboardStats extends App.Controller @bind('dashboard_stats_rebuild', @load) load: => - stats_store = App.StatsStore.first() - if stats_store - @render(stats_store.data) - else - @render() + @setupStatsWidget('Stats', 'stats', @el) - render: (data = {}) -> - if !data.StatsTicketWaitingTime - data.StatsTicketWaitingTime = - handling_time: 0 - average: 0 - state: 'supergood' - average_per_agent: 0 - if !data.StatsTicketEscalation - data.StatsTicketEscalation = - state: 'supergood' - own: 0 - total: 0 - if !data.StatsTicketChannelDistribution - data.StatsTicketChannelDistribution = - channels: - 1: - inbound: 0 - outbound: 0 - inbound_in_percent: 0 - outbound_in_percent: 0 - 2: - inbound: 0 - outbound: 0 - inbound_in_percent: 0 - outbound_in_percent: 0 - 3: - inbound: 0 - outbound: 0 - inbound_in_percent: 0 - outbound_in_percent: 0 - if !data.StatsTicketLoadMeasure - data.StatsTicketLoadMeasure = - state: 'supergood' - percent: 0 - own: 0 - total: 0 - average_per_agent: 0 - if !data.StatsTicketInProcess - data.StatsTicketInProcess = - state: 'supergood' - percent: 0 - average_per_agent: 0 - if !data.StatsTicketReopen - data.StatsTicketReopen = - state: 'supergood' - percent: 0 - average_per_agent: 0 + setupStatsWidget: (config, event, el) -> - @html App.view('dashboard/stats')(data) + # load all statsWidgets ./stats/* + App.Event.trigger(event + ':init') + statsWidgets = App.Config.get(config) + if statsWidgets + widgets = $.map(statsWidgets, (v) -> v ) + widgets = _.sortBy(widgets, (item) -> return item.prio) - if data.StatsTicketWaitingTime - @renderWidgetClockFace(data.StatsTicketWaitingTime.handling_time, data.StatsTicketWaitingTime.state, data.StatsTicketWaitingTime.percent) - - renderWidgetClockFace: (time, state, percent) => - canvas = @el.find 'canvas' - ctx = canvas.get(0).getContext '2d' - radius = 26 - - @el.find('.time.stat-widget .stat-amount').text time - - canvas.attr 'width', 2 * radius - canvas.attr 'height', 2 * radius - - handlingTimeColors = {} - handlingTimeColors['supergood'] = '#38AE6A' # supergood - handlingTimeColors['good'] = '#A9AC41' # good - handlingTimeColors['ok'] = '#FAAB00' # ok - handlingTimeColors['bad'] = '#F6820B' # bad - handlingTimeColors['superbad'] = '#F35910' # superbad - - for handlingState, timeColor of handlingTimeColors - if state == handlingState - backgroundColor = timeColor - break - - # 30% background - if time isnt 0 - ctx.globalAlpha = 0.3 - ctx.fillStyle = backgroundColor - ctx.beginPath() - ctx.arc radius, radius, radius, 0, Math.PI * 2, true - ctx.closePath() - ctx.fill() - - # 100% pie piece - ctx.globalAlpha = 1 - - ctx.beginPath() - ctx.moveTo radius, radius - arcsector = Math.PI * 2 * percent - ctx.arc radius, radius, radius, -Math.PI/2, arcsector - Math.PI/2, false - ctx.lineTo radius, radius - ctx.closePath() - ctx.fill() + for widget in widgets + if @permissionCheck(widget.permission) + try + new widget.controller( + el: el + ) + catch e + @log 'error', "statsWidgets #{key}:", e + App.Event.trigger(event + ':ready') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee new file mode 100644 index 000000000..47e8e752e --- /dev/null +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee @@ -0,0 +1,40 @@ +class Stats extends App.Controller + constructor: -> + super + @load() + + load: => + stats_store = App.StatsStore.first() + if stats_store + @render(stats_store.data) + else + @render() + + render: (data = {}) -> + if !data.StatsTicketChannelDistribution + data.StatsTicketChannelDistribution = + channels: + 1: + inbound: 1 + outbound: 0 + inbound_in_percent: 0 + outbound_in_percent: 0 + 2: + inbound: 0 + outbound: 0 + inbound_in_percent: 0 + outbound_in_percent: 0 + 3: + inbound: 2 + outbound: 0 + inbound_in_percent: 0 + outbound_in_percent: 0 + + content = App.view('dashboard/stats/ticket_channel_distribution')(data) + + if @$('.ticket_channel_distribution').length > 0 + @$('.ticket_channel_distribution').html(content) + else + @el.append(content) + +App.Config.set('ticket_channel_distribution', {controller: Stats, permission: 'ticket.agent', prio: 300 }, 'Stats') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_escalation.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_escalation.coffee new file mode 100644 index 000000000..6d29c02cb --- /dev/null +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_escalation.coffee @@ -0,0 +1,27 @@ +class Stats extends App.Controller + constructor: -> + super + @load() + + load: => + stats_store = App.StatsStore.first() + if stats_store + @render(stats_store.data) + else + @render() + + render: (data = {}) -> + if !data.StatsTicketEscalation + data.StatsTicketEscalation = + state: 'supergood' + own: 0 + total: 0 + + content = App.view('dashboard/stats/ticket_escalation')(data) + + if @$('.ticket_escalation').length > 0 + @$('.ticket_escalation').html(content) + else + @el.append(content) + +App.Config.set('ticket_escalation', {controller: Stats, permission: 'ticket.agent', prio: 200 }, 'Stats') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_in_process.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_in_process.coffee new file mode 100644 index 000000000..e15cacf0b --- /dev/null +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_in_process.coffee @@ -0,0 +1,28 @@ +class Stats extends App.Controller + constructor: -> + super + @load() + + load: => + stats_store = App.StatsStore.first() + if stats_store + @render(stats_store.data) + else + @render() + + render: (data = {}) -> + if !data.StatsTicketInProcess + data.StatsTicketInProcess = + state: 'supergood' + percent: 0 + average_per_agent: 0 + + content = App.view('dashboard/stats/ticket_in_process')(data) + + if @$('.ticket_in_process').length > 0 + @$('.ticket_in_process').html(content) + else + @el.append(content) + + +App.Config.set('ticket_in_process', {controller: Stats, permission: 'ticket.agent', prio: 500 }, 'Stats') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_load_measure.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_load_measure.coffee new file mode 100644 index 000000000..74b8112ed --- /dev/null +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_load_measure.coffee @@ -0,0 +1,29 @@ +class Stats extends App.Controller + constructor: -> + super + @load() + + load: => + stats_store = App.StatsStore.first() + if stats_store + @render(stats_store.data) + else + @render() + + render: (data = {}) -> + if !data.StatsTicketLoadMeasure + data.StatsTicketLoadMeasure = + state: 'supergood' + percent: 0 + own: 0 + total: 0 + average_per_agent: 0 + + content = App.view('dashboard/stats/ticket_load_measure')(data) + + if @$('.ticket_load_measure').length > 0 + @$('.ticket_load_measure').html(content) + else + @el.append(content) + +App.Config.set('ticket_load_measure', {controller: Stats, permission: 'ticket.agent', prio: 400 }, 'Stats') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_reopen.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_reopen.coffee new file mode 100644 index 000000000..1a6b845dd --- /dev/null +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_reopen.coffee @@ -0,0 +1,26 @@ +class Stats extends App.Controller + constructor: -> + super + @load() + + load: => + stats_store = App.StatsStore.first() + if stats_store + @render(stats_store.data) + else + @render() + + render: (data = {}) -> + if !data.StatsTicketReopen + data.StatsTicketReopen = + state: 'supergood' + percent: 0 + average_per_agent: 0 + + content = App.view('dashboard/stats/ticket_reopen')(data) + if @$('.ticket_reopen').length > 0 + @$('.ticket_reopen').html(content) + else + @el.append(content) + +App.Config.set('ticket_reopen', {controller: Stats, permission: 'ticket.agent', prio: 600 }, 'Stats') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_waiting_time.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_waiting_time.coffee new file mode 100644 index 000000000..c6e0f85f8 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_waiting_time.coffee @@ -0,0 +1,72 @@ +class Stats extends App.Controller + constructor: -> + super + @load() + + load: => + stats_store = App.StatsStore.first() + if stats_store + @render(stats_store.data) + else + @render() + + render: (data = {}) -> + if !data.StatsTicketWaitingTime + data.StatsTicketWaitingTime = + handling_time: 0 + average: 0 + state: 'supergood' + average_per_agent: 0 + + content = App.view('dashboard/stats/ticket_waiting_time')(data) + if @$('.ticket_waiting_time').length > 0 + @$('.ticket_waiting_time').html(content) + else + @el.append(content) + + if data.StatsTicketWaitingTime + @renderWidgetClockFace(data.StatsTicketWaitingTime.handling_time, data.StatsTicketWaitingTime.state, data.StatsTicketWaitingTime.percent) + + renderWidgetClockFace: (time, state, percent) => + canvas = @el.find 'canvas' + ctx = canvas.get(0).getContext '2d' + radius = 26 + + @el.find('.time.stat-widget .stat-amount').text time + + canvas.attr 'width', 2 * radius + canvas.attr 'height', 2 * radius + + handlingTimeColors = {} + handlingTimeColors['supergood'] = '#38AE6A' # supergood + handlingTimeColors['good'] = '#A9AC41' # good + handlingTimeColors['ok'] = '#FAAB00' # ok + handlingTimeColors['bad'] = '#F6820B' # bad + handlingTimeColors['superbad'] = '#F35910' # superbad + + for handlingState, timeColor of handlingTimeColors + if state == handlingState + backgroundColor = timeColor + break + + # 30% background + if time isnt 0 + ctx.globalAlpha = 0.3 + ctx.fillStyle = backgroundColor + ctx.beginPath() + ctx.arc radius, radius, radius, 0, Math.PI * 2, true + ctx.closePath() + ctx.fill() + + # 100% pie piece + ctx.globalAlpha = 1 + + ctx.beginPath() + ctx.moveTo radius, radius + arcsector = Math.PI * 2 * percent + ctx.arc radius, radius, radius, -Math.PI/2, arcsector - Math.PI/2, false + ctx.lineTo radius, radius + ctx.closePath() + ctx.fill() + +App.Config.set('ticket_waiting_time', {controller: Stats, permission: 'ticket.agent', prio: 100 }, 'Stats') diff --git a/app/assets/javascripts/app/views/dashboard/stats.jst.eco b/app/assets/javascripts/app/views/dashboard/stats.jst.eco deleted file mode 100644 index 940f38c1d..000000000 --- a/app/assets/javascripts/app/views/dashboard/stats.jst.eco +++ /dev/null @@ -1,77 +0,0 @@ -
-
-
<%- @T('∅ Waiting time today') %>
-
-
- <%- @Icon('stopwatch', 'stat-icon stopwatch-icon') %> - -
-
-
-
<%- @T('My handling time: %s minutes', @StatsTicketWaitingTime.handling_time) %>
-
<%- @T('Average: %s minutes', @StatsTicketWaitingTime.average_per_agent) %>
-
-
-
-
-
<%- @T('Mood') %>
-
- <%- @Icon("mood-#{@StatsTicketEscalation.state}", 'stat-icon mood-icon') %> -
-
<%- @T('%s of my tickets escalated.', @StatsTicketEscalation.own) %>
-
<%- @T('Total: %s', @StatsTicketEscalation.total) %>
-
-
-
-
-
<%- @T('Channel Distribution') %>
-
- <% for channel_name, channel of @StatsTicketChannelDistribution.channels: %> - - <% end %> -
-
-
-
-
-
<%- @T('Assigned') %>
-
-
- <% stack_counter = parseInt(@StatsTicketLoadMeasure.percent*0.16) %> - <% for count in [1..stack_counter]: %> - <%- @Icon('one-ticket', "one-ticket #{@StatsTicketLoadMeasure.state}-color") %> - <% end %> -
- <%- @Icon('total-tickets', 'total-tickets') %> -
-
<%- @T('Tickets assigned to me: %s of %s', @StatsTicketLoadMeasure.own, @StatsTicketLoadMeasure.total) %>
-
<%- @T('Average: %s', @StatsTicketLoadMeasure.average_per_agent) %>
-
-
-
-
-
<%- @T('Your tickets in process') %>
-
- <%- @Icon('in-process', "in-process-icon #{@StatsTicketInProcess.state}-color") %> -
-
<%- @T('%s% are currently in process', @StatsTicketInProcess.percent) %>
-
<%- @T('Average: %s%', @StatsTicketInProcess.average_per_agent) %>
-
-
-
-
-
<%- @T('Reopening rate') %>
-
- <%- @Icon('reopening', "reopening-icon #{@StatsTicketReopen.state}-color") %> -
-
<%- @T('%s% have been reopened', @StatsTicketReopen.percent) %>
-
<%- @T('Average: %s%', @StatsTicketReopen.average_per_agent) %>
-
-
diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_channel_distribution.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_channel_distribution.jst.eco new file mode 100644 index 000000000..2ed22d9e8 --- /dev/null +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_channel_distribution.jst.eco @@ -0,0 +1,17 @@ +
+
+
<%- @T('Channel Distribution') %>
+
+ <% for channel_name, channel of @StatsTicketChannelDistribution.channels: %> + + <% end %> +
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_escalation.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_escalation.jst.eco new file mode 100644 index 000000000..3495ef1b1 --- /dev/null +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_escalation.jst.eco @@ -0,0 +1,10 @@ +
+
+
<%- @T('Mood') %>
+
+ <%- @Icon("mood-#{@StatsTicketEscalation.state}", 'stat-icon mood-icon') %> +
+
<%- @T('%s of my tickets escalated.', @StatsTicketEscalation.own) %>
+
<%- @T('Total: %s', @StatsTicketEscalation.total) %>
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_in_process.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_in_process.jst.eco new file mode 100644 index 000000000..030320fb2 --- /dev/null +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_in_process.jst.eco @@ -0,0 +1,10 @@ +
+
+
<%- @T('Your tickets in process') %>
+
+ <%- @Icon('in-process', "in-process-icon #{@StatsTicketInProcess.state}-color") %> +
+
<%- @T('%s% are currently in process', @StatsTicketInProcess.percent) %>
+
<%- @T('Average: %s%', @StatsTicketInProcess.average_per_agent) %>
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_load_measure.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_load_measure.jst.eco new file mode 100644 index 000000000..fe3650dd3 --- /dev/null +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_load_measure.jst.eco @@ -0,0 +1,16 @@ +
+
+
<%- @T('Assigned') %>
+
+
+ <% stack_counter = parseInt(@StatsTicketLoadMeasure.percent*0.16) %> + <% for count in [1..stack_counter]: %> + <%- @Icon('one-ticket', "one-ticket #{@StatsTicketLoadMeasure.state}-color") %> + <% end %> +
+ <%- @Icon('total-tickets', 'total-tickets') %> +
+
<%- @T('Tickets assigned to me: %s of %s', @StatsTicketLoadMeasure.own, @StatsTicketLoadMeasure.total) %>
+
<%- @T('Average: %s', @StatsTicketLoadMeasure.average_per_agent) %>
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_reopen.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_reopen.jst.eco new file mode 100644 index 000000000..296399bb8 --- /dev/null +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_reopen.jst.eco @@ -0,0 +1,10 @@ +
+
+
<%- @T('Reopening rate') %>
+
+ <%- @Icon('reopening', "reopening-icon #{@StatsTicketReopen.state}-color") %> +
+
<%- @T('%s% have been reopened', @StatsTicketReopen.percent) %>
+
<%- @T('Average: %s%', @StatsTicketReopen.average_per_agent) %>
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_waiting_time.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_waiting_time.jst.eco new file mode 100644 index 000000000..de9277781 --- /dev/null +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_waiting_time.jst.eco @@ -0,0 +1,14 @@ +
+
+
<%- @T('∅ Waiting time today') %>
+
+
+ <%- @Icon('stopwatch', 'stat-icon stopwatch-icon') %> + +
+
+
+
<%- @T('My handling time: %s minutes', @StatsTicketWaitingTime.handling_time) %>
+
<%- @T('Average: %s minutes', @StatsTicketWaitingTime.average_per_agent) %>
+
+
\ No newline at end of file diff --git a/db/migrate/20181023163804_add_stats_backends.rb b/db/migrate/20181023163804_add_stats_backends.rb new file mode 100644 index 000000000..b46c2da17 --- /dev/null +++ b/db/migrate/20181023163804_add_stats_backends.rb @@ -0,0 +1,98 @@ +class AddStatsBackends < ActiveRecord::Migration[5.1] + + def up + + return if !Setting.find_by(name: 'system_init_done') + + # add the dashboard stats backend for 'Stats::TicketWaitingTime' + Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketWaitingTime', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketWaitingTime', + preferences: { + permission: ['ticket.agent'], + prio: 1, + }, + frontend: false + ) + + # add the dashboard stats backend for 'Stats::TicketEscalation' + Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketEscalation', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketEscalation', + preferences: { + permission: ['ticket.agent'], + prio: 2, + }, + frontend: false + ) + + # add the dashboard stats backend for 'Stats::TicketChannelDistribution' + Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketChannelDistribution', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketChannelDistribution', + preferences: { + permission: ['ticket.agent'], + prio: 3, + }, + frontend: false + ) + + # add the dashboard stats backend for 'Stats::TicketLoadMeasure' + Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketLoadMeasure', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketLoadMeasure', + preferences: { + permission: ['ticket.agent'], + prio: 4, + }, + frontend: false + ) + + # add the dashboard stats backend for 'Stats::TicketInProcess' + Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketInProcess', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketInProcess', + preferences: { + permission: ['ticket.agent'], + prio: 5, + }, + frontend: false + ) + + # add the dashboard stats backend for 'Stats::TicketReopen' + Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketReopen', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketReopen', + preferences: { + permission: ['ticket.agent'], + prio: 6, + }, + frontend: false + ) + + end +end diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index 8fe8d4d84..6d90cafb9 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -4225,3 +4225,93 @@ Setting.create_if_not_exists( }, frontend: false ) + +# add the dashboard stats backend for 'Stats::TicketWaitingTime' +Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketWaitingTime', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketWaitingTime', + preferences: { + permission: ['ticket.agent'], + prio: 1, + }, + frontend: false +) + +# add the dashboard stats backend for 'Stats::TicketEscalation' +Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketEscalation', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketEscalation', + preferences: { + permission: ['ticket.agent'], + prio: 2, + }, + frontend: false +) + +# add the dashboard stats backend for 'Stats::TicketChannelDistribution' +Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketChannelDistribution', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketChannelDistribution', + preferences: { + permission: ['ticket.agent'], + prio: 3, + }, + frontend: false +) + +# add the dashboard stats backend for 'Stats::TicketLoadMeasure' +Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketLoadMeasure', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketLoadMeasure', + preferences: { + permission: ['ticket.agent'], + prio: 4, + }, + frontend: false +) + +# add the dashboard stats backend for 'Stats::TicketInProcess' +Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketInProcess', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketInProcess', + preferences: { + permission: ['ticket.agent'], + prio: 5, + }, + frontend: false +) + +# add the dashboard stats backend for 'Stats::TicketReopen' +Setting.create_if_not_exists( + title: 'Stats Backend', + name: 'Stats::TicketReopen', + area: 'Dashboard::Stats', + description: 'Defines a dashboard stats backend that get scheduled automatically.', + options: {}, + state: 'Stats::TicketReopen', + preferences: { + permission: ['ticket.agent'], + prio: 6, + }, + frontend: false +) diff --git a/lib/stats.rb b/lib/stats.rb index 5ad4f7075..5050f676b 100644 --- a/lib/stats.rb +++ b/lib/stats.rb @@ -15,15 +15,6 @@ returns def self.generate - backends = [ - Stats::TicketChannelDistribution, - Stats::TicketInProcess, - Stats::TicketLoadMeasure, - Stats::TicketEscalation, - Stats::TicketReopen, - Stats::TicketWaitingTime, - ] - # generate stats per agent users = User.with_permissions('ticket.agent') agent_count = 0 @@ -34,7 +25,24 @@ returns agent_count += 1 data = {} - backends.each do |backend| + + backends = Setting.where(area: 'Dashboard::Stats') + if backends.blank? + raise "No settings with area 'Dashboard::Stats' defined" + end + + backends.each do |stats_item| + # additional permission check + next if stats_item.preferences[:permission] && !user.permissions?(stats_item.preferences[:permission]) + + backend = stats_item.state_current[:value] + if !backend + raise 'Dashboard::Stats backend ' + stats_item.name + ' is not defined' + end + + require_dependency backend.to_filename + backend = backend.constantize + data[backend] = backend.generate(user) end user_result[user.id] = data diff --git a/spec/lib/stats_spec.rb b/spec/lib/stats_spec.rb new file mode 100644 index 000000000..5a74f7d3b --- /dev/null +++ b/spec/lib/stats_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +RSpec.describe Stats do + + describe '#generate' do + + before do + # create a user for which the stats can be generated + create(:agent_user) + end + + it 'generates stats' do + expect { Stats.generate }.to_not raise_error + end + + context 'when backend registration is invalid' do + + it 'fails for empty registration' do + Setting.set('Stats::TicketWaitingTime', nil) + expect { Stats.generate }.to raise_error(RuntimeError) + end + + it 'fails for unknown backend' do + Setting.set('Stats::TicketWaitingTime', 'Stats::UNKNOWN') + expect { Stats.generate }.to raise_error(LoadError) + end + end + end +end diff --git a/spec/system/dashboard_spec.rb b/spec/system/dashboard_spec.rb new file mode 100644 index 000000000..d6e359a57 --- /dev/null +++ b/spec/system/dashboard_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +RSpec.describe 'Dashboard', type: :system, authenticated: true do + + it 'shows default widgets' do + visit 'dashboard' + + expect(page).to have_css('.stat-widgets') + expect(page).to have_css('.ticket_waiting_time > div > div.stat-title', text: /∅ Waiting time today/i) + expect(page).to have_css('.ticket_escalation > div > div.stat-title', text: /Mood/i) + expect(page).to have_css('.ticket_channel_distribution > div > div.stat-title', text: /Channel Distribution/i) + expect(page).to have_css('.ticket_load_measure > div > div.stat-title', text: /Assigned/i) + expect(page).to have_css('.ticket_in_process > div > div.stat-title', text: /Your tickets in process/i) + expect(page).to have_css('.ticket_reopen > div > div.stat-title', text: /Reopening rate/i) + end +end