diff --git a/README.md b/README.md
index ea03f7c33..6277da631 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,12 @@ Afin d'initialiser l'environnement de développement, exécutez la commande suiv
bundle install
yarn install
+## Bouchonnage de l’authentification
+
+Créer les fichiers de configuration avec les valeurs par défaut :
+
+ cp config/france_connect.example.yml config/france_connect.yml
+ cp config/github_secrets.example.yml config/github_secrets.yml
## Création de la base de données
@@ -53,13 +59,6 @@ Afin de générer la BDD de l'application, il est nécessaire d'exécuter les co
# Migrate the development database and the test database
bin/rails db:migrate
-## Bouchonnage de l’authentification
-
-Créer les fichiers de configuration avec les valeurs par défaut :
-
- cp config/france_connect.example.yml config/france_connect.yml
- cp config/github_secrets.example.yml config/github_secrets.yml
-
## Connexion a Pipedrive
Dans le fichier `config/intializers/token.rb`, ajouter
@@ -70,9 +69,9 @@ Dans le fichier `config/intializers/token.rb`, ajouter
## Lancement de l'application
- overmind s
+ overmind start
-Un utilisateur de test est disponible, avec les identifiants `test@exemple.fr`/`testpassword`.
+L'application tourne à l'adresse `http://localhost:3000`. Un utilisateur de test est disponible, avec les identifiants `test@exemple.fr`/`testpassword`.
## Programmation des jobs
diff --git a/app/assets/images/icons/frown-regular.svg b/app/assets/images/icons/frown-regular.svg
new file mode 100644
index 000000000..1ba528365
--- /dev/null
+++ b/app/assets/images/icons/frown-regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/images/icons/meh-regular.svg b/app/assets/images/icons/meh-regular.svg
new file mode 100644
index 000000000..d5069653e
--- /dev/null
+++ b/app/assets/images/icons/meh-regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/images/icons/smile-regular.svg b/app/assets/images/icons/smile-regular.svg
new file mode 100644
index 000000000..9f33fc403
--- /dev/null
+++ b/app/assets/images/icons/smile-regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/javascripts/old_design/dossiers_list_link.js b/app/assets/javascripts/old_design/dossiers_list_link.js
index 7193bb433..18b167ace 100644
--- a/app/assets/javascripts/old_design/dossiers_list_link.js
+++ b/app/assets/javascripts/old_design/dossiers_list_link.js
@@ -1,9 +1,10 @@
$(document).on('turbolinks:load', link_init);
function link_init() {
- $('#dossiers-list tr').on('click', function (event) {
- if (event.target.className !== 'btn-sm btn-danger') {
- $(location).attr('href', $(this).data('dossier_url'));
+ $('#dossiers-list tr').on('click', function(event) {
+ var href = $(this).data('href');
+ if (href && event.target.tagName !== 'A') {
+ location.href = href;
}
});
}
diff --git a/app/assets/stylesheets/new_design/dossier_index.scss b/app/assets/stylesheets/new_design/dossier_index.scss
new file mode 100644
index 000000000..a7b52ca0d
--- /dev/null
+++ b/app/assets/stylesheets/new_design/dossier_index.scss
@@ -0,0 +1,15 @@
+@import "colors";
+@import "constants";
+
+#user-satisfaction {
+ text-align: center;
+ padding: 20px;
+
+ .icon {
+ padding: 10px 5px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/new_design/icons.scss b/app/assets/stylesheets/new_design/icons.scss
index c640357d0..7962cf268 100644
--- a/app/assets/stylesheets/new_design/icons.scss
+++ b/app/assets/stylesheets/new_design/icons.scss
@@ -89,4 +89,16 @@
background-image: image-url("icons/info-blue.svg");
object-fit: contain;
}
+
+ &.smile {
+ background-image: image-url("icons/smile-regular.svg");
+ }
+
+ &.frown {
+ background-image: image-url("icons/frown-regular.svg");
+ }
+
+ &.meh {
+ background-image: image-url("icons/meh-regular.svg");
+ }
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5f13b18e2..2ee12840f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -75,38 +75,37 @@ class ApplicationController < ActionController::Base
].compact
end
+ def logged_user
+ logged_users.first
+ end
+
def logged_user_roles
roles = logged_users.map { |logged_user| logged_user.class.name }
roles.any? ? roles.join(', ') : 'Guest'
end
- def logged_user_info
- logged_user = logged_users.first
-
- if logged_user
- {
- id: logged_user.id,
- email: logged_user.email
- }
- end
- end
-
def set_raven_context
+ user = logged_user
+
context = {
ip_address: request.ip,
+ id: user&.id,
+ email: user&.email,
roles: logged_user_roles
- }
- context.merge!(logged_user_info || {})
+ }.compact
Raven.user_context(context)
end
- def append_info_to_payload(payload)
- payload.merge!({
+ def session_info_payload
+ user = logged_user
+
+ payload = {
user_agent: request.user_agent,
- current_user: logged_user_info,
+ current_user_id: user&.id,
+ current_user_email: user&.email,
current_user_roles: logged_user_roles
- }.compact)
+ }.compact
if browser.known?
payload.merge!({
@@ -115,6 +114,8 @@ class ApplicationController < ActionController::Base
platform: browser.platform.name,
})
end
+
+ payload
end
def reject
diff --git a/app/controllers/new_gestionnaire/avis_controller.rb b/app/controllers/new_gestionnaire/avis_controller.rb
index e20c36812..d926de9da 100644
--- a/app/controllers/new_gestionnaire/avis_controller.rb
+++ b/app/controllers/new_gestionnaire/avis_controller.rb
@@ -96,7 +96,6 @@ module NewGestionnaire
sign_in(gestionnaire, scope: :gestionnaire)
Avis.link_avis_to_gestionnaire(gestionnaire)
- avis = Avis.find(params[:id])
redirect_to url_for(gestionnaire_avis_index_path)
else
flash[:alert] = gestionnaire.errors.full_messages
diff --git a/app/controllers/new_user/dossiers_controller.rb b/app/controllers/new_user/dossiers_controller.rb
index b94747c7e..6f53d7295 100644
--- a/app/controllers/new_user/dossiers_controller.rb
+++ b/app/controllers/new_user/dossiers_controller.rb
@@ -4,8 +4,8 @@ module NewUser
helper_method :new_demarche_url
- before_action :ensure_ownership!, except: [:index, :modifier, :update, :recherche]
- before_action :ensure_ownership_or_invitation!, only: [:modifier, :update]
+ before_action :ensure_ownership!, except: [:index, :show, :modifier, :update, :recherche]
+ before_action :ensure_ownership_or_invitation!, only: [:show, :modifier, :update]
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update]
before_action :forbid_invite_submission!, only: [:update]
@@ -23,6 +23,17 @@ module NewUser
end
end
+ def show
+ if dossier.brouillon?
+ redirect_to modifier_dossier_path(dossier)
+
+ elsif !Flipflop.new_dossier_details?
+ redirect_to users_dossier_recapitulatif_path(dossier)
+ end
+
+ @dossier = dossier
+ end
+
def attestation
send_data(dossier.attestation.pdf.read, filename: 'attestation.pdf', type: 'application/pdf')
end
@@ -209,7 +220,7 @@ module NewUser
end
def draft?
- params[:submit_action] == 'draft'
+ params[:save_draft]
end
end
end
diff --git a/app/controllers/new_user/feedbacks_controller.rb b/app/controllers/new_user/feedbacks_controller.rb
new file mode 100644
index 000000000..9e7ca19f5
--- /dev/null
+++ b/app/controllers/new_user/feedbacks_controller.rb
@@ -0,0 +1,6 @@
+class NewUser::FeedbacksController < ApplicationController
+ def create
+ current_user.feedbacks.create!(mark: params[:mark])
+ flash.notice = "Merci de votre retour"
+ end
+end
diff --git a/app/javascript/packs/application-old.js b/app/javascript/packs/application-old.js
index 38a2ae10f..89c9a9706 100644
--- a/app/javascript/packs/application-old.js
+++ b/app/javascript/packs/application-old.js
@@ -1,6 +1,6 @@
import Turbolinks from 'turbolinks';
import Rails from 'rails-ujs';
-import * as ActiveStorage from 'activestorage';
+import ActiveStorage from '../shared/activestorage/ujs';
import Chartkick from 'chartkick';
import Highcharts from 'highcharts';
import Bloodhound from 'bloodhound-js';
@@ -14,7 +14,6 @@ import 'babel-polyfill';
import 'typeahead.js';
import '../shared/rails-ujs-fix';
-import '../shared/direct-uploads';
// Start Rails helpers
Chartkick.addAdapter(Highcharts);
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 73f5862e8..dce4ef0c5 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -1,7 +1,6 @@
import Turbolinks from 'turbolinks';
import Rails from 'rails-ujs';
-import * as ActiveStorage from 'activestorage';
-
+import ActiveStorage from '../shared/activestorage/ujs';
import Chartkick from 'chartkick';
import Highcharts from 'highcharts';
import jQuery from 'jquery';
@@ -15,7 +14,6 @@ import 'select2';
import 'typeahead.js';
import '../shared/rails-ujs-fix';
-import '../shared/direct-uploads';
import '../new_design/buttons';
import '../new_design/form-validation';
diff --git a/app/javascript/packs/mailjet.js b/app/javascript/packs/mailjet.js
index 30b85a446..358f26c29 100644
--- a/app/javascript/packs/mailjet.js
+++ b/app/javascript/packs/mailjet.js
@@ -1,3 +1,8 @@
+// Include runtime-polyfills for older browsers.
+// Due to .babelrc's 'useBuiltIns', only polyfills actually
+// required by the browsers we support will be included.
+import 'babel-polyfill';
+
// This file is copied from mailjet. We serve here a copy of it ourselves
// to avoid loading javascript files from other domains on the frontpage.
diff --git a/app/javascript/shared/activestorage/direct_upload_controller.js b/app/javascript/shared/activestorage/direct_upload_controller.js
new file mode 100644
index 000000000..81cf3e9ad
--- /dev/null
+++ b/app/javascript/shared/activestorage/direct_upload_controller.js
@@ -0,0 +1,69 @@
+import { DirectUpload } from 'activestorage';
+import { dispatchEvent } from './helpers';
+
+export class DirectUploadController {
+ constructor(input, file) {
+ this.input = input;
+ this.file = file;
+ this.directUpload = new DirectUpload(this.file, this.url, this);
+ this.dispatch('initialize');
+ }
+
+ start(callback) {
+ const hiddenInput = document.createElement('input');
+ hiddenInput.type = 'hidden';
+ hiddenInput.name = this.input.name;
+ this.input.insertAdjacentElement('beforebegin', hiddenInput);
+
+ this.dispatch('start');
+
+ this.directUpload.create((error, attributes) => {
+ if (error) {
+ hiddenInput.parentNode.removeChild(hiddenInput);
+ this.dispatchError(error);
+ } else {
+ hiddenInput.value = attributes.signed_id;
+ }
+
+ this.dispatch('end');
+ callback(error);
+ });
+ }
+
+ uploadRequestDidProgress(event) {
+ const progress = (event.loaded / event.total) * 100;
+ if (progress) {
+ this.dispatch('progress', { progress });
+ }
+ }
+
+ get url() {
+ return this.input.getAttribute('data-direct-upload-url');
+ }
+
+ dispatch(name, detail = {}) {
+ detail.file = this.file;
+ detail.id = this.directUpload.id;
+ return dispatchEvent(this.input, `direct-upload:${name}`, { detail });
+ }
+
+ dispatchError(error) {
+ const event = this.dispatch('error', { error });
+ if (!event.defaultPrevented) {
+ alert(error);
+ }
+ }
+
+ // DirectUpload delegate
+
+ directUploadWillCreateBlobWithXHR(xhr) {
+ this.dispatch('before-blob-request', { xhr });
+ }
+
+ directUploadWillStoreFileWithXHR(xhr) {
+ this.dispatch('before-storage-request', { xhr });
+ xhr.upload.addEventListener('progress', event =>
+ this.uploadRequestDidProgress(event)
+ );
+ }
+}
diff --git a/app/javascript/shared/activestorage/direct_uploads_controller.js b/app/javascript/shared/activestorage/direct_uploads_controller.js
new file mode 100644
index 000000000..c9b453ca8
--- /dev/null
+++ b/app/javascript/shared/activestorage/direct_uploads_controller.js
@@ -0,0 +1,53 @@
+import { DirectUploadController } from './direct_upload_controller';
+import { findElements, dispatchEvent, toArray } from './helpers';
+
+const inputSelector =
+ 'input[type=file][data-direct-upload-url]:not([disabled])';
+
+export class DirectUploadsController {
+ constructor(form) {
+ this.form = form;
+ this.inputs = findElements(form, inputSelector).filter(
+ input => input.files.length
+ );
+ }
+
+ start(callback) {
+ const controllers = this.createDirectUploadControllers();
+
+ const startNextController = () => {
+ const controller = controllers.shift();
+ if (controller) {
+ controller.start(error => {
+ if (error) {
+ callback(error);
+ this.dispatch('end');
+ } else {
+ startNextController();
+ }
+ });
+ } else {
+ callback();
+ this.dispatch('end');
+ }
+ };
+
+ this.dispatch('start');
+ startNextController();
+ }
+
+ createDirectUploadControllers() {
+ const controllers = [];
+ this.inputs.forEach(input => {
+ toArray(input.files).forEach(file => {
+ const controller = new DirectUploadController(input, file);
+ controllers.push(controller);
+ });
+ });
+ return controllers;
+ }
+
+ dispatch(name, detail = {}) {
+ return dispatchEvent(this.form, `direct-uploads:${name}`, { detail });
+ }
+}
diff --git a/app/javascript/shared/activestorage/helpers.js b/app/javascript/shared/activestorage/helpers.js
new file mode 100644
index 000000000..96623fcac
--- /dev/null
+++ b/app/javascript/shared/activestorage/helpers.js
@@ -0,0 +1,51 @@
+export function getMetaValue(name) {
+ const element = findElement(document.head, `meta[name="${name}"]`);
+ if (element) {
+ return element.getAttribute('content');
+ }
+}
+
+export function findElements(root, selector) {
+ if (typeof root == 'string') {
+ selector = root;
+ root = document;
+ }
+ const elements = root.querySelectorAll(selector);
+ return toArray(elements);
+}
+
+export function findElement(root, selector) {
+ if (typeof root == 'string') {
+ selector = root;
+ root = document;
+ }
+ return root.querySelector(selector);
+}
+
+export function dispatchEvent(element, type, eventInit = {}) {
+ const { disabled } = element;
+ const { bubbles, cancelable, detail } = eventInit;
+ const event = document.createEvent('Event');
+
+ event.initEvent(type, bubbles || true, cancelable || true);
+ event.detail = detail || {};
+
+ try {
+ element.disabled = false;
+ element.dispatchEvent(event);
+ } finally {
+ element.disabled = disabled;
+ }
+
+ return event;
+}
+
+export function toArray(value) {
+ if (Array.isArray(value)) {
+ return value;
+ } else if (Array.from) {
+ return Array.from(value);
+ } else {
+ return [].slice.call(value);
+ }
+}
diff --git a/app/javascript/shared/direct-uploads.js b/app/javascript/shared/activestorage/progress.js
similarity index 59%
rename from app/javascript/shared/direct-uploads.js
rename to app/javascript/shared/activestorage/progress.js
index eca7daa45..efaf9349c 100644
--- a/app/javascript/shared/direct-uploads.js
+++ b/app/javascript/shared/activestorage/progress.js
@@ -47,30 +47,3 @@ addEventListener('direct-upload:end', event => {
element.classList.add('direct-upload--complete');
});
-
-addEventListener('turbolinks:load', () => {
- const submitButtons = document.querySelectorAll(
- 'form button[type=submit][data-action]'
- );
- const hiddenInput = document.querySelector(
- 'form input[type=hidden][name=submit_action]'
- );
-
- for (let button of submitButtons) {
- button.addEventListener('click', () => {
- // Active Storage will intercept the form.submit event to upload
- // the attached files, and then fire the submit action again – but forgetting
- // which button was clicked. So we manually set the type of action that trigerred
- // the form submission.
- const action = button.getAttribute('data-action');
- hiddenInput.value = action;
- // Some form fields are marked as mandatory, but when saving a draft we don't want them
- // to be enforced by the browser.
- if (action === 'submit') {
- button.form.removeAttribute('novalidate');
- } else {
- button.form.setAttribute('novalidate', 'novalidate');
- }
- });
- }
-});
diff --git a/app/javascript/shared/activestorage/ujs.js b/app/javascript/shared/activestorage/ujs.js
new file mode 100644
index 000000000..bf70c2310
--- /dev/null
+++ b/app/javascript/shared/activestorage/ujs.js
@@ -0,0 +1,105 @@
+import { DirectUploadsController } from './direct_uploads_controller';
+import { findElement } from './helpers';
+import './progress';
+
+// This is a patched copy of https://github.com/rails/rails/blob/master/activestorage/app/javascript/activestorage/ujs.js
+// It fixes support for multiple input/button elements on direct upload forms
+
+const processingAttribute = 'data-direct-uploads-processing';
+let started = false;
+
+export function start() {
+ if (!started) {
+ started = true;
+ document.addEventListener('submit', didSubmitForm);
+ document.addEventListener('click', didSubmitFormElement);
+ document.addEventListener('ajax:before', didSubmitRemoteElement);
+ }
+}
+
+export default { start };
+
+function didSubmitForm(event) {
+ handleFormSubmissionEvent(event);
+}
+
+function didSubmitFormElement(event) {
+ const { target } = event;
+ if (isSubmitElement(target)) {
+ handleFormSubmissionEvent(formSubmitEvent(event), target);
+ }
+}
+
+function didSubmitRemoteElement(event) {
+ if (event.target.tagName == 'FORM') {
+ handleFormSubmissionEvent(event);
+ }
+}
+
+function formSubmitEvent(event) {
+ return {
+ target: event.target.form,
+ preventDefault() {
+ event.preventDefault();
+ }
+ };
+}
+
+function isSubmitElement({ tagName, type, form }) {
+ if (form && (tagName === 'BUTTON' || tagName === 'INPUT')) {
+ return type === 'submit';
+ }
+ return false;
+}
+
+function handleFormSubmissionEvent(event, button) {
+ const form = event.target;
+
+ if (form.hasAttribute(processingAttribute)) {
+ event.preventDefault();
+ return;
+ }
+
+ const controller = new DirectUploadsController(form);
+ const { inputs } = controller;
+
+ if (inputs.length) {
+ event.preventDefault();
+ form.setAttribute(processingAttribute, '');
+ inputs.forEach(disable);
+ controller.start(error => {
+ form.removeAttribute(processingAttribute);
+ if (error) {
+ inputs.forEach(enable);
+ } else {
+ submitForm(form, button);
+ }
+ });
+ }
+}
+
+function submitForm(form, button) {
+ button = button || findElement(form, 'input[type=submit]');
+ if (button) {
+ const { disabled } = button;
+ button.disabled = false;
+ button.focus();
+ button.click();
+ button.disabled = disabled;
+ } else {
+ button = document.createElement('input');
+ button.type = 'submit';
+ button.style.display = 'none';
+ form.appendChild(button);
+ button.click();
+ form.removeChild(button);
+ }
+}
+
+function disable(input) {
+ input.disabled = true;
+}
+
+function enable(input) {
+ input.disabled = false;
+}
diff --git a/app/models/feedback.rb b/app/models/feedback.rb
new file mode 100644
index 000000000..63affddde
--- /dev/null
+++ b/app/models/feedback.rb
@@ -0,0 +1,3 @@
+class Feedback < ApplicationRecord
+ belongs_to :user
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 7770f5a22..f1287a153 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -16,6 +16,7 @@ class User < ApplicationRecord
has_many :invites, dependent: :destroy
has_many :dossiers_invites, through: :invites, source: :dossier
has_many :piece_justificative, dependent: :destroy
+ has_many :feedbacks, dependent: :destroy
has_one :france_connect_information, dependent: :destroy
delegate :given_name, :family_name, :email_france_connect, :gender, :birthdate, :birthplace, :france_connect_particulier_id, to: :france_connect_information
diff --git a/app/views/admin/procedures/_list.html.haml b/app/views/admin/procedures/_list.html.haml
index f1948b526..b0157955e 100644
--- a/app/views/admin/procedures/_list.html.haml
+++ b/app/views/admin/procedures/_list.html.haml
@@ -13,22 +13,20 @@
- @procedures.each do |procedure|
- procedure = procedure.decorate
- %tr{ id: "tr_dossier_#{procedure.id}", 'data-dossier_url' => admin_procedure_path(id: procedure.id) }
- %td= procedure.id
- %td.col-xs-6
- = procedure.libelle
- - if @active_class
- %td.procedure-lien= link_to procedure.lien, procedure.lien, 'data-method' => :get
- - if @active_class || @archived_class
- %td
- = procedure.published_at_fr
+ - admin_procedure_href = admin_procedure_path(procedure)
+ %tr{ id: "tr_dossier_#{procedure.id}", data: { href: admin_procedure_href } }
+ %td= link_to(procedure.id, admin_procedure_href)
+ %td.col-xs-6= link_to(procedure.libelle, admin_procedure_href)
+ - if procedure.publiee?
+ %td.procedure-lien= link_to(procedure.lien, procedure.lien)
+ - if procedure.publiee_ou_archivee?
+ %td= link_to(procedure.published_at_fr, admin_procedure_href)
- else
- %td
- = procedure.created_at_fr
+ %td= link_to(procedure.created_at_fr, admin_procedure_href)
%td
- = link_to('Cloner', admin_procedure_clone_path(procedure.id), 'data-method' => :put, class: 'btn-sm btn-primary clone-btn')
+ = link_to('Cloner', admin_procedure_clone_path(procedure.id), data: { method: :put }, class: 'btn-sm btn-primary clone-btn')
- if !procedure.publiee_ou_archivee?
- = link_to('X', url_for(controller: 'admin/procedures', action: :destroy, id: procedure.id), 'data-method' => :delete, class: 'btn-sm btn-danger')
+ = link_to('X', url_for(controller: 'admin/procedures', action: :destroy, id: procedure.id), data: { method: :delete }, class: 'btn-sm btn-danger')
= smart_listing.paginate
= smart_listing.pagination_per_page_links
diff --git a/app/views/avis_mailer/avis_invitation.html.haml b/app/views/avis_mailer/avis_invitation.html.haml
index 6bb33dba4..401ba1816 100644
--- a/app/views/avis_mailer/avis_invitation.html.haml
+++ b/app/views/avis_mailer/avis_invitation.html.haml
@@ -12,7 +12,7 @@
- if @avis.gestionnaire.present?
%p
- = link_to "Connectez-vous pour donner votre avis", gestionnaire_dossier_url(@avis.dossier.procedure, @avis.dossier)
+ = link_to "Connectez-vous pour donner votre avis", gestionnaire_avis_url(@avis)
- else
%p
= link_to "Inscrivez-vous pour donner votre avis", sign_up_gestionnaire_avis_url(@avis.id, @avis.email)
diff --git a/app/views/new_user/dossiers/index.html.haml b/app/views/new_user/dossiers/index.html.haml
index f2de5783f..5124dae82 100644
--- a/app/views/new_user/dossiers/index.html.haml
+++ b/app/views/new_user/dossiers/index.html.haml
@@ -15,6 +15,17 @@
= 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
diff --git a/app/views/new_user/dossiers/show.html.haml b/app/views/new_user/dossiers/show.html.haml
new file mode 100644
index 000000000..439052790
--- /dev/null
+++ b/app/views/new_user/dossiers/show.html.haml
@@ -0,0 +1,3 @@
+%h1
+ Dossier
+ = @dossier.id
diff --git a/app/views/new_user/feedbacks/create.js.erb b/app/views/new_user/feedbacks/create.js.erb
new file mode 100644
index 000000000..1e3fea19f
--- /dev/null
+++ b/app/views/new_user/feedbacks/create.js.erb
@@ -0,0 +1,4 @@
+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
diff --git a/app/views/notification_mailer/send_draft_notification.html.haml b/app/views/notification_mailer/send_draft_notification.html.haml
index e8acfea87..0ac60722c 100644
--- a/app/views/notification_mailer/send_draft_notification.html.haml
+++ b/app/views/notification_mailer/send_draft_notification.html.haml
@@ -5,7 +5,7 @@
Vous pouvez retrouver et compléter le brouillon que vous avez créé pour la démarche
%strong= @dossier.procedure.libelle
à l'adresse suivante :
- = link_to users_dossiers_url(liste: 'brouillon'), users_dossiers_url(liste: 'brouillon'), target: '_blank'
+ = link_to dossier_url(@dossier), dossier_url(@dossier), target: '_blank'
%p
Bonne journée,
diff --git a/app/views/root/patron.html.haml b/app/views/root/patron.html.haml
index 69d0400af..b0326c258 100644
--- a/app/views/root/patron.html.haml
+++ b/app/views/root/patron.html.haml
@@ -23,6 +23,9 @@
%span.icon.search
%span.icon.sign-out
%span.icon.info
+ %span.icon.frown
+ %span.icon.meh
+ %span.icon.smile
%h1 Formulaires
diff --git a/app/views/shared/dossiers/_edit.html.haml b/app/views/shared/dossiers/_edit.html.haml
index 2c83362b3..beae0cdbd 100644
--- a/app/views/shared/dossiers/_edit.html.haml
+++ b/app/views/shared/dossiers/_edit.html.haml
@@ -4,7 +4,7 @@
- if apercu
- form_options = { url: '', method: :get, html: { class: 'form', multipart: true } }
- else
- - form_options = { url: modifier_dossier_url(dossier), method: :patch, html: { class: 'form', multipart: true, novalidate: dossier.brouillon? } }
+ - form_options = { url: modifier_dossier_url(dossier), method: :patch, html: { class: 'form', multipart: true } }
= form_for dossier, form_options do |f|
@@ -59,31 +59,31 @@
- if !apercu
.send-wrapper
- = hidden_field_tag 'submit_action', 'draft'
-
- if dossier.brouillon?
- if current_user.owns?(dossier)
= link_to ask_deletion_dossier_path(dossier),
method: :post,
class: 'button danger',
- data: { confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraine l’annulation de la démarche en cours.\n\nConfirmer la suppression ?" } do
+ data: { disable_with: 'Supprimer le brouillon', confirm: 'En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraine l’annulation de la démarche en cours.\n\nConfirmer la suppression ?' } do
Supprimer le brouillon
= f.button 'Enregistrer le brouillon',
formnovalidate: true,
+ name: :save_draft,
+ value: true,
class: 'button send secondary',
- data: { action: 'draft', disable_with: 'Envoi...' }
+ data: { disable_with: 'Enregistrer le brouillon' }
- if dossier.can_transition_to_en_construction?
= f.button 'Soumettre le dossier',
class: 'button send primary',
disabled: !current_user.owns?(dossier),
- data: { action: 'submit', disable_with: 'Envoi...' }
+ data: { disable_with: 'Soumettre le dossier' }
- else
= f.button 'Enregistrer les modifications du dossier',
class: 'button send primary',
- data: { action: 'submit', disable_with: 'Envoi...' }
+ data: { disable_with: 'Enregistrer les modifications du dossier' }
- if dossier.brouillon? && !current_user.owns?(dossier)
.send-notice.invite-cannot-submit
diff --git a/config/features.rb b/config/features.rb
index f5bcb649f..383c95364 100644
--- a/config/features.rb
+++ b/config/features.rb
@@ -17,6 +17,9 @@ Flipflop.configure do
feature :web_hook
+ feature :new_dossier_details,
+ title: "Nouvelle page « Dossier »"
+
group :production do
feature :remote_storage,
default: Rails.env.production? || Rails.env.staging?
diff --git a/config/initializers/logstasher.rb b/config/initializers/logstasher.rb
index d85bb8d6a..0b14a8ede 100644
--- a/config/initializers/logstasher.rb
+++ b/config/initializers/logstasher.rb
@@ -3,17 +3,7 @@ if LogStasher.enabled
fields[:type] = "tps"
end
- LogStasher.watch('process_action.action_controller') do |name, start, finish, id, payload, store|
- store[:user_agent] = payload[:user_agent]
- store[:browser] = payload[:browser]
- store[:browser_version] = payload[:browser_version]
- store[:platform] = payload[:platform]
-
- store[:current_user_roles] = payload[:current_user_roles]
-
- if payload[:current_user].present?
- store[:current_user_id] = payload[:current_user][:id]
- store[:current_user_email] = payload[:current_user][:email]
- end
+ LogStasher.add_custom_fields_to_request_context do |fields|
+ fields.merge!(session_info_payload)
end
end
diff --git a/config/locales/models/type_de_champ/fr.yml b/config/locales/models/type_de_champ/fr.yml
index 465fa0745..aa6a33b41 100644
--- a/config/locales/models/type_de_champ/fr.yml
+++ b/config/locales/models/type_de_champ/fr.yml
@@ -10,7 +10,7 @@ fr:
date: 'Date'
datetime: 'Date et Heure'
number: 'Nombre'
- checkbox: 'Checkbox'
+ checkbox: 'Case à cocher'
civilite: 'Civilité'
email: 'Email'
phone: 'Téléphone'
diff --git a/config/logstasher.yml b/config/logstasher.yml
new file mode 100644
index 000000000..9ffa58258
--- /dev/null
+++ b/config/logstasher.yml
@@ -0,0 +1,11 @@
+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/routes.rb b/config/routes.rb
index ce2bf183c..e7830425c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -267,7 +267,7 @@ Rails.application.routes.draw do
#
scope module: 'new_user' do
- resources :dossiers, only: [:index, :update] do
+ resources :dossiers, only: [:index, :show, :update] do
member do
get 'identite'
patch 'update_identite'
@@ -282,6 +282,7 @@ Rails.application.routes.draw do
post 'recherche'
end
end
+ resource :feedback, only: [:create]
end
#
diff --git a/db/migrate/20180808142237_create_feedbacks.rb b/db/migrate/20180808142237_create_feedbacks.rb
new file mode 100644
index 000000000..b79ed6dc3
--- /dev/null
+++ b/db/migrate/20180808142237_create_feedbacks.rb
@@ -0,0 +1,10 @@
+class CreateFeedbacks < ActiveRecord::Migration[5.2]
+ def change
+ create_table :feedbacks do |t|
+ t.references :user, foreign_key: true
+ t.integer :mark
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8f1d9c2dd..8358eb230 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2018_07_24_153247) do
+ActiveRecord::Schema.define(version: 2018_08_08_142237) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -328,6 +328,14 @@ ActiveRecord::Schema.define(version: 2018_07_24_153247) do
t.datetime "updated_at"
end
+ create_table "feedbacks", force: :cascade do |t|
+ t.bigint "user_id"
+ t.integer "mark"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_feedbacks_on_user_id"
+ end
+
create_table "flipflop_features", force: :cascade do |t|
t.string "key", null: false
t.boolean "enabled", default: false, null: false
@@ -625,6 +633,7 @@ ActiveRecord::Schema.define(version: 2018_07_24_153247) do
add_foreign_key "closed_mails", "procedures"
add_foreign_key "commentaires", "dossiers"
add_foreign_key "dossiers", "users"
+ add_foreign_key "feedbacks", "users"
add_foreign_key "initiated_mails", "procedures"
add_foreign_key "procedure_paths", "administrateurs"
add_foreign_key "procedure_paths", "procedures"
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index ce2dbc1c1..c264994bb 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) { {} }
+ let(:payload) { @controller.send(:session_info_payload) }
before do
expect(@controller).to receive(:current_user).and_return(current_user)
@@ -27,7 +27,6 @@ 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
@@ -50,10 +49,8 @@ describe ApplicationController, type: :controller do
it do
expect(payload).to eq({
user_agent: 'Rails Testing',
- current_user: {
- id: current_user.id,
- email: current_user.email
- },
+ current_user_id: current_user.id,
+ current_user_email: current_user.email,
current_user_roles: 'User'
})
end
@@ -73,10 +70,8 @@ describe ApplicationController, type: :controller do
it do
expect(payload).to eq({
user_agent: 'Rails Testing',
- current_user: {
- id: current_user.id,
- email: current_user.email
- },
+ current_user_id: current_user.id,
+ current_user_email: current_user.email,
current_user_roles: 'User, Gestionnaire, Administrateur, Administration'
})
end
diff --git a/spec/controllers/new_user/dossiers_controller_spec.rb b/spec/controllers/new_user/dossiers_controller_spec.rb
index 15f75cae1..c62e00de9 100644
--- a/spec/controllers/new_user/dossiers_controller_spec.rb
+++ b/spec/controllers/new_user/dossiers_controller_spec.rb
@@ -100,38 +100,38 @@ describe NewUser::DossiersController, type: :controller do
let(:user) { create(:user) }
let(:asked_dossier) { create(:dossier) }
let(:ensure_authorized) { :forbid_invite_submission! }
- let(:submit_action) { 'submit' }
+ let(:draft) { false }
before do
- @controller.params = @controller.params.merge(dossier_id: asked_dossier.id, submit_action: submit_action)
+ @controller.params = @controller.params.merge(dossier_id: asked_dossier.id, save_draft: draft)
allow(@controller).to receive(:current_user).and_return(user)
allow(@controller).to receive(:redirect_to)
end
context 'when a user save their own draft' do
let(:asked_dossier) { create(:dossier, user: user) }
- let(:submit_action) { 'draft' }
+ let(:draft) { true }
it_behaves_like 'does not redirect nor flash'
end
context 'when a user submit their own dossier' do
let(:asked_dossier) { create(:dossier, user: user) }
- let(:submit_action) { 'submit' }
+ let(:draft) { false }
it_behaves_like 'does not redirect nor flash'
end
context 'when an invite save the draft for a dossier where they where invited' do
before { create(:invite, dossier: asked_dossier, user: user, type: 'InviteUser') }
- let(:submit_action) { 'draft' }
+ let(:draft) { true }
it_behaves_like 'does not redirect nor flash'
end
context 'when an invite submit a dossier where they where invited' do
before { create(:invite, dossier: asked_dossier, user: user, type: 'InviteUser') }
- let(:submit_action) { 'submit' }
+ let(:draft) { false }
it_behaves_like 'redirects and flashes'
end
@@ -353,7 +353,7 @@ describe NewUser::DossiersController, type: :controller do
it { expect(flash.alert).to eq(['Le champ l doit être rempli.', 'pj']) }
context 'and the user saves a draft' do
- let(:payload) { submit_payload.merge(submit_action: 'draft') }
+ let(:payload) { submit_payload.merge(save_draft: true) }
it { expect(response).to render_template(:modifier) }
it { expect(flash.notice).to eq('Votre brouillon a bien été sauvegardé.') }
@@ -376,7 +376,7 @@ describe NewUser::DossiersController, type: :controller do
let!(:invite) { create(:invite, dossier: dossier, user: user, type: 'InviteUser') }
context 'and the invite saves a draft' do
- let(:payload) { submit_payload.merge(submit_action: 'draft') }
+ let(:payload) { submit_payload.merge(save_draft: true) }
before do
first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
@@ -478,6 +478,37 @@ describe NewUser::DossiersController, type: :controller do
end
end
+ describe '#show' do
+ let(:new_dossier_details_enabled) { false }
+
+ before do
+ Flipflop::FeatureSet.current.test!.switch!(:new_dossier_details, new_dossier_details_enabled)
+ sign_in(user)
+ end
+
+ subject! { get(:show, params: { id: dossier.id }) }
+
+ context 'when the dossier is a brouillon' do
+ let(:dossier) { create(:dossier, user: user) }
+ it { is_expected.to redirect_to(modifier_dossier_path(dossier)) }
+ end
+
+ context 'when the dossier has been submitted' do
+ let(:dossier) { create(:dossier, :en_construction, user: user) }
+
+ context 'and the new dossier details page is disabled' do
+ let(:new_dossier_details_enabled) { false }
+ it { is_expected.to redirect_to(users_dossier_recapitulatif_path(dossier)) }
+ end
+
+ context 'and the new dossier details page is enabled' do
+ let(:new_dossier_details_enabled) { true }
+ it { expect(assigns(:dossier)).to eq(dossier) }
+ it { is_expected.to render_template(:show) }
+ end
+ end
+ end
+
describe '#ask_deletion' do
before { sign_in(user) }
diff --git a/spec/factories/feedback.rb b/spec/factories/feedback.rb
new file mode 100644
index 000000000..231989f34
--- /dev/null
+++ b/spec/factories/feedback.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+ factory :feedback do
+ mark 3
+ end
+end
diff --git a/spec/features/new_user/dossier_details_spec.rb b/spec/features/new_user/dossier_details_spec.rb
new file mode 100644
index 000000000..a82cd881b
--- /dev/null
+++ b/spec/features/new_user/dossier_details_spec.rb
@@ -0,0 +1,28 @@
+describe 'Dossier details:' do
+ let(:user) { create(:user) }
+ let(:dossier) { create(:dossier, :en_construction, user: user) }
+
+ before do
+ Flipflop::FeatureSet.current.test!.switch!(:new_dossier_details, true)
+ end
+
+ scenario 'the user can see the details of their dossier' do
+ visit_dossier dossier
+
+ expect(page).to have_current_path(dossier_path(dossier))
+ expect(page).to have_content(dossier.id)
+ end
+
+ private
+
+ def visit_dossier(dossier)
+ visit dossier_path(dossier)
+
+ expect(page).to have_current_path(new_user_session_path)
+ fill_in 'user_email', with: user.email
+ fill_in 'user_password', with: user.password
+ click_on 'Se connecter'
+
+ expect(page).to have_current_path(dossier_path(dossier))
+ end
+end
diff --git a/spec/mailers/avis_mailer_spec.rb b/spec/mailers/avis_mailer_spec.rb
index 69e12687f..4d4af26dd 100644
--- a/spec/mailers/avis_mailer_spec.rb
+++ b/spec/mailers/avis_mailer_spec.rb
@@ -9,5 +9,15 @@ RSpec.describe AvisMailer, type: :mailer do
it { expect(subject.subject).to eq("Donnez votre avis sur le dossier nº #{avis.dossier.id} (#{avis.dossier.procedure.libelle})") }
it { expect(subject.body).to include("Vous avez été invité par #{avis.claimant.email} à donner votre avis sur le dossier nº #{avis.dossier.id} de la procédure "#{avis.dossier.procedure.libelle}".") }
it { expect(subject.body).to include(avis.introduction) }
+ it { expect(subject.body).to include(gestionnaire_avis_url(avis)) }
+
+ context 'when the recipient is not already registered' do
+ before do
+ avis.email = 'accompagnateur@email.com'
+ avis.gestionnaire = nil
+ end
+
+ it { expect(subject.body).to include(sign_up_gestionnaire_avis_url(avis.id, avis.email)) }
+ end
end
end
diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb
index e873d2448..de51f60e9 100644
--- a/spec/models/dossier_spec.rb
+++ b/spec/models/dossier_spec.rb
@@ -556,6 +556,8 @@ describe Dossier do
end
describe "#send_draft_notification_email" do
+ include Rails.application.routes.url_helpers
+
let(:procedure) { create(:procedure) }
let(:user) { create(:user) }
@@ -564,15 +566,17 @@ describe Dossier do
end
it "send an email when the dossier is created for the very first time" do
+ dossier = nil
ActiveJob::Base.queue_adapter = :test
expect do
perform_enqueued_jobs do
- Dossier.create(procedure: procedure, state: "brouillon", user: user)
+ dossier = Dossier.create(procedure: procedure, state: "brouillon", user: user)
end
end.to change(ActionMailer::Base.deliveries, :size).from(0).to(1)
mail = ActionMailer::Base.deliveries.last
expect(mail.subject).to eq("Retrouvez votre brouillon pour la démarche \"#{procedure.libelle}\"")
+ expect(mail.html_part.body).to include(dossier_url(dossier))
end
it "does not send an email when the dossier is created with a non brouillon state" do
diff --git a/spec/views/new_user/dossiers/index.html.haml_spec.rb b/spec/views/new_user/dossiers/index.html.haml_spec.rb
index 3df0520b7..9ee653464 100644
--- a/spec/views/new_user/dossiers/index.html.haml_spec.rb
+++ b/spec/views/new_user/dossiers/index.html.haml_spec.rb
@@ -10,6 +10,7 @@ describe 'new_user/dossiers/index.html.haml', type: :view do
before do
allow(view).to receive(:new_demarche_url).and_return('#')
+ allow(controller).to receive(:current_user) { user }
assign(:user_dossiers, Kaminari.paginate_array(user_dossiers).page(1))
assign(:dossiers_invites, Kaminari.paginate_array(dossiers_invites).page(1))
assign(:dossiers, Kaminari.paginate_array(user_dossiers).page(1))
@@ -70,4 +71,17 @@ describe 'new_user/dossiers/index.html.haml', type: :view do
expect(rendered).to have_selector('ul.tabs li.active', count: 1)
end
end
+
+ context "quand le user n'a aucun feedback" do
+ it "affiche le formulaire de satisfaction" do
+ expect(rendered).to have_selector('#user-satisfaction', text: 'Que pensez-vous de ce service ?')
+ end
+ end
+
+ context "quand le user a un feedback" do
+ let(:user) { create(:user, feedbacks: [build(:feedback)]) }
+ it "n'affiche pas le formulaire de satisfaction" do
+ expect(rendered).to_not have_selector('#user-satisfaction')
+ 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
new file mode 100644
index 000000000..278315692
--- /dev/null
+++ b/spec/views/new_user/dossiers/show.html.haml_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe 'new_user/dossiers/show.html.haml', type: :view do
+ let(:dossier) { create(:dossier, :with_service, state: 'brouillon', procedure: create(:procedure)) }
+
+ before do
+ sign_in dossier.user
+ assign(:dossier, dossier)
+ end
+
+ subject! { render }
+
+ it 'affiche les informations du dossier' do
+ expect(rendered).to have_text("Dossier #{dossier.id}")
+ end
+end