From b193dd1465cdbe00b0270d7e83c0f333227d4d15 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 5 Nov 2019 10:01:07 +0100 Subject: [PATCH 01/53] User get the active notion --- app/models/user.rb | 4 ++++ spec/models/user_spec.rb | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 126c7adec..62176d692 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -92,6 +92,10 @@ class User < ApplicationRecord "User:#{id}" end + def active? + last_sign_in_at.present? + end + private def link_invites! diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4ff839507..a24949fbc 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -191,4 +191,22 @@ describe User, type: :model do it { expect(AdministrationMailer).to have_received(:invite_admin).with(user, nil, administration.id) } end end + + describe '#active?' do + let!(:user) { create(:user) } + + subject { user.active? } + + context 'when the user has never signed in' do + before { user.update(last_sign_in_at: nil) } + + it { is_expected.to be false } + end + + context 'when the user has already signed in' do + before { user.update(last_sign_in_at: Time.zone.now) } + + it { is_expected.to be true } + end + end end From 5643e671a0de8896e294419fdeb8c03309250ad1 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 5 Nov 2019 10:05:59 +0100 Subject: [PATCH 02/53] Code use user.active? --- app/controllers/application_controller.rb | 2 +- app/models/administrateur.rb | 4 ++-- app/models/user.rb | 2 +- app/services/administrateur_usage_statistics_service.rb | 2 +- spec/features/admin/admin_creation_spec.rb | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 03c8bcd57..1ee6f1411 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -250,7 +250,7 @@ class ApplicationController < ActionController::Base payload: { DS_SIGN_IN_COUNT: current_user&.sign_in_count, DS_CREATED_AT: current_administrateur&.created_at, - DS_ACTIVE: current_administrateur&.active?, + DS_ACTIVE: current_user&.active?, DS_ID: current_administrateur&.id, DS_GESTIONNAIRE_ID: current_instructeur&.id, DS_ROLES: current_user_roles diff --git a/app/models/administrateur.rb b/app/models/administrateur.rb index a874fbe27..2f834f39a 100644 --- a/app/models/administrateur.rb +++ b/app/models/administrateur.rb @@ -46,7 +46,7 @@ class Administrateur < ApplicationRecord end def registration_state - if active? + if user.active? 'Actif' elsif user.reset_password_period_valid? 'En attente' @@ -56,7 +56,7 @@ class Administrateur < ApplicationRecord end def invitation_expired? - !active? && !user.reset_password_period_valid? + !user.active? && !user.reset_password_period_valid? end def self.reset_password(reset_password_token, password) diff --git a/app/models/user.rb b/app/models/user.rb index 62176d692..41a4984b3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -49,7 +49,7 @@ class User < ApplicationRecord def invite_administrateur!(administration_id) reset_password_token = nil - if !administrateur.active? + if !active? reset_password_token = set_reset_password_token end diff --git a/app/services/administrateur_usage_statistics_service.rb b/app/services/administrateur_usage_statistics_service.rb index b9b38f868..5c85f2a96 100644 --- a/app/services/administrateur_usage_statistics_service.rb +++ b/app/services/administrateur_usage_statistics_service.rb @@ -29,7 +29,7 @@ class AdministrateurUsageStatisticsService result = { ds_sign_in_count: administrateur.user.sign_in_count, ds_created_at: administrateur.created_at, - ds_active: administrateur.active?, + ds_active: administrateur.user.active?, ds_id: administrateur.id, nb_services: nb_services_by_administrateur_id[administrateur.id], nb_instructeurs: nb_instructeurs_by_administrateur_id[administrateur.id], diff --git a/spec/features/admin/admin_creation_spec.rb b/spec/features/admin/admin_creation_spec.rb index 166983b64..de719edc9 100644 --- a/spec/features/admin/admin_creation_spec.rb +++ b/spec/features/admin/admin_creation_spec.rb @@ -12,7 +12,7 @@ feature 'As an administrateur', js: true do end scenario 'I can register' do - expect(new_admin.reload.active?).to be(false) + expect(new_admin.reload.user.active?).to be(false) confirmation_email = open_email(admin_email) token_params = confirmation_email.body.match(/token=[^"]+/) @@ -24,6 +24,6 @@ feature 'As an administrateur', js: true do expect(page).to have_content 'Mot de passe enregistré' - expect(new_admin.reload.active?).to be(true) + expect(new_admin.reload.user.active?).to be(true) end end From 395aba8bbcb825b0214db8a1df23fc8da15fdb6f Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 5 Nov 2019 10:06:54 +0100 Subject: [PATCH 03/53] Remove administrateur active notion --- app/models/administrateur.rb | 4 ---- spec/models/administrateur_spec.rb | 18 ------------------ 2 files changed, 22 deletions(-) diff --git a/app/models/administrateur.rb b/app/models/administrateur.rb index 2f834f39a..0c43f0eca 100644 --- a/app/models/administrateur.rb +++ b/app/models/administrateur.rb @@ -80,8 +80,4 @@ class Administrateur < ApplicationRecord def can_be_deleted? dossiers.state_instruction_commencee.none? && procedures.none? end - - def active? - user.last_sign_in_at.present? - end end diff --git a/spec/models/administrateur_spec.rb b/spec/models/administrateur_spec.rb index e06c53630..4b9fef1f0 100644 --- a/spec/models/administrateur_spec.rb +++ b/spec/models/administrateur_spec.rb @@ -50,22 +50,4 @@ describe Administrateur, type: :model do # it { expect(subject).to eq([]) } # end # end - - describe '#active?' do - let!(:administrateur) { create(:administrateur) } - - subject { administrateur.active? } - - context 'when the user has never signed in' do - before { administrateur.user.update(last_sign_in_at: nil) } - - it { is_expected.to be false } - end - - context 'when the user has already signed in' do - before { administrateur.user.update(last_sign_in_at: Time.zone.now) } - - it { is_expected.to be true } - end - end end From 9f5169eb7d59b1ff40c041cd7e4e93b40c8525ed Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 5 Nov 2019 10:18:37 +0100 Subject: [PATCH 04/53] Remove unused Administrateur.reset_password method --- app/models/administrateur.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/models/administrateur.rb b/app/models/administrateur.rb index 0c43f0eca..d097ebd74 100644 --- a/app/models/administrateur.rb +++ b/app/models/administrateur.rb @@ -59,16 +59,6 @@ class Administrateur < ApplicationRecord !user.active? && !user.reset_password_period_valid? end - def self.reset_password(reset_password_token, password) - administrateur = self.reset_password_by_token({ - password: password, - password_confirmation: password, - reset_password_token: reset_password_token - }) - - administrateur - end - def owns?(procedure) procedure.administrateurs.include?(self) end From 91db5f4ec28b89d80653b9e3d546b3c8a32e884d Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Tue, 5 Nov 2019 17:56:23 +0100 Subject: [PATCH 05/53] =?UTF-8?q?met=20=C3=A0=20jour=20la=20doc=20d'instal?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 97683c010..faf159341 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,14 @@ Les informations nécessaire à l'initialisation de la base doivent être pré-c > create user tps_test with password 'tps_test' superuser; > \q + ### Initialisation de l'environnement de développement +Sous Ubuntu, certains packages doivent être installés au préalable : + + sudo apt-get install libcurl3 libcurl3-gnutls libcurl4-openssl-dev libcurl4-gnutls-dev zlib1g-dev + + Afin d'initialiser l'environnement de développement, exécutez la commande suivante : bin/setup From 08b1a0f90d48e0cf804ae1568330a699da1381f5 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 5 Nov 2019 14:18:58 +0100 Subject: [PATCH 06/53] [API Carto]: use typhoeus and correctly handle errors --- app/lib/api_carto/api.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/lib/api_carto/api.rb b/app/lib/api_carto/api.rb index f94c11e2d..eadb813f8 100644 --- a/app/lib/api_carto/api.rb +++ b/app/lib/api_carto/api.rb @@ -12,11 +12,14 @@ class ApiCarto::API private def self.call(url, geojson) - params = geojson.to_s - RestClient.post(url, params, content_type: 'application/json') + response = Typhoeus.post(url, body: geojson.to_s, headers: { 'content-type' => 'application/json' }) - rescue RestClient::InternalServerError, RestClient::BadGateway, RestClient::GatewayTimeout, RestClient::ServiceUnavailable => e - Rails.logger.error "[ApiCarto] Error on #{url}: #{e}" - raise RestClient::ResourceNotFound + if response.success? + response.body + else + message = response.code == 0 ? response.return_message : response.code.to_s + Rails.logger.error "[ApiCarto] Error on #{url}: #{message}" + raise RestClient::ResourceNotFound + end end end From efd03f0169d06254091c7d96ad536bec0c8b6f47 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 5 Nov 2019 15:47:55 +0100 Subject: [PATCH 07/53] Use webdrivers gem to keep webdrivers updated --- Gemfile | 1 + Gemfile.lock | 5 +++++ bin/setup | 2 ++ bin/update | 3 +++ spec/spec_helper.rb | 2 +- 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 13763e699..1d23d00d8 100644 --- a/Gemfile +++ b/Gemfile @@ -93,6 +93,7 @@ group :test do gem 'shoulda-matchers', require: false gem 'timecop' gem 'vcr' + gem 'webdrivers', '~> 4.0' gem 'webmock' end diff --git a/Gemfile.lock b/Gemfile.lock index 7eaa820eb..969792790 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -678,6 +678,10 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) + webdrivers (4.1.3) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (>= 3.0, < 4.0) webfinger (1.1.0) activesupport httpclient (>= 2.4) @@ -812,6 +816,7 @@ DEPENDENCIES vcr warden web-console + webdrivers (~> 4.0) webmock webpacker xray-rails diff --git a/bin/setup b/bin/setup index 3d592bcd5..34110e498 100755 --- a/bin/setup +++ b/bin/setup @@ -18,6 +18,8 @@ chdir APP_ROOT do system('bundle check') || system!('bundle install') system! 'bin/yarn install' + puts "\n== Updating webdrivers ==" + system! 'RAILS_ENV=test bin/rails webdrivers:chromedriver:update' puts "\n== Copying sample files ==" unless File.exist?('.env') diff --git a/bin/update b/bin/update index c1968201d..04eb642c5 100755 --- a/bin/update +++ b/bin/update @@ -18,6 +18,9 @@ chdir APP_ROOT do system('bundle check') || system!('bundle install') system! 'bin/yarn install' + puts "\n== Updating webdrivers ==" + system! 'RAILS_ENV=test bin/rails webdrivers:chromedriver:update' + puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index dd0af845a..655686eb2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -83,7 +83,7 @@ VCR.configure do |c| c.hook_into :webmock c.cassette_library_dir = 'spec/fixtures/cassettes' c.configure_rspec_metadata! - c.ignore_hosts 'test.host' + c.ignore_hosts 'test.host', 'chromedriver.storage.googleapis.com' end DatabaseCleaner.strategy = :transaction From c5f2dacb7185ed5d367bb3478f37fe91253b0372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2019 10:33:00 +0000 Subject: [PATCH 08/53] build(deps): bump loofah from 2.2.3 to 2.3.1 Bumps [loofah](https://github.com/flavorjones/loofah) from 2.2.3 to 2.3.1. - [Release notes](https://github.com/flavorjones/loofah/releases) - [Changelog](https://github.com/flavorjones/loofah/blob/master/CHANGELOG.md) - [Commits](https://github.com/flavorjones/loofah/compare/v2.2.3...v2.3.1) Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 969792790..fae5871bb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -162,7 +162,7 @@ GEM carrierwave (>= 0.9) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.4) + crass (1.0.5) css_parser (1.6.0) addressable curb (0.9.10) @@ -356,7 +356,7 @@ GEM railties (>= 4) request_store (~> 1.0) logstash-event (1.2.02) - loofah (2.2.3) + loofah (2.3.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) lumberjack (1.0.13) @@ -385,7 +385,7 @@ GEM nenv (0.3.0) netrc (0.11.0) nio4r (2.3.1) - nokogiri (1.10.4) + nokogiri (1.10.5) mini_portile2 (~> 2.4.0) notiffany (0.1.1) nenv (~> 0.1) From 0eeac59ecdf61e773b0cf198648147d36fb13d4f Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 6 Nov 2019 11:59:15 +0100 Subject: [PATCH 09/53] assigns: fix search among the unassigned instructeurs This was broken since moving the email out of the Instructeur column. --- app/controllers/admin/assigns_controller.rb | 2 +- .../admin/assigns_controller_spec.rb | 34 ++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/controllers/admin/assigns_controller.rb b/app/controllers/admin/assigns_controller.rb index 5a59330c4..57d05670a 100644 --- a/app/controllers/admin/assigns_controller.rb +++ b/app/controllers/admin/assigns_controller.rb @@ -18,7 +18,7 @@ class Admin::AssignsController < AdminController not_assign_scope = current_administrateur.instructeurs.where.not(id: assign_scope.ids) if params[:filter] - not_assign_scope = not_assign_scope.where("email LIKE ?", "%#{params[:filter]}%") + not_assign_scope = not_assign_scope.where('users.email LIKE ?', "%#{params[:filter]}%") end @instructeurs_not_assign = smart_listing_create :instructeurs_not_assign, diff --git a/spec/controllers/admin/assigns_controller_spec.rb b/spec/controllers/admin/assigns_controller_spec.rb index 95fb4474a..923d553d2 100644 --- a/spec/controllers/admin/assigns_controller_spec.rb +++ b/spec/controllers/admin/assigns_controller_spec.rb @@ -2,19 +2,45 @@ require 'spec_helper' describe Admin::AssignsController, type: :controller do let(:admin) { create(:administrateur) } - let(:procedure) { create :procedure, administrateur: admin } - let(:instructeur) { create :instructeur, administrateurs: [admin] } before do sign_in(admin.user) end describe 'GET #show' do - subject { get :show, params: { procedure_id: procedure.id } } - it { expect(subject.status).to eq(200) } + let(:procedure) { create :procedure, administrateur: admin, instructeurs: [instructeur_assigned_1, instructeur_assigned_2] } + let!(:instructeur_assigned_1) { create :instructeur, email: 'instructeur_1@ministere_a.gouv.fr', administrateurs: [admin] } + let!(:instructeur_assigned_2) { create :instructeur, email: 'instructeur_2@ministere_b.gouv.fr', administrateurs: [admin] } + let!(:instructeur_not_assigned_1) { create :instructeur, email: 'instructeur_3@ministere_a.gouv.fr', administrateurs: [admin] } + let!(:instructeur_not_assigned_2) { create :instructeur, email: 'instructeur_4@ministere_b.gouv.fr', administrateurs: [admin] } + let(:filter) { nil } + + subject! { get :show, params: { procedure_id: procedure.id, filter: filter } } + + it { expect(response.status).to eq(200) } + + it 'sets the assigned and not assigned instructeurs' do + expect(assigns(:instructeurs_assign)).to match_array([instructeur_assigned_1, instructeur_assigned_2]) + expect(assigns(:instructeurs_not_assign)).to match_array([instructeur_not_assigned_1, instructeur_not_assigned_2]) + end + + context 'with a search filter' do + let(:filter) { '@ministere_a.gouv.fr' } + + it 'filters the unassigned instructeurs' do + expect(assigns(:instructeurs_not_assign)).to match_array([instructeur_not_assigned_1]) + end + + it 'does not filter the assigned instructeurs' do + expect(assigns(:instructeurs_assign)).to match_array([instructeur_assigned_1, instructeur_assigned_2]) + end + end end describe 'PUT #update' do + let(:procedure) { create :procedure, administrateur: admin } + let(:instructeur) { create :instructeur, administrateurs: [admin] } + subject { put :update, params: { instructeur_id: instructeur.id, procedure_id: procedure.id, to: 'assign' } } it { expect(subject).to redirect_to admin_procedure_assigns_path(procedure_id: procedure.id) } From 8df91df9fa2b41a913f6825ba9e3a9de4fdc28ca Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 6 Nov 2019 12:05:41 +0100 Subject: [PATCH 10/53] assigns: strip and lowercase the search filter --- app/controllers/admin/assigns_controller.rb | 5 +++-- spec/controllers/admin/assigns_controller_spec.rb | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/assigns_controller.rb b/app/controllers/admin/assigns_controller.rb index 57d05670a..cf2899643 100644 --- a/app/controllers/admin/assigns_controller.rb +++ b/app/controllers/admin/assigns_controller.rb @@ -17,8 +17,9 @@ class Admin::AssignsController < AdminController not_assign_scope = current_administrateur.instructeurs.where.not(id: assign_scope.ids) - if params[:filter] - not_assign_scope = not_assign_scope.where('users.email LIKE ?', "%#{params[:filter]}%") + if params[:filter].present? + filter = params[:filter].downcase.strip + not_assign_scope = not_assign_scope.where('users.email LIKE ?', "%#{filter}%") end @instructeurs_not_assign = smart_listing_create :instructeurs_not_assign, diff --git a/spec/controllers/admin/assigns_controller_spec.rb b/spec/controllers/admin/assigns_controller_spec.rb index 923d553d2..741937dd8 100644 --- a/spec/controllers/admin/assigns_controller_spec.rb +++ b/spec/controllers/admin/assigns_controller_spec.rb @@ -34,6 +34,14 @@ describe Admin::AssignsController, type: :controller do it 'does not filter the assigned instructeurs' do expect(assigns(:instructeurs_assign)).to match_array([instructeur_assigned_1, instructeur_assigned_2]) end + + context 'when the filter has spaces or a mixed case' do + let(:filter) { ' @ministere_A.gouv.fr ' } + + it 'trims spaces and ignores the case' do + expect(assigns(:instructeurs_not_assign)).to match_array([instructeur_not_assigned_1]) + end + end end end From 95f98fe60519b21e5035c630ad7719ac2926d228 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 5 Nov 2019 16:36:12 +0100 Subject: [PATCH 11/53] API v1 correctly handle resultats_par_page --- app/controllers/api/v1/dossiers_controller.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/dossiers_controller.rb b/app/controllers/api/v1/dossiers_controller.rb index 980ba7e81..9a990734d 100644 --- a/app/controllers/api/v1/dossiers_controller.rb +++ b/app/controllers/api/v1/dossiers_controller.rb @@ -2,6 +2,7 @@ class API::V1::DossiersController < APIController before_action :fetch_procedure_and_check_token DEFAULT_PAGE_SIZE = 100 + MAX_PAGE_SIZE = 1000 ORDER_DIRECTIONS = { 'asc' => :asc, 'desc' => :desc } def index @@ -33,7 +34,12 @@ class API::V1::DossiersController < APIController end def per_page # inherited value from will_paginate - [params[:resultats_par_page]&.to_i || DEFAULT_PAGE_SIZE, 1000].min + resultats_par_page = params[:resultats_par_page]&.to_i + if resultats_par_page && resultats_par_page > 0 + [resultats_par_page, MAX_PAGE_SIZE].min + else + DEFAULT_PAGE_SIZE + end end def fetch_procedure_and_check_token From f5c80f211d5ede70ea4d3fb819a64715daa5cbf2 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 5 Nov 2019 16:44:28 +0100 Subject: [PATCH 12/53] Guard for missing attestation on dossier --- app/controllers/users/dossiers_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 23cb10410..8c0ccad0f 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -48,8 +48,11 @@ module Users end def attestation - if dossier.attestation.pdf.attached? + if dossier.attestation&.pdf&.attached? redirect_to url_for(dossier.attestation.pdf) + else + flash.notice = "L'attestation n'est plus disponible sur ce dossier." + redirect_to dossier_path(dossier) end end From a708b071dd81dab7354ddc1d24f47e2d92835b9c Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 5 Nov 2019 16:54:43 +0100 Subject: [PATCH 13/53] Do not crash with champ repetable with no children --- app/views/champs/repetition/_show.html.haml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/views/champs/repetition/_show.html.haml b/app/views/champs/repetition/_show.html.haml index 508593712..8b3240829 100644 --- a/app/views/champs/repetition/_show.html.haml +++ b/app/views/champs/repetition/_show.html.haml @@ -1,10 +1,11 @@ - champs = champ.rows.last -- index = (champ.rows.size - 1) * champs.size -%div{ class: "row row-#{champs.first.row}" } - - champs.each.with_index(index) do |champ, index| - = fields_for "#{attribute}[#{index}]", champ do |form| - = render partial: "shared/dossiers/editable_champs/editable_champ", locals: { champ: champ, form: form } - = form.hidden_field :id - = form.hidden_field :_destroy, disabled: true - %button.button.danger.remove-row - Supprimer +- if champs.present? + - index = (champ.rows.size - 1) * champs.size + %div{ class: "row row-#{champs.first.row}" } + - champs.each.with_index(index) do |champ, index| + = fields_for "#{attribute}[#{index}]", champ do |form| + = render partial: "shared/dossiers/editable_champs/editable_champ", locals: { champ: champ, form: form } + = form.hidden_field :id + = form.hidden_field :_destroy, disabled: true + %button.button.danger.remove-row + Supprimer From ee62d6fca453bf7fb1a32274a1e48f888caaf04b Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 5 Nov 2019 17:06:49 +0100 Subject: [PATCH 14/53] Fix move type de champs --- app/models/procedure.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 6a6d3ecd6..96a427dbb 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -595,14 +595,18 @@ class Procedure < ApplicationRecord def move_type_de_champ_attributes(types_de_champ, type_de_champ, new_index) old_index = types_de_champ.index(type_de_champ) - types_de_champ.insert(new_index, types_de_champ.delete_at(old_index)) - .map.with_index do |type_de_champ, index| - { - id: type_de_champ.id, - libelle: type_de_champ.libelle, - order_place: index - } - end + if types_de_champ.delete_at(old_index) + types_de_champ.insert(new_index, type_de_champ) + .map.with_index do |type_de_champ, index| + { + id: type_de_champ.id, + libelle: type_de_champ.libelle, + order_place: index + } + end + else + [] + end end def before_publish From 0562e2728f67715279d4aaca1d47a9d5e4a532ec Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 6 Nov 2019 11:20:28 +0100 Subject: [PATCH 15/53] Fix type_de_champ validation error --- .../linked_drop_down_list_type_de_champ.rb | 2 +- spec/models/type_de_champ_shared_example.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/types_de_champ/linked_drop_down_list_type_de_champ.rb b/app/models/types_de_champ/linked_drop_down_list_type_de_champ.rb index 71f0784b1..24410c6f6 100644 --- a/app/models/types_de_champ/linked_drop_down_list_type_de_champ.rb +++ b/app/models/types_de_champ/linked_drop_down_list_type_de_champ.rb @@ -53,7 +53,7 @@ class TypesDeChamp::LinkedDropDownListTypeDeChamp < TypesDeChamp::TypeDeChampBas def check_presence_of_primary_options if !PRIMARY_PATTERN.match?(drop_down_list.options.second) - errors.add(libelle, "doit commencer par une entrée de menu primaire de la forme --texte--") + errors.add(libelle.presence || "La liste", "doit commencer par une entrée de menu primaire de la forme --texte--") end end diff --git a/spec/models/type_de_champ_shared_example.rb b/spec/models/type_de_champ_shared_example.rb index d0b6e4ecf..a710b4ef0 100644 --- a/spec/models/type_de_champ_shared_example.rb +++ b/spec/models/type_de_champ_shared_example.rb @@ -148,4 +148,22 @@ shared_examples 'type_de_champ_spec' do expect(cloned_procedure.types_de_champ.first.types_de_champ).not_to be_empty end end + + describe "linked_drop_down_list" do + let(:type_de_champ) { create(:type_de_champ_linked_drop_down_list) } + + it 'should validate without label' do + type_de_champ.drop_down_list_value = 'toto' + expect(type_de_champ.validate).to be_falsey + messages = type_de_champ.errors.full_messages + expect(messages.size).to eq(1) + expect(messages.first.starts_with?("#{type_de_champ.libelle} doit commencer par")).to be_truthy + + type_de_champ.libelle = '' + expect(type_de_champ.validate).to be_falsey + messages = type_de_champ.errors.full_messages + expect(messages.size).to eq(2) + expect(messages.last.starts_with?("La liste doit commencer par")).to be_truthy + end + end end From 959aacdea5fbb9e060c5fd04c4457e8a3584e86a Mon Sep 17 00:00:00 2001 From: clemkeirua Date: Tue, 5 Nov 2019 09:32:35 +0100 Subject: [PATCH 16/53] Sendinblue email balancing using proper credentials This reverts commit c61981e7957340cbf268d449df809f27555aece6. --- .../dynamic_smtp_settings_interceptor.rb | 16 +++++++++++ config/env.example | 7 ++++- config/environments/development.rb | 28 +++++++++++++------ config/environments/production.rb | 10 +++++++ .../dynamic_smtp_settings_interceptor.rb | 1 + config/secrets.yml | 1 + 6 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 app/models/dynamic_smtp_settings_interceptor.rb create mode 100644 config/initializers/dynamic_smtp_settings_interceptor.rb diff --git a/app/models/dynamic_smtp_settings_interceptor.rb b/app/models/dynamic_smtp_settings_interceptor.rb new file mode 100644 index 000000000..bbd702420 --- /dev/null +++ b/app/models/dynamic_smtp_settings_interceptor.rb @@ -0,0 +1,16 @@ +class DynamicSmtpSettingsInterceptor + def self.delivering_email(message) + if ENV['SENDINBLUE_BALANCING'] == 'enabled' + if rand(0..99) < ENV['SENDINBLUE_BALANCING_VALUE'].to_i + message.delivery_method.settings = { + user_name: ENV['SENDINBLUE_USER_NAME'], + password: ENV['SENDINBLUE_CLIENT_KEY'], + address: 'smtp-relay.sendinblue.com', + domain: 'smtp-relay.sendinblue.com', + port: '587', + authentication: :cram_md5 + } + end + end + end +end diff --git a/config/env.example b/config/env.example index 4f704adab..143e586ed 100644 --- a/config/env.example +++ b/config/env.example @@ -46,8 +46,13 @@ SENTRY_DSN_JS="" MATOMO_ENABLED="disabled" MATOMO_ID="73" -SENDINBLUE_ENABLED="disabled" +SENDINBLUE_BALANCING="" +SENDINBLUE_BALANCING_VALUE="" +SENDINBLUE_ENABLED="" SENDINBLUE_CLIENT_KEY="" +SENDINBLUE_USER_NAME="" + + CRISP_ENABLED="disabled" CRISP_CLIENT_KEY="" diff --git a/config/environments/development.rb b/config/environments/development.rb index 385185b36..1018ab6cf 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -45,14 +45,26 @@ Rails.application.configure do config.assets.raise_runtime_errors = true # Action Mailer settings - config.action_mailer.delivery_method = :letter_opener_web - # Configure default root URL for generating URLs to routes - config.action_mailer.default_url_options = { - host: 'localhost', - port: 3000 - } - # Configure default root URL for email assets - config.action_mailer.asset_host = "http://" + ENV['APP_HOST'] + + if ENV['SENDINBLUE_ENABLED'] == 'enabled' + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { + user_name: Rails.application.secrets.sendinblue[:username], + password: Rails.application.secrets.sendinblue[:client_key], + address: 'smtp-relay.sendinblue.com', + domain: 'smtp-relay.sendinblue.com', + port: '587', + authentication: :cram_md5 + } + else + config.action_mailer.delivery_method = :letter_opener_web + config.action_mailer.default_url_options = { + host: 'localhost', + port: 3000 + } + + config.action_mailer.asset_host = "http://" + ENV['APP_HOST'] + end Rails.application.routes.default_url_options = { host: 'localhost', diff --git a/config/environments/production.rb b/config/environments/production.rb index 8837fb88b..38de5db80 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -77,6 +77,16 @@ Rails.application.configure do port: '2525', authentication: :cram_md5 } + elsif ENV['SENDINBLUE_ENABLED'] == 'enabled' + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { + user_name: Rails.application.secrets.sendinblue[:username], + password: Rails.application.secrets.sendinblue[:client_key], + address: 'smtp-relay.sendinblue.com', + domain: 'smtp-relay.sendinblue.com', + port: '587', + authentication: :cram_md5 + } else config.action_mailer.delivery_method = :mailjet end diff --git a/config/initializers/dynamic_smtp_settings_interceptor.rb b/config/initializers/dynamic_smtp_settings_interceptor.rb new file mode 100644 index 000000000..a3f8e2d13 --- /dev/null +++ b/config/initializers/dynamic_smtp_settings_interceptor.rb @@ -0,0 +1 @@ +ActionMailer::Base.register_interceptor "DynamicSmtpSettingsInterceptor" diff --git a/config/secrets.yml b/config/secrets.yml index 7ad675ffe..c0989401f 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -54,6 +54,7 @@ defaults: &defaults webhook_secret: <%= ENV['HELPSCOUT_WEBHOOK_SECRET'] %> sendinblue: enabled: <%= ENV['SENDINBLUE_ENABLED'] == 'enabled' %> + username: <%= ENV['SENDINBLUE_USER_NAME'] %> client_key: <%= ENV['SENDINBLUE_CLIENT_KEY'] %> api_v3_key: <%= ENV['SENDINBLUE_API_V3_KEY'] %> matomo: From 04c13190c3a4de1c532f585b83ab0f92fd1daaa3 Mon Sep 17 00:00:00 2001 From: clemkeirua Date: Tue, 5 Nov 2019 09:38:08 +0100 Subject: [PATCH 17/53] =?UTF-8?q?introduce=20smtp=5Fkey=20in=20order=20to?= =?UTF-8?q?=20use=202=C2=A0different=20sendinblue=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit client_key is exposed to the client via gon, so if we use it for sending email too we are exposing a key so anybody could send an email. The current client_key has a different level of right and can't send emails so it's ok to expose it. --- app/models/dynamic_smtp_settings_interceptor.rb | 2 +- config/env.example | 1 + config/environments/development.rb | 2 +- config/environments/production.rb | 2 +- config/secrets.yml | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/dynamic_smtp_settings_interceptor.rb b/app/models/dynamic_smtp_settings_interceptor.rb index bbd702420..885c4b8e9 100644 --- a/app/models/dynamic_smtp_settings_interceptor.rb +++ b/app/models/dynamic_smtp_settings_interceptor.rb @@ -4,7 +4,7 @@ class DynamicSmtpSettingsInterceptor if rand(0..99) < ENV['SENDINBLUE_BALANCING_VALUE'].to_i message.delivery_method.settings = { user_name: ENV['SENDINBLUE_USER_NAME'], - password: ENV['SENDINBLUE_CLIENT_KEY'], + password: ENV['SENDINBLUE_SMTP_KEY'], address: 'smtp-relay.sendinblue.com', domain: 'smtp-relay.sendinblue.com', port: '587', diff --git a/config/env.example b/config/env.example index 143e586ed..c3c40d089 100644 --- a/config/env.example +++ b/config/env.example @@ -50,6 +50,7 @@ SENDINBLUE_BALANCING="" SENDINBLUE_BALANCING_VALUE="" SENDINBLUE_ENABLED="" SENDINBLUE_CLIENT_KEY="" +SENDINBLUE_SMTP_KEY="" SENDINBLUE_USER_NAME="" diff --git a/config/environments/development.rb b/config/environments/development.rb index 1018ab6cf..718db3436 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -50,7 +50,7 @@ Rails.application.configure do config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { user_name: Rails.application.secrets.sendinblue[:username], - password: Rails.application.secrets.sendinblue[:client_key], + password: Rails.application.secrets.sendinblue[:smtp_key], address: 'smtp-relay.sendinblue.com', domain: 'smtp-relay.sendinblue.com', port: '587', diff --git a/config/environments/production.rb b/config/environments/production.rb index 38de5db80..262c15c4b 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -81,7 +81,7 @@ Rails.application.configure do config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { user_name: Rails.application.secrets.sendinblue[:username], - password: Rails.application.secrets.sendinblue[:client_key], + password: Rails.application.secrets.sendinblue[:smtp_key], address: 'smtp-relay.sendinblue.com', domain: 'smtp-relay.sendinblue.com', port: '587', diff --git a/config/secrets.yml b/config/secrets.yml index c0989401f..2ac8aa244 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -56,6 +56,7 @@ defaults: &defaults enabled: <%= ENV['SENDINBLUE_ENABLED'] == 'enabled' %> username: <%= ENV['SENDINBLUE_USER_NAME'] %> client_key: <%= ENV['SENDINBLUE_CLIENT_KEY'] %> + smtp_key: <%= ENV['SENDINBLUE_SMTP_KEY'] %> api_v3_key: <%= ENV['SENDINBLUE_API_V3_KEY'] %> matomo: enabled: <%= ENV['MATOMO_ENABLED'] == 'enabled' %> From 47b177fd31edd01bb2b0d98594e891fbf10531bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chai=CC=88b=20Martinez?= Date: Mon, 4 Nov 2019 15:01:58 +0100 Subject: [PATCH 18/53] RGAA A : L'attribut alt est absent pour le logo des demarches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Chaïb Martinez --- app/views/shared/_procedure_description.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_procedure_description.html.haml b/app/views/shared/_procedure_description.html.haml index 52d7b9460..ebdf356ea 100644 --- a/app/views/shared/_procedure_description.html.haml +++ b/app/views/shared/_procedure_description.html.haml @@ -1,5 +1,5 @@ .procedure-logos - = image_tag procedure.logo_url + = image_tag procedure.logo_url, alt: "logo #{procedure.libelle}" - if procedure.euro_flag = image_tag("flag_of_europe.svg", id: 'euro_flag', class: (!procedure.euro_flag ? "hidden" : "")) %h2.procedure-title From a00ce94eeaa9ab37c0830baae007d1a919e7141c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chai=CC=88b=20Martinez?= Date: Mon, 4 Nov 2019 15:31:56 +0100 Subject: [PATCH 19/53] RGAA A : ajout de l'attribut alt pour les images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Chaïb Martinez --- app/views/layouts/commencer/_no_procedure.html.haml | 2 +- app/views/root/landing.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/commencer/_no_procedure.html.haml b/app/views/layouts/commencer/_no_procedure.html.haml index 34c054883..15a7c59a6 100644 --- a/app/views/layouts/commencer/_no_procedure.html.haml +++ b/app/views/layouts/commencer/_no_procedure.html.haml @@ -1,5 +1,5 @@ .no-procedure - = image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo" + = image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo", alt: "moins de papier" .baseline.center %h3 Un outil simple %p diff --git a/app/views/root/landing.html.haml b/app/views/root/landing.html.haml index 72bd6b341..3c72e717c 100644 --- a/app/views/root/landing.html.haml +++ b/app/views/root/landing.html.haml @@ -14,13 +14,13 @@ %em.hero-tagline-em en ligne .hero-illustration - %img{ :src => image_url("landing/hero/dematerialiser.svg"), alt: "" } + %img{ :src => image_url("landing/hero/dematerialiser.svg"), alt: "dématérialisez" } .landing-panel.usagers-panel .container .role-panel-wrapper .role-panel-30.role-usagers-image - %img.role-image{ :src => image_url("landing/roles/usagers.svg"), alt: "" } + %img.role-image{ :src => image_url("landing/roles/usagers.svg"), alt: "usager" } .role-panel-70 %h1.role-panel-title Vous souhaitez effectuer une demande auprès d'une administration ? From daeeeda5830587b537dc9ef23842403356b6a5e0 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Tue, 5 Nov 2019 17:20:01 +0100 Subject: [PATCH 20/53] RGAA: ajout de title aux links --- app/views/root/_footer.html.haml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/root/_footer.html.haml b/app/views/root/_footer.html.haml index ac7462fec..7a49639b8 100644 --- a/app/views/root/_footer.html.haml +++ b/app/views/root/_footer.html.haml @@ -6,22 +6,22 @@ %ul.footer-logos %li.footer-text Un service fourni par la - = link_to "DINSIC", "http://www.modernisation.gouv.fr/" + = link_to "DINUM", "http://www.modernisation.gouv.fr/", title: "Direction Interministérielle au Numérique" %br et incubé par - = link_to "beta.gouv.fr", "https://beta.gouv.fr" + = link_to "beta.gouv.fr", "https://beta.gouv.fr", title: "le site de Beta.gouv.fr" %li - = link_to "http://www.modernisation.gouv.fr/" do + = link_to "http://www.modernisation.gouv.fr/", title: "DINUM" do %span.footer-logo.footer-logo-dinsic{ role: 'img', 'aria-label': 'DINSIC' } - = link_to "https://beta.gouv.fr" do + = link_to "https://beta.gouv.fr", title: "le site de Beta.gouv.fr" do %span.footer-logo.footer-logo-beta-gouv-fr{ role: 'img', 'aria-label': 'beta.gouv.fr' } %li.footer-column %ul.footer-links %li.footer-link - = link_to "Newsletter", "https://my.sendinblue.com/users/subscribe/js_id/3s2q1/id/1", :class => "footer-link", :target => "_blank", rel: "noopener" + = link_to "Newsletter", "https://my.sendinblue.com/users/subscribe/js_id/3s2q1/id/1", :title => "Notre newsletter", :class => "footer-link", :target => "_blank", rel: "noopener" %li.footer-link - = link_to "Nouveautés", "https://github.com/betagouv/demarches-simplifiees.fr/releases", :class => "footer-link" + = link_to "Nouveautés", "https://github.com/betagouv/demarches-simplifiees.fr/releases", :class => "footer-link", :title => "Nos nouveautés" %li.footer-link = link_to "Statistiques", stats_path, :class => "footer-link", data: { turbolinks: false } # Turbolinks disabled for Chartkick. See Issue #350 %li.footer-link From 79968f12fbef47e2a33aae9f12f33b3c476339d1 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 6 Nov 2019 09:11:55 +0100 Subject: [PATCH 21/53] RGAA A : h4 => span in top dropdown item to respect title hierarchy (9.1.2) --- app/assets/stylesheets/new_design/help_dropdown.scss | 2 +- app/views/shared/help/dropdown_items/_email_item.html.haml | 2 +- app/views/shared/help/dropdown_items/_faq_item.html.haml | 2 +- app/views/shared/help/dropdown_items/_messagerie_item.html.haml | 2 +- app/views/shared/help/dropdown_items/_service_item.html.haml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/new_design/help_dropdown.scss b/app/assets/stylesheets/new_design/help_dropdown.scss index 01d8bd70c..d0f0d66d4 100644 --- a/app/assets/stylesheets/new_design/help_dropdown.scss +++ b/app/assets/stylesheets/new_design/help_dropdown.scss @@ -11,7 +11,7 @@ } } -h4.help-dropdown-title { +.help-dropdown-title { font-size: 16px; color: $blue; } diff --git a/app/views/shared/help/dropdown_items/_email_item.html.haml b/app/views/shared/help/dropdown_items/_email_item.html.haml index e921003e5..d409ab875 100644 --- a/app/views/shared/help/dropdown_items/_email_item.html.haml +++ b/app/views/shared/help/dropdown_items/_email_item.html.haml @@ -2,5 +2,5 @@ = mail_to CONTACT_EMAIL do %span.icon.mail .dropdown-description - %h4.help-dropdown-title Contact technique + %span.help-dropdown-title Contact technique %p Envoyez nous un message à #{CONTACT_EMAIL}. diff --git a/app/views/shared/help/dropdown_items/_faq_item.html.haml b/app/views/shared/help/dropdown_items/_faq_item.html.haml index 86f82348b..fd48bb56d 100644 --- a/app/views/shared/help/dropdown_items/_faq_item.html.haml +++ b/app/views/shared/help/dropdown_items/_faq_item.html.haml @@ -2,5 +2,5 @@ = link_to FAQ_URL, target: "_blank", rel: "noopener" do %span.icon.help .dropdown-description - %h4.help-dropdown-title Un problème avec le site ? + %span.help-dropdown-title Un problème avec le site ? %p Trouvez votre réponse dans l’aide en ligne. diff --git a/app/views/shared/help/dropdown_items/_messagerie_item.html.haml b/app/views/shared/help/dropdown_items/_messagerie_item.html.haml index 2e25a78c8..94d31143d 100644 --- a/app/views/shared/help/dropdown_items/_messagerie_item.html.haml +++ b/app/views/shared/help/dropdown_items/_messagerie_item.html.haml @@ -2,5 +2,5 @@ = link_to messagerie_dossier_path(dossier) do %span.icon.mail .dropdown-description - %h4.help-dropdown-title= title + %span.help-dropdown-title= title %p Envoyez directement un message à l’instructeur. diff --git a/app/views/shared/help/dropdown_items/_service_item.html.haml b/app/views/shared/help/dropdown_items/_service_item.html.haml index af8624549..c57a0bada 100644 --- a/app/views/shared/help/dropdown_items/_service_item.html.haml +++ b/app/views/shared/help/dropdown_items/_service_item.html.haml @@ -1,7 +1,7 @@ %li.help-dropdown-service %span.icon.person .dropdown-description - %h4.help-dropdown-title= title + %span.help-dropdown-title= title .help-dropdown-service-action %p Contactez directement l’administration : %p.help-dropdown-service-item From 8efac29eba57b740b1a96e7d0f64655734b9e087 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 6 Nov 2019 14:56:49 +0100 Subject: [PATCH 22/53] Better structure footer-site-links in list it follows service-public.fr structure - remove extraneous footer-link class - remove extraneous space with `>` haml weird stuff - homogenize json-style hash - merge classes on ul - use pseudo element for eye candy --- .../stylesheets/new_design/new_footer.scss | 16 ++++++++++++++++ app/views/users/_general_footer_row.html.haml | 18 +++++++----------- app/views/users/_procedure_footer.html.haml | 4 ++-- .../users/dossiers/_index_footer.html.haml | 3 +-- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/new_design/new_footer.scss b/app/assets/stylesheets/new_design/new_footer.scss index 092ee442b..7694d8d34 100644 --- a/app/assets/stylesheets/new_design/new_footer.scss +++ b/app/assets/stylesheets/new_design/new_footer.scss @@ -113,3 +113,19 @@ footer { margin-bottom: 0; } } + +.footer-site-links { + li { + display: inline; + + + &::before { + content: "-"; + margin: $default-spacer; + } + + &:first-child::before { + content: none; + } + } +} diff --git a/app/views/users/_general_footer_row.html.haml b/app/views/users/_general_footer_row.html.haml index 8d994718e..987c199e4 100644 --- a/app/views/users/_general_footer_row.html.haml +++ b/app/views/users/_general_footer_row.html.haml @@ -1,11 +1,7 @@ -= link_to "Accessibilité", accessibilite_path, :class => "footer-link" -– -= link_to "CGU", CGU_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" -– -= link_to "Mentions légales", MENTIONS_LEGALES_URL, :class => "footer-link", :target => "_blank", rel: "noopener noreferrer" -– -= link_to 'Documentation', DOC_URL -– -= contact_link "Contact technique", class: "footer-link", dossier_id: dossier&.id -– -= link_to 'Aide', FAQ_URL +%ul.footer-row.footer-bottom-line.footer-site-links + %li>= link_to "Accessibilité", accessibilite_path + %li>= link_to "CGU", CGU_URL, target: "_blank", rel: "noopener noreferrer" + %li>= link_to "Mentions légales", MENTIONS_LEGALES_URL, target: "_blank", rel: "noopener noreferrer" + %li>= link_to 'Documentation', DOC_URL + %li>= contact_link "Contact technique", dossier_id: dossier&.id + %li>= link_to 'Aide', FAQ_URL diff --git a/app/views/users/_procedure_footer.html.haml b/app/views/users/_procedure_footer.html.haml index 458c6ed00..230319df8 100644 --- a/app/views/users/_procedure_footer.html.haml +++ b/app/views/users/_procedure_footer.html.haml @@ -38,5 +38,5 @@ - politiques.each do |politique| %p= politique - .footer-row.footer-bottom-line - = render partial: 'users/general_footer_row', locals: { dossier: dossier } + = render partial: 'users/general_footer_row', locals: { dossier: dossier } + diff --git a/app/views/users/dossiers/_index_footer.html.haml b/app/views/users/dossiers/_index_footer.html.haml index 935c93bbc..16f9f1273 100644 --- a/app/views/users/dossiers/_index_footer.html.haml +++ b/app/views/users/dossiers/_index_footer.html.haml @@ -1,4 +1,3 @@ %footer.procedure-footer .container - .footer-row.footer-bottom-line - = render partial: "users/general_footer_row", locals: { dossier: nil } + = render partial: "users/general_footer_row", locals: { dossier: nil } From 16814af474bd23943bbeca32887194e6253aeb2f Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 6 Nov 2019 16:30:20 +0100 Subject: [PATCH 23/53] Make (part of) the html valid again simple_format always wraps its content so the better idea I have got was to use a span to avoid p in p. --- app/helpers/string_to_html_helper.rb | 4 ++-- app/views/users/_procedure_footer.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/string_to_html_helper.rb b/app/helpers/string_to_html_helper.rb index 76a595699..1bdb188f0 100644 --- a/app/helpers/string_to_html_helper.rb +++ b/app/helpers/string_to_html_helper.rb @@ -1,6 +1,6 @@ module StringToHtmlHelper - def string_to_html(str) - html_formatted = simple_format(str) + def string_to_html(str, wrapper_tag = 'p') + html_formatted = simple_format(str, {}, { wrapper_tag: wrapper_tag }) with_links = html_formatted.gsub(URI.regexp, '\0') sanitize(with_links, attributes: ['target', 'rel', 'href']) end diff --git a/app/views/users/_procedure_footer.html.haml b/app/views/users/_procedure_footer.html.haml index 230319df8..2a206f23d 100644 --- a/app/views/users/_procedure_footer.html.haml +++ b/app/views/users/_procedure_footer.html.haml @@ -10,7 +10,7 @@ %br = service.organisme %br - = string_to_html(service.adresse) + = string_to_html(service.adresse, wrapper_tag = 'span') %li.footer-column %h3.footer-header Poser une question sur votre dossier : @@ -28,7 +28,7 @@ %p - horaires = "Horaires : #{formatted_horaires(service.horaires)}" - = simple_format(horaires) + = simple_format(horaires, {}, wrapper_tag: 'span') - politiques = politiques_conservation_de_donnees(procedure) From 6ec2b084c71bc365252db56649d77ac3b2e607fc Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 6 Nov 2019 16:37:03 +0100 Subject: [PATCH 24/53] h3 -> p --- app/views/users/_procedure_footer.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/users/_procedure_footer.html.haml b/app/views/users/_procedure_footer.html.haml index 2a206f23d..9aad1e0fb 100644 --- a/app/views/users/_procedure_footer.html.haml +++ b/app/views/users/_procedure_footer.html.haml @@ -4,7 +4,7 @@ - if service.present? %ul.footer-row.footer-columns %li.footer-column - %h3.footer-header Cette démarche est gérée par : + %p.footer-header Cette démarche est gérée par : %p = service.nom %br @@ -13,7 +13,7 @@ = string_to_html(service.adresse, wrapper_tag = 'span') %li.footer-column - %h3.footer-header Poser une question sur votre dossier : + %p.footer-header Poser une question sur votre dossier : %p - if dossier.present? && dossier.messagerie_available? Directement @@ -34,7 +34,7 @@ - politiques = politiques_conservation_de_donnees(procedure) - if politiques.present? %li.footer-column - %h3.footer-header Conservation des données : + %p.footer-header Conservation des données : - politiques.each do |politique| %p= politique From b6765a03aeb495b3696766db96a2f1cb6b87441a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 6 Nov 2019 16:38:52 +0100 Subject: [PATCH 25/53] Footer: remove top list and make each column a list as service-public.fr (note: not so sure because of https://www.service-public.fr/P10025) --- app/views/users/_procedure_footer.html.haml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/views/users/_procedure_footer.html.haml b/app/views/users/_procedure_footer.html.haml index 9aad1e0fb..1286ea584 100644 --- a/app/views/users/_procedure_footer.html.haml +++ b/app/views/users/_procedure_footer.html.haml @@ -2,19 +2,19 @@ .container - service = procedure.service - if service.present? - %ul.footer-row.footer-columns - %li.footer-column + .footer-row.footer-columns + %ul.footer-column %p.footer-header Cette démarche est gérée par : - %p + %li = service.nom %br = service.organisme %br = string_to_html(service.adresse, wrapper_tag = 'span') - %li.footer-column + %ul.footer-column %p.footer-header Poser une question sur votre dossier : - %p + %li - if dossier.present? && dossier.messagerie_available? Directement = link_to "par la messagerie", messagerie_dossier_path(dossier) @@ -22,21 +22,21 @@ Par email : = link_to service.email, "mailto:#{service.email}" - %p + %li Par téléphone : %a{ href: "tel:#{service.telephone}" }= service.telephone - %p + %li - horaires = "Horaires : #{formatted_horaires(service.horaires)}" = simple_format(horaires, {}, wrapper_tag: 'span') - politiques = politiques_conservation_de_donnees(procedure) - if politiques.present? - %li.footer-column + %ul.footer-column %p.footer-header Conservation des données : - politiques.each do |politique| - %p= politique + %li= politique = render partial: 'users/general_footer_row', locals: { dossier: dossier } From 9c7feae0b2d911c272f9767eb57bd777ed2af47e Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 6 Nov 2019 16:58:07 +0100 Subject: [PATCH 26/53] =?UTF-8?q?RGAA=20fix=20:=209.1.2=20Dans=20chaque=20?= =?UTF-8?q?page=20Web,=20la=20hi=C3=A9rarchie=20entre=20les=20titres=20(ba?= =?UTF-8?q?lises=20h=20ou=20balise=20poss=C3=A9dant=20un=20role=20ARIA=20"?= =?UTF-8?q?heading"=20associ=C3=A9=20=C3=A0=20une=20propri=C3=A9t=C3=A9=20?= =?UTF-8?q?aria-level)=20est-elle=20pertinente=20=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/stylesheets/new_design/title.scss | 12 ++++++++++++ app/views/commencer/show.html.haml | 8 ++++---- app/views/shared/_procedure_description.html.haml | 2 +- app/views/users/sessions/new.html.haml | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 app/assets/stylesheets/new_design/title.scss diff --git a/app/assets/stylesheets/new_design/title.scss b/app/assets/stylesheets/new_design/title.scss new file mode 100644 index 000000000..d02120e2f --- /dev/null +++ b/app/assets/stylesheets/new_design/title.scss @@ -0,0 +1,12 @@ +@import "constants"; + +.huge-title { + text-align: center; + margin-bottom: 20px; + font-size: 35px; + font-weight: bold; + + @media (max-width: $two-columns-breakpoint) { + font-size: 25px; + } +} diff --git a/app/views/commencer/show.html.haml b/app/views/commencer/show.html.haml index ee3564f92..661b2b4d2 100644 --- a/app/views/commencer/show.html.haml +++ b/app/views/commencer/show.html.haml @@ -2,7 +2,7 @@ .commencer.form - if !user_signed_in? - %h1 Commencer la démarche + %h2.huge-title Commencer la démarche = link_to commencer_sign_up_path(path: @procedure.path), class: ['button large expand primary'] do Créer un compte %span.optional-on-small-screens @@ -20,7 +20,7 @@ - elsif drafts.count == 1 && not_drafts.count == 0 - dossier = drafts.first - %h1 Vous avez déjà commencé à remplir un dossier + %h2.huge-title Vous avez déjà commencé à remplir un dossier %p Il y a #{time_ago_in_words(dossier.created_at)}, vous avez commencé à remplir un dossier sur la démarche « #{dossier.procedure.libelle} ». @@ -29,7 +29,7 @@ - elsif not_drafts.count == 1 - dossier = not_drafts.first - %h1 Vous avez déjà déposé un dossier + %h2.huge-title Vous avez déjà déposé un dossier %p Il y a #{time_ago_in_words(dossier.en_construction_at)}, vous avez déposé un dossier sur la démarche « #{dossier.procedure.libelle} ». @@ -37,6 +37,6 @@ = link_to 'Commencer un nouveau dossier', url_for_new_dossier(@procedure), class: ['button large expand'] - else - %h1 Vous avez déjà des dossiers pour cette démarche + %h2.huge-title Vous avez déjà des dossiers pour cette démarche = link_to 'Voir mes dossiers en cours', dossiers_path, class: ['button large expand primary'] = link_to 'Commencer un nouveau dossier', url_for_new_dossier(@procedure), class: ['button large expand'] diff --git a/app/views/shared/_procedure_description.html.haml b/app/views/shared/_procedure_description.html.haml index ebdf356ea..03713ae51 100644 --- a/app/views/shared/_procedure_description.html.haml +++ b/app/views/shared/_procedure_description.html.haml @@ -2,7 +2,7 @@ = image_tag procedure.logo_url, alt: "logo #{procedure.libelle}" - if procedure.euro_flag = image_tag("flag_of_europe.svg", id: 'euro_flag', class: (!procedure.euro_flag ? "hidden" : "")) -%h2.procedure-title +%h1.procedure-title = procedure.libelle .procedure-description .procedure-description-body.read-more-enabled.read-more-collapsed diff --git a/app/views/users/sessions/new.html.haml b/app/views/users/sessions/new.html.haml index e92e681e2..5cdd40be4 100644 --- a/app/views/users/sessions/new.html.haml +++ b/app/views/users/sessions/new.html.haml @@ -3,7 +3,7 @@ .auth-form.sign-in-form = form_for User.new, url: user_session_path, html: { class: "form" } do |f| - %h1 Connectez-vous + %h2.huge-title Connectez-vous = f.label :email, "Email" = f.text_field :email, autofocus: true From 1a1bd306b14cadb3f43c87fbeace31f40e227bca Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 6 Nov 2019 17:12:57 +0100 Subject: [PATCH 27/53] Add main and headers tags in layout (RGAA 9.2.1) --- app/views/layouts/_new_header.haml | 2 +- app/views/layouts/application.html.haml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/_new_header.haml b/app/views/layouts/_new_header.haml index 440a01b4a..7eae51026 100644 --- a/app/views/layouts/_new_header.haml +++ b/app/views/layouts/_new_header.haml @@ -3,7 +3,7 @@ - dossier = controller.try(:dossier_for_help) - procedure = controller.try(:procedure_for_help) -.new-header{ class: current_page?(root_path) ? nil : "new-header-with-border" } +%header.new-header{ class: current_page?(root_path) ? nil : "new-header-with-border" } .header-inner-content .flex.align-center diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 7178d975f..8eb3e4004 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -33,8 +33,9 @@ Env Test = render partial: "layouts/new_header" - = render partial: "layouts/flash_messages" - = content_for?(:content) ? yield(:content) : yield + %main + = render partial: "layouts/flash_messages" + = content_for?(:content) ? yield(:content) : yield - if content_for?(:footer) = content_for(:footer) From 7f76ab66710c61fdc325da34789117a45656d518 Mon Sep 17 00:00:00 2001 From: clemkeirua Date: Thu, 31 Oct 2019 16:31:40 +0100 Subject: [PATCH 28/53] disable retry sending a mail with an invalid email adress --- app/mailers/application_mailer.rb | 4 ++++ spec/mailers/application_mailer_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 spec/mailers/application_mailer_spec.rb diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3504f1a7a..0eb908467 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -3,6 +3,10 @@ class ApplicationMailer < ActionMailer::Base default from: "demarches-simplifiees.fr <#{CONTACT_EMAIL}>" layout 'mailer' + rescue_from Net::SMTPSyntaxError do |_error| + message.perform_deliveries = false + end + # Attach the procedure logo to the email (if any). # Returns the attachment url. def attach_logo(procedure) diff --git a/spec/mailers/application_mailer_spec.rb b/spec/mailers/application_mailer_spec.rb new file mode 100644 index 000000000..9cfa295f9 --- /dev/null +++ b/spec/mailers/application_mailer_spec.rb @@ -0,0 +1,20 @@ +RSpec.describe ApplicationMailer, type: :mailer do + describe 'dealing with invalid emails' do + let(:dossier) { create(:dossier, procedure: build(:simple_procedure)) } + subject { DossierMailer.notify_new_draft(dossier) } + + describe 'invalid emails are not sent' do + before do + allow_any_instance_of(DossierMailer) + .to receive(:notify_new_draft) + .and_raise(Net::SMTPSyntaxError) + end + + it { expect(subject.message).to be_an_instance_of(ActionMailer::Base::NullMail) } + end + + describe 'valid emails are sent' do + it { expect(subject.message).not_to be_an_instance_of(ActionMailer::Base::NullMail) } + end + end +end From 75923f7cb2a4dbe1a0adc339e3070e4020f9cfad Mon Sep 17 00:00:00 2001 From: clemkeirua Date: Thu, 7 Nov 2019 10:13:11 +0100 Subject: [PATCH 29/53] add explanation about rescue_from in application_mailer --- app/mailers/application_mailer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 0eb908467..b444548f4 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -3,6 +3,7 @@ class ApplicationMailer < ActionMailer::Base default from: "demarches-simplifiees.fr <#{CONTACT_EMAIL}>" layout 'mailer' + # Don’t retry to send a message if the server rejects the recipient address rescue_from Net::SMTPSyntaxError do |_error| message.perform_deliveries = false end From 9469734f29c65b797114a3992104fa453536e18a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Thu, 7 Nov 2019 11:09:05 +0100 Subject: [PATCH 30/53] Follow flipper name convention to appears on manager admin page --- .../_left_panel_admin_procedurescontroller_navbar.html.haml | 4 ++-- spec/features/routing/full_scenario_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml b/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml index 4f0284677..80a7e9d19 100644 --- a/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml +++ b/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml @@ -30,14 +30,14 @@ .procedure-list-element Administrateurs - - if !feature_enabled?(:routage) + - if !feature_enabled?(:administrateur_routage) %a#onglet-instructeurs{ href: url_for(admin_procedure_assigns_path(@procedure)) } .procedure-list-element{ class: ('active' if active == 'Instructeurs') } Instructeurs - if @procedure.missing_steps.include?(:instructeurs) %p.missing-steps (à compléter) - - if feature_enabled?(:routage) + - if feature_enabled?(:administrateur_routage) %a#onglet-instructeurs{ href: url_for(procedure_groupe_instructeurs_path(@procedure)) } .procedure-list-element Groupe d'instructeurs diff --git a/spec/features/routing/full_scenario_spec.rb b/spec/features/routing/full_scenario_spec.rb index 02e338c1a..3eeb141dc 100644 --- a/spec/features/routing/full_scenario_spec.rb +++ b/spec/features/routing/full_scenario_spec.rb @@ -7,7 +7,7 @@ feature 'The routing' do let(:scientifique_user) { create(:user, password: password) } let(:litteraire_user) { create(:user, password: password) } - before { Flipper.enable_actor(:routage, administrateur.user) } + before { Flipper.enable_actor(:administrateur_routage, administrateur.user) } scenario 'works' do login_as administrateur.user, scope: :user From 4231b8172738442957bfbb61f0e01f6a6fbeafd4 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 17 Sep 2019 18:11:34 +0200 Subject: [PATCH 31/53] [GraphQL] Add create direct upload mutation --- app/graphql/mutations/base_mutation.rb | 2 +- app/graphql/mutations/create_direct_upload.rb | 41 ++++++++++ app/graphql/schema.graphql | 75 +++++++++++++++++++ app/graphql/types/mutation_type.rb | 1 + .../api/v2/graphql_controller_spec.rb | 33 ++++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 app/graphql/mutations/create_direct_upload.rb diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 84d59a361..c543a75f6 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -1,4 +1,4 @@ module Mutations - class BaseMutation < GraphQL::Schema::Mutation + class BaseMutation < GraphQL::Schema::RelayClassicMutation end end diff --git a/app/graphql/mutations/create_direct_upload.rb b/app/graphql/mutations/create_direct_upload.rb new file mode 100644 index 000000000..d4c3a90e0 --- /dev/null +++ b/app/graphql/mutations/create_direct_upload.rb @@ -0,0 +1,41 @@ +module Mutations + class CreateDirectUpload < Mutations::BaseMutation + description "File information required to prepare a direct upload" + + argument :dossier_id, ID, "Dossier ID", required: true, loads: Types::DossierType + argument :filename, String, "Original file name", required: true + argument :byte_size, Int, "File size (bytes)", required: true + argument :checksum, String, "MD5 file checksum as base64", required: true + argument :content_type, String, "File content type", required: true + + class DirectUpload < Types::BaseObject + description "Represents direct upload credentials" + + field :url, String, "Upload URL", null: false + field :headers, String, "HTTP request headers (JSON-encoded)", null: false + field :blob_id, ID, "Created blob record ID", null: false + field :signed_blob_id, ID, "Created blob record signed ID", null: false + end + + field :direct_upload, DirectUpload, null: false + + def resolve(filename:, byte_size:, checksum:, content_type:, dossier:) + blob = ActiveStorage::Blob.create_before_direct_upload!( + filename: filename, + byte_size: byte_size, + checksum: checksum, + content_type: content_type + ) + + { + direct_upload: { + url: blob.service_url_for_direct_upload, + # NOTE: we pass headers as JSON since they have no schema + headers: blob.service_headers_for_direct_upload.to_json, + blob_id: blob.id, + signed_blob_id: blob.signed_id + } + } + end + end +end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 6b6c5e355..a79948d38 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -40,6 +40,52 @@ GeoJSON coordinates """ scalar Coordinates +""" +Autogenerated input type of CreateDirectUpload +""" +input CreateDirectUploadInput { + """ + File size (bytes) + """ + byteSize: Int! + + """ + MD5 file checksum as base64 + """ + checksum: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + File content type + """ + contentType: String! + + """ + Dossier ID + """ + dossierId: ID! + + """ + Original file name + """ + filename: String! +} + +""" +Autogenerated return type of CreateDirectUpload +""" +type CreateDirectUploadPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + directUpload: DirectUpload! +} + type DateChamp implements Champ { id: ID! label: String! @@ -131,6 +177,31 @@ enum DemarcheState { publiee } +""" +Represents direct upload credentials +""" +type DirectUpload { + """ + Created blob record ID + """ + blobId: ID! + + """ + HTTP request headers (JSON-encoded) + """ + headers: String! + + """ + Created blob record signed ID + """ + signedBlobId: ID! + + """ + Upload URL + """ + url: String! +} + """ Un dossier """ @@ -324,6 +395,10 @@ type MultipleDropDownListChamp implements Champ { } type Mutation { + """ + File information required to prepare a direct upload + """ + createDirectUpload(input: CreateDirectUploadInput!): CreateDirectUploadPayload } """ diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 113861978..bbdb5021e 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,4 +1,5 @@ module Types class MutationType < Types::BaseObject + field :create_direct_upload, mutation: Mutations::CreateDirectUpload end end diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 384d85808..8aa7c0e7b 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -166,6 +166,39 @@ describe API::V2::GraphqlController do expect(gql_data[:dossier][:champs][0][:id]).to eq(dossier.champs[0].type_de_champ.to_typed_id) end end + + context "mutations" do + context 'createDirectUpload' do + let(:query) do + "mutation { + createDirectUpload(input: { + dossierId: \"#{dossier.to_typed_id}\", + filename: \"hello.png\", + byteSize: 1234, + checksum: \"qwerty1234\", + contentType: \"image/png\" + }) { + directUpload { + url + headers + blobId + signedBlobId + } + } + }" + end + + it "should initiate a direct upload" do + expect(gql_errors).to eq(nil) + + data = gql_data[:createDirectUpload][:directUpload] + expect(data[:url]).not_to be_nil + expect(data[:headers]).not_to be_nil + expect(data[:blobId]).not_to be_nil + expect(data[:signedBlobId]).not_to be_nil + end + end + end end context "when not authenticated" do From 2a61ed5b1c034f2d88a9b8365a426fcafd89e2c7 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 6 Nov 2019 15:55:01 +0100 Subject: [PATCH 32/53] Export etablissement information in csv fix #4440 --- app/models/dossier.rb | 47 +++++++++- app/services/procedure_export_v2_service.rb | 12 +-- .../procedure_export_v2_service_spec.rb | 86 +++++++++++++++++++ 3 files changed, 138 insertions(+), 7 deletions(-) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index d7d25ad7f..466948a7a 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -472,7 +472,19 @@ class Dossier < ApplicationRecord log_dossier_operation(avis.claimant, :demander_un_avis, avis) end - def spreadsheet_columns + def spreadsheet_columns_csv + spreadsheet_columns(with_etablissement: true) + end + + def spreadsheet_columns_xlsx + spreadsheet_columns + end + + def spreadsheet_columns_ods + spreadsheet_columns + end + + def spreadsheet_columns(with_etablissement: false) columns = [ ['ID', id.to_s], ['Email', user.email] @@ -485,6 +497,39 @@ class Dossier < ApplicationRecord ['Prénom', individual&.prenom], ['Date de naissance', individual&.birthdate] ] + elsif with_etablissement + columns += [ + ['Établissement SIRET', etablissement&.siret], + ['Établissement siège social', etablissement&.siege_social], + ['Établissement NAF', etablissement&.naf], + ['Établissement libellé NAF', etablissement&.libelle_naf], + ['Établissement Adresse', etablissement&.adresse], + ['Établissement numero voie', etablissement&.numero_voie], + ['Établissement type voie', etablissement&.type_voie], + ['Établissement nom voie', etablissement&.nom_voie], + ['Établissement complément adresse', etablissement&.complement_adresse], + ['Établissement code postal', etablissement&.code_postal], + ['Établissement localité', etablissement&.localite], + ['Établissement code INSEE localité', etablissement&.code_insee_localite], + ['Entreprise SIREN', etablissement&.entreprise_siren], + ['Entreprise capital social', etablissement&.entreprise_capital_social], + ['Entreprise numero TVA intracommunautaire', etablissement&.entreprise_numero_tva_intracommunautaire], + ['Entreprise forme juridique', etablissement&.entreprise_forme_juridique], + ['Entreprise forme juridique code', etablissement&.entreprise_forme_juridique_code], + ['Entreprise nom commercial', etablissement&.entreprise_nom_commercial], + ['Entreprise raison sociale', etablissement&.entreprise_raison_sociale], + ['Entreprise SIRET siège social', etablissement&.entreprise_siret_siege_social], + ['Entreprise code effectif entreprise', etablissement&.entreprise_code_effectif_entreprise], + ['Entreprise date de création', etablissement&.entreprise_date_creation], + ['Entreprise nom', etablissement&.entreprise_nom], + ['Entreprise prénom', etablissement&.entreprise_prenom], + ['Association RNA', etablissement&.association_rna], + ['Association titre', etablissement&.association_titre], + ['Association objet', etablissement&.association_objet], + ['Association date de création', etablissement&.association_date_creation], + ['Association date de déclaration', etablissement&.association_date_declaration], + ['Association date de publication', etablissement&.association_date_publication] + ] else columns << ['Entreprise raison sociale', etablissement&.entreprise_raison_sociale] end diff --git a/app/services/procedure_export_v2_service.rb b/app/services/procedure_export_v2_service.rb index 67409b723..bef8b0ec7 100644 --- a/app/services/procedure_export_v2_service.rb +++ b/app/services/procedure_export_v2_service.rb @@ -16,21 +16,21 @@ class ProcedureExportV2Service @tables = [:dossiers, :etablissements, :avis] + champs_repetables_options end - def to_csv(table = :dossiers) - SpreadsheetArchitect.to_csv(options_for(table)) + def to_csv + SpreadsheetArchitect.to_csv(options_for(:dossiers, :csv)) end def to_xlsx # We recursively build multi page spreadsheet @tables.reduce(nil) do |package, table| - SpreadsheetArchitect.to_axlsx_package(options_for(table), package) + SpreadsheetArchitect.to_axlsx_package(options_for(table, :xlsx), package) end.to_stream.read end def to_ods # We recursively build multi page spreadsheet @tables.reduce(nil) do |spreadsheet, table| - SpreadsheetArchitect.to_rodf_spreadsheet(options_for(table), spreadsheet) + SpreadsheetArchitect.to_rodf_spreadsheet(options_for(table, :ods), spreadsheet) end.bytes end @@ -74,10 +74,10 @@ class ProcedureExportV2Service ActiveStorage::Filename.new(name.to_s).sanitized.truncate(30) end - def options_for(table) + def options_for(table, format) case table when :dossiers - { instances: dossiers.to_a, sheet_name: 'Dossiers' }.merge(DEFAULT_STYLES) + { instances: dossiers.to_a, sheet_name: 'Dossiers', spreadsheet_columns: :"spreadsheet_columns_#{format}" }.merge(DEFAULT_STYLES) when :etablissements { instances: etablissements.to_a, sheet_name: 'Etablissements' }.merge(DEFAULT_STYLES) when :avis diff --git a/spec/services/procedure_export_v2_service_spec.rb b/spec/services/procedure_export_v2_service_spec.rb index 5635c0380..932bffebf 100644 --- a/spec/services/procedure_export_v2_service_spec.rb +++ b/spec/services/procedure_export_v2_service_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'csv' describe ProcedureExportV2Service do describe 'to_data' do @@ -150,6 +151,91 @@ describe ProcedureExportV2Service do ] end + context 'as csv' do + subject do + Tempfile.create do |f| + f << ProcedureExportV2Service.new(procedure, procedure.dossiers).to_csv + f.rewind + CSV.read(f.path) + end + end + + let(:nominal_headers) do + [ + "ID", + "Email", + "Établissement SIRET", + "Établissement siège social", + "Établissement NAF", + "Établissement libellé NAF", + "Établissement Adresse", + "Établissement numero voie", + "Établissement type voie", + "Établissement nom voie", + "Établissement complément adresse", + "Établissement code postal", + "Établissement localité", + "Établissement code INSEE localité", + "Entreprise SIREN", + "Entreprise capital social", + "Entreprise numero TVA intracommunautaire", + "Entreprise forme juridique", + "Entreprise forme juridique code", + "Entreprise nom commercial", + "Entreprise raison sociale", + "Entreprise SIRET siège social", + "Entreprise code effectif entreprise", + "Entreprise date de création", + "Entreprise nom", + "Entreprise prénom", + "Association RNA", + "Association titre", + "Association objet", + "Association date de création", + "Association date de déclaration", + "Association date de publication", + "Archivé", + "État du dossier", + "Dernière mise à jour le", + "Déposé le", + "Passé en instruction le", + "Traité le", + "Motivation de la décision", + "Instructeurs", + "textarea", + "date", + "datetime", + "number", + "decimal_number", + "integer_number", + "checkbox", + "civilite", + "email", + "phone", + "address", + "yes_no", + "simple_drop_down_list", + "multiple_drop_down_list", + "linked_drop_down_list", + "pays", + "regions", + "departements", + "engagement", + "dossier_link", + "piece_justificative", + "siret", + "carte", + "text" + ] + end + + let(:dossiers_sheet_headers) { subject.first } + + it 'should have headers' do + expect(dossiers_sheet_headers).to match(nominal_headers) + end + end + it 'should have headers' do expect(dossiers_sheet.headers).to match(nominal_headers) From f22b39b7c53be865970e9a693dc51f0efef976b5 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 6 Nov 2019 17:03:07 +0100 Subject: [PATCH 33/53] Handle non unique champ repetable labels fix #4466 --- app/models/champs/repetition_champ.rb | 6 ++++++ app/services/procedure_export_v2_service.rb | 9 ++------- spec/services/procedure_export_v2_service_spec.rb | 15 +++++++++++++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/models/champs/repetition_champ.rb b/app/models/champs/repetition_champ.rb index 9296dbdca..7d31812cd 100644 --- a/app/models/champs/repetition_champ.rb +++ b/app/models/champs/repetition_champ.rb @@ -33,6 +33,12 @@ class Champs::RepetitionChamp < Champ end end + # We have to truncate the label here as spreadsheets have a (30 char) limit on length. + def libelle_for_export + str = "(#{type_de_champ.stable_id}) #{libelle}" + ActiveStorage::Filename.new(str).sanitized.truncate(30) + end + class Row < Hashie::Dash property :index property :dossier_id diff --git a/app/services/procedure_export_v2_service.rb b/app/services/procedure_export_v2_service.rb index bef8b0ec7..c8ba3bffe 100644 --- a/app/services/procedure_export_v2_service.rb +++ b/app/services/procedure_export_v2_service.rb @@ -53,7 +53,7 @@ class ProcedureExportV2Service [dossier.champs, dossier.champs_private] .flatten .filter { |champ| champ.is_a?(Champs::RepetitionChamp) } - end.group_by(&:libelle) + end.group_by(&:libelle_for_export) end def champs_repetables_options @@ -70,10 +70,6 @@ class ProcedureExportV2Service row_style: { background_color: nil, color: "000000", font_size: 12 } } - def sanitize_sheet_name(name) - ActiveStorage::Filename.new(name.to_s).sanitized.truncate(30) - end - def options_for(table, format) case table when :dossiers @@ -83,8 +79,7 @@ class ProcedureExportV2Service when :avis { instances: avis.to_a, sheet_name: 'Avis' }.merge(DEFAULT_STYLES) when Array - # We have to truncate the label here as spreadsheets have a (30 char) limit on length. - { instances: table.last, sheet_name: sanitize_sheet_name(table.first) }.merge(DEFAULT_STYLES) + { instances: table.last, sheet_name: table.first }.merge(DEFAULT_STYLES) end end end diff --git a/spec/services/procedure_export_v2_service_spec.rb b/spec/services/procedure_export_v2_service_spec.rb index 932bffebf..d8c30ecd7 100644 --- a/spec/services/procedure_export_v2_service_spec.rb +++ b/spec/services/procedure_export_v2_service_spec.rb @@ -311,7 +311,7 @@ describe ProcedureExportV2Service do let(:champ_repetition) { dossiers.first.champs.find { |champ| champ.type_champ == 'repetition' } } it 'should have sheets' do - expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle]) + expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle_for_export]) end it 'should have headers' do @@ -333,7 +333,18 @@ describe ProcedureExportV2Service do end it 'should have valid sheet name' do - expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', "A - B - C"]) + expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', "(#{champ_repetition.type_de_champ.stable_id}) A - B - C"]) + end + end + + context 'with non unique labels' do + let(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure) } + let(:champ_repetition) { dossier.champs.find { |champ| champ.type_champ == 'repetition' } } + let(:type_de_champ_repetition) { create(:type_de_champ_repetition, procedure: procedure, libelle: champ_repetition.libelle) } + let!(:another_champ_repetition) { create(:champ_repetition, type_de_champ: type_de_champ_repetition, dossier: dossier) } + + it 'should have sheets' do + expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle_for_export, another_champ_repetition.libelle_for_export]) end end end From fc8a0d46ffcc0978e555851cb9ac4430a5abd8ad Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Thu, 7 Nov 2019 15:19:11 +0100 Subject: [PATCH 34/53] editor: insert new champs after the last fully visible champ Before, when the "Add new champ" button was clicked, the new champ was inserted after the **first** fully visible champ. That was most of the time unexpected. The correct behavior would be to insert the new champ after the **last** fully visible champ. That's what this commit does. Now the "Add new champ" behavior feels much less confusing. --- .../components/TypesDeChampEditor/typeDeChampsReducer.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/javascript/components/TypesDeChampEditor/typeDeChampsReducer.js b/app/javascript/components/TypesDeChampEditor/typeDeChampsReducer.js index 5d27c8399..7810b2931 100644 --- a/app/javascript/components/TypesDeChampEditor/typeDeChampsReducer.js +++ b/app/javascript/components/TypesDeChampEditor/typeDeChampsReducer.js @@ -219,7 +219,7 @@ function getUpdateHandler(typeDeChamp, { queue, flash }) { } function findItemToInsertAfter() { - const target = getFirstTarget(); + const target = getLastVisibleTypeDeChamp(); return { target, @@ -227,8 +227,10 @@ function findItemToInsertAfter() { }; } -function getFirstTarget() { - const [target] = document.querySelectorAll('[data-in-view]'); +function getLastVisibleTypeDeChamp() { + const typeDeChamps = document.querySelectorAll('[data-in-view]'); + const target = typeDeChamps[typeDeChamps.length - 1]; + if (target) { const parentTarget = target.closest('[data-repetition]'); if (parentTarget) { From 8a7f4ec7696d92b2225da06048848d57dc73f329 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Thu, 7 Nov 2019 15:44:55 +0100 Subject: [PATCH 35/53] editor: reduce scroll duration when inserting a new champ The previous default was 1s, which felt slooooow. --- .../components/TypesDeChampEditor/typeDeChampsReducer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/components/TypesDeChampEditor/typeDeChampsReducer.js b/app/javascript/components/TypesDeChampEditor/typeDeChampsReducer.js index 7810b2931..81e603082 100644 --- a/app/javascript/components/TypesDeChampEditor/typeDeChampsReducer.js +++ b/app/javascript/components/TypesDeChampEditor/typeDeChampsReducer.js @@ -56,7 +56,9 @@ function addTypeDeChamp(state, typeDeChamps, insertAfter, done) { state.flash.success(); done(); if (insertAfter) { - scrollToComponent(insertAfter.target.nextElementSibling); + scrollToComponent(insertAfter.target.nextElementSibling, { + duration: 300 + }); } }) .catch(message => state.flash.error(message)); From 5bc8b5fe12e5de00300fad7402db161376e22011 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 7 Nov 2019 17:20:09 +0100 Subject: [PATCH 36/53] Implement ActiveStorage::FileNotFoundError --- app/lib/active_storage/file_not_found_error.rb | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 app/lib/active_storage/file_not_found_error.rb diff --git a/app/lib/active_storage/file_not_found_error.rb b/app/lib/active_storage/file_not_found_error.rb new file mode 100644 index 000000000..580d30666 --- /dev/null +++ b/app/lib/active_storage/file_not_found_error.rb @@ -0,0 +1,4 @@ +module ActiveStorage + # activestorage-openstack uses ActiveStorage::FileNotFoundError which only exists in rails 6 + class FileNotFoundError < StandardError; end +end From 6351eabfdd347da9ddacef71807b3ed2647f04a0 Mon Sep 17 00:00:00 2001 From: clemkeirua Date: Thu, 7 Nov 2019 17:21:45 +0100 Subject: [PATCH 37/53] remove notification to report-uri in production --- config/initializers/content_security_policy.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index bb61fc264..960c3cd3d 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -1,9 +1,8 @@ Rails.application.config.content_security_policy do |policy| - # En cas de non respect d'une des règles, faire un POST sur cette URL - if Rails.env.production? - policy.report_uri "https://demarchessimplifieestest.report-uri.com/r/d/csp/reportOnly" - else - policy.report_uri "http://#{ENV['APP_HOST']}/csp/" # ne pas notifier report-uri en dev/test + if Rails.env.development? + # les CSP ne sont pas appliquées en dev: on notifie cependant une url quelconque de la violation + # pour détecter les erreurs lors de l'ajout d'une nouvelle brique externe durant le développement + policy.report_uri "http://#{ENV['APP_HOST']}/csp/" end # Whitelist image policy.img_src :self, "*.openstreetmap.org", "static.demarches-simplifiees.fr", "*.cloud.ovh.net", "stats.data.gouv.fr", "*", :data From 990c867c2ef2e7034d5e490ab0e6549afc4200c7 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 26 Sep 2019 14:57:58 +0200 Subject: [PATCH 38/53] [GraphQL] Add more filters to dossiers --- app/controllers/api/v1/dossiers_controller.rb | 2 +- app/graphql/schema.graphql | 36 +++++-- app/graphql/types/demarche_type.rb | 22 +++-- app/graphql/types/order.rb | 6 ++ app/models/dossier.rb | 5 +- app/models/procedure.rb | 2 +- app/services/procedure_export_service.rb | 11 +-- app/services/procedure_export_v2_service.rb | 11 +-- .../api/v2/graphql_controller_spec.rb | 99 ++++++++++++++----- 9 files changed, 130 insertions(+), 64 deletions(-) create mode 100644 app/graphql/types/order.rb diff --git a/app/controllers/api/v1/dossiers_controller.rb b/app/controllers/api/v1/dossiers_controller.rb index 9a990734d..222e786dd 100644 --- a/app/controllers/api/v1/dossiers_controller.rb +++ b/app/controllers/api/v1/dossiers_controller.rb @@ -53,7 +53,7 @@ class API::V1::DossiersController < APIController end order = ORDER_DIRECTIONS.fetch(params[:order], :asc) - @dossiers = @procedure.dossiers.state_not_brouillon.order_for_api(order) + @dossiers = @procedure.dossiers.state_not_brouillon.order_by_created_at(order) rescue ActiveRecord::RecordNotFound render json: {}, status: :not_found diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index a79948d38..c2a558d5b 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -128,25 +128,35 @@ type Demarche { """ before: String + """ + Dossiers déposés depuis la date. + """ + createdSince: ISO8601DateTime + """ Returns the first _n_ elements from the list. """ first: Int - """ - Filtrer les dossiers par ID. - """ - ids: [ID!] - """ Returns the last _n_ elements from the list. """ last: Int """ - Dossiers crées depuis la date. + L'ordre des dossiers. """ - since: ISO8601DateTime + order: Order = ASC + + """ + Dossiers avec statut. + """ + state: DossierState + + """ + Dossiers mis à jour depuis la date. + """ + updatedSince: ISO8601DateTime ): DossierConnection! groupeInstructeurs: [GroupeInstructeur!]! id: ID! @@ -401,6 +411,18 @@ type Mutation { createDirectUpload(input: CreateDirectUploadInput!): CreateDirectUploadPayload } +enum Order { + """ + L‘ordre ascendant. + """ + ASC + + """ + L‘ordre descendant. + """ + DESC +} + """ Information about pagination in a connection. """ diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index 37782aafe..665ab28b9 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -21,8 +21,10 @@ module Types field :groupe_instructeurs, [Types::GroupeInstructeurType], null: false field :dossiers, Types::DossierType.connection_type, "Liste de tous les dossiers d'une démarche.", null: false do - argument :ids, [ID], required: false, description: "Filtrer les dossiers par ID." - argument :since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers crées depuis la date." + argument :order, Types::Order, default_value: :asc, required: false, description: "L'ordre des dossiers." + argument :created_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers déposés depuis la date." + argument :updated_since, GraphQL::Types::ISO8601DateTime, required: false, description: "Dossiers mis à jour depuis la date." + argument :state, Types::DossierType::DossierState, required: false, description: "Dossiers avec statut." end field :champ_descriptors, [Types::ChampDescriptorType], null: false, method: :types_de_champ @@ -36,15 +38,21 @@ module Types Loaders::Association.for(object.class, groupe_instructeurs: { procedure: [:administrateurs] }).load(object) end - def dossiers(ids: nil, since: nil) + def dossiers(updated_since: nil, created_since: nil, state: nil, order:) dossiers = object.dossiers.for_api_v2 - if ids.present? - dossiers = dossiers.where(id: ids) + if state.present? + dossiers = dossiers.where(state: state) end - if since.present? - dossiers = dossiers.since(since) + if updated_since.present? + dossiers = dossiers.updated_since(updated_since).order_by_updated_at(order) + else + if created_since.present? + dossiers = dossiers.created_since(created_since) + end + + dossiers = dossiers.order_by_created_at(order) end dossiers diff --git a/app/graphql/types/order.rb b/app/graphql/types/order.rb new file mode 100644 index 000000000..885e80c22 --- /dev/null +++ b/app/graphql/types/order.rb @@ -0,0 +1,6 @@ +module Types + class Order < Types::BaseEnum + value('ASC', 'L‘ordre ascendant.', value: :asc) + value('DESC', 'L‘ordre descendant.', value: :desc) + end +end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 466948a7a..0b2bc94a1 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -105,7 +105,9 @@ class Dossier < ApplicationRecord scope :not_archived, -> { where(archived: false) } scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) } - scope :order_for_api, -> (order = :asc) { order(en_construction_at: order, created_at: order, id: order) } + scope :order_by_created_at, -> (order = :asc) { order(en_construction_at: order, created_at: order, id: order) } + scope :updated_since, -> (since) { where('dossiers.updated_at >= ?', since) } + scope :created_since, -> (since) { where('dossiers.en_construction_at >= ?', since) } scope :all_state, -> { not_archived.state_not_brouillon } scope :en_construction, -> { not_archived.state_en_construction } @@ -134,7 +136,6 @@ class Dossier < ApplicationRecord scope :without_followers, -> { left_outer_joins(:follows).where(follows: { id: nil }) } scope :with_champs, -> { includes(champs: :type_de_champ) } scope :nearing_end_of_retention, -> (duration = '1 month') { joins(:procedure).where("en_instruction_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - now() < interval ?", duration) } - scope :since, -> (since) { where('dossiers.en_construction_at >= ?', since) } scope :for_api, -> { includes(commentaires: { piece_jointe_attachment: :blob }, champs: [ diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 96a427dbb..6abe348c7 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -444,7 +444,7 @@ class Procedure < ApplicationRecord version = options.delete(:version) if version == 'v2' options.delete(:tables) - ProcedureExportV2Service.new(self, dossiers, **options.to_h.symbolize_keys) + ProcedureExportV2Service.new(self, dossiers) else ProcedureExportService.new(self, dossiers, **options.to_h.symbolize_keys) end diff --git a/app/services/procedure_export_service.rb b/app/services/procedure_export_service.rb index f2a192b1b..6d0b1386c 100644 --- a/app/services/procedure_export_service.rb +++ b/app/services/procedure_export_service.rb @@ -49,7 +49,7 @@ class ProcedureExportService :prenom ] - def initialize(procedure, dossiers, tables: [], ids: nil, since: nil, limit: nil) + def initialize(procedure, dossiers, tables: []) @procedure = procedure @attributes = ATTRIBUTES.dup @@ -59,15 +59,6 @@ class ProcedureExportService end @dossiers = dossiers.downloadable_sorted - if ids - @dossiers = @dossiers.where(id: ids) - end - if since - @dossiers = @dossiers.since(since) - end - if limit - @dossiers = @dossiers.limit(limit) - end @dossiers = @dossiers.to_a @tables = tables.map(&:to_sym) end diff --git a/app/services/procedure_export_v2_service.rb b/app/services/procedure_export_v2_service.rb index c8ba3bffe..eae682c1e 100644 --- a/app/services/procedure_export_v2_service.rb +++ b/app/services/procedure_export_v2_service.rb @@ -1,18 +1,9 @@ class ProcedureExportV2Service attr_reader :dossiers - def initialize(procedure, dossiers, ids: nil, since: nil, limit: nil) + def initialize(procedure, dossiers) @procedure = procedure @dossiers = dossiers.downloadable_sorted - if ids - @dossiers = @dossiers.where(id: ids) - end - if since - @dossiers = @dossiers.since(since) - end - if limit - @dossiers = @dossiers.limit(limit) - end @tables = [:dossiers, :etablissements, :avis] + champs_repetables_options end diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 8aa7c0e7b..048c098f2 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -12,6 +12,14 @@ describe API::V2::GraphqlController do create(:commentaire, dossier: dossier, email: 'test@test.com') dossier end + let(:dossier1) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: 1.day.ago) } + let(:dossier2) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: 3.days.ago) } + let(:dossiers) { [dossier2, dossier1, dossier] } + let(:instructeur) { create(:instructeur, followed_dossiers: dossiers) } + + before do + instructeur.assign_to_procedure(procedure) + end let(:query) do "{ @@ -62,31 +70,65 @@ describe API::V2::GraphqlController do request.env['HTTP_AUTHORIZATION'] = authorization_header end - it "should return demarche" do - expect(gql_errors).to eq(nil) - expect(gql_data).to eq(demarche: { - id: procedure.to_typed_id, - number: procedure.id.to_s, - title: procedure.libelle, - description: procedure.description, - state: 'brouillon', - archivedAt: nil, - createdAt: procedure.created_at.iso8601, - updatedAt: procedure.updated_at.iso8601, - groupeInstructeurs: [{ instructeurs: [], label: "défaut" }], - champDescriptors: procedure.types_de_champ.map do |tdc| - { - id: tdc.to_typed_id, - label: tdc.libelle, - type: tdc.type_champ, - description: tdc.description, - required: tdc.mandatory? + context "demarche" do + it "should be returned" do + expect(gql_errors).to eq(nil) + expect(gql_data).to eq(demarche: { + id: procedure.to_typed_id, + number: procedure.id.to_s, + title: procedure.libelle, + description: procedure.description, + state: 'brouillon', + archivedAt: nil, + createdAt: procedure.created_at.iso8601, + updatedAt: procedure.updated_at.iso8601, + groupeInstructeurs: [ + { + instructeurs: [{ email: instructeur.email }], + label: "défaut" + } + ], + champDescriptors: procedure.types_de_champ.map do |tdc| + { + id: tdc.to_typed_id, + label: tdc.libelle, + type: tdc.type_champ, + description: tdc.description, + required: tdc.mandatory? + } + end, + dossiers: { + nodes: dossiers.map { |dossier| { id: dossier.to_typed_id } } } - end, - dossiers: { - nodes: [] - } - }) + }) + end + + context "filter dossiers" do + let(:query) do + "{ + demarche(number: #{procedure.id}) { + id + number + dossiers(createdSince: \"#{2.days.ago.iso8601}\") { + nodes { + id + } + } + } + }" + end + + it "should be returned" do + expect(gql_errors).to eq(nil) + expect(gql_data).to eq(demarche: { + id: procedure.to_typed_id, + number: procedure.id.to_s, + dossiers: { + nodes: [{ id: dossier1.to_typed_id }, { id: dossier.to_typed_id }] + } + }) + end + end end context "dossier" do @@ -130,7 +172,7 @@ describe API::V2::GraphqlController do }" end - it "should return dossier" do + it "should be returned" do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossier: { id: dossier.to_typed_id, @@ -146,7 +188,12 @@ describe API::V2::GraphqlController do id: dossier.user.to_typed_id, email: dossier.user.email }, - instructeurs: [], + instructeurs: [ + { + id: instructeur.to_typed_id, + email: instructeur.email + } + ], messages: dossier.commentaires.map do |commentaire| { body: commentaire.body, From fa741c9baee48fb366a9294ec9befe26786488cb Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 7 Nov 2019 15:08:54 +0100 Subject: [PATCH 39/53] [GraphQL]: do not return dossiers brouillons --- app/graphql/types/demarche_type.rb | 2 +- spec/controllers/api/v2/graphql_controller_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index 665ab28b9..b78ea4f14 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -39,7 +39,7 @@ module Types end def dossiers(updated_since: nil, created_since: nil, state: nil, order:) - dossiers = object.dossiers.for_api_v2 + dossiers = object.dossiers.state_not_brouillon.for_api_v2 if state.present? dossiers = dossiers.where(state: state) diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 048c098f2..3d1f0a53a 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -14,6 +14,7 @@ describe API::V2::GraphqlController do end let(:dossier1) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: 1.day.ago) } let(:dossier2) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: 3.days.ago) } + let!(:dossier_brouillon) { create(:dossier, procedure: procedure) } let(:dossiers) { [dossier2, dossier1, dossier] } let(:instructeur) { create(:instructeur, followed_dossiers: dossiers) } From fe84e8e0f7bf4f1fa4042e929c13d14345e4fb4a Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 12 Nov 2019 15:26:18 +0100 Subject: [PATCH 40/53] Remove carrierwave --- Gemfile | 3 - Gemfile.lock | 10 -- config/env.example | 1 - config/secrets.yml | 4 - ...mmentaire_piece_justificative_to_file.rake | 51 -------- lib/tasks/cloud_storage.rake | 113 ------------------ 6 files changed, 182 deletions(-) delete mode 100644 lib/tasks/2017_10_30_copy_commentaire_piece_justificative_to_file.rake delete mode 100644 lib/tasks/cloud_storage.rake diff --git a/Gemfile b/Gemfile index 1d23d00d8..3a224b34a 100644 --- a/Gemfile +++ b/Gemfile @@ -13,12 +13,9 @@ gem 'bcrypt' gem 'bootstrap-sass', '>= 3.4.1' gem 'bootstrap-wysihtml5-rails', '~> 0.3.3.8' gem 'browser' -gem 'carrierwave' -gem 'carrierwave-i18n' gem 'chartkick' gem 'chunky_png' gem 'clamav-client', require: 'clamav/client' -gem 'copy_carrierwave_file' gem 'daemons' gem 'deep_cloneable' # Enable deep clone of active record models gem 'delayed_cron_job' # Cron jobs diff --git a/Gemfile.lock b/Gemfile.lock index fae5871bb..71de81e68 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,11 +136,6 @@ GEM capybara-selenium (0.0.6) capybara selenium-webdriver - carrierwave (1.3.1) - activemodel (>= 4.0.0) - activesupport (>= 4.0.0) - mime-types (>= 1.16) - carrierwave-i18n (0.2.0) case_transform (0.2) activesupport chartkick (3.2.0) @@ -158,8 +153,6 @@ GEM coffee-script-source (1.12.2) concurrent-ruby (1.1.5) connection_pool (2.2.2) - copy_carrierwave_file (1.3.0) - carrierwave (>= 0.9) crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.5) @@ -730,12 +723,9 @@ DEPENDENCIES capybara-email capybara-screenshot capybara-selenium - carrierwave - carrierwave-i18n chartkick chunky_png clamav-client - copy_carrierwave_file daemons database_cleaner deep_cloneable diff --git a/config/env.example b/config/env.example index c3c40d089..aa54c288a 100644 --- a/config/env.example +++ b/config/env.example @@ -23,7 +23,6 @@ FOG_OPENSTACK_IDENTITY_API_VERSION="" FOG_OPENSTACK_REGION="" FOG_DIRECTORY="" FOG_ENABLED="" -CARRIERWAVE_CACHE_DIR="/tmp/tps-local-cache" DS_PROXY_URL="" FC_PARTICULIER_ID="" diff --git a/config/secrets.yml b/config/secrets.yml index 2ac8aa244..23c8e5267 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -42,8 +42,6 @@ defaults: &defaults openstack_identity_api_version: "<%= ENV['FOG_OPENSTACK_IDENTITY_API_VERSION'] %>" openstack_region: <%= ENV['FOG_OPENSTACK_REGION'] %> directory: <%= ENV['FOG_DIRECTORY'] %> - carrierwave: - cache_dir: <%= ENV['CARRIERWAVE_CACHE_DIR'] %> mailtrap: username: <%= ENV['MAILTRAP_USERNAME'] %> password: <%= ENV['MAILTRAP_PASSWORD'] %> @@ -84,8 +82,6 @@ test: key: api_entreprise_test_key fog: directory: tps_dev - carrierwave: - cache_dir: /tmp/tps-test-cache pipedrive: key: pipedrive_test_key france_connect_particulier: diff --git a/lib/tasks/2017_10_30_copy_commentaire_piece_justificative_to_file.rake b/lib/tasks/2017_10_30_copy_commentaire_piece_justificative_to_file.rake deleted file mode 100644 index 6a721ed00..000000000 --- a/lib/tasks/2017_10_30_copy_commentaire_piece_justificative_to_file.rake +++ /dev/null @@ -1,51 +0,0 @@ -require Rails.root.join("lib", "tasks", "task_helper") - -namespace :'2017_10_30_copy_commentaire_piece_justificative_to_file' do - task set: :environment do - commentaires_to_process = Commentaire.where(file: nil).where.not(piece_justificative_id: nil).reorder(id: :desc) - - rake_puts "#{commentaires_to_process.count} commentaires to process..." - - commentaires_to_process.each do |c| - process_commentaire(c) - end - end - - task fix: :environment do - commentaires_to_fix = Commentaire.where.not(file: nil).where.not(piece_justificative_id: nil).reorder(id: :desc) - - rake_puts "#{commentaires_to_fix.count} commentaires to fix..." - - commentaires_to_fix.each do |c| - process_commentaire(c) - end - end - - def sanitize_name(name) # from https://github.com/carrierwaveuploader/carrierwave/blob/master/lib/carrierwave/sanitized_file.rb#L323 - name = name.gsub(/[^[:word:]\.\-\+]/, "_") - name = "_#{name}" if name.match?(/\A\.+\z/) - name = "unnamed" if name.empty? - return name.mb_chars.to_s - end - - def process_commentaire(commentaire) - rake_puts "Processing commentaire #{commentaire.id}" - if commentaire.piece_justificative.present? - # https://github.com/carrierwaveuploader/carrierwave#uploading-files-from-a-remote-location - commentaire.remote_file_url = commentaire.piece_justificative.content_url - - if commentaire.piece_justificative.original_filename.present? - commentaire.file.define_singleton_method(:filename) { sanitize_name(commentaire.piece_justificative.original_filename) } - end - - if commentaire.body.blank? - commentaire.body = commentaire.piece_justificative.original_filename || "." - end - - commentaire.save - if commentaire.file.blank? - rake_puts "Failed to save file for commentaire #{commentaire.id}" - end - end - end -end diff --git a/lib/tasks/cloud_storage.rake b/lib/tasks/cloud_storage.rake deleted file mode 100644 index 2a276ac55..000000000 --- a/lib/tasks/cloud_storage.rake +++ /dev/null @@ -1,113 +0,0 @@ -require Rails.root.join("lib", "tasks", "task_helper") - -namespace :cloudstorage do - task init: :environment do - os_config = (YAML.load_file(Fog.credentials_path))['default'] - @os = OpenStack::Connection.create( - { - username: os_config['openstack_username'], - api_key: os_config['openstack_api_key'], - auth_method: "password", - auth_url: "https://auth.cloud.ovh.net/v2.0/", - authtenant_name: os_config['openstack_tenant'], - service_type: "object-store", - region: os_config['openstack_region'] - } - ) - @cont = @os.container(CarrierWave::Uploader::Base.fog_directory) - end - - desc 'Move local attestations on cloud storage' - task migrate: :environment do - puts 'Starting migration' - - Rake::Task['cloudstorage:init'].invoke - - error_count = 0 - [Cerfa, PieceJustificative, Procedure].each do |c| - c.all.each do |entry| - content = (c == Procedure) ? entry.logo : entry.content - if !(content.current_path.nil? || File.exist?(File.dirname(content.current_path) + '/uploaded')) - secure_token = SecureRandom.uuid - filename = "#{entry.class.to_s.underscore}-#{secure_token}#{File.extname(content.current_path)}" - rake_puts "Uploading #{content.current_path}" - begin - @cont.create_object(filename, {}, File.open(content.current_path)) - - File.open(File.dirname(content.current_path) + '/uploaded', "w+") { |f| f.write(File.basename(content.current_path)) } - File.open(File.dirname(content.current_path) + '/filename_cloudstorage', "w+") { |f| f.write(filename) } - File.open(File.dirname(content.current_path) + '/secure_token_cloudstorage', "w+") { |f| f.write(secure_token) } - - entry.update_column(c == Procedure ? :logo : :content, filename) - entry.update_column(c == Procedure ? :logo_secure_token : :content_secure_token, secure_token) - rescue Errno::ENOENT - rake_puts "ERROR: #{content.current_path} does not exist!" - File.open('upload_errors.report', "a+") { |f| f.write(content.current_path) } - error_count += 1 - end - else - if content.current_path.present? && File.exist?(File.dirname(content.current_path) + '/uploaded') - filename = File.open(File.dirname(content.current_path) + '/filename_cloudstorage', "r").read - secure_token = File.open(File.dirname(content.current_path) + '/secure_token_cloudstorage', "r").read - - entry.update_column(c == Procedure ? :logo : :content, filename) - entry.update_column(c == Procedure ? :logo_secure_token : :content_secure_token, secure_token) - - rake_puts "RESTORE IN DATABASE: #{filename} " - elsif content.current_path.present? - rake_puts "Skipping #{content.current_path}" - end - end - end - end - - rake_puts "There were #{error_count} errors while uploading files. See upload_errors.report file for details." - puts 'Enf of migration' - end - - desc 'Clear documents in tenant and revert file entries in database' - task :revert do - Rake::Task['cloudstorage:init'].invoke - - [Cerfa, PieceJustificative, Procedure].each do |c| - c.all.each do |entry| - content = (c == Procedure) ? entry.logo : entry.content - if content.current_path.present? - if File.exist?(File.dirname(content.current_path) + '/uploaded') - previous_filename = File.read(File.dirname(content.current_path) + '/uploaded') - - entry.update_column(c == Procedure ? :logo : :content, previous_filename) - entry.update_column(c == Procedure ? :logo_secure_token : :content_secure_token, nil) - - rake_puts "restoring #{content.current_path} db data to #{previous_filename}" - - @cont.delete_object(File.open(File.dirname(content.current_path) + '/filename_cloudstorage', "r").read) - - FileUtils.rm(File.dirname(content.current_path) + '/uploaded') - FileUtils.rm(File.dirname(content.current_path) + '/filename_cloudstorage') - FileUtils.rm(File.dirname(content.current_path) + '/secure_token_cloudstorage') - end - end - end - end - end - - desc 'Clear old documents in tenant' - task :clear do - Rake::Task['cloudstorage:init'].invoke - - @cont.objects.each do |object| - rake_puts "Removing #{object}" - @cont.delete_object(object) - end - end - - task :clear_old_objects do - Rake::Task['cloudstorage:init'].invoke - - @cont.objects_detail.each do |object, details| - last_modified = Time.zone.parse(details[:last_modified]) - @cont.delete_object(object) if last_modified.utc <= (Time.zone.now - 2.years).utc - end - end -end From ac092089ff9b0c88237cfbed750c13747333783d Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 12 Nov 2019 17:38:27 +0100 Subject: [PATCH 41/53] Remove service.siret from manager --- app/dashboards/service_dashboard.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/dashboards/service_dashboard.rb b/app/dashboards/service_dashboard.rb index 5e3ea65fe..36134116a 100644 --- a/app/dashboards/service_dashboard.rb +++ b/app/dashboards/service_dashboard.rb @@ -18,8 +18,7 @@ class ServiceDashboard < Administrate::BaseDashboard email: Field::String, telephone: Field::String, horaires: Field::String, - adresse: Field::String, - siret: Field::String + adresse: Field::String }.freeze # COLLECTION_ATTRIBUTES @@ -45,8 +44,7 @@ class ServiceDashboard < Administrate::BaseDashboard :email, :telephone, :horaires, - :adresse, - :siret + :adresse ].freeze # FORM_ATTRIBUTES From 88373ddf2086f39903b33202be7739242f7d914e Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Tue, 12 Nov 2019 18:29:37 +0100 Subject: [PATCH 42/53] individual: raise when the individual object cannot be created MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, a number of dossiers in production are for procedures for individuals, but don't have a `individual` object. This is probably because creating the Individual from France Connect infos fails – but fails quietly, and nullify the relationship. As a first step, we now raise an exception when the creation from FC infos fails. We will then identify the issue, and see if we can fix it – or if we should be resilient to this kind of issues. --- app/models/individual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/individual.rb b/app/models/individual.rb index 32d2e0445..41a6f3963 100644 --- a/app/models/individual.rb +++ b/app/models/individual.rb @@ -10,7 +10,7 @@ class Individual < ApplicationRecord GENDER_FEMALE = 'Mme' def self.create_from_france_connect(fc_information) - create( + create!( nom: fc_information.family_name, prenom: fc_information.given_name, gender: fc_information.gender == 'female' ? GENDER_FEMALE : GENDER_MALE From 2874b31c449f38a711070f2380debdd902a8bf12 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 13 Nov 2019 11:08:21 +0100 Subject: [PATCH 43/53] Bump activestorage-openstack --- Gemfile.lock | 92 +++++++++---------- .../active_storage/file_not_found_error.rb | 4 - 2 files changed, 46 insertions(+), 50 deletions(-) delete mode 100644 app/lib/active_storage/file_not_found_error.rb diff --git a/Gemfile.lock b/Gemfile.lock index 71de81e68..8d40fc7fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -20,25 +20,25 @@ GEM specs: aasm (5.0.1) concurrent-ruby (~> 1.0) - actioncable (5.2.2.1) - actionpack (= 5.2.2.1) + actioncable (5.2.3) + actionpack (= 5.2.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.2.1) - actionpack (= 5.2.2.1) - actionview (= 5.2.2.1) - activejob (= 5.2.2.1) + actionmailer (5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.2.1) - actionview (= 5.2.2.1) - activesupport (= 5.2.2.1) + actionpack (5.2.3) + actionview (= 5.2.3) + activesupport (= 5.2.3) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.2.1) - activesupport (= 5.2.2.1) + actionview (5.2.3) + activesupport (= 5.2.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -51,25 +51,25 @@ GEM activemodel (>= 4.1, < 6) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (5.2.2.1) - activesupport (= 5.2.2.1) + activejob (5.2.3) + activesupport (= 5.2.3) globalid (>= 0.3.6) - activemodel (5.2.2.1) - activesupport (= 5.2.2.1) - activerecord (5.2.2.1) - activemodel (= 5.2.2.1) - activesupport (= 5.2.2.1) + activemodel (5.2.3) + activesupport (= 5.2.3) + activerecord (5.2.3) + activemodel (= 5.2.3) + activesupport (= 5.2.3) arel (>= 9.0) - activestorage (5.2.2.1) - actionpack (= 5.2.2.1) - activerecord (= 5.2.2.1) + activestorage (5.2.3) + actionpack (= 5.2.3) + activerecord (= 5.2.3) marcel (~> 0.3.1) - activestorage-openstack (1.0.0) + activestorage-openstack (1.2.0) fog-openstack (~> 1.0) marcel mime-types - rails (<= 6) - activesupport (5.2.2.1) + rails (>= 5.2.2) + activesupport (5.2.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -196,7 +196,7 @@ GEM em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) - erubi (1.8.0) + erubi (1.9.0) erubis (2.7.0) ethon (0.11.0) ffi (>= 1.3.0) @@ -304,7 +304,7 @@ GEM domain_name (~> 0.5) http_parser.rb (0.6.0) httpclient (2.8.3) - i18n (1.6.0) + i18n (1.7.0) concurrent-ruby (~> 1.0) ipaddress (0.8.3) jaro_winkler (1.5.2) @@ -368,7 +368,7 @@ GEM mimemagic (0.3.3) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.11.3) + minitest (5.13.0) momentjs-rails (2.20.1) railties (>= 3.1) multi_json (1.14.1) @@ -377,7 +377,7 @@ GEM mustermann (1.0.3) nenv (0.3.0) netrc (0.11.0) - nio4r (2.3.1) + nio4r (2.5.2) nokogiri (1.10.5) mini_portile2 (~> 2.4.0) notiffany (0.1.1) @@ -462,18 +462,18 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.2.1) - actioncable (= 5.2.2.1) - actionmailer (= 5.2.2.1) - actionpack (= 5.2.2.1) - actionview (= 5.2.2.1) - activejob (= 5.2.2.1) - activemodel (= 5.2.2.1) - activerecord (= 5.2.2.1) - activestorage (= 5.2.2.1) - activesupport (= 5.2.2.1) + rails (5.2.3) + actioncable (= 5.2.3) + actionmailer (= 5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + activemodel (= 5.2.3) + activerecord (= 5.2.3) + activestorage (= 5.2.3) + activesupport (= 5.2.3) bundler (>= 1.3.0) - railties (= 5.2.2.1) + railties (= 5.2.3) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.4) actionpack (>= 5.0.1.x) @@ -482,14 +482,14 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.2.0) - loofah (~> 2.2, >= 2.2.2) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) rails-i18n (5.1.2) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) - railties (5.2.2.1) - actionpack (= 5.2.2.1) - activesupport (= 5.2.2.1) + railties (5.2.3) + actionpack (= 5.2.3) + activesupport (= 5.2.3) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -686,9 +686,9 @@ GEM activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) - websocket-driver (0.7.0) + websocket-driver (0.7.1) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) + websocket-extensions (0.1.4) xpath (3.2.0) nokogiri (~> 1.8) xray-rails (0.3.1) diff --git a/app/lib/active_storage/file_not_found_error.rb b/app/lib/active_storage/file_not_found_error.rb deleted file mode 100644 index 580d30666..000000000 --- a/app/lib/active_storage/file_not_found_error.rb +++ /dev/null @@ -1,4 +0,0 @@ -module ActiveStorage - # activestorage-openstack uses ActiveStorage::FileNotFoundError which only exists in rails 6 - class FileNotFoundError < StandardError; end -end From 556ec351f37765b9a3630a187a0afbb3a9455ec1 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 13 Nov 2019 15:38:44 +0100 Subject: [PATCH 44/53] Drop email on instructeurs --- .../instructeurs/groupe_instructeurs_controller.rb | 2 +- .../new_administrateur/groupe_instructeurs_controller.rb | 2 +- db/migrate/20191113142816_instructeurs_remove_email.rb | 5 +++++ db/schema.rb | 4 +--- 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20191113142816_instructeurs_remove_email.rb diff --git a/app/controllers/instructeurs/groupe_instructeurs_controller.rb b/app/controllers/instructeurs/groupe_instructeurs_controller.rb index 0e5a3b7e0..e55d7bb79 100644 --- a/app/controllers/instructeurs/groupe_instructeurs_controller.rb +++ b/app/controllers/instructeurs/groupe_instructeurs_controller.rb @@ -14,7 +14,7 @@ module Instructeurs end def add_instructeur - @instructeur = Instructeur.find_by(email: instructeur_email) || + @instructeur = Instructeur.by_email(instructeur_email) || create_instructeur(instructeur_email) if groupe_instructeur.instructeurs.include?(@instructeur) diff --git a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb index b8923d4a3..4bacdb64d 100644 --- a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb +++ b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb @@ -47,7 +47,7 @@ module NewAdministrateur end def add_instructeur - @instructeur = Instructeur.find_by(email: instructeur_email) || + @instructeur = Instructeur.by_email(instructeur_email) || create_instructeur(instructeur_email) if groupe_instructeur.instructeurs.include?(@instructeur) diff --git a/db/migrate/20191113142816_instructeurs_remove_email.rb b/db/migrate/20191113142816_instructeurs_remove_email.rb new file mode 100644 index 000000000..ffe93e0be --- /dev/null +++ b/db/migrate/20191113142816_instructeurs_remove_email.rb @@ -0,0 +1,5 @@ +class InstructeursRemoveEmail < ActiveRecord::Migration[5.2] + def change + remove_column :instructeurs, :email + end +end diff --git a/db/schema.rb b/db/schema.rb index 2fb2e05ac..f19d1d49e 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: 2019_10_24_150452) do +ActiveRecord::Schema.define(version: 2019_11_13_142816) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -414,12 +414,10 @@ ActiveRecord::Schema.define(version: 2019_10_24_150452) do end create_table "instructeurs", id: :serial, force: :cascade do |t| - t.string "email", default: "", null: false t.datetime "created_at" t.datetime "updated_at" t.text "encrypted_login_token" t.datetime "login_token_created_at" - t.index ["email"], name: "index_instructeurs_on_email" end create_table "invites", id: :serial, force: :cascade do |t| From ea6667ff9f14b6021a99d0c6de3c3ed8aa34d3a6 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 7 Nov 2019 13:24:34 +0100 Subject: [PATCH 45/53] =?UTF-8?q?[GraphQL]:=20d=C3=A9marche=20and=20dossie?= =?UTF-8?q?r=20number=20should=20be=20a=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/graphql/schema.graphql | 8 ++++---- app/graphql/types/demarche_type.rb | 2 +- app/graphql/types/dossier_type.rb | 2 +- app/graphql/types/query_type.rb | 4 ++-- spec/controllers/api/v2/graphql_controller_spec.rb | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index c2a558d5b..02fbbf097 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -164,7 +164,7 @@ type Demarche { """ Le numero de la démarche. """ - number: ID! + number: Int! state: DemarcheState! title: String! updatedAt: ISO8601DateTime! @@ -244,7 +244,7 @@ type Dossier { """ Le numero du dossier. """ - number: ID! + number: Int! """ L'état du dossier. @@ -507,7 +507,7 @@ type Query { """ Numéro de la démarche. """ - number: ID! + number: Int! ): Demarche! """ @@ -517,7 +517,7 @@ type Query { """ Numéro du dossier. """ - number: ID! + number: Int! ): Dossier! } diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index b78ea4f14..1fe527588 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -9,7 +9,7 @@ module Types description "Une demarche" global_id_field :id - field :number, ID, "Le numero de la démarche.", null: false, method: :id + field :number, Int, "Le numero de la démarche.", null: false, method: :id field :title, String, null: false, method: :libelle field :description, String, "Déscription de la démarche.", null: false field :state, DemarcheState, null: false diff --git a/app/graphql/types/dossier_type.rb b/app/graphql/types/dossier_type.rb index 30892ca55..eecdc8816 100644 --- a/app/graphql/types/dossier_type.rb +++ b/app/graphql/types/dossier_type.rb @@ -9,7 +9,7 @@ module Types description "Un dossier" global_id_field :id - field :number, ID, "Le numero du dossier.", null: false, method: :id + field :number, Int, "Le numero du dossier.", null: false, method: :id field :state, DossierState, "L'état du dossier.", null: false field :updated_at, GraphQL::Types::ISO8601DateTime, "Date de dernière mise à jour.", null: false diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 9d29ef0f8..ed1114e53 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -1,11 +1,11 @@ module Types class QueryType < Types::BaseObject field :demarche, DemarcheType, null: false, description: "Informations concernant une démarche." do - argument :number, ID, "Numéro de la démarche.", required: true + argument :number, Int, "Numéro de la démarche.", required: true end field :dossier, DossierType, null: false, description: "Informations sur un dossier d'une démarche." do - argument :number, ID, "Numéro du dossier.", required: true + argument :number, Int, "Numéro du dossier.", required: true end def demarche(number:) diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 3d1f0a53a..3d499d8e5 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -76,7 +76,7 @@ describe API::V2::GraphqlController do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { id: procedure.to_typed_id, - number: procedure.id.to_s, + number: procedure.id, title: procedure.libelle, description: procedure.description, state: 'brouillon', @@ -123,7 +123,7 @@ describe API::V2::GraphqlController do expect(gql_errors).to eq(nil) expect(gql_data).to eq(demarche: { id: procedure.to_typed_id, - number: procedure.id.to_s, + number: procedure.id, dossiers: { nodes: [{ id: dossier1.to_typed_id }, { id: dossier.to_typed_id }] } @@ -177,7 +177,7 @@ describe API::V2::GraphqlController do expect(gql_errors).to eq(nil) expect(gql_data).to eq(dossier: { id: dossier.to_typed_id, - number: dossier.id.to_s, + number: dossier.id, state: 'en_construction', updatedAt: dossier.updated_at.iso8601, datePassageEnConstruction: dossier.en_construction_at.iso8601, From a23e84bc573394b1d0ab13d0af4535c660a3ad4f Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 7 Nov 2019 13:34:19 +0100 Subject: [PATCH 46/53] [GraphQL]: more docs --- app/graphql/schema.graphql | 123 +++++++++++++++++++++ app/graphql/types/champ_descriptor_type.rb | 8 +- app/graphql/types/champ_type.rb | 4 +- app/graphql/types/demarche_type.rb | 2 +- 4 files changed, 130 insertions(+), 7 deletions(-) diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 02fbbf097..869f5522b 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -10,27 +10,66 @@ type Avis { type CarteChamp implements Champ { geoAreas: [GeoArea!]! id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String } interface Champ { id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String } type ChampDescriptor { + """ + Description du champ. + """ description: String id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + Est-ce que le champ est obligatoire ? + """ required: Boolean! + + """ + Type de la valeur du champ. + """ type: TypeDeChamp! } type CheckboxChamp implements Champ { id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String value: Boolean! } @@ -88,14 +127,30 @@ type CreateDirectUploadPayload { type DateChamp implements Champ { id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String value: ISO8601DateTime } type DecimalNumberChamp implements Champ { id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String value: Float } @@ -165,6 +220,10 @@ type Demarche { Le numero de la démarche. """ number: Int! + + """ + L'état de la démarche. + """ state: DemarcheState! title: String! updatedAt: ISO8601DateTime! @@ -296,7 +355,15 @@ type DossierEdge { type DossierLinkChamp implements Champ { dossier: Dossier id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String } @@ -376,16 +443,32 @@ scalar ISO8601DateTime type IntegerNumberChamp implements Champ { id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String value: Int } type LinkedDropDownListChamp implements Champ { id: ID! + + """ + Libellé du champ. + """ label: String! primaryValue: String secondaryValue: String + + """ + La valeur du champ sous forme texte. + """ stringValue: String } @@ -399,7 +482,15 @@ type Message { type MultipleDropDownListChamp implements Champ { id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String values: [String!]! } @@ -480,7 +571,15 @@ type PersonneMorale { type PieceJustificativeChamp implements Champ { id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String url: URL } @@ -524,7 +623,15 @@ type Query { type RepetitionChamp implements Champ { champs: [Champ!]! id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String } @@ -537,13 +644,29 @@ type SelectionUtilisateur implements GeoArea { type SiretChamp implements Champ { etablissement: PersonneMorale id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String } type TextChamp implements Champ { id: ID! + + """ + Libellé du champ. + """ label: String! + + """ + La valeur du champ sous forme texte. + """ stringValue: String value: String } diff --git a/app/graphql/types/champ_descriptor_type.rb b/app/graphql/types/champ_descriptor_type.rb index 2a9a18db1..ade23605e 100644 --- a/app/graphql/types/champ_descriptor_type.rb +++ b/app/graphql/types/champ_descriptor_type.rb @@ -9,9 +9,9 @@ module Types end global_id_field :id - field :type, TypeDeChampType, null: false, method: :type_champ - field :label, String, null: false, method: :libelle - field :description, String, null: true - field :required, Boolean, null: false, method: :mandatory? + field :type, TypeDeChampType, "Type de la valeur du champ.", null: false, method: :type_champ + field :label, String, "Libellé du champ.", null: false, method: :libelle + field :description, String, "Description du champ.", null: true + field :required, Boolean, "Est-ce que le champ est obligatoire ?", null: false, method: :mandatory? end end diff --git a/app/graphql/types/champ_type.rb b/app/graphql/types/champ_type.rb index dc76378d9..8383e409f 100644 --- a/app/graphql/types/champ_type.rb +++ b/app/graphql/types/champ_type.rb @@ -3,8 +3,8 @@ module Types include Types::BaseInterface global_id_field :id - field :label, String, null: false, method: :libelle - field :string_value, String, null: true, method: :for_api_v2 + field :label, String, "Libellé du champ.", null: false, method: :libelle + field :string_value, String, "La valeur du champ sous forme texte.", null: true, method: :for_api_v2 definition_methods do def resolve_type(object, context) diff --git a/app/graphql/types/demarche_type.rb b/app/graphql/types/demarche_type.rb index 1fe527588..4b1ed3129 100644 --- a/app/graphql/types/demarche_type.rb +++ b/app/graphql/types/demarche_type.rb @@ -12,7 +12,7 @@ module Types field :number, Int, "Le numero de la démarche.", null: false, method: :id field :title, String, null: false, method: :libelle field :description, String, "Déscription de la démarche.", null: false - field :state, DemarcheState, null: false + field :state, DemarcheState, "L'état de la démarche.", null: false field :created_at, GraphQL::Types::ISO8601DateTime, null: false field :updated_at, GraphQL::Types::ISO8601DateTime, null: false From 9ce81f665b0a5471271288e0c13309d494c09f42 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 7 Nov 2019 13:52:48 +0100 Subject: [PATCH 47/53] [GraphQL]: fix geo_areas docs --- app/graphql/schema.graphql | 11 +++-------- app/graphql/types/geo_area_type.rb | 8 +++++--- config/locales/models/geo_area/fr.yml | 8 ++++++++ 3 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 config/locales/models/geo_area/fr.yml diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 869f5522b..d43003eb3 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -402,22 +402,17 @@ interface GeoArea { enum GeoAreaSource { """ - translation missing: fr.activerecord.attributes.geo_area.source.cadastre + Parcelle cadastrale """ cadastre """ - translation missing: fr.activerecord.attributes.geo_area.source.parcelle_agricole - """ - parcelle_agricole - - """ - translation missing: fr.activerecord.attributes.geo_area.source.quartier_prioritaire + Quartier prioritaire """ quartier_prioritaire """ - translation missing: fr.activerecord.attributes.geo_area.source.selection_utilisateur + Sélection utilisateur """ selection_utilisateur } diff --git a/app/graphql/types/geo_area_type.rb b/app/graphql/types/geo_area_type.rb index 1d55b2fac..c54c054c2 100644 --- a/app/graphql/types/geo_area_type.rb +++ b/app/graphql/types/geo_area_type.rb @@ -4,9 +4,11 @@ module Types class GeoAreaSource < Types::BaseEnum GeoArea.sources.each do |symbol_name, string_name| - value(string_name, - I18n.t(symbol_name, scope: [:activerecord, :attributes, :geo_area, :source]), - value: symbol_name) + if string_name != "parcelle_agricole" + value(string_name, + I18n.t(symbol_name, scope: [:activerecord, :attributes, :geo_area, :source]), + value: symbol_name) + end end end diff --git a/config/locales/models/geo_area/fr.yml b/config/locales/models/geo_area/fr.yml new file mode 100644 index 000000000..913e4f0df --- /dev/null +++ b/config/locales/models/geo_area/fr.yml @@ -0,0 +1,8 @@ +fr: + activerecord: + attributes: + geo_area: + source: + cadastre: Parcelle cadastrale + quartier_prioritaire: Quartier prioritaire + selection_utilisateur: Sélection utilisateur From f7cbbe815ca5e033fb01580be3f6f6dedb2dfb0b Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 13 Nov 2019 11:58:35 +0100 Subject: [PATCH 48/53] [GraphQL]: Add default query to playground --- config/initializers/graphiql.rb | 79 +++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 config/initializers/graphiql.rb diff --git a/config/initializers/graphiql.rb b/config/initializers/graphiql.rb new file mode 100644 index 000000000..65a8eeb28 --- /dev/null +++ b/config/initializers/graphiql.rb @@ -0,0 +1,79 @@ +DEFAULT_QUERY = "# La documentation officielle de la spécification (Anglais) : https://graphql.org/ +# Une introduction aux concepts et raisons d'être de GraphQL (Français) : https://blog.octo.com/graphql-et-pourquoi-faire/ +# Le schema GraphQL de demarches-simplifiees.fr : https://demarches-simplifiees-graphql.netlify.com +# Le endpoint GraphQL de demarches-simplifiees.fr : https://demarches-simplifiees.fr/api/v2/graphql + +query getDemarche($demarcheNumber: Int!) { + demarche(number: $demarcheNumber) { + id + number + title + champDescriptors { + id + type + label + } + dossiers(first: 3) { + nodes { + id + number + datePassageEnConstruction + datePassageEnInstruction + dateTraitement + usager { + email + } + champs { + id + label + ... on TextChamp { + value + } + ... on DecimalNumberChamp { + value + } + ... on IntegerNumberChamp { + value + } + ... on CheckboxChamp { + value + } + ... on DateChamp { + value + } + ... on DossierLinkChamp { + dossier { + id + } + } + ... on MultipleDropDownListChamp { + values + } + ... on LinkedDropDownListChamp { + primaryValue + secondaryValue + } + ... on PieceJustificativeChamp { + url + } + ... on CarteChamp { + geoAreas { + source + geometry { + type + coordinates + } + } + } + } + } + pageInfo { + hasNextPage + endCursor + } + } + } +}" + +GraphiQL::Rails.config.initial_query = DEFAULT_QUERY +GraphiQL::Rails.config.title = 'demarches-simplifiees.fr' From 3db741b6b6529dcb2dd258e0296b4bbe27dea3e9 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 7 Nov 2019 14:57:42 +0100 Subject: [PATCH 49/53] [GraphQL]: add ValidationErrorType --- app/graphql/types/validation_error_type.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/graphql/types/validation_error_type.rb diff --git a/app/graphql/types/validation_error_type.rb b/app/graphql/types/validation_error_type.rb new file mode 100644 index 000000000..7df3a6fb4 --- /dev/null +++ b/app/graphql/types/validation_error_type.rb @@ -0,0 +1,11 @@ +module Types + class ValidationErrorType < Types::BaseObject + description "Éreur de validation" + + field :message, String, "A description of the error", null: false + + def message + object + end + end +end From f0ac01bf19836a2281b6675acb372d0989f4fe3a Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 7 Nov 2019 16:30:37 +0100 Subject: [PATCH 50/53] [GraphQL]: test unauthorized mutation --- .../api/v2/graphql_controller_spec.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index 3d499d8e5..c575e8278 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -263,5 +263,26 @@ describe API::V2::GraphqlController do expect(gql_errors).not_to eq(nil) end end + + context "mutation" do + let(:query) do + "mutation { + dossierEnvoyerMessage(input: { + dossierId: \"#{dossier.to_typed_id}\", + instructeurId: \"#{instructeur.to_typed_id}\", + body: \"Bonjour\" + }) { + message { + body + } + } + }" + end + + it "should return error" do + expect(gql_data[:dossierEnvoyerMessage]).to eq(nil) + expect(gql_errors).not_to eq(nil) + end + end end end From ba03dbf8dd5efe85598965df8e49175515cbcaeb Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Tue, 12 Nov 2019 15:51:51 +0100 Subject: [PATCH 51/53] [GraphQL] Add dossierEnvoyerMessage mutation --- .../mutations/dossier_envoyer_message.rb | 27 +++++++ app/graphql/schema.graphql | 44 ++++++++++- app/graphql/types/mutation_type.rb | 2 + app/models/commentaire.rb | 2 +- config/locales/models/commentaire/fr.yml | 1 + .../api/v2/graphql_controller_spec.rb | 75 +++++++++++++++++++ 6 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 app/graphql/mutations/dossier_envoyer_message.rb diff --git a/app/graphql/mutations/dossier_envoyer_message.rb b/app/graphql/mutations/dossier_envoyer_message.rb new file mode 100644 index 000000000..601e448de --- /dev/null +++ b/app/graphql/mutations/dossier_envoyer_message.rb @@ -0,0 +1,27 @@ +module Mutations + class DossierEnvoyerMessage < Mutations::BaseMutation + description "Envoyer un message à l'usager du dossier." + + argument :dossier_id, ID, required: true, loads: Types::DossierType + argument :instructeur_id, ID, required: true, loads: Types::ProfileType + argument :body, String, required: true + argument :attachment, ID, required: false + + field :message, Types::MessageType, null: true + field :errors, [Types::ValidationErrorType], null: true + + def resolve(dossier:, instructeur:, body:, attachment: nil) + message = CommentaireService.build(instructeur, dossier, body: body, piece_jointe: attachment) + + if message.save + { message: message } + else + { errors: message.errors.full_messages } + end + end + + def authorized?(dossier:, instructeur:, body:) + instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id) + end + end +end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index d43003eb3..b60fc03ae 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -352,6 +352,33 @@ type DossierEdge { node: Dossier } +""" +Autogenerated input type of DossierEnvoyerMessage +""" +input DossierEnvoyerMessageInput { + attachment: ID + body: String! + + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + dossierId: ID! + instructeurId: ID! +} + +""" +Autogenerated return type of DossierEnvoyerMessage +""" +type DossierEnvoyerMessagePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + errors: [ValidationError!] + message: Message +} + type DossierLinkChamp implements Champ { dossier: Dossier id: ID! @@ -495,6 +522,11 @@ type Mutation { File information required to prepare a direct upload """ createDirectUpload(input: CreateDirectUploadInput!): CreateDirectUploadPayload + + """ + Envoyer un message à l'usager du dossier. + """ + dossierEnvoyerMessage(input: DossierEnvoyerMessageInput!): DossierEnvoyerMessagePayload } enum Order { @@ -806,4 +838,14 @@ enum TypeDeChamp { """ A valid URL, transported as a string """ -scalar URL \ No newline at end of file +scalar URL + +""" +Éreur de validation +""" +type ValidationError { + """ + A description of the error + """ + message: String! +} \ No newline at end of file diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index bbdb5021e..92da07a80 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,5 +1,7 @@ module Types class MutationType < Types::BaseObject field :create_direct_upload, mutation: Mutations::CreateDirectUpload + + field :dossier_envoyer_message, mutation: Mutations::DossierEnvoyerMessage end end diff --git a/app/models/commentaire.rb b/app/models/commentaire.rb index 5f7c4f11c..9a93319d4 100644 --- a/app/models/commentaire.rb +++ b/app/models/commentaire.rb @@ -10,7 +10,7 @@ class Commentaire < ApplicationRecord has_one_attached :piece_jointe - validates :body, presence: { message: "Votre message ne peut être vide" } + validates :body, presence: { message: "ne peut être vide" } default_scope { order(created_at: :asc) } scope :updated_since?, -> (date) { where('commentaires.updated_at > ?', date) } diff --git a/config/locales/models/commentaire/fr.yml b/config/locales/models/commentaire/fr.yml index 16b77c012..71ed7e25e 100644 --- a/config/locales/models/commentaire/fr.yml +++ b/config/locales/models/commentaire/fr.yml @@ -2,4 +2,5 @@ fr: activerecord: attributes: commentaire: + body: 'Votre message' file: fichier diff --git a/spec/controllers/api/v2/graphql_controller_spec.rb b/spec/controllers/api/v2/graphql_controller_spec.rb index c575e8278..eb2721145 100644 --- a/spec/controllers/api/v2/graphql_controller_spec.rb +++ b/spec/controllers/api/v2/graphql_controller_spec.rb @@ -216,6 +216,81 @@ describe API::V2::GraphqlController do end context "mutations" do + describe 'dossierEnvoyerMessage' do + context 'success' do + let(:query) do + "mutation { + dossierEnvoyerMessage(input: { + dossierId: \"#{dossier.to_typed_id}\", + instructeurId: \"#{instructeur.to_typed_id}\", + body: \"Bonjour\" + }) { + message { + body + } + } + }" + end + + it "should post a message" do + expect(gql_errors).to eq(nil) + + expect(gql_data).to eq(dossierEnvoyerMessage: { + message: { + body: "Bonjour" + } + }) + end + end + + context 'schema error' do + let(:query) do + "mutation { + dossierEnvoyerMessage(input: { + dossierId: \"#{dossier.to_typed_id}\", + instructeurId: \"#{instructeur.to_typed_id}\" + }) { + message { + body + } + } + }" + end + + it "should fail" do + expect(gql_data).to eq(nil) + expect(gql_errors).not_to eq(nil) + end + end + + context 'validation error' do + let(:query) do + "mutation { + dossierEnvoyerMessage(input: { + dossierId: \"#{dossier.to_typed_id}\", + instructeurId: \"#{instructeur.to_typed_id}\", + body: \"\" + }) { + message { + body + } + errors { + message + } + } + }" + end + + it "should fail" do + expect(gql_errors).to eq(nil) + expect(gql_data).to eq(dossierEnvoyerMessage: { + errors: [{ message: "Votre message ne peut être vide" }], + message: nil + }) + end + end + end + context 'createDirectUpload' do let(:query) do "mutation { From efd35a3de79144bea82301b3ea4836b88aced9db Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Thu, 14 Nov 2019 09:22:02 +0100 Subject: [PATCH 52/53] Accessibility: outline links to ease keyboard navigation --- app/assets/stylesheets/new_design/_placeholders.scss | 8 ++++++++ app/assets/stylesheets/new_design/buttons.scss | 8 +++----- app/assets/stylesheets/new_design/custom_reset.scss | 5 +++++ app/assets/stylesheets/new_design/forms.scss | 3 +++ app/assets/stylesheets/new_design/landing.scss | 5 +++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/new_design/_placeholders.scss b/app/assets/stylesheets/new_design/_placeholders.scss index 53e98918e..2dc8cbaad 100644 --- a/app/assets/stylesheets/new_design/_placeholders.scss +++ b/app/assets/stylesheets/new_design/_placeholders.scss @@ -1,3 +1,4 @@ +@import "colors"; @import "constants"; %horizontal-list { @@ -17,3 +18,10 @@ animation-fill-mode: forwards; animation-duration: 0.3s; } + +%outline { + &:active, + &:focus { + outline: 3px solid $blue; + } +} diff --git a/app/assets/stylesheets/new_design/buttons.scss b/app/assets/stylesheets/new_design/buttons.scss index a36b7a719..e66e2c9fa 100644 --- a/app/assets/stylesheets/new_design/buttons.scss +++ b/app/assets/stylesheets/new_design/buttons.scss @@ -1,7 +1,10 @@ @import "colors"; @import "constants"; +@import "placeholders"; .button { + @extend %outline; + display: inline-block; padding: 8px 16px; border-radius: 30px; @@ -20,11 +23,6 @@ text-decoration: none; } - &:active, - &:focus { - outline: none; - } - &:disabled { opacity: 0.5; filter: saturate(50%); diff --git a/app/assets/stylesheets/new_design/custom_reset.scss b/app/assets/stylesheets/new_design/custom_reset.scss index 054f1c141..f1825a12e 100644 --- a/app/assets/stylesheets/new_design/custom_reset.scss +++ b/app/assets/stylesheets/new_design/custom_reset.scss @@ -1,3 +1,6 @@ +@import "colors"; +@import "placeholders"; + html, body { height: 100%; @@ -14,5 +17,7 @@ html { } a { + @extend %outline; + text-decoration: none; } diff --git a/app/assets/stylesheets/new_design/forms.scss b/app/assets/stylesheets/new_design/forms.scss index f12f5681a..8136b53e9 100644 --- a/app/assets/stylesheets/new_design/forms.scss +++ b/app/assets/stylesheets/new_design/forms.scss @@ -1,5 +1,6 @@ @import "constants"; @import "colors"; +@import "placeholders"; .form { h1 { @@ -177,6 +178,8 @@ input[type=checkbox], input[type=radio] { + @extend %outline; + margin-left: 5px; margin-right: 4px; margin-bottom: 2 * $default-padding; diff --git a/app/assets/stylesheets/new_design/landing.scss b/app/assets/stylesheets/new_design/landing.scss index f6bec1754..1b18fea58 100644 --- a/app/assets/stylesheets/new_design/landing.scss +++ b/app/assets/stylesheets/new_design/landing.scss @@ -365,6 +365,11 @@ $cta-panel-button-border-size: 2px; color: #FFFFFF; text-decoration: none; } + + &:active, + &:focus { + outline: 3px solid #FFFFFF; + } } .cta-panel-button-blue { From 804190cb73f30c32b6ad295762e6fe894bee4a8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2019 15:26:57 +0000 Subject: [PATCH 53/53] build(deps): bump json-jwt from 1.10.0 to 1.11.0 Bumps [json-jwt](https://github.com/nov/json-jwt) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/nov/json-jwt/releases) - [Commits](https://github.com/nov/json-jwt/compare/v1.10.0...v1.11.0) Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8d40fc7fa..9efd53568 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -313,7 +313,7 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.2.0) - json-jwt (1.10.0) + json-jwt (1.11.0) activesupport (>= 4.2) aes_key_wrap bindata