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

1033 external form upload #1038

Merged
merged 13 commits into from
Oct 17, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ def index
authorize! :external_form_upload,
context: {organization: Current.organization}
end

def create
authorize! :external_form_upload,
context: {organization: Current.organization}
file = params.require(:files).first
import_service = Organizations::Importers::GoogleCsvImportService.new(file)
import_service.call
end
end
end
end
4 changes: 4 additions & 0 deletions app/policies/organizations/external_form_upload_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@ class ExternalFormUploadPolicy < ApplicationPolicy
def index?
permission?(:manage_external_form_uploads)
end

def create?
permission?(:manage_external_form_uploads)
end
end
end
32 changes: 0 additions & 32 deletions app/services/organizations/csv_import_service.rb

This file was deleted.

45 changes: 45 additions & 0 deletions app/services/organizations/importers/google_csv_import_service.rb
kasugaijin marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require "csv"

module Organizations
module Importers
class GoogleCsvImportService
def initialize(file)
@file = file
@organization = Current.organization
end

def call
CSV.foreach(@file.to_path, headers: true, skip_blanks: true) do |row|
# Using Google Form headers
email = row["Email"].downcase
csv_timestamp = Time.parse(row["Timestamp"])

person = Person.find_by(email:, organization: @organization)
previous = FormSubmission.where(person:, csv_timestamp:)
next unless person && previous.empty?

latest_form_submission = person.latest_form_submission

if latest_form_submission.form_answers.empty?
create_form_answers(latest_form_submission, row)
else
create_form_answers(FormSubmission.create!(person:, csv_timestamp:), row)
end
end
end

private

def create_form_answers(form_submission, row)
ActiveRecord::Base.transaction do
row.each do |col|
next if col[0] == "Email" || col[0] == "Timestamp"

FormAnswer.create!(form_submission:,
question_snapshot: col[0], value: col[1])
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<div>
<div>
<p class="fs-4">If you use a third party form service, like Google Forms, to provide questionnaires to potential adopters, and receive their answers, you can upload the CSV of data here to import the questions and responses to this application. This means the questionnaire data will live in one place, and you will be able to view it for a given adoption application at any time. Note that the adopter must have an account in this application using the same email address they used in the third party form.</p>
<%= render "organizations/staff/shared/attachment_form", instance: @external_form, title: 'Files', url: staff_external_form_upload_index_path(@pet), attachment_type: 'files' %>
<p class="mb-4">Current form service supported: Google Forms</p>
<%= render "organizations/staff/shared/attachment_form", instance: nil, title: 'Files', url: staff_external_form_upload_index_path, multiple: false, attachment_type: 'files' %>
</div>
</div>
</section>
Expand Down
2 changes: 1 addition & 1 deletion app/views/organizations/staff/pets/tabs/_files.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="mb-4">
<%= render "organizations/staff/shared/attachment_form", instance: @pet, title: 'Files', url: attach_files_staff_pet_path(@pet), attachment_type: 'files' %>
<%= render "organizations/staff/shared/attachment_form", multiple: true, instance: @pet, title: 'Files', url: attach_files_staff_pet_path(@pet), attachment_type: 'files' %>
<%= render "organizations/shared/file_attachment_table", pet: @pet %>
</div>
2 changes: 1 addition & 1 deletion app/views/organizations/staff/pets/tabs/_photos.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="mb-4">
<%= render "organizations/staff/shared/attachment_form", instance: @pet, title: 'Photos', url: attach_images_staff_pet_path(@pet), attachment_type: 'images' %>
<%= render "organizations/staff/shared/attachment_form", multiple: true, instance: @pet, title: 'Photos', url: attach_images_staff_pet_path(@pet), attachment_type: 'images' %>
<%= render ImageAttachmentTableComponent.new(images: @pet.images) %>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<%= bootstrap_form_with(model: instance, url: url, method: :post) do |form| %>
<div class='form-group mt-2 d-flex'>
<div class="flex-grow-1">
<%= form.file_field attachment_type.to_sym, multiple: true, direct_upload: true,
<%= form.file_field attachment_type.to_sym, multiple: multiple, direct_upload: true,
class: "custom-attachments",
hide_label: true %>
</div>
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
resource :organization, only: %i[edit update]
resource :custom_page, only: %i[edit update]
resources :profile_reviews, only: [:show]
resources :external_form_upload, only: [:index]
resources :external_form_upload, only: %i[index create]

resources :pets do
resources :tasks
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require "test_helper"

module Organizations
module Staff
class ExternalFormUploadControllerTest < ActionDispatch::IntegrationTest
setup do
file = fixture_file_upload("google_form_sample.csv", "text/csv")
@params = {files: [file]}
user = create(:admin)
kasugaijin marked this conversation as resolved.
Show resolved Hide resolved
kasugaijin marked this conversation as resolved.
Show resolved Hide resolved
@adopter = create(:adopter, email: "adopter1111@alta.com")
@adopter2 = create(:adopter, email: "no_answer_will_be_created@alta.com")
sign_in user
end

test "Creates form answers for adopter in its latest form submission" do
assert_changes -> { @adopter.latest_form_submission.form_answers.count } do
post staff_external_form_upload_index_path, params: @params
end
end

test "It does not create form answers for adopter2" do
assert_no_difference -> { @adopter2.latest_form_submission.form_answers.count } do
post staff_external_form_upload_index_path, params: @params
end
end
end
end
end
7 changes: 1 addition & 6 deletions test/factories/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,8 @@
end
end

trait :with_person do
person
end

factory :adopter do
person
after(:build) do |user, _context|
after(:create) do |user, _context|
user.add_role(:adopter, user.organization)
create(:form_submission, person: user.person)
end
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/files/google_form_sample.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Timestamp,Nombre,Email,Dirección,Número de teléfono,Comentarios
2024/10/05 7:14:21 p. m. GMT-4,dsf,adopter1111@alta.com,dsfsdf,sdfsdf,ds
51 changes: 39 additions & 12 deletions test/services/organizations/csv_import_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@
module Organizations
class CsvImportServiceTest < ActiveSupport::TestCase
setup do
person = create(:person)
Current.organization = person.organization
adopter = create(:adopter)
Current.organization = adopter.organization

@file = Tempfile.new(["test", ".csv"])
headers = ["Timestamp", "First name", "Last name", "Email", "Address", "Phone number", *Faker::Lorem.questions]

@data = [
Time.now,
person.first_name,
person.last_name,
person.email,
"2024-10-02 12:45:37.000000000 +0000",
adopter.first_name,
adopter.last_name,
adopter.email,
Faker::Address.full_address,
Faker::PhoneNumber.phone_number,
*Faker::Lorem.sentences
]

@adopter = adopter

CSV.open(@file.path, "wb") do |csv|
csv << headers
end
Expand All @@ -29,26 +31,26 @@ class CsvImportServiceTest < ActiveSupport::TestCase
@file.unlink
end

should "add row information to database if person exists" do
should "add row information to database if adopter exists" do
CSV.open(@file.path, "ab") do |csv|
csv << @data
end

assert_difference "FormSubmission.count" do
assert_no_difference "FormSubmission.count" do
assert_difference("FormAnswer.count", + 7) do
Organizations::CsvImportService.new(@file).call
Organizations::Importers::GoogleCsvImportService.new(@file).call
end
end
end

should "skip row if person with email does not exist" do
should "skip row if adopter with email does not exist" do
@data[3] = "email@skip.com"
CSV.open(@file.path, "ab") do |csv|
csv << @data
end

assert_no_difference "FormSubmission.count" do
Organizations::CsvImportService.new(@file).call
Organizations::Importers::GoogleCsvImportService.new(@file).call
end
end

Expand All @@ -58,7 +60,32 @@ class CsvImportServiceTest < ActiveSupport::TestCase
csv << @data
end
assert_difference "FormSubmission.count" do
Organizations::CsvImportService.new(@file).call
Organizations::Importers::GoogleCsvImportService.new(@file).call
end
end

should "skip if the user exists and the timestamp matches that on the FormSubmisson" do
CSV.open(@file.path, "ab") do |csv|
csv << @data
end
@adopter.latest_form_submission.update(csv_timestamp: @data[0])

assert_no_difference -> { @adopter.latest_form_submission.form_answers.count } do
Organizations::Importers::GoogleCsvImportService.new(@file).call
end
end

should "creates a new form submission and adds the form answers if there is no 'empty' form submission and the timestamp is different" do
CSV.open(@file.path, "ab") do |csv|
csv << @data
end
Organizations::Importers::GoogleCsvImportService.new(@file).call
@adopter.latest_form_submission.update(csv_timestamp: "2024-10-03 12:45:37.000000000 +0000")

assert_difference -> { @adopter.person.form_submissions.count } do
assert_difference -> { @adopter.person.form_answers.count }, 7 do
Organizations::Importers::GoogleCsvImportService.new(@file).call
end
end
end
end
Expand Down
Loading