Skip to content

Commit

Permalink
ER-793 feedback forms (#1102)
Browse files Browse the repository at this point in the history
- extend question model to support a new feedback model type
- add new user journey for site-wide and module-specific feedback forms
- support feedback from authenticated and unauthenticated users
  • Loading branch information
peterdavidhamilton authored Jul 5, 2024
1 parent ace1c86 commit 5590a98
Show file tree
Hide file tree
Showing 141 changed files with 3,191 additions and 603 deletions.
5 changes: 0 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ POSTGRES_PASSWORD=
# PGHOST=localhost
# PGDATABASE=early_years_foundation_recovery_test

# user research
FEEDBACK_URL=


# Account unlock wait duration if :time is enabled
UNLOCK_IN_MINUTES=
Expand Down Expand Up @@ -82,8 +79,6 @@ CONTENTFUL_PREVIEW_TOKEN=
CONTENTFUL_ENVIRONMENT=test # master, staging
CONTENTFUL_PREVIEW=true

# Contentful DB migration flags
DISABLE_USER_ANSWER=true
# Opt-in end-to-end testing (inactive for review apps)
E2E=true

Expand Down
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ jobs:
--health-timeout 5s
--health-retries 5
steps:
-
name: Checkout Code
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ inherit_mode:
require: rubocop-performance

AllCops:
TargetRubyVersion: 3.1.3
TargetRubyVersion: 3.2.2

Style/StringLiterals:
EnforcedStyle: single_quotes
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ gem 'govuk_markdown'

gem 'govuk_notify_rails'

# Sentry -Monitor errors
# Monitor errors
gem 'sentry-rails'
gem 'sentry-ruby'

Expand Down
17 changes: 11 additions & 6 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,20 @@ pre.code-tip {
font-family: monospace !important;
}
}

// --------------------------------------------

.app-sidebar {
padding: govuk-spacing(5) govuk-spacing(3);
background-color: govuk-colour('light-grey');
#feedback-cta {
padding: govuk-spacing(5);
background-color: govuk-organisation-colour('department-for-education');

* {
color: govuk-colour('white');
}

*:last-child {
margin: 0;
.govuk-button {
background-color: govuk-organisation-colour('department-for-education');
border-color: govuk-colour('white');
box-shadow: none;
}
}

Expand Down
9 changes: 8 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,23 @@ def set_time_zone(&block)
end

# @see Auditing
# @return [User]
# @return [User, nil]
def current_user
return bot if bot?

super
end

# @return [Guest, nil]
def guest
visit = Visit.find_by(visit_token: cookies[:course_feedback]) || current_visit
Guest.new(visit: visit) if visit.present?
end

# @see Auditing
# @return [Boolean]
def user_signed_in?
return false if current_user&.guest?
return true if bot?

super
Expand Down
4 changes: 0 additions & 4 deletions app/controllers/close_accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,4 @@ def confirm; end
def user_params
params.require(:user).permit(:closed_reason, :closed_reason_custom)
end

def user_password_params
params.require(:user).permit(:current_password)
end
end
10 changes: 7 additions & 3 deletions app/controllers/concerns/learning.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ def module_table
ModuleDebugDecorator.new(progress_service).rows
end

# @return [PaginationDecorator]
# @return [PaginationDecorator, FeedbackPaginationDecorator]
def section_bar
PaginationDecorator.new(content)
if content.feedback_question? || content.previous_item.feedback_question?
FeedbackPaginationDecorator.new(content, current_user)
else
PaginationDecorator.new(content)
end
end

# ----------------------------------------------------------------------------
Expand All @@ -67,6 +71,6 @@ def section_bar
# @note memoization ensures validation errors work
# @return [UserAnswer, Response]
def current_user_response
@current_user_response ||= current_user.response_for(content)
@current_user_response ||= content.feedback_question? ? current_user.response_for_shared(content, mod) : current_user.response_for(content)
end
end
7 changes: 4 additions & 3 deletions app/controllers/errors_controller.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
class ErrorsController < ApplicationController
before_action :log_error

# 404 error
def not_found
render status: :not_found
end

# 422 error
def unprocessable_entity
render status: :unprocessable_entity
end

# 500 error
def internal_server_error
render status: :internal_server_error
end

def service_unavailable
render status: :service_unavailable
end

private

def log_error
Expand Down
112 changes: 112 additions & 0 deletions app/controllers/feedback_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
class FeedbackController < ApplicationController
before_action :research_participation, only: :show

helper_method :content,
:mod,
:current_user_response

def index; end

def show
if content_name.eql? 'thank-you'
track_feedback_complete
render :thank_you
end
end

def update
if current_user.profile_updated? && save_response!
flash[:success] = 'Your details have been updated'
redirect_to user_path
elsif save_response!
feedback_cookie
track_feedback_start
redirect_to feedback_path(helpers.next_page.name)
else
render :show, status: :unprocessable_entity
end
end

private

# @note
# associate the user research participation question to the course form
# if answered during a training module
#
def research_participation
response = current_user.user_research_response
if content.skippable? && response.present? && !response.training_module.eql?('course')
response.update!(training_module: 'course')
end
end

# @return [Boolean]
def save_response!
current_user_response.update(
answers: user_answers,
correct: true,
text_input: response_params[:text_input],
)
end

# @return [Course]
def mod
Course.config
end

# @return [Training::Question, Training::Page]
def content
mod.page_by_name(content_name)
end

# @return [User, Guest, nil]
def current_user
super || guest
end

# @param question [Training::Question] default: current question
# @return [Response]
def current_user_response(question = content)
@current_user_response ||= current_user.response_for_shared(question, mod)
end

# @return [String]
def content_name
params[:id]
end

# OPTIMIZE: duplicated from ResponsesController
def response_params
params.require(:response).permit!
end

# OPTIMIZE: duplicated from ResponsesController
def user_answers
Array(response_params[:answers]).compact_blank.map(&:to_i)
end

# @return [Hash]
def feedback_cookie
cookies[:course_feedback] = { value: current_user.visit_token }
end

# @return [Boolean, nil]
def track_feedback_start
track('feedback_start') if untracked?('feedback_start')
end

# @return [Boolean, nil]
def track_feedback_complete
track('feedback_complete') if untracked?('feedback_complete')
end

# @param key [String]
# @return [Boolean]
def untracked?(key)
if current_user.guest?
Event.where(visit_id: current_visit, name: key).empty?
else
Event.where(user_id: current_user, name: key).empty?
end
end
end
26 changes: 25 additions & 1 deletion app/controllers/training/questions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ def show
# @see Tracking
# @return [Event] Show action
def track_events
if track_confidence_start?
if track_feedback_start?
track('feedback_start')
elsif track_feedback_complete?
track('feedback_complete')
elsif track_confidence_start?
track('confidence_check_start')
elsif track_assessment_start?
track('summative_assessment_start')
Expand All @@ -52,6 +56,16 @@ def track_confidence_start?
content.first_confidence? && confidence_start_untracked?
end

# @return [Boolean]
def track_feedback_start?
content.first_feedback? && feedback_start_untracked?
end

# @return [Boolean]
def track_feedback_complete?
content.last_feedback? && feedback_complete_untracked?
end

# @return [Boolean]
def track_assessment_start?
content.first_assessment? && summative_start_untracked?
Expand All @@ -68,5 +82,15 @@ def summative_start_untracked?
def confidence_start_untracked?
untracked?('confidence_check_start', training_module_id: mod.name)
end

# @return [Boolean]
def feedback_start_untracked?
untracked?('feedback_start', training_module_id: mod.name)
end

# @return [Boolean]
def feedback_complete_untracked?
untracked?('feedback_complete', training_module_id: mod.name)
end
end
end
11 changes: 8 additions & 3 deletions app/controllers/training/responses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
module Training
class ResponsesController < ApplicationController
include Learning
include Pagination

before_action :authenticate_registered_user!

Expand Down Expand Up @@ -41,10 +42,10 @@ def response_params
# @note migrate from user_answer to response
# @return [Boolean]
def save_response!
correct_answers = content.confidence_question? ? true : content.correct_answers.eql?(user_answers)
correct_answers = content.opinion_question? ? true : content.correct_answers.eql?(user_answers)

if Rails.application.migrated_answers?
current_user_response.update(answers: user_answers, correct: correct_answers)
current_user_response.update(answers: user_answers, correct: correct_answers, text_input: user_answer_text)
else
current_user_response.update(answer: user_answers, correct: correct_answers)
end
Expand All @@ -55,13 +56,17 @@ def user_answers
Array(response_params[:answers]).compact_blank.map(&:to_i)
end

def user_answer_text
response_params[:text_input]
end

def redirect
assessment.grade! if content.last_assessment?

if content.formative_question?
redirect_to training_module_question_path(mod.name, content.name)
else
redirect_to training_module_page_path(mod.name, content.next_item.name)
redirect_to training_module_page_path(mod.name, helpers.next_page.name)
end
end

Expand Down
22 changes: 0 additions & 22 deletions app/controllers/user_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,6 @@ def update_name
end
end

# @see config/initializers/devise.rb
def update_password
if current_user.update_with_password(user_password_params)
track('user_password_change', success: true)
bypass_sign_in(current_user)
redirect_to user_path, notice: 'Your new password has been saved.'
else
track('user_password_change', success: false)
render :edit_password, status: :unprocessable_entity
end
end

def update_email
if current_user.update(user_params)
track('user_email_change', success: true)
redirect_to user_path, notice: t('notice.email_changed')
else
track('user_email_change', success: false)
render :edit_email, status: :unprocessable_entity
end
end

def update_training_emails
if current_user.update(user_params)
track('user_training_emails_change', success: true)
Expand Down
Loading

0 comments on commit 5590a98

Please sign in to comment.