Merge pull request #5230 from solgenomics/topic/open_pollinated
[sgn.git] / lib / SGN / Controller / AJAX / VectorConstruct.pm
blobd1e068cdfe7e7c0909a97bc4214bea8502a754c0
1 =head1 NAME
3 SGN::Controller::AJAX::VectorConstruct - a REST controller class for Vector Constructs.
5 =head1 DESCRIPTION
7 Synchronizes vector constructs into the database from the ETHZ CASS database.
9 =head1 AUTHOR
11 Nicolas Morales <nm529@cornell.edu>
13 =cut
15 package SGN::Controller::AJAX::VectorConstruct;
17 use Moose;
18 use JSON -support_by_pp;
19 use List::MoreUtils qw /any /;
20 use Data::Dumper;
21 use JSON;
22 use SGN::Model::Cvterm;
23 use CXGN::Stock::Vector;
24 use CXGN::Stock::Vector::ParseUpload;
25 use Try::Tiny;
26 use Encode;
27 use JSON::XS qw | decode_json |;
28 use utf8;
31 BEGIN { extends 'Catalyst::Controller::REST' }
33 __PACKAGE__->config(
34 default => 'application/json',
35 stash_key => 'rest',
36 map => { 'application/json' => 'JSON' },
39 sub sync_cass_constructs : Path('/ajax/cass_vector_construct/sync') Args(0) ActionClass('REST') { }
41 sub sync_cass_constructs_POST {
42 my $self = shift;
43 my $c = shift;
44 my $status = '';
45 my $sp_person_id = $c->user() ? $c->user->get_object()->get_sp_person_id() : undef;
46 my $schema = $c->dbic_schema("Bio::Chado::Schema", undef, $sp_person_id);
48 my $construct_names = decode_json($c->req->param("data"));
49 my %construct_hash = %$construct_names;
50 my $constructs = $construct_hash{construct};
51 my @construct_array = @$constructs;
53 my $stock_type_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'vector_construct', 'stock_type')->cvterm_id();
55 my $create_db = $schema->resultset("General::Db")->find_or_create({
56 name => 'ETHZ_CASS',
57 description => 'Internal ETHZ CASS DB',
58 urlprefix => '',
59 url => 'https://cass.pb.ethz.ch'
60 });
62 foreach (@construct_array) {
63 #print STDERR $_->{construct};
64 #print STDERR $_->{construct_id};
65 #print STDERR $_->{level};
67 my $create_stock = $schema->resultset("Stock::Stock")->find_or_create({
68 uniquename => $_->{construct},
69 name => $_->{construct},
70 type_id => $stock_type_id,
71 });
73 my $create_dbxref = $schema->resultset("General::Dbxref")->find_or_create({
74 db_id => $create_db->db_id(),
75 accession => $_->{construct_id},
76 version => 'vector_construct',
77 description => 'ETHZ_CASS vector_construct id'
78 });
80 my $create_stock_dbxref = $schema->resultset("Stock::StockDbxref")->find_or_create({
81 stock_id => $create_stock->stock_id(),
82 dbxref_id => $create_dbxref->dbxref_id()
83 });
86 #print STDERR Dumper $constructs;
87 #print STDERR $status;
89 $c->stash->{rest} = {response=>$status};
92 sub create_vector_construct: Path('/ajax/create_vector_construct') Args(0) ActionClass('REST') { }
94 sub create_vector_construct_POST {
95 my $self = shift;
96 my $c = shift;
97 my $status = '';
98 my $vector_list;
99 my $organism_list;
100 my $user_id = $c->user ? $c->user->get_object()->get_sp_person_id():undef;
102 if (!$user_id){
103 $status = sprintf('You must be logged in to add a vector!');
104 $c->stash->{rest} = {error=>$status};
105 return;
108 my $schema = $c->dbic_schema("Bio::Chado::Schema",'sgn_chado', $user_id);
109 my $dbh = $schema->storage()->dbh();
110 my $person = CXGN::People::Person->new($dbh, $user_id);
111 my $user_name = $person->get_username;
113 my $data = decode_json( encode("utf8", $c->req->param('data')));
115 foreach (@$data){
116 my $vector = $_->{uniqueName} || undef;
117 my $organism = $_->{species_name} || undef;
118 push @$vector_list, $vector;
119 push @$organism_list, $organism;
122 #validate accessions/vector
123 my $validator = CXGN::List::Validate->new();
124 my @absent_accessions = @{$validator->validate($schema, 'accessions', $vector_list)->{'missing'}};
125 my %accessions_missing_hash = map { $_ => 1 } @absent_accessions;
126 my $existing_vectors = '';
128 my $validator2 = CXGN::List::Validate->new();
129 my @absent_vectors = @{$validator2->validate($schema, 'vector_constructs', $vector_list)->{'missing'}};
130 my %vectors_missing_hash = map { $_ => 1 } @absent_vectors;
132 foreach (@$vector_list){
133 if (!exists($accessions_missing_hash{$_})){
134 $existing_vectors = $existing_vectors . $_ ."," ;
136 if (!exists($vectors_missing_hash{$_})){
137 $existing_vectors = $existing_vectors . $_ ."," ;
141 if (length($existing_vectors) >0){
142 $status = sprintf('Existing vectors or accessions in the database: %s', $existing_vectors);
143 $c->stash->{rest} = {error=>$status};
144 return;
147 #validate organism
148 my $organism_search = CXGN::BreedersToolbox::OrganismFuzzySearch->new({schema => $schema});
149 my $organism_result = $organism_search->get_matches($organism_list, '1');
151 my @allowed_organisms;
152 my $missing_organisms = '';
153 my $found = $organism_result->{found};
155 foreach (@$found){
156 push @allowed_organisms, $_->{unique_name};
158 my %allowed_organisms = map {$_=>1} @allowed_organisms;
160 foreach (@$organism_list){
161 if (!exists($allowed_organisms{$_})){
162 $missing_organisms = $missing_organisms . $_ . ",";
165 if (length($missing_organisms) >0){
166 $status = sprintf('Organisms were not found on the database: %s', $missing_organisms);
167 $c->stash->{rest} = {error=>$status};
168 return;
171 my $type_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'vector_construct', 'stock_type')->cvterm_id();
173 my @added_stocks;
174 my @added_fullinfo_stocks;
175 my $coderef_bcs = sub {
176 foreach my $params (@$data){
177 my $species = $params->{species_name} || undef;
178 my $uniquename = $params->{uniqueName} || undef;
179 my $strain = $params->{Strain} || undef;
180 my $backbone = $params->{Backbone} || undef;
181 my $cloning_organism = $params->{CloningOrganism} || undef;
182 my $inherent_marker = $params->{InherentMarker} || undef;
183 my $selection_marker = $params->{SelectionMarker} || undef;
184 my $cassette_name = $params->{CassetteName} || undef;
185 my $vector_type = $params->{VectorType} || undef;
186 my $gene = $params->{Gene} || undef;
187 my $promotors = $params->{Promotors} || undef;
188 my $terminators = $params->{Terminators} || undef;
189 my $plant_antibiotic_resistant_marker = $params->{PlantAntibioticResistantMarker} || undef;
190 my $bacterial_resistant_marker = $params->{BacterialResistantMarker} || undef;
192 if (exists($allowed_organisms{$species})){
193 my $stock = CXGN::Stock::Vector->new({
194 schema=>$schema,
195 check_name_exists=>0,
196 type=>'vector_construct',
197 type_id=>$type_id,
198 sp_person_id => $user_id,
199 user_name => $user_name,
200 species=>$species,
201 name=>$uniquename,
202 uniquename=>$uniquename,
203 Strain=>$strain,
204 Backbone=>$backbone,
205 CloningOrganism=>$cloning_organism,
206 InherentMarker=>$inherent_marker,
207 SelectionMarker=>$selection_marker,
208 CassetteName=>$cassette_name,
209 VectorType=>$vector_type,
210 Gene=>$gene,
211 Promotors=>$promotors,
212 Terminators=>$terminators,
213 PlantAntibioticResistantMarker=>$plant_antibiotic_resistant_marker,
214 BacterialResistantMarker=>$bacterial_resistant_marker
216 my $added_stock_id = $stock->store();
217 push @added_stocks, $added_stock_id;
218 push @added_fullinfo_stocks, [$added_stock_id, $uniquename];
224 #save data
225 my $transaction_error;
227 try {
228 $schema->txn_do($coderef_bcs);
230 catch {
231 $transaction_error = $_;
234 if ($transaction_error){
235 $status = sprintf('There was an error storing vector %s', $transaction_error);
238 if (scalar(@added_stocks) > 0){
239 my $dbh = $c->dbc->dbh();
240 my $bs = CXGN::BreederSearch->new( { dbh=>$dbh, dbname=>$c->config->{dbname}, } );
241 my $refresh = $bs->refresh_matviews($c->config->{dbhost}, $c->config->{dbname}, $c->config->{dbuser}, $c->config->{dbpass}, 'stockprop', 'concurrent', $c->config->{basepath});
244 $c->stash->{rest} = {
245 response=>$status,
246 success => "1",
247 added => \@added_fullinfo_stocks
251 sub verify_vectors_file : Path('/ajax/vectors/verify_vectors_file') : ActionClass('REST') { }
252 sub verify_vectors_file_POST : Args(0) {
253 my ($self, $c) = @_;
255 my $user_id;
256 my $user_name;
257 my $user_role;
258 my $session_id = $c->req->param("sgn_session_id");
260 if ($session_id){
261 my $dbh = $c->dbc->dbh;
262 my @user_info = CXGN::Login->new($dbh)->query_from_cookie($session_id);
263 if (!$user_info[0]){
264 $c->stash->{rest} = {error=>'You must be logged in to upload this vector info!'};
265 $c->detach();
267 $user_id = $user_info[0];
268 $user_role = $user_info[1];
269 my $p = CXGN::People::Person->new($dbh, $user_id);
270 $user_name = $p->get_username;
271 } else {
272 if (!$c->user){
273 $c->stash->{rest} = {error=>'You must be logged in to upload this vector info!'};
274 $c->detach();
276 $user_id = $c->user()->get_object()->get_sp_person_id();
277 $user_name = $c->user()->get_object()->get_username();
278 $user_role = $c->user->get_object->get_user_type();
281 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado', $user_id);
282 my $upload = $c->req->upload('new_vectors_upload_file');
283 my $do_fuzzy_search = $user_role eq 'curator' && !$c->req->param('fuzzy_check_upload_vectors') ? 0 : 1;
284 my $autogenerate_uniquename = !$c->req->param('autogenerate_uniquename') ? 0 : 1;
286 if ($user_role ne 'curator' && !$do_fuzzy_search) {
287 $c->stash->{rest} = {error=>'Only a curator can add vectors without using the fuzzy search!'};
288 $c->detach();
291 # These roles are required by CXGN::UploadFile
292 if ($user_role ne 'curator' && $user_role ne 'submitter' && $user_role ne 'sequencer' ) {
293 $c->stash->{rest} = {error=>'Only a curator, submitter or sequencer can upload a file'};
294 $c->detach();
297 my $subdirectory = "vectors_spreadsheet_upload";
298 my $upload_original_name = $upload->filename();
299 my $upload_tempfile = $upload->tempname;
300 my $time = DateTime->now();
301 my $timestamp = $time->ymd()."_".$time->hms();
303 ## Store uploaded temporary file in archive
304 my $uploader = CXGN::UploadFile->new({
305 tempfile => $upload_tempfile,
306 subdirectory => $subdirectory,
307 archive_path => $c->config->{archive_path},
308 archive_filename => $upload_original_name,
309 timestamp => $timestamp,
310 user_id => $user_id,
311 user_role => $user_role
313 my $archived_filename_with_path = $uploader->archive();
314 my $md5 = $uploader->get_md5($archived_filename_with_path);
315 if (!$archived_filename_with_path) {
316 $c->stash->{rest} = {error => "Could not save file $upload_original_name in archive",};
317 $c->detach();
319 unlink $upload_tempfile;
321 my @editable_vector_props = split ',', $c->config->{editable_vector_props};
322 my $parser = CXGN::Stock::Vector::ParseUpload->new(chado_schema => $schema, filename => $archived_filename_with_path, editable_stock_props=>\@editable_vector_props, do_fuzzy_search=>$do_fuzzy_search, autogenerate_uniquename=>$autogenerate_uniquename);
323 $parser->load_plugin('VectorsXLS');
324 my $parsed_data = $parser->parse();
326 if (!$parsed_data) {
327 my $return_error = '';
328 my $parse_errors;
329 if (!$parser->has_parse_errors() ){
330 $c->stash->{rest} = {error_string => "Could not get parsing errors"};
331 $c->detach();
332 } else {
333 $parse_errors = $parser->get_parse_errors();
335 foreach my $error_string (@{$parse_errors->{'error_messages'}}){
336 $return_error .= $error_string."<br>";
339 $c->stash->{rest} = {error_string => $return_error, missing_species => $parse_errors->{'missing_species'}};
340 $c->detach();
343 my $full_data = $parsed_data->{parsed_data};
344 my @vector_names;
345 my %full_vectors;
346 while (my ($k,$val) = each %$full_data){
347 push @vector_names, $val->{germplasmName};
348 $full_vectors{$val->{germplasmName}} = $val;
351 my $new_list_id = CXGN::List::create_list($c->dbc->dbh, "VectorsIn".$upload_original_name.$timestamp, 'Autocreated when upload vectors from file '.$upload_original_name.$timestamp, $user_id);
352 my $list = CXGN::List->new( { dbh => $c->dbc->dbh, list_id => $new_list_id } );
354 $list->add_bulk(\@vector_names);
355 $list->type('vector_construct');
357 my %return = (
358 success => "1",
359 list_id => $new_list_id,
360 full_data => \%full_vectors,
361 absent => $parsed_data->{absent_vectors},
362 fuzzy => $parsed_data->{fuzzy_vectors},
363 found => $parsed_data->{found_vectors},
364 absent_organisms => $parsed_data->{absent_organisms},
365 fuzzy_organisms => $parsed_data->{fuzzy_organisms},
366 found_organisms => $parsed_data->{found_organisms}
369 if ($parsed_data->{error_string}){
370 $return{error_string} = $parsed_data->{error_string};
374 $c->stash->{rest} = \%return;
378 sub verify_vectors_fuzzy_options : Path('/ajax/vector_list/fuzzy_options') : ActionClass('REST') { }
380 sub verify_vectors_fuzzy_options_POST : Args(0) {
381 my ($self, $c) = @_;
382 my $sp_person_id = $c->user() ? $c->user->get_object()->get_sp_person_id() : undef;
383 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado', $sp_person_id);
384 my $vector_list_id = $c->req->param('vector_list_id');
385 my $fuzzy_option_hash = decode_json( encode("utf8", $c->req->param('fuzzy_option_data')));
386 my $names_to_add = _parse_list_from_json($c, $c->req->param('names_to_add'));
388 my $list = CXGN::List->new( { dbh => $c->dbc()->dbh(), list_id => $vector_list_id } );
390 my %names_to_add = map {$_ => 1} @$names_to_add;
391 foreach my $form_name (keys %$fuzzy_option_hash){
392 my $item_name = $fuzzy_option_hash->{$form_name}->{'fuzzy_name'};
393 my $select_name = $fuzzy_option_hash->{$form_name}->{'fuzzy_select'};
394 my $fuzzy_option = $fuzzy_option_hash->{$form_name}->{'fuzzy_option'};
395 if ($fuzzy_option eq 'replace'){
396 $list->replace_by_name($item_name, $select_name);
397 delete $names_to_add{$item_name};
398 } elsif ($fuzzy_option eq 'keep'){
399 $names_to_add{$item_name} = 1;
400 } elsif ($fuzzy_option eq 'remove'){
401 $list->remove_by_name($item_name);
402 delete $names_to_add{$item_name};
403 } elsif ($fuzzy_option eq 'synonymize'){
404 my $stock_id = $schema->resultset('Stock::Stock')->find({uniquename=>$select_name})->stock_id();
405 my $stock = CXGN::Chado::Stock->new($schema, $stock_id);
406 $stock->add_synonym($item_name);
408 delete $names_to_add{$item_name};
412 my @names_to_add = sort keys %names_to_add;
413 my $rest = {
414 success => "1",
415 names_to_add => \@names_to_add
417 $c->stash->{rest} = $rest;
421 sub get_new_vector_uniquename : Path('/ajax/get_new_vector_uniquename') : ActionClass('REST') { }
423 sub get_new_vector_uniquename_GET : Args(0) {
424 my ($self, $c) = @_;
425 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
427 my $stock_type_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'vector_construct', 'stock_type')->cvterm_id();
429 my $stocks = $schema->resultset("Stock::Stock")->search({
430 type_id => $stock_type_id,
433 my $id;
434 my $max=0;
435 while (my $r = $stocks->next()) {
436 $id = $r->uniquename;
437 if ($id =~ m/T[0-9]+/){
438 $id =~ s/T//;
439 if($max < $id){
440 $max = $id;
444 $max += 1;
445 #Vector construct has letter T before autogenerated number.
446 $c->stash->{rest} = [ "T". $max];
450 sub _parse_list_from_json {
451 my $c = shift;
452 my $list_json = shift;
453 my $json = JSON::XS->new();
455 if ($list_json) {
456 debug($c, "LIST_JSON is utf8? ".utf8::is_utf8($list_json)." valid utf8? ".utf8::valid($list_json)."\n");
457 print STDERR "JSON NOW: $list_json\n";
458 my $decoded_list = $json->decode($list_json);
460 my @array_of_list_items = ();
461 if (ref($decoded_list) eq "ARRAY" ) {
462 @array_of_list_items = @{$decoded_list};
464 else {
465 debug($c, "Dont know what to do " );
468 return \@array_of_list_items;
470 else {
471 return;
475 sub debug {
476 my $c = shift;
477 my $message = shift;