From 80d9f7f71ec8de921bbc40b900381b0c81a0ca7c Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 4 Dec 2024 14:21:14 +0100 Subject: [PATCH 001/103] add action_text Signed-off-by: Florian --- .rspec | 1 + app/assets/stylesheets/actiontext.css | 31 ++++++++++ app/views/active_storage/blobs/_blob.html.erb | 14 +++++ .../action_text/contents/_content.html.erb | 3 + ...te_active_storage_tables.active_storage.rb | 57 +++++++++++++++++++ ...0_create_action_text_tables.action_text.rb | 26 +++++++++ db/schema.rb | 42 +++++++++++++- package.json | 2 + yarn.lock | 24 ++++++++ 9 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 .rspec create mode 100644 app/assets/stylesheets/actiontext.css create mode 100644 app/views/active_storage/blobs/_blob.html.erb create mode 100644 app/views/layouts/action_text/contents/_content.html.erb create mode 100644 db/migrate/20241204132029_create_active_storage_tables.active_storage.rb create mode 100644 db/migrate/20241204132030_create_action_text_tables.action_text.rb diff --git a/.rspec b/.rspec new file mode 100644 index 000000000..c99d2e739 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/app/assets/stylesheets/actiontext.css b/app/assets/stylesheets/actiontext.css new file mode 100644 index 000000000..3cfcb2b75 --- /dev/null +++ b/app/assets/stylesheets/actiontext.css @@ -0,0 +1,31 @@ +/* + * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and + * the trix-editor content (whether displayed or under editing). Feel free to incorporate this + * inclusion directly in any other asset bundle and remove this file. + * + *= require trix +*/ + +/* + * We need to override trix.css’s image gallery styles to accommodate the + * element we wrap around attachments. Otherwise, + * images in galleries will be squished by the max-width: 33%; rule. +*/ +.trix-content .attachment-gallery > action-text-attachment, +.trix-content .attachment-gallery > .attachment { + flex: 1 0 33%; + padding: 0 0.5em; + max-width: 33%; +} + +.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment, +.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment, +.trix-content .attachment-gallery.attachment-gallery--4 > .attachment { + flex-basis: 50%; + max-width: 50%; +} + +.trix-content action-text-attachment .attachment { + padding: 0 !important; + max-width: 100% !important; +} diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb new file mode 100644 index 000000000..49ba357dd --- /dev/null +++ b/app/views/active_storage/blobs/_blob.html.erb @@ -0,0 +1,14 @@ +
attachment--<%= blob.filename.extension %>"> + <% if blob.representable? %> + <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> + <% end %> + +
+ <% if caption = blob.try(:caption) %> + <%= caption %> + <% else %> + <%= blob.filename %> + <%= number_to_human_size blob.byte_size %> + <% end %> +
+
diff --git a/app/views/layouts/action_text/contents/_content.html.erb b/app/views/layouts/action_text/contents/_content.html.erb new file mode 100644 index 000000000..9e3c0d0df --- /dev/null +++ b/app/views/layouts/action_text/contents/_content.html.erb @@ -0,0 +1,3 @@ +
+ <%= yield -%> +
diff --git a/db/migrate/20241204132029_create_active_storage_tables.active_storage.rb b/db/migrate/20241204132029_create_active_storage_tables.active_storage.rb new file mode 100644 index 000000000..e4706aa21 --- /dev/null +++ b/db/migrate/20241204132029_create_active_storage_tables.active_storage.rb @@ -0,0 +1,57 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[7.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :active_storage_blobs, id: primary_key_type do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.string :service_name, null: false + t.bigint :byte_size, null: false + t.string :checksum + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments, id: primary_key_type do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + t.references :blob, null: false, type: foreign_key_type + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + + create_table :active_storage_variant_records, id: primary_key_type do |t| + t.belongs_to :blob, null: false, index: false, type: foreign_key_type + t.string :variation_digest, null: false + + t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [primary_key_type, foreign_key_type] + end +end diff --git a/db/migrate/20241204132030_create_action_text_tables.action_text.rb b/db/migrate/20241204132030_create_action_text_tables.action_text.rb new file mode 100644 index 000000000..1be48d70b --- /dev/null +++ b/db/migrate/20241204132030_create_action_text_tables.action_text.rb @@ -0,0 +1,26 @@ +# This migration comes from action_text (originally 20180528164100) +class CreateActionTextTables < ActiveRecord::Migration[6.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :action_text_rich_texts, id: primary_key_type do |t| + t.string :name, null: false + t.text :body, size: :long + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + + t.timestamps + + t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true + end + end + + private + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [primary_key_type, foreign_key_type] + end +end diff --git a/db/schema.rb b/db/schema.rb index 0a6d204ba..29c7fcaf9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,49 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_09_21_221000) do +ActiveRecord::Schema[7.1].define(version: 2024_12_04_132030) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" + create_table "action_text_rich_texts", force: :cascade do |t| + t.string "name", null: false + t.text "body" + t.string "record_type", null: false + t.bigint "record_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true + end + + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum" + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + create_table "annotations", force: :cascade do |t| t.bigint "medium_id", null: false t.bigint "user_id", null: false @@ -962,6 +1000,8 @@ t.index ["watchlist_entry_id"], name: "index_watchlists_on_watchlist_entry_id" end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "annotations", "media" add_foreign_key "annotations", "users" add_foreign_key "announcements", "lectures" diff --git a/package.json b/package.json index 92b90cc56..60f7c0edf 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "mampf", "private": true, "dependencies": { + "@rails/actiontext": "^8.0.0", "@rails/webpacker": "5.4.4", "@webpack-cli/serve": "^1.7.0", "coffee-loader": "^1.0.1", @@ -13,6 +14,7 @@ "regenerator-runtime": "^0.13.11", "sass": "^1.63.4", "sass-loader": "^10.1.1", + "trix": "^2.1.8", "webpack": "^4.46.0", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.15.1" diff --git a/yarn.lock b/yarn.lock index 5399feb94..7fcd78eec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1126,6 +1126,20 @@ "@parcel/watcher-win32-ia32" "2.5.0" "@parcel/watcher-win32-x64" "2.5.0" +"@rails/actiontext@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@rails/actiontext/-/actiontext-8.0.0.tgz#01133b7bbb2eb541890bf3a8bdc761fc27f9a649" + integrity sha512-8pvXDEHqlVHptzfYDUXmBpstHsfHAVacYxO47cWDRjRmp1zdVXusLcom8UvqkRdTcAPXpte+LkjcfpD9S4DSSQ== + dependencies: + "@rails/activestorage" ">= 8.0.0-alpha" + +"@rails/activestorage@>= 8.0.0-alpha": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-8.0.0.tgz#be5dd10acc141b95ffa2b3026e61790c36f1679c" + integrity sha512-qoA7U1gMcWXhDnImwDIyRQDXkQKzThT2lu2Xpim8CnTOCEeAgkQ5Co2kzodpAI2grF1JSDvwXSPYNWwVAswndA== + dependencies: + spark-md5 "^3.0.1" + "@rails/webpacker@5.4.4": version "5.4.4" resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.4.4.tgz#971a41b987c096c908ce4088accd57c1a9a7e2f7" @@ -7415,6 +7429,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spark-md5@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" + integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== + spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -7792,6 +7811,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +trix@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/trix/-/trix-2.1.8.tgz#b9383af8cd9c1a0a0818d6b4e0c9e771bf7fd564" + integrity sha512-y1h5mKQcjMsZDsUOqOgyIUfw+Z31u4Fe9JqXtKGUzIC7FM9cTpxZFFWxQggwXBo18ccIKYx1Fn9toVO5mCpn9g== + ts-api-utils@^1.0.1: version "1.4.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" From 63c9672fec16077b9af7763f8f9e89be7e7f8906 Mon Sep 17 00:00:00 2001 From: Florian Date: Wed, 4 Dec 2024 14:40:38 +0100 Subject: [PATCH 002/103] add questionaire and slide model for vignettes Signed-off-by: Florian --- .../vignettes/questionnaires_controller.rb | 22 ++++++++ .../vignettes/slides_controller.rb | 22 ++++++++ .../vignettes/questionnaires_helper.rb | 2 + app/helpers/vignettes/slides_helper.rb | 2 + app/models/vignettes.rb | 5 ++ app/models/vignettes/questionnaire.rb | 5 ++ app/models/vignettes/slide.rb | 6 +++ .../vignettes/questionnaires/create.html.erb | 2 + .../vignettes/questionnaires/destroy.html.erb | 2 + .../vignettes/questionnaires/edit.html.erb | 2 + .../vignettes/questionnaires/index.html.erb | 2 + .../vignettes/questionnaires/new.html.erb | 2 + .../vignettes/questionnaires/show.html.erb | 2 + .../vignettes/questionnaires/update.html.erb | 2 + app/views/vignettes/slides/create.html.erb | 2 + app/views/vignettes/slides/destroy.html.erb | 2 + app/views/vignettes/slides/edit.html.erb | 2 + app/views/vignettes/slides/index.html.erb | 2 + app/views/vignettes/slides/new.html.erb | 2 + app/views/vignettes/slides/show.html.erb | 2 + app/views/vignettes/slides/update.html.erb | 2 + config/routes.rb | 21 ++++++++ ...4132354_create_vignettes_questionnaires.rb | 9 ++++ .../20241204132407_create_vignettes_slides.rb | 9 ++++ db/schema.rb | 16 +++++- spec/factories/vignettes/questionnaires.rb | 5 ++ spec/factories/vignettes/slides.rb | 6 +++ .../vignettes/questionnaires_helper_spec.rb | 15 ++++++ spec/helpers/vignettes/slides_helper_spec.rb | 15 ++++++ spec/models/vignettes/questionnaire_spec.rb | 5 ++ spec/models/vignettes/slide_spec.rb | 5 ++ .../requests/vignettes/questionnaires_spec.rb | 53 +++++++++++++++++++ spec/requests/vignettes/slides_spec.rb | 53 +++++++++++++++++++ .../questionnaires/create.html.erb_spec.rb | 5 ++ .../questionnaires/destroy.html.erb_spec.rb | 5 ++ .../questionnaires/edit.html.erb_spec.rb | 5 ++ .../questionnaires/index.html.erb_spec.rb | 5 ++ .../questionnaires/new.html.erb_spec.rb | 5 ++ .../questionnaires/show.html.erb_spec.rb | 5 ++ .../questionnaires/update.html.erb_spec.rb | 5 ++ .../vignettes/slides/create.html.erb_spec.rb | 5 ++ .../vignettes/slides/destroy.html.erb_spec.rb | 5 ++ .../vignettes/slides/edit.html.erb_spec.rb | 5 ++ .../vignettes/slides/index.html.erb_spec.rb | 5 ++ .../vignettes/slides/new.html.erb_spec.rb | 5 ++ .../vignettes/slides/show.html.erb_spec.rb | 5 ++ .../vignettes/slides/update.html.erb_spec.rb | 5 ++ 47 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 app/controllers/vignettes/questionnaires_controller.rb create mode 100644 app/controllers/vignettes/slides_controller.rb create mode 100644 app/helpers/vignettes/questionnaires_helper.rb create mode 100644 app/helpers/vignettes/slides_helper.rb create mode 100644 app/models/vignettes.rb create mode 100644 app/models/vignettes/questionnaire.rb create mode 100644 app/models/vignettes/slide.rb create mode 100644 app/views/vignettes/questionnaires/create.html.erb create mode 100644 app/views/vignettes/questionnaires/destroy.html.erb create mode 100644 app/views/vignettes/questionnaires/edit.html.erb create mode 100644 app/views/vignettes/questionnaires/index.html.erb create mode 100644 app/views/vignettes/questionnaires/new.html.erb create mode 100644 app/views/vignettes/questionnaires/show.html.erb create mode 100644 app/views/vignettes/questionnaires/update.html.erb create mode 100644 app/views/vignettes/slides/create.html.erb create mode 100644 app/views/vignettes/slides/destroy.html.erb create mode 100644 app/views/vignettes/slides/edit.html.erb create mode 100644 app/views/vignettes/slides/index.html.erb create mode 100644 app/views/vignettes/slides/new.html.erb create mode 100644 app/views/vignettes/slides/show.html.erb create mode 100644 app/views/vignettes/slides/update.html.erb create mode 100644 db/migrate/20241204132354_create_vignettes_questionnaires.rb create mode 100644 db/migrate/20241204132407_create_vignettes_slides.rb create mode 100644 spec/factories/vignettes/questionnaires.rb create mode 100644 spec/factories/vignettes/slides.rb create mode 100644 spec/helpers/vignettes/questionnaires_helper_spec.rb create mode 100644 spec/helpers/vignettes/slides_helper_spec.rb create mode 100644 spec/models/vignettes/questionnaire_spec.rb create mode 100644 spec/models/vignettes/slide_spec.rb create mode 100644 spec/requests/vignettes/questionnaires_spec.rb create mode 100644 spec/requests/vignettes/slides_spec.rb create mode 100644 spec/views/vignettes/questionnaires/create.html.erb_spec.rb create mode 100644 spec/views/vignettes/questionnaires/destroy.html.erb_spec.rb create mode 100644 spec/views/vignettes/questionnaires/edit.html.erb_spec.rb create mode 100644 spec/views/vignettes/questionnaires/index.html.erb_spec.rb create mode 100644 spec/views/vignettes/questionnaires/new.html.erb_spec.rb create mode 100644 spec/views/vignettes/questionnaires/show.html.erb_spec.rb create mode 100644 spec/views/vignettes/questionnaires/update.html.erb_spec.rb create mode 100644 spec/views/vignettes/slides/create.html.erb_spec.rb create mode 100644 spec/views/vignettes/slides/destroy.html.erb_spec.rb create mode 100644 spec/views/vignettes/slides/edit.html.erb_spec.rb create mode 100644 spec/views/vignettes/slides/index.html.erb_spec.rb create mode 100644 spec/views/vignettes/slides/new.html.erb_spec.rb create mode 100644 spec/views/vignettes/slides/show.html.erb_spec.rb create mode 100644 spec/views/vignettes/slides/update.html.erb_spec.rb diff --git a/app/controllers/vignettes/questionnaires_controller.rb b/app/controllers/vignettes/questionnaires_controller.rb new file mode 100644 index 000000000..0f3942bcd --- /dev/null +++ b/app/controllers/vignettes/questionnaires_controller.rb @@ -0,0 +1,22 @@ +class Vignettes::QuestionnairesController < ApplicationController + def index + end + + def show + end + + def new + end + + def create + end + + def edit + end + + def update + end + + def destroy + end +end diff --git a/app/controllers/vignettes/slides_controller.rb b/app/controllers/vignettes/slides_controller.rb new file mode 100644 index 000000000..a4e54cd39 --- /dev/null +++ b/app/controllers/vignettes/slides_controller.rb @@ -0,0 +1,22 @@ +class Vignettes::SlidesController < ApplicationController + def index + end + + def show + end + + def new + end + + def create + end + + def edit + end + + def update + end + + def destroy + end +end diff --git a/app/helpers/vignettes/questionnaires_helper.rb b/app/helpers/vignettes/questionnaires_helper.rb new file mode 100644 index 000000000..1dc3753e9 --- /dev/null +++ b/app/helpers/vignettes/questionnaires_helper.rb @@ -0,0 +1,2 @@ +module Vignettes::QuestionnairesHelper +end diff --git a/app/helpers/vignettes/slides_helper.rb b/app/helpers/vignettes/slides_helper.rb new file mode 100644 index 000000000..9d82aa8d2 --- /dev/null +++ b/app/helpers/vignettes/slides_helper.rb @@ -0,0 +1,2 @@ +module Vignettes::SlidesHelper +end diff --git a/app/models/vignettes.rb b/app/models/vignettes.rb new file mode 100644 index 000000000..b118ac843 --- /dev/null +++ b/app/models/vignettes.rb @@ -0,0 +1,5 @@ +module Vignettes + def self.table_name_prefix + "vignettes_" + end +end diff --git a/app/models/vignettes/questionnaire.rb b/app/models/vignettes/questionnaire.rb new file mode 100644 index 000000000..a38152173 --- /dev/null +++ b/app/models/vignettes/questionnaire.rb @@ -0,0 +1,5 @@ +module Vignettes + class Questionnaire < ApplicationRecord + has_many :slides, dependent: :destroy + end +end diff --git a/app/models/vignettes/slide.rb b/app/models/vignettes/slide.rb new file mode 100644 index 000000000..77460997c --- /dev/null +++ b/app/models/vignettes/slide.rb @@ -0,0 +1,6 @@ +module Vignettes + class Slide < ApplicationRecord + belongs_to :questionnaire + has_rich_text :content + end +end diff --git a/app/views/vignettes/questionnaires/create.html.erb b/app/views/vignettes/questionnaires/create.html.erb new file mode 100644 index 000000000..99c8ff6d1 --- /dev/null +++ b/app/views/vignettes/questionnaires/create.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Questionnaires#create

+

Find me in app/views/vignettes/questionnaires/create.html.erb

diff --git a/app/views/vignettes/questionnaires/destroy.html.erb b/app/views/vignettes/questionnaires/destroy.html.erb new file mode 100644 index 000000000..6d04570ad --- /dev/null +++ b/app/views/vignettes/questionnaires/destroy.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Questionnaires#destroy

+

Find me in app/views/vignettes/questionnaires/destroy.html.erb

diff --git a/app/views/vignettes/questionnaires/edit.html.erb b/app/views/vignettes/questionnaires/edit.html.erb new file mode 100644 index 000000000..e28205dde --- /dev/null +++ b/app/views/vignettes/questionnaires/edit.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Questionnaires#edit

+

Find me in app/views/vignettes/questionnaires/edit.html.erb

diff --git a/app/views/vignettes/questionnaires/index.html.erb b/app/views/vignettes/questionnaires/index.html.erb new file mode 100644 index 000000000..2d9e58aeb --- /dev/null +++ b/app/views/vignettes/questionnaires/index.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Questionnaires#index

+

Find me in app/views/vignettes/questionnaires/index.html.erb

diff --git a/app/views/vignettes/questionnaires/new.html.erb b/app/views/vignettes/questionnaires/new.html.erb new file mode 100644 index 000000000..b6366f5ff --- /dev/null +++ b/app/views/vignettes/questionnaires/new.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Questionnaires#new

+

Find me in app/views/vignettes/questionnaires/new.html.erb

diff --git a/app/views/vignettes/questionnaires/show.html.erb b/app/views/vignettes/questionnaires/show.html.erb new file mode 100644 index 000000000..f490def29 --- /dev/null +++ b/app/views/vignettes/questionnaires/show.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Questionnaires#show

+

Find me in app/views/vignettes/questionnaires/show.html.erb

diff --git a/app/views/vignettes/questionnaires/update.html.erb b/app/views/vignettes/questionnaires/update.html.erb new file mode 100644 index 000000000..4b2ab2794 --- /dev/null +++ b/app/views/vignettes/questionnaires/update.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Questionnaires#update

+

Find me in app/views/vignettes/questionnaires/update.html.erb

diff --git a/app/views/vignettes/slides/create.html.erb b/app/views/vignettes/slides/create.html.erb new file mode 100644 index 000000000..dfc01807c --- /dev/null +++ b/app/views/vignettes/slides/create.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Slides#create

+

Find me in app/views/vignettes/slides/create.html.erb

diff --git a/app/views/vignettes/slides/destroy.html.erb b/app/views/vignettes/slides/destroy.html.erb new file mode 100644 index 000000000..85a83ef18 --- /dev/null +++ b/app/views/vignettes/slides/destroy.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Slides#destroy

+

Find me in app/views/vignettes/slides/destroy.html.erb

diff --git a/app/views/vignettes/slides/edit.html.erb b/app/views/vignettes/slides/edit.html.erb new file mode 100644 index 000000000..75c8efd20 --- /dev/null +++ b/app/views/vignettes/slides/edit.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Slides#edit

+

Find me in app/views/vignettes/slides/edit.html.erb

diff --git a/app/views/vignettes/slides/index.html.erb b/app/views/vignettes/slides/index.html.erb new file mode 100644 index 000000000..b63293821 --- /dev/null +++ b/app/views/vignettes/slides/index.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Slides#index

+

Find me in app/views/vignettes/slides/index.html.erb

diff --git a/app/views/vignettes/slides/new.html.erb b/app/views/vignettes/slides/new.html.erb new file mode 100644 index 000000000..0d878fe23 --- /dev/null +++ b/app/views/vignettes/slides/new.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Slides#new

+

Find me in app/views/vignettes/slides/new.html.erb

diff --git a/app/views/vignettes/slides/show.html.erb b/app/views/vignettes/slides/show.html.erb new file mode 100644 index 000000000..7947d6abe --- /dev/null +++ b/app/views/vignettes/slides/show.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Slides#show

+

Find me in app/views/vignettes/slides/show.html.erb

diff --git a/app/views/vignettes/slides/update.html.erb b/app/views/vignettes/slides/update.html.erb new file mode 100644 index 000000000..7fb2828e4 --- /dev/null +++ b/app/views/vignettes/slides/update.html.erb @@ -0,0 +1,2 @@ +

Vignettes::Slides#update

+

Find me in app/views/vignettes/slides/update.html.erb

diff --git a/config/routes.rb b/config/routes.rb index f567579cd..77fce6cb7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -561,6 +561,27 @@ resources :vertices, except: [:index, :show, :edit] end + # vignettes routes + namespace :vignettes do + get 'slides/index' + get 'slides/show' + get 'slides/new' + get 'slides/create' + get 'slides/edit' + get 'slides/update' + get 'slides/destroy' + get 'questionnaires/index' + get 'questionnaires/show' + get 'questionnaires/new' + get 'questionnaires/create' + get 'questionnaires/edit' + get 'questionnaires/update' + get 'questionnaires/destroy' + resources :questionnaires do + resources :slides + end + end + # readers routes patch "readers/update", diff --git a/db/migrate/20241204132354_create_vignettes_questionnaires.rb b/db/migrate/20241204132354_create_vignettes_questionnaires.rb new file mode 100644 index 000000000..37e074575 --- /dev/null +++ b/db/migrate/20241204132354_create_vignettes_questionnaires.rb @@ -0,0 +1,9 @@ +class CreateVignettesQuestionnaires < ActiveRecord::Migration[7.1] + def change + create_table :vignettes_questionnaires do |t| + t.string :title + + t.timestamps + end + end +end diff --git a/db/migrate/20241204132407_create_vignettes_slides.rb b/db/migrate/20241204132407_create_vignettes_slides.rb new file mode 100644 index 000000000..d1ccc77ed --- /dev/null +++ b/db/migrate/20241204132407_create_vignettes_slides.rb @@ -0,0 +1,9 @@ +class CreateVignettesSlides < ActiveRecord::Migration[7.1] + def change + create_table :vignettes_slides do |t| + t.references :vignettes_questionnaire, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 29c7fcaf9..c9facb30b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_12_04_132030) do +ActiveRecord::Schema[7.1].define(version: 2024_12_04_132407) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -943,6 +943,19 @@ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end + create_table "vignettes_questionnaires", force: :cascade do |t| + t.string "title" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "vignettes_slides", force: :cascade do |t| + t.bigint "vignettes_questionnaire_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["vignettes_questionnaire_id"], name: "index_vignettes_slides_on_vignettes_questionnaire_id" + end + create_table "votes", force: :cascade do |t| t.string "votable_type" t.bigint "votable_id" @@ -1047,6 +1060,7 @@ add_foreign_key "user_favorite_lecture_joins", "lectures" add_foreign_key "user_favorite_lecture_joins", "users" add_foreign_key "user_submission_joins", "users" + add_foreign_key "vignettes_slides", "vignettes_questionnaires" add_foreign_key "vouchers", "lectures" add_foreign_key "watchlist_entries", "media" add_foreign_key "watchlist_entries", "watchlists" diff --git a/spec/factories/vignettes/questionnaires.rb b/spec/factories/vignettes/questionnaires.rb new file mode 100644 index 000000000..7ae3ba864 --- /dev/null +++ b/spec/factories/vignettes/questionnaires.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :vignettes_questionnaire, class: 'Vignettes::Questionnaire' do + title { "MyString" } + end +end diff --git a/spec/factories/vignettes/slides.rb b/spec/factories/vignettes/slides.rb new file mode 100644 index 000000000..eabab927b --- /dev/null +++ b/spec/factories/vignettes/slides.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :vignettes_slide, class: 'Vignettes::Slide' do + questionnaire { nil } + content { nil } + end +end diff --git a/spec/helpers/vignettes/questionnaires_helper_spec.rb b/spec/helpers/vignettes/questionnaires_helper_spec.rb new file mode 100644 index 000000000..009a4f672 --- /dev/null +++ b/spec/helpers/vignettes/questionnaires_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Vignettes::QuestionnairesHelper. For example: +# +# describe Vignettes::QuestionnairesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Vignettes::QuestionnairesHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/vignettes/slides_helper_spec.rb b/spec/helpers/vignettes/slides_helper_spec.rb new file mode 100644 index 000000000..85ae90fbb --- /dev/null +++ b/spec/helpers/vignettes/slides_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Vignettes::SlidesHelper. For example: +# +# describe Vignettes::SlidesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Vignettes::SlidesHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/vignettes/questionnaire_spec.rb b/spec/models/vignettes/questionnaire_spec.rb new file mode 100644 index 000000000..8fc5b7c72 --- /dev/null +++ b/spec/models/vignettes/questionnaire_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Vignettes::Questionnaire, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/vignettes/slide_spec.rb b/spec/models/vignettes/slide_spec.rb new file mode 100644 index 000000000..fe0d7f6b3 --- /dev/null +++ b/spec/models/vignettes/slide_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Vignettes::Slide, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/vignettes/questionnaires_spec.rb b/spec/requests/vignettes/questionnaires_spec.rb new file mode 100644 index 000000000..ac5d63b76 --- /dev/null +++ b/spec/requests/vignettes/questionnaires_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +RSpec.describe "Vignettes::Questionnaires", type: :request do + describe "GET /index" do + it "returns http success" do + get "/vignettes/questionnaires/index" + expect(response).to have_http_status(:success) + end + end + + describe "GET /show" do + it "returns http success" do + get "/vignettes/questionnaires/show" + expect(response).to have_http_status(:success) + end + end + + describe "GET /new" do + it "returns http success" do + get "/vignettes/questionnaires/new" + expect(response).to have_http_status(:success) + end + end + + describe "GET /create" do + it "returns http success" do + get "/vignettes/questionnaires/create" + expect(response).to have_http_status(:success) + end + end + + describe "GET /edit" do + it "returns http success" do + get "/vignettes/questionnaires/edit" + expect(response).to have_http_status(:success) + end + end + + describe "GET /update" do + it "returns http success" do + get "/vignettes/questionnaires/update" + expect(response).to have_http_status(:success) + end + end + + describe "GET /destroy" do + it "returns http success" do + get "/vignettes/questionnaires/destroy" + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/requests/vignettes/slides_spec.rb b/spec/requests/vignettes/slides_spec.rb new file mode 100644 index 000000000..968b1f14f --- /dev/null +++ b/spec/requests/vignettes/slides_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +RSpec.describe "Vignettes::Slides", type: :request do + describe "GET /index" do + it "returns http success" do + get "/vignettes/slides/index" + expect(response).to have_http_status(:success) + end + end + + describe "GET /show" do + it "returns http success" do + get "/vignettes/slides/show" + expect(response).to have_http_status(:success) + end + end + + describe "GET /new" do + it "returns http success" do + get "/vignettes/slides/new" + expect(response).to have_http_status(:success) + end + end + + describe "GET /create" do + it "returns http success" do + get "/vignettes/slides/create" + expect(response).to have_http_status(:success) + end + end + + describe "GET /edit" do + it "returns http success" do + get "/vignettes/slides/edit" + expect(response).to have_http_status(:success) + end + end + + describe "GET /update" do + it "returns http success" do + get "/vignettes/slides/update" + expect(response).to have_http_status(:success) + end + end + + describe "GET /destroy" do + it "returns http success" do + get "/vignettes/slides/destroy" + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/views/vignettes/questionnaires/create.html.erb_spec.rb b/spec/views/vignettes/questionnaires/create.html.erb_spec.rb new file mode 100644 index 000000000..f1a74e4ed --- /dev/null +++ b/spec/views/vignettes/questionnaires/create.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "questionnaires/create.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/questionnaires/destroy.html.erb_spec.rb b/spec/views/vignettes/questionnaires/destroy.html.erb_spec.rb new file mode 100644 index 000000000..b0a776f71 --- /dev/null +++ b/spec/views/vignettes/questionnaires/destroy.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "questionnaires/destroy.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/questionnaires/edit.html.erb_spec.rb b/spec/views/vignettes/questionnaires/edit.html.erb_spec.rb new file mode 100644 index 000000000..cdd07dc2d --- /dev/null +++ b/spec/views/vignettes/questionnaires/edit.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "questionnaires/edit.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/questionnaires/index.html.erb_spec.rb b/spec/views/vignettes/questionnaires/index.html.erb_spec.rb new file mode 100644 index 000000000..0f8ab3c4b --- /dev/null +++ b/spec/views/vignettes/questionnaires/index.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "questionnaires/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/questionnaires/new.html.erb_spec.rb b/spec/views/vignettes/questionnaires/new.html.erb_spec.rb new file mode 100644 index 000000000..0f7656fce --- /dev/null +++ b/spec/views/vignettes/questionnaires/new.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "questionnaires/new.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/questionnaires/show.html.erb_spec.rb b/spec/views/vignettes/questionnaires/show.html.erb_spec.rb new file mode 100644 index 000000000..dd8f2f440 --- /dev/null +++ b/spec/views/vignettes/questionnaires/show.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "questionnaires/show.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/questionnaires/update.html.erb_spec.rb b/spec/views/vignettes/questionnaires/update.html.erb_spec.rb new file mode 100644 index 000000000..86f285c7b --- /dev/null +++ b/spec/views/vignettes/questionnaires/update.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "questionnaires/update.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/slides/create.html.erb_spec.rb b/spec/views/vignettes/slides/create.html.erb_spec.rb new file mode 100644 index 000000000..fada1b1e1 --- /dev/null +++ b/spec/views/vignettes/slides/create.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "slides/create.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/slides/destroy.html.erb_spec.rb b/spec/views/vignettes/slides/destroy.html.erb_spec.rb new file mode 100644 index 000000000..77e93ad3c --- /dev/null +++ b/spec/views/vignettes/slides/destroy.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "slides/destroy.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/slides/edit.html.erb_spec.rb b/spec/views/vignettes/slides/edit.html.erb_spec.rb new file mode 100644 index 000000000..0b3cd04d6 --- /dev/null +++ b/spec/views/vignettes/slides/edit.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "slides/edit.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/slides/index.html.erb_spec.rb b/spec/views/vignettes/slides/index.html.erb_spec.rb new file mode 100644 index 000000000..d40718a37 --- /dev/null +++ b/spec/views/vignettes/slides/index.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "slides/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/slides/new.html.erb_spec.rb b/spec/views/vignettes/slides/new.html.erb_spec.rb new file mode 100644 index 000000000..72ac17646 --- /dev/null +++ b/spec/views/vignettes/slides/new.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "slides/new.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/slides/show.html.erb_spec.rb b/spec/views/vignettes/slides/show.html.erb_spec.rb new file mode 100644 index 000000000..a2abbc42c --- /dev/null +++ b/spec/views/vignettes/slides/show.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "slides/show.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/vignettes/slides/update.html.erb_spec.rb b/spec/views/vignettes/slides/update.html.erb_spec.rb new file mode 100644 index 000000000..8342a8929 --- /dev/null +++ b/spec/views/vignettes/slides/update.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "slides/update.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end From 0664a270cbb767c4acedcd3e376cd6f0b1f1c85f Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 5 Dec 2024 17:04:46 +0100 Subject: [PATCH 003/103] add basic views for create, list and edit questionnaire and slides Signed-off-by: Florian --- .../vignettes/questionnaires_controller.rb | 40 ++++++++++++------- .../vignettes/slides_controller.rb | 40 ++++++++++++------- app/models/vignettes/questionnaire.rb | 4 +- app/models/vignettes/slide.rb | 2 +- .../vignettes/questionnaires/edit.html.erb | 3 +- .../vignettes/questionnaires/index.html.erb | 12 +++++- .../vignettes/questionnaires/new.html.erb | 12 +++++- .../vignettes/questionnaires/show.html.erb | 11 ++++- app/views/vignettes/slides/new.html.erb | 16 +++++++- config/application.rb | 2 +- config/routes.rb | 14 ------- 11 files changed, 101 insertions(+), 55 deletions(-) diff --git a/app/controllers/vignettes/questionnaires_controller.rb b/app/controllers/vignettes/questionnaires_controller.rb index 0f3942bcd..96e7f54d6 100644 --- a/app/controllers/vignettes/questionnaires_controller.rb +++ b/app/controllers/vignettes/questionnaires_controller.rb @@ -1,22 +1,34 @@ -class Vignettes::QuestionnairesController < ApplicationController - def index - end +module Vignettes + class QuestionnairesController < ApplicationController + def index + @questionnaires = Questionnaire.all + end - def show - end + def show + @questionnaire = Questionnaire.find(params[:id]) + end - def new - end + def new + @questionnaire = Questionnaire.new + end - def create - end + def create + @questionnaire = Questionnaire.new(questionnaire_params) + if @questionnaire.save + redirect_to @questionnaire + else + render :new, status: :unprocessable_entity + end + end - def edit - end + def edit + @questionnaire = Questionnaire.find(params[:id]) + end - def update - end + private - def destroy + def questionnaire_params + params.require(:questionnaire).permit(:title) + end end end diff --git a/app/controllers/vignettes/slides_controller.rb b/app/controllers/vignettes/slides_controller.rb index a4e54cd39..1c2fa03b8 100644 --- a/app/controllers/vignettes/slides_controller.rb +++ b/app/controllers/vignettes/slides_controller.rb @@ -1,22 +1,34 @@ -class Vignettes::SlidesController < ApplicationController - def index - end +module Vignettes + class SlidesController < ApplicationController + before_action :set_questionnaire + def index + end - def show - end + def show + @slide = @questionnaire.slides.find(params[:id]) + end - def new - end + def new + @slide = @questionnaire.slides.new + end - def create - end + def create + @slide = @questionnaire.slides.new(slide_params) + if @slide.save + redirect_to @questionnaire, notice: "Slide was successfully created" + else + render :new, status: :unprocessable_entity + end + end - def edit - end + private - def update - end + def set_questionnaire + @questionnaire = Questionnaire.find(params[:questionnaire_id]) + end - def destroy + def slide_params + params.require(:slide).permit(:content) + end end end diff --git a/app/models/vignettes/questionnaire.rb b/app/models/vignettes/questionnaire.rb index a38152173..73041f3b6 100644 --- a/app/models/vignettes/questionnaire.rb +++ b/app/models/vignettes/questionnaire.rb @@ -1,5 +1,7 @@ module Vignettes class Questionnaire < ApplicationRecord - has_many :slides, dependent: :destroy + has_many :slides, + foreign_key: "vignettes_questionnaire_id", + dependent: :destroy end end diff --git a/app/models/vignettes/slide.rb b/app/models/vignettes/slide.rb index 77460997c..cd187990a 100644 --- a/app/models/vignettes/slide.rb +++ b/app/models/vignettes/slide.rb @@ -1,6 +1,6 @@ module Vignettes class Slide < ApplicationRecord - belongs_to :questionnaire + belongs_to :questionnaire, foreign_key: "vignettes_questionnaire_id" has_rich_text :content end end diff --git a/app/views/vignettes/questionnaires/edit.html.erb b/app/views/vignettes/questionnaires/edit.html.erb index e28205dde..5f6b801b7 100644 --- a/app/views/vignettes/questionnaires/edit.html.erb +++ b/app/views/vignettes/questionnaires/edit.html.erb @@ -1,2 +1 @@ -

Vignettes::Questionnaires#edit

-

Find me in app/views/vignettes/questionnaires/edit.html.erb

+<%= button_to 'Create new slide', new_vignettes_questionnaire_slide_path(@questionnaire), method: :get, class: 'btn btn-primary' %> diff --git a/app/views/vignettes/questionnaires/index.html.erb b/app/views/vignettes/questionnaires/index.html.erb index 2d9e58aeb..c03293afc 100644 --- a/app/views/vignettes/questionnaires/index.html.erb +++ b/app/views/vignettes/questionnaires/index.html.erb @@ -1,2 +1,10 @@ -

Vignettes::Questionnaires#index

-

Find me in app/views/vignettes/questionnaires/index.html.erb

+

Vignetten

+ +
    + <% @questionnaires.each do |questionnaire| %> +
  • + <%= questionnaire.title %> +
  • + <%end%> +
+<%= button_to 'Create new Vignette', new_vignettes_questionnaire_path(), method: :get, class: 'btn btn-primary' %> \ No newline at end of file diff --git a/app/views/vignettes/questionnaires/new.html.erb b/app/views/vignettes/questionnaires/new.html.erb index b6366f5ff..0496a7c16 100644 --- a/app/views/vignettes/questionnaires/new.html.erb +++ b/app/views/vignettes/questionnaires/new.html.erb @@ -1,2 +1,10 @@ -

Vignettes::Questionnaires#new

-

Find me in app/views/vignettes/questionnaires/new.html.erb

+<%= form_with local: true, scope: :questionnaire, model: @questionnaire do |form| %> +
+ <%= form.label :title %>
+ <%= form.text_field :title %> +
+
+
+ <%= form.submit 'Create Questionnaire', class: 'btn btn-primary'%> +
+<%end%> diff --git a/app/views/vignettes/questionnaires/show.html.erb b/app/views/vignettes/questionnaires/show.html.erb index f490def29..3c52e4f72 100644 --- a/app/views/vignettes/questionnaires/show.html.erb +++ b/app/views/vignettes/questionnaires/show.html.erb @@ -1,2 +1,9 @@ -

Vignettes::Questionnaires#show

-

Find me in app/views/vignettes/questionnaires/show.html.erb

+

Slides for vignette <%= @questionnaire.title %>

+
    + <% @questionnaire.slides.each do |slide| %> +
  • + <%= slide.title %> +
  • + <% end %> +
+<%= button_to 'Create new slide', new_vignettes_questionnaire_slide_path(@questionnaire), method: :get, class: 'btn btn-primary' %> \ No newline at end of file diff --git a/app/views/vignettes/slides/new.html.erb b/app/views/vignettes/slides/new.html.erb index 0d878fe23..0feda7e10 100644 --- a/app/views/vignettes/slides/new.html.erb +++ b/app/views/vignettes/slides/new.html.erb @@ -1,2 +1,14 @@ -

Vignettes::Slides#new

-

Find me in app/views/vignettes/slides/new.html.erb

+<%= form_with(model: @slide, url: vignettes_questionnaire_slides_path(@questionnaire), local: true) do |form| %> + + +<%= form.label :title%> +
+<%= form.text_field :title%> + +
+ +<%= form.label :content %> +<%= form.rich_text_area :content %> + +<%= form.submit 'Create Slide' %> +<% end %> diff --git a/config/application.rb b/config/application.rb index de5fae844..901c42a6f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -14,7 +14,7 @@ class Application < Rails::Application # Autoload subfolders of modules (recursively) # https://stackoverflow.com/a/4794775/ - additional_paths = Rails.root.glob("app/models/**/") + additional_paths = Rails.root.glob("app/models/voucher/") config.autoload_paths += additional_paths config.eager_load_paths += additional_paths diff --git a/config/routes.rb b/config/routes.rb index 77fce6cb7..86e9492eb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -563,20 +563,6 @@ # vignettes routes namespace :vignettes do - get 'slides/index' - get 'slides/show' - get 'slides/new' - get 'slides/create' - get 'slides/edit' - get 'slides/update' - get 'slides/destroy' - get 'questionnaires/index' - get 'questionnaires/show' - get 'questionnaires/new' - get 'questionnaires/create' - get 'questionnaires/edit' - get 'questionnaires/update' - get 'questionnaires/destroy' resources :questionnaires do resources :slides end From a1f4762ce4eae9c001794908811fd26856853a18 Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 8 Dec 2024 21:32:10 +0100 Subject: [PATCH 004/103] add modal create vignette panel Signed-off-by: Florian --- .../vignettes/questionnaires_controller.rb | 4 +++- app/controllers/vignettes/slides_controller.rb | 2 +- .../questionnaires/_vignette_form.html.erb | 9 +++++++++ .../questionnaires/_vignette_modal.html.erb | 13 +++++++++++++ app/views/vignettes/questionnaires/edit.html.erb | 9 +++++++++ app/views/vignettes/questionnaires/index.html.erb | 8 ++++++-- app/views/vignettes/questionnaires/show.html.erb | 4 ++-- config/application.rb | 5 ++++- 8 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 app/views/vignettes/questionnaires/_vignette_form.html.erb create mode 100644 app/views/vignettes/questionnaires/_vignette_modal.html.erb diff --git a/app/controllers/vignettes/questionnaires_controller.rb b/app/controllers/vignettes/questionnaires_controller.rb index 96e7f54d6..904e47548 100644 --- a/app/controllers/vignettes/questionnaires_controller.rb +++ b/app/controllers/vignettes/questionnaires_controller.rb @@ -2,6 +2,8 @@ module Vignettes class QuestionnairesController < ApplicationController def index @questionnaires = Questionnaire.all + # Because the create model form works on the index page. + @questionnaire = Questionnaire.new end def show @@ -28,7 +30,7 @@ def edit private def questionnaire_params - params.require(:questionnaire).permit(:title) + params.require(:vignettes_questionnaire).permit(:title) end end end diff --git a/app/controllers/vignettes/slides_controller.rb b/app/controllers/vignettes/slides_controller.rb index 1c2fa03b8..5c7a7f9e6 100644 --- a/app/controllers/vignettes/slides_controller.rb +++ b/app/controllers/vignettes/slides_controller.rb @@ -28,7 +28,7 @@ def set_questionnaire end def slide_params - params.require(:slide).permit(:content) + params.require(:vignettes_slide).permit(:content) end end end diff --git a/app/views/vignettes/questionnaires/_vignette_form.html.erb b/app/views/vignettes/questionnaires/_vignette_form.html.erb new file mode 100644 index 000000000..e9fce2e21 --- /dev/null +++ b/app/views/vignettes/questionnaires/_vignette_form.html.erb @@ -0,0 +1,9 @@ +<%= form_with(model: @questionnaire, local: true) do |form| %> +
+ <%= form.label :title %> + <%= form.text_field :title, class: 'form-control', placeholder: 'Enter title' %> +
+
+ <%= form.submit 'Create Vignette', class: 'btn btn-primary' %> +
+<% end %> \ No newline at end of file diff --git a/app/views/vignettes/questionnaires/_vignette_modal.html.erb b/app/views/vignettes/questionnaires/_vignette_modal.html.erb new file mode 100644 index 000000000..dc8393783 --- /dev/null +++ b/app/views/vignettes/questionnaires/_vignette_modal.html.erb @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/app/views/vignettes/questionnaires/edit.html.erb b/app/views/vignettes/questionnaires/edit.html.erb index 5f6b801b7..628c47f5e 100644 --- a/app/views/vignettes/questionnaires/edit.html.erb +++ b/app/views/vignettes/questionnaires/edit.html.erb @@ -1 +1,10 @@ +

Slides

+ +
    + <% @questionnaire.slides.each() do |slide| %> +
  • + <%= link_to slide.id, edit_vignettes_questionnaire_slide_path(slide) %> +
  • + <%end%> +
<%= button_to 'Create new slide', new_vignettes_questionnaire_slide_path(@questionnaire), method: :get, class: 'btn btn-primary' %> diff --git a/app/views/vignettes/questionnaires/index.html.erb b/app/views/vignettes/questionnaires/index.html.erb index c03293afc..6c99a8367 100644 --- a/app/views/vignettes/questionnaires/index.html.erb +++ b/app/views/vignettes/questionnaires/index.html.erb @@ -3,8 +3,12 @@
    <% @questionnaires.each do |questionnaire| %>
  • - <%= questionnaire.title %> + <%= link_to questionnaire.title, edit_vignettes_questionnaire_path(questionnaire) %>
  • <%end%>
-<%= button_to 'Create new Vignette', new_vignettes_questionnaire_path(), method: :get, class: 'btn btn-primary' %> \ No newline at end of file + + +<%= render 'vignettes/questionnaires/vignette_modal' %> \ No newline at end of file diff --git a/app/views/vignettes/questionnaires/show.html.erb b/app/views/vignettes/questionnaires/show.html.erb index 3c52e4f72..197a4d1e4 100644 --- a/app/views/vignettes/questionnaires/show.html.erb +++ b/app/views/vignettes/questionnaires/show.html.erb @@ -2,8 +2,8 @@
    <% @questionnaire.slides.each do |slide| %>
  • - <%= slide.title %> + <%= slide.id %>
  • <% end %>
-<%= button_to 'Create new slide', new_vignettes_questionnaire_slide_path(@questionnaire), method: :get, class: 'btn btn-primary' %> \ No newline at end of file +<%= button_to 'Edit vignette', edit_vignettes_questionnaire_path(@questionnaire), method: :get, class: 'btn btn-primary' %> \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 901c42a6f..45e82a3fd 100644 --- a/config/application.rb +++ b/config/application.rb @@ -14,9 +14,12 @@ class Application < Rails::Application # Autoload subfolders of modules (recursively) # https://stackoverflow.com/a/4794775/ - additional_paths = Rails.root.glob("app/models/voucher/") + additional_paths = Rails.root.glob("app/models/**/") + namespace_paths = Rails.root.glob("app/models/vignettes/**/") config.autoload_paths += additional_paths config.eager_load_paths += additional_paths + config.autoload_paths -= namespace_paths + config.eager_load_paths -= namespace_paths # Autoload lib extensions path config.autoload_lib(ignore: ["assets", "collectors", "core_ext", "scrapers", "tasks"]) From d59dbf04a6d50eedcf83226026d97bc0917c024c Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 10 Dec 2024 17:11:46 +0100 Subject: [PATCH 005/103] Update action of slide Signed-off-by: Florian --- app/assets/stylesheets/application.scss | 6 +++--- app/controllers/vignettes/slides_controller.rb | 13 +++++++++++++ .../vignettes/questionnaires/edit.html.erb | 2 +- .../vignettes/questionnaires/show.html.erb | 2 +- app/views/vignettes/slides/_form.html.erb | 17 +++++++++++++++++ app/views/vignettes/slides/edit.html.erb | 6 ++++-- app/views/vignettes/slides/new.html.erb | 15 ++------------- app/views/vignettes/slides/show.html.erb | 3 +-- 8 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 app/views/vignettes/slides/_form.html.erb diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index eaa73aa06..6d1d6c9ab 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -68,9 +68,9 @@ trix-toolbar .trix-button-row { flex-wrap: wrap; } -.trix-button-group--file-tools { - display: none !important; -} +// .trix-button-group--file-tools { +// display: none !important; +// } .kaviar-sidebar { padding-left: 1rem; diff --git a/app/controllers/vignettes/slides_controller.rb b/app/controllers/vignettes/slides_controller.rb index 5c7a7f9e6..5861660e0 100644 --- a/app/controllers/vignettes/slides_controller.rb +++ b/app/controllers/vignettes/slides_controller.rb @@ -12,6 +12,10 @@ def new @slide = @questionnaire.slides.new end + def edit + @slide = @questionnaire.slides.find(params[:id]) + end + def create @slide = @questionnaire.slides.new(slide_params) if @slide.save @@ -21,6 +25,15 @@ def create end end + def update + @slide = @questionnaire.slides.find(params[:id]) + if @slide.update(slide_params) + redirect_to @questionnaire, notice: "Slide was successfully updated" + else + render :edit, status: :unprocessable_entity + end + end + private def set_questionnaire diff --git a/app/views/vignettes/questionnaires/edit.html.erb b/app/views/vignettes/questionnaires/edit.html.erb index 628c47f5e..87715de2d 100644 --- a/app/views/vignettes/questionnaires/edit.html.erb +++ b/app/views/vignettes/questionnaires/edit.html.erb @@ -3,7 +3,7 @@
    <% @questionnaire.slides.each() do |slide| %>
  • - <%= link_to slide.id, edit_vignettes_questionnaire_slide_path(slide) %> + <%= link_to slide.id, edit_vignettes_questionnaire_slide_path(@questionnaire, slide) %>
  • <%end%>
diff --git a/app/views/vignettes/questionnaires/show.html.erb b/app/views/vignettes/questionnaires/show.html.erb index 197a4d1e4..e15e0ab98 100644 --- a/app/views/vignettes/questionnaires/show.html.erb +++ b/app/views/vignettes/questionnaires/show.html.erb @@ -2,7 +2,7 @@
    <% @questionnaire.slides.each do |slide| %>
  • - <%= slide.id %> + <%= link_to slide.id, vignettes_questionnaire_slide_path(@questionnaire, slide) %>
  • <% end %>
diff --git a/app/views/vignettes/slides/_form.html.erb b/app/views/vignettes/slides/_form.html.erb new file mode 100644 index 000000000..c1e7d2a66 --- /dev/null +++ b/app/views/vignettes/slides/_form.html.erb @@ -0,0 +1,17 @@ +<%= form_with( + model: [@questionnaire, @slide], + url: @slide.new_record? ? vignettes_questionnaire_slides_path(@questionnaire) : vignettes_questionnaire_slide_path(@questionnaire, @slide), + local: true + ) do |form| %> +
+ <%= form.label :title %>
+ <%= form.text_field :title %> +
+
+ <%= form.label :content %>
+ <%= form.rich_text_area :content %> +
+
+ <%= form.submit @slide.new_record? ? 'Create Slide' : 'Update Slide' %> +
+<% end %> \ No newline at end of file diff --git a/app/views/vignettes/slides/edit.html.erb b/app/views/vignettes/slides/edit.html.erb index 75c8efd20..943bba215 100644 --- a/app/views/vignettes/slides/edit.html.erb +++ b/app/views/vignettes/slides/edit.html.erb @@ -1,2 +1,4 @@ -

Vignettes::Slides#edit

-

Find me in app/views/vignettes/slides/edit.html.erb

+ +

Edit Slide

+ +<%= render 'form' %> diff --git a/app/views/vignettes/slides/new.html.erb b/app/views/vignettes/slides/new.html.erb index 0feda7e10..aaa474add 100644 --- a/app/views/vignettes/slides/new.html.erb +++ b/app/views/vignettes/slides/new.html.erb @@ -1,14 +1,3 @@ -<%= form_with(model: @slide, url: vignettes_questionnaire_slides_path(@questionnaire), local: true) do |form| %> - +

New Slide

-<%= form.label :title%> -
-<%= form.text_field :title%> - -
- -<%= form.label :content %> -<%= form.rich_text_area :content %> - -<%= form.submit 'Create Slide' %> -<% end %> +<%= render 'form' %> diff --git a/app/views/vignettes/slides/show.html.erb b/app/views/vignettes/slides/show.html.erb index 7947d6abe..3fb5911b5 100644 --- a/app/views/vignettes/slides/show.html.erb +++ b/app/views/vignettes/slides/show.html.erb @@ -1,2 +1 @@ -

Vignettes::Slides#show

-

Find me in app/views/vignettes/slides/show.html.erb

+<%= @slide.content %> \ No newline at end of file From 12ef9b53572773889ebeb54e04d14f64391c7592 Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 15 Dec 2024 17:39:54 +0100 Subject: [PATCH 006/103] add import statement of trix in application.js Signed-off-by: Florian --- app/javascript/packs/application.js | 5 +++++ app/views/vignettes/slides/_form.html.erb | 1 - db/schema.rb | 8 +++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 889b776c1..86f78216f 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -18,6 +18,11 @@ import { WidgetInstance, } from "friendly-challenge"; +import "trix"; +import "@rails/actiontext"; +import * as ActiveStorage from "@rails/activestorage"; +ActiveStorage.start(); + document.addEventListener("turbolinks:load", function () { var doneCallback, element, options; diff --git a/app/views/vignettes/slides/_form.html.erb b/app/views/vignettes/slides/_form.html.erb index c1e7d2a66..e0cddad03 100644 --- a/app/views/vignettes/slides/_form.html.erb +++ b/app/views/vignettes/slides/_form.html.erb @@ -1,7 +1,6 @@ <%= form_with( model: [@questionnaire, @slide], url: @slide.new_record? ? vignettes_questionnaire_slides_path(@questionnaire) : vignettes_questionnaire_slide_path(@questionnaire, @slide), - local: true ) do |form| %>
<%= form.label :title %>
diff --git a/db/schema.rb b/db/schema.rb index c9facb30b..4375da35a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_12_04_132407) do +ActiveRecord::Schema[7.1].define(version: 2024_12_15_132228) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -103,6 +103,12 @@ t.index ["medium_id"], name: "index_assignments_on_medium_id" end + create_table "blogs", force: :cascade do |t| + t.string "title" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "chapters", force: :cascade do |t| t.integer "lecture_id" t.string "title" From 0002f39821726357fcfc5df9c5e18f598dfc4535 Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 15 Dec 2024 17:48:59 +0100 Subject: [PATCH 007/103] rebased dev into vignette Signed-off-by: Florian --- spec/models/user_cleaner_spec.rb | 93 ++++- .../models/user_cleaner_spec_BACKUP_140121.rb | 352 ++++++++++++++++++ spec/models/user_cleaner_spec_BASE_140121.rb | 282 ++++++++++++++ spec/models/user_cleaner_spec_LOCAL_140121.rb | 336 +++++++++++++++++ .../models/user_cleaner_spec_REMOTE_140121.rb | 329 ++++++++++++++++ 5 files changed, 1387 insertions(+), 5 deletions(-) create mode 100644 spec/models/user_cleaner_spec_BACKUP_140121.rb create mode 100644 spec/models/user_cleaner_spec_BASE_140121.rb create mode 100644 spec/models/user_cleaner_spec_LOCAL_140121.rb create mode 100644 spec/models/user_cleaner_spec_REMOTE_140121.rb diff --git a/spec/models/user_cleaner_spec.rb b/spec/models/user_cleaner_spec.rb index eb00e07ad..3b2b01178 100644 --- a/spec/models/user_cleaner_spec.rb +++ b/spec/models/user_cleaner_spec.rb @@ -4,15 +4,18 @@ # Non-generic users are either admins, teachers or editors let(:user_admin) do return FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day, admin: true) + return FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day, admin: true) end let(:user_teacher) do + user_teacher = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) user_teacher = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) FactoryBot.create(:lecture, teacher: user_teacher) return user_teacher end let(:user_editor) do + user_editor = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) user_editor = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) FactoryBot.create(:lecture, editors: [user_editor]) return user_editor @@ -23,6 +26,15 @@ end describe("#inactive_users") do + context "when user is confirmed" do + it "counts users without current_sign_in_at date as inactive" do + # but only if also the confirmation date is older than the threshold + FactoryBot.create(:confirmed_user, :with_confirmation_sent_date, + confirmation_sent_date: 5.months.ago, current_sign_in_at: nil) + FactoryBot.create(:confirmed_user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago, current_sign_in_at: nil) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end context "when user is confirmed" do it "counts users without current_sign_in_at date as inactive" do # but only if also the confirmation date is older than the threshold @@ -37,7 +49,46 @@ FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) expect(UserCleaner.new.inactive_users.count).to eq(1) end + it("counts users with current_sign_in_at date older than threshold as inactive") do + FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it "does not count users with current_sign_in_at date younger than threshold as inactive" do + FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(0) + end + end + + context "when user is not confirmed yet" do + def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_count) + user = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: nil) + FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: 5.months.ago) + FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: 7.months.ago) + + expect(user.confirmed_at).to be_nil + expect(user.confirmation_sent_at).to eq(confirmation_sent_date) + + expect(UserCleaner.new.inactive_users.count).to eq(expected_inactive_users_count) + end + + context "when registration was recently" do + it "does not count user as inactive regardless of value of last_sign_in_date" do + test_non_confirmed_user(5.days.ago, 0) + end + end + context "when registration was long ago" do + it "counts users as inactive regardless of value of last_sign_in_date" do + test_non_confirmed_user(7.months.ago, 3) + end + end it "does not count users with current_sign_in_at date younger than threshold as inactive" do FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) expect(UserCleaner.new.inactive_users.count).to eq(0) @@ -83,14 +134,20 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun inactive_user2 = FactoryBot.create(:user, :with_confirmation_sent_date, confirmation_sent_date: 7.months.ago) active_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) + inactive_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + inactive_user2 = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago) + active_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) UserCleaner.new.set_deletion_date_for_inactive_users inactive_user.reload inactive_user2.reload + inactive_user2.reload active_user.reload expect(inactive_user.deletion_date).to eq(Date.current + 40.days) expect(inactive_user2.deletion_date).to eq(Date.current + 40.days) + expect(inactive_user2.deletion_date).to eq(Date.current + 40.days) expect(active_user.deletion_date).to be_nil end @@ -98,6 +155,8 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun max_deletions = 3 UserCleaner::MAX_DELETIONS_PER_RUN = max_deletions + FactoryBot.create_list(:confirmed_user, max_deletions + 2, + current_sign_in_at: 7.months.ago) FactoryBot.create_list(:confirmed_user, max_deletions + 2, current_sign_in_at: 7.months.ago) @@ -115,13 +174,20 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun user2 = FactoryBot.create(:user, :with_confirmation_sent_date, confirmation_sent_date: 7.months.ago, deletion_date: Date.current + 44.days) + user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago, + deletion_date: Date.current + 42.days) + user2 = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago, + deletion_date: Date.current + 44.days) UserCleaner.new.set_deletion_date_for_inactive_users user.reload user2.reload + user2.reload expect(user.deletion_date).to eq(Date.current + 42.days) expect(user2.deletion_date).to eq(Date.current + 44.days) + expect(user2.deletion_date).to eq(Date.current + 44.days) end end @@ -133,6 +199,12 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun current_sign_in_at: 6.months.ago - 1.day) user_active = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, current_sign_in_at: 2.days.ago) + user_inactive = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 7.months.ago) + user_inactive2 = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 6.months.ago - 1.day) + user_active = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 2.days.ago) UserCleaner.new.unset_deletion_date_for_recently_active_users user_inactive.reload @@ -150,6 +222,9 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun user_past1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) user_past2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.year) user_present = FactoryBot.create(:confirmed_user, deletion_date: Date.current) + user_past1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + user_past2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.year) + user_present = FactoryBot.create(:confirmed_user, deletion_date: Date.current) UserCleaner.new.delete_users_according_to_deletion_date! @@ -161,6 +236,8 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun it "does not delete users with a deletion date in the future" do user_future1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.day) user_future2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.year) + user_future1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.day) + user_future2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.year) UserCleaner.new.delete_users_according_to_deletion_date! @@ -169,12 +246,14 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun end it "does not delete users without a deletion date" do + user = FactoryBot.create(:confirmed_user, deletion_date: nil) user = FactoryBot.create(:confirmed_user, deletion_date: nil) UserCleaner.new.delete_users_according_to_deletion_date! expect(User.where(id: user.id)).to exist end it "deletes only generic users" do + user_generic = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) user_generic = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) user_admin user_teacher @@ -192,7 +271,7 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun describe("mails") do context "when setting a deletion date" do it "enqueues a deletion warning mail (40 days)" do - user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) expect do UserCleaner.new.set_deletion_date_for_inactive_users @@ -213,7 +292,7 @@ def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_coun context "when a deletion date is assigned" do def test_enqueues_additional_deletion_warning_mails(num_days) - user = FactoryBot.create(:confirmed_user, deletion_date: Date.current + num_days.days) + FactoryBot.create(:confirmed_user, deletion_date: Date.current + num_days.days) expect do UserCleaner.new.send_additional_warning_mails @@ -229,6 +308,7 @@ def test_enqueues_additional_deletion_warning_mails(num_days) it "does not enqueue an additional deletion warning mail for 40 days" do FactoryBot.create(:confirmed_user, deletion_date: Date.current + 40.days) + FactoryBot.create(:confirmed_user, deletion_date: Date.current + 40.days) expect do UserCleaner.new.send_additional_warning_mails @@ -248,7 +328,7 @@ def test_enqueues_additional_deletion_warning_mails(num_days) context "when a user is finally deleted" do it "enqueues a deletion mail" do - user = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) expect do UserCleaner.new.delete_users_according_to_deletion_date! @@ -261,11 +341,12 @@ def test_enqueues_additional_deletion_warning_mails(num_days) describe("#pending_deletion_mail") do let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } + let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } + let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } def test_subject_line(num_days) user = FactoryBot.create(:confirmed_user) - mailer = UserCleanerMailer - .pending_deletion_email(user.email, user.locale, num_days) + mailer = UserCleanerMailer.with(user: user).pending_deletion_email(num_days) expect(mailer.subject).to include(num_days.to_s) end @@ -307,6 +388,8 @@ def test_subject_line(num_days) describe("#deletion_mail") do let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } + let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } + let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } it "has mail subject localized to the user's locale" do mailer = UserCleanerMailer.deletion_email(user_de.email, user_de.locale) diff --git a/spec/models/user_cleaner_spec_BACKUP_140121.rb b/spec/models/user_cleaner_spec_BACKUP_140121.rb new file mode 100644 index 000000000..d53b20697 --- /dev/null +++ b/spec/models/user_cleaner_spec_BACKUP_140121.rb @@ -0,0 +1,352 @@ +require "rails_helper" + +RSpec.describe(UserCleaner, type: :model) do + # Non-generic users are either admins, teachers or editors + let(:user_admin) do + return FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day, admin: true) + end + + let(:user_teacher) do + user_teacher = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + FactoryBot.create(:lecture, teacher: user_teacher) + return user_teacher + end + + let(:user_editor) do + user_editor = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + FactoryBot.create(:lecture, editors: [user_editor]) + return user_editor + end + + before(:each) do + UserCleaner::INACTIVE_USER_THRESHOLD = 6.months + end + + describe("#inactive_users") do + context "when user is confirmed" do + it "counts users without current_sign_in_at date as inactive" do + # but only if also the confirmation date is older than the threshold + FactoryBot.create(:confirmed_user, :with_confirmation_sent_date, + confirmation_sent_date: 5.months.ago, current_sign_in_at: nil) + FactoryBot.create(:confirmed_user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago, current_sign_in_at: nil) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it("counts users with current_sign_in_at date older than threshold as inactive") do + FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it "does not count users with current_sign_in_at date younger than threshold as inactive" do + FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(0) + end + end + + context "when user is not confirmed yet" do + def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_count) + user = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: nil) + FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: 5.months.ago) + FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: 7.months.ago) + + expect(user.confirmed_at).to be_nil + expect(user.confirmation_sent_at).to eq(confirmation_sent_date) + + expect(UserCleaner.new.inactive_users.count).to eq(expected_inactive_users_count) + end + + context "when registration was recently" do + it "does not count user as inactive regardless of value of last_sign_in_date" do + test_non_confirmed_user(5.days.ago, 0) + end + end + + context "when registration was long ago" do + it "counts users as inactive regardless of value of last_sign_in_date" do + test_non_confirmed_user(7.months.ago, 3) + end + end + end + end + + describe("#set/unset_deletion_date") do + context "when deletion date is nil" do + it "assigns a deletion date to inactive users" do + inactive_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + inactive_user2 = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago) + active_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) + + UserCleaner.new.set_deletion_date_for_inactive_users + inactive_user.reload + inactive_user2.reload + active_user.reload + + expect(inactive_user.deletion_date).to eq(Date.current + 40.days) + expect(inactive_user2.deletion_date).to eq(Date.current + 40.days) + expect(active_user.deletion_date).to be_nil + end + + it "only assigns a deletion date to a limited number of users" do + max_deletions = 3 + UserCleaner::MAX_DELETIONS_PER_RUN = max_deletions + + FactoryBot.create_list(:confirmed_user, max_deletions + 2, + current_sign_in_at: 7.months.ago) + + UserCleaner.new.set_deletion_date_for_inactive_users + + users_flagged = User.where(deletion_date: Date.current + 40.days) + expect(users_flagged.count).to eq(max_deletions) + end + end + + context "when a deletion date is assigned" do + it "does not overwrite the deletion date" do + user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago, + deletion_date: Date.current + 42.days) + user2 = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago, + deletion_date: Date.current + 44.days) + + UserCleaner.new.set_deletion_date_for_inactive_users + user.reload + user2.reload + + expect(user.deletion_date).to eq(Date.current + 42.days) + expect(user2.deletion_date).to eq(Date.current + 44.days) + end + end + + it "unassigns a deletion date from recently active users" do + deletion_date = Date.current + 5.days + user_inactive = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 7.months.ago) + user_inactive2 = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 6.months.ago - 1.day) + user_active = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 2.days.ago) + + UserCleaner.new.unset_deletion_date_for_recently_active_users + user_inactive.reload + user_inactive2.reload + user_active.reload + + expect(user_inactive.deletion_date).to eq(deletion_date) + expect(user_inactive2.deletion_date).to eq(deletion_date) + expect(user_active.deletion_date).to be_nil + end + end + + describe("#delete_users") do + it "deletes users with a deletion date in the past or present" do + user_past1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + user_past2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.year) + user_present = FactoryBot.create(:confirmed_user, deletion_date: Date.current) + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_past1.id)).not_to exist + expect(User.where(id: user_past2.id)).not_to exist + expect(User.where(id: user_present.id)).not_to exist + end + + it "does not delete users with a deletion date in the future" do + user_future1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.day) + user_future2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.year) + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_future1.id)).to exist + expect(User.where(id: user_future2.id)).to exist + end + + it "does not delete users without a deletion date" do + user = FactoryBot.create(:confirmed_user, deletion_date: nil) + UserCleaner.new.delete_users_according_to_deletion_date! + expect(User.where(id: user.id)).to exist + end + + it "deletes only generic users" do + user_generic = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + user_admin + user_teacher + user_editor + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_generic.id)).not_to exist + expect(User.where(id: user_admin.id)).to exist + expect(User.where(id: user_teacher.id)).to exist + expect(User.where(id: user_editor.id)).to exist + end + end + + describe("mails") do + context "when setting a deletion date" do + it "enqueues a deletion warning mail (40 days)" do +<<<<<<< HEAD + user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) +======= + FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) +>>>>>>> 2dc4bd6b (Deal with registration edge cases in UserCleaner (#693) (#716)) + + expect do + UserCleaner.new.set_deletion_date_for_inactive_users + end.to(have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + .with(user.email, user.locale, 40)) + end + + it "does not enqueue a deletion warning mail (40 days) for non-generic users" do + user_admin + user_teacher + user_editor + + expect do + UserCleaner.new.set_deletion_date_for_inactive_users + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + end + + context "when a deletion date is assigned" do + def test_enqueues_additional_deletion_warning_mails(num_days) +<<<<<<< HEAD + user = FactoryBot.create(:confirmed_user, deletion_date: Date.current + num_days.days) +======= + FactoryBot.create(:confirmed_user, deletion_date: Date.current + num_days.days) +>>>>>>> 2dc4bd6b (Deal with registration edge cases in UserCleaner (#693) (#716)) + + expect do + UserCleaner.new.send_additional_warning_mails + end.to(have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + .with(user.email, user.locale, num_days)) + end + + it "enqueues additional deletion warning mails" do + test_enqueues_additional_deletion_warning_mails(14) + test_enqueues_additional_deletion_warning_mails(7) + test_enqueues_additional_deletion_warning_mails(2) + end + + it "does not enqueue an additional deletion warning mail for 40 days" do + FactoryBot.create(:confirmed_user, deletion_date: Date.current + 40.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + + it "does not enqueue additional deletion warning mails for non-generic users" do + user_admin.update(deletion_date: Date.current + 14.days) + user_teacher.update(deletion_date: Date.current + 7.days) + user_editor.update(deletion_date: Date.current + 2.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + end + + context "when a user is finally deleted" do + it "enqueues a deletion mail" do +<<<<<<< HEAD + user = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) +======= + FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) +>>>>>>> 2dc4bd6b (Deal with registration edge cases in UserCleaner (#693) (#716)) + + expect do + UserCleaner.new.delete_users_according_to_deletion_date! + end.to(have_enqueued_mail(UserCleanerMailer, :deletion_email) + .with(user.email, user.locale)) + end + end + end + + describe("#pending_deletion_mail") do + let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } + let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } + + def test_subject_line(num_days) + user = FactoryBot.create(:confirmed_user) +<<<<<<< HEAD + mailer = UserCleanerMailer + .pending_deletion_email(user.email, user.locale, num_days) +======= + mailer = UserCleanerMailer.with(user: user).pending_deletion_email(num_days) +>>>>>>> 2dc4bd6b (Deal with registration edge cases in UserCleaner (#693) (#716)) + expect(mailer.subject).to include(num_days.to_s) + end + + it "has mail subject containing the number of days until deletion" do + test_subject_line(40) + test_subject_line(14) + test_subject_line(7) + test_subject_line(2) + end + + it "has mail subject localized to the user's locale" do + mailer = UserCleanerMailer + .pending_deletion_email(user_de.email, user_de.locale, 40) + expect(mailer.subject).to include("Tage") + expect(mailer.subject).not_to include("days") + + mailer = UserCleanerMailer + .pending_deletion_email(user_en.email, user_en.locale, 40) + expect(mailer.subject).to include("days") + expect(mailer.subject).not_to include("Tage") + end + + it "has mail body localized to the user's locale" do + expected_de = "verloren" + expected_en = "lost" + + mailer = UserCleanerMailer + .pending_deletion_email(user_de.email, user_de.locale, 40) + expect(mailer.html_part.body).to include(expected_de) + expect(mailer.html_part.body).not_to include(expected_en) + + mailer = UserCleanerMailer + .pending_deletion_email(user_en.email, user_en.locale, 40) + expect(mailer.html_part.body).to include(expected_en) + expect(mailer.html_part.body).not_to include(expected_de) + end + end + + describe("#deletion_mail") do + let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } + let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } + + it "has mail subject localized to the user's locale" do + mailer = UserCleanerMailer.deletion_email(user_de.email, user_de.locale) + expect(mailer.subject).to include("gelöscht") + expect(mailer.subject).not_to include("deleted") + + mailer = UserCleanerMailer.deletion_email(user_en.email, user_en.locale) + expect(mailer.subject).to include("deleted") + expect(mailer.subject).not_to include("gelöscht") + end + + it "has mail body localized to the user's locale" do + expected_de = "vollständig gelöscht" + expected_en = "deleted entirely" + + mailer = UserCleanerMailer.deletion_email(user_de.email, user_de.locale) + + expect(mailer.html_part.body).to include(expected_de) + expect(mailer.html_part.body).not_to include(expected_en) + + mailer = UserCleanerMailer.deletion_email(user_en.email, user_en.locale) + + expect(mailer.html_part.body).to include(expected_en) + expect(mailer.html_part.body).not_to include(expected_de) + end + end +end diff --git a/spec/models/user_cleaner_spec_BASE_140121.rb b/spec/models/user_cleaner_spec_BASE_140121.rb new file mode 100644 index 000000000..689962e3e --- /dev/null +++ b/spec/models/user_cleaner_spec_BASE_140121.rb @@ -0,0 +1,282 @@ +require "rails_helper" + +RSpec.describe(UserCleaner, type: :model) do + # Non-generic users are either admins, teachers or editors + let(:user_admin) do + return FactoryBot.create(:user, deletion_date: Date.current - 1.day, admin: true) + end + + let(:user_teacher) do + user_teacher = FactoryBot.create(:user, deletion_date: Date.current - 1.day) + FactoryBot.create(:lecture, teacher: user_teacher) + return user_teacher + end + + let(:user_editor) do + user_editor = FactoryBot.create(:user, deletion_date: Date.current - 1.day) + FactoryBot.create(:lecture, editors: [user_editor]) + return user_editor + end + + before(:each) do + UserCleaner::INACTIVE_USER_THRESHOLD = 6.months + end + + describe("#inactive_users") do + it "counts users without last_sign_in_at date as inactive" do + FactoryBot.create(:user, last_sign_in_at: nil) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it("counts users with last_sign_in_at date older than threshold as inactive") do + FactoryBot.create(:user, last_sign_in_at: 7.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it "does not count users with last_sign_in_at date younger than threshold as inactive" do + FactoryBot.create(:user, last_sign_in_at: 5.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(0) + end + end + + describe("#set/unset_deletion_date") do + context "when deletion date is nil" do + it "assigns a deletion date to inactive users" do + inactive_user = FactoryBot.create(:user, last_sign_in_at: 7.months.ago) + active_user = FactoryBot.create(:user, last_sign_in_at: 5.months.ago) + + UserCleaner.new.set_deletion_date_for_inactive_users + inactive_user.reload + active_user.reload + + expect(inactive_user.deletion_date).to eq(Date.current + 40.days) + expect(active_user.deletion_date).to be_nil + end + + it "only assigns a deletion date to a limited number of users" do + max_deletions = 3 + UserCleaner::MAX_DELETIONS_PER_RUN = max_deletions + + FactoryBot.create_list(:user, max_deletions + 2, last_sign_in_at: 7.months.ago) + + UserCleaner.new.set_deletion_date_for_inactive_users + + users_flagged = User.where(deletion_date: Date.current + 40.days) + expect(users_flagged.count).to eq(max_deletions) + end + end + + context "when a deletion date is assigned" do + it "does not overwrite the deletion date" do + user = FactoryBot.create(:user, last_sign_in_at: 7.months.ago, + deletion_date: Date.current + 42.days) + + UserCleaner.new.set_deletion_date_for_inactive_users + user.reload + + expect(user.deletion_date).to eq(Date.current + 42.days) + end + end + + it "unassigns a deletion date from recently active users" do + deletion_date = Date.current + 5.days + user_inactive = FactoryBot.create(:user, deletion_date: deletion_date, + last_sign_in_at: 7.months.ago) + user_inactive2 = FactoryBot.create(:user, deletion_date: deletion_date, + last_sign_in_at: 6.months.ago - 1.day) + user_active = FactoryBot.create(:user, deletion_date: deletion_date, + last_sign_in_at: 2.days.ago) + + UserCleaner.new.unset_deletion_date_for_recently_active_users + user_inactive.reload + user_inactive2.reload + user_active.reload + + expect(user_inactive.deletion_date).to eq(deletion_date) + expect(user_inactive2.deletion_date).to eq(deletion_date) + expect(user_active.deletion_date).to be_nil + end + end + + describe("#delete_users") do + it "deletes users with a deletion date in the past or present" do + user_past1 = FactoryBot.create(:user, deletion_date: Date.current - 1.day) + user_past2 = FactoryBot.create(:user, deletion_date: Date.current - 1.year) + user_present = FactoryBot.create(:user, deletion_date: Date.current) + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_past1.id)).not_to exist + expect(User.where(id: user_past2.id)).not_to exist + expect(User.where(id: user_present.id)).not_to exist + end + + it "does not delete users with a deletion date in the future" do + user_future1 = FactoryBot.create(:user, deletion_date: Date.current + 1.day) + user_future2 = FactoryBot.create(:user, deletion_date: Date.current + 1.year) + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_future1.id)).to exist + expect(User.where(id: user_future2.id)).to exist + end + + it "does not delete users without a deletion date" do + user = FactoryBot.create(:user, deletion_date: nil) + UserCleaner.new.delete_users_according_to_deletion_date! + expect(User.where(id: user.id)).to exist + end + + it "deletes only generic users" do + user_generic = FactoryBot.create(:user, deletion_date: Date.current - 1.day) + user_admin + user_teacher + user_editor + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_generic.id)).not_to exist + expect(User.where(id: user_admin.id)).to exist + expect(User.where(id: user_teacher.id)).to exist + expect(User.where(id: user_editor.id)).to exist + end + end + + describe("mails") do + context "when setting a deletion date" do + it "enqueues a deletion warning mail (40 days)" do + FactoryBot.create(:user, last_sign_in_at: 7.months.ago) + + expect do + UserCleaner.new.set_deletion_date_for_inactive_users + end.to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + .with(a_hash_including(args: [40])) + end + + it "does not enqueue a deletion warning mail (40 days) for non-generic users" do + user_admin + user_teacher + user_editor + + expect do + UserCleaner.new.set_deletion_date_for_inactive_users + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + end + + context "when a deletion date is assigned" do + def test_enqueues_additional_deletion_warning_mails(num_days) + FactoryBot.create(:user, deletion_date: Date.current + num_days.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + .with(a_hash_including(args: [num_days])) + end + + it "enqueues additional deletion warning mails" do + test_enqueues_additional_deletion_warning_mails(14) + test_enqueues_additional_deletion_warning_mails(7) + test_enqueues_additional_deletion_warning_mails(2) + end + + it "does not enqueue an additional deletion warning mail for 40 days" do + FactoryBot.create(:user, deletion_date: Date.current + 40.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + + it "does not enqueue additional deletion warning mails for non-generic users" do + user_admin.update(deletion_date: Date.current + 14.days) + user_teacher.update(deletion_date: Date.current + 7.days) + user_editor.update(deletion_date: Date.current + 2.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + end + + context "when a user is finally deleted" do + it "enqueues a deletion mail" do + FactoryBot.create(:user, deletion_date: Date.current - 1.day) + + expect do + UserCleaner.new.delete_users_according_to_deletion_date! + end.to have_enqueued_mail(UserCleanerMailer, :deletion_email) + end + end + end + + describe("#pending_deletion_mail") do + let(:user_de) { FactoryBot.create(:user, locale: "de") } + let(:user_en) { FactoryBot.create(:user, locale: "en") } + + def test_subject_line(num_days) + user = FactoryBot.create(:user) + mailer = UserCleanerMailer.with(user: user).pending_deletion_email(num_days) + expect(mailer.subject).to include(num_days.to_s) + end + + it "has mail subject containing the number of days until deletion" do + test_subject_line(40) + test_subject_line(14) + test_subject_line(7) + test_subject_line(2) + end + + it "has mail subject localized to the user's locale" do + mailer = UserCleanerMailer.with(user: user_de).pending_deletion_email(40) + expect(mailer.subject).to include("Tage") + expect(mailer.subject).not_to include("days") + + mailer = UserCleanerMailer.with(user: user_en).pending_deletion_email(40) + expect(mailer.subject).to include("days") + expect(mailer.subject).not_to include("Tage") + end + + it "has mail body localized to the user's locale" do + expected_de = "verloren" + expected_en = "lost" + + mailer = UserCleanerMailer.with(user: user_de).pending_deletion_email(40) + expect(mailer.html_part.body).to include(expected_de) + expect(mailer.html_part.body).not_to include(expected_en) + + mailer = UserCleanerMailer.with(user: user_en).pending_deletion_email(40) + expect(mailer.html_part.body).to include(expected_en) + expect(mailer.html_part.body).not_to include(expected_de) + end + end + + describe("#deletion_mail") do + let(:user_de) { FactoryBot.create(:user, locale: "de") } + let(:user_en) { FactoryBot.create(:user, locale: "en") } + + it "has mail subject localized to the user's locale" do + mailer = UserCleanerMailer.with(user: user_de).deletion_email + expect(mailer.subject).to include("gelöscht") + expect(mailer.subject).not_to include("deleted") + + mailer = UserCleanerMailer.with(user: user_en).deletion_email + expect(mailer.subject).to include("deleted") + expect(mailer.subject).not_to include("gelöscht") + end + + it "has mail body localized to the user's locale" do + expected_de = "vollständig gelöscht" + expected_en = "deleted entirely" + + mailer = UserCleanerMailer.with(user: user_de).deletion_email + + expect(mailer.html_part.body).to include(expected_de) + expect(mailer.html_part.body).not_to include(expected_en) + + mailer = UserCleanerMailer.with(user: user_en).deletion_email + expect(mailer.html_part.body).to include(expected_en) + expect(mailer.html_part.body).not_to include(expected_de) + end + end +end diff --git a/spec/models/user_cleaner_spec_LOCAL_140121.rb b/spec/models/user_cleaner_spec_LOCAL_140121.rb new file mode 100644 index 000000000..eb00e07ad --- /dev/null +++ b/spec/models/user_cleaner_spec_LOCAL_140121.rb @@ -0,0 +1,336 @@ +require "rails_helper" + +RSpec.describe(UserCleaner, type: :model) do + # Non-generic users are either admins, teachers or editors + let(:user_admin) do + return FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day, admin: true) + end + + let(:user_teacher) do + user_teacher = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + FactoryBot.create(:lecture, teacher: user_teacher) + return user_teacher + end + + let(:user_editor) do + user_editor = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + FactoryBot.create(:lecture, editors: [user_editor]) + return user_editor + end + + before(:each) do + UserCleaner::INACTIVE_USER_THRESHOLD = 6.months + end + + describe("#inactive_users") do + context "when user is confirmed" do + it "counts users without current_sign_in_at date as inactive" do + # but only if also the confirmation date is older than the threshold + FactoryBot.create(:confirmed_user, :with_confirmation_sent_date, + confirmation_sent_date: 5.months.ago, current_sign_in_at: nil) + FactoryBot.create(:confirmed_user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago, current_sign_in_at: nil) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it("counts users with current_sign_in_at date older than threshold as inactive") do + FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it "does not count users with current_sign_in_at date younger than threshold as inactive" do + FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(0) + end + end + + context "when user is not confirmed yet" do + def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_count) + user = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: nil) + FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: 5.months.ago) + FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: 7.months.ago) + + expect(user.confirmed_at).to be_nil + expect(user.confirmation_sent_at).to eq(confirmation_sent_date) + + expect(UserCleaner.new.inactive_users.count).to eq(expected_inactive_users_count) + end + + context "when registration was recently" do + it "does not count user as inactive regardless of value of last_sign_in_date" do + test_non_confirmed_user(5.days.ago, 0) + end + end + + context "when registration was long ago" do + it "counts users as inactive regardless of value of last_sign_in_date" do + test_non_confirmed_user(7.months.ago, 3) + end + end + end + end + + describe("#set/unset_deletion_date") do + context "when deletion date is nil" do + it "assigns a deletion date to inactive users" do + inactive_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + inactive_user2 = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago) + active_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) + + UserCleaner.new.set_deletion_date_for_inactive_users + inactive_user.reload + inactive_user2.reload + active_user.reload + + expect(inactive_user.deletion_date).to eq(Date.current + 40.days) + expect(inactive_user2.deletion_date).to eq(Date.current + 40.days) + expect(active_user.deletion_date).to be_nil + end + + it "only assigns a deletion date to a limited number of users" do + max_deletions = 3 + UserCleaner::MAX_DELETIONS_PER_RUN = max_deletions + + FactoryBot.create_list(:confirmed_user, max_deletions + 2, + current_sign_in_at: 7.months.ago) + + UserCleaner.new.set_deletion_date_for_inactive_users + + users_flagged = User.where(deletion_date: Date.current + 40.days) + expect(users_flagged.count).to eq(max_deletions) + end + end + + context "when a deletion date is assigned" do + it "does not overwrite the deletion date" do + user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago, + deletion_date: Date.current + 42.days) + user2 = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago, + deletion_date: Date.current + 44.days) + + UserCleaner.new.set_deletion_date_for_inactive_users + user.reload + user2.reload + + expect(user.deletion_date).to eq(Date.current + 42.days) + expect(user2.deletion_date).to eq(Date.current + 44.days) + end + end + + it "unassigns a deletion date from recently active users" do + deletion_date = Date.current + 5.days + user_inactive = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 7.months.ago) + user_inactive2 = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 6.months.ago - 1.day) + user_active = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 2.days.ago) + + UserCleaner.new.unset_deletion_date_for_recently_active_users + user_inactive.reload + user_inactive2.reload + user_active.reload + + expect(user_inactive.deletion_date).to eq(deletion_date) + expect(user_inactive2.deletion_date).to eq(deletion_date) + expect(user_active.deletion_date).to be_nil + end + end + + describe("#delete_users") do + it "deletes users with a deletion date in the past or present" do + user_past1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + user_past2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.year) + user_present = FactoryBot.create(:confirmed_user, deletion_date: Date.current) + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_past1.id)).not_to exist + expect(User.where(id: user_past2.id)).not_to exist + expect(User.where(id: user_present.id)).not_to exist + end + + it "does not delete users with a deletion date in the future" do + user_future1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.day) + user_future2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.year) + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_future1.id)).to exist + expect(User.where(id: user_future2.id)).to exist + end + + it "does not delete users without a deletion date" do + user = FactoryBot.create(:confirmed_user, deletion_date: nil) + UserCleaner.new.delete_users_according_to_deletion_date! + expect(User.where(id: user.id)).to exist + end + + it "deletes only generic users" do + user_generic = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + user_admin + user_teacher + user_editor + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_generic.id)).not_to exist + expect(User.where(id: user_admin.id)).to exist + expect(User.where(id: user_teacher.id)).to exist + expect(User.where(id: user_editor.id)).to exist + end + end + + describe("mails") do + context "when setting a deletion date" do + it "enqueues a deletion warning mail (40 days)" do + user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + + expect do + UserCleaner.new.set_deletion_date_for_inactive_users + end.to(have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + .with(user.email, user.locale, 40)) + end + + it "does not enqueue a deletion warning mail (40 days) for non-generic users" do + user_admin + user_teacher + user_editor + + expect do + UserCleaner.new.set_deletion_date_for_inactive_users + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + end + + context "when a deletion date is assigned" do + def test_enqueues_additional_deletion_warning_mails(num_days) + user = FactoryBot.create(:confirmed_user, deletion_date: Date.current + num_days.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.to(have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + .with(user.email, user.locale, num_days)) + end + + it "enqueues additional deletion warning mails" do + test_enqueues_additional_deletion_warning_mails(14) + test_enqueues_additional_deletion_warning_mails(7) + test_enqueues_additional_deletion_warning_mails(2) + end + + it "does not enqueue an additional deletion warning mail for 40 days" do + FactoryBot.create(:confirmed_user, deletion_date: Date.current + 40.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + + it "does not enqueue additional deletion warning mails for non-generic users" do + user_admin.update(deletion_date: Date.current + 14.days) + user_teacher.update(deletion_date: Date.current + 7.days) + user_editor.update(deletion_date: Date.current + 2.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + end + + context "when a user is finally deleted" do + it "enqueues a deletion mail" do + user = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + + expect do + UserCleaner.new.delete_users_according_to_deletion_date! + end.to(have_enqueued_mail(UserCleanerMailer, :deletion_email) + .with(user.email, user.locale)) + end + end + end + + describe("#pending_deletion_mail") do + let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } + let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } + + def test_subject_line(num_days) + user = FactoryBot.create(:confirmed_user) + mailer = UserCleanerMailer + .pending_deletion_email(user.email, user.locale, num_days) + expect(mailer.subject).to include(num_days.to_s) + end + + it "has mail subject containing the number of days until deletion" do + test_subject_line(40) + test_subject_line(14) + test_subject_line(7) + test_subject_line(2) + end + + it "has mail subject localized to the user's locale" do + mailer = UserCleanerMailer + .pending_deletion_email(user_de.email, user_de.locale, 40) + expect(mailer.subject).to include("Tage") + expect(mailer.subject).not_to include("days") + + mailer = UserCleanerMailer + .pending_deletion_email(user_en.email, user_en.locale, 40) + expect(mailer.subject).to include("days") + expect(mailer.subject).not_to include("Tage") + end + + it "has mail body localized to the user's locale" do + expected_de = "verloren" + expected_en = "lost" + + mailer = UserCleanerMailer + .pending_deletion_email(user_de.email, user_de.locale, 40) + expect(mailer.html_part.body).to include(expected_de) + expect(mailer.html_part.body).not_to include(expected_en) + + mailer = UserCleanerMailer + .pending_deletion_email(user_en.email, user_en.locale, 40) + expect(mailer.html_part.body).to include(expected_en) + expect(mailer.html_part.body).not_to include(expected_de) + end + end + + describe("#deletion_mail") do + let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } + let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } + + it "has mail subject localized to the user's locale" do + mailer = UserCleanerMailer.deletion_email(user_de.email, user_de.locale) + expect(mailer.subject).to include("gelöscht") + expect(mailer.subject).not_to include("deleted") + + mailer = UserCleanerMailer.deletion_email(user_en.email, user_en.locale) + expect(mailer.subject).to include("deleted") + expect(mailer.subject).not_to include("gelöscht") + end + + it "has mail body localized to the user's locale" do + expected_de = "vollständig gelöscht" + expected_en = "deleted entirely" + + mailer = UserCleanerMailer.deletion_email(user_de.email, user_de.locale) + + expect(mailer.html_part.body).to include(expected_de) + expect(mailer.html_part.body).not_to include(expected_en) + + mailer = UserCleanerMailer.deletion_email(user_en.email, user_en.locale) + + expect(mailer.html_part.body).to include(expected_en) + expect(mailer.html_part.body).not_to include(expected_de) + end + end +end diff --git a/spec/models/user_cleaner_spec_REMOTE_140121.rb b/spec/models/user_cleaner_spec_REMOTE_140121.rb new file mode 100644 index 000000000..1223a9048 --- /dev/null +++ b/spec/models/user_cleaner_spec_REMOTE_140121.rb @@ -0,0 +1,329 @@ +require "rails_helper" + +RSpec.describe(UserCleaner, type: :model) do + # Non-generic users are either admins, teachers or editors + let(:user_admin) do + return FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day, admin: true) + end + + let(:user_teacher) do + user_teacher = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + FactoryBot.create(:lecture, teacher: user_teacher) + return user_teacher + end + + let(:user_editor) do + user_editor = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + FactoryBot.create(:lecture, editors: [user_editor]) + return user_editor + end + + before(:each) do + UserCleaner::INACTIVE_USER_THRESHOLD = 6.months + end + + describe("#inactive_users") do + context "when user is confirmed" do + it "counts users without current_sign_in_at date as inactive" do + # but only if also the confirmation date is older than the threshold + FactoryBot.create(:confirmed_user, :with_confirmation_sent_date, + confirmation_sent_date: 5.months.ago, current_sign_in_at: nil) + FactoryBot.create(:confirmed_user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago, current_sign_in_at: nil) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it("counts users with current_sign_in_at date older than threshold as inactive") do + FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(1) + end + + it "does not count users with current_sign_in_at date younger than threshold as inactive" do + FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) + expect(UserCleaner.new.inactive_users.count).to eq(0) + end + end + + context "when user is not confirmed yet" do + def test_non_confirmed_user(confirmation_sent_date, expected_inactive_users_count) + user = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: nil) + FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: 5.months.ago) + FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: confirmation_sent_date, + current_sign_in_at: 7.months.ago) + + expect(user.confirmed_at).to be_nil + expect(user.confirmation_sent_at).to eq(confirmation_sent_date) + + expect(UserCleaner.new.inactive_users.count).to eq(expected_inactive_users_count) + end + + context "when registration was recently" do + it "does not count user as inactive regardless of value of last_sign_in_date" do + test_non_confirmed_user(5.days.ago, 0) + end + end + + context "when registration was long ago" do + it "counts users as inactive regardless of value of last_sign_in_date" do + test_non_confirmed_user(7.months.ago, 3) + end + end + end + end + + describe("#set/unset_deletion_date") do + context "when deletion date is nil" do + it "assigns a deletion date to inactive users" do + inactive_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + inactive_user2 = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago) + active_user = FactoryBot.create(:confirmed_user, current_sign_in_at: 5.months.ago) + + UserCleaner.new.set_deletion_date_for_inactive_users + inactive_user.reload + inactive_user2.reload + active_user.reload + + expect(inactive_user.deletion_date).to eq(Date.current + 40.days) + expect(inactive_user2.deletion_date).to eq(Date.current + 40.days) + expect(active_user.deletion_date).to be_nil + end + + it "only assigns a deletion date to a limited number of users" do + max_deletions = 3 + UserCleaner::MAX_DELETIONS_PER_RUN = max_deletions + + FactoryBot.create_list(:confirmed_user, max_deletions + 2, + current_sign_in_at: 7.months.ago) + + UserCleaner.new.set_deletion_date_for_inactive_users + + users_flagged = User.where(deletion_date: Date.current + 40.days) + expect(users_flagged.count).to eq(max_deletions) + end + end + + context "when a deletion date is assigned" do + it "does not overwrite the deletion date" do + user = FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago, + deletion_date: Date.current + 42.days) + user2 = FactoryBot.create(:user, :with_confirmation_sent_date, + confirmation_sent_date: 7.months.ago, + deletion_date: Date.current + 44.days) + + UserCleaner.new.set_deletion_date_for_inactive_users + user.reload + user2.reload + + expect(user.deletion_date).to eq(Date.current + 42.days) + expect(user2.deletion_date).to eq(Date.current + 44.days) + end + end + + it "unassigns a deletion date from recently active users" do + deletion_date = Date.current + 5.days + user_inactive = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 7.months.ago) + user_inactive2 = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 6.months.ago - 1.day) + user_active = FactoryBot.create(:confirmed_user, deletion_date: deletion_date, + current_sign_in_at: 2.days.ago) + + UserCleaner.new.unset_deletion_date_for_recently_active_users + user_inactive.reload + user_inactive2.reload + user_active.reload + + expect(user_inactive.deletion_date).to eq(deletion_date) + expect(user_inactive2.deletion_date).to eq(deletion_date) + expect(user_active.deletion_date).to be_nil + end + end + + describe("#delete_users") do + it "deletes users with a deletion date in the past or present" do + user_past1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + user_past2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.year) + user_present = FactoryBot.create(:confirmed_user, deletion_date: Date.current) + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_past1.id)).not_to exist + expect(User.where(id: user_past2.id)).not_to exist + expect(User.where(id: user_present.id)).not_to exist + end + + it "does not delete users with a deletion date in the future" do + user_future1 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.day) + user_future2 = FactoryBot.create(:confirmed_user, deletion_date: Date.current + 1.year) + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_future1.id)).to exist + expect(User.where(id: user_future2.id)).to exist + end + + it "does not delete users without a deletion date" do + user = FactoryBot.create(:confirmed_user, deletion_date: nil) + UserCleaner.new.delete_users_according_to_deletion_date! + expect(User.where(id: user.id)).to exist + end + + it "deletes only generic users" do + user_generic = FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + user_admin + user_teacher + user_editor + + UserCleaner.new.delete_users_according_to_deletion_date! + + expect(User.where(id: user_generic.id)).not_to exist + expect(User.where(id: user_admin.id)).to exist + expect(User.where(id: user_teacher.id)).to exist + expect(User.where(id: user_editor.id)).to exist + end + end + + describe("mails") do + context "when setting a deletion date" do + it "enqueues a deletion warning mail (40 days)" do + FactoryBot.create(:confirmed_user, current_sign_in_at: 7.months.ago) + + expect do + UserCleaner.new.set_deletion_date_for_inactive_users + end.to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + .with(a_hash_including(args: [40])) + end + + it "does not enqueue a deletion warning mail (40 days) for non-generic users" do + user_admin + user_teacher + user_editor + + expect do + UserCleaner.new.set_deletion_date_for_inactive_users + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + end + + context "when a deletion date is assigned" do + def test_enqueues_additional_deletion_warning_mails(num_days) + FactoryBot.create(:confirmed_user, deletion_date: Date.current + num_days.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + .with(a_hash_including(args: [num_days])) + end + + it "enqueues additional deletion warning mails" do + test_enqueues_additional_deletion_warning_mails(14) + test_enqueues_additional_deletion_warning_mails(7) + test_enqueues_additional_deletion_warning_mails(2) + end + + it "does not enqueue an additional deletion warning mail for 40 days" do + FactoryBot.create(:confirmed_user, deletion_date: Date.current + 40.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + + it "does not enqueue additional deletion warning mails for non-generic users" do + user_admin.update(deletion_date: Date.current + 14.days) + user_teacher.update(deletion_date: Date.current + 7.days) + user_editor.update(deletion_date: Date.current + 2.days) + + expect do + UserCleaner.new.send_additional_warning_mails + end.not_to have_enqueued_mail(UserCleanerMailer, :pending_deletion_email) + end + end + + context "when a user is finally deleted" do + it "enqueues a deletion mail" do + FactoryBot.create(:confirmed_user, deletion_date: Date.current - 1.day) + + expect do + UserCleaner.new.delete_users_according_to_deletion_date! + end.to have_enqueued_mail(UserCleanerMailer, :deletion_email) + end + end + end + + describe("#pending_deletion_mail") do + let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } + let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } + + def test_subject_line(num_days) + user = FactoryBot.create(:confirmed_user) + mailer = UserCleanerMailer.with(user: user).pending_deletion_email(num_days) + expect(mailer.subject).to include(num_days.to_s) + end + + it "has mail subject containing the number of days until deletion" do + test_subject_line(40) + test_subject_line(14) + test_subject_line(7) + test_subject_line(2) + end + + it "has mail subject localized to the user's locale" do + mailer = UserCleanerMailer.with(user: user_de).pending_deletion_email(40) + expect(mailer.subject).to include("Tage") + expect(mailer.subject).not_to include("days") + + mailer = UserCleanerMailer.with(user: user_en).pending_deletion_email(40) + expect(mailer.subject).to include("days") + expect(mailer.subject).not_to include("Tage") + end + + it "has mail body localized to the user's locale" do + expected_de = "verloren" + expected_en = "lost" + + mailer = UserCleanerMailer.with(user: user_de).pending_deletion_email(40) + expect(mailer.html_part.body).to include(expected_de) + expect(mailer.html_part.body).not_to include(expected_en) + + mailer = UserCleanerMailer.with(user: user_en).pending_deletion_email(40) + expect(mailer.html_part.body).to include(expected_en) + expect(mailer.html_part.body).not_to include(expected_de) + end + end + + describe("#deletion_mail") do + let(:user_de) { FactoryBot.create(:confirmed_user, locale: "de") } + let(:user_en) { FactoryBot.create(:confirmed_user, locale: "en") } + + it "has mail subject localized to the user's locale" do + mailer = UserCleanerMailer.with(user: user_de).deletion_email + expect(mailer.subject).to include("gelöscht") + expect(mailer.subject).not_to include("deleted") + + mailer = UserCleanerMailer.with(user: user_en).deletion_email + expect(mailer.subject).to include("deleted") + expect(mailer.subject).not_to include("gelöscht") + end + + it "has mail body localized to the user's locale" do + expected_de = "vollständig gelöscht" + expected_en = "deleted entirely" + + mailer = UserCleanerMailer.with(user: user_de).deletion_email + + expect(mailer.html_part.body).to include(expected_de) + expect(mailer.html_part.body).not_to include(expected_en) + + mailer = UserCleanerMailer.with(user: user_en).deletion_email + expect(mailer.html_part.body).to include(expected_en) + expect(mailer.html_part.body).not_to include(expected_de) + end + end +end From 7ed2702b73185943cb0cc03e8e41cfac5c424609 Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 16 Dec 2024 10:52:09 +0100 Subject: [PATCH 008/103] fix active storage direct upload routing error Signed-off-by: Florian --- config/routes.rb | 8 +++++++- storage/17/zz/17zz1muveku4uty3cn9joxiyat6f | Bin 0 -> 6734 bytes 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 storage/17/zz/17zz1muveku4uty3cn9joxiyat6f diff --git a/config/routes.rb b/config/routes.rb index 86e9492eb..6f722f922 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -955,7 +955,13 @@ # redirect bs requests to error page - match "*path", to: "main#error", via: :all + # match "*path", to: "main#error", via: :all + + # Wildcard route at the very bottom, with a constraint to exclude Active Storage paths + match "*path", to: "main#error", via: :all, constraints: lambda { |req| + !req.path.start_with?("/rails/active_storage") + } + match "/", to: "main#error", via: [:post, :put, :patch, :delete] # For details on the DSL available within this file, diff --git a/storage/17/zz/17zz1muveku4uty3cn9joxiyat6f b/storage/17/zz/17zz1muveku4uty3cn9joxiyat6f new file mode 100644 index 0000000000000000000000000000000000000000..78abe57880647de98f30cb8ee56bf8045bc39000 GIT binary patch literal 6734 zcmcIpcQBl7zut)U@+L$CQ4`T3Y7i|*BBFOf)UZnQzRD_*M6?$Xy{t}jL6o(+AbO&N zRaXhYu2om(+`gGJ^PP9*oS8H8e)k_{*Y5nD`?;U%cm1yG+HfrmMG8g=2n2FPS?Q?` z1aj#+cs)%{1paD9*d{|DjC9IRAM5#~<7VviXbombH+dgX!`X@KZQe-TzZ69oN%_+A zDG@nY)UB*+{YSB{!g68e}ZNtzcLU?UmKfIJ;6y?2@>vnVTfxx%n zOGb1r8jer3Y}Ys)pXdgZFlEkt`dHq)A9RhixWsuTM_yk3E(D?_%O8=mu&{6-a2fJ4 zjFOUa1cM@lyfz>qqKCYuCKiA|Zj+lpAWx|4A&_^sTrWY$A7h9h)NfizAh(EvDIt%@ z|A{ZY>HRS_mTzaoN4H~24(a~SUhMDNKe$a7iNj)_vTL8u={$eFytZbft)1Q90Es6L zraTc65_0wM@Njo`_w@AN#5|spHZU-N`}rBYc#)ElqK=SC(2IgF%VUV-UZq~8ppf!d zRs)~p=Udy@*to#yZrpfbXh=&-Yh`QO-PLuK>A_!)`H*yQhMA5|PLYw3HDA6oHa4=~ zxr4%g`1o-Fi{*<|+TPijnVC5(y!Kjz+ynz^{aPLQL_+k~({nePMOvyQ{^Q3giw@E& zSEAC>nc;BwnLROvl!*R798SRY_p7BAKrJmNJSf>?UoCb;xg*mhzx_fzgIvAk`k0f? z=nD@IPgqXj>({Rd1Xx6l>;dL-irVt8U%&A9A#X3p^droh;)xoE@!i#t^|iG;ox2L! zH8nM$kgA5pGD-k42+HXH{{5R-#!t`KIPH*IeZqf1ITCCb2*vwTH(J2J_Np zs=K?ptt}WI0jYo2N-{=6L&Hu@E-EH=hn@YPoa8ms$e$jvDV?hrLq`#kkdRPNP~bmK z)G7=rr0>td@jY?e{sxy?Wx#u8Zf@>oRfrZvddL5XIvzKpi9v*P!ng#avDeefOYZCd z+FZgQWKH4>z^hwe8ThX%c&e44jUY)!E5O z#-^Vk_|Z#C%LP0>Xudt<@q@J3*h=Rasqm-m+9~pn;qYTDy4o)FYy*WqC|B5f_x^ov zLRNNmu$}qMsJ>s@)6>)XCC>*kWYSo|A|v}=GCJDXZSCw-fBx)qa^SX7!Oh9JM<_%2 z?b*AOg?BD!%AM-nqSm6V@mw92Ju-ZujAc|3}_;}DfU~eq! z+8w{?x$*J3hK7b?V=j&KiN&+%=;(*h3cLyLLqpep|Mr|qDOfEtt|LqY%5}3Rr~m{k zhF|BNt#^C7BV1e*7Dm1dPe35b@&wY;(|IC#`}%Zsj#X$F8H>w<`%*>iXhyW3JbBX| zH99)lkB}b!@k5(E*3&oqiixS|%*n~g`}gnZ&+o+bG>DIV|4x15hM4=362agLzSYUa z#nuSy8X0&hwG)?zZ4Fp2s{_^3($dhCo@fz1zS8b`3&0nk-93E`931YA7(t=AN*t*| z7DU0V0jq}o0ReX-{7(1APft&Cb92YbjBTBrXMOp|s93SHXRo{54>m32 ziV>9b08bR(0S^Btem(`o~bVV%!}Z<(2yB_<|< zqsAIawWP+zUZbbCef26iKHhU>5CQsK+Is_VfalrCAy#(0-1K-UPP0L@q^xY<3l}vx zAUDZ}lDV6P1pHiPMn^J(t78H#=y+<5mzVcrTpU^k+^ey%F_{RRx%nKG z+{yRO&iuwklRxD3nAFwP0d94Q;_*-PlKjY=6%-VlocI;ApFMk~`$BK^5>)fqGhjW@g~w z_OB==@zmtx->a)B$;qWANF5FuP`?r$xHo2CP+^A4rJwEAF)*kLI3{$2QgL%|c>b~Q z`Za_=)ddJkMMdRk<5xa3JR+j1veM1X4H&1%~CMMGdTZ?4oL-BWYC!9JNv4Nv%hDJsJ3X6d5qN1WujZa4|IG&E* z=%h{2`oO+WdA0s1UczC#&m{?zuAU?41 zg!kLfketuvOMCm`j11Pfn!@tiH1z;1dKesFX(SSPom;~M1>+yj*TxD~`$_sAY}PtW zHGJYP*;$=o5_kSkta`1^c_tz%3Ww9TDJQCy@!S2~<}GgBOY7o-A08gw;5L5Wn3|fp zKNob~K_%x2gDs=z5(>J4r$$FTw-ywZe|P-&;g;n>AKA$kuzPJsQb|=+RZ^@t1t4as z&V`JWlu#LiK+H^3LQ|Y74b_T86__9TXn~`SVMz|30+kV;P=7wt(xO(-HV-=WngiF% z*jpcuVV1lvAP^0(fM6usINVv8nwq*Bktz)BN*0Ea9ub=0`zjnXsRc=b=H=-`V^kvE z+xcvNpcl@K`HrYPvW%fro;F7S|9xeE5(jqQP~(9c)_C5%x#}A=Rm- z%Kd%(^mxC{5lsU~7YdcnlJ*kMBQoHl1NT*7iXwpK92gh?4eadfETf^*GdKud6!7%^ z{F&%DCf_!L@=m!zPfy?Mp#2>;RR>VP$HzxONqH=Co_9VQcZ$lsNM9^Jx{L7=$f1rjh*tg)S|pwb|M83HJ=HvPgZ! zKG)Ut0NuLbA_v1VRA>L}e$`l5T57c{JCq_4dWC+^eSgDLQ*-FrooDG1Zf|yw4%k%; zpjAL6&P;gj)Plu@g`Aw6=7R;weApK-ssMVx5nFV;zgv@gTi*2Y{xs5~KYRTR38kl} zCjc^?fC=cw!x`_nfmE@+p&?BmFVNM;M3hcWPT0o2!RyE@xrrA2H6N`zNqtnz5-%+* zw)zBH09UJ}J>Zv>&6pDS>#x7gHbdovR4Gi5?jciM$3wHTfm5|k78Vw5ZEY$I*XO~E zkByB5?AIlzeO}~%fk|e5hT)8;sA7iX&eIPU052q`kNy0)aWpPu5zI^yVovk(T<42o?IhJZn75_=A zI60XiTPyj)2Mh+Yxx1?C%oMb>5K>pSe{#4Z?lk$;sZV}Ni+uXsyLa;)p^A!%)fH88 zzpHHq)|%~m0Orv+*^OTz8vrm~r$=5gGEH|Bqc=_aT2D@PhJYq3FE8&)5}6cMO}Y=Hz{#pt>C=&}gq9_zF5VU~Z5S0cM9KxD>5IeqP=Oq*Ic-fPg?| z(3!8lzyI^+&-L~7{rvn^B%QXlwt$c`+3O_rGc{!~aN>QYsJH?eJ=1?X{3$#7i(MRO zoL_xOWOI&yG%YPHb#!QCZ(WTyA}v?qusnM{g+`N*k`{dWy4>uAqCiFWVAnI z!E^$7vN40Azjp05EyZEEwHA1kD=1_4Lda2f(D(9$l^jA0;>d z*nk<;-`hJpJ}$`5j}WmP1YJeuVg4*2;1u8>iQl-^evCJS$=ABbdYYD=UW5rKq>PLV zY{iwuAGwPC?_f?&xR$fMikZ2=S$wWc(L0TXcqS@qRp@jq`Df)$)w|{D@v3WRu>NKW z&f%$P%ukRBo|>4b2VWAXsA`e)Sngll`biI)<*Z9mdH?x%A1`R$Jl)yJ2}lcXRBr`# zH9z0>F!^1HKca6F<_kvqaK7q5 zfB$_Ekl+&zYM3frdeVx@%2+hRZ~hxtOSSaAfq}jK{fd&3k?-FPD=kSpMnZ{j zLW*C(S%M`vb9jtzv90TlVhU~5?`u7s4`oY+)0x9@HGpSebHOt0IGlj~<;$00#_X}K z@Z2BNBa@R2o~t@wg2!=d!p^525cekwzeQs)keAzgdwC@# zeSem|@TcEf(`jk79^&@#^%WNBm%bj^Def|BC1A1(^VkQ+fyVXrst`N7~33cZ$!z18@H1B0{s1} zs;hB0oXcz@5*R%b&SgaEDH%{Y!epO8OW+%KH1=5nk5;*DplxqR`n$jdQRY+@OZY#b%uerL4S413EWpcq1h!cADvJ5# z^Ccl+;d%R;xxKx;M@N3k@#BrYI}&!f!t?X-SR}0eJ%-y1JdtDJLgqqYn8sgBz5!4Vluva0bBIrd&GP z?4VL3Y@lG;o+Z`whjBd5H#7P?(k+2H80 zJro3F``djiT7=6f1$ubVFIrSqN0)Wf@X#H5M4oNf0BUZ+_c9V^qtyi>UBqvK)v=c99 zZG(TA=$)RIo1&K2*Qd@oHTwJeY0XyVIy#`IM|-5Cr2PYT(_=zp%D(b_Lju6Goct1e zk}br=#U&*4eROoU?JXG)PW@}tCU^Pyo4nTP)JIS-+`?ycuu-Fro0%DF3`rp^P;Cfl zZ$<_NwTy?7A|i2fW3bV?u3+m+?no1Xii!&SmR||b)>=HYU?z93EBgv9uGD=Vu~K!hJm9cLR4!C)76o)!`nU0PE% z;S&+5uC0CG+j3>uZ~U`G$5m#DTXc~r$5$oTC>6AQeSLxAttl%j140ex2{o>JUP|!B;p`NXVp_NshAzw4NQM{&)c6FqUpjejt zH!$eIsxWIxeW9q5;^o%=?b|mnWV$>2Yec{tipZfwZ56oyI0Xj>UzU=;&pQA(Bo# zRaCjMuH@T=mjPT1wX)hB3Oe&pn*B(Y&S0-tPZ1UqQ&Cj3wYkZ|&(9(ab96zuNuaZ+ zPG6W8*VK%4ciZl7%uvWB7u5Z59O-h}L`0OFt-a0JqTuR8FwpBlV^= z#d>V}!QkMaY9imN+u+^YygUwme)ZsA*rSTtTG;v7X#oBXT_ngyKp&5nzQ}b|1TOla z_l5}^j%w#`90hk^cBB`!z3mRN4B+t8Gi6QZqkY%_`>#jbxEBaqcqF~x!m8PKabf`l z?fAsRpV{)EvjO|2OiXFB%SOuiI&yL?z)TOHcl1~Z0-CFB14g9LGv$ZFc=_S+F_pXa zB)}zla?;+xLFClECiPuqrLc6O_?-XFN@mao{QTp`k3en1wiY_yuEx(*3I&}5N202t z!ljnnsyr_A?gyY;ppnw+?!_tr(X6*ZYb;GbOZvVygS<$ zY zli&X>@A_Nl{6j|v1(Hm3mYvZ>R(H=~v``xuB7XznmlEwHkiFajMfYB*_OX!>D3$vq z2t*OY)l2`6xcc$0-)n2bqWpA`hXTZ}p?|yjeDJcQAv#hif@M3`mLZ$c_B>J{%Fq^14 z83_qO(xU);GCT}KTXmgGWJJV;Go+vZiUJCShF)QMR;zOn;s$Hh*46?>11$@Z9^i>) zyp@!d-C(dpC62?xLoieQN69Y&-Azzy8>GQR!MCKO{JNuA5aue%%IMD)Z{NRv2F77i zlT45pk?UoUm8Z$TVKz2g>LWlMW054WN?+U-pCL^KnU}Ur??A|?>$7M;vH{ZYr`meJ zIKko43fgS`o55;a0FT@I`zt6Zi1S6hjGe4;INDwH^6~=tPfj5qDYwNgPHyfUQ+kLk zD8maJV>GKwF-V(E;{IiI0s;b>*NHHH64C30A(6hWu0qwg#l=Morqx>kV)6v@#{Q2# z{& z?)n@jgok8s{@)(kKlIzj|E$Qu$k^+B1p=vfcYZl+XZmTgVUZU2p$MX^pz*Z)$;&tY E1#5D(_y7O^ literal 0 HcmV?d00001 From b9176b082e62dbf3acc47d08e84d75620d932641 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 24 Dec 2024 09:32:23 +0100 Subject: [PATCH 009/103] fix activeStorage routing bug and add libsvips42 Signed-off-by: Florian --- .gitignore | 3 +++ Gemfile | 1 + Gemfile.lock | 1 + config/routes.rb | 6 +----- docker/development/Dockerfile | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index fa5ed4bd3..488dc111b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ coverage # Ignore shrine store /public/uploads +# Ignore activeStorage uploads +/storage/* + # Ignore environment variables completed_initial_run /public/uploads.zip diff --git a/Gemfile b/Gemfile index 182d74061..645764650 100644 --- a/Gemfile +++ b/Gemfile @@ -51,6 +51,7 @@ gem "rails-i18n", "~> 7.0" gem "responders", "~> 3.1" gem "rgl", "~> 0.6" gem "rqrcode", "~> 2.2" +# gem "ruby-vips", "~> 2.2", ">= 2.2.2" gem "rubyzip", "~> 2.3" gem "sass-rails", "~> 6.0" # SCSS for stylesheets gem "shrine", "~> 3.6" diff --git a/Gemfile.lock b/Gemfile.lock index 4bd07aa0e..6983eb032 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -728,6 +728,7 @@ DEPENDENCIES rubocop (~> 1.65) rubocop-performance (~> 1.21) rubocop-rails (~> 2.24) + ruby-vips (~> 2.2, >= 2.2.2) rubyzip (~> 2.3) sass-rails (~> 6.0) selenium-webdriver (~> 4.10.0) diff --git a/config/routes.rb b/config/routes.rb index 6f722f922..c7d20d051 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -953,11 +953,7 @@ mount Thredded::Engine => "/forum" - # redirect bs requests to error page - - # match "*path", to: "main#error", via: :all - - # Wildcard route at the very bottom, with a constraint to exclude Active Storage paths + # redirect bs requests to error page (except active storage routes) match "*path", to: "main#error", via: :all, constraints: lambda { |req| !req.path.start_with?("/rails/active_storage") } diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile index 7a3efdf5b..83fb79cbc 100644 --- a/docker/development/Dockerfile +++ b/docker/development/Dockerfile @@ -53,7 +53,7 @@ RUN yarn set version "${YARN_VERSION}" RUN apt update && \ apt-get install -y --no-install-recommends \ ffmpeg imagemagick pdftk ghostscript shared-mime-info \ - libarchive-tools postgresql-client-13 wget wait-for-it graphviz + libarchive-tools postgresql-client-13 wget wait-for-it graphviz libvips42 # Setup ImageMagick RUN sed -i '/disable ghostscript format types/,+6d' /etc/ImageMagick-6/policy.xml From 695ab131616d9226d5daca7dc244a55d6012cb8a Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 26 Dec 2024 13:43:24 +0100 Subject: [PATCH 010/103] add question model to vignettes and plain javascript approach for dynamic form Signed-off-by: Florian --- app/assets/config/manifest.js | 1 + .../javascripts/vignettes/question_type.js | 24 +++++++++++++++++++ app/models/vignettes/question.rb | 10 ++++++++ app/models/vignettes/slide.rb | 1 + app/models/vignettes/text_question.rb | 4 ++++ app/views/vignettes/slides/_form.html.erb | 11 +++++++++ ...241224094245_create_vignettes_questions.rb | 11 +++++++++ db/schema.rb | 15 +++++++++++- spec/factories/vignettes/questions.rb | 7 ++++++ spec/models/vignettes/question_spec.rb | 5 ++++ 10 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/vignettes/question_type.js create mode 100644 app/models/vignettes/question.rb create mode 100644 app/models/vignettes/text_question.rb create mode 100644 db/migrate/20241224094245_create_vignettes_questions.rb create mode 100644 spec/factories/vignettes/questions.rb create mode 100644 spec/models/vignettes/question_spec.rb diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index ff4d5c580..8c87ffd3a 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,4 +1,5 @@ //= link_tree ../images +//= link_directory ../javascripts/vignettes .js //= link_directory ../javascripts .js //= link_directory ../stylesheets .css //= link commontator/manifest.js diff --git a/app/assets/javascripts/vignettes/question_type.js b/app/assets/javascripts/vignettes/question_type.js new file mode 100644 index 000000000..2d7bb9610 --- /dev/null +++ b/app/assets/javascripts/vignettes/question_type.js @@ -0,0 +1,24 @@ +var questionTypeHTML = { + TextQuestion: ` +
+ <%= form.label :question_text, "Question Text" %>
+ <%= form.text_area :question_text %> +
+ `, +}; +document.addEventListener("turbolinks:load", () => { + const questionTypeDropdown = document.getElementById("question_type"); + const questionField = document.getElementById("question_field"); + if (questionTypeDropdown && questionField) { + questionTypeDropdown.addEventListener("change", function (event) { + const selectedType = event.target.value; + if (selectedType == "") { + questionField.innerHTML = ""; + return; + } + else { + questionField.innerHTML = questionTypeHTML[selectedType]; + } + }); + }; +}); diff --git a/app/models/vignettes/question.rb b/app/models/vignettes/question.rb new file mode 100644 index 000000000..0eb94be4c --- /dev/null +++ b/app/models/vignettes/question.rb @@ -0,0 +1,10 @@ +module Vignettes + class Question < ApplicationRecord + # Uses single table inheritance to store different types of answers + belongs_to :slide + + validates :question_text, presence: true + + self.abstract_class = false + end +end diff --git a/app/models/vignettes/slide.rb b/app/models/vignettes/slide.rb index cd187990a..85502e2c9 100644 --- a/app/models/vignettes/slide.rb +++ b/app/models/vignettes/slide.rb @@ -2,5 +2,6 @@ module Vignettes class Slide < ApplicationRecord belongs_to :questionnaire, foreign_key: "vignettes_questionnaire_id" has_rich_text :content + has_one :question, dependent: :destroy, inverse_of: :vignettes_slide end end diff --git a/app/models/vignettes/text_question.rb b/app/models/vignettes/text_question.rb new file mode 100644 index 000000000..73895965c --- /dev/null +++ b/app/models/vignettes/text_question.rb @@ -0,0 +1,4 @@ +module Vignettes + class TextQuestion < Question + end +end diff --git a/app/views/vignettes/slides/_form.html.erb b/app/views/vignettes/slides/_form.html.erb index e0cddad03..d27ce3e2f 100644 --- a/app/views/vignettes/slides/_form.html.erb +++ b/app/views/vignettes/slides/_form.html.erb @@ -1,3 +1,4 @@ +<%= javascript_include_tag 'vignettes/question_type' %> <%= form_with( model: [@questionnaire, @slide], url: @slide.new_record? ? vignettes_questionnaire_slides_path(@questionnaire) : vignettes_questionnaire_slide_path(@questionnaire, @slide), @@ -13,4 +14,14 @@
<%= form.submit @slide.new_record? ? 'Create Slide' : 'Update Slide' %>
+
+ <%= form.label :type, "Question Type" %> + <%= form.select :type, options_for_select([ + ['No Question', ''], + ['Text Field', 'TextQuestion'] + ]), {}, { id: "question_type" } %> +
+ +
+
<% end %> \ No newline at end of file diff --git a/db/migrate/20241224094245_create_vignettes_questions.rb b/db/migrate/20241224094245_create_vignettes_questions.rb new file mode 100644 index 000000000..e419f3430 --- /dev/null +++ b/db/migrate/20241224094245_create_vignettes_questions.rb @@ -0,0 +1,11 @@ +class CreateVignettesQuestions < ActiveRecord::Migration[7.1] + def change + create_table :vignettes_questions do |t| + t.string :type + t.text :question_text + t.references :vignettes_slide, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4375da35a..0cdff38cd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_12_15_132228) do +ActiveRecord::Schema[7.1].define(version: 2024_12_24_094245) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -955,10 +955,22 @@ t.datetime "updated_at", null: false end + create_table "vignettes_questions", force: :cascade do |t| + t.string "type" + t.text "question_text" + t.bigint "vignettes_slide_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["vignettes_slide_id"], name: "index_vignettes_questions_on_vignettes_slide_id" + end + create_table "vignettes_slides", force: :cascade do |t| t.bigint "vignettes_questionnaire_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "questionable_type" + t.bigint "questionable_id" + t.index ["questionable_type", "questionable_id"], name: "index_vignettes_slides_on_questionable" t.index ["vignettes_questionnaire_id"], name: "index_vignettes_slides_on_vignettes_questionnaire_id" end @@ -1066,6 +1078,7 @@ add_foreign_key "user_favorite_lecture_joins", "lectures" add_foreign_key "user_favorite_lecture_joins", "users" add_foreign_key "user_submission_joins", "users" + add_foreign_key "vignettes_questions", "vignettes_slides" add_foreign_key "vignettes_slides", "vignettes_questionnaires" add_foreign_key "vouchers", "lectures" add_foreign_key "watchlist_entries", "media" diff --git a/spec/factories/vignettes/questions.rb b/spec/factories/vignettes/questions.rb new file mode 100644 index 000000000..4beaf73cd --- /dev/null +++ b/spec/factories/vignettes/questions.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :vignettes_question, class: 'Vignettes::Question' do + type { "" } + question_text { "MyText" } + slide { nil } + end +end diff --git a/spec/models/vignettes/question_spec.rb b/spec/models/vignettes/question_spec.rb new file mode 100644 index 000000000..b5d837b21 --- /dev/null +++ b/spec/models/vignettes/question_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Vignettes::Question, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From c772b2ebaa45c91dfea1afbed056944c98dc0ec9 Mon Sep 17 00:00:00 2001 From: Florian Date: Thu, 26 Dec 2024 14:19:52 +0100 Subject: [PATCH 011/103] add stimulus Signed-off-by: Florian --- Gemfile | 1 + Gemfile.lock | 4 +++- app/javascript/controllers/application.js | 9 +++++++++ app/javascript/controllers/hello_controller.js | 7 +++++++ app/javascript/controllers/index.js | 8 ++++++++ app/views/vignettes/slides/_form.html.erb | 16 ++++++++++------ package.json | 1 + yarn.lock | 5 +++++ 8 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 app/javascript/controllers/application.js create mode 100644 app/javascript/controllers/hello_controller.js create mode 100644 app/javascript/controllers/index.js diff --git a/Gemfile b/Gemfile index 645764650..8ee6894eb 100644 --- a/Gemfile +++ b/Gemfile @@ -58,6 +58,7 @@ gem "shrine", "~> 3.6" gem "sidekiq", "~> 7.3" gem "sidekiq-cron", "~> 1.12" gem "sprockets-rails", "~>3.5" +gem "stimulus-rails" gem "streamio-ffmpeg", "~> 3.0" gem "sunspot_rails", "~> 2.7" gem "sunspot_solr", "~> 2.7" diff --git a/Gemfile.lock b/Gemfile.lock index 6983eb032..3d5d7bf80 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -602,6 +602,8 @@ GEM actionpack (>= 6.1) activesupport (>= 6.1) sprockets (>= 3.0.0) + stimulus-rails (1.3.4) + railties (>= 6.0.0) stream (0.5.5) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) @@ -728,7 +730,6 @@ DEPENDENCIES rubocop (~> 1.65) rubocop-performance (~> 1.21) rubocop-rails (~> 2.24) - ruby-vips (~> 2.2, >= 2.2.2) rubyzip (~> 2.3) sass-rails (~> 6.0) selenium-webdriver (~> 4.10.0) @@ -740,6 +741,7 @@ DEPENDENCIES spring (~> 2.1) spring-watcher-listen (~> 2.0) sprockets-rails (~> 3.5) + stimulus-rails streamio-ffmpeg (~> 3.0) sunspot_rails (~> 2.7) sunspot_solr (~> 2.7) diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js new file mode 100644 index 000000000..1213e85c7 --- /dev/null +++ b/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js new file mode 100644 index 000000000..5975c0789 --- /dev/null +++ b/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 000000000..d0685d3b7 --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,8 @@ +// This file is auto-generated by ./bin/rails stimulus:manifest:update +// Run that command whenever you add a new controller or create them with +// ./bin/rails generate stimulus controllerName + +import { application } from "./application" + +import HelloController from "./hello_controller" +application.register("hello", HelloController) diff --git a/app/views/vignettes/slides/_form.html.erb b/app/views/vignettes/slides/_form.html.erb index d27ce3e2f..bab5b286b 100644 --- a/app/views/vignettes/slides/_form.html.erb +++ b/app/views/vignettes/slides/_form.html.erb @@ -11,17 +11,21 @@ <%= form.label :content %>
<%= form.rich_text_area :content %>
-
- <%= form.submit @slide.new_record? ? 'Create Slide' : 'Update Slide' %> -
<%= form.label :type, "Question Type" %> <%= form.select :type, options_for_select([ ['No Question', ''], ['Text Field', 'TextQuestion'] - ]), {}, { id: "question_type" } %> + ]), {}, data: { action: "change->toggle-fields#switch" } %>
- -
+
+ <%= form.label :question_text, "Question Text" %> +
+ <%= form.text_area :question_text %> +
+
+ <%= form.submit @slide.new_record? ? 'Create Slide' : 'Update Slide' %>
+ + <% end %> \ No newline at end of file diff --git a/package.json b/package.json index 60f7c0edf..063cc1af0 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "mampf", "private": true, "dependencies": { + "@hotwired/stimulus": "^3.2.2", "@rails/actiontext": "^8.0.0", "@rails/webpacker": "5.4.4", "@webpack-cli/serve": "^1.7.0", diff --git a/yarn.lock b/yarn.lock index 7fcd78eec..563252a47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -927,6 +927,11 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@hotwired/stimulus@^3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.2.tgz#071aab59c600fed95b97939e605ff261a4251608" + integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A== + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" From c754ba167d685de571d69df500a9baa84b06bcbf Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 29 Dec 2024 18:39:11 +0100 Subject: [PATCH 012/103] add multiple choice option for questionnaire Signed-off-by: Florian --- .../javascripts/vignettes/question_type.js | 29 +++++------- .../vignettes/slides_controller.rb | 15 +++++- .../vignettes/multiple_choice_question.rb | 4 ++ app/models/vignettes/option.rb | 6 +++ app/models/vignettes/question.rb | 5 +- app/models/vignettes/slide.rb | 3 +- app/views/vignettes/slides/_form.html.erb | 47 +++++++++++++++---- .../questions/_text_question_field.html.erb | 4 ++ ...move_questionable_from_vignettes_slides.rb | 7 +++ db/migrate/20241228200302_create_options.rb | 10 ++++ db/schema.rb | 14 ++++-- spec/factories/options.rb | 6 +++ spec/models/option_spec.rb | 5 ++ 13 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 app/models/vignettes/multiple_choice_question.rb create mode 100644 app/models/vignettes/option.rb create mode 100644 app/views/vignettes/slides/questions/_text_question_field.html.erb create mode 100644 db/migrate/20241228184829_remove_questionable_from_vignettes_slides.rb create mode 100644 db/migrate/20241228200302_create_options.rb create mode 100644 spec/factories/options.rb create mode 100644 spec/models/option_spec.rb diff --git a/app/assets/javascripts/vignettes/question_type.js b/app/assets/javascripts/vignettes/question_type.js index 2d7bb9610..d5dfde800 100644 --- a/app/assets/javascripts/vignettes/question_type.js +++ b/app/assets/javascripts/vignettes/question_type.js @@ -1,23 +1,20 @@ -var questionTypeHTML = { - TextQuestion: ` -
- <%= form.label :question_text, "Question Text" %>
- <%= form.text_area :question_text %> -
- `, -}; document.addEventListener("turbolinks:load", () => { const questionTypeDropdown = document.getElementById("question_type"); - const questionField = document.getElementById("question_field"); - if (questionTypeDropdown && questionField) { + if (questionTypeDropdown) { questionTypeDropdown.addEventListener("change", function (event) { - const selectedType = event.target.value; - if (selectedType == "") { - questionField.innerHTML = ""; - return; + // Disable all fields by default + const allQuestionFields = document.getElementsByClassName("question-field"); + for (let field of allQuestionFields) { + field.style.display = "none"; } - else { - questionField.innerHTML = questionTypeHTML[selectedType]; + // make selected field visible + const selectedType = event.target.value; + if (selectedType) { + const selectedField = document.getElementById(selectedType); + console.log(selectedField); + if (selectedField) { + selectedField.style.display = "block"; + } } }); }; diff --git a/app/controllers/vignettes/slides_controller.rb b/app/controllers/vignettes/slides_controller.rb index 5861660e0..db1ee6657 100644 --- a/app/controllers/vignettes/slides_controller.rb +++ b/app/controllers/vignettes/slides_controller.rb @@ -10,10 +10,14 @@ def show def new @slide = @questionnaire.slides.new + @slide.build_question + @slide.question.options.build end def edit @slide = @questionnaire.slides.find(params[:id]) + @slide.build_question unless @slide.question + @slide.question.options.build unless @slide.question.options.any? end def create @@ -41,7 +45,16 @@ def set_questionnaire end def slide_params - params.require(:vignettes_slide).permit(:content) + params.require(:vignettes_slide).permit( + :content, + question_attributes: [ + :id, + :type, + :question_text, + :_destroy, + { options_attributes: [:id, :text, :_destroy] } + ] + ) end end end diff --git a/app/models/vignettes/multiple_choice_question.rb b/app/models/vignettes/multiple_choice_question.rb new file mode 100644 index 000000000..8435b083a --- /dev/null +++ b/app/models/vignettes/multiple_choice_question.rb @@ -0,0 +1,4 @@ +module Vignettes + class MultipleChoiceQuestion < Question + end +end diff --git a/app/models/vignettes/option.rb b/app/models/vignettes/option.rb new file mode 100644 index 000000000..76f78785e --- /dev/null +++ b/app/models/vignettes/option.rb @@ -0,0 +1,6 @@ +module Vignettes + class Option < ApplicationRecord + belongs_to :question, inverse_of: :options, foreign_key: "vignettes_question_id" + validates :text, presence: true + end +end diff --git a/app/models/vignettes/question.rb b/app/models/vignettes/question.rb index 0eb94be4c..b3b563400 100644 --- a/app/models/vignettes/question.rb +++ b/app/models/vignettes/question.rb @@ -1,7 +1,10 @@ module Vignettes class Question < ApplicationRecord # Uses single table inheritance to store different types of answers - belongs_to :slide + belongs_to :slide, inverse_of: :question, foreign_key: "vignettes_slide_id" + has_many :options, dependent: :destroy, inverse_of: :question + + accepts_nested_attributes_for :options, allow_destroy: true, reject_if: :all_blank validates :question_text, presence: true diff --git a/app/models/vignettes/slide.rb b/app/models/vignettes/slide.rb index 85502e2c9..02bf79df9 100644 --- a/app/models/vignettes/slide.rb +++ b/app/models/vignettes/slide.rb @@ -2,6 +2,7 @@ module Vignettes class Slide < ApplicationRecord belongs_to :questionnaire, foreign_key: "vignettes_questionnaire_id" has_rich_text :content - has_one :question, dependent: :destroy, inverse_of: :vignettes_slide + has_one :question, dependent: :destroy, inverse_of: :slide + accepts_nested_attributes_for :question, allow_destroy: true end end diff --git a/app/views/vignettes/slides/_form.html.erb b/app/views/vignettes/slides/_form.html.erb index d27ce3e2f..135527dd7 100644 --- a/app/views/vignettes/slides/_form.html.erb +++ b/app/views/vignettes/slides/_form.html.erb @@ -11,17 +11,44 @@ <%= form.label :content %>
<%= form.rich_text_area :content %>
+ <%= form.fields_for :question do |question_form| %> + +
+ <%= question_form.label :type, "Question Type" %> + <%= question_form.select :type, options_for_select([ + ['No Question', ''], + ['Text Field', 'TextQuestion'], + ['Multiple Choice', 'MultipleChoiceQuestion'] + ]), {}, { id: "question_type" } %> +
+ + + + + <% end %>
<%= form.submit @slide.new_record? ? 'Create Slide' : 'Update Slide' %>
-
- <%= form.label :type, "Question Type" %> - <%= form.select :type, options_for_select([ - ['No Question', ''], - ['Text Field', 'TextQuestion'] - ]), {}, { id: "question_type" } %> -
- -
-
<% end %> \ No newline at end of file diff --git a/app/views/vignettes/slides/questions/_text_question_field.html.erb b/app/views/vignettes/slides/questions/_text_question_field.html.erb new file mode 100644 index 000000000..cccddf583 --- /dev/null +++ b/app/views/vignettes/slides/questions/_text_question_field.html.erb @@ -0,0 +1,4 @@ +
+ <%= form.label :question_text, "Question Text" %>
+ <%= form.text_area :question_text, placeholder: "Enter your question here" %> +
\ No newline at end of file diff --git a/db/migrate/20241228184829_remove_questionable_from_vignettes_slides.rb b/db/migrate/20241228184829_remove_questionable_from_vignettes_slides.rb new file mode 100644 index 000000000..779d81381 --- /dev/null +++ b/db/migrate/20241228184829_remove_questionable_from_vignettes_slides.rb @@ -0,0 +1,7 @@ +class RemoveQuestionableFromVignettesSlides < ActiveRecord::Migration[7.1] + def change + remove_index :vignettes_slides, name: "index_vignettes_slides_on_questionable" + remove_column :vignettes_slides, :questionable_type, :string + remove_column :vignettes_slides, :questionable_id, :bigint + end +end diff --git a/db/migrate/20241228200302_create_options.rb b/db/migrate/20241228200302_create_options.rb new file mode 100644 index 000000000..47a4cb59a --- /dev/null +++ b/db/migrate/20241228200302_create_options.rb @@ -0,0 +1,10 @@ +class CreateOptions < ActiveRecord::Migration[7.1] + def change + create_table :vignettes_options do |t| + t.string :text + t.references :vignettes_question, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 0cdff38cd..049dfe442 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_12_24_094245) do +ActiveRecord::Schema[7.1].define(version: 2024_12_28_200302) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -949,6 +949,14 @@ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end + create_table "vignettes_options", force: :cascade do |t| + t.string "text" + t.bigint "vignettes_question_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["vignettes_question_id"], name: "index_vignettes_options_on_vignettes_question_id" + end + create_table "vignettes_questionnaires", force: :cascade do |t| t.string "title" t.datetime "created_at", null: false @@ -968,9 +976,6 @@ t.bigint "vignettes_questionnaire_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "questionable_type" - t.bigint "questionable_id" - t.index ["questionable_type", "questionable_id"], name: "index_vignettes_slides_on_questionable" t.index ["vignettes_questionnaire_id"], name: "index_vignettes_slides_on_vignettes_questionnaire_id" end @@ -1078,6 +1083,7 @@ add_foreign_key "user_favorite_lecture_joins", "lectures" add_foreign_key "user_favorite_lecture_joins", "users" add_foreign_key "user_submission_joins", "users" + add_foreign_key "vignettes_options", "vignettes_questions" add_foreign_key "vignettes_questions", "vignettes_slides" add_foreign_key "vignettes_slides", "vignettes_questionnaires" add_foreign_key "vouchers", "lectures" diff --git a/spec/factories/options.rb b/spec/factories/options.rb new file mode 100644 index 000000000..08e0687a1 --- /dev/null +++ b/spec/factories/options.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :option do + text { "MyString" } + question { nil } + end +end diff --git a/spec/models/option_spec.rb b/spec/models/option_spec.rb new file mode 100644 index 000000000..eeda255a3 --- /dev/null +++ b/spec/models/option_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Option, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From b220ab9f38719e6170cda4c826b5f680088a3663 Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 5 Jan 2025 18:12:44 +0100 Subject: [PATCH 013/103] ensure correct post request for dynamic form Signed-off-by: Florian --- Gemfile | 1 - .../javascripts/vignettes/question_type.js | 37 ++++++++++++++++++- app/views/vignettes/slides/_form.html.erb | 33 +++++++++++------ app/views/vignettes/slides/show.html.erb | 14 ++++++- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/Gemfile b/Gemfile index 8ee6894eb..645764650 100644 --- a/Gemfile +++ b/Gemfile @@ -58,7 +58,6 @@ gem "shrine", "~> 3.6" gem "sidekiq", "~> 7.3" gem "sidekiq-cron", "~> 1.12" gem "sprockets-rails", "~>3.5" -gem "stimulus-rails" gem "streamio-ffmpeg", "~> 3.0" gem "sunspot_rails", "~> 2.7" gem "sunspot_solr", "~> 2.7" diff --git a/app/assets/javascripts/vignettes/question_type.js b/app/assets/javascripts/vignettes/question_type.js index d5dfde800..6d79ce2ad 100644 --- a/app/assets/javascripts/vignettes/question_type.js +++ b/app/assets/javascripts/vignettes/question_type.js @@ -1,5 +1,5 @@ document.addEventListener("turbolinks:load", () => { - const questionTypeDropdown = document.getElementById("question_type"); + const questionTypeDropdown = document.getElementById("question-type"); if (questionTypeDropdown) { questionTypeDropdown.addEventListener("change", function (event) { // Disable all fields by default @@ -17,5 +17,40 @@ document.addEventListener("turbolinks:load", () => { } } }); + + questionTypeDropdown.dispatchEvent(new Event("change")); }; + const optionsContainer = document.getElementById("options-container"); + + // Handle dynamic addition of options + const addOptionButton = document.getElementById("add-option"); + const optionTemplate = document.getElementById("new-option-template"); + if (addOptionButton) { + addOptionButton.addEventListener("click", (e) => { + const optionTemplateHTML = optionTemplate.innerHTML; + const uniqueId = new Date().getTime().toString(); + const newBlockHTML = optionTemplateHTML.replace(/NEW_RECORD/g, uniqueId); + + optionsContainer.insertAdjacentHTML("beforeend", newBlockHTML); + }); + } + + // Handle removal of options + if (optionsContainer) { + optionsContainer.addEventListener("click", (e) => { + console.log(e.target); + if (e.target.className == "remove-option") { + e.preventDefault(); + const optionBlock = e.target.parentElement; + if (optionBlock.className == "option-field") { + const destroyField = optionBlock.querySelector("input[type='hidden'][name*='_destroy']"); + console.log(destroyField); + if (destroyField) { + destroyField.value = "1"; + optionBlock.style.display = "none"; + } + } + } + }); + } }); diff --git a/app/views/vignettes/slides/_form.html.erb b/app/views/vignettes/slides/_form.html.erb index 135527dd7..7c2eec7f8 100644 --- a/app/views/vignettes/slides/_form.html.erb +++ b/app/views/vignettes/slides/_form.html.erb @@ -17,32 +17,43 @@ <%= question_form.label :type, "Question Type" %> <%= question_form.select :type, options_for_select([ ['No Question', ''], - ['Text Field', 'TextQuestion'], - ['Multiple Choice', 'MultipleChoiceQuestion'] - ]), {}, { id: "question_type" } %> + ['Text Field', 'Vignettes::TextQuestion'], + ['Multiple Choice', 'Vignettes::MultipleChoiceQuestion'] + ], question_form.object.type), {}, { id: "question-type" } %> - -