diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 705df6ba60..15154764fd 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,7 @@ class ApplicationController < ActionController::Base include CurrentTheme include Pagy::Backend + extend RequiresFeature before_action :configure_permitted_parameters, if: :devise_controller? before_action :set_sentry_user, if: :current_user diff --git a/app/controllers/concerns/requires_feature.rb b/app/controllers/concerns/requires_feature.rb new file mode 100644 index 0000000000..e4659c29e1 --- /dev/null +++ b/app/controllers/concerns/requires_feature.rb @@ -0,0 +1,12 @@ +module RequiresFeature + def requires_feature(name, **) + before_action( + lambda do + return if Flipper.enabled?(name, current_user) + + redirect_to dashboard_path, notice: 'Feature not enabled' + end, + ** + ) + end +end diff --git a/app/controllers/interview_surveys_controller.rb b/app/controllers/interview_surveys_controller.rb index 67f332524d..7c33c8d0ac 100644 --- a/app/controllers/interview_surveys_controller.rb +++ b/app/controllers/interview_surveys_controller.rb @@ -1,12 +1,27 @@ class InterviewSurveysController < ApplicationController + before_action :authenticate_user! + requires_feature :survey_feature + def new - unless Feature.enabled?(:survey_feature, current_user) - redirect_to dashboard_path, notice: 'Feature not enabled' - end + @interview_survey = InterviewSurvey.new end def create - # puts params[:survey] - redirect_to dashboard_path, notice: 'Survey Submitted' + @interview_survey = current_user.interview_surveys.build(interview_survey_params) + + if @interview_survey.save + redirect_to dashboard_path, notice: 'Survey Submitted' + else + render :new, status: :unprocessable_entity + end + end + + private + + def interview_survey_params + params.require(:interview_survey).permit( + :interview_date, + interview_concept_names: [] + ) end end diff --git a/app/javascript/controllers/multi_select_controller.js b/app/javascript/controllers/multi_select_controller.js new file mode 100644 index 0000000000..5307a10960 --- /dev/null +++ b/app/javascript/controllers/multi_select_controller.js @@ -0,0 +1,18 @@ +import { Controller } from '@hotwired/stimulus' +import SlimSelect from 'slim-select' +import 'slim-select/styles' + +export default class MultiSelectController extends Controller { + connect () { + this.slimSelect = new SlimSelect({ + select: this.element, + events: { + addable: function (value) { return value } + } + }) + } + + disconnect () { + this.slimSelect.destroy() + } +} diff --git a/app/models/interview_concept.rb b/app/models/interview_concept.rb index d5ce87b348..54c28bfde8 100644 --- a/app/models/interview_concept.rb +++ b/app/models/interview_concept.rb @@ -1,5 +1,6 @@ class InterviewConcept < ApplicationRecord - has_many :interview_survey_concepts, dependent: :destroy + has_many :interview_survey_concepts, dependent: :destroy, inverse_of: :interview_concept + has_many :interview_surveys, through: :interview_survey_concepts, inverse_of: :interview_concepts validates :name, presence: true validates :name, length: { minimum: 2, maximum: 25 } diff --git a/app/models/interview_survey.rb b/app/models/interview_survey.rb index ce0a258272..d06044219d 100644 --- a/app/models/interview_survey.rb +++ b/app/models/interview_survey.rb @@ -1,10 +1,25 @@ class InterviewSurvey < ApplicationRecord belongs_to :user - has_many :interview_survey_concepts, dependent: :destroy + has_many :interview_survey_concepts, dependent: :destroy, inverse_of: :interview_survey + has_many :interview_concepts, + through: :interview_survey_concepts, + inverse_of: :interview_surveys validates :interview_date, presence: true validate :interview_date_must_be_in_the_past + def interview_concept_names + interview_concepts.pluck(:name) + end + + def interview_concept_names=(names) + self.interview_concepts = names.compact_blank.map do |name| + InterviewConcept.find_or_create_by(name:) + end + end + + private + def interview_date_must_be_in_the_past return if interview_date.present? && interview_date <= Time.zone.today diff --git a/app/models/user.rb b/app/models/user.rb index c9c7fdff8d..0dcbcf85d7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,6 +19,7 @@ class User < ApplicationRecord has_many :notifications, as: :recipient, dependent: :destroy has_many :announcements, dependent: nil has_many :likes, dependent: :destroy + has_many :interview_surveys, dependent: :destroy belongs_to :path, optional: true, counter_cache: true diff --git a/app/views/interview_surveys/new.html.erb b/app/views/interview_surveys/new.html.erb index ea1894ae8a..3e65144f67 100644 --- a/app/views/interview_surveys/new.html.erb +++ b/app/views/interview_surveys/new.html.erb @@ -4,8 +4,18 @@

Interview Survey

-
- <%# where form will be rendered %> -
+ <%= form_with model: @interview_survey, builder: TailwindFormBuilder do |form| %> + <%= form.label :interview_date %> + <%= form.date_field :interview_date %> + + <%= form.label :interview_concept_names %> + <%= form.select :interview_concept_names, + InterviewConcept.pluck(:name, :name), + { multiple: true }, + { data: { controller: 'multi-select' } } %> + + <%= form.submit 'Submit', class: 'button button--primary w-full text-sm' %> + <% end %> +
diff --git a/db/schema.rb b/db/schema.rb index 739116c265..20f4212b4e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -171,7 +171,7 @@ create_table "interview_surveys", force: :cascade do |t| t.bigint "user_id", null: false - t.date "interview_date" + t.date "interview_date", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["user_id"], name: "index_interview_surveys_on_user_id" diff --git a/package.json b/package.json index 3a6fae0ba8..54c462334d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "mermaid": "^11.8.1", "postcss": "^8.5.6", "prismjs": "^1.30.0", + "slim-select": "^2.11.0", "sortablejs": "^1.15.6", "stimulus-use": "^0.52.3", "tailwindcss": "4.1.11", diff --git a/spec/models/interview_concept_spec.rb b/spec/models/interview_concept_spec.rb index e3010b412a..780542ca74 100644 --- a/spec/models/interview_concept_spec.rb +++ b/spec/models/interview_concept_spec.rb @@ -4,6 +4,7 @@ subject(:interview_concept) { create(:interview_concept) } it { (is_expected.to have_many(:interview_survey_concepts)) } + it { (is_expected.to have_many(:interview_surveys)) } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_length_of(:name).is_at_least(2).is_at_most(25) } end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 20c041013e..3f4946891d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -17,6 +17,7 @@ it { is_expected.to have_many(:notifications) } it { is_expected.to have_many(:likes).dependent(:destroy) } it { is_expected.to belong_to(:path).optional(true) } + it { is_expected.to have_many(:interview_surveys).dependent(:destroy) } context 'when user is created' do let!(:default_path) { create(:path, default_path: true) } diff --git a/spec/system/interview_survey_spec.rb b/spec/system/interview_survey_spec.rb index b9f83e2f50..4591df2a80 100644 --- a/spec/system/interview_survey_spec.rb +++ b/spec/system/interview_survey_spec.rb @@ -10,11 +10,46 @@ context 'when the feature is enabled' do before do Flipper.enable(:survey_feature) + create(:interview_concept, name: 'Rails routing') end - it 'shows the survey' do + it 'creates an interview survey with existing concepts' do visit new_interview_survey_path - expect(page).to have_content('Interview Survey') + + fill_in :interview_survey_interview_date, with: Date.current + + find('div[aria-label="Combobox"]').click + find('div[class="ss-option"]', text: 'Rails routing').click + + click_on 'Submit' + + expect(page).to have_content('Survey Submitted') + end + + it 'creates an interview survey with new concepts' do + visit new_interview_survey_path + + fill_in :interview_survey_interview_date, with: Date.current + + find('div[aria-label="Combobox"]').click + find('input[type="search"]').set('React props').send_keys(:return) + + click_on 'Submit' + + expect(page).to have_content('Survey Submitted') + end + + it 'reports validation errors with invalid form data' do + visit new_interview_survey_path + + fill_in :interview_survey_interview_date, with: Date.current + 3.days + + find('div[aria-label="Combobox"]').click + find('input[type="search"]').set('React props').send_keys(:return) + + click_on 'Submit' + + expect(page).to have_content("Interview date can't be in the future") end end diff --git a/yarn.lock b/yarn.lock index 824172967f..0e3fcd8003 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6261,6 +6261,7 @@ __metadata: mermaid: "npm:^11.8.1" postcss: "npm:^8.5.6" prismjs: "npm:^1.30.0" + slim-select: "npm:^2.11.0" sortablejs: "npm:^1.15.6" standard: "npm:^17.1.2" stimulus-use: "npm:^0.52.3" @@ -6426,6 +6427,13 @@ __metadata: languageName: node linkType: hard +"slim-select@npm:^2.11.0": + version: 2.11.0 + resolution: "slim-select@npm:2.11.0" + checksum: 10c0/a39d7c3f34f26eef8203232cc923c38a5a57e85ff624994765a129a725fc1abd98e4315e7a2eaadfaea1797d3e8dd7ee42b33251aa4fb81307dcb114341b7bcb + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0"