commit
7cdf71f1c5
88 changed files with 1855 additions and 1000 deletions
2
.github/workflows/sentry-release.yml
vendored
2
.github/workflows/sentry-release.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Sentry Release
|
||||
uses: getsentry/action-release@v1.0.0
|
||||
uses: getsentry/action-release@v1
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
|
|
|
@ -1320,6 +1320,8 @@ Rails/ActiveRecordCallbacksOrder:
|
|||
Enabled: false
|
||||
Rails/AfterCommitOverride:
|
||||
Enabled: true
|
||||
Rails/AttributeDefaultBlockValue:
|
||||
Enabled: true
|
||||
Rails/FindById:
|
||||
Enabled: true
|
||||
Rails/Inquiry:
|
||||
|
@ -1342,6 +1344,8 @@ Rails/ShortI18n:
|
|||
Enabled: true
|
||||
Rails/SquishedSQLHeredocs:
|
||||
Enabled: true
|
||||
Rails/WhereEquals:
|
||||
Enabled: true
|
||||
Rails/WhereExists:
|
||||
Enabled: true
|
||||
Rails/WhereNot:
|
||||
|
|
376
Gemfile.lock
376
Gemfile.lock
|
@ -22,38 +22,38 @@ GEM
|
|||
specs:
|
||||
aasm (5.1.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
actioncable (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
actioncable (6.0.3.5)
|
||||
actionpack (= 6.0.3.5)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
activejob (= 6.0.3.4)
|
||||
activerecord (= 6.0.3.4)
|
||||
activestorage (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
actionmailbox (6.0.3.5)
|
||||
actionpack (= 6.0.3.5)
|
||||
activejob (= 6.0.3.5)
|
||||
activerecord (= 6.0.3.5)
|
||||
activestorage (= 6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
actionview (= 6.0.3.4)
|
||||
activejob (= 6.0.3.4)
|
||||
actionmailer (6.0.3.5)
|
||||
actionpack (= 6.0.3.5)
|
||||
actionview (= 6.0.3.5)
|
||||
activejob (= 6.0.3.5)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.3.4)
|
||||
actionview (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
actionpack (6.0.3.5)
|
||||
actionview (= 6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
activerecord (= 6.0.3.4)
|
||||
activestorage (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
actiontext (6.0.3.5)
|
||||
actionpack (= 6.0.3.5)
|
||||
activerecord (= 6.0.3.5)
|
||||
activestorage (= 6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
actionview (6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -61,32 +61,31 @@ GEM
|
|||
active_link_to (1.0.5)
|
||||
actionpack
|
||||
addressable
|
||||
active_model_serializers (0.10.10)
|
||||
actionpack (>= 4.1, < 6.1)
|
||||
activemodel (>= 4.1, < 6.1)
|
||||
active_model_serializers (0.10.12)
|
||||
actionpack (>= 4.1, < 6.2)
|
||||
activemodel (>= 4.1, < 6.2)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
active_storage_validations (0.9.0)
|
||||
active_storage_validations (0.9.2)
|
||||
rails (>= 5.2.0)
|
||||
activejob (6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
activejob (6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
activerecord (6.0.3.4)
|
||||
activemodel (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
activestorage (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
activejob (= 6.0.3.4)
|
||||
activerecord (= 6.0.3.4)
|
||||
activemodel (6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
activerecord (6.0.3.5)
|
||||
activemodel (= 6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
activestorage (6.0.3.5)
|
||||
actionpack (= 6.0.3.5)
|
||||
activejob (= 6.0.3.5)
|
||||
activerecord (= 6.0.3.5)
|
||||
marcel (~> 0.3.1)
|
||||
activestorage-openstack (1.4.1)
|
||||
activestorage-openstack (1.5.1)
|
||||
fog-openstack (~> 1.0)
|
||||
marcel
|
||||
mime-types
|
||||
rails (>= 5.2.2)
|
||||
activesupport (6.0.3.4)
|
||||
activesupport (6.0.3.5)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
|
@ -111,11 +110,11 @@ GEM
|
|||
annotate (3.1.1)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.1)
|
||||
ast (2.4.2)
|
||||
attr_encrypted (3.1.0)
|
||||
encryptor (~> 3.0.0)
|
||||
attr_required (1.0.1)
|
||||
autoprefixer-rails (10.0.1.0)
|
||||
autoprefixer-rails (10.2.4.0)
|
||||
execjs
|
||||
axe-matchers (2.6.1)
|
||||
dumb_delegator (~> 0.8)
|
||||
|
@ -124,7 +123,7 @@ GEM
|
|||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
axlsx_styler (1.0.0)
|
||||
axlsx_styler (1.1.0)
|
||||
activesupport (>= 3.1)
|
||||
caxlsx (>= 2.0.2)
|
||||
babel-source (5.8.35)
|
||||
|
@ -134,22 +133,22 @@ GEM
|
|||
bcrypt (3.1.16)
|
||||
bindata (2.4.8)
|
||||
bindex (0.8.1)
|
||||
brakeman (4.9.1)
|
||||
browser (5.0.0)
|
||||
brakeman (5.0.0)
|
||||
browser (5.3.0)
|
||||
builder (3.2.4)
|
||||
byebug (11.1.3)
|
||||
capybara (3.33.0)
|
||||
capybara (3.35.3)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (~> 1.5)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
capybara-email (3.0.2)
|
||||
capybara (>= 2.4, < 4.0)
|
||||
mail
|
||||
capybara-screenshot (1.0.24)
|
||||
capybara-screenshot (1.0.25)
|
||||
capybara (>= 1.0, < 4)
|
||||
launchy
|
||||
capybara-selenium (0.0.6)
|
||||
|
@ -157,15 +156,15 @@ GEM
|
|||
selenium-webdriver
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
caxlsx (3.0.2)
|
||||
caxlsx (3.0.4)
|
||||
htmlentities (~> 4.3, >= 4.3.4)
|
||||
mimemagic (~> 0.3)
|
||||
nokogiri (~> 1.10, >= 1.10.4)
|
||||
rubyzip (>= 1.3.0, < 3)
|
||||
chartkick (3.4.0)
|
||||
chartkick (3.4.2)
|
||||
childprocess (3.0.0)
|
||||
choice (0.2.0)
|
||||
chunky_png (1.3.11)
|
||||
chunky_png (1.4.0)
|
||||
clamav-client (3.2.0)
|
||||
coderay (1.1.3)
|
||||
coercible (1.0.0)
|
||||
|
@ -179,22 +178,28 @@ GEM
|
|||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.1.8)
|
||||
connection_pool (2.2.3)
|
||||
crack (0.4.4)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (1.7.1)
|
||||
css_parser (1.9.0)
|
||||
addressable
|
||||
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)
|
||||
momentjs-rails (>= 2.8.1)
|
||||
deep_cloneable (3.0.0)
|
||||
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.8)
|
||||
activesupport (>= 3.0, < 6.1)
|
||||
delayed_job_active_record (4.1.4)
|
||||
activerecord (>= 3.0, < 6.1)
|
||||
delayed_job (4.1.9)
|
||||
activesupport (>= 3.0, < 6.2)
|
||||
delayed_job_active_record (4.1.5)
|
||||
activerecord (>= 3.0, < 6.2)
|
||||
delayed_job (>= 3.0, < 5)
|
||||
delayed_job_web (1.4.3)
|
||||
activerecord (> 3.0.0)
|
||||
|
@ -225,9 +230,9 @@ GEM
|
|||
railties (>= 3.2)
|
||||
dry-inflector (0.2.0)
|
||||
dumb_delegator (0.8.1)
|
||||
ecma-re-validator (0.2.1)
|
||||
regexp_parser (~> 1.2)
|
||||
em-websocket (0.5.1)
|
||||
ecma-re-validator (0.3.0)
|
||||
regexp_parser (~> 2.0)
|
||||
em-websocket (0.5.2)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
encryptor (3.0.0)
|
||||
|
@ -239,28 +244,28 @@ GEM
|
|||
ethon (0.12.0)
|
||||
ffi (>= 1.3.0)
|
||||
eventmachine (1.2.7)
|
||||
excon (0.76.0)
|
||||
excon (0.79.0)
|
||||
execjs (2.7.0)
|
||||
factory_bot (5.1.2)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot (6.1.0)
|
||||
activesupport (>= 5.0.0)
|
||||
faraday (1.3.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ruby2_keywords
|
||||
faraday-net_http (1.0.1)
|
||||
ffi (1.13.1)
|
||||
ffi (1.14.2)
|
||||
ffi-geos (2.1.0)
|
||||
ffi (>= 1.0.0)
|
||||
flipper (0.19.0)
|
||||
flipper-active_record (0.19.0)
|
||||
flipper (0.20.3)
|
||||
flipper-active_record (0.20.3)
|
||||
activerecord (>= 5.0, < 7)
|
||||
flipper (~> 0.19.0)
|
||||
flipper-ui (0.19.0)
|
||||
flipper (~> 0.20.3)
|
||||
flipper-ui (0.20.3)
|
||||
erubi (>= 1.0.0, < 2.0.0)
|
||||
flipper (~> 0.19.0)
|
||||
flipper (~> 0.20.3)
|
||||
rack (>= 1.4, < 3)
|
||||
rack-protection (>= 1.5.3, < 2.1.0)
|
||||
fog-core (2.2.0)
|
||||
rack-protection (>= 1.5.3, < 2.2.0)
|
||||
fog-core (2.2.3)
|
||||
builder
|
||||
excon (~> 0.71)
|
||||
formatador (~> 0.2)
|
||||
|
@ -268,16 +273,16 @@ GEM
|
|||
fog-json (1.2.0)
|
||||
fog-core
|
||||
multi_json (~> 1.10)
|
||||
fog-openstack (1.0.10)
|
||||
fog-openstack (1.0.11)
|
||||
fog-core (~> 2.1)
|
||||
fog-json (>= 1.0)
|
||||
ipaddress (>= 0.8)
|
||||
formatador (0.2.5)
|
||||
fugit (1.3.4)
|
||||
fugit (1.4.2)
|
||||
et-orbi (~> 1.1, >= 1.1.8)
|
||||
raabro (~> 1.1)
|
||||
geo_coord (0.1.0)
|
||||
geocoder (1.6.3)
|
||||
raabro (~> 1.4)
|
||||
geo_coord (0.2.0)
|
||||
geocoder (1.6.5)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
gon (6.4.0)
|
||||
|
@ -285,11 +290,11 @@ GEM
|
|||
i18n (>= 0.7)
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
graphql (1.10.9)
|
||||
graphql (1.12.4)
|
||||
graphql-batch (0.4.3)
|
||||
graphql (>= 1.3, < 2)
|
||||
promise.rb (~> 0.7.2)
|
||||
graphql-rails_logger (1.2.2)
|
||||
graphql-rails_logger (1.2.3)
|
||||
actionpack (> 5.0)
|
||||
activesupport (> 5.0)
|
||||
railties (> 5.0)
|
||||
|
@ -300,7 +305,7 @@ GEM
|
|||
thor (>= 0.19, < 2.0)
|
||||
graphql_playground-rails (2.1.0)
|
||||
rails (>= 5.1.0)
|
||||
groupdate (5.2.1)
|
||||
groupdate (5.2.2)
|
||||
activesupport (>= 5)
|
||||
guard (2.16.2)
|
||||
formatador (>= 0.2.4)
|
||||
|
@ -321,7 +326,7 @@ GEM
|
|||
guard (~> 2.1)
|
||||
guard-compat (~> 1.1)
|
||||
rspec (>= 2.99.0, < 4.0)
|
||||
haml (5.1.2)
|
||||
haml (5.2.1)
|
||||
temple (>= 0.8.0)
|
||||
tilt
|
||||
haml-lint (0.999.999)
|
||||
|
@ -332,8 +337,9 @@ GEM
|
|||
haml (>= 4.0.6, < 6.0)
|
||||
html2haml (>= 1.0.1)
|
||||
railties (>= 5.1)
|
||||
haml_lint (0.35.0)
|
||||
haml (>= 4.0, < 5.2)
|
||||
haml_lint (0.37.0)
|
||||
haml (>= 4.0, < 5.3)
|
||||
parallel (~> 1.10)
|
||||
rainbow
|
||||
rubocop (>= 0.50.0)
|
||||
sysexits (~> 1.1)
|
||||
|
@ -353,7 +359,7 @@ GEM
|
|||
http_accept_language (2.1.1)
|
||||
http_parser.rb (0.6.0)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.7)
|
||||
i18n (1.8.9)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-tasks (0.9.33)
|
||||
activesupport (>= 4.0.2)
|
||||
|
@ -375,15 +381,15 @@ GEM
|
|||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.3.1)
|
||||
json (2.5.1)
|
||||
json-jwt (1.13.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
json_schemer (0.2.16)
|
||||
ecma-re-validator (~> 0.2)
|
||||
json_schemer (0.2.17)
|
||||
ecma-re-validator (~> 0.3)
|
||||
hana (~> 1.3)
|
||||
regexp_parser (~> 1.5)
|
||||
regexp_parser (~> 2.0)
|
||||
uri_template (~> 0.7)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.2.2)
|
||||
|
@ -407,7 +413,7 @@ GEM
|
|||
actionmailer (>= 3.2)
|
||||
letter_opener (~> 1.0)
|
||||
railties (>= 3.2)
|
||||
listen (3.2.1)
|
||||
listen (3.4.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
lograge (0.11.2)
|
||||
|
@ -419,19 +425,19 @@ GEM
|
|||
loofah (2.9.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
lumberjack (1.2.4)
|
||||
lumberjack (1.2.8)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
mailjet (1.5.4)
|
||||
mailjet (1.6.0)
|
||||
activesupport (>= 3.1.0)
|
||||
rack (>= 1.4.0)
|
||||
rest-client
|
||||
rest-client (>= 2.0.0)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2020.0512)
|
||||
mime-types-data (3.2021.0212)
|
||||
mimemagic (0.3.5)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.0.2)
|
||||
|
@ -445,7 +451,7 @@ GEM
|
|||
ruby2_keywords (~> 0.0.1)
|
||||
nenv (0.3.0)
|
||||
netrc (0.11.0)
|
||||
nio4r (2.5.4)
|
||||
nio4r (2.5.5)
|
||||
nokogiri (1.11.1)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
racc (~> 1.4)
|
||||
|
@ -453,7 +459,7 @@ GEM
|
|||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
open4 (1.3.4)
|
||||
openid_connect (1.1.8)
|
||||
openid_connect (1.2.0)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
json-jwt (>= 1.5.0)
|
||||
|
@ -464,25 +470,25 @@ GEM
|
|||
validate_url
|
||||
webfinger (>= 1.0.1)
|
||||
orm_adapter (0.5.0)
|
||||
parallel (1.19.2)
|
||||
parser (2.7.1.5)
|
||||
parallel (1.20.1)
|
||||
parser (3.0.0.0)
|
||||
ast (~> 2.4.1)
|
||||
pdf-core (0.7.0)
|
||||
pdf-core (0.9.0)
|
||||
pg (1.2.3)
|
||||
phonelib (0.6.45)
|
||||
prawn (2.2.2)
|
||||
pdf-core (~> 0.7.0)
|
||||
ttfunk (~> 1.5)
|
||||
phonelib (0.6.48)
|
||||
prawn (2.4.0)
|
||||
pdf-core (~> 0.9.0)
|
||||
ttfunk (~> 1.7)
|
||||
prawn-rails (1.3.0)
|
||||
prawn
|
||||
prawn-table
|
||||
rails (>= 3.1.0)
|
||||
prawn-svg (0.30.0)
|
||||
prawn-svg (0.31.0)
|
||||
css_parser (~> 1.6)
|
||||
prawn (>= 0.11.1, < 3)
|
||||
prawn-table (0.2.2)
|
||||
prawn (>= 1.3.0, < 3.0.0)
|
||||
premailer (1.14.1)
|
||||
premailer (1.14.2)
|
||||
addressable
|
||||
css_parser (>= 1.6.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
|
@ -497,43 +503,43 @@ GEM
|
|||
byebug (~> 11.0)
|
||||
pry (~> 0.13.0)
|
||||
public_suffix (4.0.6)
|
||||
puma (5.0.2)
|
||||
puma (5.2.1)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.1.6)
|
||||
raabro (1.4.0)
|
||||
racc (1.5.2)
|
||||
rack (2.2.3)
|
||||
rack-attack (6.3.1)
|
||||
rack-attack (6.5.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-mini-profiler (2.1.0)
|
||||
rack-mini-profiler (2.3.1)
|
||||
rack (>= 1.2.0)
|
||||
rack-oauth2 (1.15.0)
|
||||
rack-oauth2 (1.16.0)
|
||||
activesupport
|
||||
attr_required
|
||||
httpclient
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-protection (2.0.8.1)
|
||||
rack-protection (2.1.0)
|
||||
rack
|
||||
rack-proxy (0.6.5)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.3.4)
|
||||
actioncable (= 6.0.3.4)
|
||||
actionmailbox (= 6.0.3.4)
|
||||
actionmailer (= 6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
actiontext (= 6.0.3.4)
|
||||
actionview (= 6.0.3.4)
|
||||
activejob (= 6.0.3.4)
|
||||
activemodel (= 6.0.3.4)
|
||||
activerecord (= 6.0.3.4)
|
||||
activestorage (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
rails (6.0.3.5)
|
||||
actioncable (= 6.0.3.5)
|
||||
actionmailbox (= 6.0.3.5)
|
||||
actionmailer (= 6.0.3.5)
|
||||
actionpack (= 6.0.3.5)
|
||||
actiontext (= 6.0.3.5)
|
||||
actionview (= 6.0.3.5)
|
||||
activejob (= 6.0.3.5)
|
||||
activemodel (= 6.0.3.5)
|
||||
activerecord (= 6.0.3.5)
|
||||
activestorage (= 6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.3.4)
|
||||
railties (= 6.0.3.5)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -552,9 +558,9 @@ GEM
|
|||
rails-i18n (6.0.0)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 7)
|
||||
railties (6.0.3.4)
|
||||
actionpack (= 6.0.3.4)
|
||||
activesupport (= 6.0.3.4)
|
||||
railties (6.0.3.5)
|
||||
actionpack (= 6.0.3.5)
|
||||
activesupport (= 6.0.3.5)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
|
@ -570,7 +576,7 @@ GEM
|
|||
execjs
|
||||
railties (>= 3.2)
|
||||
tilt
|
||||
regexp_parser (1.8.0)
|
||||
regexp_parser (2.0.3)
|
||||
request_store (1.5.0)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.1)
|
||||
|
@ -582,7 +588,7 @@ GEM
|
|||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
rexml (3.2.4)
|
||||
rgeo (2.1.1)
|
||||
rgeo (2.2.0)
|
||||
rgeo-geojson (2.1.1)
|
||||
rgeo (>= 1.0.0)
|
||||
rodf (1.1.1)
|
||||
|
@ -591,56 +597,59 @@ GEM
|
|||
rubyzip (>= 1.0)
|
||||
rotp (4.1.0)
|
||||
addressable (~> 2.5)
|
||||
rouge (3.17.0)
|
||||
rqrcode (1.1.2)
|
||||
rouge (3.26.0)
|
||||
rqrcode (1.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 0.1)
|
||||
rqrcode_core (0.1.2)
|
||||
rspec (3.9.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
rspec-mocks (~> 3.9.0)
|
||||
rspec-core (3.9.2)
|
||||
rspec-support (~> 3.9.3)
|
||||
rspec-expectations (3.9.2)
|
||||
rqrcode_core (~> 0.2)
|
||||
rqrcode_core (0.2.0)
|
||||
rspec (3.10.0)
|
||||
rspec-core (~> 3.10.0)
|
||||
rspec-expectations (~> 3.10.0)
|
||||
rspec-mocks (~> 3.10.0)
|
||||
rspec-core (3.10.1)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-expectations (3.10.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.1)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-mocks (3.10.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (4.0.1)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-rails (4.0.2)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-core (~> 3.9)
|
||||
rspec-expectations (~> 3.9)
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-support (3.9.3)
|
||||
rspec-core (~> 3.10)
|
||||
rspec-expectations (~> 3.10)
|
||||
rspec-mocks (~> 3.10)
|
||||
rspec-support (~> 3.10)
|
||||
rspec-support (3.10.2)
|
||||
rspec_junit_formatter (0.4.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rubocop (0.92.0)
|
||||
rubocop (1.10.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.1.5)
|
||||
parser (>= 3.0.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.7)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml
|
||||
rubocop-ast (>= 0.5.0)
|
||||
rubocop-ast (>= 1.2.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-ast (0.7.0)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.4.1)
|
||||
parser (>= 2.7.1.5)
|
||||
strscan (>= 1.0.0)
|
||||
rubocop-performance (1.8.1)
|
||||
rubocop (>= 0.87.0)
|
||||
rubocop-packaging (0.5.1)
|
||||
rubocop (>= 0.89, < 2.0)
|
||||
rubocop-performance (1.9.2)
|
||||
rubocop (>= 0.90.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.8.1)
|
||||
rubocop-rails (2.9.1)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.87.0)
|
||||
rubocop-rails_config (0.12.6)
|
||||
rubocop (>= 0.90.0, < 2.0)
|
||||
rubocop-rails_config (1.3.0)
|
||||
railties (>= 5.0)
|
||||
rubocop (~> 0.82)
|
||||
rubocop (>= 1.8)
|
||||
rubocop-ast (>= 1.0.1)
|
||||
rubocop-packaging (~> 0.5)
|
||||
rubocop-performance (~> 1.3)
|
||||
rubocop-rails (~> 2.0)
|
||||
rubocop-rspec-focused (1.0.0)
|
||||
|
@ -649,12 +658,12 @@ GEM
|
|||
rake (>= 0.8.1)
|
||||
ruby-graphviz (1.2.5)
|
||||
rexml
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-saml-idp (0.3.5)
|
||||
ruby-vips (2.0.17)
|
||||
ffi (~> 1.9)
|
||||
ruby2_keywords (0.0.4)
|
||||
ruby_parser (3.15.0)
|
||||
ruby_parser (3.15.1)
|
||||
sexp_processor (~> 4.9)
|
||||
rubyzip (2.3.0)
|
||||
sanitize-url (0.1.4)
|
||||
|
@ -677,7 +686,7 @@ GEM
|
|||
selenium-webdriver (3.142.7)
|
||||
childprocess (>= 0.5, < 4.0)
|
||||
rubyzip (>= 1.2.2)
|
||||
semantic_range (2.3.0)
|
||||
semantic_range (2.3.1)
|
||||
sentry-delayed_job (4.2.0)
|
||||
sentry-ruby-core (~> 4.2.0)
|
||||
sentry-rails (4.2.1)
|
||||
|
@ -690,31 +699,31 @@ GEM
|
|||
sentry-ruby-core (4.2.1)
|
||||
concurrent-ruby
|
||||
faraday
|
||||
sexp_processor (4.15.1)
|
||||
sexp_processor (4.15.2)
|
||||
shellany (0.0.1)
|
||||
shoulda-matchers (4.4.1)
|
||||
shoulda-matchers (4.5.1)
|
||||
activesupport (>= 4.2.0)
|
||||
sib-api-v3-sdk (7.0.0)
|
||||
sib-api-v3-sdk (7.4.0)
|
||||
json (~> 2.1, >= 2.1.0)
|
||||
typhoeus (~> 1.0, >= 1.0.1)
|
||||
simple_xlsx_reader (1.0.4)
|
||||
nokogiri
|
||||
rubyzip
|
||||
sinatra (2.0.8.1)
|
||||
sinatra (2.1.0)
|
||||
mustermann (~> 1.0)
|
||||
rack (~> 2.0)
|
||||
rack-protection (= 2.0.8.1)
|
||||
rack (~> 2.2)
|
||||
rack-protection (= 2.1.0)
|
||||
tilt (~> 2.0)
|
||||
skylight (4.3.1)
|
||||
skylight-core (= 4.3.1)
|
||||
skylight-core (4.3.1)
|
||||
skylight (4.3.2)
|
||||
skylight-core (= 4.3.2)
|
||||
skylight-core (4.3.2)
|
||||
activesupport (>= 4.2.0)
|
||||
smart_listing (1.2.3)
|
||||
coffee-rails
|
||||
jquery-rails
|
||||
kaminari (>= 0.17)
|
||||
rails (>= 3.2)
|
||||
spreadsheet_architect (4.0.0)
|
||||
spreadsheet_architect (4.1.0)
|
||||
axlsx_styler (>= 1.0.0, < 2)
|
||||
caxlsx (>= 2.0.2, < 4)
|
||||
rodf (>= 1.0.0, < 2)
|
||||
|
@ -728,8 +737,7 @@ GEM
|
|||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
strscan (1.0.3)
|
||||
swd (1.1.2)
|
||||
swd (1.2.0)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
httpclient (>= 2.4)
|
||||
|
@ -740,8 +748,8 @@ GEM
|
|||
thor (1.1.0)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.10)
|
||||
timecop (0.9.1)
|
||||
ttfunk (1.6.2.1)
|
||||
timecop (0.9.4)
|
||||
ttfunk (1.7.0)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.9)
|
||||
|
@ -754,7 +762,7 @@ GEM
|
|||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
validate_url (1.0.11)
|
||||
validate_url (1.0.13)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
vcr (6.0.0)
|
||||
|
@ -765,19 +773,19 @@ GEM
|
|||
equalizer (~> 0.0, >= 0.0.9)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.0.4)
|
||||
web-console (4.1.0)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webdrivers (4.4.1)
|
||||
webdrivers (4.5.0)
|
||||
nokogiri (~> 1.6)
|
||||
rubyzip (>= 1.3.0)
|
||||
selenium-webdriver (>= 3.0, < 4.0)
|
||||
webfinger (1.1.0)
|
||||
activesupport
|
||||
httpclient (>= 2.4)
|
||||
webmock (3.9.1)
|
||||
webmock (3.11.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
|
@ -798,7 +806,7 @@ GEM
|
|||
zipline (1.3.0)
|
||||
actionpack (>= 3.2.1, < 7.0)
|
||||
zip_tricks (>= 4.2.1, < 6.0)
|
||||
zxcvbn-ruby (1.1.0)
|
||||
zxcvbn-ruby (1.2.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
|
10
README.md
10
README.md
|
@ -128,8 +128,14 @@ Le projet utilise plusieurs linters pour vérifier la lisibilité et la qualité
|
|||
|
||||
## Déploiement
|
||||
|
||||
- Tout nouveau commit ajouté à la branche `dev` est automatiquement déployé [en intégration](https://dev.demarches-simplifiees.fr/)
|
||||
- Tout nouveau commit ajouté à la branche `master` est automatiquement déployé [en production](https://www.demarches-simplifiees.fr/)
|
||||
Dans le cas d’un déploiement sur plusieurs serveurs, l’application peut être déployée avec la tâche :
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
|
|
|
@ -5,34 +5,4 @@
|
|||
.select-instructeurs {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,5 @@
|
|||
// = require ./utils
|
||||
// = require ./fonts
|
||||
// = require leaflet
|
||||
// = require select2
|
||||
// = require_tree .
|
||||
// = stub ./print.scss
|
||||
|
|
|
@ -281,18 +281,6 @@
|
|||
.dropdown-form {
|
||||
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 {
|
||||
width: 340px;
|
||||
}
|
||||
|
@ -310,10 +298,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.select2-dropdown {
|
||||
border: 1px solid $border-grey;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: $blue;
|
||||
}
|
||||
|
|
|
@ -260,8 +260,7 @@
|
|||
max-width: 180px;
|
||||
}
|
||||
|
||||
select,
|
||||
.select2-selection {
|
||||
select {
|
||||
// hack found here: https://stackoverflow.com/questions/1895476/how-to-style-a-select-dropdown-with-css-only-without-javascript
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
|
@ -305,39 +304,26 @@
|
|||
border-color: $blue;
|
||||
}
|
||||
|
||||
.select2 {
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
.select2-container {
|
||||
display: block;
|
||||
margin-bottom: $default-fields-spacer;
|
||||
|
||||
&.select2-container--focus {
|
||||
.select2-selection {
|
||||
border-color: $border-grey;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
[data-reach-combobox-token-list] {
|
||||
padding: $default-padding;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.select2-selection__choice {
|
||||
background-color: #FFFFFF;
|
||||
[data-reach-combobox-token] {
|
||||
border: solid 1px $border-grey;
|
||||
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;
|
||||
}
|
||||
// scss-lint:enable
|
||||
|
||||
[data-reach-combobox-token]:focus {
|
||||
background-color: $black;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.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] {
|
||||
font-size: 16px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
[data-reach-combobox-option][aria-selected="true"] {
|
||||
background: $light-blue !important;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,50 @@
|
|||
@import "constants";
|
||||
@import "colors";
|
||||
|
||||
.personnes-impliquees {
|
||||
padding-bottom: 50px;
|
||||
|
||||
ul {
|
||||
ul.tab-list {
|
||||
list-style-type: disc;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
// scss-lint:disable SelectorFormat
|
||||
.form .select2-container .select2-selection__rendered {
|
||||
padding: 12px;
|
||||
[data-react-class="ComboMultipleDropdownList"] {
|
||||
margin-bottom: $default-fields-spacer;
|
||||
|
||||
[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
|
||||
}
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
flex: 10;
|
||||
padding: 0 $default-padding;
|
||||
background-color: $light-grey;
|
||||
|
||||
&.no-background {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.procedure-form__column--preview {
|
||||
|
|
|
@ -61,4 +61,42 @@
|
|||
margin-bottom: 3 * $default-spacer;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ class API::V2::GraphqlController < API::V2::BaseController
|
|||
|
||||
render json: result
|
||||
rescue => exception
|
||||
if Rails.env.development?
|
||||
handle_error_in_development(exception)
|
||||
else
|
||||
if Rails.env.production?
|
||||
handle_error_in_production(exception)
|
||||
else
|
||||
handle_error_in_development(exception)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def send_to_instructeurs
|
||||
recipients = Instructeur.find(params[:recipients])
|
||||
recipients = Instructeur.find(JSON.parse(params[:recipients]))
|
||||
|
||||
recipients.each do |recipient|
|
||||
recipient.follow(dossier)
|
||||
|
|
|
@ -138,7 +138,7 @@ module Instructeurs
|
|||
end
|
||||
|
||||
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))
|
||||
end
|
||||
|
|
|
@ -60,7 +60,7 @@ module NewAdministrateur
|
|||
|
||||
def uninterlaced_png(uploaded_file)
|
||||
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)
|
||||
uploaded_file.tempfile.reopen(uploaded_file.tempfile.to_path, 'rb')
|
||||
end
|
||||
|
|
|
@ -80,8 +80,8 @@ module NewAdministrateur
|
|||
end
|
||||
|
||||
def add_instructeur
|
||||
emails = params['emails'].presence || []
|
||||
emails = emails.map(&:strip).map(&:downcase)
|
||||
emails = params['emails'].presence || [].to_json
|
||||
emails = JSON.parse(emails).map(&:strip).map(&:downcase)
|
||||
|
||||
correct_emails, bad_emails = emails
|
||||
.partition { |email| URI::MailTo::EMAIL_REGEXP.match?(email) }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module NewAdministrateur
|
||||
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]
|
||||
|
||||
ITEMS_PER_PAGE = 25
|
||||
|
@ -186,7 +186,14 @@ module NewAdministrateur
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
private
|
||||
|
@ -215,5 +222,9 @@ module NewAdministrateur
|
|||
def publish_params
|
||||
params.permit(:path, :lien_site_web)
|
||||
end
|
||||
|
||||
def allow_decision_access_params
|
||||
params.require(:experts_procedure).permit(:allow_decision_access)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
66
app/graphql/mutations/dossier_modifier_annotation.rb
Normal file
66
app/graphql/mutations/dossier_modifier_annotation.rb
Normal 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
|
|
@ -0,0 +1,23 @@
|
|||
module Mutations
|
||||
class DossierModifierAnnotationCheckbox < Mutations::DossierModifierAnnotation
|
||||
description "Modifier l’annotation 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
|
17
app/graphql/mutations/dossier_modifier_annotation_date.rb
Normal file
17
app/graphql/mutations/dossier_modifier_annotation_date.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module Mutations
|
||||
class DossierModifierAnnotationDate < Mutations::DossierModifierAnnotation
|
||||
description "Modifier l’annotation 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
|
|
@ -0,0 +1,17 @@
|
|||
module Mutations
|
||||
class DossierModifierAnnotationDatetime < Mutations::DossierModifierAnnotation
|
||||
description "Modifier l’annotation 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
|
|
@ -0,0 +1,17 @@
|
|||
module Mutations
|
||||
class DossierModifierAnnotationIntegerNumber < Mutations::DossierModifierAnnotation
|
||||
description "Modifier l’annotation 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
|
17
app/graphql/mutations/dossier_modifier_annotation_text.rb
Normal file
17
app/graphql/mutations/dossier_modifier_annotation_text.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module Mutations
|
||||
class DossierModifierAnnotationText < Mutations::DossierModifierAnnotation
|
||||
description "Modifier l’annotation 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
|
|
@ -685,6 +685,201 @@ type DossierLinkChamp implements Champ {
|
|||
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
|
||||
"""
|
||||
|
@ -986,42 +1181,132 @@ type Mutation {
|
|||
"""
|
||||
File information required to prepare a direct upload
|
||||
"""
|
||||
createDirectUpload(input: CreateDirectUploadInput!): CreateDirectUploadPayload
|
||||
createDirectUpload(
|
||||
"""
|
||||
Parameters for CreateDirectUpload
|
||||
"""
|
||||
input: CreateDirectUploadInput!
|
||||
): CreateDirectUploadPayload
|
||||
|
||||
"""
|
||||
Accepter le dossier.
|
||||
"""
|
||||
dossierAccepter(input: DossierAccepterInput!): DossierAccepterPayload
|
||||
dossierAccepter(
|
||||
"""
|
||||
Parameters for DossierAccepter
|
||||
"""
|
||||
input: DossierAccepterInput!
|
||||
): DossierAccepterPayload
|
||||
|
||||
"""
|
||||
Archiver le dossier.
|
||||
"""
|
||||
dossierArchiver(input: DossierArchiverInput!): DossierArchiverPayload
|
||||
dossierArchiver(
|
||||
"""
|
||||
Parameters for DossierArchiver
|
||||
"""
|
||||
input: DossierArchiverInput!
|
||||
): DossierArchiverPayload
|
||||
|
||||
"""
|
||||
Changer le grope instructeur du dossier.
|
||||
"""
|
||||
dossierChangerGroupeInstructeur(input: DossierChangerGroupeInstructeurInput!): DossierChangerGroupeInstructeurPayload
|
||||
dossierChangerGroupeInstructeur(
|
||||
"""
|
||||
Parameters for DossierChangerGroupeInstructeur
|
||||
"""
|
||||
input: DossierChangerGroupeInstructeurInput!
|
||||
): DossierChangerGroupeInstructeurPayload
|
||||
|
||||
"""
|
||||
Classer le dossier sans suite.
|
||||
"""
|
||||
dossierClasserSansSuite(input: DossierClasserSansSuiteInput!): DossierClasserSansSuitePayload
|
||||
dossierClasserSansSuite(
|
||||
"""
|
||||
Parameters for DossierClasserSansSuite
|
||||
"""
|
||||
input: DossierClasserSansSuiteInput!
|
||||
): DossierClasserSansSuitePayload
|
||||
|
||||
"""
|
||||
Envoyer un message à l'usager du dossier.
|
||||
"""
|
||||
dossierEnvoyerMessage(input: DossierEnvoyerMessageInput!): DossierEnvoyerMessagePayload
|
||||
dossierEnvoyerMessage(
|
||||
"""
|
||||
Parameters for DossierEnvoyerMessage
|
||||
"""
|
||||
input: DossierEnvoyerMessageInput!
|
||||
): DossierEnvoyerMessagePayload
|
||||
|
||||
"""
|
||||
Modifier l’annotation au format oui/non.
|
||||
"""
|
||||
dossierModifierAnnotationCheckbox(
|
||||
"""
|
||||
Parameters for DossierModifierAnnotationCheckbox
|
||||
"""
|
||||
input: DossierModifierAnnotationCheckboxInput!
|
||||
): DossierModifierAnnotationCheckboxPayload
|
||||
|
||||
"""
|
||||
Modifier l’annotation au format date.
|
||||
"""
|
||||
dossierModifierAnnotationDate(
|
||||
"""
|
||||
Parameters for DossierModifierAnnotationDate
|
||||
"""
|
||||
input: DossierModifierAnnotationDateInput!
|
||||
): DossierModifierAnnotationDatePayload
|
||||
|
||||
"""
|
||||
Modifier l’annotation au format date et heure.
|
||||
"""
|
||||
dossierModifierAnnotationDatetime(
|
||||
"""
|
||||
Parameters for DossierModifierAnnotationDatetime
|
||||
"""
|
||||
input: DossierModifierAnnotationDatetimeInput!
|
||||
): DossierModifierAnnotationDatetimePayload
|
||||
|
||||
"""
|
||||
Modifier l’annotation au format nombre entier.
|
||||
"""
|
||||
dossierModifierAnnotationIntegerNumber(
|
||||
"""
|
||||
Parameters for DossierModifierAnnotationIntegerNumber
|
||||
"""
|
||||
input: DossierModifierAnnotationIntegerNumberInput!
|
||||
): DossierModifierAnnotationIntegerNumberPayload
|
||||
|
||||
"""
|
||||
Modifier l’annotation au format text.
|
||||
"""
|
||||
dossierModifierAnnotationText(
|
||||
"""
|
||||
Parameters for DossierModifierAnnotationText
|
||||
"""
|
||||
input: DossierModifierAnnotationTextInput!
|
||||
): DossierModifierAnnotationTextPayload
|
||||
|
||||
"""
|
||||
Passer le dossier en instruction.
|
||||
"""
|
||||
dossierPasserEnInstruction(input: DossierPasserEnInstructionInput!): DossierPasserEnInstructionPayload
|
||||
dossierPasserEnInstruction(
|
||||
"""
|
||||
Parameters for DossierPasserEnInstruction
|
||||
"""
|
||||
input: DossierPasserEnInstructionInput!
|
||||
): DossierPasserEnInstructionPayload
|
||||
|
||||
"""
|
||||
Refuser le dossier.
|
||||
"""
|
||||
dossierRefuser(input: DossierRefuserInput!): DossierRefuserPayload
|
||||
dossierRefuser(
|
||||
"""
|
||||
Parameters for DossierRefuser
|
||||
"""
|
||||
input: DossierRefuserInput!
|
||||
): DossierRefuserPayload
|
||||
}
|
||||
|
||||
enum Order {
|
||||
|
|
|
@ -9,5 +9,11 @@ module Types
|
|||
field :dossier_accepter, mutation: Mutations::DossierAccepter
|
||||
field :dossier_archiver, mutation: Mutations::DossierArchiver
|
||||
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
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { ReactQueryCacheProvider } from 'react-query';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ComboSearch from './ComboSearch';
|
||||
import { queryCache } from './shared/queryCache';
|
||||
import { queryClient } from './shared/queryClient';
|
||||
|
||||
function ComboAdresseSearch({
|
||||
mandatory,
|
||||
|
@ -16,7 +16,7 @@ function ComboAdresseSearch({
|
|||
const transformResults = useCallback((_, { features }) => features);
|
||||
|
||||
return (
|
||||
<ReactQueryCacheProvider queryCache={queryCache}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ComboSearch
|
||||
placeholder={placeholder}
|
||||
required={mandatory}
|
||||
|
@ -28,7 +28,7 @@ function ComboAdresseSearch({
|
|||
transformResult={transformResult}
|
||||
transformResults={transformResults}
|
||||
/>
|
||||
</ReactQueryCacheProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { ReactQueryCacheProvider } from 'react-query';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
|
||||
import ComboSearch from './ComboSearch';
|
||||
import { queryCache } from './shared/queryCache';
|
||||
import { queryClient } from './shared/queryClient';
|
||||
|
||||
function ComboAnnuaireEducationSearch(params) {
|
||||
return (
|
||||
<ReactQueryCacheProvider queryCache={queryCache}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ComboSearch
|
||||
required={params.mandatory}
|
||||
hiddenFieldId={params.hiddenFieldId}
|
||||
|
@ -21,7 +21,7 @@ function ComboAnnuaireEducationSearch(params) {
|
|||
}
|
||||
}) => [id, `${nom_etablissement}, ${nom_commune} (${id})`]}
|
||||
/>
|
||||
</ReactQueryCacheProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { ReactQueryCacheProvider } from 'react-query';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
|
||||
import ComboSearch from './ComboSearch';
|
||||
import { queryCache } from './shared/queryCache';
|
||||
import { queryClient } from './shared/queryClient';
|
||||
|
||||
function ComboCommunesSearch(params) {
|
||||
return (
|
||||
<ReactQueryCacheProvider queryCache={queryCache}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ComboSearch
|
||||
required={params.mandatory}
|
||||
hiddenFieldId={params.hiddenFieldId}
|
||||
|
@ -17,7 +17,7 @@ function ComboCommunesSearch(params) {
|
|||
`${nom} (${codesPostaux[0]})`
|
||||
]}
|
||||
/>
|
||||
</ReactQueryCacheProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { ReactQueryCacheProvider } from 'react-query';
|
||||
import matchSorter from 'match-sorter';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
import { matchSorter } from 'match-sorter';
|
||||
|
||||
import ComboSearch from './ComboSearch';
|
||||
import { queryCache } from './shared/queryCache';
|
||||
import { queryClient } from './shared/queryClient';
|
||||
|
||||
const extraTerms = [{ code: '99', nom: 'Etranger' }];
|
||||
|
||||
|
@ -16,7 +16,7 @@ function ComboDepartementsSearch(params) {
|
|||
]);
|
||||
|
||||
return (
|
||||
<ReactQueryCacheProvider queryCache={queryCache}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ComboSearch
|
||||
required={params.mandatory}
|
||||
hiddenFieldId={params.hiddenFieldId}
|
||||
|
@ -25,7 +25,7 @@ function ComboDepartementsSearch(params) {
|
|||
transformResult={({ code, nom }) => [code, `${code} - ${nom}`]}
|
||||
transformResults={transformResults}
|
||||
/>
|
||||
</ReactQueryCacheProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
250
app/javascript/components/ComboMultipleDropdownList.jsx
Normal file
250
app/javascript/components/ComboMultipleDropdownList.jsx
Normal 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;
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { ReactQueryCacheProvider } from 'react-query';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
|
||||
import ComboSearch from './ComboSearch';
|
||||
import { queryCache } from './shared/queryCache';
|
||||
import { queryClient } from './shared/queryClient';
|
||||
|
||||
function ComboPaysSearch(params) {
|
||||
return (
|
||||
<ReactQueryCacheProvider queryCache={queryCache}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ComboSearch
|
||||
required={params.mandatory}
|
||||
hiddenFieldId={params.hiddenFieldId}
|
||||
|
@ -14,7 +14,7 @@ function ComboPaysSearch(params) {
|
|||
minimumInputLength={0}
|
||||
transformResult={({ nom }) => [nom, nom]}
|
||||
/>
|
||||
</ReactQueryCacheProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { ReactQueryCacheProvider } from 'react-query';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
|
||||
import ComboSearch from './ComboSearch';
|
||||
import { queryCache } from './shared/queryCache';
|
||||
import { queryClient } from './shared/queryClient';
|
||||
|
||||
function ComboRegionsSearch(params) {
|
||||
return (
|
||||
<ReactQueryCacheProvider queryCache={queryCache}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ComboSearch
|
||||
required={params.mandatory}
|
||||
hiddenFieldId={params.hiddenFieldId}
|
||||
|
@ -14,7 +14,7 @@ function ComboRegionsSearch(params) {
|
|||
minimumInputLength={0}
|
||||
transformResult={({ code, nom }) => [code, nom]}
|
||||
/>
|
||||
</ReactQueryCacheProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useMemo, useCallback, useRef } from 'react';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { useQuery } from 'react-query';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
|
@ -41,7 +41,7 @@ function ComboSearch({
|
|||
);
|
||||
const initialValue = hiddenValueField && hiddenValueField.value;
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
|
||||
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const resultsMap = useRef({});
|
||||
const setExternalValue = useCallback((value) => {
|
||||
|
@ -64,14 +64,6 @@ function ComboSearch({
|
|||
}
|
||||
});
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
setDebouncedSearchTerm(searchTerm);
|
||||
},
|
||||
300,
|
||||
[searchTerm]
|
||||
);
|
||||
|
||||
const handleOnChange = useCallback(
|
||||
({ target: { value } }) => {
|
||||
setValue(value);
|
|
@ -1,4 +1,3 @@
|
|||
import scrollToComponent from 'react-scroll-to-component';
|
||||
import { debounce } from '@utils';
|
||||
import {
|
||||
createTypeDeChampOperation,
|
||||
|
@ -53,8 +52,10 @@ function addTypeDeChamp(state, typeDeChamps, insertAfter, done) {
|
|||
state.flash.success();
|
||||
done();
|
||||
if (insertAfter) {
|
||||
scrollToComponent(insertAfter.target.nextElementSibling, {
|
||||
duration: 300
|
||||
insertAfter.target.nextElementSibling.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { QueryCache } from 'react-query';
|
||||
import { QueryClient } from 'react-query';
|
||||
import { isNumeric } from '@utils';
|
||||
import matchSorter from 'match-sorter';
|
||||
import { matchSorter } from 'match-sorter';
|
||||
|
||||
const { api_geo_url, api_adresse_url, api_education_url } =
|
||||
gon.autocomplete || {};
|
||||
|
||||
export const queryCache = new QueryCache({
|
||||
defaultConfig: {
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
queryFn: defaultQueryFn
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ function buildOptions() {
|
|||
return [{}, null];
|
||||
}
|
||||
|
||||
async function defaultQueryFn(scope, term) {
|
||||
async function defaultQueryFn({ queryKey: [scope, term] }) {
|
||||
if (scope == 'pays') {
|
||||
return matchSorter(await getPays(), term, { keys: ['nom'] });
|
||||
}
|
5
app/javascript/loaders/ComboMultipleDropdownList.js
Normal file
5
app/javascript/loaders/ComboMultipleDropdownList.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() =>
|
||||
import('../components/ComboMultipleDropdownList')
|
||||
);
|
|
@ -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 l’adresse email de l’instructeur',
|
||||
tags: true,
|
||||
tokenSeparators: [',', ' '],
|
||||
templateResult: templateOption,
|
||||
templateSelection: templateOption
|
||||
});
|
||||
});
|
|
@ -17,7 +17,6 @@ import '../new_design/dropdown';
|
|||
import '../new_design/form-validation';
|
||||
import '../new_design/procedure-context';
|
||||
import '../new_design/procedure-form';
|
||||
import '../new_design/select2';
|
||||
import '../new_design/spinner';
|
||||
import '../new_design/support';
|
||||
import '../new_design/dossiers/auto-save';
|
||||
|
|
|
@ -679,6 +679,10 @@ class Dossier < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def log_modifier_annotation!(champ, instructeur)
|
||||
log_dossier_operation(instructeur, :modifier_annotation, champ)
|
||||
end
|
||||
|
||||
def demander_un_avis!(avis)
|
||||
log_dossier_operation(avis.claimant, :demander_un_avis, avis)
|
||||
end
|
||||
|
|
|
@ -14,15 +14,4 @@ class ExpertsProcedure < ApplicationRecord
|
|||
belongs_to :procedure
|
||||
|
||||
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
|
||||
|
|
|
@ -62,7 +62,7 @@ class DossierSearchService
|
|||
|
||||
def self.to_tsquery(search_terms)
|
||||
(search_terms || "")
|
||||
.gsub(/['?\\:&|!<>\(\)]/, "") # drop disallowed characters
|
||||
.gsub(/['?\\:&|!<>()]/, "") # drop disallowed characters
|
||||
.strip
|
||||
.split(/\s+/) # split words
|
||||
.map { |x| "#{x}:*" } # enable prefix matching
|
||||
|
|
|
@ -8,9 +8,8 @@
|
|||
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|
|
||||
.flex.justify-start.align-start
|
||||
= select_tag(:recipients,
|
||||
options_from_collection_for_select(potential_recipients, :id, :email),
|
||||
multiple: true,
|
||||
class: 'select2-limited',
|
||||
placeholder: '')
|
||||
- hidden_field_id = SecureRandom.uuid
|
||||
= hidden_field_tag :recipients, nil, data: { uuid: hidden_field_id }
|
||||
= react_component("ComboMultipleDropdownList", options: potential_recipients.map{|r| [r.email, r.id]}, selected: [], disabled: [], hiddenFieldId: hidden_field_id, label: "email instructeur")
|
||||
|
||||
= f.submit "Envoyer", class: "button large send gap-left"
|
||||
|
|
|
@ -122,10 +122,10 @@
|
|||
Personnaliser
|
||||
#custom-menu.dropdown-content.fade-in-down
|
||||
= form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form columns-form' do
|
||||
= select_tag :values,
|
||||
options_for_select(@displayed_fields_options, selected: @displayed_fields_selected),
|
||||
multiple: true,
|
||||
class: 'select2-limited'
|
||||
- hidden_field_id = SecureRandom.uuid
|
||||
= hidden_field_tag :values, nil, data: { uuid: hidden_field_id }
|
||||
= react_component("ComboMultipleDropdownList", options: @displayed_fields_options, selected: @displayed_fields_selected, disabled: [], hiddenFieldId: hidden_field_id, label: 'colonne')
|
||||
|
||||
= submit_tag "Enregistrer", class: 'button'
|
||||
|
||||
%tbody
|
||||
|
|
|
@ -25,10 +25,13 @@
|
|||
.instructeur-wrapper
|
||||
- if !@procedure.routee?
|
||||
%p.notice Entrez les adresses email des instructeurs que vous souhaitez affecter à cette démarche
|
||||
= select_tag :emails,
|
||||
options_for_select(@available_instructeur_emails),
|
||||
multiple: true,
|
||||
class: 'select-instructeurs select2-limited'
|
||||
- hidden_field_id = SecureRandom.uuid
|
||||
= hidden_field_tag :emails, nil, data: { uuid: hidden_field_id }
|
||||
= react_component("ComboMultipleDropdownList",
|
||||
options: @available_instructeur_emails, selected: [], disabled: [],
|
||||
hiddenFieldId: hidden_field_id,
|
||||
label: 'email instructeur',
|
||||
acceptNewValues: true)
|
||||
|
||||
= f.submit 'Affecter', class: 'button primary send'
|
||||
|
||||
|
|
|
@ -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
|
||||
%h1.mt-2 Experts invités sur #{@procedure.libelle}
|
||||
- if @invited_expert_emails.present?
|
||||
%h1.page-title.mt-2 Experts invités sur #{@procedure.libelle}
|
||||
- if @experts_procedure.present?
|
||||
%table.table.mt-2
|
||||
%thead
|
||||
%tr
|
||||
%th Liste des experts
|
||||
- if feature_enabled_for?(:make_experts_notifiable, @procedure)
|
||||
%th Notifier des décisions sur les dossiers
|
||||
%tbody
|
||||
- @invited_expert_emails.each do |expert|
|
||||
- @experts_procedure.each do |expert_procedure|
|
||||
%tr
|
||||
%td
|
||||
%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
|
||||
.blank-tab
|
||||
%h2.empty-text Aucun expert invité pour le moment.
|
||||
|
|
|
@ -167,7 +167,7 @@
|
|||
%p.card-admin-subtitle Liste des experts invités par les instructeurs
|
||||
|
||||
.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
|
||||
|
|
|
@ -7,10 +7,7 @@
|
|||
= b.text
|
||||
|
||||
- else
|
||||
= form.select :value,
|
||||
champ.options,
|
||||
{ selected: champ.selected_options,
|
||||
disabled: champ.disabled_options },
|
||||
multiple: true,
|
||||
class: 'select2'
|
||||
- hidden_field_id = SecureRandom.uuid
|
||||
= form.hidden_field :value, { data: { uuid: hidden_field_id } }
|
||||
= react_component("ComboMultipleDropdownList", options: champ.options, selected: champ.selected_options, disabled: champ.disabled_options, hiddenFieldId: hidden_field_id, label: champ.libelle)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ Rails.application.configure do
|
|||
# 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
|
||||
# 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
|
||||
# just for the purpose of running a single test. If you are using a tool that
|
||||
|
|
|
@ -5,11 +5,10 @@ Rails.application.config.assets.version = '1.0'
|
|||
|
||||
# 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', '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', '@reach', 'combobox')
|
||||
Rails.application.config.assets.paths << Rails.root.join('node_modules', '@mapbox', 'mapbox-gl-draw', 'dist')
|
||||
|
||||
# Precompile additional assets.
|
||||
# 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']
|
||||
|
|
|
@ -3,5 +3,5 @@ Sentry.init do |config|
|
|||
config.send_default_pii = false
|
||||
config.enabled_environments = ['production']
|
||||
config.breadcrumbs_logger = [:active_support_logger]
|
||||
config.traces_sample_rate = 0.01
|
||||
config.traces_sample_rate = 0.001
|
||||
end
|
||||
|
|
|
@ -379,13 +379,14 @@ Rails.application.routes.draw do
|
|||
get 'jeton'
|
||||
patch 'update_jeton'
|
||||
put :allow_expert_review
|
||||
get 'invited_expert_list'
|
||||
end
|
||||
|
||||
get 'publication' => 'procedures#publication', as: :publication
|
||||
put 'publish' => 'procedures#publish', as: :publish
|
||||
get 'transfert' => 'procedures#transfert', as: :transfert
|
||||
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]
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ default: &default
|
|||
extensions:
|
||||
- .mjs
|
||||
- .js
|
||||
- .jsx
|
||||
- .sass
|
||||
- .scss
|
||||
- .css
|
||||
|
|
33
package.json
33
package.json
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.9",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.34",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.2",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@mapbox/mapbox-gl-draw": "^1.2.0",
|
||||
"@rails/actiontext": "^6.0.3",
|
||||
"@rails/activestorage": "^6.0.3",
|
||||
"@rails/ujs": "^6.0.3",
|
||||
"@rails/webpacker": "5.1.1",
|
||||
"@reach/combobox": "^0.10.2",
|
||||
"@reach/combobox": "^0.13.0",
|
||||
"@sentry/browser": "^5.15.5",
|
||||
"@tmcw/togeojson": "^4.0.0",
|
||||
"babel-plugin-macros": "^2.8.0",
|
||||
|
@ -20,23 +20,20 @@
|
|||
"dom4": "^2.1.5",
|
||||
"email-butler": "^1.0.13",
|
||||
"highcharts": "^8.1.1",
|
||||
"intersection-observer": "^0.10.0",
|
||||
"jquery": "^3.5.1",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"mapbox-gl": "^1.11.1",
|
||||
"match-sorter": "^4.2.1",
|
||||
"match-sorter": "^6.2.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-intersection-observer": "^8.26.2",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-intersection-observer": "^8.31.0",
|
||||
"react-mapbox-gl": "^4.8.6",
|
||||
"react-mapbox-gl-draw": "^2.0.4",
|
||||
"react-query": "^2.23.1",
|
||||
"react-scroll-to-component": "^1.0.2",
|
||||
"react-query": "^3.9.7",
|
||||
"react-sortable-hoc": "^1.11.0",
|
||||
"react-use": "^15.3.4",
|
||||
"react_ujs": "^2.6.1",
|
||||
"select2": "^4.0.13",
|
||||
"trix": "^1.2.3",
|
||||
"use-debounce": "^5.2.0",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -45,15 +42,15 @@
|
|||
"eslint": "^7.0.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.20.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.2",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"netlify-cli": "^2.61.2",
|
||||
"prettier": "^2.0.5",
|
||||
"webpack-bundle-analyzer": "^3.7.0",
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"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",
|
||||
"graphql:docs:build": "graphdoc --force",
|
||||
"graphql:docs:deploy": "netlify deploy -d ./docs/graphql --prod",
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
describe API::V2::GraphqlController do
|
||||
let(:admin) { create(:administrateur) }
|
||||
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
|
||||
dossier = create(:dossier,
|
||||
:en_construction,
|
||||
:with_all_champs,
|
||||
:with_all_annotations,
|
||||
:with_individual,
|
||||
procedure: procedure)
|
||||
create(:commentaire, :with_file, dossier: dossier, email: 'test@test.com')
|
||||
|
@ -1117,6 +1118,240 @@ describe API::V2::GraphqlController do
|
|||
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
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
post(
|
||||
:send_to_instructeurs,
|
||||
params: {
|
||||
recipients: [recipient],
|
||||
recipients: [recipient.id].to_json,
|
||||
procedure_id: procedure.id,
|
||||
dossier_id: dossier.id
|
||||
}
|
||||
|
|
|
@ -207,11 +207,11 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
|
|||
|
||||
describe '#add_instructeur_procedure_non_routee' do
|
||||
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 } }
|
||||
|
||||
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(subject.request.flash[:alert]).to be_nil }
|
||||
it { expect(subject.request.flash[:notice]).to be_present }
|
||||
|
@ -219,7 +219,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
|
|||
end
|
||||
|
||||
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(subject.request.flash[:alert]).to be_present }
|
||||
it { expect(subject.request.flash[:notice]).to be_present }
|
||||
|
@ -227,7 +227,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
|
|||
end
|
||||
|
||||
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).to redirect_to admin_procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur) }
|
||||
end
|
||||
|
@ -247,7 +247,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
|
|||
params: {
|
||||
procedure_id: procedure.id,
|
||||
id: gi_1_2.id,
|
||||
emails: new_instructeur_emails
|
||||
emails: new_instructeur_emails.to_json
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -281,7 +281,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
|
|||
end
|
||||
|
||||
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)) }
|
||||
end
|
||||
|
|
|
@ -532,4 +532,34 @@ describe NewAdministrateur::ProceduresController, type: :controller do
|
|||
it { expect(procedure.allow_expert_review).to be_truthy }
|
||||
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
|
||||
|
|
|
@ -216,8 +216,8 @@ FactoryBot.define do
|
|||
|
||||
trait :with_all_annotations do
|
||||
after(:create) do |dossier, _evaluator|
|
||||
dossier.champs = dossier.types_de_champ.map do |type_de_champ|
|
||||
build(:"champ_#{type_de_champ.type_champ}", dossier: dossier, type_de_champ: type_de_champ)
|
||||
dossier.champs_private = dossier.types_de_champ_private.map do |type_de_champ|
|
||||
build(:"champ_#{type_de_champ.type_champ}", private: true, dossier: dossier, type_de_champ: type_de_champ)
|
||||
end
|
||||
dossier.save!
|
||||
end
|
||||
|
|
|
@ -128,10 +128,8 @@ feature 'Instructing a dossier:' do
|
|||
|
||||
click_on 'Personnes impliquées'
|
||||
|
||||
first('.select2-container', minimum: 1).click
|
||||
find('li.select2-results__option[role="option"]', text: instructeur_2.email).click
|
||||
first('.select2-container', minimum: 1).click
|
||||
find('li.select2-results__option[role="option"]', text: instructeur_3.email).click
|
||||
select_multi('email instructeur', instructeur_2.email)
|
||||
select_multi('email instructeur', instructeur_3.email)
|
||||
|
||||
click_on 'Envoyer'
|
||||
|
||||
|
|
|
@ -125,15 +125,13 @@ feature "procedure filters" do
|
|||
|
||||
def add_column(column_name)
|
||||
click_on 'Personnaliser'
|
||||
find("span.select2-container").click
|
||||
find(:xpath, "//li[text()='#{column_name}']").click
|
||||
select_multi('colonne', column_name)
|
||||
click_button "Enregistrer"
|
||||
end
|
||||
|
||||
def remove_column(column_name)
|
||||
click_on 'Personnaliser'
|
||||
find(:xpath, "//li[contains(@title, '#{column_name}')]/span[contains(text(), '×')]").click
|
||||
find(:xpath, "//form[contains(@class, 'columns-form')]//span[contains(@class, 'select2-container')]").click
|
||||
find(:xpath, "//li[contains(text(), '#{column_name}')]/span[contains(text(), 'x')]").click
|
||||
click_button "Enregistrer"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,14 +30,14 @@ feature 'The routing', js: true do
|
|||
expect(page).to have_field('Nom du groupe', with: 'littéraire')
|
||||
|
||||
# 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' }
|
||||
expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche")
|
||||
|
||||
victor = User.find_by(email: 'victor@inst.com').instructeur
|
||||
|
||||
# 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' }
|
||||
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 d’instructeurs « scientifique » a été créé.')
|
||||
|
||||
# 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' }
|
||||
expect(page).to have_text("L’instructeur marie@inst.com a été affecté")
|
||||
|
||||
marie = User.find_by(email: 'marie@inst.com').instructeur
|
||||
|
||||
# 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' }
|
||||
expect(page).to have_text("L’instructeur superwoman@inst.com a été affecté")
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ feature 'The user' do
|
|||
check('val1')
|
||||
check('val3')
|
||||
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('charly', from: form_id_for('multiple_choice_drop_down_list_long'))
|
||||
select_multi('multiple_choice_drop_down_list_long', 'alpha')
|
||||
select_multi('multiple_choice_drop_down_list_long', 'charly')
|
||||
|
||||
select_champ_geo('pays', 'aust', 'AUSTRALIE')
|
||||
|
||||
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('val3')
|
||||
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('regions', with: 'Martinique')
|
||||
expect(page).to have_hidden_field('departements', with: '02 - Aisne')
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe ExpertsProcedure, type: :model do
|
|||
let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) }
|
||||
let(:experts_procedure2) { ExpertsProcedure.create(expert: expert2, 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
|
||||
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
|
||||
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
|
||||
|
||||
context 'when a dossier has 2 avis from the same expert' do
|
||||
let!(:avis) { 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
|
||||
|
||||
|
@ -35,7 +39,9 @@ RSpec.describe ExpertsProcedure, type: :model do
|
|||
let!(:avis2) { create(:avis, dossier: dossier2, experts_procedure: experts_procedure2) }
|
||||
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
|
||||
|
|
|
@ -914,7 +914,9 @@ describe ProcedurePresentation do
|
|||
it 'should downcase and transform value' do
|
||||
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({
|
||||
"suivis" =>
|
||||
[
|
||||
{ "label" => first_type_de_champ.libelle, "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "true" }
|
||||
]
|
||||
})
|
||||
|
@ -927,7 +929,8 @@ describe ProcedurePresentation do
|
|||
it 'should passthrough value' do
|
||||
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({
|
||||
"suivis" => [
|
||||
{ "label" => first_type_de_champ.libelle, "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "Oui" }
|
||||
]
|
||||
})
|
||||
|
|
|
@ -27,8 +27,8 @@ module FeatureHelpers
|
|||
if sign_in_by_link
|
||||
mail = ActionMailer::Base.deliveries.last
|
||||
message = mail.html_part.body.raw_source
|
||||
instructeur_id = message[/\".+\/connexion-par-jeton\/(.+)\?jeton=(.*)\"/, 1]
|
||||
jeton = message[/\".+\/connexion-par-jeton\/(.+)\?jeton=(.*)\"/, 2]
|
||||
instructeur_id = message[/".+\/connexion-par-jeton\/(.+)\?jeton=(.*)"/, 1]
|
||||
jeton = message[/".+\/connexion-par-jeton\/(.+)\?jeton=(.*)"/, 2]
|
||||
|
||||
visit sign_in_by_link_path(instructeur_id, jeton: jeton)
|
||||
end
|
||||
|
@ -103,6 +103,25 @@ module FeatureHelpers
|
|||
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
|
||||
# allow inspecting the page or the console.
|
||||
#
|
||||
|
|
|
@ -13,7 +13,7 @@ describe 'instructeurs/dossiers/envoyer_dossier_block.html.haml', type: :view do
|
|||
let(:instructeur) { create(:instructeur, email: 'yop@totomail.fr') }
|
||||
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") }
|
||||
end
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@ describe 'new_administrateur/procedures/invited_expert_list.html.haml', type: :v
|
|||
context 'when the procedure has 0 avis' do
|
||||
let!(:dossier) { create(:dossier, procedure: procedure) }
|
||||
before do
|
||||
@invited_expert_emails = ExpertsProcedure.invited_expert_emails(procedure)
|
||||
@invited_experts = procedure.experts_procedures
|
||||
subject
|
||||
end
|
||||
|
||||
it 'has 0 experts into the page' do
|
||||
expect(@invited_expert_emails.count).to eq(0)
|
||||
expect(@invited_expert_emails).to eq([])
|
||||
expect(@invited_experts.count).to eq(0)
|
||||
expect(@invited_experts).to eq([])
|
||||
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) }
|
||||
|
||||
before do
|
||||
@invited_expert_emails = ExpertsProcedure.invited_expert_emails(procedure)
|
||||
@invited_experts = procedure.experts_procedures
|
||||
subject
|
||||
end
|
||||
|
||||
it 'has 2 experts and match array' do
|
||||
expect(@invited_expert_emails.count).to eq(2)
|
||||
expect(@invited_expert_emails).to eq([expert.email, expert2.email].sort)
|
||||
expect(@invited_experts.count).to eq(2)
|
||||
expect(@invited_experts).to match_array([experts_procedure, experts_procedure2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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) }
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue