diff --git a/.circleci/config.yml b/.circleci/config.yml
index 887b8e06b..baba1cf04 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -90,20 +90,8 @@ jobs:
- *yarn_restore_cache
- *yarn_install
- run:
- name: Run eslint
- command: yarn lint:js
- - 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/
+ name: Run linters
+ command: bundle exec rake lint
deploy:
<<: *defaults
steps:
diff --git a/Gemfile b/Gemfile
index 97e2e2e0d..ed7fde5c0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -70,6 +70,8 @@ gem 'leaflet-draw-rails'
gem 'chartkick'
gem 'logstasher'
+gem 'lograge'
+gem 'logstash-event'
gem 'font-awesome-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index cfd127f09..ac66e576a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -457,6 +457,11 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
+ lograge (0.10.0)
+ actionpack (>= 4)
+ activesupport (>= 4)
+ railties (>= 4)
+ request_store (~> 1.0)
logstash-event (1.2.02)
logstasher (1.2.2)
activesupport (>= 4.0)
@@ -845,6 +850,8 @@ DEPENDENCIES
leaflet-draw-rails
leaflet-markercluster-rails (~> 0.7.0)
leaflet-rails
+ lograge
+ logstash-event
logstasher
mailjet
maruku
diff --git a/README.md b/README.md
index 6277da631..cb8deaf55 100644
--- a/README.md
+++ b/README.md
@@ -108,11 +108,9 @@ Une fois `overmind` lancé, et un breakpoint `byebug` inséré dans le code, il
## Linting
-- Faire tourner RuboCop : `bundle exec rubocop`
-- Faire tourner Brakeman : `bundle exec brakeman`
-- Linter les fichiers HAML : `bundle exec haml-lint app/views/`
-- Linter les fichiers SCSS : `bundle exec scss-lint app/assets/stylesheets/`
-- Linter les fichiers JavaScript : `yarn lint:js` (`yarn lint:js --fix`)
+Le projet utilise plusieurs linters pour vérifier la lisibilité et la qualité code.
+
+- Faire tourner tous les linters : `bin/rake lint`
- [AccessLint](http://accesslint.com/) tourne automatiquement sur les PRs
## Déploiement
diff --git a/Rakefile b/Rakefile
index 186e5b1ed..b1ed331ca 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,6 +5,14 @@ require File.expand_path('config/application', __dir__)
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
domains = %w(37.187.249.111 149.202.72.152 149.202.198.6)
domains.each do |domain|
diff --git a/app/assets/javascripts/old_design/admin.js b/app/assets/javascripts/old_design/admin.js
index 981032ec3..5b7af468e 100644
--- a/app/assets/javascripts/old_design/admin.js
+++ b/app/assets/javascripts/old_design/admin.js
@@ -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(){
- destroy_action();
- on_change_type_de_champ_select();
-}
+$(document).on('click', '.cancel', function() {
+ $(this)
+ .closest('td')
+ .find('.delete')
+ .show();
+ $(this)
+ .closest('td')
+ .find('.confirm')
+ .hide();
+});
-function destroy_action(){
- $(".delete").on('click', function(){
- $(this).hide();
- $(this).closest('td').find(".confirm").show();
- });
+$(document).on('change', 'select.form-control.type-champ', function() {
+ var parent = $(this)
+ .parent()
+ .parent();
- $(".cancel").on('click', function(){
- $(this).closest('td').find(".delete").show();
- $(this).closest('td').find(".confirm").hide();
- });
+ parent.removeClass('header-section');
+ parent.children('.drop-down-list').removeClass('show-inline');
+ parent.children('.pj-template').removeClass('show-inline');
- $("#liste-gestionnaire #libelle").on('click', function(){
- setTimeout(destroy_action, 500);
- });
-}
+ $('.mandatory', parent).show();
-function on_change_type_de_champ_select (){
- $("select.form-control.type-champ").on('change', function(e){
-
- parent = $(this).parent().parent();
-
- parent.removeClass('header-section');
- parent.children(".drop-down-list").removeClass('show-inline');
- parent.children(".pj-template").removeClass('show-inline');
-
- $('.mandatory', parent).show();
-
- switch(this.value){
- case 'header_section':
- parent.addClass('header-section');
- 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;
- }
- });
-}
+ switch (this.value) {
+ case 'header_section':
+ parent.addClass('header-section');
+ 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;
+ }
+});
diff --git a/app/assets/javascripts/old_design/dossiers_list_link.js b/app/assets/javascripts/old_design/dossiers_list_link.js
index 18b167ace..70425251a 100644
--- a/app/assets/javascripts/old_design/dossiers_list_link.js
+++ b/app/assets/javascripts/old_design/dossiers_list_link.js
@@ -1,10 +1,6 @@
-$(document).on('turbolinks:load', link_init);
-
-function link_init() {
- $('#dossiers-list tr').on('click', function(event) {
- var href = $(this).data('href');
- if (href && event.target.tagName !== 'A') {
- location.href = href;
- }
- });
-}
+$(document).on('click', '#dossiers-list tr', function(event) {
+ var href = $(this).data('href');
+ if (href && event.target.tagName !== 'A') {
+ location.href = href;
+ }
+});
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index aa4da2aa3..53796fe13 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -178,7 +178,8 @@ div.pagination {
}
.alert.alert-success.move-up,
-.alert.alert-danger.siret {
+.alert.alert-danger.siret,
+.alert.sticky {
position: fixed;
top: 0px;
left: 0;
diff --git a/app/assets/stylesheets/new_design/dossier-edit.scss b/app/assets/stylesheets/new_design/dossier_edit.scss
similarity index 100%
rename from app/assets/stylesheets/new_design/dossier-edit.scss
rename to app/assets/stylesheets/new_design/dossier_edit.scss
diff --git a/app/assets/stylesheets/new_design/dossier_index.scss b/app/assets/stylesheets/new_design/dossier_index.scss
index a7b52ca0d..e5a67e1ba 100644
--- a/app/assets/stylesheets/new_design/dossier_index.scss
+++ b/app/assets/stylesheets/new_design/dossier_index.scss
@@ -7,6 +7,7 @@
.icon {
padding: 10px 5px;
+ margin: 10px 10px;
&:hover {
cursor: pointer;
diff --git a/app/assets/stylesheets/new_design/dossier_show.scss b/app/assets/stylesheets/new_design/dossier_show.scss
new file mode 100644
index 000000000..66dc60376
--- /dev/null
+++ b/app/assets/stylesheets/new_design/dossier_show.scss
@@ -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;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/new_design/procedures_show.scss b/app/assets/stylesheets/new_design/procedure_show.scss
similarity index 100%
rename from app/assets/stylesheets/new_design/procedures_show.scss
rename to app/assets/stylesheets/new_design/procedure_show.scss
diff --git a/app/assets/stylesheets/new_design/status_progress.scss b/app/assets/stylesheets/new_design/status_progress.scss
new file mode 100644
index 000000000..cff35aac6
--- /dev/null
+++ b/app/assets/stylesheets/new_design/status_progress.scss
@@ -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;
+ }
+}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 2ee12840f..0997db1f9 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -97,15 +97,18 @@ class ApplicationController < ActionController::Base
Raven.user_context(context)
end
- def session_info_payload
+ def append_info_to_payload(payload)
+ super
user = logged_user
- payload = {
+ payload[:xhr] = !!request.xhr?
+
+ payload.merge!({
user_agent: request.user_agent,
- current_user_id: user&.id,
- current_user_email: user&.email,
- current_user_roles: logged_user_roles
- }.compact
+ user_id: user&.id,
+ user_email: user&.email,
+ user_roles: logged_user_roles
+ }.compact)
if browser.known?
payload.merge!({
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 46bae9be5..0cf7d51dd 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -7,13 +7,43 @@ module ApplicationHelper
end
end
- def flash_class(level)
+ def flash_class(level, sticky = false)
case level
- when "notice" then "alert-success"
- when "alert" then "alert-danger"
+ when "notice" then "alert-success#{sticky ? ' sticky' : ''}"
+ when "alert" then "alert-danger#{sticky ? ' sticky' : ''}"
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
current_user&.email ||
current_gestionnaire&.email ||
@@ -37,4 +67,17 @@ module ApplicationHelper
Raven.capture_exception(e)
{}
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
diff --git a/app/javascript/packs/application-old.js b/app/javascript/packs/application-old.js
index 89c9a9706..2af95caeb 100644
--- a/app/javascript/packs/application-old.js
+++ b/app/javascript/packs/application-old.js
@@ -13,6 +13,7 @@ import 'babel-polyfill';
import 'typeahead.js';
+import '../shared/sentry';
import '../shared/rails-ujs-fix';
// Start Rails helpers
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index dce4ef0c5..e94c60e8d 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -13,6 +13,7 @@ import 'babel-polyfill';
import 'select2';
import 'typeahead.js';
+import '../shared/sentry';
import '../shared/rails-ujs-fix';
import '../new_design/buttons';
diff --git a/app/javascript/shared/sentry.js b/app/javascript/shared/sentry.js
new file mode 100644
index 000000000..7332390d0
--- /dev/null
+++ b/app/javascript/shared/sentry.js
@@ -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 });
+ });
+ }
+}
diff --git a/app/models/champs/checkbox_champ.rb b/app/models/champs/checkbox_champ.rb
index 5bd6dea75..9202ca635 100644
--- a/app/models/champs/checkbox_champ.rb
+++ b/app/models/champs/checkbox_champ.rb
@@ -4,4 +4,8 @@ class Champs::CheckboxChamp < Champ
[ libelle ]
end
end
+
+ def to_s
+ value == 'on' ? 'oui' : 'non'
+ end
end
diff --git a/app/views/admin/pieces_justificatives/show.js.erb b/app/views/admin/pieces_justificatives/show.js.erb
index d89882b53..32d4bf4d3 100644
--- a/app/views/admin/pieces_justificatives/show.js.erb
+++ b/app/views/admin/pieces_justificatives/show.js.erb
@@ -1,4 +1,2 @@
-<% flash.each do |type, message| %>
-$("#flash_message").html("
<%= sanitize(message) %>
").children().fadeOut(5000)
-<% end %>
-$('#piece_justificative_form').html("<%= escape_javascript(render partial: 'form', locals: { procedure: @procedure } ) %>");
+<%= render_flash(timeout: 3000, sticky: true) %>
+<%= render_to_element('#piece_justificative_form', partial: 'admin/pieces_justificatives/form', locals: { procedure: @procedure }) %>
diff --git a/app/views/admin/procedures/index.js.erb b/app/views/admin/procedures/index.js.erb
index 232965d99..b2e0bdc6a 100644
--- a/app/views/admin/procedures/index.js.erb
+++ b/app/views/admin/procedures/index.js.erb
@@ -1,2 +1 @@
<%= smart_listing_update :procedures %>
-link_init();
\ No newline at end of file
diff --git a/app/views/admin/procedures/transfer.js.erb b/app/views/admin/procedures/transfer.js.erb
index a23b011f9..7dbdff4fe 100644
--- a/app/views/admin/procedures/transfer.js.erb
+++ b/app/views/admin/procedures/transfer.js.erb
@@ -1,9 +1,7 @@
<%- if response.status == 404 %>
transfer_errors_message(true);
<%- else %>
- $("#main-container").prepend("");
- $("#flash_message").prepend(" <%= sanitize(flash.notice) %>
");
- <% flash.clear %>
+ <%= render_flash %>
transfer_errors_message(false);
$("#email_admin").val('');
diff --git a/app/views/admin/types_de_champ/show.js.erb b/app/views/admin/types_de_champ/show.js.erb
index c76ba0c80..5c22f8080 100644
--- a/app/views/admin/types_de_champ/show.js.erb
+++ b/app/views/admin/types_de_champ/show.js.erb
@@ -1,5 +1,2 @@
-<% flash.each do |type, message| %>
-$("#flash_message").html(" <%= sanitize(message) %>
").children().fadeOut(5000)
-<% 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 ();
+<%= render_flash(timeout: 3000, sticky: true) %>
+<%= render_to_element('#liste-champ', partial: 'admin/types_de_champ/form', locals: { procedure: @procedure, types_de_champ: @types_de_champ }) %>
diff --git a/app/views/champs/siret/index.js.erb b/app/views/champs/siret/index.js.erb
index 4f13c1678..3b74d6a35 100644
--- a/app/views/champs/siret/index.js.erb
+++ b/app/views/champs/siret/index.js.erb
@@ -1,8 +1,5 @@
-(function() {
- <% if @blank || @error %>
- var html = "<%= escape_javascript(render partial: 'shared/champs/siret/delete_etablissement', locals: { message: @error, position: @champ.order_place, etablissement: @etablissement }) %>";
- <% else %>
- var html = "<%= escape_javascript(render partial: 'shared/champs/siret/etablissement', locals: { position: @champ.order_place, etablissement: @etablissement }) %>";
- <% end %>
- document.querySelector("#etablissement-for-<%= @champ.id %>").innerHTML = html;
-})();
+<% 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 }) %>
+<% else %>
+ <%= render_to_element("#etablissement-for-#{@champ.id}", partial: 'shared/champs/siret/etablissement', locals: { position: @champ.order_place, etablissement: @etablissement }) %>
+<% end %>
diff --git a/app/views/invites/create.js.erb b/app/views/invites/create.js.erb
index 19c233149..c73db9f67 100644
--- a/app/views/invites/create.js.erb
+++ b/app/views/invites/create.js.erb
@@ -1,6 +1,2 @@
-var formView = "<%= escape_javascript(render partial: 'invites/form', locals: { dossier: @dossier }) %>";
-document.querySelector("#invites-form").outerHTML = formView;
-
-var flashMessagesView = "<%= escape_javascript(render partial: 'layouts/flash_messages') %>";
-document.querySelector("#flash_messages").outerHTML = flashMessagesView;
-<% flash.clear %>
+<%= render_to_element('#invites-form', partial: 'invites/form', locals: { dossier: @dossier }, outer: true) %>
+<%= render_flash %>
diff --git a/app/views/layouts/_flash_messages.html.haml b/app/views/layouts/_flash_messages.html.haml
index 5f4b14ef3..a365b07f6 100644
--- a/app/views/layouts/_flash_messages.html.haml
+++ b/app/views/layouts/_flash_messages.html.haml
@@ -2,11 +2,12 @@
- if flash.any?
#flash_message.center
- flash.each do |key, value|
+ - sticky = defined?(sticky) ? sticky : false
- if value.class == Array
- .alert{ class: flash_class(key) }
+ .alert{ class: flash_class(key, sticky) }
- value.each do |message|
= sanitize(message)
%br
- else
- .alert{ class: flash_class(key) }
+ .alert{ class: flash_class(key, sticky) }
= sanitize(value)
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index cc6e06cb7..be9304b91 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -17,7 +17,9 @@
= csrf_meta_tags
:javascript
- DATA = [];
+ DATA = [{
+ sentry: #{raw(sentry_config)}
+ }];
%body
= render partial: 'layouts/support_navigator_banner'
= render partial: 'layouts/pre_maintenance'
diff --git a/app/views/layouts/new_application.html.haml b/app/views/layouts/new_application.html.haml
index 661a8990c..268c24f61 100644
--- a/app/views/layouts/new_application.html.haml
+++ b/app/views/layouts/new_application.html.haml
@@ -22,7 +22,9 @@
= stylesheet_link_tag :xray
:javascript
- DATA = [];
+ DATA = [{
+ sentry: #{raw(sentry_config)}
+ }];
%body
.page-wrapper
= render partial: "layouts/support_navigator_banner"
diff --git a/app/views/new_gestionnaire/dossiers/_state_button.html.haml b/app/views/new_gestionnaire/dossiers/_state_button.html.haml
index 61615d8da..5769317c5 100644
--- a/app/views/new_gestionnaire/dossiers/_state_button.html.haml
+++ b/app/views/new_gestionnaire/dossiers/_state_button.html.haml
@@ -37,7 +37,7 @@
%span.icon.without-continuation
.description
%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');" }
%span.icon.refuse
.description
diff --git a/app/views/new_gestionnaire/dossiers/_state_button_motivation.html.haml b/app/views/new_gestionnaire/dossiers/_state_button_motivation.html.haml
index 1c4a79335..c1bbfa66f 100644
--- a/app/views/new_gestionnaire/dossiers/_state_button_motivation.html.haml
+++ b/app/views/new_gestionnaire/dossiers/_state_button_motivation.html.haml
@@ -4,8 +4,8 @@
#{popup_title}
= 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'
+ = text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: 'Rédigez votre motivation ici (facultative)', required: false
%p.help
L'acceptation du dossier envoie automatiquement une attestation à l'usager.
@@ -26,7 +26,8 @@
%ul
- unspecified_annotations_privees.each do |unspecified_annotations_privee|
%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
%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 }
diff --git a/app/views/new_user/dossiers/index.html.haml b/app/views/new_user/dossiers/index.html.haml
index 5124dae82..2613d2a50 100644
--- a/app/views/new_user/dossiers/index.html.haml
+++ b/app/views/new_user/dossiers/index.html.haml
@@ -15,17 +15,6 @@
= link_to(dossiers_path(current_tab: 'dossiers-invites')) do
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
- if @dossiers.present?
- if @dossiers.total_pages >= 2
@@ -64,6 +53,17 @@
= dossier.updated_at.localtime.strftime("%d/%m/%Y")
= 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
.dossiers-table-empty
%h2.empty-text Aucun dossier.
diff --git a/app/views/new_user/dossiers/show.html.haml b/app/views/new_user/dossiers/show.html.haml
index 439052790..a9154569c 100644
--- a/app/views/new_user/dossiers/show.html.haml
+++ b/app/views/new_user/dossiers/show.html.haml
@@ -1,3 +1,4 @@
-%h1
- Dossier
- = @dossier.id
+#dossier-show
+ = render partial: 'new_user/dossiers/show/header', locals: { dossier: @dossier }
+
+ = render partial: 'new_user/dossiers/show/resume', locals: { dossier: @dossier }
diff --git a/app/views/new_user/dossiers/show/_header.html.haml b/app/views/new_user/dossiers/show/_header.html.haml
new file mode 100644
index 000000000..c4d4b63d4
--- /dev/null
+++ b/app/views/new_user/dossiers/show/_header.html.haml
@@ -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)
diff --git a/app/views/new_user/dossiers/show/_resume.html.haml b/app/views/new_user/dossiers/show/_resume.html.haml
new file mode 100644
index 000000000..d0e584949
--- /dev/null
+++ b/app/views/new_user/dossiers/show/_resume.html.haml
@@ -0,0 +1,2 @@
+.container
+ = render partial: 'new_user/dossiers/show/status_progress', locals: { dossier: dossier }
diff --git a/app/views/new_user/dossiers/show/_status_progress.html.haml b/app/views/new_user/dossiers/show/_status_progress.html.haml
new file mode 100644
index 000000000..f2acfb447
--- /dev/null
+++ b/app/views/new_user/dossiers/show/_status_progress.html.haml
@@ -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 n’est pas encore visible par l’administration.
+ %p Quand vous aurez terminé, soumettez votre dossier pour qu’il soit examiné.
+
+ - elsif dossier.en_construction?
+ .en-construction
+ %p Un accompagnant de l’administration 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 d’examen par les agent·e·s de l’administration.
+ %p Dès que l’administration 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 l’acceptation
+ %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
diff --git a/app/views/new_user/feedbacks/create.js.erb b/app/views/new_user/feedbacks/create.js.erb
index 1e3fea19f..de4c271b3 100644
--- a/app/views/new_user/feedbacks/create.js.erb
+++ b/app/views/new_user/feedbacks/create.js.erb
@@ -1,4 +1,2 @@
-document.querySelector('#user-satisfaction').innerHTML = '';
-var flashMessagesView = "<%= escape_javascript(render partial: 'layouts/flash_messages') %>";
-document.querySelector("#flash_messages").outerHTML = flashMessagesView;
-<% flash.clear %>
\ No newline at end of file
+<%= remove_element('#user-satisfaction') %>
+<%= render_flash %>
diff --git a/app/views/notification_mailer/refused_mail.html.haml b/app/views/notification_mailer/refused_mail.html.haml
index dc198cead..79f5991c6 100644
--- a/app/views/notification_mailer/refused_mail.html.haml
+++ b/app/views/notification_mailer/refused_mail.html.haml
@@ -4,6 +4,9 @@
%p
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
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--
diff --git a/app/views/notification_mailer/without_continuation_mail.html.haml b/app/views/notification_mailer/without_continuation_mail.html.haml
index ac98bc1a0..c0721aee1 100644
--- a/app/views/notification_mailer/without_continuation_mail.html.haml
+++ b/app/views/notification_mailer/without_continuation_mail.html.haml
@@ -4,6 +4,9 @@
%p
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
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--
diff --git a/app/views/shared/dossiers/_status.html.haml b/app/views/shared/dossiers/_status.html.haml
index 89ec7d002..6afc81dc6 100644
--- a/app/views/shared/dossiers/_status.html.haml
+++ b/app/views/shared/dossiers/_status.html.haml
@@ -1,9 +1,9 @@
- if dossier.brouillon?
%span.label.brouillon brouillon
-- if dossier.en_instruction?
- %span.label.instruction en instruction
- elsif dossier.en_construction?
%span.label.construction en construction
+- if dossier.en_instruction?
+ %span.label.instruction en instruction
- elsif dossier.accepte?
%span.label.accepted accepté
- elsif dossier.refuse?
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
new file mode 100644
index 000000000..40ba1dcdb
--- /dev/null
+++ b/config/initializers/lograge.rb
@@ -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
diff --git a/config/logstasher.yml b/config/logstasher.yml
deleted file mode 100644
index 9ffa58258..000000000
--- a/config/logstasher.yml
+++ /dev/null
@@ -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
diff --git a/config/webpack/production.js b/config/webpack/production.js
index be0f53aac..067efd4e4 100644
--- a/config/webpack/production.js
+++ b/config/webpack/production.js
@@ -2,4 +2,8 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'production'
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()
diff --git a/package.json b/package.json
index 75418d893..15fcf1da8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
{
"dependencies": {
"@rails/webpacker": "4.0.0-pre.2",
+ "@sentry/browser": "^4.0.0-beta.12",
"activestorage": "^5.2.0",
"bloodhound-js": "^1.2.2",
"chartkick": "^2.3.6",
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index c264994bb..09ae4b18f 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -17,7 +17,7 @@ describe ApplicationController, type: :controller do
let(:current_gestionnaire) { nil }
let(:current_administrateur) { nil }
let(:current_administration) { nil }
- let(:payload) { @controller.send(:session_info_payload) }
+ let(:payload) { {} }
before do
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)
@controller.send(:set_raven_context)
+ @controller.send(:append_info_to_payload, payload)
end
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' })
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
context 'when a user is logged in' do
@@ -47,11 +57,15 @@ describe ApplicationController, type: :controller do
end
it do
+ [:db_runtime, :view_runtime, :variant, :rendered_format].each do |key|
+ payload.delete(key)
+ end
expect(payload).to eq({
user_agent: 'Rails Testing',
- current_user_id: current_user.id,
- current_user_email: current_user.email,
- current_user_roles: 'User'
+ user_id: current_user.id,
+ user_email: current_user.email,
+ user_roles: 'User',
+ xhr: false
})
end
end
@@ -68,11 +82,15 @@ describe ApplicationController, type: :controller do
end
it do
+ [:db_runtime, :view_runtime, :variant, :rendered_format].each do |key|
+ payload.delete(key)
+ end
expect(payload).to eq({
user_agent: 'Rails Testing',
- current_user_id: current_user.id,
- current_user_email: current_user.email,
- current_user_roles: 'User, Gestionnaire, Administrateur, Administration'
+ user_id: current_user.id,
+ user_email: current_user.email,
+ user_roles: 'User, Gestionnaire, Administrateur, Administration',
+ xhr: false
})
end
end
diff --git a/spec/factories/dossier.rb b/spec/factories/dossier.rb
index 09b4140fd..fec682138 100644
--- a/spec/factories/dossier.rb
+++ b/spec/factories/dossier.rb
@@ -87,5 +87,48 @@ FactoryBot.define do
dossier.save!
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'
+ 'L’entreprise concernée n’est pas agréée.'
+ when 'sans_suite'
+ 'Le département n’est 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
diff --git a/spec/features/new_user/dossier_details_spec.rb b/spec/features/new_user/dossier_details_spec.rb
index a82cd881b..3804804ae 100644
--- a/spec/features/new_user/dossier_details_spec.rb
+++ b/spec/features/new_user/dossier_details_spec.rb
@@ -6,11 +6,12 @@ describe 'Dossier details:' do
Flipflop::FeatureSet.current.test!.switch!(:new_dossier_details, true)
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
expect(page).to have_current_path(dossier_path(dossier))
expect(page).to have_content(dossier.id)
+ expect(page).to have_selector('.status-explanation')
end
private
diff --git a/spec/models/champs/checkbox_champ_spec.rb b/spec/models/champs/checkbox_champ_spec.rb
new file mode 100644
index 000000000..556a8a69a
--- /dev/null
+++ b/spec/models/champs/checkbox_champ_spec.rb
@@ -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
diff --git a/spec/views/new_user/dossiers/show.html.haml_spec.rb b/spec/views/new_user/dossiers/show.html.haml_spec.rb
index 278315692..c3feada30 100644
--- a/spec/views/new_user/dossiers/show.html.haml_spec.rb
+++ b/spec/views/new_user/dossiers/show.html.haml_spec.rb
@@ -11,6 +11,7 @@ describe 'new_user/dossiers/show.html.haml', type: :view do
subject! { render }
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
diff --git a/spec/views/new_user/dossiers/show/_status_progress.html.haml_spec.rb b/spec/views/new_user/dossiers/show/_status_progress.html.haml_spec.rb
new file mode 100644
index 000000000..3ed819b07
--- /dev/null
+++ b/spec/views/new_user/dossiers/show/_status_progress.html.haml_spec.rb
@@ -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
diff --git a/yarn.lock b/yarn.lock
index 387c088b0..bd59124a8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -49,6 +49,49 @@
dependencies:
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":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"