Merge pull request #5918 from betagouv/main

2021-02-17-01
This commit is contained in:
krichtof 2021-02-17 17:08:06 +01:00 committed by GitHub
commit 7cdf71f1c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 1855 additions and 1000 deletions

View file

@ -9,7 +9,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Sentry Release - name: Sentry Release
uses: getsentry/action-release@v1.0.0 uses: getsentry/action-release@v1
env: env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}

View file

@ -1320,6 +1320,8 @@ Rails/ActiveRecordCallbacksOrder:
Enabled: false Enabled: false
Rails/AfterCommitOverride: Rails/AfterCommitOverride:
Enabled: true Enabled: true
Rails/AttributeDefaultBlockValue:
Enabled: true
Rails/FindById: Rails/FindById:
Enabled: true Enabled: true
Rails/Inquiry: Rails/Inquiry:
@ -1342,6 +1344,8 @@ Rails/ShortI18n:
Enabled: true Enabled: true
Rails/SquishedSQLHeredocs: Rails/SquishedSQLHeredocs:
Enabled: true Enabled: true
Rails/WhereEquals:
Enabled: true
Rails/WhereExists: Rails/WhereExists:
Enabled: true Enabled: true
Rails/WhereNot: Rails/WhereNot:

View file

@ -22,38 +22,38 @@ GEM
specs: specs:
aasm (5.1.1) aasm (5.1.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
actioncable (6.0.3.4) actioncable (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.4) actionmailbox (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
activejob (= 6.0.3.4) activejob (= 6.0.3.5)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.5)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.0.3.4) actionmailer (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
actionview (= 6.0.3.4) actionview (= 6.0.3.5)
activejob (= 6.0.3.4) activejob (= 6.0.3.5)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.0.3.4) actionpack (6.0.3.5)
actionview (= 6.0.3.4) actionview (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3.4) actiontext (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.5)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.0.3.4) actionview (6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -61,32 +61,31 @@ GEM
active_link_to (1.0.5) active_link_to (1.0.5)
actionpack actionpack
addressable addressable
active_model_serializers (0.10.10) active_model_serializers (0.10.12)
actionpack (>= 4.1, < 6.1) actionpack (>= 4.1, < 6.2)
activemodel (>= 4.1, < 6.1) activemodel (>= 4.1, < 6.2)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_storage_validations (0.9.0) active_storage_validations (0.9.2)
rails (>= 5.2.0) rails (>= 5.2.0)
activejob (6.0.3.4) activejob (6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.0.3.4) activemodel (6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
activerecord (6.0.3.4) activerecord (6.0.3.5)
activemodel (= 6.0.3.4) activemodel (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
activestorage (6.0.3.4) activestorage (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
activejob (= 6.0.3.4) activejob (= 6.0.3.5)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.5)
marcel (~> 0.3.1) marcel (~> 0.3.1)
activestorage-openstack (1.4.1) activestorage-openstack (1.5.1)
fog-openstack (~> 1.0) fog-openstack (~> 1.0)
marcel marcel
mime-types
rails (>= 5.2.2) rails (>= 5.2.2)
activesupport (6.0.3.4) activesupport (6.0.3.5)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@ -111,11 +110,11 @@ GEM
annotate (3.1.1) annotate (3.1.1)
activerecord (>= 3.2, < 7.0) activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 14.0) rake (>= 10.4, < 14.0)
ast (2.4.1) ast (2.4.2)
attr_encrypted (3.1.0) attr_encrypted (3.1.0)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
attr_required (1.0.1) attr_required (1.0.1)
autoprefixer-rails (10.0.1.0) autoprefixer-rails (10.2.4.0)
execjs execjs
axe-matchers (2.6.1) axe-matchers (2.6.1)
dumb_delegator (~> 0.8) dumb_delegator (~> 0.8)
@ -124,7 +123,7 @@ GEM
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
axlsx_styler (1.0.0) axlsx_styler (1.1.0)
activesupport (>= 3.1) activesupport (>= 3.1)
caxlsx (>= 2.0.2) caxlsx (>= 2.0.2)
babel-source (5.8.35) babel-source (5.8.35)
@ -134,22 +133,22 @@ GEM
bcrypt (3.1.16) bcrypt (3.1.16)
bindata (2.4.8) bindata (2.4.8)
bindex (0.8.1) bindex (0.8.1)
brakeman (4.9.1) brakeman (5.0.0)
browser (5.0.0) browser (5.3.0)
builder (3.2.4) builder (3.2.4)
byebug (11.1.3) byebug (11.1.3)
capybara (3.33.0) capybara (3.35.3)
addressable addressable
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
nokogiri (~> 1.8) nokogiri (~> 1.8)
rack (>= 1.6.0) rack (>= 1.6.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (~> 1.5) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
capybara-email (3.0.2) capybara-email (3.0.2)
capybara (>= 2.4, < 4.0) capybara (>= 2.4, < 4.0)
mail mail
capybara-screenshot (1.0.24) capybara-screenshot (1.0.25)
capybara (>= 1.0, < 4) capybara (>= 1.0, < 4)
launchy launchy
capybara-selenium (0.0.6) capybara-selenium (0.0.6)
@ -157,15 +156,15 @@ GEM
selenium-webdriver selenium-webdriver
case_transform (0.2) case_transform (0.2)
activesupport activesupport
caxlsx (3.0.2) caxlsx (3.0.4)
htmlentities (~> 4.3, >= 4.3.4) htmlentities (~> 4.3, >= 4.3.4)
mimemagic (~> 0.3) mimemagic (~> 0.3)
nokogiri (~> 1.10, >= 1.10.4) nokogiri (~> 1.10, >= 1.10.4)
rubyzip (>= 1.3.0, < 3) rubyzip (>= 1.3.0, < 3)
chartkick (3.4.0) chartkick (3.4.2)
childprocess (3.0.0) childprocess (3.0.0)
choice (0.2.0) choice (0.2.0)
chunky_png (1.3.11) chunky_png (1.4.0)
clamav-client (3.2.0) clamav-client (3.2.0)
coderay (1.1.3) coderay (1.1.3)
coercible (1.0.0) coercible (1.0.0)
@ -179,22 +178,28 @@ GEM
coffee-script-source (1.12.2) coffee-script-source (1.12.2)
concurrent-ruby (1.1.8) concurrent-ruby (1.1.8)
connection_pool (2.2.3) connection_pool (2.2.3)
crack (0.4.4) crack (0.4.5)
rexml
crass (1.0.6) crass (1.0.6)
css_parser (1.7.1) css_parser (1.9.0)
addressable addressable
daemons (1.3.1) daemons (1.3.1)
database_cleaner (1.8.5) database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0)
database_cleaner-active_record (2.0.0)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
datetime_picker_rails (0.0.7) datetime_picker_rails (0.0.7)
momentjs-rails (>= 2.8.1) momentjs-rails (>= 2.8.1)
deep_cloneable (3.0.0) deep_cloneable (3.0.0)
activerecord (>= 3.1.0, < 7) activerecord (>= 3.1.0, < 7)
delayed_cron_job (0.7.3) delayed_cron_job (0.7.4)
delayed_job (>= 4.1) delayed_job (>= 4.1)
delayed_job (4.1.8) delayed_job (4.1.9)
activesupport (>= 3.0, < 6.1) activesupport (>= 3.0, < 6.2)
delayed_job_active_record (4.1.4) delayed_job_active_record (4.1.5)
activerecord (>= 3.0, < 6.1) activerecord (>= 3.0, < 6.2)
delayed_job (>= 3.0, < 5) delayed_job (>= 3.0, < 5)
delayed_job_web (1.4.3) delayed_job_web (1.4.3)
activerecord (> 3.0.0) activerecord (> 3.0.0)
@ -225,9 +230,9 @@ GEM
railties (>= 3.2) railties (>= 3.2)
dry-inflector (0.2.0) dry-inflector (0.2.0)
dumb_delegator (0.8.1) dumb_delegator (0.8.1)
ecma-re-validator (0.2.1) ecma-re-validator (0.3.0)
regexp_parser (~> 1.2) regexp_parser (~> 2.0)
em-websocket (0.5.1) em-websocket (0.5.2)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0.6.0)
encryptor (3.0.0) encryptor (3.0.0)
@ -239,28 +244,28 @@ GEM
ethon (0.12.0) ethon (0.12.0)
ffi (>= 1.3.0) ffi (>= 1.3.0)
eventmachine (1.2.7) eventmachine (1.2.7)
excon (0.76.0) excon (0.79.0)
execjs (2.7.0) execjs (2.7.0)
factory_bot (5.1.2) factory_bot (6.1.0)
activesupport (>= 4.2.0) activesupport (>= 5.0.0)
faraday (1.3.0) faraday (1.3.0)
faraday-net_http (~> 1.0) faraday-net_http (~> 1.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
ruby2_keywords ruby2_keywords
faraday-net_http (1.0.1) faraday-net_http (1.0.1)
ffi (1.13.1) ffi (1.14.2)
ffi-geos (2.1.0) ffi-geos (2.1.0)
ffi (>= 1.0.0) ffi (>= 1.0.0)
flipper (0.19.0) flipper (0.20.3)
flipper-active_record (0.19.0) flipper-active_record (0.20.3)
activerecord (>= 5.0, < 7) activerecord (>= 5.0, < 7)
flipper (~> 0.19.0) flipper (~> 0.20.3)
flipper-ui (0.19.0) flipper-ui (0.20.3)
erubi (>= 1.0.0, < 2.0.0) erubi (>= 1.0.0, < 2.0.0)
flipper (~> 0.19.0) flipper (~> 0.20.3)
rack (>= 1.4, < 3) rack (>= 1.4, < 3)
rack-protection (>= 1.5.3, < 2.1.0) rack-protection (>= 1.5.3, < 2.2.0)
fog-core (2.2.0) fog-core (2.2.3)
builder builder
excon (~> 0.71) excon (~> 0.71)
formatador (~> 0.2) formatador (~> 0.2)
@ -268,16 +273,16 @@ GEM
fog-json (1.2.0) fog-json (1.2.0)
fog-core fog-core
multi_json (~> 1.10) multi_json (~> 1.10)
fog-openstack (1.0.10) fog-openstack (1.0.11)
fog-core (~> 2.1) fog-core (~> 2.1)
fog-json (>= 1.0) fog-json (>= 1.0)
ipaddress (>= 0.8) ipaddress (>= 0.8)
formatador (0.2.5) formatador (0.2.5)
fugit (1.3.4) fugit (1.4.2)
et-orbi (~> 1.1, >= 1.1.8) et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.1) raabro (~> 1.4)
geo_coord (0.1.0) geo_coord (0.2.0)
geocoder (1.6.3) geocoder (1.6.5)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
gon (6.4.0) gon (6.4.0)
@ -285,11 +290,11 @@ GEM
i18n (>= 0.7) i18n (>= 0.7)
multi_json multi_json
request_store (>= 1.0) request_store (>= 1.0)
graphql (1.10.9) graphql (1.12.4)
graphql-batch (0.4.3) graphql-batch (0.4.3)
graphql (>= 1.3, < 2) graphql (>= 1.3, < 2)
promise.rb (~> 0.7.2) promise.rb (~> 0.7.2)
graphql-rails_logger (1.2.2) graphql-rails_logger (1.2.3)
actionpack (> 5.0) actionpack (> 5.0)
activesupport (> 5.0) activesupport (> 5.0)
railties (> 5.0) railties (> 5.0)
@ -300,7 +305,7 @@ GEM
thor (>= 0.19, < 2.0) thor (>= 0.19, < 2.0)
graphql_playground-rails (2.1.0) graphql_playground-rails (2.1.0)
rails (>= 5.1.0) rails (>= 5.1.0)
groupdate (5.2.1) groupdate (5.2.2)
activesupport (>= 5) activesupport (>= 5)
guard (2.16.2) guard (2.16.2)
formatador (>= 0.2.4) formatador (>= 0.2.4)
@ -321,7 +326,7 @@ GEM
guard (~> 2.1) guard (~> 2.1)
guard-compat (~> 1.1) guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0) rspec (>= 2.99.0, < 4.0)
haml (5.1.2) haml (5.2.1)
temple (>= 0.8.0) temple (>= 0.8.0)
tilt tilt
haml-lint (0.999.999) haml-lint (0.999.999)
@ -332,8 +337,9 @@ GEM
haml (>= 4.0.6, < 6.0) haml (>= 4.0.6, < 6.0)
html2haml (>= 1.0.1) html2haml (>= 1.0.1)
railties (>= 5.1) railties (>= 5.1)
haml_lint (0.35.0) haml_lint (0.37.0)
haml (>= 4.0, < 5.2) haml (>= 4.0, < 5.3)
parallel (~> 1.10)
rainbow rainbow
rubocop (>= 0.50.0) rubocop (>= 0.50.0)
sysexits (~> 1.1) sysexits (~> 1.1)
@ -353,7 +359,7 @@ GEM
http_accept_language (2.1.1) http_accept_language (2.1.1)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.8.7) i18n (1.8.9)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n-tasks (0.9.33) i18n-tasks (0.9.33)
activesupport (>= 4.0.2) activesupport (>= 4.0.2)
@ -375,15 +381,15 @@ GEM
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json (2.3.1) json (2.5.1)
json-jwt (1.13.0) json-jwt (1.13.0)
activesupport (>= 4.2) activesupport (>= 4.2)
aes_key_wrap aes_key_wrap
bindata bindata
json_schemer (0.2.16) json_schemer (0.2.17)
ecma-re-validator (~> 0.2) ecma-re-validator (~> 0.3)
hana (~> 1.3) hana (~> 1.3)
regexp_parser (~> 1.5) regexp_parser (~> 2.0)
uri_template (~> 0.7) uri_template (~> 0.7)
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
jwt (2.2.2) jwt (2.2.2)
@ -407,7 +413,7 @@ GEM
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
listen (3.2.1) listen (3.4.1)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
lograge (0.11.2) lograge (0.11.2)
@ -419,19 +425,19 @@ GEM
loofah (2.9.0) loofah (2.9.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lumberjack (1.2.4) lumberjack (1.2.8)
mail (2.7.1) mail (2.7.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
mailjet (1.5.4) mailjet (1.6.0)
activesupport (>= 3.1.0) activesupport (>= 3.1.0)
rack (>= 1.4.0) rack (>= 1.4.0)
rest-client rest-client (>= 2.0.0)
marcel (0.3.3) marcel (0.3.3)
mimemagic (~> 0.3.2) mimemagic (~> 0.3.2)
method_source (1.0.0) method_source (1.0.0)
mime-types (3.3.1) mime-types (3.3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2020.0512) mime-types-data (3.2021.0212)
mimemagic (0.3.5) mimemagic (0.3.5)
mini_magick (4.11.0) mini_magick (4.11.0)
mini_mime (1.0.2) mini_mime (1.0.2)
@ -445,7 +451,7 @@ GEM
ruby2_keywords (~> 0.0.1) ruby2_keywords (~> 0.0.1)
nenv (0.3.0) nenv (0.3.0)
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.4) nio4r (2.5.5)
nokogiri (1.11.1) nokogiri (1.11.1)
mini_portile2 (~> 2.5.0) mini_portile2 (~> 2.5.0)
racc (~> 1.4) racc (~> 1.4)
@ -453,7 +459,7 @@ GEM
nenv (~> 0.1) nenv (~> 0.1)
shellany (~> 0.0) shellany (~> 0.0)
open4 (1.3.4) open4 (1.3.4)
openid_connect (1.1.8) openid_connect (1.2.0)
activemodel activemodel
attr_required (>= 1.0.0) attr_required (>= 1.0.0)
json-jwt (>= 1.5.0) json-jwt (>= 1.5.0)
@ -464,25 +470,25 @@ GEM
validate_url validate_url
webfinger (>= 1.0.1) webfinger (>= 1.0.1)
orm_adapter (0.5.0) orm_adapter (0.5.0)
parallel (1.19.2) parallel (1.20.1)
parser (2.7.1.5) parser (3.0.0.0)
ast (~> 2.4.1) ast (~> 2.4.1)
pdf-core (0.7.0) pdf-core (0.9.0)
pg (1.2.3) pg (1.2.3)
phonelib (0.6.45) phonelib (0.6.48)
prawn (2.2.2) prawn (2.4.0)
pdf-core (~> 0.7.0) pdf-core (~> 0.9.0)
ttfunk (~> 1.5) ttfunk (~> 1.7)
prawn-rails (1.3.0) prawn-rails (1.3.0)
prawn prawn
prawn-table prawn-table
rails (>= 3.1.0) rails (>= 3.1.0)
prawn-svg (0.30.0) prawn-svg (0.31.0)
css_parser (~> 1.6) css_parser (~> 1.6)
prawn (>= 0.11.1, < 3) prawn (>= 0.11.1, < 3)
prawn-table (0.2.2) prawn-table (0.2.2)
prawn (>= 1.3.0, < 3.0.0) prawn (>= 1.3.0, < 3.0.0)
premailer (1.14.1) premailer (1.14.2)
addressable addressable
css_parser (>= 1.6.0) css_parser (>= 1.6.0)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
@ -497,43 +503,43 @@ GEM
byebug (~> 11.0) byebug (~> 11.0)
pry (~> 0.13.0) pry (~> 0.13.0)
public_suffix (4.0.6) public_suffix (4.0.6)
puma (5.0.2) puma (5.2.1)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.1.0) pundit (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.1.6) raabro (1.4.0)
racc (1.5.2) racc (1.5.2)
rack (2.2.3) rack (2.2.3)
rack-attack (6.3.1) rack-attack (6.5.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-mini-profiler (2.1.0) rack-mini-profiler (2.3.1)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-oauth2 (1.15.0) rack-oauth2 (1.16.0)
activesupport activesupport
attr_required attr_required
httpclient httpclient
json-jwt (>= 1.11.0) json-jwt (>= 1.11.0)
rack (>= 2.1.0) rack (>= 2.1.0)
rack-protection (2.0.8.1) rack-protection (2.1.0)
rack rack
rack-proxy (0.6.5) rack-proxy (0.6.5)
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (6.0.3.4) rails (6.0.3.5)
actioncable (= 6.0.3.4) actioncable (= 6.0.3.5)
actionmailbox (= 6.0.3.4) actionmailbox (= 6.0.3.5)
actionmailer (= 6.0.3.4) actionmailer (= 6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
actiontext (= 6.0.3.4) actiontext (= 6.0.3.5)
actionview (= 6.0.3.4) actionview (= 6.0.3.5)
activejob (= 6.0.3.4) activejob (= 6.0.3.5)
activemodel (= 6.0.3.4) activemodel (= 6.0.3.5)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.5)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 6.0.3.4) railties (= 6.0.3.5)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
@ -552,9 +558,9 @@ GEM
rails-i18n (6.0.0) rails-i18n (6.0.0)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7) railties (>= 6.0.0, < 7)
railties (6.0.3.4) railties (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
@ -570,7 +576,7 @@ GEM
execjs execjs
railties (>= 3.2) railties (>= 3.2)
tilt tilt
regexp_parser (1.8.0) regexp_parser (2.0.3)
request_store (1.5.0) request_store (1.5.0)
rack (>= 1.4) rack (>= 1.4)
responders (3.0.1) responders (3.0.1)
@ -582,7 +588,7 @@ GEM
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
rexml (3.2.4) rexml (3.2.4)
rgeo (2.1.1) rgeo (2.2.0)
rgeo-geojson (2.1.1) rgeo-geojson (2.1.1)
rgeo (>= 1.0.0) rgeo (>= 1.0.0)
rodf (1.1.1) rodf (1.1.1)
@ -591,56 +597,59 @@ GEM
rubyzip (>= 1.0) rubyzip (>= 1.0)
rotp (4.1.0) rotp (4.1.0)
addressable (~> 2.5) addressable (~> 2.5)
rouge (3.17.0) rouge (3.26.0)
rqrcode (1.1.2) rqrcode (1.2.0)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 0.1) rqrcode_core (~> 0.2)
rqrcode_core (0.1.2) rqrcode_core (0.2.0)
rspec (3.9.0) rspec (3.10.0)
rspec-core (~> 3.9.0) rspec-core (~> 3.10.0)
rspec-expectations (~> 3.9.0) rspec-expectations (~> 3.10.0)
rspec-mocks (~> 3.9.0) rspec-mocks (~> 3.10.0)
rspec-core (3.9.2) rspec-core (3.10.1)
rspec-support (~> 3.9.3) rspec-support (~> 3.10.0)
rspec-expectations (3.9.2) rspec-expectations (3.10.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0) rspec-support (~> 3.10.0)
rspec-mocks (3.9.1) rspec-mocks (3.10.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0) rspec-support (~> 3.10.0)
rspec-rails (4.0.1) rspec-rails (4.0.2)
actionpack (>= 4.2) actionpack (>= 4.2)
activesupport (>= 4.2) activesupport (>= 4.2)
railties (>= 4.2) railties (>= 4.2)
rspec-core (~> 3.9) rspec-core (~> 3.10)
rspec-expectations (~> 3.9) rspec-expectations (~> 3.10)
rspec-mocks (~> 3.9) rspec-mocks (~> 3.10)
rspec-support (~> 3.9) rspec-support (~> 3.10)
rspec-support (3.9.3) rspec-support (3.10.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.92.0) rubocop (1.10.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.7.1.5) parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7) regexp_parser (>= 1.8, < 3.0)
rexml rexml
rubocop-ast (>= 0.5.0) rubocop-ast (>= 1.2.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0) unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (0.7.0) rubocop-ast (1.4.1)
parser (>= 2.7.1.5) parser (>= 2.7.1.5)
strscan (>= 1.0.0) rubocop-packaging (0.5.1)
rubocop-performance (1.8.1) rubocop (>= 0.89, < 2.0)
rubocop (>= 0.87.0) rubocop-performance (1.9.2)
rubocop (>= 0.90.0, < 2.0)
rubocop-ast (>= 0.4.0) rubocop-ast (>= 0.4.0)
rubocop-rails (2.8.1) rubocop-rails (2.9.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 0.87.0) rubocop (>= 0.90.0, < 2.0)
rubocop-rails_config (0.12.6) rubocop-rails_config (1.3.0)
railties (>= 5.0) railties (>= 5.0)
rubocop (~> 0.82) rubocop (>= 1.8)
rubocop-ast (>= 1.0.1)
rubocop-packaging (~> 0.5)
rubocop-performance (~> 1.3) rubocop-performance (~> 1.3)
rubocop-rails (~> 2.0) rubocop-rails (~> 2.0)
rubocop-rspec-focused (1.0.0) rubocop-rspec-focused (1.0.0)
@ -649,12 +658,12 @@ GEM
rake (>= 0.8.1) rake (>= 0.8.1)
ruby-graphviz (1.2.5) ruby-graphviz (1.2.5)
rexml rexml
ruby-progressbar (1.10.1) ruby-progressbar (1.11.0)
ruby-saml-idp (0.3.5) ruby-saml-idp (0.3.5)
ruby-vips (2.0.17) ruby-vips (2.0.17)
ffi (~> 1.9) ffi (~> 1.9)
ruby2_keywords (0.0.4) ruby2_keywords (0.0.4)
ruby_parser (3.15.0) ruby_parser (3.15.1)
sexp_processor (~> 4.9) sexp_processor (~> 4.9)
rubyzip (2.3.0) rubyzip (2.3.0)
sanitize-url (0.1.4) sanitize-url (0.1.4)
@ -677,7 +686,7 @@ GEM
selenium-webdriver (3.142.7) selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 4.0) childprocess (>= 0.5, < 4.0)
rubyzip (>= 1.2.2) rubyzip (>= 1.2.2)
semantic_range (2.3.0) semantic_range (2.3.1)
sentry-delayed_job (4.2.0) sentry-delayed_job (4.2.0)
sentry-ruby-core (~> 4.2.0) sentry-ruby-core (~> 4.2.0)
sentry-rails (4.2.1) sentry-rails (4.2.1)
@ -690,31 +699,31 @@ GEM
sentry-ruby-core (4.2.1) sentry-ruby-core (4.2.1)
concurrent-ruby concurrent-ruby
faraday faraday
sexp_processor (4.15.1) sexp_processor (4.15.2)
shellany (0.0.1) shellany (0.0.1)
shoulda-matchers (4.4.1) shoulda-matchers (4.5.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
sib-api-v3-sdk (7.0.0) sib-api-v3-sdk (7.4.0)
json (~> 2.1, >= 2.1.0) json (~> 2.1, >= 2.1.0)
typhoeus (~> 1.0, >= 1.0.1) typhoeus (~> 1.0, >= 1.0.1)
simple_xlsx_reader (1.0.4) simple_xlsx_reader (1.0.4)
nokogiri nokogiri
rubyzip rubyzip
sinatra (2.0.8.1) sinatra (2.1.0)
mustermann (~> 1.0) mustermann (~> 1.0)
rack (~> 2.0) rack (~> 2.2)
rack-protection (= 2.0.8.1) rack-protection (= 2.1.0)
tilt (~> 2.0) tilt (~> 2.0)
skylight (4.3.1) skylight (4.3.2)
skylight-core (= 4.3.1) skylight-core (= 4.3.2)
skylight-core (4.3.1) skylight-core (4.3.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
smart_listing (1.2.3) smart_listing (1.2.3)
coffee-rails coffee-rails
jquery-rails jquery-rails
kaminari (>= 0.17) kaminari (>= 0.17)
rails (>= 3.2) rails (>= 3.2)
spreadsheet_architect (4.0.0) spreadsheet_architect (4.1.0)
axlsx_styler (>= 1.0.0, < 2) axlsx_styler (>= 1.0.0, < 2)
caxlsx (>= 2.0.2, < 4) caxlsx (>= 2.0.2, < 4)
rodf (>= 1.0.0, < 2) rodf (>= 1.0.0, < 2)
@ -728,8 +737,7 @@ GEM
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
strscan (1.0.3) swd (1.2.0)
swd (1.1.2)
activesupport (>= 3) activesupport (>= 3)
attr_required (>= 0.0.5) attr_required (>= 0.0.5)
httpclient (>= 2.4) httpclient (>= 2.4)
@ -740,8 +748,8 @@ GEM
thor (1.1.0) thor (1.1.0)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.10) tilt (2.0.10)
timecop (0.9.1) timecop (0.9.4)
ttfunk (1.6.2.1) ttfunk (1.7.0)
typhoeus (1.4.0) typhoeus (1.4.0)
ethon (>= 0.9.0) ethon (>= 0.9.0)
tzinfo (1.2.9) tzinfo (1.2.9)
@ -754,7 +762,7 @@ GEM
validate_email (0.1.6) validate_email (0.1.6)
activemodel (>= 3.0) activemodel (>= 3.0)
mail (>= 2.2.5) mail (>= 2.2.5)
validate_url (1.0.11) validate_url (1.0.13)
activemodel (>= 3.0.0) activemodel (>= 3.0.0)
public_suffix public_suffix
vcr (6.0.0) vcr (6.0.0)
@ -765,19 +773,19 @@ GEM
equalizer (~> 0.0, >= 0.0.9) equalizer (~> 0.0, >= 0.0.9)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
web-console (4.0.4) web-console (4.1.0)
actionview (>= 6.0.0) actionview (>= 6.0.0)
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
webdrivers (4.4.1) webdrivers (4.5.0)
nokogiri (~> 1.6) nokogiri (~> 1.6)
rubyzip (>= 1.3.0) rubyzip (>= 1.3.0)
selenium-webdriver (>= 3.0, < 4.0) selenium-webdriver (>= 3.0, < 4.0)
webfinger (1.1.0) webfinger (1.1.0)
activesupport activesupport
httpclient (>= 2.4) httpclient (>= 2.4)
webmock (3.9.1) webmock (3.11.2)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
@ -798,7 +806,7 @@ GEM
zipline (1.3.0) zipline (1.3.0)
actionpack (>= 3.2.1, < 7.0) actionpack (>= 3.2.1, < 7.0)
zip_tricks (>= 4.2.1, < 6.0) zip_tricks (>= 4.2.1, < 6.0)
zxcvbn-ruby (1.1.0) zxcvbn-ruby (1.2.0)
PLATFORMS PLATFORMS
ruby ruby

View file

@ -128,8 +128,14 @@ Le projet utilise plusieurs linters pour vérifier la lisibilité et la qualité
## Déploiement ## Déploiement
- Tout nouveau commit ajouté à la branche `dev` est automatiquement déployé [en intégration](https://dev.demarches-simplifiees.fr/) Dans le cas dun déploiement sur plusieurs serveurs, lapplication peut être déployée avec la tâche :
- Tout nouveau commit ajouté à la branche `master` est automatiquement déployé [en production](https://www.demarches-simplifiees.fr/)
```
DOMAINS="web1 web2" BRANCH="main" bin/rake deploy
```
En interne, cette tâche utilise [mina](https://github.com/mina-deploy/mina) pour lancer les commandes
de déploiement sur tous les serveurs spécifiés.
## Tâches courantes ## Tâches courantes

View file

@ -5,34 +5,4 @@
.select-instructeurs { .select-instructeurs {
width: 100%; width: 100%;
} }
.select2-container--default {
.select2-selection--multiple {
border: solid 1px $border-grey;
.select2-selection__choice, // scss-lint:disable SelectorFormat
.select2-search--inline {
padding: $default-spacer;
}
}
&.select2-container--focus {
.select2-selection--multiple {
border: 1px solid $blue;
box-shadow: 0px 0px 2px 1px $blue;
}
}
.select2-results__option { // scss-lint:disable SelectorFormat
padding: $default-spacer;
}
.custom-select2-option {
.icon {
margin-right: $default-spacer;
}
}
}
} }

View file

@ -4,6 +4,5 @@
// = require ./utils // = require ./utils
// = require ./fonts // = require ./fonts
// = require leaflet // = require leaflet
// = require select2
// = require_tree . // = require_tree .
// = stub ./print.scss // = stub ./print.scss

View file

@ -281,18 +281,6 @@
.dropdown-form { .dropdown-form {
padding: 2 * $default-spacer; padding: 2 * $default-spacer;
.select2-container {
margin-bottom: 2 * $default-spacer;
}
.select2-selection {
border: 1px solid $border-grey;
&.select2-selection--multiple {
border: 1px solid $border-grey;
}
}
&.large { &.large {
width: 340px; width: 340px;
} }
@ -310,10 +298,6 @@
} }
} }
.select2-dropdown {
border: 1px solid $border-grey;
}
.link { .link {
color: $blue; color: $blue;
} }

View file

@ -260,8 +260,7 @@
max-width: 180px; max-width: 180px;
} }
select, select {
.select2-selection {
// hack found here: https://stackoverflow.com/questions/1895476/how-to-style-a-select-dropdown-with-css-only-without-javascript // hack found here: https://stackoverflow.com/questions/1895476/how-to-style-a-select-dropdown-with-css-only-without-javascript
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
@ -305,39 +304,26 @@
border-color: $blue; border-color: $blue;
} }
.select2 { [data-reach-combobox-token-list] {
min-width: 50%; padding: $default-padding;
display: flex;
} }
.select2-container { [data-reach-combobox-token] {
display: block; border: solid 1px $border-grey;
margin-bottom: $default-fields-spacer; color: $black;
margin-top: $default-padding;
margin-bottom: $default-padding;
margin-right: 0.5 * $default-padding;
border-radius: 4px;
padding: $default-padding;
cursor: pointer;
list-style: none;
}
&.select2-container--focus { [data-reach-combobox-token]:focus {
.select2-selection { background-color: $black;
border-color: $border-grey; color: $white;
}
}
.select2-selection--single {
min-height: 62px;
// scss-lint:disable SelectorFormat
.select2-selection__arrow {
display: none;
}
// scss-lint:enable
}
// scss-lint:disable SelectorFormat
.select2-selection__rendered {
padding: $default-padding;
}
.select2-selection__choice {
background-color: #FFFFFF;
}
// scss-lint:enable
} }
.editable-champ { .editable-champ {
@ -481,11 +467,55 @@
} }
} }
[data-react-class="ComboMultipleDropdownList"] {
margin-bottom: $default-fields-spacer;
[data-reach-combobox-input] {
outline: none;
border: none;
flex-grow: 1;
margin: 0.25rem;
background-image: image-url("icons/chevron-down");
background-size: 14px;
background-repeat: no-repeat;
background-position: right 10px center;
}
[data-reach-combobox-input]:focus {
outline: solid;
outline-color: $light-blue;
}
}
[data-combobox-token-label] {
border: 1px solid #CCCCCC;
border-radius: 4px;
display: flex;
flex-wrap: wrap;
}
[data-reach-combobox-option] { [data-reach-combobox-option] {
font-size: 16px; font-size: 16px;
list-style-type: none;
} }
[data-reach-combobox-option][aria-selected="true"] { [data-reach-combobox-option][aria-selected="true"] {
background: $light-blue !important; background: $light-blue !important;
color: $white; color: $white;
} }
[data-combobox-separator] {
font-size: 16px;
color: $dark-grey;
margin-top: 6px;
}
[data-combobox-remove-token] {
color: $dark-grey;
padding-right: 4px;
}
[data-reach-combobox-input]:focus {
outline-color: $light-blue;
}

View file

@ -1,14 +1,50 @@
@import "constants";
@import "colors";
.personnes-impliquees { .personnes-impliquees {
padding-bottom: 50px; padding-bottom: 50px;
ul { ul.tab-list {
list-style-type: disc; list-style-type: disc;
margin-left: 16px; margin-left: 16px;
} }
// scss-lint:disable SelectorFormat [data-react-class="ComboMultipleDropdownList"] {
.form .select2-container .select2-selection__rendered { margin-bottom: $default-fields-spacer;
padding: 12px;
[data-reach-combobox-token-list] {
padding: 0.5 * $default-padding;
display: flex;
}
[data-reach-combobox-token] {
border: solid 1px $border-grey;
color: $black;
margin-top: 0.5 * $default-padding;
margin-bottom: 0.5 * $default-padding;
margin-right: 0.5 * $default-padding;
border-radius: 4px;
padding: 0.5 * $default-padding;
cursor: pointer;
list-style: none;
}
[data-reach-combobox-token]:focus {
background-color: $black;
color: $white;
}
[data-reach-combobox-input] {
outline: none;
border: none;
flex-grow: 1;
margin: 0.25rem;
}
[data-reach-combobox-input]:focus {
outline: solid;
outline-color: $light-blue;
}
} }
// scss-lint:enable
} }

View file

@ -23,6 +23,10 @@
flex: 10; flex: 10;
padding: 0 $default-padding; padding: 0 $default-padding;
background-color: $light-grey; background-color: $light-grey;
&.no-background {
background-color: transparent;
}
} }
.procedure-form__column--preview { .procedure-form__column--preview {

View file

@ -61,4 +61,42 @@
margin-bottom: 3 * $default-spacer; margin-bottom: 3 * $default-spacer;
text-align: center; text-align: center;
} }
[data-react-class="ComboMultipleDropdownList"] {
margin-bottom: $default-fields-spacer;
[data-reach-combobox-token-list] {
padding: 0.25 * $default-padding;
display: inline-block;
width: 100%;
}
[data-reach-combobox-token] {
border: solid 1px $border-grey;
color: $black;
margin: 0.25 * $default-padding;
border-radius: 2px;
padding: 0.25 * $default-padding;
cursor: pointer;
list-style: none;
}
[data-reach-combobox-token]:focus {
background-color: $black;
color: $white;
}
[data-reach-combobox-input] {
outline: none;
border: none;
flex-grow: 1;
margin: 0.25rem;
}
[data-reach-combobox-input]:focus {
outline: solid;
outline-color: $light-blue;
}
}
} }

View file

@ -9,10 +9,10 @@ class API::V2::GraphqlController < API::V2::BaseController
render json: result render json: result
rescue => exception rescue => exception
if Rails.env.development? if Rails.env.production?
handle_error_in_development(exception)
else
handle_error_in_production(exception) handle_error_in_production(exception)
else
handle_error_in_development(exception)
end end
end end

View file

@ -72,7 +72,7 @@ module Instructeurs
end end
def send_to_instructeurs def send_to_instructeurs
recipients = Instructeur.find(params[:recipients]) recipients = Instructeur.find(JSON.parse(params[:recipients]))
recipients.each do |recipient| recipients.each do |recipient|
recipient.follow(dossier) recipient.follow(dossier)

View file

@ -138,7 +138,7 @@ module Instructeurs
end end
def update_displayed_fields def update_displayed_fields
procedure_presentation.update_displayed_fields(params[:values]) procedure_presentation.update_displayed_fields(JSON.parse(params[:values]))
redirect_back(fallback_location: instructeur_procedure_url(procedure)) redirect_back(fallback_location: instructeur_procedure_url(procedure))
end end

View file

@ -60,7 +60,7 @@ module NewAdministrateur
def uninterlaced_png(uploaded_file) def uninterlaced_png(uploaded_file)
if uploaded_file&.content_type == 'image/png' if uploaded_file&.content_type == 'image/png'
chunky_img = ChunkyPNG::Image.from_io(uploaded_file) chunky_img = ChunkyPNG::Image.from_io(uploaded_file.to_io)
chunky_img.save(uploaded_file.tempfile.to_path, interlace: false) chunky_img.save(uploaded_file.tempfile.to_path, interlace: false)
uploaded_file.tempfile.reopen(uploaded_file.tempfile.to_path, 'rb') uploaded_file.tempfile.reopen(uploaded_file.tempfile.to_path, 'rb')
end end

View file

@ -80,8 +80,8 @@ module NewAdministrateur
end end
def add_instructeur def add_instructeur
emails = params['emails'].presence || [] emails = params['emails'].presence || [].to_json
emails = emails.map(&:strip).map(&:downcase) emails = JSON.parse(emails).map(&:strip).map(&:downcase)
correct_emails, bad_emails = emails correct_emails, bad_emails = emails
.partition { |email| URI::MailTo::EMAIL_REGEXP.match?(email) } .partition { |email| URI::MailTo::EMAIL_REGEXP.match?(email) }

View file

@ -1,6 +1,6 @@
module NewAdministrateur module NewAdministrateur
class ProceduresController < AdministrateurController class ProceduresController < AdministrateurController
before_action :retrieve_procedure, only: [:champs, :annotations, :edit, :monavis, :update_monavis, :jeton, :update_jeton, :publication, :publish, :transfert, :allow_expert_review, :invited_expert_list] before_action :retrieve_procedure, only: [:champs, :annotations, :edit, :monavis, :update_monavis, :jeton, :update_jeton, :publication, :publish, :transfert, :allow_expert_review, :invited_expert_list, :update_allow_decision_access]
before_action :procedure_locked?, only: [:champs, :annotations] before_action :procedure_locked?, only: [:champs, :annotations]
ITEMS_PER_PAGE = 25 ITEMS_PER_PAGE = 25
@ -186,7 +186,14 @@ module NewAdministrateur
end end
def invited_expert_list def invited_expert_list
@invited_expert_emails = ExpertsProcedure.invited_expert_emails(@procedure) @experts_procedure = @procedure.experts_procedures.sort_by { |expert_procedure| expert_procedure.expert.email }
end
def update_allow_decision_access
@procedure
.experts_procedures
.find(params[:expert_procedure])
.update!(allow_decision_access_params)
end end
private private
@ -215,5 +222,9 @@ module NewAdministrateur
def publish_params def publish_params
params.permit(:path, :lien_site_web) params.permit(:path, :lien_site_web)
end end
def allow_decision_access_params
params.require(:experts_procedure).permit(:allow_decision_access)
end
end end
end end

View file

@ -0,0 +1,66 @@
module Mutations
class DossierModifierAnnotation < Mutations::BaseMutation
argument :dossier_id, ID, "Dossier ID", required: true, loads: Types::DossierType
argument :instructeur_id, ID, "Instructeur qui demande la modification.", required: true, loads: Types::ProfileType
argument :annotation_id, ID, "Annotation ID", required: true
field :annotation, Types::ChampType, null: true
field :errors, [Types::ValidationErrorType], null: true
def resolve_with_type(type, dossier, annotation_id, instructeur, value)
annotation = find_annotation(dossier, type, annotation_id)
if block_given?
annotation.value = yield annotation.type_champ, value
else
annotation.value = value
end
if annotation.save
dossier.log_modifier_annotation!(annotation, instructeur)
{ annotation: annotation }
else
{ errors: annotation.errors.full_messages }
end
end
def authorized?(dossier:, instructeur:, **args)
dossier_authorized_for?(dossier, instructeur)
end
private
def find_annotation(dossier, type, annotation_id)
_, stable_id = GraphQL::Schema::UniqueWithinType.decode(annotation_id)
dossier.champs_private
.joins(:type_de_champ)
.find_by!(types_de_champ: {
type_champ: annotation_type_champ(type),
stable_id: stable_id
})
end
def annotation_type_champ(type)
case type
when :text
[
TypeDeChamp.type_champs.fetch(:text),
TypeDeChamp.type_champs.fetch(:textarea)
]
when :checkbox
[
TypeDeChamp.type_champs.fetch(:checkbox),
TypeDeChamp.type_champs.fetch(:yes_no),
TypeDeChamp.type_champs.fetch(:engagement)
]
when :date
TypeDeChamp.type_champs.fetch(:date)
when :datetime
TypeDeChamp.type_champs.fetch(:datetime)
when :integer_number
TypeDeChamp.type_champs.fetch(:integer_number)
end
end
end
end

View file

@ -0,0 +1,23 @@
module Mutations
class DossierModifierAnnotationCheckbox < Mutations::DossierModifierAnnotation
description "Modifier lannotation au format oui/non."
argument :value, Boolean, required: true
def resolve(dossier:, annotation_id:, instructeur:, value:)
resolve_with_type(
:checkbox,
dossier,
annotation_id,
instructeur,
value
) do |type_champ, value|
if type_champ == TypeDeChamp.type_champs.fetch(:yes_no)
value ? 'true' : 'false'
else
value ? 'on' : 'off'
end
end
end
end
end

View file

@ -0,0 +1,17 @@
module Mutations
class DossierModifierAnnotationDate < Mutations::DossierModifierAnnotation
description "Modifier lannotation au format date."
argument :value, GraphQL::Types::ISO8601Date, required: true
def resolve(dossier:, annotation_id:, instructeur:, value:)
resolve_with_type(
:date,
dossier,
annotation_id,
instructeur,
value
)
end
end
end

View file

@ -0,0 +1,17 @@
module Mutations
class DossierModifierAnnotationDatetime < Mutations::DossierModifierAnnotation
description "Modifier lannotation au format date et heure."
argument :value, GraphQL::Types::ISO8601DateTime, required: true
def resolve(dossier:, annotation_id:, instructeur:, value:)
resolve_with_type(
:datetime,
dossier,
annotation_id,
instructeur,
value
)
end
end
end

View file

@ -0,0 +1,17 @@
module Mutations
class DossierModifierAnnotationIntegerNumber < Mutations::DossierModifierAnnotation
description "Modifier lannotation au format nombre entier."
argument :value, Int, required: true
def resolve(dossier:, annotation_id:, instructeur:, value:)
resolve_with_type(
:integer_number,
dossier,
annotation_id,
instructeur,
value
)
end
end
end

View file

@ -0,0 +1,17 @@
module Mutations
class DossierModifierAnnotationText < Mutations::DossierModifierAnnotation
description "Modifier lannotation au format text."
argument :value, String, required: true
def resolve(dossier:, annotation_id:, instructeur:, value:)
resolve_with_type(
:text,
dossier,
annotation_id,
instructeur,
value
)
end
end
end

View file

@ -685,6 +685,201 @@ type DossierLinkChamp implements Champ {
stringValue: String stringValue: String
} }
"""
Autogenerated input type of DossierModifierAnnotationCheckbox
"""
input DossierModifierAnnotationCheckboxInput {
"""
Annotation ID
"""
annotationId: ID!
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Dossier ID
"""
dossierId: ID!
"""
Instructeur qui demande la modification.
"""
instructeurId: ID!
value: Boolean!
}
"""
Autogenerated return type of DossierModifierAnnotationCheckbox
"""
type DossierModifierAnnotationCheckboxPayload {
annotation: Champ
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
errors: [ValidationError!]
}
"""
Autogenerated input type of DossierModifierAnnotationDate
"""
input DossierModifierAnnotationDateInput {
"""
Annotation ID
"""
annotationId: ID!
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Dossier ID
"""
dossierId: ID!
"""
Instructeur qui demande la modification.
"""
instructeurId: ID!
value: ISO8601Date!
}
"""
Autogenerated return type of DossierModifierAnnotationDate
"""
type DossierModifierAnnotationDatePayload {
annotation: Champ
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
errors: [ValidationError!]
}
"""
Autogenerated input type of DossierModifierAnnotationDatetime
"""
input DossierModifierAnnotationDatetimeInput {
"""
Annotation ID
"""
annotationId: ID!
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Dossier ID
"""
dossierId: ID!
"""
Instructeur qui demande la modification.
"""
instructeurId: ID!
value: ISO8601DateTime!
}
"""
Autogenerated return type of DossierModifierAnnotationDatetime
"""
type DossierModifierAnnotationDatetimePayload {
annotation: Champ
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
errors: [ValidationError!]
}
"""
Autogenerated input type of DossierModifierAnnotationIntegerNumber
"""
input DossierModifierAnnotationIntegerNumberInput {
"""
Annotation ID
"""
annotationId: ID!
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Dossier ID
"""
dossierId: ID!
"""
Instructeur qui demande la modification.
"""
instructeurId: ID!
value: Int!
}
"""
Autogenerated return type of DossierModifierAnnotationIntegerNumber
"""
type DossierModifierAnnotationIntegerNumberPayload {
annotation: Champ
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
errors: [ValidationError!]
}
"""
Autogenerated input type of DossierModifierAnnotationText
"""
input DossierModifierAnnotationTextInput {
"""
Annotation ID
"""
annotationId: ID!
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Dossier ID
"""
dossierId: ID!
"""
Instructeur qui demande la modification.
"""
instructeurId: ID!
value: String!
}
"""
Autogenerated return type of DossierModifierAnnotationText
"""
type DossierModifierAnnotationTextPayload {
annotation: Champ
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
errors: [ValidationError!]
}
""" """
Autogenerated input type of DossierPasserEnInstruction Autogenerated input type of DossierPasserEnInstruction
""" """
@ -986,42 +1181,132 @@ type Mutation {
""" """
File information required to prepare a direct upload File information required to prepare a direct upload
""" """
createDirectUpload(input: CreateDirectUploadInput!): CreateDirectUploadPayload createDirectUpload(
"""
Parameters for CreateDirectUpload
"""
input: CreateDirectUploadInput!
): CreateDirectUploadPayload
""" """
Accepter le dossier. Accepter le dossier.
""" """
dossierAccepter(input: DossierAccepterInput!): DossierAccepterPayload dossierAccepter(
"""
Parameters for DossierAccepter
"""
input: DossierAccepterInput!
): DossierAccepterPayload
""" """
Archiver le dossier. Archiver le dossier.
""" """
dossierArchiver(input: DossierArchiverInput!): DossierArchiverPayload dossierArchiver(
"""
Parameters for DossierArchiver
"""
input: DossierArchiverInput!
): DossierArchiverPayload
""" """
Changer le grope instructeur du dossier. Changer le grope instructeur du dossier.
""" """
dossierChangerGroupeInstructeur(input: DossierChangerGroupeInstructeurInput!): DossierChangerGroupeInstructeurPayload dossierChangerGroupeInstructeur(
"""
Parameters for DossierChangerGroupeInstructeur
"""
input: DossierChangerGroupeInstructeurInput!
): DossierChangerGroupeInstructeurPayload
""" """
Classer le dossier sans suite. Classer le dossier sans suite.
""" """
dossierClasserSansSuite(input: DossierClasserSansSuiteInput!): DossierClasserSansSuitePayload dossierClasserSansSuite(
"""
Parameters for DossierClasserSansSuite
"""
input: DossierClasserSansSuiteInput!
): DossierClasserSansSuitePayload
""" """
Envoyer un message à l'usager du dossier. Envoyer un message à l'usager du dossier.
""" """
dossierEnvoyerMessage(input: DossierEnvoyerMessageInput!): DossierEnvoyerMessagePayload dossierEnvoyerMessage(
"""
Parameters for DossierEnvoyerMessage
"""
input: DossierEnvoyerMessageInput!
): DossierEnvoyerMessagePayload
"""
Modifier lannotation au format oui/non.
"""
dossierModifierAnnotationCheckbox(
"""
Parameters for DossierModifierAnnotationCheckbox
"""
input: DossierModifierAnnotationCheckboxInput!
): DossierModifierAnnotationCheckboxPayload
"""
Modifier lannotation au format date.
"""
dossierModifierAnnotationDate(
"""
Parameters for DossierModifierAnnotationDate
"""
input: DossierModifierAnnotationDateInput!
): DossierModifierAnnotationDatePayload
"""
Modifier lannotation au format date et heure.
"""
dossierModifierAnnotationDatetime(
"""
Parameters for DossierModifierAnnotationDatetime
"""
input: DossierModifierAnnotationDatetimeInput!
): DossierModifierAnnotationDatetimePayload
"""
Modifier lannotation au format nombre entier.
"""
dossierModifierAnnotationIntegerNumber(
"""
Parameters for DossierModifierAnnotationIntegerNumber
"""
input: DossierModifierAnnotationIntegerNumberInput!
): DossierModifierAnnotationIntegerNumberPayload
"""
Modifier lannotation au format text.
"""
dossierModifierAnnotationText(
"""
Parameters for DossierModifierAnnotationText
"""
input: DossierModifierAnnotationTextInput!
): DossierModifierAnnotationTextPayload
""" """
Passer le dossier en instruction. Passer le dossier en instruction.
""" """
dossierPasserEnInstruction(input: DossierPasserEnInstructionInput!): DossierPasserEnInstructionPayload dossierPasserEnInstruction(
"""
Parameters for DossierPasserEnInstruction
"""
input: DossierPasserEnInstructionInput!
): DossierPasserEnInstructionPayload
""" """
Refuser le dossier. Refuser le dossier.
""" """
dossierRefuser(input: DossierRefuserInput!): DossierRefuserPayload dossierRefuser(
"""
Parameters for DossierRefuser
"""
input: DossierRefuserInput!
): DossierRefuserPayload
} }
enum Order { enum Order {

View file

@ -9,5 +9,11 @@ module Types
field :dossier_accepter, mutation: Mutations::DossierAccepter field :dossier_accepter, mutation: Mutations::DossierAccepter
field :dossier_archiver, mutation: Mutations::DossierArchiver field :dossier_archiver, mutation: Mutations::DossierArchiver
field :dossier_changer_groupe_instructeur, mutation: Mutations::DossierChangerGroupeInstructeur field :dossier_changer_groupe_instructeur, mutation: Mutations::DossierChangerGroupeInstructeur
field :dossier_modifier_annotation_text, mutation: Mutations::DossierModifierAnnotationText
field :dossier_modifier_annotation_checkbox, mutation: Mutations::DossierModifierAnnotationCheckbox
field :dossier_modifier_annotation_date, mutation: Mutations::DossierModifierAnnotationDate
field :dossier_modifier_annotation_datetime, mutation: Mutations::DossierModifierAnnotationDatetime
field :dossier_modifier_annotation_integer_number, mutation: Mutations::DossierModifierAnnotationIntegerNumber
end end
end end

View file

@ -1,9 +1,9 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { ReactQueryCacheProvider } from 'react-query'; import { QueryClientProvider } from 'react-query';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ComboSearch from './ComboSearch'; import ComboSearch from './ComboSearch';
import { queryCache } from './shared/queryCache'; import { queryClient } from './shared/queryClient';
function ComboAdresseSearch({ function ComboAdresseSearch({
mandatory, mandatory,
@ -16,7 +16,7 @@ function ComboAdresseSearch({
const transformResults = useCallback((_, { features }) => features); const transformResults = useCallback((_, { features }) => features);
return ( return (
<ReactQueryCacheProvider queryCache={queryCache}> <QueryClientProvider client={queryClient}>
<ComboSearch <ComboSearch
placeholder={placeholder} placeholder={placeholder}
required={mandatory} required={mandatory}
@ -28,7 +28,7 @@ function ComboAdresseSearch({
transformResult={transformResult} transformResult={transformResult}
transformResults={transformResults} transformResults={transformResults}
/> />
</ReactQueryCacheProvider> </QueryClientProvider>
); );
} }

View file

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { ReactQueryCacheProvider } from 'react-query'; import { QueryClientProvider } from 'react-query';
import ComboSearch from './ComboSearch'; import ComboSearch from './ComboSearch';
import { queryCache } from './shared/queryCache'; import { queryClient } from './shared/queryClient';
function ComboAnnuaireEducationSearch(params) { function ComboAnnuaireEducationSearch(params) {
return ( return (
<ReactQueryCacheProvider queryCache={queryCache}> <QueryClientProvider client={queryClient}>
<ComboSearch <ComboSearch
required={params.mandatory} required={params.mandatory}
hiddenFieldId={params.hiddenFieldId} hiddenFieldId={params.hiddenFieldId}
@ -21,7 +21,7 @@ function ComboAnnuaireEducationSearch(params) {
} }
}) => [id, `${nom_etablissement}, ${nom_commune} (${id})`]} }) => [id, `${nom_etablissement}, ${nom_commune} (${id})`]}
/> />
</ReactQueryCacheProvider> </QueryClientProvider>
); );
} }

View file

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { ReactQueryCacheProvider } from 'react-query'; import { QueryClientProvider } from 'react-query';
import ComboSearch from './ComboSearch'; import ComboSearch from './ComboSearch';
import { queryCache } from './shared/queryCache'; import { queryClient } from './shared/queryClient';
function ComboCommunesSearch(params) { function ComboCommunesSearch(params) {
return ( return (
<ReactQueryCacheProvider queryCache={queryCache}> <QueryClientProvider client={queryClient}>
<ComboSearch <ComboSearch
required={params.mandatory} required={params.mandatory}
hiddenFieldId={params.hiddenFieldId} hiddenFieldId={params.hiddenFieldId}
@ -17,7 +17,7 @@ function ComboCommunesSearch(params) {
`${nom} (${codesPostaux[0]})` `${nom} (${codesPostaux[0]})`
]} ]}
/> />
</ReactQueryCacheProvider> </QueryClientProvider>
); );
} }

View file

@ -1,9 +1,9 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { ReactQueryCacheProvider } from 'react-query'; import { QueryClientProvider } from 'react-query';
import matchSorter from 'match-sorter'; import { matchSorter } from 'match-sorter';
import ComboSearch from './ComboSearch'; import ComboSearch from './ComboSearch';
import { queryCache } from './shared/queryCache'; import { queryClient } from './shared/queryClient';
const extraTerms = [{ code: '99', nom: 'Etranger' }]; const extraTerms = [{ code: '99', nom: 'Etranger' }];
@ -16,7 +16,7 @@ function ComboDepartementsSearch(params) {
]); ]);
return ( return (
<ReactQueryCacheProvider queryCache={queryCache}> <QueryClientProvider client={queryClient}>
<ComboSearch <ComboSearch
required={params.mandatory} required={params.mandatory}
hiddenFieldId={params.hiddenFieldId} hiddenFieldId={params.hiddenFieldId}
@ -25,7 +25,7 @@ function ComboDepartementsSearch(params) {
transformResult={({ code, nom }) => [code, `${code} - ${nom}`]} transformResult={({ code, nom }) => [code, `${code} - ${nom}`]}
transformResults={transformResults} transformResults={transformResults}
/> />
</ReactQueryCacheProvider> </QueryClientProvider>
); );
} }

View file

@ -0,0 +1,250 @@
import React, {
useMemo,
useState,
useRef,
useContext,
createContext,
useEffect,
useLayoutEffect
} from 'react';
import PropTypes from 'prop-types';
import {
Combobox,
ComboboxInput,
ComboboxList,
ComboboxOption,
ComboboxPopover
} from '@reach/combobox';
import '@reach/combobox/styles.css';
import { matchSorter } from 'match-sorter';
import { fire } from '@utils';
const Context = createContext();
function ComboMultipleDropdownList({
options,
hiddenFieldId,
selected,
label,
acceptNewValues = false
}) {
if (label == undefined) {
label = 'Choisir une option';
}
if (Array.isArray(options[0]) == false) {
options = options.map((o) => [o, o]);
}
const inputRef = useRef();
const [term, setTerm] = useState('');
const [selections, setSelections] = useState(selected);
const [newValues, setNewValues] = useState([]);
const results = useMemo(
() =>
(term
? matchSorter(
options.filter((o) => !o[0].startsWith('--')),
term
)
: options
).filter((o) => o[0] && !selections.includes(o[1])),
[term, selections.join(',')]
);
const hiddenField = useMemo(
() => document.querySelector(`input[data-uuid="${hiddenFieldId}"]`),
[hiddenFieldId]
);
const handleChange = (event) => {
setTerm(event.target.value);
};
const onKeyDown = (event) => {
if (event.key === 'Enter') {
if (term && options.map((o) => o[0]).includes(term)) {
event.preventDefault();
return onSelect(term);
}
if (
acceptNewValues &&
term &&
matchSorter(
options.map((o) => o[0]),
term
).length == 0 // ignore when was pressed for selecting popover option
) {
event.preventDefault();
setNewValues([...newValues, term]);
saveSelection([...selections, term]);
setTerm('');
}
}
};
const saveSelection = (selections) => {
setSelections(selections);
if (hiddenField) {
hiddenField.setAttribute('value', JSON.stringify(selections));
fire(hiddenField, 'autosave:trigger');
}
};
const onSelect = (value) => {
let sel = options.find((o) => o[0] == value)[1];
saveSelection([...selections, sel]);
setTerm('');
};
const onRemove = (value) => {
saveSelection(
selections.filter((s) =>
newValues.includes(value)
? s != value
: s !== options.find((o) => o[0] == value)[1]
)
);
inputRef.current.focus();
};
return (
<Combobox openOnFocus={true} onSelect={onSelect} aria-label={label}>
<ComboboxTokenLabel onRemove={onRemove}>
<ul
aria-live="polite"
aria-atomic={true}
data-reach-combobox-token-list
>
{selections.map((selection) => (
<ComboboxToken
key={selection}
value={
newValues.find((newValue) => newValue == selection) ||
options.find((o) => o[1] == selection)[0]
}
/>
))}
</ul>
<ComboboxInput
ref={inputRef}
value={term}
onChange={handleChange}
onKeyDown={onKeyDown}
autocomplete={false}
/>
</ComboboxTokenLabel>
{results && (
<ComboboxPopover portal={false}>
{results.length === 0 && (
<p>
Aucun résultat{' '}
<button
onClick={() => {
setTerm('');
}}
>
Effacer
</button>
</p>
)}
<ComboboxList>
{results.map((value, index) => {
if (value[0].startsWith('--')) {
return <ComboboxSeparator key={index} value={value[0]} />;
}
return <ComboboxOption key={index} value={value[0]} />;
})}
</ComboboxList>
</ComboboxPopover>
)}
</Combobox>
);
}
function ComboboxTokenLabel({ onRemove, ...props }) {
const selectionsRef = useRef([]);
useLayoutEffect(() => {
selectionsRef.current = [];
return () => (selectionsRef.current = []);
});
const context = {
onRemove,
selectionsRef
};
return (
<Context.Provider value={context}>
<div data-combobox-token-label {...props} />
</Context.Provider>
);
}
ComboboxTokenLabel.propTypes = {
onRemove: PropTypes.func
};
function ComboboxSeparator({ value }) {
return (
<li aria-disabled="true" role="option" data-combobox-separator>
{value.slice(2, -2)}
</li>
);
}
ComboboxSeparator.propTypes = {
value: PropTypes.string
};
function ComboboxToken({ value, ...props }) {
const { selectionsRef, onRemove } = useContext(Context);
useEffect(() => {
selectionsRef.current.push(value);
});
return (
<li
data-reach-combobox-token
tabIndex="0"
onKeyDown={(event) => {
if (event.key === 'Backspace') {
onRemove(value);
}
}}
{...props}
>
<span
role="presentation"
data-combobox-remove-token
onClick={() => {
onRemove(value);
}}
>
x
</span>
{value}
</li>
);
}
ComboboxToken.propTypes = {
value: PropTypes.string,
label: PropTypes.string
};
ComboMultipleDropdownList.propTypes = {
options: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.arrayOf(
PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
)
)
]),
hiddenFieldId: PropTypes.string,
selected: PropTypes.arrayOf(PropTypes.string),
arraySelected: PropTypes.arrayOf(PropTypes.array),
label: PropTypes.string,
acceptNewValues: PropTypes.bool
};
export default ComboMultipleDropdownList;

View file

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { ReactQueryCacheProvider } from 'react-query'; import { QueryClientProvider } from 'react-query';
import ComboSearch from './ComboSearch'; import ComboSearch from './ComboSearch';
import { queryCache } from './shared/queryCache'; import { queryClient } from './shared/queryClient';
function ComboPaysSearch(params) { function ComboPaysSearch(params) {
return ( return (
<ReactQueryCacheProvider queryCache={queryCache}> <QueryClientProvider client={queryClient}>
<ComboSearch <ComboSearch
required={params.mandatory} required={params.mandatory}
hiddenFieldId={params.hiddenFieldId} hiddenFieldId={params.hiddenFieldId}
@ -14,7 +14,7 @@ function ComboPaysSearch(params) {
minimumInputLength={0} minimumInputLength={0}
transformResult={({ nom }) => [nom, nom]} transformResult={({ nom }) => [nom, nom]}
/> />
</ReactQueryCacheProvider> </QueryClientProvider>
); );
} }

View file

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { ReactQueryCacheProvider } from 'react-query'; import { QueryClientProvider } from 'react-query';
import ComboSearch from './ComboSearch'; import ComboSearch from './ComboSearch';
import { queryCache } from './shared/queryCache'; import { queryClient } from './shared/queryClient';
function ComboRegionsSearch(params) { function ComboRegionsSearch(params) {
return ( return (
<ReactQueryCacheProvider queryCache={queryCache}> <QueryClientProvider client={queryClient}>
<ComboSearch <ComboSearch
required={params.mandatory} required={params.mandatory}
hiddenFieldId={params.hiddenFieldId} hiddenFieldId={params.hiddenFieldId}
@ -14,7 +14,7 @@ function ComboRegionsSearch(params) {
minimumInputLength={0} minimumInputLength={0}
transformResult={({ code, nom }) => [code, nom]} transformResult={({ code, nom }) => [code, nom]}
/> />
</ReactQueryCacheProvider> </QueryClientProvider>
); );
} }

View file

@ -1,5 +1,5 @@
import React, { useState, useMemo, useCallback, useRef } from 'react'; import React, { useState, useMemo, useCallback, useRef } from 'react';
import { useDebounce } from 'react-use'; import { useDebounce } from 'use-debounce';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
@ -41,7 +41,7 @@ function ComboSearch({
); );
const initialValue = hiddenValueField && hiddenValueField.value; const initialValue = hiddenValueField && hiddenValueField.value;
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(''); const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
const resultsMap = useRef({}); const resultsMap = useRef({});
const setExternalValue = useCallback((value) => { const setExternalValue = useCallback((value) => {
@ -64,14 +64,6 @@ function ComboSearch({
} }
}); });
useDebounce(
() => {
setDebouncedSearchTerm(searchTerm);
},
300,
[searchTerm]
);
const handleOnChange = useCallback( const handleOnChange = useCallback(
({ target: { value } }) => { ({ target: { value } }) => {
setValue(value); setValue(value);

View file

@ -1,4 +1,3 @@
import scrollToComponent from 'react-scroll-to-component';
import { debounce } from '@utils'; import { debounce } from '@utils';
import { import {
createTypeDeChampOperation, createTypeDeChampOperation,
@ -53,8 +52,10 @@ function addTypeDeChamp(state, typeDeChamps, insertAfter, done) {
state.flash.success(); state.flash.success();
done(); done();
if (insertAfter) { if (insertAfter) {
scrollToComponent(insertAfter.target.nextElementSibling, { insertAfter.target.nextElementSibling.scrollIntoView({
duration: 300 behavior: 'smooth',
block: 'start',
inline: 'nearest'
}); });
} }
}) })

View file

@ -1,12 +1,12 @@
import { QueryCache } from 'react-query'; import { QueryClient } from 'react-query';
import { isNumeric } from '@utils'; import { isNumeric } from '@utils';
import matchSorter from 'match-sorter'; import { matchSorter } from 'match-sorter';
const { api_geo_url, api_adresse_url, api_education_url } = const { api_geo_url, api_adresse_url, api_education_url } =
gon.autocomplete || {}; gon.autocomplete || {};
export const queryCache = new QueryCache({ export const queryClient = new QueryClient({
defaultConfig: { defaultOptions: {
queries: { queries: {
queryFn: defaultQueryFn queryFn: defaultQueryFn
} }
@ -35,7 +35,7 @@ function buildOptions() {
return [{}, null]; return [{}, null];
} }
async function defaultQueryFn(scope, term) { async function defaultQueryFn({ queryKey: [scope, term] }) {
if (scope == 'pays') { if (scope == 'pays') {
return matchSorter(await getPays(), term, { keys: ['nom'] }); return matchSorter(await getPays(), term, { keys: ['nom'] });
} }

View file

@ -0,0 +1,5 @@
import Loadable from '../components/Loadable';
export default Loadable(() =>
import('../components/ComboMultipleDropdownList')
);

View file

@ -1,80 +0,0 @@
import $ from 'jquery';
import 'select2';
const language = {
errorLoading: function () {
return 'Les résultats ne peuvent pas être chargés.';
},
inputTooLong: function (args) {
var overChars = args.input.length - args.maximum;
return 'Supprimez ' + overChars + ' caractère' + (overChars > 1 ? 's' : '');
},
inputTooShort: function (args) {
var remainingChars = args.minimum - args.input.length;
return (
'Saisissez au moins ' +
remainingChars +
' caractère' +
(remainingChars > 1 ? 's' : '')
);
},
loadingMore: function () {
return 'Chargement de résultats supplémentaires…';
},
maximumSelected: function (args) {
return (
'Vous pouvez seulement sélectionner ' +
args.maximum +
' élément' +
(args.maximum > 1 ? 's' : '')
);
},
noResults: function () {
return 'Aucun résultat trouvé';
},
searching: function () {
return 'Recherche en cours…';
},
removeAllItems: function () {
return 'Supprimer tous les éléments';
}
};
const baseOptions = {
language,
width: '100%'
};
const templateOption = ({ text }) =>
$(
`<span class="custom-select2-option"><span class="icon person"></span>${text}</span>`
);
addEventListener('ds:page:update', () => {
$('select.select2').select2(baseOptions);
$('.columns-form select.select2-limited').select2({
width: '300px',
placeholder: 'Sélectionnez des colonnes',
maximumSelectionLength: '5'
});
$('.recipients-form select.select2-limited').select2({
language,
width: '300px',
placeholder: 'Sélectionnez des instructeurs',
maximumSelectionLength: '30'
});
$('select.select2-limited.select-instructeurs').select2({
language,
dropdownParent: $('.instructeur-wrapper'),
placeholder: 'Saisir ladresse email de linstructeur',
tags: true,
tokenSeparators: [',', ' '],
templateResult: templateOption,
templateSelection: templateOption
});
});

View file

@ -17,7 +17,6 @@ import '../new_design/dropdown';
import '../new_design/form-validation'; import '../new_design/form-validation';
import '../new_design/procedure-context'; import '../new_design/procedure-context';
import '../new_design/procedure-form'; import '../new_design/procedure-form';
import '../new_design/select2';
import '../new_design/spinner'; import '../new_design/spinner';
import '../new_design/support'; import '../new_design/support';
import '../new_design/dossiers/auto-save'; import '../new_design/dossiers/auto-save';

View file

@ -679,6 +679,10 @@ class Dossier < ApplicationRecord
end end
end end
def log_modifier_annotation!(champ, instructeur)
log_dossier_operation(instructeur, :modifier_annotation, champ)
end
def demander_un_avis!(avis) def demander_un_avis!(avis)
log_dossier_operation(avis.claimant, :demander_un_avis, avis) log_dossier_operation(avis.claimant, :demander_un_avis, avis)
end end

View file

@ -14,15 +14,4 @@ class ExpertsProcedure < ApplicationRecord
belongs_to :procedure belongs_to :procedure
has_many :avis, dependent: :destroy has_many :avis, dependent: :destroy
def email_to_display
expert&.email
end
def self.invited_expert_emails(procedure)
joins(:expert)
.where(procedure: procedure)
.map(&:email_to_display)
.sort
end
end end

View file

@ -62,7 +62,7 @@ class DossierSearchService
def self.to_tsquery(search_terms) def self.to_tsquery(search_terms)
(search_terms || "") (search_terms || "")
.gsub(/['?\\:&|!<>\(\)]/, "") # drop disallowed characters .gsub(/['?\\:&|!<>()]/, "") # drop disallowed characters
.strip .strip
.split(/\s+/) # split words .split(/\s+/) # split words
.map { |x| "#{x}:*" } # enable prefix matching .map { |x| "#{x}:*" } # enable prefix matching

View file

@ -8,9 +8,8 @@
Le destinataire suivra automatiquement le dossier Le destinataire suivra automatiquement le dossier
= form_for dossier, url: send_to_instructeurs_instructeur_dossier_path(dossier.procedure, dossier), method: :post, html: { class: 'form recipients-form' } do |f| = form_for dossier, url: send_to_instructeurs_instructeur_dossier_path(dossier.procedure, dossier), method: :post, html: { class: 'form recipients-form' } do |f|
.flex.justify-start.align-start .flex.justify-start.align-start
= select_tag(:recipients, - hidden_field_id = SecureRandom.uuid
options_from_collection_for_select(potential_recipients, :id, :email), = hidden_field_tag :recipients, nil, data: { uuid: hidden_field_id }
multiple: true, = react_component("ComboMultipleDropdownList", options: potential_recipients.map{|r| [r.email, r.id]}, selected: [], disabled: [], hiddenFieldId: hidden_field_id, label: "email instructeur")
class: 'select2-limited',
placeholder: '')
= f.submit "Envoyer", class: "button large send gap-left" = f.submit "Envoyer", class: "button large send gap-left"

View file

@ -122,10 +122,10 @@
Personnaliser Personnaliser
#custom-menu.dropdown-content.fade-in-down #custom-menu.dropdown-content.fade-in-down
= form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form columns-form' do = form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form columns-form' do
= select_tag :values, - hidden_field_id = SecureRandom.uuid
options_for_select(@displayed_fields_options, selected: @displayed_fields_selected), = hidden_field_tag :values, nil, data: { uuid: hidden_field_id }
multiple: true, = react_component("ComboMultipleDropdownList", options: @displayed_fields_options, selected: @displayed_fields_selected, disabled: [], hiddenFieldId: hidden_field_id, label: 'colonne')
class: 'select2-limited'
= submit_tag "Enregistrer", class: 'button' = submit_tag "Enregistrer", class: 'button'
%tbody %tbody

View file

@ -25,12 +25,15 @@
.instructeur-wrapper .instructeur-wrapper
- if !@procedure.routee? - if !@procedure.routee?
%p.notice Entrez les adresses email des instructeurs que vous souhaitez affecter à cette démarche %p.notice Entrez les adresses email des instructeurs que vous souhaitez affecter à cette démarche
= select_tag :emails, - hidden_field_id = SecureRandom.uuid
options_for_select(@available_instructeur_emails), = hidden_field_tag :emails, nil, data: { uuid: hidden_field_id }
multiple: true, = react_component("ComboMultipleDropdownList",
class: 'select-instructeurs select2-limited' options: @available_instructeur_emails, selected: [], disabled: [],
hiddenFieldId: hidden_field_id,
label: 'email instructeur',
acceptNewValues: true)
= f.submit 'Affecter', class: 'button primary send' = f.submit 'Affecter', class: 'button primary send'
%table.table.mt-2 %table.table.mt-2
%thead %thead

View file

@ -1,16 +1,36 @@
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to('Démarches', admin_procedures_path),
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
'Liste des experts'] }
.container .container
%h1.mt-2 Experts invités sur #{@procedure.libelle} %h1.page-title.mt-2 Experts invités sur #{@procedure.libelle}
- if @invited_expert_emails.present? - if @experts_procedure.present?
%table.table.mt-2 %table.table.mt-2
%thead %thead
%tr %tr
%th Liste des experts %th Liste des experts
- if feature_enabled_for?(:make_experts_notifiable, @procedure)
%th Notifier des décisions sur les dossiers
%tbody %tbody
- @invited_expert_emails.each do |expert| - @experts_procedure.each do |expert_procedure|
%tr %tr
%td %td
%span.icon.person %span.icon.person
= expert = expert_procedure.expert.email
- if feature_enabled_for?(:make_experts_notifiable, @procedure)
%td
= form_for expert_procedure,
url: admin_procedure_update_allow_decision_access_path(expert_procedure: expert_procedure),
remote: true,
method: :put,
authenticity_token: true,
html: { class: 'form procedure-form__column--form no-background' } do |f|
%label.toggle-switch
= f.check_box :allow_decision_access, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()'
%span.toggle-switch-control.round
%span.toggle-switch-label.on
%span.toggle-switch-label.off
- else - else
.blank-tab .blank-tab
%h2.empty-text Aucun expert invité pour le moment. %h2.empty-text Aucun expert invité pour le moment.

View file

@ -167,7 +167,7 @@
%p.card-admin-subtitle Liste des experts invités par les instructeurs %p.card-admin-subtitle Liste des experts invités par les instructeurs
.card-admin-action .card-admin-action
= link_to "Voir", invited_expert_list_admin_procedure_path(@procedure), class: 'button' = link_to "Voir", admin_procedure_invited_expert_list_path(@procedure), class: 'button'
.card-admin .card-admin

View file

@ -7,10 +7,7 @@
= b.text = b.text
- else - else
= form.select :value, - hidden_field_id = SecureRandom.uuid
champ.options, = form.hidden_field :value, { data: { uuid: hidden_field_id } }
{ selected: champ.selected_options, = react_component("ComboMultipleDropdownList", options: champ.options, selected: champ.selected_options, disabled: champ.disabled_options, hiddenFieldId: hidden_field_id, label: champ.libelle)
disabled: champ.disabled_options },
multiple: true,
class: 'select2'

View file

@ -5,7 +5,7 @@ Rails.application.configure do
# test suite. You never need to work with it otherwise. Remember that # test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped # your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there! # and recreated between test runs. Don't rely on the data there!
config.cache_classes = true config.cache_classes = false
# Do not eager load code on boot. This avoids loading your whole application # Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that # just for the purpose of running a single test. If you are using a tool that

View file

@ -5,11 +5,10 @@ Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path # Add additional assets to the asset load path
Rails.application.config.assets.paths << Rails.root.join('node_modules', 'trix', 'dist') Rails.application.config.assets.paths << Rails.root.join('node_modules', 'trix', 'dist')
Rails.application.config.assets.paths << Rails.root.join('node_modules', 'select2', 'dist', 'css')
Rails.application.config.assets.paths << Rails.root.join('node_modules', 'mapbox-gl', 'dist') Rails.application.config.assets.paths << Rails.root.join('node_modules', 'mapbox-gl', 'dist')
Rails.application.config.assets.paths << Rails.root.join('node_modules', '@reach', 'combobox') Rails.application.config.assets.paths << Rails.root.join('node_modules', '@reach', 'combobox')
Rails.application.config.assets.paths << Rails.root.join('node_modules', '@mapbox', 'mapbox-gl-draw', 'dist') Rails.application.config.assets.paths << Rails.root.join('node_modules', '@mapbox', 'mapbox-gl-draw', 'dist')
# Precompile additional assets. # Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
Rails.application.config.assets.precompile += ['print.css', 'new_design/new_application.css', 'new_design/print.css', 'new_design/application.js', 'new_design/manager.css'] Rails.application.config.assets.precompile += ['print.css']

View file

@ -3,5 +3,5 @@ Sentry.init do |config|
config.send_default_pii = false config.send_default_pii = false
config.enabled_environments = ['production'] config.enabled_environments = ['production']
config.breadcrumbs_logger = [:active_support_logger] config.breadcrumbs_logger = [:active_support_logger]
config.traces_sample_rate = 0.01 config.traces_sample_rate = 0.001
end end

View file

@ -379,13 +379,14 @@ Rails.application.routes.draw do
get 'jeton' get 'jeton'
patch 'update_jeton' patch 'update_jeton'
put :allow_expert_review put :allow_expert_review
get 'invited_expert_list'
end end
get 'publication' => 'procedures#publication', as: :publication get 'publication' => 'procedures#publication', as: :publication
put 'publish' => 'procedures#publish', as: :publish put 'publish' => 'procedures#publish', as: :publish
get 'transfert' => 'procedures#transfert', as: :transfert get 'transfert' => 'procedures#transfert', as: :transfert
post 'transfer' => 'procedures#transfer', as: :transfer post 'transfer' => 'procedures#transfer', as: :transfer
get 'invited_expert_list'
put 'update_allow_decision_access' => 'procedures#update_allow_decision_access', as: :update_allow_decision_access
resources :mail_templates, only: [:edit, :update] resources :mail_templates, only: [:edit, :update]

View file

@ -36,6 +36,7 @@ default: &default
extensions: extensions:
- .mjs - .mjs
- .js - .js
- .jsx
- .sass - .sass
- .scss - .scss
- .css - .css

View file

@ -1,15 +1,15 @@
{ {
"dependencies": { "dependencies": {
"@babel/preset-react": "^7.10.4", "@babel/preset-react": "^7.12.13",
"@fortawesome/fontawesome-svg-core": "^1.2.28", "@fortawesome/fontawesome-svg-core": "^1.2.34",
"@fortawesome/free-solid-svg-icons": "^5.13.0", "@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/react-fontawesome": "^0.1.9", "@fortawesome/react-fontawesome": "^0.1.14",
"@mapbox/mapbox-gl-draw": "^1.2.0", "@mapbox/mapbox-gl-draw": "^1.2.0",
"@rails/actiontext": "^6.0.3", "@rails/actiontext": "^6.0.3",
"@rails/activestorage": "^6.0.3", "@rails/activestorage": "^6.0.3",
"@rails/ujs": "^6.0.3", "@rails/ujs": "^6.0.3",
"@rails/webpacker": "5.1.1", "@rails/webpacker": "5.1.1",
"@reach/combobox": "^0.10.2", "@reach/combobox": "^0.13.0",
"@sentry/browser": "^5.15.5", "@sentry/browser": "^5.15.5",
"@tmcw/togeojson": "^4.0.0", "@tmcw/togeojson": "^4.0.0",
"babel-plugin-macros": "^2.8.0", "babel-plugin-macros": "^2.8.0",
@ -20,23 +20,20 @@
"dom4": "^2.1.5", "dom4": "^2.1.5",
"email-butler": "^1.0.13", "email-butler": "^1.0.13",
"highcharts": "^8.1.1", "highcharts": "^8.1.1",
"intersection-observer": "^0.10.0", "intersection-observer": "^0.12.0",
"jquery": "^3.5.1",
"mapbox-gl": "^1.11.1", "mapbox-gl": "^1.11.1",
"match-sorter": "^4.2.1", "match-sorter": "^6.2.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.13.1", "react": "^17.0.1",
"react-dom": "^16.13.1", "react-dom": "^17.0.1",
"react-intersection-observer": "^8.26.2", "react-intersection-observer": "^8.31.0",
"react-mapbox-gl": "^4.8.6", "react-mapbox-gl": "^4.8.6",
"react-mapbox-gl-draw": "^2.0.4", "react-mapbox-gl-draw": "^2.0.4",
"react-query": "^2.23.1", "react-query": "^3.9.7",
"react-scroll-to-component": "^1.0.2",
"react-sortable-hoc": "^1.11.0", "react-sortable-hoc": "^1.11.0",
"react-use": "^15.3.4",
"react_ujs": "^2.6.1", "react_ujs": "^2.6.1",
"select2": "^4.0.13",
"trix": "^1.2.3", "trix": "^1.2.3",
"use-debounce": "^5.2.0",
"whatwg-fetch": "^3.0.0" "whatwg-fetch": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
@ -45,15 +42,15 @@
"eslint": "^7.0.0", "eslint": "^7.0.0",
"eslint-config-prettier": "^6.11.0", "eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3", "eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.20.0", "eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.0.2", "eslint-plugin-react-hooks": "^4.2.0",
"netlify-cli": "^2.61.2", "netlify-cli": "^2.61.2",
"prettier": "^2.0.5", "prettier": "^2.0.5",
"webpack-bundle-analyzer": "^3.7.0", "webpack-bundle-analyzer": "^3.7.0",
"webpack-dev-server": "^3.11.0" "webpack-dev-server": "^3.11.0"
}, },
"scripts": { "scripts": {
"lint:js": "eslint ./app/javascript ./config/webpack", "lint:js": "eslint --ext .js,.jsx,.ts,.tsx ./app/javascript ./config/webpack",
"webpack:build": "NODE_ENV=production bin/webpack", "webpack:build": "NODE_ENV=production bin/webpack",
"graphql:docs:build": "graphdoc --force", "graphql:docs:build": "graphdoc --force",
"graphql:docs:deploy": "netlify deploy -d ./docs/graphql --prod", "graphql:docs:deploy": "netlify deploy -d ./docs/graphql --prod",

View file

@ -1,11 +1,12 @@
describe API::V2::GraphqlController do describe API::V2::GraphqlController do
let(:admin) { create(:administrateur) } let(:admin) { create(:administrateur) }
let(:token) { admin.renew_api_token } let(:token) { admin.renew_api_token }
let(:procedure) { create(:procedure, :published, :for_individual, :with_service, :with_all_champs, administrateurs: [admin]) } let(:procedure) { create(:procedure, :published, :for_individual, :with_service, :with_all_champs, :with_all_annotations, administrateurs: [admin]) }
let(:dossier) do let(:dossier) do
dossier = create(:dossier, dossier = create(:dossier,
:en_construction, :en_construction,
:with_all_champs, :with_all_champs,
:with_all_annotations,
:with_individual, :with_individual,
procedure: procedure) procedure: procedure)
create(:commentaire, :with_file, dossier: dossier, email: 'test@test.com') create(:commentaire, :with_file, dossier: dossier, email: 'test@test.com')
@ -1117,6 +1118,240 @@ describe API::V2::GraphqlController do
end end
end end
end end
describe 'dossierModifierAnnotation' do
describe 'text' do
let(:query) do
"mutation {
dossierModifierAnnotationText(input: {
dossierId: \"#{dossier.to_typed_id}\",
annotationId: \"#{dossier.champs_private.first.to_typed_id}\",
instructeurId: \"#{instructeur.to_typed_id}\",
value: \"hello\"
}) {
annotation {
stringValue
}
errors {
message
}
}
}"
end
context "success" do
it 'should be a success' do
expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierModifierAnnotationText: {
annotation: {
stringValue: 'hello'
},
errors: nil
})
end
end
end
describe 'checkbox' do
let(:value) { 'true' }
let(:query) do
"mutation {
dossierModifierAnnotationCheckbox(input: {
dossierId: \"#{dossier.to_typed_id}\",
annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'checkbox' }.to_typed_id}\",
instructeurId: \"#{instructeur.to_typed_id}\",
value: #{value}
}) {
annotation {
stringValue
}
errors {
message
}
}
}"
end
context "success when true" do
it 'should be a success' do
expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierModifierAnnotationCheckbox: {
annotation: {
stringValue: 'true'
},
errors: nil
})
end
end
context "success when false" do
let(:value) { 'false' }
it 'should be a success' do
expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierModifierAnnotationCheckbox: {
annotation: {
stringValue: 'false'
},
errors: nil
})
end
end
end
describe 'yes_no' do
let(:value) { 'true' }
let(:query) do
"mutation {
dossierModifierAnnotationCheckbox(input: {
dossierId: \"#{dossier.to_typed_id}\",
annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'yes_no' }.to_typed_id}\",
instructeurId: \"#{instructeur.to_typed_id}\",
value: #{value}
}) {
annotation {
stringValue
}
errors {
message
}
}
}"
end
context "success when true" do
it 'should be a success' do
expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierModifierAnnotationCheckbox: {
annotation: {
stringValue: 'true'
},
errors: nil
})
end
end
context "success when false" do
let(:value) { 'false' }
it 'should be a success' do
expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierModifierAnnotationCheckbox: {
annotation: {
stringValue: 'false'
},
errors: nil
})
end
end
end
describe 'date' do
let(:query) do
"mutation {
dossierModifierAnnotationDate(input: {
dossierId: \"#{dossier.to_typed_id}\",
annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'date' }.to_typed_id}\",
instructeurId: \"#{instructeur.to_typed_id}\",
value: \"#{1.day.from_now.to_date.iso8601}\"
}) {
annotation {
stringValue
}
errors {
message
}
}
}"
end
context "success" do
it 'should be a success' do
expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierModifierAnnotationDate: {
annotation: {
stringValue: dossier.reload.champs_private.find { |c| c.type_champ == 'date' }.to_s
},
errors: nil
})
end
end
end
describe 'datetime' do
let(:query) do
"mutation {
dossierModifierAnnotationDatetime(input: {
dossierId: \"#{dossier.to_typed_id}\",
annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'datetime' }.to_typed_id}\",
instructeurId: \"#{instructeur.to_typed_id}\",
value: \"#{1.day.from_now.iso8601}\"
}) {
annotation {
stringValue
}
errors {
message
}
}
}"
end
context "success" do
it 'should be a success' do
expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierModifierAnnotationDatetime: {
annotation: {
stringValue: dossier.reload.champs_private.find { |c| c.type_champ == 'datetime' }.to_s
},
errors: nil
})
end
end
end
describe 'integer_number' do
let(:query) do
"mutation {
dossierModifierAnnotationIntegerNumber(input: {
dossierId: \"#{dossier.to_typed_id}\",
annotationId: \"#{dossier.champs_private.find { |c| c.type_champ == 'integer_number' }.to_typed_id}\",
instructeurId: \"#{instructeur.to_typed_id}\",
value: 42
}) {
annotation {
stringValue
}
errors {
message
}
}
}"
end
context "success" do
it 'should be a success' do
expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierModifierAnnotationIntegerNumber: {
annotation: {
stringValue: '42'
},
errors: nil
})
end
end
end
end
end end
end end

View file

@ -38,7 +38,7 @@ describe Instructeurs::DossiersController, type: :controller do
post( post(
:send_to_instructeurs, :send_to_instructeurs,
params: { params: {
recipients: [recipient], recipients: [recipient.id].to_json,
procedure_id: procedure.id, procedure_id: procedure.id,
dossier_id: dossier.id dossier_id: dossier.id
} }

View file

@ -207,11 +207,11 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
describe '#add_instructeur_procedure_non_routee' do describe '#add_instructeur_procedure_non_routee' do
let(:procedure) { create :procedure, administrateur: admin } let(:procedure) { create :procedure, administrateur: admin }
let(:emails) { ['instructeur_3@ministere_a.gouv.fr', 'instructeur_4@ministere_b.gouv.fr'] } let(:emails) { ['instructeur_3@ministere_a.gouv.fr', 'instructeur_4@ministere_b.gouv.fr'].to_json }
subject { post :add_instructeur, params: { emails: emails, procedure_id: procedure.id, id: gi_1_1.id } } subject { post :add_instructeur, params: { emails: emails, procedure_id: procedure.id, id: gi_1_1.id } }
context 'when all emails are valid' do context 'when all emails are valid' do
let(:emails) { ['test@b.gouv.fr', 'test2@b.gouv.fr'] } let(:emails) { ['test@b.gouv.fr', 'test2@b.gouv.fr'].to_json }
it { expect(response.status).to eq(200) } it { expect(response.status).to eq(200) }
it { expect(subject.request.flash[:alert]).to be_nil } it { expect(subject.request.flash[:alert]).to be_nil }
it { expect(subject.request.flash[:notice]).to be_present } it { expect(subject.request.flash[:notice]).to be_present }
@ -219,7 +219,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
end end
context 'when there is at least one bad email' do context 'when there is at least one bad email' do
let(:emails) { ['badmail', 'instructeur2@gmail.com'] } let(:emails) { ['badmail', 'instructeur2@gmail.com'].to_json }
it { expect(response.status).to eq(200) } it { expect(response.status).to eq(200) }
it { expect(subject.request.flash[:alert]).to be_present } it { expect(subject.request.flash[:alert]).to be_present }
it { expect(subject.request.flash[:notice]).to be_present } it { expect(subject.request.flash[:notice]).to be_present }
@ -227,7 +227,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
end end
context 'when the admin wants to assign an instructor who is already assigned on this procedure' do context 'when the admin wants to assign an instructor who is already assigned on this procedure' do
let(:emails) { ['instructeur_1@ministere_a.gouv.fr'] } let(:emails) { ['instructeur_1@ministere_a.gouv.fr'].to_json }
it { expect(subject.request.flash[:alert]).to be_present } it { expect(subject.request.flash[:alert]).to be_present }
it { expect(subject).to redirect_to admin_procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur) } it { expect(subject).to redirect_to admin_procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur) }
end end
@ -247,7 +247,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
params: { params: {
procedure_id: procedure.id, procedure_id: procedure.id,
id: gi_1_2.id, id: gi_1_2.id,
emails: new_instructeur_emails emails: new_instructeur_emails.to_json
} }
end end
@ -281,7 +281,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
end end
context 'of an empty string' do context 'of an empty string' do
let(:new_instructeur_emails) { '' } let(:new_instructeur_emails) { [''] }
it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_2)) } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_2)) }
end end

View file

@ -532,4 +532,34 @@ describe NewAdministrateur::ProceduresController, type: :controller do
it { expect(procedure.allow_expert_review).to be_truthy } it { expect(procedure.allow_expert_review).to be_truthy }
end end
end end
describe 'PUT #update_allow_decision_access' do
let!(:procedure) { create :procedure, :with_service, administrateur: admin }
let(:expert) { create(:expert) }
let(:expert_procedure) { ExpertsProcedure.create(procedure: procedure, expert: expert) }
subject do
put :update_allow_decision_access, params: { procedure_id: procedure.id, experts_procedure: { allow_decision_access: !expert_procedure.allow_decision_access }, expert_procedure: expert_procedure }, format: :js
end
context 'when the experts_procedure is true' do
let(:expert_procedure) { ExpertsProcedure.create(procedure: procedure, expert: expert, allow_decision_access: true) }
before do
subject
expert_procedure.reload
end
it { expect(expert_procedure.allow_decision_access).to be_falsy }
end
context 'when the experts_procedure is false' do
before do
subject
expert_procedure.reload
end
it { expect(expert_procedure.allow_decision_access).to be_truthy }
end
end
end end

View file

@ -216,8 +216,8 @@ FactoryBot.define do
trait :with_all_annotations do trait :with_all_annotations do
after(:create) do |dossier, _evaluator| after(:create) do |dossier, _evaluator|
dossier.champs = dossier.types_de_champ.map do |type_de_champ| dossier.champs_private = dossier.types_de_champ_private.map do |type_de_champ|
build(:"champ_#{type_de_champ.type_champ}", dossier: dossier, type_de_champ: type_de_champ) build(:"champ_#{type_de_champ.type_champ}", private: true, dossier: dossier, type_de_champ: type_de_champ)
end end
dossier.save! dossier.save!
end end

View file

@ -128,10 +128,8 @@ feature 'Instructing a dossier:' do
click_on 'Personnes impliquées' click_on 'Personnes impliquées'
first('.select2-container', minimum: 1).click select_multi('email instructeur', instructeur_2.email)
find('li.select2-results__option[role="option"]', text: instructeur_2.email).click select_multi('email instructeur', instructeur_3.email)
first('.select2-container', minimum: 1).click
find('li.select2-results__option[role="option"]', text: instructeur_3.email).click
click_on 'Envoyer' click_on 'Envoyer'

View file

@ -125,15 +125,13 @@ feature "procedure filters" do
def add_column(column_name) def add_column(column_name)
click_on 'Personnaliser' click_on 'Personnaliser'
find("span.select2-container").click select_multi('colonne', column_name)
find(:xpath, "//li[text()='#{column_name}']").click
click_button "Enregistrer" click_button "Enregistrer"
end end
def remove_column(column_name) def remove_column(column_name)
click_on 'Personnaliser' click_on 'Personnaliser'
find(:xpath, "//li[contains(@title, '#{column_name}')]/span[contains(text(), '×')]").click find(:xpath, "//li[contains(text(), '#{column_name}')]/span[contains(text(), 'x')]").click
find(:xpath, "//form[contains(@class, 'columns-form')]//span[contains(@class, 'select2-container')]").click
click_button "Enregistrer" click_button "Enregistrer"
end end
end end

View file

@ -30,14 +30,14 @@ feature 'The routing', js: true do
expect(page).to have_field('Nom du groupe', with: 'littéraire') expect(page).to have_field('Nom du groupe', with: 'littéraire')
# add victor to littéraire groupe # add victor to littéraire groupe
find('input.select2-search__field').send_keys('victor@inst.com', :enter) find("input[aria-label='email instructeur'").send_keys('victor@inst.com', :enter)
perform_enqueued_jobs { click_on 'Affecter' } perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche") expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche")
victor = User.find_by(email: 'victor@inst.com').instructeur victor = User.find_by(email: 'victor@inst.com').instructeur
# add superwoman to littéraire groupe # add superwoman to littéraire groupe
find('input.select2-search__field').send_keys('superwoman@inst.com', :enter) find("input[aria-label='email instructeur'").send_keys('superwoman@inst.com', :enter)
perform_enqueued_jobs { click_on 'Affecter' } perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche") expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche")
@ -50,14 +50,14 @@ feature 'The routing', js: true do
expect(page).to have_text('Le groupe dinstructeurs « scientifique » a été créé.') expect(page).to have_text('Le groupe dinstructeurs « scientifique » a été créé.')
# add marie to scientifique groupe # add marie to scientifique groupe
find('input.select2-search__field').send_keys('marie@inst.com', :enter) find("input[aria-label='email instructeur'").send_keys('marie@inst.com', :enter)
perform_enqueued_jobs { click_on 'Affecter' } perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur marie@inst.com a été affecté") expect(page).to have_text("Linstructeur marie@inst.com a été affecté")
marie = User.find_by(email: 'marie@inst.com').instructeur marie = User.find_by(email: 'marie@inst.com').instructeur
# add superwoman to scientifique groupe # add superwoman to scientifique groupe
find('input.select2-search__field').send_keys('superwoman@inst.com', :enter) find("input[aria-label='email instructeur'").send_keys('superwoman@inst.com', :enter)
perform_enqueued_jobs { click_on 'Affecter' } perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur superwoman@inst.com a été affecté") expect(page).to have_text("Linstructeur superwoman@inst.com a été affecté")

View file

@ -25,8 +25,9 @@ feature 'The user' do
check('val1') check('val1')
check('val3') check('val3')
select('bravo', from: form_id_for('simple_choice_drop_down_list_long')) select('bravo', from: form_id_for('simple_choice_drop_down_list_long'))
select('alpha', from: form_id_for('multiple_choice_drop_down_list_long')) select_multi('multiple_choice_drop_down_list_long', 'alpha')
select('charly', from: form_id_for('multiple_choice_drop_down_list_long')) select_multi('multiple_choice_drop_down_list_long', 'charly')
select_champ_geo('pays', 'aust', 'AUSTRALIE') select_champ_geo('pays', 'aust', 'AUSTRALIE')
select_champ_geo('regions', 'Ma', 'Martinique') select_champ_geo('regions', 'Ma', 'Martinique')
@ -83,7 +84,7 @@ feature 'The user' do
expect(page).to have_checked_field('val1') expect(page).to have_checked_field('val1')
expect(page).to have_checked_field('val3') expect(page).to have_checked_field('val3')
expect(page).to have_selected_value('simple_choice_drop_down_list_long', selected: 'bravo') expect(page).to have_selected_value('simple_choice_drop_down_list_long', selected: 'bravo')
expect(page).to have_selected_value('multiple_choice_drop_down_list_long', selected: ['alpha', 'charly']) check_selected_values('multiple_choice_drop_down_list_long', ['alpha', 'charly'])
expect(page).to have_hidden_field('pays', with: 'AUSTRALIE') expect(page).to have_hidden_field('pays', with: 'AUSTRALIE')
expect(page).to have_hidden_field('regions', with: 'Martinique') expect(page).to have_hidden_field('regions', with: 'Martinique')
expect(page).to have_hidden_field('departements', with: '02 - Aisne') expect(page).to have_hidden_field('departements', with: '02 - Aisne')

View file

@ -7,7 +7,7 @@ RSpec.describe ExpertsProcedure, type: :model do
let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) }
let(:experts_procedure2) { ExpertsProcedure.create(expert: expert2, procedure: procedure) } let(:experts_procedure2) { ExpertsProcedure.create(expert: expert2, procedure: procedure) }
let(:experts_procedure3) { ExpertsProcedure.create(expert: expert3, procedure: procedure) } let(:experts_procedure3) { ExpertsProcedure.create(expert: expert3, procedure: procedure) }
subject { ExpertsProcedure.invited_expert_emails(procedure) } subject { procedure.experts_procedures }
context 'when there is one dossier' do context 'when there is one dossier' do
let!(:dossier) { create(:dossier, procedure: procedure) } let!(:dossier) { create(:dossier, procedure: procedure) }
@ -15,14 +15,18 @@ RSpec.describe ExpertsProcedure, type: :model do
context 'when a procedure has one avis and known instructeur' do context 'when a procedure has one avis and known instructeur' do
let!(:avis) { create(:avis, dossier: dossier, instructeur: create(:instructeur, email: expert.email), experts_procedure: experts_procedure) } let!(:avis) { create(:avis, dossier: dossier, instructeur: create(:instructeur, email: expert.email), experts_procedure: experts_procedure) }
it { is_expected.to eq([expert.email]) } it { is_expected.to eq([experts_procedure]) }
it { expect(procedure.experts.count).to eq(1) }
it { expect(procedure.experts.first.email).to eq(expert.email) }
end end
context 'when a dossier has 2 avis from the same expert' do context 'when a dossier has 2 avis from the same expert' do
let!(:avis) { create(:avis, dossier: dossier, experts_procedure: experts_procedure) } let!(:avis) { create(:avis, dossier: dossier, experts_procedure: experts_procedure) }
let!(:avis2) { create(:avis, dossier: dossier, experts_procedure: experts_procedure) } let!(:avis2) { create(:avis, dossier: dossier, experts_procedure: experts_procedure) }
it { is_expected.to eq([expert.email]) } it { is_expected.to eq([experts_procedure]) }
it { expect(procedure.experts.count).to eq(1) }
it { expect(procedure.experts.first).to eq(expert) }
end end
end end
@ -35,7 +39,9 @@ RSpec.describe ExpertsProcedure, type: :model do
let!(:avis2) { create(:avis, dossier: dossier2, experts_procedure: experts_procedure2) } let!(:avis2) { create(:avis, dossier: dossier2, experts_procedure: experts_procedure2) }
let!(:avis3) { create(:avis, dossier: dossier2, experts_procedure: experts_procedure3) } let!(:avis3) { create(:avis, dossier: dossier2, experts_procedure: experts_procedure3) }
it { is_expected.to eq([expert.email, expert2.email, expert3.email].sort) } it { is_expected.to match_array([experts_procedure, experts_procedure2, experts_procedure3]) }
it { expect(procedure.experts.count).to eq(3) }
it { expect(procedure.experts).to match_array([expert, expert2, expert3]) }
end end
end end
end end

View file

@ -914,9 +914,11 @@ describe ProcedurePresentation do
it 'should downcase and transform value' do it 'should downcase and transform value' do
procedure_presentation.add_filter("suivis", "type_de_champ/#{first_type_de_champ_id}", "Oui") procedure_presentation.add_filter("suivis", "type_de_champ/#{first_type_de_champ_id}", "Oui")
expect(procedure_presentation.filters).to eq({ "suivis" => [ expect(procedure_presentation.filters).to eq({
{ "label" => first_type_de_champ.libelle, "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "true" } "suivis" =>
] [
{ "label" => first_type_de_champ.libelle, "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "true" }
]
}) })
end end
end end
@ -927,9 +929,10 @@ describe ProcedurePresentation do
it 'should passthrough value' do it 'should passthrough value' do
procedure_presentation.add_filter("suivis", "type_de_champ/#{first_type_de_champ_id}", "Oui") procedure_presentation.add_filter("suivis", "type_de_champ/#{first_type_de_champ_id}", "Oui")
expect(procedure_presentation.filters).to eq({ "suivis" => [ expect(procedure_presentation.filters).to eq({
{ "label" => first_type_de_champ.libelle, "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "Oui" } "suivis" => [
] { "label" => first_type_de_champ.libelle, "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "Oui" }
]
}) })
end end
end end

View file

@ -27,8 +27,8 @@ module FeatureHelpers
if sign_in_by_link if sign_in_by_link
mail = ActionMailer::Base.deliveries.last mail = ActionMailer::Base.deliveries.last
message = mail.html_part.body.raw_source message = mail.html_part.body.raw_source
instructeur_id = message[/\".+\/connexion-par-jeton\/(.+)\?jeton=(.*)\"/, 1] instructeur_id = message[/".+\/connexion-par-jeton\/(.+)\?jeton=(.*)"/, 1]
jeton = message[/\".+\/connexion-par-jeton\/(.+)\?jeton=(.*)\"/, 2] jeton = message[/".+\/connexion-par-jeton\/(.+)\?jeton=(.*)"/, 2]
visit sign_in_by_link_path(instructeur_id, jeton: jeton) visit sign_in_by_link_path(instructeur_id, jeton: jeton)
end end
@ -103,6 +103,25 @@ module FeatureHelpers
end end
end end
def select_multi(champ, with)
input = find("input[aria-label='#{champ}'")
input.click
# hack because for unknown reason, the click on input doesn't show combobox-popover with selenium driver
script = "document.evaluate(\"//input[@aria-label='#{champ}']//ancestor::div[@data-reach-combobox]/div[@data-reach-combobox-popover]\", document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null).iterateNext().removeAttribute(\"hidden\")"
execute_script(script)
element = find(:xpath, "//input[@aria-label='#{champ}']/ancestor::div[@data-reach-combobox]//div[@data-reach-combobox-popover]//li/span[normalize-space(text())='#{with}']")
element.click
end
def check_selected_values(champ, values)
combobox = find(:xpath, "//input[@aria-label='#{champ}']/ancestor::div[@data-react-class='ComboMultipleDropdownList']")
hiddenFieldId = JSON.parse(combobox["data-react-props"])["hiddenFieldId"]
hiddenField = find("input[data-uuid='#{hiddenFieldId}']")
expect(values.sort).to eq(JSON.parse(hiddenField.value).sort)
end
# Keep the brower window open after a test success of failure, to # Keep the brower window open after a test success of failure, to
# allow inspecting the page or the console. # allow inspecting the page or the console.
# #

View file

@ -13,7 +13,7 @@ describe 'instructeurs/dossiers/envoyer_dossier_block.html.haml', type: :view do
let(:instructeur) { create(:instructeur, email: 'yop@totomail.fr') } let(:instructeur) { create(:instructeur, email: 'yop@totomail.fr') }
let(:potential_recipients) { [instructeur] } let(:potential_recipients) { [instructeur] }
it { is_expected.to have_css("select > option[value='#{instructeur.id}']") } it { is_expected.to match(/data-react-props.*#{instructeur.email}/) }
it { is_expected.to have_css(".button.send") } it { is_expected.to have_css(".button.send") }
end end

View file

@ -12,13 +12,13 @@ describe 'new_administrateur/procedures/invited_expert_list.html.haml', type: :v
context 'when the procedure has 0 avis' do context 'when the procedure has 0 avis' do
let!(:dossier) { create(:dossier, procedure: procedure) } let!(:dossier) { create(:dossier, procedure: procedure) }
before do before do
@invited_expert_emails = ExpertsProcedure.invited_expert_emails(procedure) @invited_experts = procedure.experts_procedures
subject subject
end end
it 'has 0 experts into the page' do it 'has 0 experts into the page' do
expect(@invited_expert_emails.count).to eq(0) expect(@invited_experts.count).to eq(0)
expect(@invited_expert_emails).to eq([]) expect(@invited_experts).to eq([])
end end
end end
@ -32,13 +32,13 @@ describe 'new_administrateur/procedures/invited_expert_list.html.haml', type: :v
let!(:avis2) { create(:avis, dossier: dossier, experts_procedure: experts_procedure2) } let!(:avis2) { create(:avis, dossier: dossier, experts_procedure: experts_procedure2) }
before do before do
@invited_expert_emails = ExpertsProcedure.invited_expert_emails(procedure) @invited_experts = procedure.experts_procedures
subject subject
end end
it 'has 2 experts and match array' do it 'has 2 experts and match array' do
expect(@invited_expert_emails.count).to eq(2) expect(@invited_experts.count).to eq(2)
expect(@invited_expert_emails).to eq([expert.email, expert2.email].sort) expect(@invited_experts).to match_array([experts_procedure, experts_procedure2])
end end
end end
end end

View file

@ -82,7 +82,7 @@ describe 'shared/dossiers/edit.html.haml', type: :view do
let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list, :long, procedure: dossier.procedure) } let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list, :long, procedure: dossier.procedure) }
it 'renders the list as a multiple-selection dropdown' do it 'renders the list as a multiple-selection dropdown' do
expect(subject).to have_selector('select.select2') expect(subject).to have_selector('[data-react-class="ComboMultipleDropdownList"]')
end end
end end
end end

814
yarn.lock

File diff suppressed because it is too large Load diff