Skip to content

Commit

Permalink
1033 external form upload (#1038)
Browse files Browse the repository at this point in the history
* adds route and controller method to use the importer to import external csv  forms with google_csv_import_service

* adds test for external_form_upload#create

* Solve linter and test issues

* Adds multiple param to partial
Modify external form controller to support just one file upload
correct controller test

* Add new test and change to nested modules

* add latest_form_submission

* - refactors google_csv_import_service
- Updates users Factorie to create the person
- fix external_form_upload_controller_tests

* Add csv_import_service_tests tests

* Apply Dry principle adding  method

* Improve naming

* admin variable
  • Loading branch information
wandergithub authored Oct 17, 2024
1 parent 389ed26 commit c4a3c32
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 55 deletions.
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
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]}
admin = create(:admin)
@adopter = create(:adopter, email: "adopter1111@alta.com")
@adopter2 = create(:adopter, email: "no_answer_will_be_created@alta.com")
sign_in admin
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

0 comments on commit c4a3c32

Please sign in to comment.