commit
6126641dc0
15 changed files with 66 additions and 76 deletions
1
Gemfile
1
Gemfile
|
@ -26,7 +26,6 @@ gem 'delayed_job_web'
|
||||||
gem 'devise' # Gestion des comptes utilisateurs
|
gem 'devise' # Gestion des comptes utilisateurs
|
||||||
gem 'devise-async'
|
gem 'devise-async'
|
||||||
gem 'dotenv-rails', require: 'dotenv/rails-now' # dotenv should always be loaded before rails
|
gem 'dotenv-rails', require: 'dotenv/rails-now' # dotenv should always be loaded before rails
|
||||||
gem 'flipflop'
|
|
||||||
gem 'flipper'
|
gem 'flipper'
|
||||||
gem 'flipper-active_record'
|
gem 'flipper-active_record'
|
||||||
gem 'flipper-ui'
|
gem 'flipper-ui'
|
||||||
|
|
|
@ -220,8 +220,6 @@ GEM
|
||||||
faraday (0.15.4)
|
faraday (0.15.4)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
ffi (1.9.25)
|
ffi (1.9.25)
|
||||||
flipflop (2.4.0)
|
|
||||||
activesupport (>= 4.0)
|
|
||||||
flipper (0.16.2)
|
flipper (0.16.2)
|
||||||
flipper-active_record (0.16.2)
|
flipper-active_record (0.16.2)
|
||||||
activerecord (>= 3.2, < 6)
|
activerecord (>= 3.2, < 6)
|
||||||
|
@ -733,7 +731,6 @@ DEPENDENCIES
|
||||||
devise-async
|
devise-async
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
factory_bot
|
factory_bot
|
||||||
flipflop
|
|
||||||
flipper
|
flipper
|
||||||
flipper-active_record
|
flipper-active_record
|
||||||
flipper-ui
|
flipper-ui
|
||||||
|
|
|
@ -13,7 +13,6 @@ class StatsController < ApplicationController
|
||||||
@satisfaction_usagers = satisfaction_usagers
|
@satisfaction_usagers = satisfaction_usagers
|
||||||
|
|
||||||
@contact_percentage = contact_percentage
|
@contact_percentage = contact_percentage
|
||||||
@contact_percentage_excluded_tags = Helpscout::UserConversationsAdapter::EXCLUDED_TAGS
|
|
||||||
|
|
||||||
@dossiers_states = dossiers_states
|
@dossiers_states = dossiers_states
|
||||||
|
|
||||||
|
@ -177,7 +176,7 @@ class StatsController < ApplicationController
|
||||||
.map do |monthly_report|
|
.map do |monthly_report|
|
||||||
start_date = monthly_report[:start_date].to_time.localtime
|
start_date = monthly_report[:start_date].to_time.localtime
|
||||||
end_date = monthly_report[:end_date].to_time.localtime
|
end_date = monthly_report[:end_date].to_time.localtime
|
||||||
replies_count = monthly_report[:conversations_count]
|
replies_count = monthly_report[:replies_sent]
|
||||||
|
|
||||||
dossiers_count = Dossier.where(en_construction_at: start_date..end_date).count
|
dossiers_count = Dossier.where(en_construction_at: start_date..end_date).count
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Helpscout::API
|
||||||
body = {
|
body = {
|
||||||
subject: subject,
|
subject: subject,
|
||||||
customer: customer(email),
|
customer: customer(email),
|
||||||
mailboxId: mailbox_id,
|
mailboxId: user_support_mailbox_id,
|
||||||
type: 'email',
|
type: 'email',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
threads: [
|
threads: [
|
||||||
|
@ -44,7 +44,7 @@ class Helpscout::API
|
||||||
|
|
||||||
def add_phone_number(email, phone)
|
def add_phone_number(email, phone)
|
||||||
query = URI.encode("(email:#{email})")
|
query = URI.encode("(email:#{email})")
|
||||||
response = call_api(:get, "#{CUSTOMERS}?mailbox=#{mailbox_id}&query=#{query}")
|
response = call_api(:get, "#{CUSTOMERS}?mailbox=#{user_support_mailbox_id}&query=#{query}")
|
||||||
if response.success?
|
if response.success?
|
||||||
body = parse_response_body(response)
|
body = parse_response_body(response)
|
||||||
if body[:page][:totalElements] > 0
|
if body[:page][:totalElements] > 0
|
||||||
|
@ -57,17 +57,18 @@ class Helpscout::API
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversations_report(year, month)
|
def productivity_report(year, month)
|
||||||
Rails.logger.info("[HelpScout API] Retrieving conversations report for #{month}-#{year}…")
|
Rails.logger.info("[HelpScout API] Retrieving productivity report for #{month}-#{year}…")
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
mailboxes: [user_support_mailbox_id].join(','),
|
||||||
start: Time.utc(year, month).iso8601,
|
start: Time.utc(year, month).iso8601,
|
||||||
end: Time.utc(year, month).next_month.iso8601
|
end: Time.utc(year, month).next_month.iso8601
|
||||||
}
|
}
|
||||||
|
|
||||||
response = call_api(:get, 'reports/conversations?' + params.to_query)
|
response = call_api(:get, 'reports/productivity?' + params.to_query)
|
||||||
if !response.success?
|
if !response.success?
|
||||||
raise StandardError, "Error while fetching conversation report: #{response.response_code} '#{response.body}'"
|
raise StandardError, "Error while fetching productivity report: #{response.response_code} '#{response.body}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
parse_response_body(response)
|
parse_response_body(response)
|
||||||
|
@ -107,7 +108,7 @@ class Helpscout::API
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_custom_fields
|
def fetch_custom_fields
|
||||||
call_api(:get, "#{MAILBOXES}/#{mailbox_id}/#{FIELDS}")
|
call_api(:get, "#{MAILBOXES}/#{user_support_mailbox_id}/#{FIELDS}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def call_api(method, path, body = nil)
|
def call_api(method, path, body = nil)
|
||||||
|
@ -135,7 +136,7 @@ class Helpscout::API
|
||||||
JSON.parse(response.body, symbolize_names: true)
|
JSON.parse(response.body, symbolize_names: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def mailbox_id
|
def user_support_mailbox_id
|
||||||
Rails.application.secrets.helpscout[:mailbox_id]
|
Rails.application.secrets.helpscout[:mailbox_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
# Fetch and compute monthly reports about the users conversations on Helpscout
|
# Fetch and compute monthly reports about the users conversations on Helpscout
|
||||||
class Helpscout::UserConversationsAdapter
|
class Helpscout::UserConversationsAdapter
|
||||||
EXCLUDED_TAGS = ['openlab', 'bizdev', 'admin', 'webinaire']
|
|
||||||
|
|
||||||
def initialize(from, to)
|
def initialize(from, to)
|
||||||
@from = from
|
@from = from
|
||||||
@to = to
|
@to = to
|
||||||
|
@ -23,19 +21,12 @@ class Helpscout::UserConversationsAdapter
|
||||||
private
|
private
|
||||||
|
|
||||||
def report(year, month)
|
def report(year, month)
|
||||||
report = fetch_conversations_report(year, month)
|
report = fetch_productivity_report(year, month)
|
||||||
|
|
||||||
total_conversations = report.dig(:current, :totalConversations)
|
|
||||||
excluded_conversations = report
|
|
||||||
.dig(:tags, :top)
|
|
||||||
.select { |tag| tag[:name]&.in?(EXCLUDED_TAGS) }
|
|
||||||
.map { |tag| tag[:count] }
|
|
||||||
.sum
|
|
||||||
|
|
||||||
{
|
{
|
||||||
start_date: report.dig(:current, :startDate).to_datetime,
|
start_date: report.dig(:current, :startDate).to_datetime,
|
||||||
end_date: report.dig(:current, :endDate).to_datetime,
|
end_date: report.dig(:current, :endDate).to_datetime,
|
||||||
conversations_count: total_conversations - excluded_conversations
|
replies_sent: report.dig(:current, :repliesSent)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -43,13 +34,13 @@ class Helpscout::UserConversationsAdapter
|
||||||
@api_client ||= Helpscout::API.new
|
@api_client ||= Helpscout::API.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_conversations_report(year, month)
|
def fetch_productivity_report(year, month)
|
||||||
if year == Date.today.year && month == Date.today.month
|
if year == Date.today.year && month == Date.today.month
|
||||||
raise ArgumentError, 'The report for the current month will change in the future, and cannot be cached.'
|
raise ArgumentError, 'The report for the current month will change in the future, and cannot be cached.'
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.cache.fetch("helpscout-conversation-report-#{year}-#{month}") do
|
Rails.cache.fetch("helpscout-productivity-report-#{year}-#{month}") do
|
||||||
api_client.conversations_report(year, month)
|
api_client.productivity_report(year, month)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
class Administrateur < ApplicationRecord
|
class Administrateur < ApplicationRecord
|
||||||
|
self.ignored_columns = ['features']
|
||||||
|
|
||||||
include EmailSanitizableConcern
|
include EmailSanitizableConcern
|
||||||
include ActiveRecord::SecureToken
|
include ActiveRecord::SecureToken
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
class Instructeur < ApplicationRecord
|
class Instructeur < ApplicationRecord
|
||||||
|
self.ignored_columns = ['features']
|
||||||
include EmailSanitizableConcern
|
include EmailSanitizableConcern
|
||||||
|
|
||||||
has_and_belongs_to_many :administrateurs
|
has_and_belongs_to_many :administrateurs
|
||||||
|
|
|
@ -31,7 +31,6 @@ class AdministrateurUsageStatisticsService
|
||||||
ds_created_at: administrateur.created_at,
|
ds_created_at: administrateur.created_at,
|
||||||
ds_active: administrateur.active,
|
ds_active: administrateur.active,
|
||||||
ds_id: administrateur.id,
|
ds_id: administrateur.id,
|
||||||
ds_features: administrateur.features.to_json,
|
|
||||||
nb_services: nb_services_by_administrateur_id[administrateur.id],
|
nb_services: nb_services_by_administrateur_id[administrateur.id],
|
||||||
nb_instructeurs: nb_instructeurs_by_administrateur_id[administrateur.id],
|
nb_instructeurs: nb_instructeurs_by_administrateur_id[administrateur.id],
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
%li
|
%li
|
||||||
%object
|
%object
|
||||||
= link_to(instructeur_procedure_path(p, statut: 'suivis')) do
|
= link_to(instructeur_procedure_path(p, statut: 'suivis')) do
|
||||||
- if current_instructeur.notifications_per_procedure[p.defaut_groupe_instructeur.id].present?
|
- if current_instructeur.notifications_per_procedure[p.id].present?
|
||||||
%span.notifications{ 'aria-label': "notifications" }
|
%span.notifications{ 'aria-label': "notifications" }
|
||||||
- followed_count = @followed_dossiers_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0
|
- followed_count = @followed_dossiers_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0
|
||||||
.stats-number
|
.stats-number
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
%li
|
%li
|
||||||
%object
|
%object
|
||||||
= link_to(instructeur_procedure_path(p, statut: 'traites')) do
|
= link_to(instructeur_procedure_path(p, statut: 'traites')) do
|
||||||
- if current_instructeur.notifications_per_procedure(:termine)[p.defaut_groupe_instructeur.id].present?
|
- if current_instructeur.notifications_per_procedure(:termine)[p.id].present?
|
||||||
%span.notifications{ 'aria-label': "notifications" }
|
%span.notifications{ 'aria-label': "notifications" }
|
||||||
- termines_count = @dossiers_termines_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0
|
- termines_count = @dossiers_termines_count_per_groupe_instructeur[p.defaut_groupe_instructeur.id] || 0
|
||||||
.stats-number
|
.stats-number
|
||||||
|
|
|
@ -34,16 +34,15 @@
|
||||||
|
|
||||||
.stat-card.stat-card-half.pull-left
|
.stat-card.stat-card-half.pull-left
|
||||||
%span.stat-card-title
|
%span.stat-card-title
|
||||||
Pourcentage de contact usager
|
Pourcentage de contact utilisateur
|
||||||
|
|
||||||
.chart-container
|
.chart-container
|
||||||
.chart
|
.chart
|
||||||
= line_chart @contact_percentage
|
= line_chart @contact_percentage
|
||||||
|
|
||||||
.stat-card-details
|
.stat-card-details
|
||||||
%abbr{ title: "À l’exception des conversations taggées #{@contact_percentage_excluded_tags.join(', ')}" }
|
Nombre de réponses envoyées via HelpScout
|
||||||
Nombre de conversations actives dans HelpScout
|
\/ nombre de dossiers déposés
|
||||||
%span / nombre de dossiers déposés
|
|
||||||
|
|
||||||
.stat-card.stat-card-half.pull-left
|
.stat-card.stat-card-half.pull-left
|
||||||
%span.stat-card-title
|
%span.stat-card-title
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -15,9 +15,7 @@ describe Helpscout::UserConversationsAdapter do
|
||||||
|
|
||||||
context 'when all required secrets are present' do
|
context 'when all required secrets are present' do
|
||||||
before do
|
before do
|
||||||
Rails.application.secrets.helpscout[:mailbox_id] = '9999'
|
mock_helpscout_secrets
|
||||||
Rails.application.secrets.helpscout[:client_id] = '1234'
|
|
||||||
Rails.application.secrets.helpscout[:client_secret] = '5678'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(described_class.new(from, to).can_fetch_reports?).to be true }
|
it { expect(described_class.new(from, to).can_fetch_reports?).to be true }
|
||||||
|
@ -25,7 +23,10 @@ describe Helpscout::UserConversationsAdapter do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#reports', vcr: { cassette_name: 'helpscout_conversations_reports' } do
|
describe '#reports', vcr: { cassette_name: 'helpscout_conversations_reports' } do
|
||||||
before { Rails.cache.clear }
|
before do
|
||||||
|
mock_helpscout_secrets
|
||||||
|
Rails.cache.clear
|
||||||
|
end
|
||||||
|
|
||||||
subject { described_class.new(from, to) }
|
subject { described_class.new(from, to) }
|
||||||
|
|
||||||
|
@ -34,13 +35,19 @@ describe Helpscout::UserConversationsAdapter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'populates each report with data' do
|
it 'populates each report with data' do
|
||||||
expect(subject.reports.first[:conversations_count]).to be > 0
|
expect(subject.reports.first[:replies_sent]).to be > 0
|
||||||
expect(subject.reports.first[:start_date]).to eq Time.utc(2017, 11)
|
expect(subject.reports.first[:start_date]).to eq Time.utc(2017, 11)
|
||||||
expect(subject.reports.first[:end_date]).to eq Time.utc(2017, 12)
|
expect(subject.reports.first[:end_date]).to eq Time.utc(2017, 12)
|
||||||
|
|
||||||
expect(subject.reports.last[:conversations_count]).to be > 0
|
expect(subject.reports.last[:replies_sent]).to be > 0
|
||||||
expect(subject.reports.last[:start_date]).to eq Time.utc(2017, 12)
|
expect(subject.reports.last[:start_date]).to eq Time.utc(2017, 12)
|
||||||
expect(subject.reports.last[:end_date]).to eq Time.utc(2018, 01)
|
expect(subject.reports.last[:end_date]).to eq Time.utc(2018, 01)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mock_helpscout_secrets
|
||||||
|
Rails.application.secrets.helpscout[:mailbox_id] = '9999'
|
||||||
|
Rails.application.secrets.helpscout[:client_id] = '1234'
|
||||||
|
Rails.application.secrets.helpscout[:client_secret] = '5678'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,6 @@ describe AdministrateurUsageStatisticsService do
|
||||||
ds_created_at: Time.zone.now,
|
ds_created_at: Time.zone.now,
|
||||||
ds_active: false,
|
ds_active: false,
|
||||||
ds_id: administrateur.id,
|
ds_id: administrateur.id,
|
||||||
ds_features: "{}",
|
|
||||||
nb_services: 0,
|
nb_services: 0,
|
||||||
nb_instructeurs: 0,
|
nb_instructeurs: 0,
|
||||||
ds_nb_demarches_actives: 0,
|
ds_nb_demarches_actives: 0,
|
||||||
|
@ -43,7 +42,6 @@ describe AdministrateurUsageStatisticsService do
|
||||||
current_sign_in_at: Time.zone.local(2019, 3, 7),
|
current_sign_in_at: Time.zone.local(2019, 3, 7),
|
||||||
last_sign_in_at: Time.zone.local(2019, 2, 27),
|
last_sign_in_at: Time.zone.local(2019, 2, 27),
|
||||||
active: true,
|
active: true,
|
||||||
features: { holy_hand_grenade_of_antioch: true },
|
|
||||||
services: [create(:service)],
|
services: [create(:service)],
|
||||||
instructeurs: [create(:instructeur)])
|
instructeurs: [create(:instructeur)])
|
||||||
end
|
end
|
||||||
|
@ -56,7 +54,6 @@ describe AdministrateurUsageStatisticsService do
|
||||||
ds_created_at: Time.zone.now,
|
ds_created_at: Time.zone.now,
|
||||||
ds_active: true,
|
ds_active: true,
|
||||||
ds_id: administrateur.id,
|
ds_id: administrateur.id,
|
||||||
ds_features: { holy_hand_grenade_of_antioch: true }.to_json,
|
|
||||||
nb_services: 1,
|
nb_services: 1,
|
||||||
nb_instructeurs: 1
|
nb_instructeurs: 1
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,7 @@ RSpec.configure do |config|
|
||||||
end
|
end
|
||||||
|
|
||||||
config.before(:each, js: true) do
|
config.before(:each, js: true) do
|
||||||
DatabaseCleaner.strategy = :truncation, { except: expect_list }
|
DatabaseCleaner.strategy = :deletion, { except: expect_list }
|
||||||
end
|
end
|
||||||
|
|
||||||
config.before(:each) do
|
config.before(:each) do
|
||||||
|
|
|
@ -5398,9 +5398,9 @@ mississippi@^3.0.0:
|
||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
|
|
||||||
mixin-deep@^1.2.0:
|
mixin-deep@^1.2.0:
|
||||||
version "1.3.1"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
|
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
|
||||||
integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
|
integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
|
||||||
dependencies:
|
dependencies:
|
||||||
for-in "^1.0.2"
|
for-in "^1.0.2"
|
||||||
is-extendable "^1.0.1"
|
is-extendable "^1.0.1"
|
||||||
|
|
Loading…
Reference in a new issue