Merge pull request #2373 from betagouv/dev

Welcome Lucien
This commit is contained in:
LeSim 2018-08-10 11:25:29 +02:00 committed by GitHub
commit 4839d4be2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 571 additions and 121 deletions

View file

@ -34,6 +34,12 @@ Afin d'initialiser l'environnement de développement, exécutez la commande suiv
bundle install bundle install
yarn install yarn install
## Bouchonnage de lauthentification
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 ## 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 # Migrate the development database and the test database
bin/rails db:migrate bin/rails db:migrate
## Bouchonnage de lauthentification
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 ## Connexion a Pipedrive
Dans le fichier `config/intializers/token.rb`, ajouter Dans le fichier `config/intializers/token.rb`, ajouter
@ -70,9 +69,9 @@ Dans le fichier `config/intializers/token.rb`, ajouter
## Lancement de l'application ## 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 ## Programmation des jobs

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 128c-40.2 0-78 17.7-103.8 48.6-8.5 10.2-7.1 25.3 3.1 33.8 10.2 8.4 25.3 7.1 33.8-3.1 16.6-19.9 41-31.4 66.9-31.4s50.3 11.4 66.9 31.4c8.1 9.7 23.1 11.9 33.8 3.1 10.2-8.5 11.5-23.6 3.1-33.8C326 321.7 288.2 304 248 304z" fill="#dc3832"/></svg>

After

Width:  |  Height:  |  Size: 613 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm8 144H160c-13.2 0-24 10.8-24 24s10.8 24 24 24h176c13.2 0 24-10.8 24-24s-10.8-24-24-24z" fill="#f9b916"/></svg>

After

Width:  |  Height:  |  Size: 479 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z" fill="#4daa75"/></svg>

After

Width:  |  Height:  |  Size: 604 B

View file

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

View file

@ -0,0 +1,15 @@
@import "colors";
@import "constants";
#user-satisfaction {
text-align: center;
padding: 20px;
.icon {
padding: 10px 5px;
&:hover {
cursor: pointer;
}
}
}

View file

@ -89,4 +89,16 @@
background-image: image-url("icons/info-blue.svg"); background-image: image-url("icons/info-blue.svg");
object-fit: contain; 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");
}
} }

View file

@ -75,38 +75,37 @@ class ApplicationController < ActionController::Base
].compact ].compact
end end
def logged_user
logged_users.first
end
def logged_user_roles def logged_user_roles
roles = logged_users.map { |logged_user| logged_user.class.name } roles = logged_users.map { |logged_user| logged_user.class.name }
roles.any? ? roles.join(', ') : 'Guest' roles.any? ? roles.join(', ') : 'Guest'
end 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 def set_raven_context
user = logged_user
context = { context = {
ip_address: request.ip, ip_address: request.ip,
id: user&.id,
email: user&.email,
roles: logged_user_roles roles: logged_user_roles
} }.compact
context.merge!(logged_user_info || {})
Raven.user_context(context) Raven.user_context(context)
end end
def append_info_to_payload(payload) def session_info_payload
payload.merge!({ user = logged_user
payload = {
user_agent: request.user_agent, 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 current_user_roles: logged_user_roles
}.compact) }.compact
if browser.known? if browser.known?
payload.merge!({ payload.merge!({
@ -115,6 +114,8 @@ class ApplicationController < ActionController::Base
platform: browser.platform.name, platform: browser.platform.name,
}) })
end end
payload
end end
def reject def reject

View file

@ -96,7 +96,6 @@ module NewGestionnaire
sign_in(gestionnaire, scope: :gestionnaire) sign_in(gestionnaire, scope: :gestionnaire)
Avis.link_avis_to_gestionnaire(gestionnaire) Avis.link_avis_to_gestionnaire(gestionnaire)
avis = Avis.find(params[:id])
redirect_to url_for(gestionnaire_avis_index_path) redirect_to url_for(gestionnaire_avis_index_path)
else else
flash[:alert] = gestionnaire.errors.full_messages flash[:alert] = gestionnaire.errors.full_messages

View file

@ -4,8 +4,8 @@ module NewUser
helper_method :new_demarche_url helper_method :new_demarche_url
before_action :ensure_ownership!, except: [:index, :modifier, :update, :recherche] before_action :ensure_ownership!, except: [:index, :show, :modifier, :update, :recherche]
before_action :ensure_ownership_or_invitation!, only: [:modifier, :update] before_action :ensure_ownership_or_invitation!, only: [:show, :modifier, :update]
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update] before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update]
before_action :forbid_invite_submission!, only: [:update] before_action :forbid_invite_submission!, only: [:update]
@ -23,6 +23,17 @@ module NewUser
end end
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 def attestation
send_data(dossier.attestation.pdf.read, filename: 'attestation.pdf', type: 'application/pdf') send_data(dossier.attestation.pdf.read, filename: 'attestation.pdf', type: 'application/pdf')
end end
@ -209,7 +220,7 @@ module NewUser
end end
def draft? def draft?
params[:submit_action] == 'draft' params[:save_draft]
end end
end end
end end

View file

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

View file

@ -1,6 +1,6 @@
import Turbolinks from 'turbolinks'; import Turbolinks from 'turbolinks';
import Rails from 'rails-ujs'; import Rails from 'rails-ujs';
import * as ActiveStorage from 'activestorage'; import ActiveStorage from '../shared/activestorage/ujs';
import Chartkick from 'chartkick'; import Chartkick from 'chartkick';
import Highcharts from 'highcharts'; import Highcharts from 'highcharts';
import Bloodhound from 'bloodhound-js'; import Bloodhound from 'bloodhound-js';
@ -14,7 +14,6 @@ import 'babel-polyfill';
import 'typeahead.js'; import 'typeahead.js';
import '../shared/rails-ujs-fix'; import '../shared/rails-ujs-fix';
import '../shared/direct-uploads';
// Start Rails helpers // Start Rails helpers
Chartkick.addAdapter(Highcharts); Chartkick.addAdapter(Highcharts);

View file

@ -1,7 +1,6 @@
import Turbolinks from 'turbolinks'; import Turbolinks from 'turbolinks';
import Rails from 'rails-ujs'; import Rails from 'rails-ujs';
import * as ActiveStorage from 'activestorage'; import ActiveStorage from '../shared/activestorage/ujs';
import Chartkick from 'chartkick'; import Chartkick from 'chartkick';
import Highcharts from 'highcharts'; import Highcharts from 'highcharts';
import jQuery from 'jquery'; import jQuery from 'jquery';
@ -15,7 +14,6 @@ import 'select2';
import 'typeahead.js'; import 'typeahead.js';
import '../shared/rails-ujs-fix'; import '../shared/rails-ujs-fix';
import '../shared/direct-uploads';
import '../new_design/buttons'; import '../new_design/buttons';
import '../new_design/form-validation'; import '../new_design/form-validation';

View file

@ -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 // 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. // to avoid loading javascript files from other domains on the frontpage.

View file

@ -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)
);
}
}

View file

@ -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 });
}
}

View file

@ -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);
}
}

View file

@ -47,30 +47,3 @@ addEventListener('direct-upload:end', event => {
element.classList.add('direct-upload--complete'); 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');
}
});
}
});

View file

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

3
app/models/feedback.rb Normal file
View file

@ -0,0 +1,3 @@
class Feedback < ApplicationRecord
belongs_to :user
end

View file

@ -16,6 +16,7 @@ class User < ApplicationRecord
has_many :invites, dependent: :destroy has_many :invites, dependent: :destroy
has_many :dossiers_invites, through: :invites, source: :dossier has_many :dossiers_invites, through: :invites, source: :dossier
has_many :piece_justificative, dependent: :destroy has_many :piece_justificative, dependent: :destroy
has_many :feedbacks, dependent: :destroy
has_one :france_connect_information, 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 delegate :given_name, :family_name, :email_france_connect, :gender, :birthdate, :birthplace, :france_connect_particulier_id, to: :france_connect_information

View file

@ -13,22 +13,20 @@
- @procedures.each do |procedure| - @procedures.each do |procedure|
- procedure = procedure.decorate - procedure = procedure.decorate
%tr{ id: "tr_dossier_#{procedure.id}", 'data-dossier_url' => admin_procedure_path(id: procedure.id) } - admin_procedure_href = admin_procedure_path(procedure)
%td= procedure.id %tr{ id: "tr_dossier_#{procedure.id}", data: { href: admin_procedure_href } }
%td.col-xs-6 %td= link_to(procedure.id, admin_procedure_href)
= procedure.libelle %td.col-xs-6= link_to(procedure.libelle, admin_procedure_href)
- if @active_class - if procedure.publiee?
%td.procedure-lien= link_to procedure.lien, procedure.lien, 'data-method' => :get %td.procedure-lien= link_to(procedure.lien, procedure.lien)
- if @active_class || @archived_class - if procedure.publiee_ou_archivee?
%td %td= link_to(procedure.published_at_fr, admin_procedure_href)
= procedure.published_at_fr
- else - else
%td %td= link_to(procedure.created_at_fr, admin_procedure_href)
= procedure.created_at_fr
%td %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? - 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.paginate
= smart_listing.pagination_per_page_links = smart_listing.pagination_per_page_links

View file

@ -12,7 +12,7 @@
- if @avis.gestionnaire.present? - if @avis.gestionnaire.present?
%p %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 - else
%p %p
= link_to "Inscrivez-vous pour donner votre avis", sign_up_gestionnaire_avis_url(@avis.id, @avis.email) = link_to "Inscrivez-vous pour donner votre avis", sign_up_gestionnaire_avis_url(@avis.id, @avis.email)

View file

@ -15,6 +15,17 @@
= 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

View file

@ -0,0 +1,3 @@
%h1
Dossier
= @dossier.id

View file

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

View file

@ -5,7 +5,7 @@
Vous pouvez retrouver et compléter le brouillon que vous avez créé pour la démarche Vous pouvez retrouver et compléter le brouillon que vous avez créé pour la démarche
%strong= @dossier.procedure.libelle %strong= @dossier.procedure.libelle
à l'adresse suivante : à 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 %p
Bonne journée, Bonne journée,

View file

@ -23,6 +23,9 @@
%span.icon.search %span.icon.search
%span.icon.sign-out %span.icon.sign-out
%span.icon.info %span.icon.info
%span.icon.frown
%span.icon.meh
%span.icon.smile
%h1 Formulaires %h1 Formulaires

View file

@ -4,7 +4,7 @@
- if apercu - if apercu
- form_options = { url: '', method: :get, html: { class: 'form', multipart: true } } - form_options = { url: '', method: :get, html: { class: 'form', multipart: true } }
- else - 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| = form_for dossier, form_options do |f|
@ -59,31 +59,31 @@
- if !apercu - if !apercu
.send-wrapper .send-wrapper
= hidden_field_tag 'submit_action', 'draft'
- if dossier.brouillon? - if dossier.brouillon?
- if current_user.owns?(dossier) - if current_user.owns?(dossier)
= link_to ask_deletion_dossier_path(dossier), = link_to ask_deletion_dossier_path(dossier),
method: :post, method: :post,
class: 'button danger', class: 'button danger',
data: { confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations quil contient. Toute suppression entraine lannulation 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 quil contient. Toute suppression entraine lannulation de la démarche en cours.\n\nConfirmer la suppression ?' } do
Supprimer le brouillon Supprimer le brouillon
= f.button 'Enregistrer le brouillon', = f.button 'Enregistrer le brouillon',
formnovalidate: true, formnovalidate: true,
name: :save_draft,
value: true,
class: 'button send secondary', class: 'button send secondary',
data: { action: 'draft', disable_with: 'Envoi...' } data: { disable_with: 'Enregistrer le brouillon' }
- if dossier.can_transition_to_en_construction? - if dossier.can_transition_to_en_construction?
= f.button 'Soumettre le dossier', = f.button 'Soumettre le dossier',
class: 'button send primary', class: 'button send primary',
disabled: !current_user.owns?(dossier), disabled: !current_user.owns?(dossier),
data: { action: 'submit', disable_with: 'Envoi...' } data: { disable_with: 'Soumettre le dossier' }
- else - else
= f.button 'Enregistrer les modifications du dossier', = f.button 'Enregistrer les modifications du dossier',
class: 'button send primary', 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) - if dossier.brouillon? && !current_user.owns?(dossier)
.send-notice.invite-cannot-submit .send-notice.invite-cannot-submit

View file

@ -17,6 +17,9 @@ Flipflop.configure do
feature :web_hook feature :web_hook
feature :new_dossier_details,
title: "Nouvelle page « Dossier »"
group :production do group :production do
feature :remote_storage, feature :remote_storage,
default: Rails.env.production? || Rails.env.staging? default: Rails.env.production? || Rails.env.staging?

View file

@ -3,17 +3,7 @@ if LogStasher.enabled
fields[:type] = "tps" fields[:type] = "tps"
end end
LogStasher.watch('process_action.action_controller') do |name, start, finish, id, payload, store| LogStasher.add_custom_fields_to_request_context do |fields|
store[:user_agent] = payload[:user_agent] fields.merge!(session_info_payload)
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
end end
end end

View file

@ -10,7 +10,7 @@ fr:
date: 'Date' date: 'Date'
datetime: 'Date et Heure' datetime: 'Date et Heure'
number: 'Nombre' number: 'Nombre'
checkbox: 'Checkbox' checkbox: 'Case à cocher'
civilite: 'Civilité' civilite: 'Civilité'
email: 'Email' email: 'Email'
phone: 'Téléphone' phone: 'Téléphone'

11
config/logstasher.yml Normal file
View file

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

View file

@ -267,7 +267,7 @@ Rails.application.routes.draw do
# #
scope module: 'new_user' do scope module: 'new_user' do
resources :dossiers, only: [:index, :update] do resources :dossiers, only: [:index, :show, :update] do
member do member do
get 'identite' get 'identite'
patch 'update_identite' patch 'update_identite'
@ -282,6 +282,7 @@ Rails.application.routes.draw do
post 'recherche' post 'recherche'
end end
end end
resource :feedback, only: [:create]
end end
# #

View file

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

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -328,6 +328,14 @@ ActiveRecord::Schema.define(version: 2018_07_24_153247) do
t.datetime "updated_at" t.datetime "updated_at"
end 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| create_table "flipflop_features", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.boolean "enabled", default: false, 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 "closed_mails", "procedures"
add_foreign_key "commentaires", "dossiers" add_foreign_key "commentaires", "dossiers"
add_foreign_key "dossiers", "users" add_foreign_key "dossiers", "users"
add_foreign_key "feedbacks", "users"
add_foreign_key "initiated_mails", "procedures" add_foreign_key "initiated_mails", "procedures"
add_foreign_key "procedure_paths", "administrateurs" add_foreign_key "procedure_paths", "administrateurs"
add_foreign_key "procedure_paths", "procedures" add_foreign_key "procedure_paths", "procedures"

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) { {} } let(:payload) { @controller.send(:session_info_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,7 +27,6 @@ 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
@ -50,10 +49,8 @@ describe ApplicationController, type: :controller do
it do it do
expect(payload).to eq({ expect(payload).to eq({
user_agent: 'Rails Testing', user_agent: 'Rails Testing',
current_user: { current_user_id: current_user.id,
id: current_user.id, current_user_email: current_user.email,
email: current_user.email
},
current_user_roles: 'User' current_user_roles: 'User'
}) })
end end
@ -73,10 +70,8 @@ describe ApplicationController, type: :controller do
it do it do
expect(payload).to eq({ expect(payload).to eq({
user_agent: 'Rails Testing', user_agent: 'Rails Testing',
current_user: { current_user_id: current_user.id,
id: current_user.id, current_user_email: current_user.email,
email: current_user.email
},
current_user_roles: 'User, Gestionnaire, Administrateur, Administration' current_user_roles: 'User, Gestionnaire, Administrateur, Administration'
}) })
end end

View file

@ -100,38 +100,38 @@ describe NewUser::DossiersController, type: :controller do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:asked_dossier) { create(:dossier) } let(:asked_dossier) { create(:dossier) }
let(:ensure_authorized) { :forbid_invite_submission! } let(:ensure_authorized) { :forbid_invite_submission! }
let(:submit_action) { 'submit' } let(:draft) { false }
before do 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(:current_user).and_return(user)
allow(@controller).to receive(:redirect_to) allow(@controller).to receive(:redirect_to)
end end
context 'when a user save their own draft' do context 'when a user save their own draft' do
let(:asked_dossier) { create(:dossier, user: user) } let(:asked_dossier) { create(:dossier, user: user) }
let(:submit_action) { 'draft' } let(:draft) { true }
it_behaves_like 'does not redirect nor flash' it_behaves_like 'does not redirect nor flash'
end end
context 'when a user submit their own dossier' do context 'when a user submit their own dossier' do
let(:asked_dossier) { create(:dossier, user: user) } let(:asked_dossier) { create(:dossier, user: user) }
let(:submit_action) { 'submit' } let(:draft) { false }
it_behaves_like 'does not redirect nor flash' it_behaves_like 'does not redirect nor flash'
end end
context 'when an invite save the draft for a dossier where they where invited' do 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') } 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' it_behaves_like 'does not redirect nor flash'
end end
context 'when an invite submit a dossier where they where invited' do context 'when an invite submit a dossier where they where invited' do
before { create(:invite, dossier: asked_dossier, user: user, type: 'InviteUser') } before { create(:invite, dossier: asked_dossier, user: user, type: 'InviteUser') }
let(:submit_action) { 'submit' } let(:draft) { false }
it_behaves_like 'redirects and flashes' it_behaves_like 'redirects and flashes'
end end
@ -353,7 +353,7 @@ describe NewUser::DossiersController, type: :controller do
it { expect(flash.alert).to eq(['Le champ l doit être rempli.', 'pj']) } it { expect(flash.alert).to eq(['Le champ l doit être rempli.', 'pj']) }
context 'and the user saves a draft' do 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(response).to render_template(:modifier) }
it { expect(flash.notice).to eq('Votre brouillon a bien été sauvegardé.') } 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') } let!(:invite) { create(:invite, dossier: dossier, user: user, type: 'InviteUser') }
context 'and the invite saves a draft' do 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 before do
first_champ.type_de_champ.update(mandatory: true, libelle: 'l') first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
@ -478,6 +478,37 @@ describe NewUser::DossiersController, type: :controller do
end end
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 describe '#ask_deletion' do
before { sign_in(user) } before { sign_in(user) }

View file

@ -0,0 +1,5 @@
FactoryBot.define do
factory :feedback do
mark 3
end
end

View file

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

View file

@ -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.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 &quot;#{avis.dossier.procedure.libelle}&quot;.") } 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 &quot;#{avis.dossier.procedure.libelle}&quot;.") }
it { expect(subject.body).to include(avis.introduction) } 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
end end

View file

@ -556,6 +556,8 @@ describe Dossier do
end end
describe "#send_draft_notification_email" do describe "#send_draft_notification_email" do
include Rails.application.routes.url_helpers
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }
let(:user) { create(:user) } let(:user) { create(:user) }
@ -564,15 +566,17 @@ describe Dossier do
end end
it "send an email when the dossier is created for the very first time" do it "send an email when the dossier is created for the very first time" do
dossier = nil
ActiveJob::Base.queue_adapter = :test ActiveJob::Base.queue_adapter = :test
expect do expect do
perform_enqueued_jobs do perform_enqueued_jobs do
Dossier.create(procedure: procedure, state: "brouillon", user: user) dossier = Dossier.create(procedure: procedure, state: "brouillon", user: user)
end end
end.to change(ActionMailer::Base.deliveries, :size).from(0).to(1) end.to change(ActionMailer::Base.deliveries, :size).from(0).to(1)
mail = ActionMailer::Base.deliveries.last mail = ActionMailer::Base.deliveries.last
expect(mail.subject).to eq("Retrouvez votre brouillon pour la démarche \"#{procedure.libelle}\"") 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 end
it "does not send an email when the dossier is created with a non brouillon state" do it "does not send an email when the dossier is created with a non brouillon state" do

View file

@ -10,6 +10,7 @@ describe 'new_user/dossiers/index.html.haml', type: :view do
before do before do
allow(view).to receive(:new_demarche_url).and_return('#') 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(:user_dossiers, Kaminari.paginate_array(user_dossiers).page(1))
assign(:dossiers_invites, Kaminari.paginate_array(dossiers_invites).page(1)) assign(:dossiers_invites, Kaminari.paginate_array(dossiers_invites).page(1))
assign(:dossiers, Kaminari.paginate_array(user_dossiers).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) expect(rendered).to have_selector('ul.tabs li.active', count: 1)
end end
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 end

View file

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