Merge branch 'dev'
This commit is contained in:
commit
42171f762e
17 changed files with 338 additions and 25 deletions
|
@ -63,6 +63,12 @@ $stat-card-half-horizontal-spacing: 4 * $default-space;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-card-details {
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
$segmented-control-margin-top: $default-space;
|
$segmented-control-margin-top: $default-space;
|
||||||
|
|
||||||
.segmented-control {
|
.segmented-control {
|
||||||
|
|
|
@ -13,6 +13,10 @@ class StatsController < ApplicationController
|
||||||
@dossiers_numbers = dossiers_numbers(dossiers)
|
@dossiers_numbers = dossiers_numbers(dossiers)
|
||||||
|
|
||||||
@satisfaction_usagers = satisfaction_usagers
|
@satisfaction_usagers = satisfaction_usagers
|
||||||
|
|
||||||
|
@contact_percentage = contact_percentage
|
||||||
|
@contact_percentage_excluded_tags = Helpscout::UserConversationsAdapter::EXCLUDED_TAGS
|
||||||
|
|
||||||
@dossiers_states = dossiers_states
|
@dossiers_states = dossiers_states
|
||||||
|
|
||||||
@procedures_cumulative = cumulative_hash(procedures, :published_at)
|
@procedures_cumulative = cumulative_hash(procedures, :published_at)
|
||||||
|
@ -160,6 +164,24 @@ class StatsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contact_percentage
|
||||||
|
from = Date.new(2017, 10)
|
||||||
|
to = Date.today.prev_month
|
||||||
|
|
||||||
|
Helpscout::UserConversationsAdapter.new(from, to)
|
||||||
|
.reports
|
||||||
|
.map do |monthly_report|
|
||||||
|
start_date = monthly_report[:start_date].to_time.localtime
|
||||||
|
end_date = monthly_report[:end_date].to_time.localtime
|
||||||
|
replies_count = monthly_report[:conversations_count]
|
||||||
|
|
||||||
|
dossiers_count = Dossier.where(en_construction_at: start_date..end_date).count
|
||||||
|
|
||||||
|
monthly_contact_percentage = replies_count.fdiv(dossiers_count || 1) * 100
|
||||||
|
[I18n.l(start_date, format: '%b %y'), monthly_contact_percentage.round(1)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def cloned_from_library_procedures_ratio
|
def cloned_from_library_procedures_ratio
|
||||||
[3.weeks.ago, 2.weeks.ago, 1.week.ago].map do |date|
|
[3.weeks.ago, 2.weeks.ago, 1.week.ago].map do |date|
|
||||||
min_date = date.beginning_of_week
|
min_date = date.beginning_of_week
|
||||||
|
|
|
@ -10,6 +10,7 @@ class ProcedureDashboard < Administrate::BaseDashboard
|
||||||
ATTRIBUTE_TYPES = {
|
ATTRIBUTE_TYPES = {
|
||||||
types_de_piece_justificative: TypesDePieceJustificativeCollectionField,
|
types_de_piece_justificative: TypesDePieceJustificativeCollectionField,
|
||||||
types_de_champ: TypesDeChampCollectionField,
|
types_de_champ: TypesDeChampCollectionField,
|
||||||
|
types_de_champ_private: TypesDeChampCollectionField,
|
||||||
path: ProcedureLinkField,
|
path: ProcedureLinkField,
|
||||||
dossiers: Field::HasMany,
|
dossiers: Field::HasMany,
|
||||||
gestionnaires: Field::HasMany,
|
gestionnaires: Field::HasMany,
|
||||||
|
@ -68,6 +69,7 @@ class ProcedureDashboard < Administrate::BaseDashboard
|
||||||
:hidden_at,
|
:hidden_at,
|
||||||
:archived_at,
|
:archived_at,
|
||||||
:types_de_champ,
|
:types_de_champ,
|
||||||
|
:types_de_champ_private,
|
||||||
:types_de_piece_justificative,
|
:types_de_piece_justificative,
|
||||||
:for_individual,
|
:for_individual,
|
||||||
:individual_with_siret,
|
:individual_with_siret,
|
||||||
|
|
|
@ -59,6 +59,22 @@ class Helpscout::API
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def conversations_report(year, month)
|
||||||
|
Rails.logger.info("[HelpScout API] Retrieving conversations report for #{month}-#{year}…")
|
||||||
|
|
||||||
|
params = {
|
||||||
|
start: Time.utc(year, month).iso8601,
|
||||||
|
end: Time.utc(year, month).next_month.iso8601
|
||||||
|
}
|
||||||
|
|
||||||
|
response = call_api(:get, 'reports/conversations?' + params.to_query)
|
||||||
|
if !response.success?
|
||||||
|
raise StandardError, "Error while fetching conversation report: #{response.status} '#{response.body}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
parse_response_body(response)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def attachments(file)
|
def attachments(file)
|
||||||
|
|
49
app/lib/helpscout/user_conversations_adapter.rb
Normal file
49
app/lib/helpscout/user_conversations_adapter.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Fetch and compute monthly reports about the users conversations on Helpscout
|
||||||
|
class Helpscout::UserConversationsAdapter
|
||||||
|
EXCLUDED_TAGS = ['openlab', 'bizdev', 'admin', 'webinaire']
|
||||||
|
|
||||||
|
def initialize(from, to)
|
||||||
|
@from = from
|
||||||
|
@to = to
|
||||||
|
end
|
||||||
|
|
||||||
|
# Return an array of monthly reports
|
||||||
|
def reports
|
||||||
|
@reports ||= (@from..@to)
|
||||||
|
.group_by { |date| [date.year, date.month] }
|
||||||
|
.keys
|
||||||
|
.map { |key| { year: key[0], month: key[1] } }
|
||||||
|
.map { |interval| report(interval[:year], interval[:month]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def report(year, month)
|
||||||
|
report = fetch_conversations_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,
|
||||||
|
end_date: report.dig(:current, :endDate).to_datetime,
|
||||||
|
conversations_count: total_conversations - excluded_conversations
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_conversations_report(year, 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.'
|
||||||
|
end
|
||||||
|
|
||||||
|
@helpscout_api ||= Helpscout::API.new
|
||||||
|
|
||||||
|
Rails.cache.fetch("helpscout-conversation-report-#{year}-#{month}") do
|
||||||
|
@helpscout_api.conversations_report(year, month)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,7 +2,6 @@ class TypesDeChampService
|
||||||
include Rails.application.routes.url_helpers
|
include Rails.application.routes.url_helpers
|
||||||
|
|
||||||
TOGGLES = {
|
TOGGLES = {
|
||||||
TypeDeChamp.type_champs.fetch(:piece_justificative) => :champ_pj?,
|
|
||||||
TypeDeChamp.type_champs.fetch(:siret) => :champ_siret?,
|
TypeDeChamp.type_champs.fetch(:siret) => :champ_siret?,
|
||||||
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?
|
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
- if champ.private?
|
- if champ.private?
|
||||||
= link_to 'supprimer', gestionnaire_champ_purge_champ_piece_justificative_path(procedure_id: champ.dossier.procedure_id, dossier_id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete
|
= link_to 'supprimer', gestionnaire_champ_purge_champ_piece_justificative_path(procedure_id: champ.dossier.procedure_id, dossier_id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete
|
||||||
- else
|
- else
|
||||||
= link_to 'supprimer', purge_champ_piece_justificative_dossier_path(champ_id: champ.id), remote: true, method: :delete
|
= link_to 'supprimer', champ_purge_champ_piece_justificative_path(id: champ.dossier_id, champ_id: champ.id), remote: true, method: :delete
|
||||||
%br
|
%br
|
||||||
Modifier :
|
Modifier :
|
||||||
= form.file_field :piece_justificative_file,
|
= form.file_field :piece_justificative_file,
|
||||||
|
|
|
@ -28,6 +28,19 @@
|
||||||
= line_chart @satisfaction_usagers,
|
= line_chart @satisfaction_usagers,
|
||||||
colors: ["#15AD70", "#F28900", "rgba(161, 0, 5, 0.9)"]
|
colors: ["#15AD70", "#F28900", "rgba(161, 0, 5, 0.9)"]
|
||||||
|
|
||||||
|
.stat-card.stat-card-half.pull-left
|
||||||
|
%span.stat-card-title
|
||||||
|
Pourcentage de contact usagers
|
||||||
|
|
||||||
|
.chart-container
|
||||||
|
.chart
|
||||||
|
= line_chart @contact_percentage
|
||||||
|
|
||||||
|
.stat-card-details
|
||||||
|
%abbr{ title: "À l’exception des conversations taggées #{@contact_percentage_excluded_tags.join(', ')}" }
|
||||||
|
Nombre de conversations actives dans HelpScout
|
||||||
|
%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
|
||||||
Répartition des dossiers
|
Répartition des dossiers
|
||||||
|
|
|
@ -7,8 +7,6 @@ Flipflop.configure do
|
||||||
strategy :default
|
strategy :default
|
||||||
|
|
||||||
group :champs do
|
group :champs do
|
||||||
feature :champ_pj,
|
|
||||||
title: "Champ pièce justificative"
|
|
||||||
feature :champ_siret,
|
feature :champ_siret,
|
||||||
title: "Champ SIRET"
|
title: "Champ SIRET"
|
||||||
feature :champ_integer_number,
|
feature :champ_integer_number,
|
||||||
|
@ -28,4 +26,12 @@ Flipflop.configure do
|
||||||
feature :pre_maintenance_mode
|
feature :pre_maintenance_mode
|
||||||
feature :maintenance_mode
|
feature :maintenance_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if Rails.env.test?
|
||||||
|
# It would be nicer to configure this in administrateur_spec.rb in #feature_enabled?,
|
||||||
|
# but that results in a FrozenError: can't modify frozen Hash
|
||||||
|
|
||||||
|
feature :test_a
|
||||||
|
feature :test_b
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -284,7 +284,10 @@ Rails.application.routes.draw do
|
||||||
post 'commentaire' => 'dossiers#create_commentaire'
|
post 'commentaire' => 'dossiers#create_commentaire'
|
||||||
post 'ask_deletion'
|
post 'ask_deletion'
|
||||||
get 'attestation'
|
get 'attestation'
|
||||||
delete 'purge_champ_piece_justificative'
|
|
||||||
|
resources :champs, only: [] do
|
||||||
|
delete 'purge_champ_piece_justificative' => 'dossiers#purge_champ_piece_justificative'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
collection do
|
collection do
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace :'2018_05_21_cerfa_to_pj' do
|
||||||
|
|
||||||
dossiers.group_by(&:procedure).each do |procedure, dossiers|
|
dossiers.group_by(&:procedure).each do |procedure, dossiers|
|
||||||
if !procedure.types_de_champ.find_by(libelle: 'CERFA')
|
if !procedure.types_de_champ.find_by(libelle: 'CERFA')
|
||||||
procedure.administrateur.enable_feature(:champ_pj)
|
|
||||||
type_de_champ = procedure.types_de_champ.create(
|
type_de_champ = procedure.types_de_champ.create(
|
||||||
type_champ: 'piece_justificative',
|
type_champ: 'piece_justificative',
|
||||||
libelle: 'CERFA'
|
libelle: 'CERFA'
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
namespace :after_party do
|
||||||
|
desc 'Deployment task: remove_champ_pj_feature'
|
||||||
|
task remove_champ_pj_feature: :environment do
|
||||||
|
rake_puts "Running deploy task 'remove_champ_pj_feature'"
|
||||||
|
|
||||||
|
Administrateur.find_by_sql(
|
||||||
|
<<~SQL
|
||||||
|
SELECT administrateurs.*
|
||||||
|
FROM administrateurs, lateral jsonb_each(features)
|
||||||
|
WHERE key = 'champ_pj'
|
||||||
|
GROUP BY id
|
||||||
|
SQL
|
||||||
|
).each do |admin|
|
||||||
|
admin.features.delete('champ_pj')
|
||||||
|
admin.save
|
||||||
|
end
|
||||||
|
|
||||||
|
AfterParty::TaskRecord.create version: '20181210185634'
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
feature 'Signin up:' do
|
feature 'Signing up:' do
|
||||||
let(:user_email) { generate :user_email }
|
let(:user_email) { generate :user_email }
|
||||||
let(:user_password) { 'testpassword' }
|
let(:user_password) { 'testpassword' }
|
||||||
|
|
||||||
|
@ -17,6 +17,22 @@ feature 'Signin up:' do
|
||||||
expect(page).to have_current_path dossiers_path
|
expect(page).to have_current_path dossiers_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scenario 'a new user can’t sign-up with too short password' do
|
||||||
|
visit root_path
|
||||||
|
click_on 'Connexion'
|
||||||
|
click_on 'Créer un compte'
|
||||||
|
|
||||||
|
expect(page).to have_current_path new_user_registration_path
|
||||||
|
sign_up_with user_email, '1234567'
|
||||||
|
expect(page).to have_current_path user_registration_path
|
||||||
|
expect(page).to have_content 'Le mot de passe est trop court'
|
||||||
|
|
||||||
|
# Then with a good password
|
||||||
|
sign_up_with user_email, user_password
|
||||||
|
expect(page).to have_current_path new_user_confirmation_path user: { email: user_email }
|
||||||
|
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
|
||||||
|
end
|
||||||
|
|
||||||
context 'when visiting a procedure' do
|
context 'when visiting a procedure' do
|
||||||
let(:procedure) { create :simple_procedure }
|
let(:procedure) { create :simple_procedure }
|
||||||
|
|
||||||
|
|
150
spec/fixtures/cassettes/helpscout_conversations_reports.yml
vendored
Normal file
150
spec/fixtures/cassettes/helpscout_conversations_reports.yml
vendored
Normal file
File diff suppressed because one or more lines are too long
26
spec/lib/helpscout/user_conversations_adapter_spec.rb
Normal file
26
spec/lib/helpscout/user_conversations_adapter_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Helpscout::UserConversationsAdapter do
|
||||||
|
describe '#reports', vcr: { cassette_name: 'helpscout_conversations_reports' } do
|
||||||
|
let(:from) { Date.new(2017, 11) }
|
||||||
|
let(:to) { Date.new(2017, 12) }
|
||||||
|
|
||||||
|
before { Rails.cache.clear }
|
||||||
|
|
||||||
|
subject { described_class.new(from, to) }
|
||||||
|
|
||||||
|
it 'returns one report result per month' do
|
||||||
|
expect(subject.reports.count).to eq 2
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'populates each report with data' do
|
||||||
|
expect(subject.reports.first[:conversations_count]).to be > 0
|
||||||
|
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.last[:conversations_count]).to be > 0
|
||||||
|
expect(subject.reports.last[:start_date]).to eq Time.utc(2017, 12)
|
||||||
|
expect(subject.reports.last[:end_date]).to eq Time.utc(2018, 01)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -65,11 +65,11 @@ describe Administrateur, type: :model do
|
||||||
let(:administrateur) { create(:administrateur) }
|
let(:administrateur) { create(:administrateur) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
administrateur.enable_feature(:champ_pj)
|
administrateur.enable_feature(:test_a)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(administrateur.feature_enabled?(:champ_siret)).to be_falsey }
|
it { expect(administrateur.feature_enabled?(:test_b)).to be_falsey }
|
||||||
it { expect(administrateur.feature_enabled?(:champ_pj)).to be_truthy }
|
it { expect(administrateur.feature_enabled?(:test_a)).to be_truthy }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#password_complexity" do
|
describe "#password_complexity" do
|
||||||
|
|
|
@ -107,20 +107,6 @@ describe TypesDeChampService do
|
||||||
|
|
||||||
subject { service.options }
|
subject { service.options }
|
||||||
|
|
||||||
context "when the champ_pj is enabled" do
|
|
||||||
before do
|
|
||||||
Flipflop::FeatureSet.current.test!.switch!(:champ_pj, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.to include(pj_option) }
|
it { is_expected.to include(pj_option) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the champ_pj is disabled" do
|
|
||||||
before do
|
|
||||||
Flipflop::FeatureSet.current.test!.switch!(:champ_pj, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.not_to include(pj_option) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue