Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feedback form #69

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
@import 'top-navbar';
@import 'results';
@import 'searcher-anchors';
@import 'feedback-form';
10 changes: 10 additions & 0 deletions app/assets/stylesheets/feedback-form.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#feedback-form {
background: $light-sandstone;
border-bottom: 3px solid $beige-40-percent;
}

.feedback-form {
background: $light-sandstone;
font-size: 14px;
padding-top: 20px;
}
1 change: 1 addition & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
layout 'application'
end
50 changes: 50 additions & 0 deletions app/controllers/feedback_forms_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

##
# Controller used for feedback forms
class FeedbackFormsController < ApplicationController

def new; end

def create
return unless request.post?
if validate
FeedbackMailer.submit_feedback(params, request.remote_ip).deliver_now
flash[:success] = t('feedback_form.success')
end
respond_to do |format|
format.json do
render json: flash
end
format.html do
redirect_to params[:url]
end
end
end

protected

def url_regex
%r{.*href=.*|.*url=.*|.*http:\/\/.*|.*https:\/\/.*}i
end

def validate
errors = []
if params[:message].nil? || params[:message] == ''
errors << 'A message is required'
end
if params[:email_address] && params[:email_address] != ''
errors << 'You have filled in a field that makes you appear as a spammer'\
'. Please follow the directions for the individual form fields.'
end
if params[:message] =~ url_regex
errors << 'Your message appears to be spam, and has not been sent. Pleas'\
'e try sending your message again without any links in the comments.'
end
if params[:user_agent] =~ url_regex || params[:viewport] =~ url_regex
errors << 'Your message appears to be spam, and has not been sent.'
end
flash[:error] = errors.join('<br/>') unless errors.empty?
flash[:error].nil?
end
end
15 changes: 15 additions & 0 deletions app/helpers/feedback_form_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

##
# Helpers for the feedback form
module FeedbackFormHelper
##
# Ported from QuickSearch, as the application layout is dependent on it.
def body_class
[controller_name, action_name].join('-')
end

def show_feedback_form?
!controller.instance_of?(FeedbackFormsController)
end
end
14 changes: 14 additions & 0 deletions app/mailers/feedback_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

##
# Mailer for feedback form
class FeedbackMailer < ActionMailer::Base
def submit_feedback(params, ip)
@mailer_parser = FeedbackMailerParser.new(params, ip)

mail(to: Settings.EMAIL_TO.FEEDBACK,
subject: 'Feedback from SearchWorks',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be "Bento" or something rather than SearchWorks?

from: '[email protected]',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here -- bento something rather than searchworks.stanford.edu?

reply_to: Settings.EMAIL_TO.FEEDBACK)
end
end
36 changes: 36 additions & 0 deletions app/models/feedback_mailer_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

##
# Class containing logic for feedback form params parsing
class FeedbackMailerParser
attr_reader :params, :ip

def initialize(params, ip)
@params = params
@ip = ip
end

def name
params.fetch(:name, 'No name given')
end

def email
params.fetch(:to, 'No email given')
end

def message
params[:message].to_s
end

def url
params[:url]
end

def user_agent
params[:user_agent]
end

def viewport
params[:viewport]
end
end
1 change: 1 addition & 0 deletions app/views/feedback_forms/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= render 'shared/feedback_forms/form' %>
11 changes: 11 additions & 0 deletions app/views/feedback_mailer/submit_feedback.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Name: <%= @mailer_parser.name %>
Email: <%= @mailer_parser.email %>

Comment:
<%= @mailer_parser.message.html_safe %>

Message sent from: <%= @mailer_parser.url %>
Host: <%= Settings.HOSTNAME %>
IP: <%= @mailer_parser.ip %>
User agent: <%= @mailer_parser.user_agent %>
Viewport: <%= @mailer_parser.viewport %>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<head>
<title><%= content_for(:title) %></title>
<meta content="ie=edge, chrome=1" http-equiv="x-ua-compatible"/>
<link rel="search" type="application/opensearchdescription+xml" href="<%= opensearch_path %>" title="<%= t('opensearch_description') %>"/>
<link rel="search" type="application/opensearchdescription+xml" href="<%= quick_search.opensearch_path %>" title="<%= t('opensearch_description') %>"/>
<!-- css -->
<%= stylesheet_link_tag 'application' %>

Expand All @@ -26,6 +26,7 @@
<div id="content" role="document" class="page">
<div class="">
<main id="main-content" role="main" class="">
<%= render partial: 'shared/flash_msg', layout: 'shared/flash_messages' %>
<div class="skip-link">
<a href="#main-content"></a>
</div>
Expand Down
7 changes: 7 additions & 0 deletions app/views/shared/_flash_messages.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class='row'>
<div class='col-md-12'>
<div id='main-flashes'>
<%= yield %>
</div>
</div>
</div>
17 changes: 17 additions & 0 deletions app/views/shared/_flash_msg.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="flash_messages">
<% [:success, :notice, :error, :alert].each do |type| %>
<%- alert_class = case type
when :success then "alert-success"
when :notice then "alert-info"
when :alert then "alert-warning"
when :error then "alert-danger"
else "alert-#{type}"
end
-%>
<% if flash[type] %>
<div class="alert <%=alert_class %>"><%= sanitize(flash[type]) %>
<a class="close" data-dismiss="alert" href="#">&times;</a>
</div>
<% end %>
<% end %>
</div>
9 changes: 8 additions & 1 deletion app/views/shared/_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@
<div id="top-navbar">
<ul>
<li><a href='https://library.stanford.edu/myaccount'>My Account</a></li>
<li><a href='https://library.stanford.edu/ask/email/feedback'>Feedback</a></li>
<li>
<%= link_to main_app.feedback_path, data: { toggle: 'collapse', target: '#feedback-form'}, 'aria-expanded' => 'false', 'aria-controls' => 'feedback-form' do %>
Feedback
<% end %>
</li>
</ul>
</div>
</div>
<div id="feedback-form" class='collapse'>
<%= render 'shared/feedback_forms/form' if show_feedback_form? %>
</div>
<div id="masthead-container">
<div id="masthead">
<%= link_to root_path do %>
Expand Down
50 changes: 50 additions & 0 deletions app/views/shared/feedback_forms/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<div class='container'>
<% if Settings.FEEDBACK_ALERT %>
<p class='feedback-alert'>
<%= t('blacklight.feedback_form.feedback_alert_html', href: mail_to('[email protected]')) %>
</p>
<% end %>
<div class='row justify-content-center'>
<div class='col-8'>
<%= form_tag main_app.feedback_form_path, method: :post, class:'feedback-form', role:'form' do %>
<div class='container'>
<div class='form-group row'>
<%= render 'shared/feedback_forms/reporting_from' %>
</div>
</div>
<span style='display:none;visibility:hidden;'>
<%= label_tag(:email_address, 'Ignore this text box. It is used to detect spammers. If you enter anything into this text box, your message will not be sent.') %><br/>
<%= email_field_tag :email_address %><br/>
<%= text_field_tag :user_agent %>
<%= text_field_tag :viewport %>
</span>
<div class='container'>
<div class='form-group row'>
<%= label_tag(:message, 'Message', class:'col-md-3 col-form-label text-md-right') %>
<div class='col-md-9'>
<%= text_area_tag :message, '', rows:'5', class:'form-control', required: true %>
</div>
</div>
<div class='form-group row'>
<%= label_tag(:name, 'Your name', class:'col-md-3 col-form-label text-md-right') %>
<div class='col-md-9'>
<%= text_field_tag :name, '', class:'form-control', required: true %>
</div>
</div>
<div class='form-group row'>
<%= label_tag(:to, 'Your email', class:'col-md-3 col-form-label text-md-right') %>
<div class='col-md-9'>
<%= email_field_tag :to, '', class:'form-control', required: true %>
</div>
</div>
<div class='form-group row'>
<div class='col text-center'>
<button type='submit' class='btn btn-primary'>Send</button>
<%= link_to 'Cancel', :back, class:'cancel-link', data: {toggle:'collapse', target: '#feedback-form' } %>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
5 changes: 5 additions & 0 deletions app/views/shared/feedback_forms/_reporting_from.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<label class='col-md-3 col-form-label text-md-right'>
Reporting from
</label>
<div class=' col-md-9 reporting-from-field'><%= request.referer %></div>
<%= hidden_field_tag :url, request.referer, class:'reporting-from-field' %>
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ en:
no_results_link: "http://library.stanford.edu/search/website"
search_form:
placeholder: "catalog + articles + website + more"
feedback_form:
success: '<strong>Thank you!</strong> Your feedback has been sent.'
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
Rails.application.routes.draw do
resource :feedback_form, path: 'feedback', only: %i[new create]
get 'feedback' => 'feedback_forms#new'

mount QuickSearch::Engine => "/"

root to: 'quick_search/search#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
3 changes: 3 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ CATALOG_HOME_URL: 'https://searchworks.stanford.edu'
ARTICLES_HOME_URL: 'https://searchworks.stanford.edu/articles'
LIBRARY_WEBSITE_HOME_URL: 'https://library.stanford.edu'
YEWNO_HOME_URL: 'https://yewno.com/edu'
EMAIL_TO:
FEEDBACK: "[email protected]"
HOSTNAME: <%= (`echo $HOSTNAME`).gsub('.stanford.edu', '') %>
1 change: 1 addition & 0 deletions config/settings/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
HOSTNAME: TEST-HOST
46 changes: 46 additions & 0 deletions spec/controllers/feedback_forms_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'rails_helper'

describe FeedbackFormsController do
describe 'format json' do
it 'should return json success' do
post :create, params: { url: 'http://test.host/', message: 'Hello Kittenz', format: 'json' }
expect(flash[:success]).to eq '<strong>Thank you!</strong> Your feedback has been sent.'
end
it 'should return html success' do
post :create, params: { url: 'http://test.host/', message: 'Hello Kittenz' }
expect(flash[:success]).to eq '<strong>Thank you!</strong> Your feedback has been sent.'
end
end
describe 'validate' do
it 'should return an error if no message is sent' do
post :create, params: { url: 'http://test.host/', message: '', email_address: '' }
expect(flash[:error]).to eq 'A message is required'
end
it 'should block potential spam with a href in the message' do
post :create, params: { message: 'I like to spam by sending you a <a href="http://www.somespam.com">Link</a>. lolzzzz', url: 'http://test.host/', email_address: '' }
expect(flash[:error]).to eq 'Your message appears to be spam, and has not been sent. Please try sending your message again without any links in the comments.'
end
it 'should block potential spam with a url= in the message' do
post :create, params: { message: 'I like to spam by sending you a <a url="http://www.somespam.com">Link</a>. lolzzzz', url: 'http://test.host/', email_address: '' }
expect(flash[:error]).to eq 'Your message appears to be spam, and has not been sent. Please try sending your message again without any links in the comments.'
end
it 'should block potential spam with a http:// in the message' do
post :create, params: { message: 'I like to spam by sending you a http://www.somespam.com. lolzzzz', url: 'http://test.host/', email_address: '' }
expect(flash[:error]).to eq 'Your message appears to be spam, and has not been sent. Please try sending your message again without any links in the comments.'
end
it 'should block potential spam with a http:// in the user_agent field' do
post :create, params: { user_agent: 'http://www.google.com', message: 'Legit message', url: 'http://test.host' }
expect(flash[:error]).to eq 'Your message appears to be spam, and has not been sent.'
end
it 'should block potential spam with a http:// in the viewport field' do
post :create, params: { viewport: 'http://www.google.com', message: 'Legit message', url: 'http://test.host' }
expect(flash[:error]).to eq 'Your message appears to be spam, and has not been sent.'
end
it 'should return an error if a bot fills in the email_addrss field (email is correct field)' do
post :create, params: { message: 'I am spamming you!', url: 'http://test.host/', email_address: 'spam!' }
expect(flash[:error]).to eq 'You have filled in a field that makes you appear as a spammer. Please follow the directions for the individual form fields.'
end
end
end
50 changes: 50 additions & 0 deletions spec/features/feedback_form_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

require 'rails_helper'

feature 'Feedback form (js)', js: true do
before do
visit root_path
end
scenario 'feedback form should be hidden' do
expect(page).to_not have_css('#feedback-form', visible: true)
end
scenario 'feedback form should be shown filled out and submitted' do
skip('Passes locally, not on Travis.') if ENV['CI']
click_link 'Feedback'
expect(page).to have_css('#feedback-form', visible: true)
# TODO: with javascript widget switch to <button>
expect(page).to have_css('a', text: 'Cancel')
within 'form.feedback-form' do
fill_in('message', with: 'This is only a test')
fill_in('name', with: 'Ronald McDonald')
fill_in('to', with: '[email protected]')
click_button 'Send'
end
expect(page).to have_css(
'div.alert-success',
text: 'Thank you! Your feedback has been sent.'
)
end
end

feature 'Feedback form (no js)' do
before do
visit root_path
end
scenario 'feedback form should be shown filled out and submitted' do
click_link 'Feedback'
expect(page).to have_css('#feedback-form', visible: true)
expect(page).to have_css('a', text: 'Cancel')
within 'form.feedback-form' do
fill_in('message', with: 'This is only a test')
fill_in('name', with: 'Ronald McDonald')
fill_in('to', with: '[email protected]')
click_button 'Send'
end
expect(page).to have_css(
'div.alert-success',
text: 'Thank you! Your feedback has been sent.'
)
end
end
Loading