helpscout: use replies_sent to compute contact rate

The `conversations_count` we previously used counts replies, but also
all conversations that were tagged or re-tagged during the month – with
made counting the actual work spent on user support brittle.

Counting the replies is a better estimation of what we get.

Unfortunately this also removes the filtering-by-tag feature. To
mitigate this, the reports are now scoped to a specific mailbox. This
allows to create extra mailboxes for conversations that should't be
counted in the stats.
This commit is contained in:
Pierre de La Morinerie 2019-09-11 15:16:48 +00:00
parent 52226804d0
commit 503c393a87
6 changed files with 57 additions and 62 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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: "À lexception 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

View file

@ -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