2020-01-28-01 (#4719)

2020-01-28-01
This commit is contained in:
Pierre de La Morinerie 2020-01-28 16:28:31 +01:00 committed by GitHub
commit 3563aa10ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 344 additions and 133 deletions

View file

@ -13,7 +13,10 @@ defaults: &defaults
bundle_restore_cache: &bundle_restore_cache bundle_restore_cache: &bundle_restore_cache
restore_cache: restore_cache:
name: Restore Bundler Package Cache name: Restore Bundler Package Cache
key: bundle-install-v9-{{ arch }}-{{ checksum "Gemfile.lock" }} keys:
- bundle-install-v9-{{ arch }}-{{ checksum "Gemfile.lock" }}
- bundle-install-v9-{{ arch }}
- bundle-install-v9
bundle_save_cache: &bundle_save_cache bundle_save_cache: &bundle_save_cache
save_cache: save_cache:
@ -30,19 +33,45 @@ bundle_install: &bundle_install
yarn_restore_cache: &yarn_restore_cache yarn_restore_cache: &yarn_restore_cache
restore_cache: restore_cache:
name: Restore Yarn Package Cache name: Restore Yarn Package Cache
key: yarn-install-v1-{{ arch }}-{{ checksum "yarn.lock" }} keys:
- yarn-install-v3-{{ arch }}-{{ checksum "yarn.lock" }}
- yarn-install-v3-{{ arch }}
- yarn-install-v3
yarn_save_cache: &yarn_save_cache yarn_save_cache: &yarn_save_cache
save_cache: save_cache:
name: Save Yarn Package Cache name: Save Yarn Package Cache
key: yarn-install-v1-{{ arch }}-{{ checksum "yarn.lock" }} key: yarn-install-v3-{{ arch }}-{{ checksum "yarn.lock" }}
paths: paths:
- ~/.cache/yarn - ~/.cache/yarn
yarn_install: &yarn_install yarn_install: &yarn_install
run: run:
name: Install JS Dependencies name: Install JS Dependencies
command: yarn install --non-interactive || yarn install --non-interactive command: yarn install --frozen-lockfile --non-interactive || yarn install --frozen-lockfile --non-interactive
webpacker_restore_cache: &webpacker_restore_cache
restore_cache:
name: Restore Webpacker Cache
keys:
- webpacker-v1-{{ .Branch }}-{{ .Revision }}
- webpacker-v1-{{ .Branch }}
- webpacker-v1
webpacker_save_cache: &webpacker_save_cache
save_cache:
name: Save Webpacker Cache
key: webpacker-v1-{{ .Branch }}-{{ .Revision }}
paths:
- public/packs-test
- tmp/cache/webpacker
webpacker_precompile: &webpacker_precompile
run:
environment:
RAILS_ENV: test
name: Precompile Webpack assets
command: bin/webpack
jobs: jobs:
build: build:
@ -53,8 +82,8 @@ jobs:
- *bundle_install - *bundle_install
- *bundle_save_cache - *bundle_save_cache
- *yarn_restore_cache - *yarn_restore_cache
- *yarn_save_cache
- *yarn_install - *yarn_install
- *yarn_save_cache
test: test:
<<: *defaults <<: *defaults
parallelism: 3 parallelism: 3
@ -64,16 +93,15 @@ jobs:
- *bundle_install - *bundle_install
- *yarn_restore_cache - *yarn_restore_cache
- *yarn_install - *yarn_install
- *webpacker_restore_cache
- *webpacker_precompile
- *webpacker_save_cache
- run: - run:
environment: environment:
DATABASE_URL: "postgres://tps_test@localhost:5432/tps_test" DATABASE_URL: "postgres://tps_test@localhost:5432/tps_test"
name: Create DB name: Create Database
command: bundle exec rake db:create db:schema:load db:migrate RAILS_ENV=test command: bundle exec rake db:create db:schema:load db:migrate RAILS_ENV=test
- run:
environment:
RAILS_ENV: test
name: Precompile Webpack assets
command: bin/webpack
- run: - run:
environment: environment:
DATABASE_URL: "postgres://tps_test@localhost:5432/tps_test" DATABASE_URL: "postgres://tps_test@localhost:5432/tps_test"

View file

@ -1,7 +1,7 @@
on: on:
issue_comment: issue_comment:
types: [created] types: [created]
name: Rebase automatique name: Automatic Rebase
jobs: jobs:
rebase: rebase:
name: Rebase name: Rebase
@ -9,13 +9,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
with:
fetch-depth: 0
- name: Automatic Rebase - name: Automatic Rebase
uses: cirrus-actions/rebase@master uses: cirrus-actions/rebase@1.2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# https://github.community/t5/GitHub-Actions/Workflow-is-failing-if-no-job-can-be-ran-due-to-condition/m-p/38186#M3250 # https://github.community/t5/GitHub-Actions/Workflow-is-failing-if-no-job-can-be-ran-due-to-condition/m-p/38186#M3250
always_job: always_job:
name: Aways run job name: Always run job
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Always run - name: Always run

View file

@ -117,7 +117,7 @@ GEM
railties (>= 3.0) railties (>= 3.0)
brakeman (4.3.1) brakeman (4.3.1)
browser (2.5.3) browser (2.5.3)
builder (3.2.3) builder (3.2.4)
byebug (10.0.2) byebug (10.0.2)
capybara (3.29.0) capybara (3.29.0)
addressable addressable
@ -155,7 +155,7 @@ GEM
connection_pool (2.2.2) connection_pool (2.2.2)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.5) crass (1.0.6)
css_parser (1.6.0) css_parser (1.6.0)
addressable addressable
curb (0.9.10) curb (0.9.10)
@ -242,7 +242,7 @@ GEM
graphiql-rails (1.7.0) graphiql-rails (1.7.0)
railties railties
sprockets-rails sprockets-rails
graphql (1.9.15) graphql (1.9.16)
graphql-batch (0.4.1) graphql-batch (0.4.1)
graphql (>= 1.3, < 2) graphql (>= 1.3, < 2)
promise.rb (~> 0.7.2) promise.rb (~> 0.7.2)
@ -305,7 +305,7 @@ GEM
domain_name (~> 0.5) domain_name (~> 0.5)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.7.0) i18n (1.8.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ipaddress (0.8.3) ipaddress (0.8.3)
jaro_winkler (1.5.2) jaro_winkler (1.5.2)
@ -350,7 +350,7 @@ GEM
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
logstash-event (1.2.02) logstash-event (1.2.02)
loofah (2.3.1) loofah (2.4.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lumberjack (1.0.13) lumberjack (1.0.13)
@ -369,7 +369,7 @@ GEM
mimemagic (0.3.3) mimemagic (0.3.3)
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.13.0) minitest (5.14.0)
momentjs-rails (2.20.1) momentjs-rails (2.20.1)
railties (>= 3.1) railties (>= 3.1)
multi_json (1.14.1) multi_json (1.14.1)
@ -379,7 +379,7 @@ GEM
nenv (0.3.0) nenv (0.3.0)
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.2) nio4r (2.5.2)
nokogiri (1.10.5) nokogiri (1.10.7)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
notiffany (0.1.1) notiffany (0.1.1)
nenv (~> 0.1) nenv (~> 0.1)
@ -450,7 +450,7 @@ GEM
puma (3.12.2) puma (3.12.2)
pundit (2.0.1) pundit (2.0.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
rack (2.0.8) rack (2.1.2)
rack-attack (6.0.0) rack-attack (6.0.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-mini-profiler (1.0.1) rack-mini-profiler (1.0.1)
@ -528,27 +528,27 @@ GEM
builder (>= 3.0) builder (>= 3.0)
rubyzip (>= 1.0) rubyzip (>= 1.0)
rouge (3.9.0) rouge (3.9.0)
rspec (3.8.0) rspec (3.9.0)
rspec-core (~> 3.8.0) rspec-core (~> 3.9.0)
rspec-expectations (~> 3.8.0) rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.8.0) rspec-mocks (~> 3.9.0)
rspec-core (3.8.0) rspec-core (3.9.1)
rspec-support (~> 3.8.0) rspec-support (~> 3.9.1)
rspec-expectations (3.8.2) rspec-expectations (3.9.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.9.0)
rspec-mocks (3.8.0) rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.9.0)
rspec-rails (3.8.1) rspec-rails (3.9.0)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
rspec-core (~> 3.8.0) rspec-core (~> 3.9.0)
rspec-expectations (~> 3.8.0) rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.8.0) rspec-mocks (~> 3.9.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.9.0)
rspec-support (3.8.0) rspec-support (3.9.2)
rspec_junit_formatter (0.4.1) rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0) rspec-core (>= 2, < 4, != 2.12.0)
rubocop (0.62.0) rubocop (0.62.0)
@ -614,9 +614,9 @@ GEM
rack (~> 2.0) rack (~> 2.0)
rack-protection (= 2.0.5) rack-protection (= 2.0.5)
tilt (~> 2.0) tilt (~> 2.0)
skylight (3.1.2) skylight (4.2.1)
skylight-core (= 3.1.2) skylight-core (= 4.2.1)
skylight-core (3.1.2) skylight-core (4.2.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
smart_listing (1.2.2) smart_listing (1.2.2)
coffee-rails coffee-rails
@ -644,7 +644,7 @@ GEM
httpclient (>= 2.4) httpclient (>= 2.4)
sysexits (1.2.0) sysexits (1.2.0)
temple (0.8.0) temple (0.8.0)
thor (0.20.3) thor (1.0.1)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.9) tilt (2.0.9)
timecop (0.9.1) timecop (0.9.1)
@ -654,7 +654,7 @@ GEM
turbolinks-source (5.2.0) turbolinks-source (5.2.0)
typhoeus (1.3.1) typhoeus (1.3.1)
ethon (>= 0.9.0) ethon (>= 0.9.0)
tzinfo (1.2.5) tzinfo (1.2.6)
thread_safe (~> 0.1) thread_safe (~> 0.1)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext

View file

@ -1,6 +1,8 @@
@import "colors"; @import "colors";
@import "constants"; @import "constants";
$dossier-actions-bar-border-width: 1px;
.dossier-header { .dossier-header {
.container { .container {
padding-bottom: $default-padding; padding-bottom: $default-padding;
@ -46,29 +48,40 @@
border-radius: 4px; border-radius: 4px;
} }
.send-dossier-actions-bar { .dossier-edit-sticky-footer {
// scss-lint:disable VendorPrefix // scss-lint:disable VendorPrefix DuplicateProperty
position: fixed; // Fallback for IE 11, and other browser that don't support sticky
position: -webkit-sticky; // This is needed on Safari (tested on 12.1) position: -webkit-sticky; // This is needed on Safari (tested on 12.1)
// scss-lint:enable VendorPrefix
position: sticky; position: sticky;
// scss-lint:enable VendorPrefix DuplicateProperty
// IE 11 uses `position:fixed` and thus needs an explicit width, content-box for better layout, etc.
width: 100%;
max-width: $page-width + 2 * $default-padding;
box-sizing: content-box;
bottom: 0; bottom: 0;
display: flex;
flex-direction: row;
align-items: center;
margin-top: $default-padding; margin-top: $default-padding;
margin-left: -$default-padding; margin-left: -$default-padding;
margin-right: -$default-padding; margin-right: -$default-padding;
margin-bottom: 0; margin-bottom: 0;
padding-top: 0;
padding-bottom: $default-spacer; padding-right: $default-padding - $dossier-actions-bar-border-width;
padding-right: $default-padding; padding-left: $default-padding - $dossier-actions-bar-border-width;
padding-left: $default-padding;
background: #FFFFFF; background: #FFFFFF;
border: 1px solid #CCCCCC;
border: $dossier-actions-bar-border-width solid #CCCCCC;
border-top-left-radius: 5px; border-top-left-radius: 5px;
border-top-right-radius: 5px; border-top-right-radius: 5px;
border-bottom: none; border-bottom: none;
}
.send-dossier-actions-bar {
display: flex;
flex-direction: row;
align-items: center;
.button:not(:small) { .button:not(:small) {
min-height: 38px; min-height: 38px;
@ -81,13 +94,13 @@
} }
// Normal layout // Normal layout
@media (min-width: 500px) { @media (min-width: 620px) {
padding-top: $default-spacer * 2; padding-top: $default-spacer * 2;
padding-bottom: $default-spacer * 2; padding-bottom: $default-spacer * 2;
} }
// Compact layout // Compact layout
@media (max-width: 500px) { @media (max-width: 620px) {
padding-top: $default-spacer; padding-top: $default-spacer;
padding-bottom: $default-spacer; padding-bottom: $default-spacer;
} }

View file

@ -1,5 +1,18 @@
module Manager module Manager
class UsersController < Manager::ApplicationController class UsersController < Manager::ApplicationController
def update
user = User.find(params[:id])
new_email = params[:user][:email]
user.skip_reconfirmation!
user.update(email: new_email)
if (user.valid?)
flash[:notice] = "L'email a été modifié en « #{new_email} » sans notification ni validation par email."
else
flash[:error] = "« #{new_email} » n'est pas une adresse valide."
end
redirect_to edit_manager_user_path(user)
end
def resend_confirmation_instructions def resend_confirmation_instructions
user = User.find(params[:id]) user = User.find(params[:id])
user.resend_confirmation_instructions user.resend_confirmation_instructions

View file

@ -41,7 +41,9 @@ class UserDashboard < Administrate::BaseDashboard
# FORM_ATTRIBUTES # FORM_ATTRIBUTES
# an array of attributes that will be displayed # an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages. # on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [].freeze FORM_ATTRIBUTES = [
:email
].freeze
# Overwrite this method to customize how users are displayed # Overwrite this method to customize how users are displayed
# across all pages of the admin dashboard. # across all pages of the admin dashboard.

View file

@ -59,19 +59,29 @@ class Api::V2::Schema < GraphQL::Schema
raise GraphQL::ExecutionError.new("An object of type #{error.type.graphql_name} was hidden due to permissions", extensions: { code: :unauthorized }) raise GraphQL::ExecutionError.new("An object of type #{error.type.graphql_name} was hidden due to permissions", extensions: { code: :unauthorized })
end end
middleware(GraphQL::Schema::TimeoutMiddleware.new(max_seconds: 5) do |_, query| use GraphQL::Execution::Interpreter
Rails.logger.info("GraphQL Timeout: #{query.query_string}") use GraphQL::Analysis::AST
end) use GraphQL::Schema::Timeout, max_seconds: 5
use GraphQL::Batch
use GraphQL::Backtrace
if Rails.env.development? if Rails.env.development?
query_analyzer(GraphQL::Analysis::QueryComplexity.new do |_, complexity| class LogQueryDepth < GraphQL::Analysis::AST::QueryDepth
Rails.logger.info("[GraphQL Query Complexity] #{complexity}") def result
end) Rails.logger.info("[GraphQL Query Depth] #{super}")
query_analyzer(GraphQL::Analysis::QueryDepth.new do |_, depth| end
Rails.logger.info("[GraphQL Query Depth] #{depth}")
end)
end end
use GraphQL::Batch class LogQueryComplexity < GraphQL::Analysis::AST::QueryComplexity
use GraphQL::Tracing::SkylightTracing def result
Rails.logger.info("[GraphQL Query Complexity] #{super}")
end
end
query_analyzer(LogQueryComplexity)
query_analyzer(LogQueryDepth)
else
query_analyzer(GraphQL::Analysis::AST::MaxQueryComplexity)
query_analyzer(GraphQL::Analysis::AST::MaxQueryDepth)
end
end end

View file

@ -43,7 +43,9 @@ class Avis < ApplicationRecord
['Question / Introduction', :introduction], ['Question / Introduction', :introduction],
['Réponse', :answer], ['Réponse', :answer],
['Créé le', :created_at], ['Créé le', :created_at],
['Répondu le', :updated_at] ['Répondu le', :updated_at],
['Instructeur', claimant&.email],
['Expert', instructeur&.email]
] ]
end end

View file

@ -45,8 +45,9 @@ class Champs::LinkedDropDownListChamp < Champ
value.present? ? { primary: primary_value, secondary: secondary_value } : nil value.present? ? { primary: primary_value, secondary: secondary_value } : nil
end end
def mandatory_and_blank? def blank?
mandatory? && (primary_value.blank? || secondary_value.blank?) primary_value.blank? ||
(has_secondary_options_for_primary? && secondary_value.blank?)
end end
def search_terms def search_terms
@ -58,4 +59,8 @@ class Champs::LinkedDropDownListChamp < Champ
def pack_value(primary, secondary) def pack_value(primary, secondary)
self.value = JSON.generate([primary, secondary]) self.value = JSON.generate([primary, secondary])
end end
def has_secondary_options_for_primary?
primary_value.present? && secondary_options[primary_value]&.any?(&:present?)
end
end end

View file

@ -216,6 +216,7 @@ class Dossier < ApplicationRecord
validates :user, presence: true validates :user, presence: true
validates :individual, presence: true, if: -> { procedure.for_individual? } validates :individual, presence: true, if: -> { procedure.for_individual? }
validates :groupe_instructeur, presence: true
def update_search_terms def update_search_terms
self.search_terms = [ self.search_terms = [

View file

@ -16,6 +16,7 @@ class Procedure < ApplicationRecord
has_one :attestation_template, dependent: :destroy has_one :attestation_template, dependent: :destroy
belongs_to :parent_procedure, class_name: 'Procedure' belongs_to :parent_procedure, class_name: 'Procedure'
belongs_to :canonical_procedure, class_name: 'Procedure'
belongs_to :service belongs_to :service
has_many :administrateurs_procedures has_many :administrateurs_procedures
@ -129,11 +130,12 @@ class Procedure < ApplicationRecord
other_procedure = other_procedure_with_path(path) other_procedure = other_procedure_with_path(path)
if other_procedure.present? && administrateur.owns?(other_procedure) if other_procedure.present? && administrateur.owns?(other_procedure)
other_procedure.unpublish! other_procedure.unpublish!
end publish!(other_procedure.canonical_procedure || other_procedure)
else
publish! publish!
end end
end end
end
def csv_export_stale? def csv_export_stale?
!csv_export_file.attached? || csv_export_file.created_at < MAX_DUREE_CONSERVATION_EXPORT.ago !csv_export_file.attached? || csv_export_file.created_at < MAX_DUREE_CONSERVATION_EXPORT.ago
@ -615,8 +617,8 @@ class Procedure < ApplicationRecord
update!(closed_at: nil, unpublished_at: nil) update!(closed_at: nil, unpublished_at: nil)
end end
def after_publish def after_publish(canonical_procedure = nil)
update!(published_at: Time.zone.now) update!(published_at: Time.zone.now, canonical_procedure: canonical_procedure)
end end
def after_close def after_close

View file

@ -24,9 +24,11 @@ as well as a link to its edit page.
<%= content_for(:title) %> <%= content_for(:title) %>
</h1> </h1>
<div>
<%= button_to "modifier", edit_manager_user_path(page.resource), method: :get, class: "button" %>
</div>
<div> <div>
<%= button_to "supprimer", delete_manager_user_path(page.resource), method: :delete, disabled: !page.resource.can_be_deleted?, class: "button", data: { confirm: "Confirmez-vous la suppression de l'utilisateur ?" }, title: page.resource.can_be_deleted? ? "Supprimer" : "Cet utilisateur a des dossiers dont l'instruction a commencé et ne peut être supprimé" %> <%= button_to "supprimer", delete_manager_user_path(page.resource), method: :delete, disabled: !page.resource.can_be_deleted?, class: "button", data: { confirm: "Confirmez-vous la suppression de l'utilisateur ?" }, title: page.resource.can_be_deleted? ? "Supprimer" : "Cet utilisateur a des dossiers dont l'instruction a commencé et ne peut être supprimé" %>
</div> </div>
<div> <div>
<% if !user.confirmed? %> <% if !user.confirmed? %>

View file

@ -30,11 +30,22 @@
%hr %hr
- if dossier.procedure.routee? - if dossier.procedure.routee?
= f.label :groupe_instructeur_id, dossier.procedure.routing_criteria_name = f.label :groupe_instructeur_id do
= dossier.procedure.routing_criteria_name
%span.mandatory *
-# The routing dropdown has 'include_blank: false', because otherwise a blank
-# value may nullify the groupe_instructeur and thus the link between the dossier
-# and its procedure.
-#
-# If, one day, we need to make clearer to the user that they must actually choose an
-# option, THINK TWICE before adding a blank option, and what would happen if the form is
-# saved when the blank option is selected.
-# Instead please consider other possibilities; like using CSS to gray out the default option,
-# or adding some "(please select an option)" wording aside the label of the default group.
-# CSS
= f.select :groupe_instructeur_id, = f.select :groupe_instructeur_id,
dossier.procedure.groupe_instructeurs.order(:label).map { |gi| [gi.label, gi.id] }, dossier.procedure.groupe_instructeurs.order(:label).map { |gi| [gi.label, gi.id] },
{}, { include_blank: false }
required: true
= f.fields_for :champs, dossier.champs do |champ_form| = f.fields_for :champs, dossier.champs do |champ_form|
- champ = champ_form.object - champ = champ_form.object
@ -42,6 +53,7 @@
locals: { champ: champ, form: champ_form } locals: { champ: champ, form: champ_form }
- if !apercu - if !apercu
.dossier-edit-sticky-footer
.send-dossier-actions-bar .send-dossier-actions-bar
- if dossier.brouillon? - if dossier.brouillon?
- if autosave_available?(dossier) - if autosave_available?(dossier)

View file

@ -49,5 +49,7 @@ module TPS
debounce_delay: 3000, debounce_delay: 3000,
status_visible_duration: 6000 status_visible_duration: 6000
} }
config.skylight.probes += [:graphql]
end end
end end

View file

@ -2,7 +2,6 @@
Browser.modern_rules.clear Browser.modern_rules.clear
Browser.modern_rules << -> b { b.chrome? && b.version.to_i >= 50 && !b.platform.ios? } Browser.modern_rules << -> b { b.chrome? && b.version.to_i >= 50 && !b.platform.ios? }
Browser.modern_rules << -> b { b.edge? && b.version.to_i >= 14 && !b.compatibility_view? } Browser.modern_rules << -> b { b.edge? && b.version.to_i >= 14 && !b.compatibility_view? }
Browser.modern_rules << -> b { b.ie? && b.version.to_i >= 11 && !b.compatibility_view? }
Browser.modern_rules << -> b { b.firefox? && b.version.to_i >= 50 && !b.platform.ios? } Browser.modern_rules << -> b { b.firefox? && b.version.to_i >= 50 && !b.platform.ios? }
Browser.modern_rules << -> b { b.opera? && b.version.to_i >= 40 } Browser.modern_rules << -> b { b.opera? && b.version.to_i >= 40 }
Browser.modern_rules << -> b { b.safari? && b.version.to_i >= 8 } Browser.modern_rules << -> b { b.safari? && b.version.to_i >= 8 }

View file

@ -24,7 +24,7 @@ Rails.application.routes.draw do
delete 'delete', on: :member delete 'delete', on: :member
end end
resources :users, only: [:index, :show] do resources :users, only: [:index, :show, :edit, :update] do
delete 'delete', on: :member delete 'delete', on: :member
post 'resend_confirmation_instructions', on: :member post 'resend_confirmation_instructions', on: :member
put 'enable_feature', on: :member put 'enable_feature', on: :member

View file

@ -0,0 +1,5 @@
class AddCanonicalProcedureIdToProcedures < ActiveRecord::Migration[5.2]
def change
add_column :procedures, :canonical_procedure_id, :bigint
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_12_18_103727) do ActiveRecord::Schema.define(version: 2020_01_14_113700) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -502,6 +502,7 @@ ActiveRecord::Schema.define(version: 2019_12_18_103727) do
t.boolean "ods_export_queued" t.boolean "ods_export_queued"
t.datetime "closed_at" t.datetime "closed_at"
t.datetime "unpublished_at" t.datetime "unpublished_at"
t.bigint "canonical_procedure_id"
t.index ["declarative_with_state"], name: "index_procedures_on_declarative_with_state" t.index ["declarative_with_state"], name: "index_procedures_on_declarative_with_state"
t.index ["hidden_at"], name: "index_procedures_on_hidden_at" t.index ["hidden_at"], name: "index_procedures_on_hidden_at"
t.index ["parent_procedure_id"], name: "index_procedures_on_parent_procedure_id" t.index ["parent_procedure_id"], name: "index_procedures_on_parent_procedure_id"

View file

@ -21,6 +21,7 @@ describe ApplicationController, type: :controller do
let(:payload) { {} } let(:payload) { {} }
before do before do
allow(@controller).to receive(:content_type).and_return('')
allow(@controller).to receive(:current_user).and_return(current_user) allow(@controller).to receive(:current_user).and_return(current_user)
expect(@controller).to receive(:current_instructeur).and_return(current_instructeur) expect(@controller).to receive(:current_instructeur).and_return(current_instructeur)
expect(@controller).to receive(:current_administrateur).and_return(current_administrateur) expect(@controller).to receive(:current_administrateur).and_return(current_administrateur)
@ -42,6 +43,8 @@ describe ApplicationController, type: :controller do
payload.delete(key) payload.delete(key)
end end
expect(payload).to eq({ expect(payload).to eq({
sk_rendered_format: nil,
sk_variant: [],
user_agent: 'Rails Testing', user_agent: 'Rails Testing',
user_roles: 'Guest' user_roles: 'Guest'
}) })
@ -61,6 +64,8 @@ describe ApplicationController, type: :controller do
payload.delete(key) payload.delete(key)
end end
expect(payload).to eq({ expect(payload).to eq({
sk_rendered_format: nil,
sk_variant: [],
user_agent: 'Rails Testing', user_agent: 'Rails Testing',
user_id: current_user.id, user_id: current_user.id,
user_email: current_user.email, user_email: current_user.email,
@ -85,6 +90,8 @@ describe ApplicationController, type: :controller do
payload.delete(key) payload.delete(key)
end end
expect(payload).to eq({ expect(payload).to eq({
sk_rendered_format: nil,
sk_variant: [],
user_agent: 'Rails Testing', user_agent: 'Rails Testing',
user_id: current_user.id, user_id: current_user.id,
user_email: current_user.email, user_email: current_user.email,

View file

@ -4,6 +4,7 @@ describe Manager::ApplicationController, type: :controller do
let(:payload) { {} } let(:payload) { {} }
before do before do
allow(@controller).to receive(:content_type).and_return('')
allow(@controller).to receive(:current_user).and_return(current_user) allow(@controller).to receive(:current_user).and_return(current_user)
@controller.send(:append_info_to_payload, payload) @controller.send(:append_info_to_payload, payload)
end end
@ -13,6 +14,8 @@ describe Manager::ApplicationController, type: :controller do
payload.delete(key) payload.delete(key)
end end
expect(payload).to eq({ expect(payload).to eq({
sk_rendered_format: nil,
sk_variant: [],
user_agent: 'Rails Testing', user_agent: 'Rails Testing',
user_id: current_user.id, user_id: current_user.id,
user_email: current_user.email user_email: current_user.email

View file

@ -1,6 +1,36 @@
describe Manager::UsersController, type: :controller do describe Manager::UsersController, type: :controller do
let(:administration) { create(:administration) } let(:administration) { create(:administration) }
describe '#update' do
let!(:user) { create(:user, email: 'ancien.email@domaine.fr') }
before {
sign_in administration
}
subject { patch :update, params: { id: user.id, user: { email: nouvel_email } } }
describe 'with a valid email' do
let(:nouvel_email) { 'nouvel.email@domaine.fr' }
it 'updates the user email' do
subject
expect(User.find_by(id: user.id).email).to eq(nouvel_email)
end
end
describe 'with an invalid email' do
let(:nouvel_email) { 'plop' }
it 'does not update the user email' do
subject
expect(User.find_by(id: user.id).email).not_to eq(nouvel_email)
expect(flash[:error]).to match("« #{nouvel_email} » n'est pas une adresse valide.")
end
end
end
describe '#delete' do describe '#delete' do
let!(:user) { create(:user) } let!(:user) { create(:user) }

View file

@ -3,29 +3,29 @@ require 'spec_helper'
feature 'Outdated browsers support:' do feature 'Outdated browsers support:' do
context 'when the user browser is outdated' do context 'when the user browser is outdated' do
before(:each) do before(:each) do
ie_10_user_agent = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)' ie_11_user_agent = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko'
Capybara.page.driver.header('user-agent', ie_10_user_agent) Capybara.page.driver.header('user-agent', ie_11_user_agent)
end end
scenario 'a banner is displayed' do scenario 'a banner is displayed' do
visit new_user_session_path visit new_user_session_path
expect(page).to have_content('Internet Explorer 10 est trop ancien') expect(page).to have_content('Internet Explorer 11 est trop ancien')
end end
scenario 'the banner can be dismissed' do scenario 'the banner can be dismissed' do
visit new_user_session_path visit new_user_session_path
expect(page).to have_content('Internet Explorer 10 est trop ancien') expect(page).to have_content('Internet Explorer 11 est trop ancien')
# The banner is hidden immediately # The banner is hidden immediately
within '#outdated-browser-banner' do within '#outdated-browser-banner' do
click_on 'Ignorer' click_on 'Ignorer'
end end
expect(page).not_to have_content('Internet Explorer 10 est trop ancien') expect(page).not_to have_content('Internet Explorer 11 est trop ancien')
expect(page).to have_current_path(new_user_session_path) expect(page).to have_current_path(new_user_session_path)
# The banner is hidden after a refresh # The banner is hidden after a refresh
page.refresh page.refresh
expect(page).not_to have_content('Internet Explorer 10 est trop ancien') expect(page).not_to have_content('Internet Explorer 11 est trop ancien')
end end
end end
end end

View file

@ -91,7 +91,14 @@ describe Champs::LinkedDropDownListChamp do
end end
context 'when there is a secondary value' do context 'when there is a secondary value' do
before { subject.secondary_value = 'Primary' } before { subject.secondary_value = 'Secondary' }
it { is_expected.not_to be_mandatory_and_blank }
end
context 'when there is nothing to select for the secondary value' do
let(:drop_down_list) { build(:drop_down_list, value: "--A--\nAbbott\nAbelard\n--B--\n--C--\nCynthia") }
before { subject.primary_value = 'B' }
it { is_expected.not_to be_mandatory_and_blank } it { is_expected.not_to be_mandatory_and_blank }
end end

View file

@ -1,4 +1,7 @@
require 'spec_helper' require 'spec_helper'
describe Exercice do describe Exercice do
describe 'validations' do
it { is_expected.to validate_presence_of(:ca) }
end
end end

View file

@ -534,41 +534,101 @@ describe Procedure do
let(:procedure) { create(:procedure, path: 'example-path') } let(:procedure) { create(:procedure, path: 'example-path') }
let(:now) { Time.zone.now.beginning_of_minute } let(:now) { Time.zone.now.beginning_of_minute }
after { Timecop.return } context 'when publishing a new procedure' do
context "without parent procedure" do
before do before do
Timecop.freeze(now) Timecop.freeze(now) do
procedure.publish! procedure.publish!
end end
end
it do it 'no reference to the canonical procedure on the published procedure' do
expect(procedure.canonical_procedure).to be_nil
end
it 'changes the procedure state to published' do
expect(procedure.closed_at).to be_nil expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now) expect(procedure.published_at).to eq(now)
expect(Procedure.find_by(path: "example-path")).to eq(procedure) expect(Procedure.find_by(path: "example-path")).to eq(procedure)
expect(Procedure.find_by(path: "example-path").administrateurs).to eq(procedure.administrateurs) expect(Procedure.find_by(path: "example-path").administrateurs).to eq(procedure.administrateurs)
end end
end end
context 'when publishing over a previous canonical procedure' do
let(:canonical_procedure) { create(:procedure, :published) }
before do
Timecop.freeze(now) do
procedure.publish!(canonical_procedure)
end
end
it 'references the canonical procedure on the published procedure' do
expect(procedure.canonical_procedure).to eq(canonical_procedure)
end
it 'changes the procedure state to published' do
expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now)
end
end
end end
describe "#publish_or_reopen!" do describe "#publish_or_reopen!" do
let(:published_procedure) { create(:procedure, :published) } let(:canonical_procedure) { create(:procedure, :published) }
let(:administrateur) { published_procedure.administrateurs.first } let(:administrateur) { canonical_procedure.administrateurs.first }
let(:procedure) { create(:procedure, administrateurs: [administrateur]) } let(:procedure) { create(:procedure, administrateurs: [administrateur]) }
let(:now) { Time.zone.now.beginning_of_minute } let(:now) { Time.zone.now.beginning_of_minute }
context "without parent procedure" do context 'when publishing over a previous canonical procedure' do
before do before do
Timecop.freeze(now) procedure.path = canonical_procedure.path
procedure.path = published_procedure.path Timecop.freeze(now) do
procedure.publish_or_reopen!(administrateur) procedure.publish_or_reopen!(administrateur)
end end
canonical_procedure.reload
end
it do it 'references the canonical procedure on the published procedure' do
expect(procedure.canonical_procedure).to eq(canonical_procedure)
end
it 'changes the procedure state to published' do
expect(procedure.closed_at).to be_nil expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now) expect(procedure.published_at).to eq(now)
end end
it 'unpublishes the canonical procedure' do
expect(canonical_procedure.unpublished_at).to eq(now)
end
end
context 'when publishing over a previous procedure with canonical procedure' do
let(:canonical_procedure) { create(:procedure, :closed) }
let(:parent_procedure) { create(:procedure, :published, administrateurs: [administrateur]) }
before do
parent_procedure.update!(path: canonical_procedure.path, canonical_procedure: canonical_procedure)
procedure.path = canonical_procedure.path
Timecop.freeze(now) do
procedure.publish_or_reopen!(administrateur)
end
parent_procedure.reload
end
it 'references the canonical procedure on the published procedure' do
expect(procedure.canonical_procedure).to eq(canonical_procedure)
end
it 'changes the procedure state to published' do
expect(procedure.canonical_procedure).to eq(canonical_procedure)
expect(procedure.closed_at).to be_nil
expect(procedure.published_at).to eq(now)
end
it 'unpublishes parent procedure' do
expect(parent_procedure.unpublished_at).to eq(now)
end
end end
end end
@ -577,10 +637,10 @@ describe Procedure do
let(:now) { Time.zone.now.beginning_of_minute } let(:now) { Time.zone.now.beginning_of_minute }
before do before do
Timecop.freeze(now) Timecop.freeze(now) do
procedure.unpublish! procedure.unpublish!
end end
after { Timecop.return } end
it { it {
expect(procedure.closed_at).to eq(nil) expect(procedure.closed_at).to eq(nil)
@ -653,11 +713,11 @@ describe Procedure do
let(:procedure) { create(:procedure, :published) } let(:procedure) { create(:procedure, :published) }
let(:now) { Time.zone.now.beginning_of_minute } let(:now) { Time.zone.now.beginning_of_minute }
before do before do
Timecop.freeze(now) Timecop.freeze(now) do
procedure.close! procedure.close!
end
procedure.reload procedure.reload
end end
after { Timecop.return }
it { expect(procedure.close?).to be_truthy } it { expect(procedure.close?).to be_truthy }
it { expect(procedure.closed_at).to eq(now) } it { expect(procedure.closed_at).to eq(now) }

View file

@ -295,7 +295,9 @@ describe ProcedureExportService do
"Question / Introduction", "Question / Introduction",
"Réponse", "Réponse",
"Créé le", "Créé le",
"Répondu le" "Répondu le",
"Instructeur",
"Expert"
]) ])
end end