Skip to content

Commit

Permalink
allow deletion of contact email addresses for exhibits
Browse files Browse the repository at this point in the history
* contact_email_controller.rb: new controller exposing ContactEmail#destroy
* _contact.html.erb: add link to delete command and initially hidden span for contact email delete err msg.  move contact id hidden field inside contact div so that it gets deleted with the other contact form field stuff.
* _form.html.erb: pass exhibit into contact partial.
* _confirmation_status.html.erb: move column spacing out of this partial and into _contact.html.erb.
* exhibits.js: listeners for contact email delete ajax events, remove contact email delete functionality when copying the first contact email field for adding blanks to the list.
* spotlight.en.yml: confirmation and error message content.
* routes.rb: contact email deletion route.
* ability.rb: allow exhibit admins to manage contact email addresses
* contact_email_controller_spec.rb, contact_emails.rb, administration_spec.rb: tests for contact email deletion
  • Loading branch information
jmartin-sul committed Feb 15, 2017
1 parent 46ae30d commit 690d4cb
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 12 deletions.
21 changes: 16 additions & 5 deletions app/assets/javascripts/spotlight/exhibits.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,32 @@ Spotlight.onLoad(function() {
$("#another-email").on("click", function() {
var container = $(this).closest('.form-group');
var contacts = container.find('.contact');
var input_container = contacts.first().clone();
var inputContainer = contacts.first().clone();

// wipe out any values from the inputs
input_container.find('input').each(function() {
inputContainer.find('input').each(function() {
$(this).val('');
$(this).attr('id', $(this).attr('id').replace('0', contacts.length));
$(this).attr('name', $(this).attr('name').replace('0', contacts.length));
});

input_container.find('.confirmation-status').remove();
inputContainer.find('.contact-email-delete-wrapper').remove();
inputContainer.find('.confirmation-status').remove();

// bootstrap does not render input-groups with only one value in them correctly.
input_container.find('.input-group input:only-child').closest('.input-group').removeClass('input-group');
inputContainer.find('.input-group input:only-child').closest('.input-group').removeClass('input-group');

$(input_container).insertAfter(contacts.last());
$(inputContainer).insertAfter(contacts.last());
});

$('.contact-email-delete').on('ajax:success', function() {
$(this).closest('.contact').fadeOut(250, function() { $(this).remove(); });
});

$('.contact-email-delete').on('ajax:error', function(_event, _xhr, _status, error) {
var errSpan = $(this).closest('.contact').find('.contact-email-delete-error');
errSpan.show();
errSpan.find('.error-msg').first().text(error);
});

$('.btn-with-tooltip').tooltip();
Expand Down
22 changes: 22 additions & 0 deletions app/controllers/spotlight/contact_email_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Spotlight
##
# CRUD actions for exhibit contact emails
class ContactEmailController < Spotlight::ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

before_action :authenticate_user!
load_and_authorize_resource :exhibit, class: 'Spotlight::Exhibit'
load_and_authorize_resource through: :exhibit

def destroy
@contact_email.destroy
render json: { success: true, error: nil }
end

private

def record_not_found(_error)
render json: { success: false, error: 'Not Found' }, status: :not_found
end
end
end
2 changes: 1 addition & 1 deletion app/models/spotlight/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(user)

# exhibit admin
can [:update, :import, :export, :destroy], Spotlight::Exhibit, id: user.admin_roles.pluck(:resource_id)
can :manage, Spotlight::BlacklightConfiguration, exhibit_id: user.admin_roles.pluck(:resource_id)
can :manage, [Spotlight::BlacklightConfiguration, Spotlight::ContactEmail], exhibit_id: user.admin_roles.pluck(:resource_id)
can :manage, Spotlight::Role, resource_id: user.admin_roles.pluck(:resource_id), resource_type: 'Spotlight::Exhibit'

can :manage, PaperTrail::Version if user.roles.any?
Expand Down
2 changes: 1 addition & 1 deletion app/views/spotlight/exhibits/_confirmation_status.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="col-md-4 confirmation-status <%= 'not-validated' unless contact_email.confirmed? or contact_email.recently_sent? %>">
<div class="confirmation-status <%= 'not-validated' unless contact_email.confirmed? or contact_email.recently_sent? %>">
<% if contact_email.confirmed? %>
<span class="confirmed"><span class="glyphicon glyphicon-ok-sign"></span> <%= t('.confirmed') %></span>
<% elsif contact_email.recently_sent? %>
Expand Down
14 changes: 12 additions & 2 deletions app/views/spotlight/exhibits/_contact.html.erb
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
<%= contact.hidden_field :id %>
<div class="row contact">
<%= contact.hidden_field :id %>
<div class="col-md-8<%= ' has-error' if contact.object.errors[:email].present? %>">
<%= text_field_tag "#{contact.object_name}[email]", contact.object.email, class: 'exhibit-contact form-control' %>
<% if contact.object.errors[:email].present? %>
<span class="help-block"><%=contact.object.errors[:email].join(", ".html_safe) %></span>
<% end %>
<p>
<span class="contact-email-delete-error text-danger callout-danger" style="display: none;"><%= t('.email_delete_error') %> <span class="error-msg"></span></span>
</p>
</div>
<div class="col-md-4">
<% if contact.object.id %>
<span class="contact-email-delete-wrapper">
<%= link_to "<span class=\"btn-xs btn-danger\">#{t('.email_delete_button')}</span>".html_safe, exhibit_contact_email_path(exhibit_id: exhibit.id, id: contact.object.id), class: 'contact-email-delete', method: :delete, data: { confirm: t('.email_delete_confirmation'), remote: true } %>
</span>
<% end %>
<%= render partial: 'confirmation_status', locals: {contact_email: contact.object} unless contact.object.new_record? %>
</div>
<%= render partial: 'confirmation_status', locals: {contact_email: contact.object} unless contact.object.new_record? %>
</div>
2 changes: 1 addition & 1 deletion app/views/spotlight/exhibits/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<%= f.text_field :tag_list, value: f.object.tag_list.to_s %>
<%= f.form_group(:contact_emails, label: { text: nil, class: nil }, help: nil) do %>
<%= f.fields_for :contact_emails do |contact| %>
<%= render partial: 'contact', locals: {contact: contact} %>
<%= render partial: 'contact', locals: {exhibit: @exhibit, contact: contact} %>
<% end %>
<span id='another-email' class="btn-sm btn-info"><%= t('.add_contact_email_button') %></span>
<p class="help-block"><%= t(:'.fields.contact_emails.help_block') %></p>
Expand Down
4 changes: 4 additions & 0 deletions config/locales/spotlight.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,10 @@ en:
header: General
basic_settings:
heading: Basic settings
contact:
email_delete_confirmation: Delete contact email address?
email_delete_error: 'Problem deleting contact email:'
email_delete_button: Delete contact
form:
add_contact_email_button: Add new contact
fields:
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
post 'reindex', to: 'exhibits#reindex'
end

resources :contact_email, only: [:destroy], defaults: { format: :json }
resources :attachments, only: :create
resource :contact_form, path: 'contact', only: [:new, :create]
resource :blacklight_configuration, only: [:update]
Expand Down
63 changes: 63 additions & 0 deletions spec/controllers/spotlight/contact_email_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
describe Spotlight::ContactEmailController, type: :controller do
routes { Spotlight::Engine.routes }
let(:contact_email) { FactoryGirl.create(:contact_email) }

context 'when not logged in' do
describe 'DELETE destroy' do
it 'redirects to the login page' do
# note about odd behavior: it was discovered in testing that if format: :json is explicitly specified here, the user is redirected
# to login on rails 4, but gets a 401 on rails 5. we suspect differing CanCan behavior, but didn't investigate in depth.
delete :destroy, params: { id: contact_email, exhibit_id: contact_email.exhibit }
# custom logic in ApplicationController redirects user to app login page on CanCan::AccessDenied if user can't read current exhibit
expect(response).to redirect_to main_app.new_user_session_path
end
end
end

context 'when logged in' do
before { sign_in user }

context 'as a visitor' do
let(:user) { FactoryGirl.create(:exhibit_visitor) }

describe 'DELETE destroy' do
it 'redirects to the home page' do
delete :destroy, params: { id: contact_email, exhibit_id: contact_email.exhibit }
# custom logic in ApplicationController redirects user to app root on CanCan::AccessDenied if user's allowed to view current exhibit
expect(response).to redirect_to main_app.root_path
end
end
end

context 'as an exhibit curator' do
let(:user) { FactoryGirl.create(:exhibit_curator, exhibit: contact_email.exhibit) }

describe 'DELETE destroy' do
it 'redirects to the home page' do
delete :destroy, params: { id: contact_email, exhibit_id: contact_email.exhibit }
# custom logic in ApplicationController redirects user to app root on CanCan::AccessDenied if user's allowed to view current exhibit
expect(response).to redirect_to main_app.root_path
end
end
end

context 'as an exhibit admin' do
let(:user) { FactoryGirl.create(:exhibit_admin, exhibit: contact_email.exhibit) }

describe 'DELETE destroy' do
it 'is successful when the record exists' do
delete :destroy, params: { id: contact_email, exhibit_id: contact_email.exhibit }
expect(response).to be_successful
expect(JSON.parse(response.body)).to eq('success' => true, 'error' => nil)
end

it 'gives a 404 with appropriate message when the record no longer exists' do
contact_email.destroy
delete :destroy, params: { id: contact_email, exhibit_id: contact_email.exhibit }
expect(response.status).to eq 404
expect(JSON.parse(response.body)).to eq('success' => false, 'error' => 'Not Found')
end
end
end
end
end
6 changes: 6 additions & 0 deletions spec/factories/contact_emails.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FactoryGirl.define do
factory :contact_email, class: Spotlight::ContactEmail do
email 'exhibit_contact@example.com'
exhibit
end
end
78 changes: 76 additions & 2 deletions spec/features/exhibits/administration_spec.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

describe 'Exhibit Administration', type: :feature do
let(:exhibit) { FactoryGirl.create(:exhibit) }
let(:admin) { FactoryGirl.create(:exhibit_admin, exhibit: exhibit) }
let(:hidden_input_id_0) { 'exhibit_contact_emails_attributes_0_id' }
let(:email_id_0) { 'exhibit_contact_emails_attributes_0_email' }
let(:email_address_0) { 'admin@example.com' }
let(:hidden_input_id_1) { 'exhibit_contact_emails_attributes_1_id' }
let(:hidden_input_val_1) { '2' }
let(:email_id_1) { 'exhibit_contact_emails_attributes_1_email' }
let(:email_address_1) { 'admin2@example.com' }
before { login_as admin }
Expand All @@ -19,14 +21,16 @@
expect(page).to have_css('input.exhibit-contact')
expect(find_field(email_id_0).value).to be_blank
end
it 'stores and retreive a contact email address' do

it 'stores and retreives a contact email address' do
visit spotlight.edit_exhibit_path(exhibit)
fill_in email_id_0, with: email_address_0
click_button 'Save changes'
expect(page).to have_content('The exhibit was successfully updated.')
visit spotlight.edit_exhibit_path(exhibit)
expect(find_field(email_id_0).value).to eq email_address_0
end

it "has new inputs added when clicking on the 'add contact' button", js: true do
# Exhibit administration edit
visit spotlight.edit_exhibit_path(exhibit)
Expand All @@ -52,5 +56,75 @@
expect(find_field(email_id_0).value).to eq email_address_0
expect(find_field(email_id_1).value).to eq email_address_1
end

it 'allows deletion of contact email addresses', js: true do
# go to edit page, fill in first email field, click the + (add contact) button, fill in the second email field, click save.
visit spotlight.edit_exhibit_path(exhibit)
fill_in email_id_0, with: email_address_0
find('#another-email').click
fill_in email_id_1, with: email_address_1
click_button 'Save changes'

# saving should redirect back to the edit page, which should now have the contact
# email addresses, with delete buttons now that the entries have been saved.
expect(find_field(email_id_0).value).to eq email_address_0
expect(find_field(email_id_1).value).to eq email_address_1
expect(find_all('.contact-email-delete').length).to eq 2

# delete the first address in the list
page.accept_confirm do
find_all('.contact-email-delete').first.click
end

# the page element for the first entry should now be gone, but the second should still be present
expect(page).not_to have_css("##{email_id_0}")
expect(find_field(email_id_1).value).to eq email_address_1

# reload the edit page to confirm deletion from db...
visit spotlight.edit_exhibit_path(exhibit)

# what was the second address should now be the only one on the page, and should now be
# in the first/only form field (form fields are numbered at page load from 0).
expect(find_field(email_id_0).value).to eq email_address_1
expect(page).not_to have_css("##{email_id_1}")

# the hidden input field is what contains the underlying id of the contact for db retrieval
expect(find("##{hidden_input_id_0}", visible: false).value).to eq hidden_input_val_1
end

it 'creates an empty form field with no associated delete command or confirmation status when creating a blank row for a new contact', js: true do
# create a contact email address and save (shouldn't see delete button or confirmation status on unsaved entries)
visit spotlight.edit_exhibit_path(exhibit)
fill_in email_id_0, with: email_address_0
click_button 'Save changes'

find('#another-email').click
expect(find_field(email_id_0).value).to eq email_address_0
expect(find_field(email_id_1).value).to eq ''

# conf status and email delete are nested in a sibling div of the hidden
# id field that's used to indicate the id of the record to be updated.
expect(page).to have_css("##{hidden_input_id_0}~div div.confirmation-status")
expect(page).to have_css("##{hidden_input_id_0}~div span.contact-email-delete-wrapper")
expect(page).not_to have_css("##{hidden_input_id_1}~div div.confirmation-status")
expect(page).not_to have_css("##{hidden_input_id_1}~div span.contact-email-delete-wrapper")
expect(find_all('.confirmation-status').length).to eq 1
expect(find_all('.contact-email-delete-wrapper').length).to eq 1
end

it 'displays the error message from the server if there is one', js: true do
visit spotlight.edit_exhibit_path(exhibit)
fill_in email_id_0, with: email_address_0
find('#another-email').click
fill_in email_id_1, with: email_address_1
click_button 'Save changes'

Spotlight::ContactEmail.all.first.destroy
page.accept_confirm do
find_all('.contact-email-delete').first.click
end

expect(page).to have_css("##{hidden_input_id_0}~div span.contact-email-delete-error", text: 'Problem deleting contact email: Not Found')
end
end
end

0 comments on commit 690d4cb

Please sign in to comment.