From 649dc7348ffb0c0c5be99d4b7757b0d899c61acc Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 13:18:23 +0100 Subject: [PATCH 01/28] Add `parse_status` to Genotype --- db/migrate/20171231121548_add_parse_status_to_genotypes.rb | 5 +++++ db/structure.sql | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20171231121548_add_parse_status_to_genotypes.rb diff --git a/db/migrate/20171231121548_add_parse_status_to_genotypes.rb b/db/migrate/20171231121548_add_parse_status_to_genotypes.rb new file mode 100644 index 00000000..ce1a2758 --- /dev/null +++ b/db/migrate/20171231121548_add_parse_status_to_genotypes.rb @@ -0,0 +1,5 @@ +class AddParseStatusToGenotypes < ActiveRecord::Migration + def change + add_column :genotypes, :parse_status, :string + end +end diff --git a/db/structure.sql b/db/structure.sql index 23081466..57c6d620 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -501,7 +501,8 @@ CREATE TABLE genotypes ( genotype_file_name character varying(255), genotype_content_type character varying(255), genotype_file_size integer, - genotype_updated_at timestamp without time zone + genotype_updated_at timestamp without time zone, + parse_status character varying ); @@ -2169,3 +2170,5 @@ INSERT INTO schema_migrations (version) VALUES ('20161226175703'); INSERT INTO schema_migrations (version) VALUES ('20171113104813'); +INSERT INTO schema_migrations (version) VALUES ('20171231121548'); + From f96250a11d70eb55efa33af3dbb80e996e033896 Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 13:31:09 +0100 Subject: [PATCH 02/28] Validate `parse_status` --- app/models/genotype.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/genotype.rb b/app/models/genotype.rb index f752f073..bc8f4703 100644 --- a/app/models/genotype.rb +++ b/app/models/genotype.rb @@ -5,6 +5,8 @@ class Genotype < ActiveRecord::Base belongs_to :user has_many :user_snps, dependent: :delete_all validates_presence_of :user + validates :parse_status, inclusion: { in: %w[queued parsing done error] }, + allow_nil: true has_attached_file :genotype, url: '/data/:fs_filename', path: "#{Rails.root}/public/data/:fs_filename" From dff43414912af89c0b981bfd51dc77bb5b39117c Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 14:00:51 +0100 Subject: [PATCH 03/28] Update parse status when parsing --- app/controllers/genotypes_controller.rb | 1 + app/workers/parsing.rb | 2 ++ app/workers/preparsing.rb | 3 +++ spec/features/upload_genotype_spec.rb | 16 +++++++++++++--- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/controllers/genotypes_controller.rb b/app/controllers/genotypes_controller.rb index 462b9d1f..adda23d4 100644 --- a/app/controllers/genotypes_controller.rb +++ b/app/controllers/genotypes_controller.rb @@ -21,6 +21,7 @@ def new def create @genotype = Genotype.create(genotype_params) @genotype.user = current_user + @genotype.parse_status = 'queued' if @genotype.valid? && @genotype.save Preparsing.perform_async(@genotype.id) # award for genotyping-upload diff --git a/app/workers/parsing.rb b/app/workers/parsing.rb index 11617d3f..b86acac0 100644 --- a/app/workers/parsing.rb +++ b/app/workers/parsing.rb @@ -21,11 +21,13 @@ def perform(genotype_id) send_logged(:insert_into_snps) send_logged(:insert_into_user_snps) end + genotype.update!(parse_status: 'done') send_logged(:notify_user) stats[:duration] = "#{(Time.current - start_time).round(3)}s" logger.info("Finished parsing: #{stats.to_a.map { |s| s.join('=') }.join(', ')}") rescue => e + genotype.update!(parse_status: 'error') unless genotype.parse_status == 'done' logger.error("Failed with #{e.class}: #{e.message}") raise end diff --git a/app/workers/preparsing.rb b/app/workers/preparsing.rb index 0f4e2d16..ae435dce 100644 --- a/app/workers/preparsing.rb +++ b/app/workers/preparsing.rb @@ -9,6 +9,7 @@ class Preparsing def perform(genotype_id) genotype = Genotype.find(genotype_id) + genotype.update!(parse_status: 'parsing') logger.info "Starting preparse" biggest = '' @@ -134,5 +135,7 @@ def perform(genotype_id) Parsing.perform_async(genotype.id) end + rescue => e + genotype.update!(parse_status: 'error') end end diff --git a/spec/features/upload_genotype_spec.rb b/spec/features/upload_genotype_spec.rb index a76f4c3e..e8fa149b 100644 --- a/spec/features/upload_genotype_spec.rb +++ b/spec/features/upload_genotype_spec.rb @@ -7,12 +7,22 @@ sign_in(user) end + let(:genotype) { Genotype.last } + scenario 'uploads first genotype' do visit '/genotypes/new' + attach_file('genotype[genotype]', File.absolute_path('test/data/23andMe_test.csv')) choose '23andme-format' - click_on 'Upload' - expect(page).to have_content('Genotype was successfully uploaded!') - expect(page).to have_content("You've unlocked an achievement:") + Sidekiq::Testing.disable! do + click_on 'Upload' + expect(page).to have_content('Genotype was successfully uploaded!') + expect(page).to have_content("You've unlocked an achievement:") + end + + expect(genotype.reload.parse_status).to eq('queued') + Preparsing.perform_async(genotype.id) + expect(genotype.reload.parse_status).to eq('done') + expect(genotype.user_snps).to be_present end end From cdeae98ae0ce7f2a24b978b868eeeea07a79fe69 Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 15:29:44 +0100 Subject: [PATCH 04/28] Introduce ParseError --- app/workers/parsing.rb | 3 ++ spec/workers/parsing_spec.rb | 61 +++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/app/workers/parsing.rb b/app/workers/parsing.rb index b86acac0..4ad637e7 100644 --- a/app/workers/parsing.rb +++ b/app/workers/parsing.rb @@ -60,6 +60,7 @@ def normalize_csv row[3].is_a?(String) && (1..2).include?(row[3].length) end @normalized_csv = csv.map { |row| row.join(',') }.join("\n") + raise(ParseError, 'No data found in file') if @normalized_csv.empty? stats[:rows_after_parsing] = csv.length end @@ -236,4 +237,6 @@ def send_logged(method) logger.info("calling of method `#{method}` took #{took} s") ret end + + ParseError = Class.new(RuntimeError) end diff --git a/spec/workers/parsing_spec.rb b/spec/workers/parsing_spec.rb index 43e50a7b..d291418b 100644 --- a/spec/workers/parsing_spec.rb +++ b/spec/workers/parsing_spec.rb @@ -1,18 +1,51 @@ # frozen_string_literal: true describe Parsing do - describe '#notify_user' do - let(:mail) { double('mail') } - let(:genotype) { double('genotype', id: 1) } - let(:stats) { { foos: 7 } } - - it 'sends an email to the user' do - subject.instance_variable_set(:@stats, stats) - subject.instance_variable_set(:@genotype, genotype) - expect(UserMailer).to receive(:finished_parsing).with(genotype.id, stats) - .and_return(mail) - expect(mail).to receive(:deliver_later) - - subject.notify_user - end + let(:genotype) do + create( + :genotype, + genotype: File.open(File.absolute_path('test/data/23andMe_test.csv')), + filetype: '23andme', + parse_status: 'parsing' + ) + end + + let(:emails) do + ActionMailer::Base.deliveries + end + + it 'parses a genotype file' do + described_class.new.perform(genotype.id) + + genotype.reload + + expect(genotype.user_snps.count).to eq(5) + expect( + genotype + .user_snps + .order(:snp_name) + .pluck(:genotype_id, :snp_name, :local_genotype) + ).to eq( + [ + [genotype.id, 'rs11240777', 'AG'], + [genotype.id, 'rs12124819', 'AG'], + [genotype.id, 'rs3094315', 'AA'], + [genotype.id, 'rs3131972', 'GG'], + [genotype.id, 'rs4477212', 'AA'] + ] + ) + expect(genotype.parse_status).to eq('done') + + expect(emails.count).to eq(1) + expect(emails.first.subject).to eq('Finished parsing your genotyping') + end + + it 'sets the parse status to "error" if parsing failed' do + genotype.update!(genotype: StringIO.new('💥')) + + expect do + described_class.new.perform(genotype.id) + end.to raise_error(Parsing::ParseError, 'No data found in file') + + expect(genotype.reload.parse_status).to eq('error') end end From 06cde9e4817cab49cd2ebcdc653b0df0c888625c Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 15:31:41 +0100 Subject: [PATCH 05/28] Add TODO --- spec/workers/parsing_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/workers/parsing_spec.rb b/spec/workers/parsing_spec.rb index d291418b..0b43ac82 100644 --- a/spec/workers/parsing_spec.rb +++ b/spec/workers/parsing_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# TODO: Add test cases for different filetypes. describe Parsing do let(:genotype) do create( @@ -13,7 +14,7 @@ ActionMailer::Base.deliveries end - it 'parses a genotype file' do + it 'parses a 23andme file' do described_class.new.perform(genotype.id) genotype.reload From 46f39f8d07cf3e3dfd75e3dac902e65e69d551db Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 15:39:38 +0100 Subject: [PATCH 06/28] Add test for `parse_status` update in Preparsing --- app/workers/preparsing.rb | 5 +++-- spec/workers/preparsing_spec.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 spec/workers/preparsing_spec.rb diff --git a/app/workers/preparsing.rb b/app/workers/preparsing.rb index ae435dce..403b99e8 100644 --- a/app/workers/preparsing.rb +++ b/app/workers/preparsing.rb @@ -29,8 +29,8 @@ def perform(genotype_id) logger.info "copied file" end - rescue - logger.info "nothing to unzip, seems to be a text-file in the first place" + rescue Zip::Error + logger.info 'nothing to unzip, seems to be a text-file in the first place' end # now that they are unzipped, check if they're actually proper files @@ -137,5 +137,6 @@ def perform(genotype_id) end rescue => e genotype.update!(parse_status: 'error') + raise end end diff --git a/spec/workers/preparsing_spec.rb b/spec/workers/preparsing_spec.rb new file mode 100644 index 00000000..651b865c --- /dev/null +++ b/spec/workers/preparsing_spec.rb @@ -0,0 +1,16 @@ +RSpec.describe Preparsing do + let(:genotype) do + create( + :genotype, + genotype: File.open(File.absolute_path('test/data/23andMe_test.csv')), + filetype: '23andme', + parse_status: 'queued' + ) + end + + it "updates the genotype's parse status" do + described_class.new.perform(genotype.id) + + expect(genotype.reload.parse_status).to eq('parsing') + end +end From e87be9076df194779185bcfe2f82eb8315b8df66 Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 15:45:46 +0100 Subject: [PATCH 07/28] Don't hit the database twice when creating Genotype --- app/controllers/genotypes_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/genotypes_controller.rb b/app/controllers/genotypes_controller.rb index adda23d4..771cb06a 100644 --- a/app/controllers/genotypes_controller.rb +++ b/app/controllers/genotypes_controller.rb @@ -19,7 +19,7 @@ def new end def create - @genotype = Genotype.create(genotype_params) + @genotype = Genotype.new(genotype_params) @genotype.user = current_user @genotype.parse_status = 'queued' if @genotype.valid? && @genotype.save From 21718c857f9d0e02b88ba8bf282087fc2c74c003 Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 15:46:54 +0100 Subject: [PATCH 08/28] Remove unneded `=> e` --- app/workers/preparsing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/preparsing.rb b/app/workers/preparsing.rb index 403b99e8..18142c84 100644 --- a/app/workers/preparsing.rb +++ b/app/workers/preparsing.rb @@ -135,7 +135,7 @@ def perform(genotype_id) Parsing.perform_async(genotype.id) end - rescue => e + rescue genotype.update!(parse_status: 'error') raise end From e656ac09521051d1d6ff2057f6e08aaa8a7fabcb Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Sun, 31 Dec 2017 15:52:41 +0100 Subject: [PATCH 09/28] Add `parse_error_message` to Genotype --- .../20171231144834_add_parse_error_message_to_genotypes.rb | 5 +++++ db/structure.sql | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20171231144834_add_parse_error_message_to_genotypes.rb diff --git a/db/migrate/20171231144834_add_parse_error_message_to_genotypes.rb b/db/migrate/20171231144834_add_parse_error_message_to_genotypes.rb new file mode 100644 index 00000000..d8e0c068 --- /dev/null +++ b/db/migrate/20171231144834_add_parse_error_message_to_genotypes.rb @@ -0,0 +1,5 @@ +class AddParseErrorMessageToGenotypes < ActiveRecord::Migration + def change + add_column :genotypes, :parse_error_message, :string + end +end diff --git a/db/structure.sql b/db/structure.sql index 57c6d620..fcd3d1d8 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -502,7 +502,8 @@ CREATE TABLE genotypes ( genotype_content_type character varying(255), genotype_file_size integer, genotype_updated_at timestamp without time zone, - parse_status character varying + parse_status character varying, + parse_error_message character varying ); @@ -2172,3 +2173,5 @@ INSERT INTO schema_migrations (version) VALUES ('20171113104813'); INSERT INTO schema_migrations (version) VALUES ('20171231121548'); +INSERT INTO schema_migrations (version) VALUES ('20171231144834'); + From 00aeadad3e1ec6b2b6cae80a5bda3aab461e60cb Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Mon, 1 Jan 2018 16:38:45 +0100 Subject: [PATCH 10/28] WIP --- app/views/users/_edit.html.erb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/views/users/_edit.html.erb b/app/views/users/_edit.html.erb index cd8035e9..ee5ae155 100644 --- a/app/views/users/_edit.html.erb +++ b/app/views/users/_edit.html.erb @@ -39,6 +39,17 @@ +
+

Your Genotypes

+ + + + + + +
File
+
+

Your Phenotypes

From 98d5d1ed9f54000aa6907a843c0b746602773e79 Mon Sep 17 00:00:00 2001 From: Helge Rausch Date: Tue, 2 Jan 2018 13:07:24 +0100 Subject: [PATCH 11/28] Add a list of uploaded genotypes to user settings --- app/helpers/users_helper.rb | 11 +++++++++++ app/views/users/_edit.html.erb | 20 ++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 app/helpers/users_helper.rb diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 00000000..0266ecb0 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,11 @@ +module UsersHelper + def display_parse_status(genotype) + label_class = case genotype.parse_status + when 'done' then 'label-success' + when 'queued' then 'label-default' + when 'parsing' then 'label-primary' + when 'error' then 'label-danger' + end + content_tag('span', genotype.parse_status, class: "label #{label_class}") + end +end diff --git a/app/views/users/_edit.html.erb b/app/views/users/_edit.html.erb index ee5ae155..d212a4cf 100644 --- a/app/views/users/_edit.html.erb +++ b/app/views/users/_edit.html.erb @@ -5,6 +5,7 @@