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

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

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");
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
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

View file

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

View file

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

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

View file

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

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
// 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');
});
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 :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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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.
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"

View file

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

View file

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

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.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(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

View file

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

View file

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

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