Merge pull request #2386 from betagouv/dev

2018-08-13-01
This commit is contained in:
Paul Chavard 2018-08-13 13:15:21 +00:00 committed by GitHub
commit 917f3cadf7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 649 additions and 161 deletions

View file

@ -90,20 +90,8 @@ jobs:
- *yarn_restore_cache - *yarn_restore_cache
- *yarn_install - *yarn_install
- run: - run:
name: Run eslint name: Run linters
command: yarn lint:js command: bundle exec rake lint
- run:
name: Run rubocop
command: bundle exec rubocop
- run:
name: Run brakeman
command: bundle exec brakeman
- run:
name: Run haml-lint
command: bundle exec haml-lint app/views/
- run:
name: Run scss-lint
command: bundle exec scss-lint app/assets/stylesheets/
deploy: deploy:
<<: *defaults <<: *defaults
steps: steps:

View file

@ -70,6 +70,8 @@ gem 'leaflet-draw-rails'
gem 'chartkick' gem 'chartkick'
gem 'logstasher' gem 'logstasher'
gem 'lograge'
gem 'logstash-event'
gem 'font-awesome-rails' gem 'font-awesome-rails'

View file

@ -457,6 +457,11 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7) rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2) ruby_dep (~> 1.2)
lograge (0.10.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
logstash-event (1.2.02) logstash-event (1.2.02)
logstasher (1.2.2) logstasher (1.2.2)
activesupport (>= 4.0) activesupport (>= 4.0)
@ -845,6 +850,8 @@ DEPENDENCIES
leaflet-draw-rails leaflet-draw-rails
leaflet-markercluster-rails (~> 0.7.0) leaflet-markercluster-rails (~> 0.7.0)
leaflet-rails leaflet-rails
lograge
logstash-event
logstasher logstasher
mailjet mailjet
maruku maruku

View file

@ -108,11 +108,9 @@ Une fois `overmind` lancé, et un breakpoint `byebug` inséré dans le code, il
## Linting ## Linting
- Faire tourner RuboCop : `bundle exec rubocop` Le projet utilise plusieurs linters pour vérifier la lisibilité et la qualité code.
- Faire tourner Brakeman : `bundle exec brakeman`
- Linter les fichiers HAML : `bundle exec haml-lint app/views/` - Faire tourner tous les linters : `bin/rake lint`
- Linter les fichiers SCSS : `bundle exec scss-lint app/assets/stylesheets/`
- Linter les fichiers JavaScript : `yarn lint:js` (`yarn lint:js --fix`)
- [AccessLint](http://accesslint.com/) tourne automatiquement sur les PRs - [AccessLint](http://accesslint.com/) tourne automatiquement sur les PRs
## Déploiement ## Déploiement

View file

@ -5,6 +5,14 @@ require File.expand_path('config/application', __dir__)
Rails.application.load_tasks Rails.application.load_tasks
task :lint do
sh "bundle exec rubocop"
sh "bundle exec haml-lint app/views/"
sh "bundle exec scss-lint app/assets/stylesheets/"
sh "bundle exec brakeman --no-pager"
sh "yarn lint:js"
end
task :deploy do task :deploy do
domains = %w(37.187.249.111 149.202.72.152 149.202.198.6) domains = %w(37.187.249.111 149.202.72.152 149.202.198.6)
domains.each do |domain| domains.each do |domain|

View file

@ -1,52 +1,47 @@
$(document).on('turbolinks:load', init_admin); $(document).on('click', '.delete', function() {
$(this).hide();
$(this)
.closest('td')
.find('.confirm')
.show();
});
function init_admin(){ $(document).on('click', '.cancel', function() {
destroy_action(); $(this)
on_change_type_de_champ_select(); .closest('td')
} .find('.delete')
.show();
$(this)
.closest('td')
.find('.confirm')
.hide();
});
function destroy_action(){ $(document).on('change', 'select.form-control.type-champ', function() {
$(".delete").on('click', function(){ var parent = $(this)
$(this).hide(); .parent()
$(this).closest('td').find(".confirm").show(); .parent();
});
$(".cancel").on('click', function(){ parent.removeClass('header-section');
$(this).closest('td').find(".delete").show(); parent.children('.drop-down-list').removeClass('show-inline');
$(this).closest('td').find(".confirm").hide(); parent.children('.pj-template').removeClass('show-inline');
});
$("#liste-gestionnaire #libelle").on('click', function(){ $('.mandatory', parent).show();
setTimeout(destroy_action, 500);
});
}
function on_change_type_de_champ_select (){ switch (this.value) {
$("select.form-control.type-champ").on('change', function(e){ case 'header_section':
parent.addClass('header-section');
parent = $(this).parent().parent(); break;
case 'drop_down_list':
parent.removeClass('header-section'); case 'multiple_drop_down_list':
parent.children(".drop-down-list").removeClass('show-inline'); case 'linked_drop_down_list':
parent.children(".pj-template").removeClass('show-inline'); parent.children('.drop-down-list').addClass('show-inline');
break;
$('.mandatory', parent).show(); case 'piece_justificative':
parent.children('.pj-template').addClass('show-inline');
switch(this.value){ break;
case 'header_section': case 'explication':
parent.addClass('header-section'); $('.mandatory', parent).hide();
break; break;
case 'drop_down_list': }
case 'multiple_drop_down_list': });
case 'linked_drop_down_list':
parent.children(".drop-down-list").addClass('show-inline');
break;
case 'piece_justificative':
parent.children(".pj-template").addClass('show-inline');
break;
case 'explication':
$('.mandatory', parent).hide();
break;
}
});
}

View file

@ -1,10 +1,6 @@
$(document).on('turbolinks:load', link_init); $(document).on('click', '#dossiers-list tr', function(event) {
var href = $(this).data('href');
function link_init() { if (href && event.target.tagName !== 'A') {
$('#dossiers-list tr').on('click', function(event) { location.href = href;
var href = $(this).data('href'); }
if (href && event.target.tagName !== 'A') { });
location.href = href;
}
});
}

View file

@ -178,7 +178,8 @@ div.pagination {
} }
.alert.alert-success.move-up, .alert.alert-success.move-up,
.alert.alert-danger.siret { .alert.alert-danger.siret,
.alert.sticky {
position: fixed; position: fixed;
top: 0px; top: 0px;
left: 0; left: 0;

View file

@ -7,6 +7,7 @@
.icon { .icon {
padding: 10px 5px; padding: 10px 5px;
margin: 10px 10px;
&:hover { &:hover {
cursor: pointer; cursor: pointer;

View file

@ -0,0 +1,33 @@
@import "colors";
@import "constants";
#dossier-show {
.sub-header {
.label {
float: right;
margin-left: $default-spacer;
}
.title-container {
margin-bottom: $default-padding * 2;
padding-left: 32px;
.icon.folder {
float: left;
margin-left: -32px;
margin-top: 3px;
}
}
h1 {
color: $black;
font-size: 22px;
margin-bottom: 0;
}
h2 {
color: $grey;
font-weight: bold;
}
}
}

View file

@ -0,0 +1,82 @@
@import "colors";
@import "constants";
.status-progress {
text-align: center;
}
.status-timeline {
display: inline-block;
margin-top: $default-padding * 2;
margin-bottom: $default-padding * 2;
border: 1px solid #808080;
border-radius: 3px;
li {
display: inline-block;
padding-top: $default-spacer;
padding-bottom: $default-spacer;
&:first-child {
padding-left: 20px;
}
&:last-child {
padding-right: 20px;
}
&.active {
font-weight: bold;
}
&.active ~ li {
color: $grey;
}
// Arrows
&:not(:last-child)::after {
content: "";
display: inline-block;
margin-left: 10px;
margin-right: 10px;
vertical-align: top;
}
}
}
.status-explanation {
text-align: left;
&.brouillon,
&.en-construction,
&.en-instruction {
max-width: 600px;
margin: auto;
}
h3 {
font-size: 1.1em;
font-weight: bold;
margin-bottom: $default-spacer;
}
p {
margin-bottom: $default-padding;
}
blockquote {
quotes: "« " " »" "" "";
}
blockquote::before {
content: open-quote;
}
blockquote::after {
content: close-quote;
}
.icon {
margin-right: $default-spacer;
}
}

View file

@ -97,15 +97,18 @@ class ApplicationController < ActionController::Base
Raven.user_context(context) Raven.user_context(context)
end end
def session_info_payload def append_info_to_payload(payload)
super
user = logged_user user = logged_user
payload = { payload[:xhr] = !!request.xhr?
payload.merge!({
user_agent: request.user_agent, user_agent: request.user_agent,
current_user_id: user&.id, user_id: user&.id,
current_user_email: user&.email, user_email: user&.email,
current_user_roles: logged_user_roles user_roles: logged_user_roles
}.compact }.compact)
if browser.known? if browser.known?
payload.merge!({ payload.merge!({

View file

@ -7,13 +7,43 @@ module ApplicationHelper
end end
end end
def flash_class(level) def flash_class(level, sticky = false)
case level case level
when "notice" then "alert-success" when "notice" then "alert-success#{sticky ? ' sticky' : ''}"
when "alert" then "alert-danger" when "alert" then "alert-danger#{sticky ? ' sticky' : ''}"
end end
end end
def render_to_element(selector, partial:, outer: false, locals: {})
method = outer ? 'outerHTML' : 'innerHTML'
html = escape_javascript(render partial: partial, locals: locals)
# rubocop:disable Rails/OutputSafety
raw("document.querySelector('#{selector}').#{method} = \"#{html}\";")
# rubocop:enable Rails/OutputSafety
end
def render_flash(timeout: false, sticky: false)
if flash.any?
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky }, outer: true)
flash.clear
if timeout
html += remove_element('#flash_messages', timeout: timeout, inner: true)
end
html
end
end
def remove_element(selector, timeout: 0, inner: false)
script = "(function() {";
script << "var el = document.querySelector('#{selector}');"
method = (inner ? "el.innerHTML = ''" : "el.parentNode.removeChild(el)")
script << "setTimeout(function() { #{method}; }, #{timeout});";
script << "})();"
# rubocop:disable Rails/OutputSafety
raw(script);
# rubocop:enable Rails/OutputSafety
end
def current_email def current_email
current_user&.email || current_user&.email ||
current_gestionnaire&.email || current_gestionnaire&.email ||
@ -37,4 +67,17 @@ module ApplicationHelper
Raven.capture_exception(e) Raven.capture_exception(e)
{} {}
end end
def sentry_config
sentry = Rails.application.secrets.sentry
if sentry
{
dsn: sentry[:browser],
id: current_user&.id,
email: current_email
}.to_json
else
{}
end
end
end end

View file

@ -13,6 +13,7 @@ import 'babel-polyfill';
import 'typeahead.js'; import 'typeahead.js';
import '../shared/sentry';
import '../shared/rails-ujs-fix'; import '../shared/rails-ujs-fix';
// Start Rails helpers // Start Rails helpers

View file

@ -13,6 +13,7 @@ import 'babel-polyfill';
import 'select2'; import 'select2';
import 'typeahead.js'; import 'typeahead.js';
import '../shared/sentry';
import '../shared/rails-ujs-fix'; import '../shared/rails-ujs-fix';
import '../new_design/buttons'; import '../new_design/buttons';

View file

@ -0,0 +1,14 @@
import { init, configureScope } from '@sentry/browser';
import { getData } from './data';
const { dsn, email, id } = getData('sentry');
if (dsn) {
init({ dsn });
if (email) {
configureScope(scope => {
scope.setUser({ id, email });
});
}
}

View file

@ -4,4 +4,8 @@ class Champs::CheckboxChamp < Champ
[ libelle ] [ libelle ]
end end
end end
def to_s
value == 'on' ? 'oui' : 'non'
end
end end

View file

@ -1,4 +1,2 @@
<% flash.each do |type, message| %> <%= render_flash(timeout: 3000, sticky: true) %>
$("#flash_message").html("<div class=\"alert alert-success move-up\" style=\"display: block:\"> <%= sanitize(message) %></div>").children().fadeOut(5000) <%= render_to_element('#piece_justificative_form', partial: 'admin/pieces_justificatives/form', locals: { procedure: @procedure }) %>
<% end %>
$('#piece_justificative_form').html("<%= escape_javascript(render partial: 'form', locals: { procedure: @procedure } ) %>");

View file

@ -1,2 +1 @@
<%= smart_listing_update :procedures %> <%= smart_listing_update :procedures %>
link_init();

View file

@ -1,9 +1,7 @@
<%- if response.status == 404 %> <%- if response.status == 404 %>
transfer_errors_message(true); transfer_errors_message(true);
<%- else %> <%- else %>
$("#main-container").prepend("<div class='row'><div id='flash_message'></div></div>"); <%= render_flash %>
$("#flash_message").prepend("<div class=\"alert alert-success\"> <%= sanitize(flash.notice) %></div>");
<% flash.clear %>
transfer_errors_message(false); transfer_errors_message(false);
$("#email_admin").val(''); $("#email_admin").val('');

View file

@ -1,5 +1,2 @@
<% flash.each do |type, message| %> <%= render_flash(timeout: 3000, sticky: true) %>
$("#flash_message").html("<div class=\"alert alert-success move-up\" style=\"display: block:\"> <%= sanitize(message) %></div>").children().fadeOut(5000) <%= render_to_element('#liste-champ', partial: 'admin/types_de_champ/form', locals: { procedure: @procedure, types_de_champ: @types_de_champ }) %>
<% end %>
$('#liste-champ').html("<%= escape_javascript(render partial: 'admin/types_de_champ/form', locals: { procedure: @procedure, types_de_champ: @types_de_champ } ) %>");
on_change_type_de_champ_select ();

View file

@ -1,8 +1,5 @@
(function() { <% if @blank || @error %>
<% if @blank || @error %> <%= render_to_element("#etablissement-for-#{@champ.id}", partial: 'shared/champs/siret/delete_etablissement', locals: { message: @error, position: @champ.order_place, etablissement: @etablissement }) %>
var html = "<%= escape_javascript(render partial: 'shared/champs/siret/delete_etablissement', locals: { message: @error, position: @champ.order_place, etablissement: @etablissement }) %>"; <% else %>
<% else %> <%= render_to_element("#etablissement-for-#{@champ.id}", partial: 'shared/champs/siret/etablissement', locals: { position: @champ.order_place, etablissement: @etablissement }) %>
var html = "<%= escape_javascript(render partial: 'shared/champs/siret/etablissement', locals: { position: @champ.order_place, etablissement: @etablissement }) %>"; <% end %>
<% end %>
document.querySelector("#etablissement-for-<%= @champ.id %>").innerHTML = html;
})();

View file

@ -1,6 +1,2 @@
var formView = "<%= escape_javascript(render partial: 'invites/form', locals: { dossier: @dossier }) %>"; <%= render_to_element('#invites-form', partial: 'invites/form', locals: { dossier: @dossier }, outer: true) %>
document.querySelector("#invites-form").outerHTML = formView; <%= render_flash %>
var flashMessagesView = "<%= escape_javascript(render partial: 'layouts/flash_messages') %>";
document.querySelector("#flash_messages").outerHTML = flashMessagesView;
<% flash.clear %>

View file

@ -2,11 +2,12 @@
- if flash.any? - if flash.any?
#flash_message.center #flash_message.center
- flash.each do |key, value| - flash.each do |key, value|
- sticky = defined?(sticky) ? sticky : false
- if value.class == Array - if value.class == Array
.alert{ class: flash_class(key) } .alert{ class: flash_class(key, sticky) }
- value.each do |message| - value.each do |message|
= sanitize(message) = sanitize(message)
%br %br
- else - else
.alert{ class: flash_class(key) } .alert{ class: flash_class(key, sticky) }
= sanitize(value) = sanitize(value)

View file

@ -17,7 +17,9 @@
= csrf_meta_tags = csrf_meta_tags
:javascript :javascript
DATA = []; DATA = [{
sentry: #{raw(sentry_config)}
}];
%body %body
= render partial: 'layouts/support_navigator_banner' = render partial: 'layouts/support_navigator_banner'
= render partial: 'layouts/pre_maintenance' = render partial: 'layouts/pre_maintenance'

View file

@ -22,7 +22,9 @@
= stylesheet_link_tag :xray = stylesheet_link_tag :xray
:javascript :javascript
DATA = []; DATA = [{
sentry: #{raw(sentry_config)}
}];
%body %body
.page-wrapper .page-wrapper
= render partial: "layouts/support_navigator_banner" = render partial: "layouts/support_navigator_banner"

View file

@ -37,7 +37,7 @@
%span.icon.without-continuation %span.icon.without-continuation
.description .description
%h4 Classer sans suite %h4 Classer sans suite
L'usager ne recevra aucune notification L'usager sera notifié que son dossier a été classé sans suite
%li{ onclick: "DS.showMotivation('refuse');" } %li{ onclick: "DS.showMotivation('refuse');" }
%span.icon.refuse %span.icon.refuse
.description .description

View file

@ -4,8 +4,8 @@
#{popup_title} #{popup_title}
= form_tag(terminer_gestionnaire_dossier_path(dossier.procedure, dossier), method: :post, class: 'form') do = form_tag(terminer_gestionnaire_dossier_path(dossier.procedure, dossier), method: :post, class: 'form') do
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: 'Rédigez votre motivation ici (facultative)'
- if title == 'Accepter' - if title == 'Accepter'
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: 'Rédigez votre motivation ici (facultative)', required: false
%p.help %p.help
L'acceptation du dossier envoie automatiquement une attestation à l'usager. L'acceptation du dossier envoie automatiquement une attestation à l'usager.
@ -26,7 +26,8 @@
%ul %ul
- unspecified_annotations_privees.each do |unspecified_annotations_privee| - unspecified_annotations_privees.each do |unspecified_annotations_privee|
%li= unspecified_annotations_privee.libelle %li= unspecified_annotations_privee.libelle
- else
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: 'Rédigez votre motivation ici (obligatoire)', required: true
.text-right .text-right
%span.button{ onclick: 'DS.motivationCancel();' } Annuler %span.button{ onclick: 'DS.motivationCancel();' } Annuler
= button_tag 'Valider la décision', name: :process_action, value: process_action, class: 'button primary', title: title, data: { confirm: confirm } = button_tag 'Valider la décision', name: :process_action, value: process_action, class: 'button primary', title: title, data: { confirm: confirm }

View file

@ -15,17 +15,6 @@
= link_to(dossiers_path(current_tab: 'dossiers-invites')) do = link_to(dossiers_path(current_tab: 'dossiers-invites')) do
dossiers invités dossiers invités
- if current_user.feedbacks.empty?
.container#user-satisfaction
%h3 Que pensez-vous de ce service ?
.icons
= link_to feedback_path(mark: 0), data: { remote: true, method: :post } do
%span.icon.frown
= link_to feedback_path(mark: 1), data: { remote: true, method: :post } do
%span.icon.meh
= link_to feedback_path(mark: 2), data: { remote: true, method: :post } do
%span.icon.smile
.container .container
- if @dossiers.present? - if @dossiers.present?
- if @dossiers.total_pages >= 2 - if @dossiers.total_pages >= 2
@ -64,6 +53,17 @@
= dossier.updated_at.localtime.strftime("%d/%m/%Y") = dossier.updated_at.localtime.strftime("%d/%m/%Y")
= paginate(@dossiers) = paginate(@dossiers)
- if current_user.feedbacks.empty?
#user-satisfaction
%h3 Que pensez-vous de ce service ?
.icons
= link_to feedback_path(mark: 0), data: { remote: true, method: :post } do
%span.icon.frown
= link_to feedback_path(mark: 1), data: { remote: true, method: :post } do
%span.icon.meh
= link_to feedback_path(mark: 2), data: { remote: true, method: :post } do
%span.icon.smile
- else - else
.dossiers-table-empty .dossiers-table-empty
%h2.empty-text Aucun dossier. %h2.empty-text Aucun dossier.

View file

@ -1,3 +1,4 @@
%h1 #dossier-show
Dossier = render partial: 'new_user/dossiers/show/header', locals: { dossier: @dossier }
= @dossier.id
= render partial: 'new_user/dossiers/show/resume', locals: { dossier: @dossier }

View file

@ -0,0 +1,12 @@
.sub-header
.container
= render partial: 'shared/dossiers/status', locals: { dossier: dossier }
.title-container
%span.icon.folder
%h1= dossier.procedure.libelle
%h2 Dossier nº #{dossier.id}
%ul.tabs
%li.active
= link_to "Résumé", dossier_path(dossier)

View file

@ -0,0 +1,2 @@
.container
= render partial: 'new_user/dossiers/show/status_progress', locals: { dossier: dossier }

View file

@ -0,0 +1,63 @@
.status-progress
- if !dossier.termine?
%ul.status-timeline
%li.brouillon{ class: dossier.brouillon? ? 'active' : nil }
brouillon
%li.en-construction{ class: dossier.en_construction? ? 'active' : nil }
en construction
%li.en-instruction{ class: dossier.en_instruction? ? 'active' : nil }
en instruction
%li.termine{ class: dossier.termine? ? 'active' : nil }
terminé
.status-explanation
- if dossier.brouillon?
.brouillon
%p Vous pouvez remplir votre dossier tranquillement : il nest pas encore visible par ladministration.
%p Quand vous aurez terminé, soumettez votre dossier pour quil soit examiné.
- elsif dossier.en_construction?
.en-construction
%p Un accompagnant de ladministration est en train de vérifier que votre dossier est bien complet.
%p Si des modifications sont nécessaires, vous recevrez un email avec les modifications à effectuer. Et sinon, dès que votre dossier sera complet, il passera automatiquement en instruction.
- elsif dossier.en_instruction?
.en-instruction
%p Votre dossier est complet. Il est en cours dexamen par les agent·e·s de ladministration.
%p Dès que ladministration aura statué sur votre dossier, vous recevrez un email avec le résultat.
- elsif dossier.accepte?
.accepte
%p
%span.icon.accept
Votre dossier a été
= succeed '.' do
%strong accepté
- if dossier.motivation.present?
%h3 Motif de lacceptation
%blockquote= dossier.motivation
- elsif dossier.refuse?
.refuse
%p
%span.icon.refuse
Nous sommes désolés, votre dossier a malheureusement été
= succeed '.' do
%strong refusé
- if dossier.motivation.present?
%h3 Motif du refus
%blockquote= dossier.motivation
- elsif dossier.sans_suite?
.sans-suite
%p
%span.icon.without-continuation
Votre dossier a été classé
= succeed '.' do
%strong sans suite
- if dossier.motivation.present?
%h3 Motif du classement sans suite
%blockquote= dossier.motivation

View file

@ -1,4 +1,2 @@
document.querySelector('#user-satisfaction').innerHTML = ''; <%= remove_element('#user-satisfaction') %>
var flashMessagesView = "<%= escape_javascript(render partial: 'layouts/flash_messages') %>"; <%= render_flash %>
document.querySelector("#flash_messages").outerHTML = flashMessagesView;
<% flash.clear %>

View file

@ -4,6 +4,9 @@
%p %p
Votre dossier nº --numéro du dossier-- a été refusé le --date de décision--. Votre dossier nº --numéro du dossier-- a été refusé le --date de décision--.
%p
Le motif de refus est le suivant : --motivation--
%p %p
Pour en savoir plus sur le motif du refus, vous pouvez consulter votre dossier et les éventuels messages de l'administration à cette adresse : --lien dossier-- Pour en savoir plus sur le motif du refus, vous pouvez consulter votre dossier et les éventuels messages de l'administration à cette adresse : --lien dossier--

View file

@ -4,6 +4,9 @@
%p %p
Votre dossier nº --numéro du dossier-- a été classé sans suite le --date de décision--. Votre dossier nº --numéro du dossier-- a été classé sans suite le --date de décision--.
%p
Le motif est le suivant : --motivation--
%p %p
Pour en savoir plus sur les raisons de ce classement sans suite, vous pouvez consulter votre dossier et les éventuels messages de l'administration à cette adresse : --lien dossier-- Pour en savoir plus sur les raisons de ce classement sans suite, vous pouvez consulter votre dossier et les éventuels messages de l'administration à cette adresse : --lien dossier--

View file

@ -1,9 +1,9 @@
- if dossier.brouillon? - if dossier.brouillon?
%span.label.brouillon brouillon %span.label.brouillon brouillon
- if dossier.en_instruction?
%span.label.instruction en instruction
- elsif dossier.en_construction? - elsif dossier.en_construction?
%span.label.construction en construction %span.label.construction en construction
- if dossier.en_instruction?
%span.label.instruction en instruction
- elsif dossier.accepte? - elsif dossier.accepte?
%span.label.accepted accepté %span.label.accepted accepté
- elsif dossier.refuse? - elsif dossier.refuse?

View file

@ -0,0 +1,30 @@
Rails.application.configure do
config.lograge.formatter = Lograge::Formatters::Logstash.new
config.lograge.base_controller_class = ['ActionController::Base', 'Manager::ApplicationController']
# This will allow to override custom options from environement file
# injected by ansible.
if !config.lograge.custom_options
config.lograge.custom_options = lambda do |event|
{
type: 'tps',
user_id: event.payload[:user_id],
user_email: event.payload[:user_email],
user_roles: event.payload[:user_roles],
user_agent: event.payload[:user_agent],
browser: event.payload[:browser],
browser_version: event.payload[:browser_version],
platform: event.payload[:platform]
}.compact
end
config.lograge.custom_payload do |controller|
{
xhr: !!controller.request.xhr?
}
end
end
config.lograge.keep_original_rails_log = true
config.lograge.logger = ActiveSupport::Logger.new Rails.root.join('log', "logstash_#{Rails.env}.log")
end

View file

@ -1,11 +0,0 @@
backtrace: true
suppress_app_log: false
log_controller_parameters: false
development:
enabled: false
test:
enabled: false
staging:
enabled: false
production:
enabled: true

View file

@ -2,4 +2,8 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const environment = require('./environment') const environment = require('./environment')
// https://github.com/rails/webpacker/issues/1235
environment.config.optimization.minimizer[0].options.uglifyOptions.ecma = 5; // for IE 11 support
environment.config.optimization.minimizer[0].options.uglifyOptions.safari10 = true;
module.exports = environment.toWebpackConfig() module.exports = environment.toWebpackConfig()

View file

@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"@rails/webpacker": "4.0.0-pre.2", "@rails/webpacker": "4.0.0-pre.2",
"@sentry/browser": "^4.0.0-beta.12",
"activestorage": "^5.2.0", "activestorage": "^5.2.0",
"bloodhound-js": "^1.2.2", "bloodhound-js": "^1.2.2",
"chartkick": "^2.3.6", "chartkick": "^2.3.6",

View file

@ -17,7 +17,7 @@ describe ApplicationController, type: :controller do
let(:current_gestionnaire) { nil } let(:current_gestionnaire) { nil }
let(:current_administrateur) { nil } let(:current_administrateur) { nil }
let(:current_administration) { nil } let(:current_administration) { nil }
let(:payload) { @controller.send(:session_info_payload) } let(:payload) { {} }
before do before do
expect(@controller).to receive(:current_user).and_return(current_user) expect(@controller).to receive(:current_user).and_return(current_user)
@ -27,6 +27,7 @@ describe ApplicationController, type: :controller do
allow(Raven).to receive(:user_context) allow(Raven).to receive(:user_context)
@controller.send(:set_raven_context) @controller.send(:set_raven_context)
@controller.send(:append_info_to_payload, payload)
end end
context 'when no one is logged in' do context 'when no one is logged in' do
@ -35,7 +36,16 @@ describe ApplicationController, type: :controller do
.with({ ip_address: '0.0.0.0', roles: 'Guest' }) .with({ ip_address: '0.0.0.0', roles: 'Guest' })
end end
it { expect(payload).to eq({ user_agent: 'Rails Testing', current_user_roles: 'Guest' }) } it do
[:db_runtime, :view_runtime, :variant, :rendered_format].each do |key|
payload.delete(key)
end
expect(payload).to eq({
user_agent: 'Rails Testing',
user_roles: 'Guest',
xhr: false
})
end
end end
context 'when a user is logged in' do context 'when a user is logged in' do
@ -47,11 +57,15 @@ describe ApplicationController, type: :controller do
end end
it do it do
[:db_runtime, :view_runtime, :variant, :rendered_format].each do |key|
payload.delete(key)
end
expect(payload).to eq({ expect(payload).to eq({
user_agent: 'Rails Testing', user_agent: 'Rails Testing',
current_user_id: current_user.id, user_id: current_user.id,
current_user_email: current_user.email, user_email: current_user.email,
current_user_roles: 'User' user_roles: 'User',
xhr: false
}) })
end end
end end
@ -68,11 +82,15 @@ describe ApplicationController, type: :controller do
end end
it do it do
[:db_runtime, :view_runtime, :variant, :rendered_format].each do |key|
payload.delete(key)
end
expect(payload).to eq({ expect(payload).to eq({
user_agent: 'Rails Testing', user_agent: 'Rails Testing',
current_user_id: current_user.id, user_id: current_user.id,
current_user_email: current_user.email, user_email: current_user.email,
current_user_roles: 'User, Gestionnaire, Administrateur, Administration' user_roles: 'User, Gestionnaire, Administrateur, Administration',
xhr: false
}) })
end end
end end

View file

@ -87,5 +87,48 @@ FactoryBot.define do
dossier.save! dossier.save!
end end
end end
trait :accepte do
after(:create) do |dossier, _evaluator|
dossier.state = 'accepte'
dossier.processed_at = dossier.created_at + 1.minute
dossier.en_construction_at = dossier.created_at + 2.minutes
dossier.created_at = dossier.created_at + 3.minutes
dossier.save!
end
end
trait :refuse do
after(:create) do |dossier, _evaluator|
dossier.state = 'refuse'
dossier.processed_at = dossier.created_at + 1.minute
dossier.en_construction_at = dossier.created_at + 2.minutes
dossier.created_at = dossier.created_at + 3.minutes
dossier.save!
end
end
trait :sans_suite do
after(:create) do |dossier, _evaluator|
dossier.state = 'sans_suite'
dossier.processed_at = dossier.created_at + 1.minute
dossier.en_construction_at = dossier.created_at + 2.minutes
dossier.created_at = dossier.created_at + 3.minutes
dossier.save!
end
end
trait :with_motivation do
after(:create) do |dossier, _evaluator|
dossier.motivation = case dossier.state
when 'refuse'
'Lentreprise concernée nest pas agréée.'
when 'sans_suite'
'Le département nest pas éligible. Veuillez remplir un nouveau dossier auprès de la DDT du 93.'
else
'Vous avez validé les conditions.'
end
end
end
end end
end end

View file

@ -6,11 +6,12 @@ describe 'Dossier details:' do
Flipflop::FeatureSet.current.test!.switch!(:new_dossier_details, true) Flipflop::FeatureSet.current.test!.switch!(:new_dossier_details, true)
end end
scenario 'the user can see the details of their dossier' do scenario 'the user can see the summary of the dossier status' do
visit_dossier dossier visit_dossier dossier
expect(page).to have_current_path(dossier_path(dossier)) expect(page).to have_current_path(dossier_path(dossier))
expect(page).to have_content(dossier.id) expect(page).to have_content(dossier.id)
expect(page).to have_selector('.status-explanation')
end end
private private

View file

@ -0,0 +1,21 @@
require 'spec_helper'
describe Champs::CheckboxChamp do
let(:checkbox) { Champs::CheckboxChamp.new(value: value) }
describe '#to_s' do
subject { checkbox.to_s }
context 'when the value is on' do
let(:value) { 'on' }
it { is_expected.to eq('oui') }
end
context 'when the value is off' do
let(:value) { 'off' }
it { is_expected.to eq('non') }
end
end
end

View file

@ -11,6 +11,7 @@ describe 'new_user/dossiers/show.html.haml', type: :view do
subject! { render } subject! { render }
it 'affiche les informations du dossier' do it 'affiche les informations du dossier' do
expect(rendered).to have_text("Dossier #{dossier.id}") expect(rendered).to have_text(dossier.procedure.libelle)
expect(rendered).to have_text("Dossier nº #{dossier.id}")
end end
end end

View file

@ -0,0 +1,87 @@
describe 'new_user/dossiers/show/_status_progress.html.haml', type: :view do
subject! { render 'new_user/dossiers/show/status_progress.html.haml', dossier: dossier }
matcher :have_timeline_item do |selector|
match do |rendered|
expect(rendered).to have_selector(item_selector(selector))
end
chain :active do
@active = true
end
chain :inactive do
@active = false
end
def item_selector(selector)
item_selector = ".status-timeline #{selector}"
item_selector += '.active' if @active == true
item_selector += ':not(.active)' if @active == false
item_selector
end
end
context 'when brouillon' do
let(:dossier) { create :dossier }
it 'renders the timeline (without the final states)' do
expect(rendered).to have_timeline_item('.brouillon').active
expect(rendered).to have_timeline_item('.en-construction').inactive
expect(rendered).to have_timeline_item('.en-instruction').inactive
expect(rendered).to have_timeline_item('.termine').inactive
end
it { is_expected.to have_selector('.status-explanation .brouillon') }
end
context 'when en construction' do
let(:dossier) { create :dossier, :en_construction }
it 'renders the timeline (without the final states)' do
expect(rendered).to have_timeline_item('.brouillon').inactive
expect(rendered).to have_timeline_item('.en-construction').active
expect(rendered).to have_timeline_item('.en-instruction').inactive
expect(rendered).to have_timeline_item('.termine').inactive
end
it { is_expected.to have_selector('.status-explanation .en-construction') }
end
context 'when en instruction' do
let(:dossier) { create :dossier, :en_instruction }
it 'renders the timeline (without the final states)' do
expect(rendered).to have_timeline_item('.brouillon').inactive
expect(rendered).to have_timeline_item('.en-construction').inactive
expect(rendered).to have_timeline_item('.en-instruction').active
expect(rendered).to have_timeline_item('.termine').inactive
end
it { is_expected.to have_selector('.status-explanation .en-instruction') }
end
context 'when accepté' do
let(:dossier) { create :dossier, :accepte, :with_motivation }
it { is_expected.not_to have_selector('.status-timeline') }
it { is_expected.to have_selector('.status-explanation .accepte') }
it { is_expected.to have_text(dossier.motivation) }
end
context 'when refusé' do
let(:dossier) { create :dossier, :refuse, :with_motivation }
it { is_expected.not_to have_selector('.status-timeline') }
it { is_expected.to have_selector('.status-explanation .refuse') }
it { is_expected.to have_text(dossier.motivation) }
end
context 'when classé sans suite' do
let(:dossier) { create :dossier, :sans_suite, :with_motivation }
it { is_expected.not_to have_selector('.status-timeline') }
it { is_expected.to have_selector('.status-explanation .sans-suite') }
it { is_expected.to have_text(dossier.motivation) }
end
end

View file

@ -49,6 +49,49 @@
dependencies: dependencies:
any-observable "^0.3.0" any-observable "^0.3.0"
"@sentry/browser@^4.0.0-beta.12":
version "4.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-4.0.0-beta.12.tgz#dff44c7a3732577057844b1643e0ba38c644138b"
dependencies:
"@sentry/core" "4.0.0-beta.12"
"@sentry/hub" "4.0.0-beta.12"
"@sentry/minimal" "4.0.0-beta.12"
"@sentry/types" "4.0.0-beta.12"
"@sentry/utils" "4.0.0-beta.12"
"@sentry/core@4.0.0-beta.12":
version "4.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.0.0-beta.12.tgz#c821e41b02c1d66e48fbef16da744f0173575558"
dependencies:
"@sentry/hub" "4.0.0-beta.12"
"@sentry/minimal" "4.0.0-beta.12"
"@sentry/types" "4.0.0-beta.12"
"@sentry/utils" "4.0.0-beta.12"
"@sentry/hub@4.0.0-beta.12":
version "4.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.0.0-beta.12.tgz#85267cec47c0bbf1094a537f7d14d19495c77234"
dependencies:
"@sentry/types" "4.0.0-beta.12"
"@sentry/utils" "4.0.0-beta.12"
"@sentry/minimal@4.0.0-beta.12":
version "4.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.0.0-beta.12.tgz#534e8edd065646e0e5f8d71443a63f6ce7187573"
dependencies:
"@sentry/hub" "4.0.0-beta.12"
"@sentry/types" "4.0.0-beta.12"
"@sentry/types@4.0.0-beta.12":
version "4.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.0.0-beta.12.tgz#0abd303692e48c0fc11afbfea8cbad87e625357a"
"@sentry/utils@4.0.0-beta.12":
version "4.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.0.0-beta.12.tgz#9d51e88634843232b6c6f0edb6df3d3fba83071e"
dependencies:
"@sentry/types" "4.0.0-beta.12"
"@sindresorhus/is@^0.7.0": "@sindresorhus/is@^0.7.0":
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"