diff --git a/.ruby-version b/.ruby-version index 4f5e6973..1cf82530 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.4.5 +3.4.6 diff --git a/Gemfile b/Gemfile index a8ec3a08..1cc1d7c8 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -ruby '3.4.5' +ruby '3.4.6' # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem 'rails', '~> 8.0' @@ -47,7 +47,7 @@ gem 'tzinfo-data', platforms: %i[windows jruby] # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', require: false -gem 'ostruct' +gem 'ostruct' # TODO: Remove once we migrate away from it # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] gem 'image_processing', '~> 1.2' gem 'shadcn-ui', '~> 0.0.13' @@ -94,6 +94,8 @@ gem 'logidze' gem 'appsignal' gem 'rack-attack' +gem 'i18n-active_record', require: 'i18n/active_record' + group :development, :test do gem 'dotenv' # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem diff --git a/Gemfile.lock b/Gemfile.lock index aa0f344c..b7b6c56a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,12 +77,12 @@ GEM annotaterb (4.19.0) activerecord (>= 6.0.0) activesupport (>= 6.0.0) - appsignal (4.7.2) + appsignal (4.7.4) logger rack (>= 2.0.0) ast (2.4.3) aws-eventstream (1.4.0) - aws-partitions (1.1164.0) + aws-partitions (1.1172.0) aws-sdk-core (3.233.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -91,7 +91,7 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-lambda (1.160.0) + aws-sdk-lambda (1.161.0) aws-sdk-core (~> 3, >= 3.231.0) aws-sigv4 (~> 1.5) aws-sdk-rails (5.1.0) @@ -102,7 +102,7 @@ GEM base64 (0.3.0) bcrypt (3.1.20) benchmark (0.4.1) - bigdecimal (3.2.3) + bigdecimal (3.3.1) bindex (0.8.1) bootsnap (1.18.6) msgpack (~> 1.2) @@ -130,7 +130,7 @@ GEM connection_pool (2.5.4) crass (1.0.6) csv (3.3.5) - cucumber (10.1.0) + cucumber (10.1.1) base64 (~> 0.2) builder (~> 3.2) cucumber-ci-environment (> 9, < 11) @@ -187,16 +187,16 @@ GEM diff-lcs (1.6.2) dotenv (3.1.8) drb (2.2.3) - erb (5.0.2) + erb (5.1.1) erubi (1.13.1) - et-orbi (1.3.0) + et-orbi (1.4.0) tzinfo factory_bot (6.5.5) activesupport (>= 6.1.0) factory_bot_rails (6.5.1) factory_bot (~> 6.5) railties (>= 6.1.0) - faraday (2.13.4) + faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json logger @@ -218,8 +218,8 @@ GEM ffi (1.17.2-x86-linux-gnu) ffi (1.17.2-x86_64-darwin) ffi (1.17.2-x86_64-linux-gnu) - fugit (1.11.2) - et-orbi (~> 1, >= 1.2.11) + fugit (1.12.0) + et-orbi (~> 1.4) raabro (~> 1.4) fx (0.9.0) activerecord (>= 7.0) @@ -236,6 +236,8 @@ GEM htmlentities (4.3.4) i18n (1.14.7) concurrent-ruby (~> 1.0) + i18n-active_record (1.4.0) + i18n (>= 0.5.0) image_processing (1.14.0) mini_magick (>= 4.9.5, < 6) ruby-vips (>= 2.0.17, < 3) @@ -252,7 +254,7 @@ GEM actionview (>= 7.0.0) activesupport (>= 7.0.0) jmespath (1.6.2) - json (2.15.0) + json (2.15.1) language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) @@ -267,7 +269,7 @@ GEM net-imap net-pop net-smtp - mailgun-ruby (1.3.10) + mailgun-ruby (1.4.0) faraday (~> 2.1) faraday-multipart (~> 1.1.0) mini_mime @@ -278,13 +280,13 @@ GEM logger mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (5.25.5) + minitest (5.26.0) msgpack (1.8.0) multi_test (1.1.0) multipart-post (2.4.1) net-http (0.6.0) uri - net-imap (0.5.10) + net-imap (0.5.12) date net-protocol net-pop (0.1.2) @@ -324,12 +326,12 @@ GEM pg (1.6.2-arm64-darwin) pg (1.6.2-x86_64-darwin) pg (1.6.2-x86_64-linux) - pp (0.6.2) + pp (0.6.3) prettyprint pretender (0.6.0) actionpack (>= 7.1) prettyprint (0.2.0) - prism (1.5.1) + prism (1.5.2) propshaft (1.3.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) @@ -349,7 +351,7 @@ GEM rspec-support (~> 3.12) raabro (1.4.0) racc (1.8.1) - rack (3.2.1) + rack (3.2.3) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-session (2.1.1) @@ -391,26 +393,27 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.3.0) - ransack (4.3.0) - activerecord (>= 6.1.5) - activesupport (>= 6.1.5) + ransack (4.4.1) + activerecord (>= 7.2) + activesupport (>= 7.2) i18n - rdoc (6.14.2) + rdoc (6.15.0) erb psych (>= 4.0.0) + tsort recaptcha (5.21.1) redis (5.4.1) redis-client (>= 0.22.0) - redis-client (0.26.0) + redis-client (0.26.1) connection_pool regexp_parser (2.11.3) reline (0.6.2) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) - responders (3.1.1) - actionpack (>= 5.2) - railties (>= 5.2) + responders (3.2.0) + actionpack (>= 7.0) + railties (>= 7.0) rspec-core (3.13.5) rspec-support (~> 3.13.0) rspec-expectations (3.13.5) @@ -430,7 +433,7 @@ GEM rspec-support (3.13.6) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.80.2) + rubocop (1.81.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -438,7 +441,7 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.47.1) @@ -454,7 +457,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rails (2.33.3) + rubocop-rails (2.33.4) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -475,7 +478,7 @@ GEM ruby-vips (2.2.5) ffi (~> 1.12) logger - rubyzip (3.1.0) + rubyzip (3.1.1) scenic (1.9.0) activerecord (>= 4.0.0) railties (>= 4.0.0) @@ -503,7 +506,7 @@ GEM timecop (0.9.10) timeout (0.4.3) tsort (0.2.0) - turbo-rails (2.0.16) + turbo-rails (2.0.17) actionpack (>= 7.1.0) railties (>= 7.1.0) tzinfo (2.0.6) @@ -511,7 +514,7 @@ GEM unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.1.0) - uri (1.0.3) + uri (1.0.4) useragent (0.16.11) view_component (4.0.2) activesupport (>= 7.1.0, < 8.1) @@ -564,6 +567,7 @@ DEPENDENCIES ffaker fx good_job + i18n-active_record image_processing (~> 1.2) importmap-rails jbuilder @@ -620,18 +624,18 @@ CHECKSUMS activesupport (8.0.3) sha256=a711ce5e30660b23232f26a38699469f8d859d47aa1f722e183fda6d7cc17823 addressable (2.8.7) sha256=462986537cf3735ab5f3c0f557f14155d778f4b43ea4f485a9deb9c8f7c58232 annotaterb (4.19.0) sha256=c951df62059b3ac1ae383f4140bf935a140a15b6461f8d9a97d34b38ce2c7208 - appsignal (4.7.2) sha256=91656547010cfbe7673f4ee683e982e1603674ee7c90ea601f8b00e83b22869d + appsignal (4.7.4) sha256=86cc15a7a64bf6bff899c3910597414b22c702057406ffcb97c20a1d8f9c8979 ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b - aws-partitions (1.1164.0) sha256=0c84863e8d8ecae9826d24f354a321e0cde6c0d3078069dba3b96525d74950f4 + aws-partitions (1.1172.0) sha256=62809c443c36260f8d6c2e978ea49adcac497ca63f67568aae3e0e51e8341246 aws-sdk-core (3.233.0) sha256=798db4e867d15dceb6504317867fc8013042984988199600c78e310d0058018a - aws-sdk-lambda (1.160.0) sha256=abb99459230e20958f0d949622406524f97f3ac33d8566eda4af6a2be43dd847 + aws-sdk-lambda (1.161.0) sha256=09a9a5a0dfabd567989b015a54aed140d479f58b447f3fc94585f84b28e1e380 aws-sdk-rails (5.1.0) sha256=8cffd3b60142e12a6747c91a7a609f668276b1082fc0c10810d5da3d14b13189 aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 benchmark (0.4.1) sha256=d4ef40037bba27f03b28013e219b950b82bace296549ec15a78016552f8d2cce - bigdecimal (3.2.3) sha256=ffd11d1ac67a0d3b2f44aec0a6487210b3f813f363dd11f1fcccf5ba00da4e1b + bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218 bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e bootsnap (1.18.6) sha256=0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00 brakeman (7.1.0) sha256=bbc708a75a53008490c8b9600b97fa85cb3d5a8818dd1560f18e0b89475d48af @@ -643,7 +647,7 @@ CHECKSUMS connection_pool (2.5.4) sha256=e9e1922327416091f3f6542f5f4446c2a20745276b9aa796dd0bb2fd0ea1e70a crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f - cucumber (10.1.0) sha256=bd9f5783b6cc2f1113ed4e64822459d4e73c973063f2325d89b5d555e4fe3e05 + cucumber (10.1.1) sha256=293be9e52e526824dc6157e1afe8ee3716fd2902f88a0f4ae0ae37a46e452271 cucumber-ci-environment (10.0.1) sha256=bb6e3fcec85c981dff4561997e8675a7123eead5fe9e587d2ad7568adbe18631 cucumber-core (15.2.1) sha256=636a329f877c7ba478b5d9090f810c1b21796f9b601fa33532133ad1910b8588 cucumber-cucumber-expressions (18.0.1) sha256=8398a0bf636af33ff3b61e459a309295eb02745b9e21bd7af0eaaa2a1e6be3e5 @@ -664,12 +668,12 @@ CHECKSUMS diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 dotenv (3.1.8) sha256=9e1176060ced581f8e6ce4384e91361817763a76e3c625c8bddc18b35bd392c3 drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 - erb (5.0.2) sha256=d30f258143d4300fb4ecf430042ac12970c9bb4b33c974a545b8f58c1ec26c0f + erb (5.1.1) sha256=b2c26e7924551d9efbae998e17ddbef220937b6422b1d2ec7ae71417b5a1f4ec erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9 - et-orbi (1.3.0) sha256=56716ce1c40f56412d6acb0721ebede6bd23b66525dab008a95d77df00c7d260 + et-orbi (1.4.0) sha256=6c7e3c90779821f9e3b324c5e96fda9767f72995d6ae435b96678a4f3e2de8bc factory_bot (6.5.5) sha256=ce59295daee1b4704dab8a2fee6816f513d467c6aa3bc587860767d74a66efbe factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68 - faraday (2.13.4) sha256=c719ff52cfd0dbaeca79dd83ed3aeea3f621032abf8bc959d1c05666157cac26 + faraday (2.14.0) sha256=8699cfe5d97e55268f2596f9a9d5a43736808a943714e3d9a53e6110593941cd faraday-multipart (1.1.1) sha256=77a18ff40149030fd1aef55bb4fc7a67ce46419a8a3fcd010e28c2526e8d8903 faraday-net_http (3.4.1) sha256=095757fae7872b94eac839c08a1a4b8d84fd91d6886cfbe75caa2143de64ab3b fast_jsonparser (0.6.0) sha256=8f6fb292a9627431f5174eab2dd55f82682c020f2bb1a40609a1cf37e41d3ebd @@ -681,38 +685,39 @@ CHECKSUMS ffi (1.17.2-x86-linux-gnu) sha256=95d8f9ebea23c39888e2ab85a02c98f54acb2f4e79b829250d7267ce741dc7b0 ffi (1.17.2-x86_64-darwin) sha256=981f2d4e32ea03712beb26e55e972797c2c5a7b0257955d8667ba58f2da6440e ffi (1.17.2-x86_64-linux-gnu) sha256=05d2026fc9dbb7cfd21a5934559f16293815b7ce0314846fee2ac8efbdb823ea - fugit (1.11.2) sha256=4c2e234f750c78d4514d0ca343a0b923847eac3846976fdb23ed4245d8fde6fe + fugit (1.12.0) sha256=033ddf76b3b0149519be6e53ed83954a4df304b8588bafd48284b2a383ff6503 fx (0.9.0) sha256=9197b8ab2f7814da17d137cd98eb7a72706d7ab87aacf803237ed014baa06d0a globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11 good_job (4.12.0) sha256=c401b360293d3c2a6c877d124c91c470b0343a02d74b16c9c85d264d18926ae3 htmlentities (4.3.4) sha256=125a73c6c9f2d1b62100b7c3c401e3624441b663762afa7fe428476435a673da i18n (1.14.7) sha256=ceba573f8138ff2c0915427f1fc5bdf4aa3ab8ae88c8ce255eb3ecf0a11a5d0f + i18n-active_record (1.4.0) sha256=d66be03d273fefda64080ac239bc814b79a8696bcab217c3e3f3fb1a50245404 image_processing (1.14.0) sha256=754cc169c9c262980889bec6bfd325ed1dafad34f85242b5a07b60af004742fb importmap-rails (2.2.2) sha256=729f5b1092f832780829ade1d0b46c7e53d91c556f06da7254da2977e93fe614 io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb irb (1.15.2) sha256=222f32952e278da34b58ffe45e8634bf4afc2dc7aa9da23fed67e581aa50fdba jbuilder (2.14.1) sha256=4eb26376ff60ef100cb4fd6fd7533cd271f9998327e86adf20fd8c0e69fabb42 jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 - json (2.15.0) sha256=bc24d490a1d81bcbf6b45ee5c02695545d4ed37f679cee879b789a2bbb53ad5c + json (2.15.1) sha256=b1c1b2e7c116eb1903e0ce0c374783e6ead8747a0f9eca132d274018ebb80b89 language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 logidze (1.4.1) sha256=6303fbeff030e21cd2f143aa5f7dba300978236509327cbee41447cb2af5e368 loofah (2.24.1) sha256=655a30842b70ec476410b347ab1cd2a5b92da46a19044357bbd9f401b009a337 mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad - mailgun-ruby (1.3.10) sha256=1917d8fb8506cd7b26c04eba23b8b94e1393e9611fe6146b7008e435c267ca57 + mailgun-ruby (1.4.0) sha256=3bf60838b796a3d48a63c1f1735bb18938bf62bfd839d55492ceaaf8a565d61d marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b memoist3 (1.0.0) sha256=686e42402cf150a362050c23143dc57b0ef88f8c344943ff8b7845792b50d56f mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4 mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289 - minitest (5.25.5) sha256=391b6c6cb43a4802bfb7c93af1ebe2ac66a210293f4a3fb7db36f2fc7dc2c756 + minitest (5.26.0) sha256=f5817ad09f863a4f7eac917707c1ca5c09cdc4a35e17d91171760178324d2c30 msgpack (1.8.0) sha256=e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732 multi_test (1.1.0) sha256=e9e550cdd863fb72becfe344aefdcd4cbd26ebf307847f4a6c039a4082324d10 multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8 net-http (0.6.0) sha256=9621b20c137898af9d890556848c93603716cab516dc2c89b01a38b894e259fb - net-imap (0.5.10) sha256=f84d206a296bff48a3a10507567fc38b050d2a40c92ea0d448164f64e60d6205 + net-imap (0.5.12) sha256=cb8cd05bd353fcc19b6cbc530a9cb06b577a969ea10b7ddb0f37787f74be4444 net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736 @@ -735,10 +740,10 @@ CHECKSUMS pg (1.6.2-arm64-darwin) sha256=4d44500b28d5193b26674583d199a6484f80f1f2ea9cf54f7d7d06a1b7e316b6 pg (1.6.2-x86_64-darwin) sha256=c441a55723584e2ae41749bf26024d7ffdfe1841b442308ed50cd6b7fda04115 pg (1.6.2-x86_64-linux) sha256=525f438137f2d1411a1ebcc4208ec35cb526b5a3b285a629355c73208506a8ea - pp (0.6.2) sha256=947ec3120c6f92195f8ee8aa25a7b2c5297bb106d83b41baa02983686577b6ff + pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 pretender (0.6.0) sha256=d4528df42dd0dc5424b5d077a23435341d31ddee09bc54c8301691f55afda63d prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 - prism (1.5.1) sha256=b40c1b76ccb9fcccc3d1553967cda6e79fa7274d8bfea0d98b15d27a6d187134 + prism (1.5.2) sha256=192741663a55af1ac1b987caa1092deb666e4ff46a30c5064ad5456acd05df1d propshaft (1.3.1) sha256=9acc664ef67e819ffa3d95bd7ad4c3623ea799110c5f4dee67fa7e583e74c392 psych (5.2.6) sha256=814328aa5dcb6d604d32126a20bc1cbcf05521a5b49dbb1a8b30a07e580f316e public_suffix (6.0.2) sha256=bfa7cd5108066f8c9602e0d6d4114999a5df5839a63149d3e8b0f9c1d3558394 @@ -747,7 +752,7 @@ CHECKSUMS pundit-matchers (4.0.0) sha256=59d6077a1d575ea7cceca3ed73df5257488ee1a111ec707b2a797e76908cffd5 raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f - rack (3.2.1) sha256=30af3f7e5ec21b0d14d822cf24446048dba5f651b617c7e97405b604f20a9e33 + rack (3.2.3) sha256=239a373da6584574f25f042d8ed4ba21e691c9799f1d2b5c8920bbbc23ca3d41 rack-attack (6.7.0) sha256=3ca47e8f66cd33b2c96af53ea4754525cd928ed3fa8da10ee6dad0277791d77c rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9 rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 @@ -758,33 +763,33 @@ CHECKSUMS railties (8.0.3) sha256=ace31dcad7134299a64d6d96310d76d32868756e58e2983e25b121acd457f1d2 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 - ransack (4.3.0) sha256=48e141814eb4af8a5cc4e9890b7a088fe818c9996c6b8c846f11104b4c12e8b1 - rdoc (6.14.2) sha256=9fdd44df130f856ae70cc9a264dfd659b9b40de369b16581f4ab746e42439226 + ransack (4.4.1) sha256=6aeaac36fc19088570e10da1044e6cfd88c740e20f871b84566fd30e32b7a63d + rdoc (6.15.0) sha256=0f0e68864969e77c2acd4063a9585baa5216d362701d827a93feaa9cbae78b05 recaptcha (5.21.1) sha256=e003e9ceba9b993a9f0c6a828c192f2d46693cd2aa0b0beae94f936649507adb redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae - redis-client (0.26.0) sha256=71e048d75650eb23b8387c71c620a49edb6d2ffc48a565734aa81efca34e90ae + redis-client (0.26.1) sha256=1e39d2862c4516a75ff777ee6ed08827af39336bfece4a48e944244891d9a073 regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 reline (0.6.2) sha256=1dad26a6008872d59c8e05244b119347c9f2ddaf4a53dce97856cd5f30a02846 request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb - responders (3.1.1) sha256=92f2a87e09028347368639cfb468f5fefa745cb0dc2377ef060db1cdd79a341a + responders (3.2.0) sha256=89c2d6ac0ae16f6458a11524cae4a8efdceba1a3baea164d28ee9046bd3df55a rspec-core (3.13.5) sha256=ab3f682897c6131c67f9a17cfee5022a597f283aebe654d329a565f9937a4fa3 rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.5) sha256=e4338a6f285ada9fe56f5893f5457783af8194f5d08884d17a87321d5195ea81 rspec-rails (8.0.2) sha256=113139a53f5d068d4f48d1c29ad5f982013ed9b0daa69d7f7b266eda5d433ace rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2 rspec_junit_formatter (0.6.0) sha256=40dde674e6ae4e6cc0ff560da25497677e34fefd2338cc467a8972f602b62b15 - rubocop (1.80.2) sha256=6485f30fefcf5c199db3b91e5e253b1ef43f7e564784e2315255809a3dd9abf4 + rubocop (1.81.1) sha256=352a9a6f314a4312f6c305f1f72bc466254d221c95445cd49e1b65d1f9411635 rubocop-ast (1.47.1) sha256=592682017855408b046a8190689490763aecea175238232b1b526826349d01ae rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c rubocop-factory_bot (2.27.1) sha256=9d744b5916778c1848e5fe6777cc69855bd96548853554ec239ba9961b8573fe rubocop-performance (1.26.0) sha256=7bb0d9d9fb2ea122bf6f9a596dd7cf9dc93ab4950923d26c4ae4f328cef71ca9 - rubocop-rails (2.33.3) sha256=848c011b58c1292f3066246c9eb18abf6ffcfbce28bc57c4ab888bbec79af74b + rubocop-rails (2.33.4) sha256=34ec8f6637706dc224483d949ccc88b3e41596a81a11a1ec0c7d74ecbea356b5 rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d rubocop-rspec (3.7.0) sha256=b7b214da112034db9c6d00f2d811a354847e870f7b6ed2482b29649c3d42058f rubocop-rspec_rails (2.31.0) sha256=775375e18a26a1184a812ef3054b79d218e85601b9ae897f38f8be24dddf1f45 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-vips (2.2.5) sha256=f3c547a172c36ba26b8614c809f5823bc6199623ec6204ec7c3bce29037f7758 - rubyzip (3.1.0) sha256=e5af14749e2e7d2ecfc43526888b2165bf4d464002964c4e281e9480fb26f415 + rubyzip (3.1.1) sha256=54c97dd156437018c6914d76df52d10560a3b7784de36b1551e4a0709f958273 scenic (1.9.0) sha256=6eec3aa049ba2a137e7eb0b4c4f0200b77f7765fc82e08ad44c01da7905563d9 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 shadcn-ui (0.0.15) sha256=6dde7aa77d6e0121db922ce5e8e2673c60bc30f0af7ddd055c90dbdf9d17f7a4 @@ -803,11 +808,11 @@ CHECKSUMS timecop (0.9.10) sha256=12ba45ce57cdcf6b1043cb6cdffa6381fd89ce10d369c28a7f6f04dc1b0cd8eb timeout (0.4.3) sha256=9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f - turbo-rails (2.0.16) sha256=d24e1b60f0c575b3549ecda967e5391027143f8220d837ed792c8d48ea0ea38d + turbo-rails (2.0.17) sha256=49fd304b62e1b7f308f4feda49d1e1941ec90e6cd2f16cd0d9f8380e72c21926 tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 - uri (1.0.3) sha256=e9f2244608eea2f7bc357d954c65c910ce0399ca5e18a7a29207ac22d8767011 + uri (1.0.4) sha256=34485d137c079f8753a0ca1d883841a7ba2e5fae556e3c30c2aab0dde616344b useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844 view_component (4.0.2) sha256=80eb4a8ecf8ff4cf152aea4821a8748d071a2e3af04f0532efcbf5011e8e9f65 warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0 @@ -819,7 +824,7 @@ CHECKSUMS zeitwerk (2.7.3) sha256=b2e86b4a9b57d26ba68a15230dcc7fe6f040f06831ce64417b0621ad96ba3e85 RUBY VERSION - ruby 3.4.5p51 + ruby 3.4.6p54 BUNDLED WITH - 2.6.3 + 2.7.2 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 34d931b4..0cacd8cc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base # rubocop:disable Metrics/C before_action :set_paper_trail_whodunnit before_action :set_actionmailer_host before_action :check_accepted_terms, if: :user_signed_in? + before_action :set_i18n # protect_from_forgery with: :exception protect_from_forgery prepend: true @@ -161,4 +162,13 @@ def flash_resource_not_found(_exception) flash[:error] = "Resource not found in workspace '#{current_workspace.name}'" redirect_to(root_path) end + + def set_i18n # rubocop:disable Metrics/AbcSize + I18n.locale = current_account&.default_locale.presence || I18n.default_locale + + return if I18n::Backend::ActiveRecord.config.scope == current_account&.i18n_scope.presence + + I18n::Backend::ActiveRecord.config.scope = current_account&.i18n_scope.presence + I18n.backend.reload! + end end diff --git a/app/controllers/impact_cards_controller.rb b/app/controllers/impact_cards_controller.rb index ae734299..000bb968 100644 --- a/app/controllers/impact_cards_controller.rb +++ b/app/controllers/impact_cards_controller.rb @@ -241,7 +241,8 @@ def impact_card_params # rubocop:disable Metrics/MethodLength def checklist_items_to_csv(checklist_items) # rubocop:disable Metrics/MethodLength CSV.generate(headers: true) do |csv| - csv << %w[initiative_name characteristic_name status comment] + initiative_name_field = "#{Initiative.model_name.human.downcase}_name" + csv << [initiative_name_field, 'characteristic_name', 'status', 'comment'] checklist_items.each do |item| csv << [ @@ -259,13 +260,20 @@ def import_checklist_items_from_csv(file, scorecard) # rubocop:disable Metrics/A errors = [] skipped_count = 0 invalid_status_errors = 0 + initiative_name_column_names = ["#{Initiative.model_name.human.downcase}_name", 'initiative_name'].uniq + initiative_name_column_name = nil # Valid status values valid_statuses = ChecklistItem.statuses.keys - ['no_comment'] CSV.foreach(file.path, headers: true, force_quotes: true) do |row| # rubocop:disable Metrics/BlockLength - # Skip empty rows - next if row['initiative_name'].blank? || row['characteristic_name'].blank? + if initiative_name_column_name.nil? + initiative_name_column_name = row.headers.find { |header| initiative_name_column_names.include?(header) } + end + + next if initiative_name_column_name.nil? + next if row[initiative_name_column_name].blank? + next if row['characteristic_name'].blank? # Skip rows with 'no_comment' status (nothing meaningful to import) if row['status']&.strip&.downcase == 'no_comment' @@ -275,7 +283,7 @@ def import_checklist_items_from_csv(file, scorecard) # rubocop:disable Metrics/A # Find the checklist item by initiative name and characteristic name within this specific scorecard checklist_item = find_checklist_item_by_names_in_scorecard( - row['initiative_name']&.strip, + row[initiative_name_column_name]&.strip, row['characteristic_name']&.strip, scorecard ) diff --git a/app/controllers/initiatives_controller.rb b/app/controllers/initiatives_controller.rb index 048eaf35..677a2659 100644 --- a/app/controllers/initiatives_controller.rb +++ b/app/controllers/initiatives_controller.rb @@ -141,7 +141,7 @@ def linked private def export_filename - "initiatives-#{Time.zone.today.strftime('%Y-%m-%d')}" + "#{Initiative.model_name.human.pluralize.downcase}-#{Time.zone.today.strftime('%Y-%m-%d')}" end def initiatives_to_csv(initiatives) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity @@ -152,8 +152,8 @@ def initiatives_to_csv(initiatives) # rubocop:disable Metrics/AbcSize,Metrics/Cy csv << (([ 'Name', 'Description', - 'Impact Card Name', - 'Impact Card Type', + "#{ScorecardType.model_name.human} Name", + "#{ScorecardType.model_name.human} Type", 'Started At', 'Finished At', 'Contact Name', @@ -162,9 +162,9 @@ def initiatives_to_csv(initiatives) # rubocop:disable Metrics/AbcSize,Metrics/Cy 'Contact Website', 'Contact Position' ] + 1.upto(max_organisation_index).map do |index| - "Stakeholder #{index} Name" + "#{Organisation.model_name.human.titleize} #{index} Name" end + 1.upto(max_subsystem_tag_index).map do |index| - "Subsystem Tag #{index} Name" + "#{SubsystemTag.model_name.human.titleize} #{index} Name" end) + ['Notes']) initiatives.each do |initiative| @@ -254,21 +254,30 @@ def import_initiatives_from_csv(file) # rubocop:disable Metrics/AbcSize,Metrics/ scorecard_errors = 0 scorecards_cache = {} + scorecard_name_column_names = ["#{Scorecard.model_name.human.titleize} Name", 'Impact Card Name'].uniq + scorecard_name_column_name = nil + # Build cache of scorecards for faster lookup policy_scope(Scorecard).each do |sc| scorecards_cache[sc.name.downcase] = sc end CSV.foreach(file.path, headers: true, force_quotes: true) do |row| # rubocop:disable Metrics/BlockLength + if scorecard_name_column_name.nil? + scorecard_name_column_name = scorecard_name_column_names.find { |col| row.headers.include?(col) } + end + # If we still don't have a scorecard name column, it means the CSV is invalid + # because it doesn't have the required columns + next if scorecard_name_column_name.nil? # Skip empty rows next if row['Name'].blank? || row['Name'].strip.empty? scorecard = nil - if row['Impact Card Name'].present? - scorecard = scorecards_cache[row['Impact Card Name'].downcase] + if row[scorecard_name_column_name].present? + scorecard = scorecards_cache[row[scorecard_name_column_name].downcase] unless scorecard scorecard_errors += 1 - errors << "Row #{$INPUT_LINE_NUMBER}: Impact card '#{row['Impact Card Name']}' not found" + errors << "Row #{$INPUT_LINE_NUMBER}: #{scorecard_name_column_name.downcase.upcase_first} '#{row[scorecard_name_column_name]}' not found" # rubocop:disable Layout/LineLength next end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a6166737..fc7a228d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -22,6 +22,10 @@ def brand_for_current_theme(logo_class: 'h-16 w-aut#o', title: nil, title_class: render 'branding', brand_image_path:, brand_text:, brand_text_class:, logo_class: end + def indefinite_article(string) + %w[a e i o u].include?(string.to_s[0].downcase) ? 'an' : 'a' + end + def link_to_registration(link_class: '') link_class = merge_tailwind_class( 'text-lg font-semibold leading-6 text-white bg-teal-900 hover:bg-teal-700 px-4 py-2 rounded-sm', link_class diff --git a/app/helpers/custom_form_builder.rb b/app/helpers/custom_form_builder.rb index c477fb68..f202c0c5 100644 --- a/app/helpers/custom_form_builder.rb +++ b/app/helpers/custom_form_builder.rb @@ -199,7 +199,7 @@ def label(method, content_or_options = nil, options = nil, &block) def multi_select(method, choices = nil, options = {}, html_options = {}, &block) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize placeholder = options.delete(:placeholder) || 'Select multiple options...' # rubocop:disable Naming/VariableName - toggleCountText = multi_select_toggle_count_text(method) + toggleCountText = multi_select_toggle_count_text(method, options) hs_select = MULTI_SELECT_DEFAULT_HS_SELECT.merge(placeholder:, toggleCountText:).to_json # rubocop:enable Naming/VariableName @@ -311,13 +311,17 @@ def merge_options(method:, options: {}, default_class: TEXT_FIELD_CLASS, error_c base_options.merge(options) end - def multi_select_toggle_count_text(method) - base_text = method.to_s.titleize.downcase.singularize + def multi_select_toggle_count_text(method, options = {}) + base_text = options[:base_text] || method.to_s.titleize.downcase.singularize pluralized_text = base_text.pluralize - diff = pluralized_text.gsub(base_text, '') + if pluralized_text.ends_with?('ies') + "#{base_text}/#{pluralized_text}" + else + diff = pluralized_text.gsub(base_text, '') - "#{base_text}(#{diff}) selected" + "#{base_text}(#{diff}) selected" + end end def wrap_field(method, classes: 'mt-2') diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index bab54db4..6839e549 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -49,14 +49,19 @@ def calculate_luminance(hex_color) # rubocop:disable Metrics/AbcSize 0.2126 * r + 0.7152 * g + 0.0722 * b end + # SMELL: This method has general utility beyond labels and should be moved to ApplicationHelper or a + # new SidebarHelper. + # SMELL: The name 'label_class_human_title' is misleading; consider renaming it to 'class_human_title'. def label_class_human_title(klass) klass.model_name.human.pluralize.titleize end + # SMELL: This method has general utility beyond labels and should be moved to ApplicationHelper or a new helper. def label_class_search_placeholder(klass) "Search #{klass.model_name.human.pluralize.downcase}..." end + # SMELL: This method has general utility beyond labels and should be moved to ApplicationHelper or a new helper. def label_class_button_name(klass) "Create #{klass.model_name.human.titleize}" end diff --git a/app/helpers/reports_helper.rb b/app/helpers/reports_helper.rb index 49b1f171..5af4b10e 100644 --- a/app/helpers/reports_helper.rb +++ b/app/helpers/reports_helper.rb @@ -15,7 +15,11 @@ def comments_report_name end def stakeholder_report_name - multiple_scorecards_types? ? 'Stakeholder Report' : "#{default_data_model_name} Stakeholder Report" + if multiple_scorecards_types? + "#{Organisation.model_name.human.titleize} Report" + else + "#{default_data_model_name} #{Organisation.model_name.human.titleize} Report" + end end def report_scorecard_label diff --git a/app/models/account.rb b/app/models/account.rb index 06cb5344..f4c202aa 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -5,6 +5,7 @@ # Table name: accounts # # id :bigint not null, primary key +# default_locale :string default("en"), not null # default_workspace_grid_mode :string default("classic"), not null # deleted_at :datetime # description :string @@ -52,6 +53,9 @@ class Account < ApplicationRecord after_create :create_default_workspace + # validate :default_i18n_scope, inclusion: { in: [nil] + I18n::Backend::ActiveRecord.config.available_scopes }, + # allow_nil: true + # TODO: Fix this to use the reminder days for each account (subtract expiry_final_reminder_days) scope :expiring_soon, lambda { @@ -68,6 +72,11 @@ def expiry_reminder_sent_on [expiry_initial_reminder_sent_on, expiry_final_reminder_sent_on].compact_blank.max end + # Returns a string that can be used as the i18n scope for this account + def i18n_scope + id.to_s + end + def max_users_reached? return false if max_users.zero? || max_users.blank? diff --git a/app/models/community.rb b/app/models/community.rb index 0a1e956f..25288a91 100644 --- a/app/models/community.rb +++ b/app/models/community.rb @@ -5,7 +5,7 @@ # Table name: communities # # id :integer not null, primary key -# color :string default("#82e150"), not null +# color :string default("#d444db"), not null # deleted_at :datetime # description :string # name :string diff --git a/app/models/reports/base.rb b/app/models/reports/base.rb index cd9f3a35..96983491 100644 --- a/app/models/reports/base.rb +++ b/app/models/reports/base.rb @@ -30,5 +30,25 @@ def header_styles(package) h3: package.workbook.styles.add_style(bg_color: 'dce6f1', fg_color: '386190', sz: 12, b: false) } end + + def with_i18n_scope(locale: 'en', scope: nil) # rubocop:disable Metrics/MethodLength + current_locale = I18n.locale + current_scope = I18n::Backend::ActiveRecord.config.scope + + I18n.locale = locale + + if current_scope != scope + I18n::Backend::ActiveRecord.config.scope = scope + I18n.backend.reload! + end + + yield if block_given? + ensure + I18n.locale = current_locale + if current_scope != scope + I18n::Backend::ActiveRecord.config.scope = current_scope + I18n.backend.reload! + end + end end end diff --git a/app/models/reports/impact_card_activity.rb b/app/models/reports/impact_card_activity.rb index 38c39f50..873d2e52 100644 --- a/app/models/reports/impact_card_activity.rb +++ b/app/models/reports/impact_card_activity.rb @@ -16,54 +16,56 @@ def initialize(scorecard, date_from, date_to) end def to_xlsx # rubocop:disable Metrics/AbcSize,Metrics/MethodLength - Axlsx::Package.new do |p| # rubocop:disable Metrics/BlockLength - p.workbook.styles.fonts.first.name = 'Calibri' - - header_1 = header_1_style(p) # rubocop:disable Naming/VariableNumber - header_2 = header_2_style(p) # rubocop:disable Naming/VariableNumber - header_3 = header_3_style(p) # rubocop:disable Naming/VariableNumber - blue_normal = blue_normal_style(p) - wrap_text = wrap_text_style(p) - date = date_style(p) - - p.workbook.add_worksheet(name: 'Report') do |sheet| # rubocop:disable Metrics/BlockLength - add_report_header(sheet, header_1, blue_normal, date) - sheet.add_row - add_initiative_columns_header(sheet, wrap_text) - add_initiative_totals(sheet, header_1) - add_characteristic_columns_header(sheet, header_1, wrap_text) - - data = ImpactCardChecklistItems.execute(scorecard.id, date_from, date_to) - - current_focus_area_group_name = '' - current_focus_area_name = '' - - data.each do |row| - if row[:focus_area_group_name] != current_focus_area_group_name - current_focus_area_group_name = row[:focus_area_group_name] - current_focus_area_name = '' - sheet.add_row([row[:focus_area_group_name], '', '', '', '', ''], style: header_2) + with_i18n_scope(scope: scorecard.account.i18n_scope) do # rubocop:disable Metrics/BlockLength + Axlsx::Package.new do |p| # rubocop:disable Metrics/BlockLength + p.workbook.styles.fonts.first.name = 'Calibri' + + header_1 = header_1_style(p) # rubocop:disable Naming/VariableNumber + header_2 = header_2_style(p) # rubocop:disable Naming/VariableNumber + header_3 = header_3_style(p) # rubocop:disable Naming/VariableNumber + blue_normal = blue_normal_style(p) + wrap_text = wrap_text_style(p) + date = date_style(p) + + p.workbook.add_worksheet(name: 'Report') do |sheet| # rubocop:disable Metrics/BlockLength + add_report_header(sheet, header_1, blue_normal, date) + sheet.add_row + add_initiative_columns_header(sheet, wrap_text) + add_initiative_totals(sheet, header_1) + add_characteristic_columns_header(sheet, header_1, wrap_text) + + data = ImpactCardChecklistItems.execute(scorecard.id, date_from, date_to) + + current_focus_area_group_name = '' + current_focus_area_name = '' + + data.each do |row| + if row[:focus_area_group_name] != current_focus_area_group_name + current_focus_area_group_name = row[:focus_area_group_name] + current_focus_area_name = '' + sheet.add_row([row[:focus_area_group_name], '', '', '', '', ''], style: header_2) + end + + if row[:focus_area_name] != current_focus_area_name + current_focus_area_name = row[:focus_area_name] + sheet.add_row([" #{row[:focus_area_name]}", '', '', '', '', ''], style: header_3) + end + + sheet.add_row( + [ + " #{row[:characteristic_name]}", + row[:actual_count_before_period], + row[:additions_count_during_period], + row[:actual_count_after_period], + row[:assigned_actuals_count_during_period] + ] + ) end - if row[:focus_area_name] != current_focus_area_name - current_focus_area_name = row[:focus_area_name] - sheet.add_row([" #{row[:focus_area_name]}", '', '', '', '', ''], style: header_3) - end - - sheet.add_row( - [ - " #{row[:characteristic_name]}", - row[:actual_count_before_period], - row[:additions_count_during_period], - row[:actual_count_after_period], - row[:assigned_actuals_count_during_period] - ] - ) + set_column_widths(sheet) end - - set_column_widths(sheet) - end - end.to_stream + end.to_stream + end end private @@ -86,10 +88,10 @@ def add_initiative_columns_header(sheet, wrap_text) # rubocop:disable Metrics/Me sheet.add_row( [ '', - 'Initiatives beginning of period', + "#{Initiative.model_name.human.pluralize} beginning of period", 'Additions', 'Removals', - 'Initiatives end of period' + "#{Initiative.model_name.human.pluralize} end of period" ], height: 48, style: wrap_text @@ -99,7 +101,7 @@ def add_initiative_columns_header(sheet, wrap_text) # rubocop:disable Metrics/Me def add_initiative_totals(sheet, header_1) # rubocop:disable Naming/VariableNumber sheet.add_row( [ - "Total #{scorecard_model_name} Initiatives", + "Total #{scorecard_model_name} #{Initiative.model_name.human.pluralize}", initiative_totals[:initial], initiative_totals[:additions], initiative_totals[:removals], diff --git a/app/models/reports/impact_card_stakeholders.rb b/app/models/reports/impact_card_stakeholders.rb index 2f14e939..7c901ca0 100644 --- a/app/models/reports/impact_card_stakeholders.rb +++ b/app/models/reports/impact_card_stakeholders.rb @@ -24,23 +24,25 @@ def initialize(scorecard) # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/AbcSize def to_xlsx - Axlsx::Package.new do |p| - p.workbook.styles.fonts.first.name = 'Calibri' - styles = default_styles(p) - - p.workbook.add_worksheet(name: 'Report') do |sheet| - sheet.add_row([DateTime.now], style: styles[:date]) - add_title(sheet, styles) - sheet.add_row - add_summary(sheet, styles) - sheet.add_row - add_unique_organisations(sheet, styles) - sheet.add_row - add_initiatives(sheet, styles) - sheet.add_row - add_stakeholder_types(sheet, styles) - end - end.to_stream + with_i18n_scope(scope: scorecard.account.i18n_scope) do + Axlsx::Package.new do |p| + p.workbook.styles.fonts.first.name = 'Calibri' + styles = default_styles(p) + + p.workbook.add_worksheet(name: 'Report') do |sheet| + sheet.add_row([DateTime.now], style: styles[:date]) + add_title(sheet, styles) + sheet.add_row + add_summary(sheet, styles) + sheet.add_row + add_unique_organisations(sheet, styles) + sheet.add_row + add_initiatives(sheet, styles) + sheet.add_row + add_stakeholder_types(sheet, styles) + end + end.to_stream + end end # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/AbcSize @@ -59,15 +61,25 @@ def scorecard_model_name end def add_summary(sheet, styles) - sheet.add_row(['Total Partnering Stakeholders', total_partnering_organisations], style: styles[:h1]) sheet.add_row( - ["Total #{scorecard_model_name} Initiatives", total_transition_card_initiatives], + ["Total Partnering #{Organisation.model_name.human.pluralize.titleize}", + total_partnering_organisations], style: styles[:h1] + ) + sheet.add_row( + ["Total #{scorecard_model_name} #{Initiative.model_name.human.pluralize.titleize}", + total_transition_card_initiatives], style: styles[:h1] ) end - def add_initiatives(sheet, styles) - sheet.add_row(['Initiatives', 'Total Stakeholders', 'Stakeholders'], style: styles[:h3]) + def add_initiatives(sheet, styles) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength + sheet.add_row( + [ + Initiative.model_name.human.pluralize.titleize, + "Total #{Organisation.model_name.human.pluralize.titleize}", + Organisation.model_name.human.pluralize.titleize + ], style: styles[:h3] + ) initiatives.each do |initiative| name = initiative.name organisations = organisations_for_initiative(initiative) @@ -78,8 +90,14 @@ def add_initiatives(sheet, styles) end end - def add_stakeholder_types(sheet, styles) - sheet.add_row(['Stakeholder Type', 'Total Stakeholders', 'Stakeholders'], style: styles[:h3]) + def add_stakeholder_types(sheet, styles) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength + sheet.add_row( + [ + StakeholderType.model_name.human.titleize, + "Total #{Organisation.model_name.human.pluralize.titleize}", + Organisation.model_name.human.pluralize.titleize + ], style: styles[:h3] + ) stakeholder_types.each do |stakeholder_type| name = stakeholder_type.name organisations = organisations_for_stakeholder_type(stakeholder_type) @@ -96,17 +114,19 @@ def add_title(sheet, styles) sheet.add_row(['Community', scorecard.community&.name || 'NOT DEFINED']) end - ORGANISTION_SECTION_HEADERS = [ - 'Organisations', - 'Betweenness Centrality', - 'Total Connections', - 'Total Initiatives', - 'Stakeholder Type', - 'Initiatives' - ].freeze + def organisation_section_headers + [ + Organisation.model_name.human.pluralize.titleize, + 'Betweenness Centrality', + 'Total Connections', + "Total #{Initiative.model_name.human.pluralize.titleize}", + StakeholderType.model_name.human.titleize, + Initiative.model_name.human.pluralize.titleize + ] + end def add_unique_organisations(sheet, styles) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength - sheet.add_row(ORGANISTION_SECTION_HEADERS, style: styles[:h3]) + sheet.add_row(organisation_section_headers, style: styles[:h3]) unique_organisations.each do |organisation| name = organisation.name diff --git a/app/models/reports/scorecard_comments.rb b/app/models/reports/scorecard_comments.rb index d1d0d808..79bffc06 100644 --- a/app/models/reports/scorecard_comments.rb +++ b/app/models/reports/scorecard_comments.rb @@ -19,73 +19,75 @@ def initiatives end def to_xlsx # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity - padding_plus_2 = Array.new(initiatives.count + 2, '') # rubocop:disable Naming/VariableNumber - - Axlsx::Package.new do |p| # rubocop:disable Metrics/BlockLength - p.workbook.styles.fonts.first.name = 'Calibri' - - # rubocop:disable Naming/VariableNumber - styles = { - header_1: p.workbook.styles.add_style(fg_color: '386190', sz: 16, b: true), - header_2: p.workbook.styles.add_style(bg_color: 'dce6f1', fg_color: '386190', sz: 12, b: true), - header_3: p.workbook.styles.add_style(bg_color: 'dce6f1', fg_color: '386190', sz: 12, b: false), - blue_normal: p.workbook.styles.add_style(fg_color: '386190', sz: 12, b: false), - wrap_text: p.workbook.styles.add_style( - alignment: { - horizontal: :general, - vertical: :bottom, - wrap_text: true - } - ), - date: date_style(p) - } - # rubocop:enable Naming/VariableNumber - - p.workbook.add_worksheet(name: 'Report') do |sheet| # rubocop:disable Metrics/BlockLength - add_header(sheet, styles) - - data = generate_data - - scorecard.data_model.focus_area_groups.order(:position).each do |focus_area_group| # rubocop:disable Metrics/BlockLength - sheet.add_row([focus_area_group.name] + padding_plus_2, style: styles[:header_2]) # rubocop:disable Naming/VariableNumber - - focus_area_group.focus_areas.order(:position).each do |focus_area| - sheet.add_row([" #{focus_area.name}"] + padding_plus_2, style: styles[:header_3]) # rubocop:disable Naming/VariableNumber - - focus_area.characteristics.order(:position).each do |characteristic| - data_row = data[characteristic.id] - - initiative_comments = - if data_row.present? - initiatives.map do |initiative| - comment_data = data_row[:comments].find { |d| d[:initiative_id] == initiative.id } - - next '' if comment_data.blank? - - comment_data[:comments] - .sort_by { |c| c[:date] } - .reverse - .map { |c| "[#{utc_date_time_string_to_date_time(c[:date])}] #{c[:comment]}" } - .join('; ') + with_i18n_scope(scope: scorecard.account.i18n_scope) do # rubocop:disable Metrics/BlockLength + padding_plus_2 = Array.new(initiatives.count + 2, '') # rubocop:disable Naming/VariableNumber + + Axlsx::Package.new do |p| # rubocop:disable Metrics/BlockLength + p.workbook.styles.fonts.first.name = 'Calibri' + + # rubocop:disable Naming/VariableNumber + styles = { + header_1: p.workbook.styles.add_style(fg_color: '386190', sz: 16, b: true), + header_2: p.workbook.styles.add_style(bg_color: 'dce6f1', fg_color: '386190', sz: 12, b: true), + header_3: p.workbook.styles.add_style(bg_color: 'dce6f1', fg_color: '386190', sz: 12, b: false), + blue_normal: p.workbook.styles.add_style(fg_color: '386190', sz: 12, b: false), + wrap_text: p.workbook.styles.add_style( + alignment: { + horizontal: :general, + vertical: :bottom, + wrap_text: true + } + ), + date: date_style(p) + } + # rubocop:enable Naming/VariableNumber + + p.workbook.add_worksheet(name: 'Report') do |sheet| # rubocop:disable Metrics/BlockLength + add_header(sheet, styles) + + data = generate_data + + scorecard.data_model.focus_area_groups.order(:position).each do |focus_area_group| # rubocop:disable Metrics/BlockLength + sheet.add_row([focus_area_group.name] + padding_plus_2, style: styles[:header_2]) # rubocop:disable Naming/VariableNumber + + focus_area_group.focus_areas.order(:position).each do |focus_area| + sheet.add_row([" #{focus_area.name}"] + padding_plus_2, style: styles[:header_3]) # rubocop:disable Naming/VariableNumber + + focus_area.characteristics.order(:position).each do |characteristic| + data_row = data[characteristic.id] + + initiative_comments = + if data_row.present? + initiatives.map do |initiative| + comment_data = data_row[:comments].find { |d| d[:initiative_id] == initiative.id } + + next '' if comment_data.blank? + + comment_data[:comments] + .sort_by { |c| c[:date] } + .reverse + .map { |c| "[#{utc_date_time_string_to_date_time(c[:date])}] #{c[:comment]}" } + .join('; ') + end + else + initiatives.map { '' } end - else - initiatives.map { '' } - end - - sheet.add_row( - [ - " #{characteristic.name}", - data_row&.dig(:initiative_count) || 0, - data_row&.dig(:initiative_comment_count) || 0 - ] + initiative_comments - ) + + sheet.add_row( + [ + " #{characteristic.name}", + data_row&.dig(:initiative_count) || 0, + data_row&.dig(:initiative_comment_count) || 0 + ] + initiative_comments + ) + end end end - end - sheet.column_widths(75.5, 10, 10) - end - end.to_stream + sheet.column_widths(75.5, 10, 10) + end + end.to_stream + end end private @@ -110,7 +112,7 @@ def add_header(sheet, styles) # rubocop:disable Metrics/AbcSize,Metrics/MethodLe sheet.add_row - sheet.add_row(['', 'Total initiatives', 'Total comments'] + initiatives.map(&:name)) + sheet.add_row(['', "Total #{Initiative.model_name.human.pluralize}", 'Total comments'] + initiatives.map(&:name)) end def utc_date_time_string_to_date_time(date_str) diff --git a/app/models/reports/subsystem_summary.rb b/app/models/reports/subsystem_summary.rb index e82df9a1..9d0ee2dc 100644 --- a/app/models/reports/subsystem_summary.rb +++ b/app/models/reports/subsystem_summary.rb @@ -15,20 +15,22 @@ def initialize(scorecard) # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/AbcSize def to_xlsx - Axlsx::Package.new do |p| - p.workbook.styles.fonts.first.name = 'Calibri' - styles = default_styles(p) - p.workbook.add_worksheet(name: 'Report') do |sheet| - sheet.add_row([Time.zone.now], style: styles[:date]) - add_title(sheet, styles) - sheet.add_row - add_summary(sheet, styles) - sheet.add_row - add_initiatives_per_subsystem(sheet, styles) - sheet.add_row - add_organisations_per_subsystem(sheet, styles) - end - end.to_stream + with_i18n_scope(scope: scorecard.account.i18n_scope) do + Axlsx::Package.new do |p| + p.workbook.styles.fonts.first.name = 'Calibri' + styles = default_styles(p) + p.workbook.add_worksheet(name: 'Report') do |sheet| + sheet.add_row([Time.zone.now], style: styles[:date]) + add_title(sheet, styles) + sheet.add_row + add_summary(sheet, styles) + sheet.add_row + add_initiatives_per_subsystem(sheet, styles) + sheet.add_row + add_organisations_per_subsystem(sheet, styles) + end + end.to_stream + end end # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/AbcSize @@ -46,31 +48,43 @@ def add_title(sheet, styles) sheet.add_row(['Community', scorecard.community&.name || 'NOT DEFINED']) end - def add_summary(sheet, styles) + def add_summary(sheet, styles) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength sheet.add_row(['Total Subsystems', total_subsystems], style: styles[:h1]) - sheet.add_row(['Total Partnering Stakeholders', total_partnering_organisations], style: styles[:h1]) sheet.add_row( - ["Total #{scorecard_model_name} Initiatives", total_transition_card_initiatives], + [ + "Total Partnering #{Organisation.model_name.human.pluralize.titleize}", + total_partnering_organisations + ], style: styles[:h1] + ) + sheet.add_row( + ["Total #{scorecard_model_name} #{Initiative.model_name.human.pluralize.titleize}", + total_transition_card_initiatives], style: styles[:h1] ) end - def add_initiatives_per_subsystem(sheet, styles) + def add_initiatives_per_subsystem(sheet, styles) # rubocop:disable Metrics/AbcSize initiatives_per_subsystem = fetch_initiatives_per_subsystem max_initiatives = initiatives_per_subsystem.values.map(&:count).max || 0 - sheet.add_row(['Subsystem ', 'Total Initiatives'] + Array.new(max_initiatives, 'Initiatives'), style: styles[:h3]) + sheet.add_row( + ['Subsystem ', + "Total #{Initiative.model_name.human.pluralize.titleize}"] + + Array.new(max_initiatives, Initiative.model_name.human.pluralize.titleize), style: styles[:h3] + ) initiatives_per_subsystem.each do |subsystem, initiatives| sheet.add_row([subsystem, initiatives.count] + initiatives) end end - def add_organisations_per_subsystem(sheet, styles) + def add_organisations_per_subsystem(sheet, styles) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength organisations_per_subsystem = fetch_organisations_per_subsystem max_organisations = organisations_per_subsystem.values.map(&:count).max || 0 sheet.add_row( - ['Subsystem ', 'Total Stakeholders'] + Array.new(max_organisations, 'Stakeholders'), + ['Subsystem ', + "Total #{Organisation.model_name.human.pluralize.titleize}"] + + Array.new(max_organisations, Organisation.model_name.human.pluralize.titleize), style: styles[:h3] ) organisations_per_subsystem.each do |subsystem, organisations| diff --git a/app/models/scorecard.rb b/app/models/scorecard.rb index 9adf00b6..4e6ae6d4 100644 --- a/app/models/scorecard.rb +++ b/app/models/scorecard.rb @@ -66,7 +66,7 @@ class Scorecard < ApplicationRecord delegate :name, :description, to: :wicked_problem, prefix: true, allow_nil: true delegate :name, :description, to: :community, prefix: true, allow_nil: true - delegate :stakeholder_types, to: :workspace + delegate :stakeholder_types, :account, to: :workspace accepts_nested_attributes_for :initiatives, allow_destroy: true, diff --git a/app/models/subsystem_tag.rb b/app/models/subsystem_tag.rb index 07ae3115..9e894078 100644 --- a/app/models/subsystem_tag.rb +++ b/app/models/subsystem_tag.rb @@ -5,7 +5,7 @@ # Table name: subsystem_tags # # id :integer not null, primary key -# color :string default("#b914aa"), not null +# color :string default("#eeef11"), not null # deleted_at :datetime # description :string # name :string diff --git a/app/models/wicked_problem.rb b/app/models/wicked_problem.rb index 72ce6d88..f9f39a74 100644 --- a/app/models/wicked_problem.rb +++ b/app/models/wicked_problem.rb @@ -5,7 +5,7 @@ # Table name: wicked_problems # # id :integer not null, primary key -# color :string default("#b85d73"), not null +# color :string default("#83306b"), not null # deleted_at :datetime # description :string # name :string diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb index efb1cb96..2e87a57f 100644 --- a/app/views/dashboard/index.html.erb +++ b/app/views/dashboard/index.html.erb @@ -15,16 +15,16 @@ <% end %>
This action cannot be undone. This will permanently delete the impact card and all associated data.
+This action cannot be undone. This will permanently delete the <%= Scorecard.model_name.human.downcase %> and all associated data.
Merge another impact card and all its associated data into this card.
++ Merge another <%= Scorecard.model_name.human.downcase %> and all its associated data into this card. +
A brief description of the impact card (optional).
+A brief description of the <%= Scorecard.model_name.human.downcase %> (optional).