4 SGN::Controller::AJAX::Cross - a REST controller class to provide the
5 functions for adding crosses
9 Add a new cross or upload a file containing crosses to add
13 Jeremy Edwards <jde22@cornell.edu>
14 Lukas Mueller <lam87@cornell.edu>
18 package SGN
::Controller
::AJAX
::Cross
;
24 use File
::Basename qw
| basename dirname
|;
27 use File
::Spec
::Functions
;
29 use List
::MoreUtils qw
/any /;
30 use Bio
::GeneticRelationships
::Pedigree
;
31 use Bio
::GeneticRelationships
::Individual
;
33 use CXGN
::Pedigree
::AddCrosses
;
34 use CXGN
::Pedigree
::AddProgeny
;
35 use CXGN
::Pedigree
::AddCrossInfo
;
36 use CXGN
::Pedigree
::ParseUpload
;
37 use CXGN
::Trial
::Folder
;
39 use File
::Path
qw(make_path);
40 use File
::Spec
::Functions qw
/ catfile catdir/;
43 BEGIN { extends
'Catalyst::Controller::REST' }
46 default => 'application/json',
48 map => { 'application/json' => 'JSON', 'text/html' => 'JSON' },
51 sub upload_cross_file
: Path
('/ajax/cross/upload_crosses_file') : ActionClass
('REST') { }
53 sub upload_cross_file_POST
: Args
(0) {
55 my $chado_schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
56 my $metadata_schema = $c->dbic_schema("CXGN::Metadata::Schema");
57 my $phenome_schema = $c->dbic_schema("CXGN::Phenome::Schema");
58 my $dbh = $c->dbc->dbh;
59 my $program = $c->req->param('cross_upload_breeding_program');
60 my $location = $c->req->param('cross_upload_location');
61 my $upload = $c->req->upload('crosses_upload_file');
62 my $prefix = $c->req->param('upload_prefix');
63 my $suffix = $c->req->param('upload_suffix');
64 my $uploader = CXGN
::UploadFile
->new();
67 my $upload_original_name = $upload->filename();
68 my $upload_tempfile = $upload->tempname;
69 my $subdirectory = "cross_upload";
70 my $archived_filename_with_path;
77 my $time = DateTime
->now();
78 my $timestamp = $time->ymd()."_".$time->hms();
81 my $upload_file_type = "crosses excel";#get from form when more options are added
84 print STDERR
"User not logged in... not adding a crosses.\n";
85 $c->stash->{rest
} = {error
=> "You need to be logged in to add a cross." };
88 $user_id = $c->user()->get_object()->get_sp_person_id();
90 $owner_name = $c->user()->get_object()->get_username();
92 ## Store uploaded temporary file in archive
93 $archived_filename_with_path = $uploader->archive($c, $subdirectory, $upload_tempfile, $upload_original_name, $timestamp);
94 $md5 = $uploader->get_md5($archived_filename_with_path);
95 if (!$archived_filename_with_path) {
96 $c->stash->{rest
} = {error
=> "Could not save file $upload_original_name in archive",};
99 unlink $upload_tempfile;
101 $upload_metadata{'archived_file'} = $archived_filename_with_path;
102 $upload_metadata{'archived_file_type'}="cross upload file";
103 $upload_metadata{'user_id'}=$user_id;
104 $upload_metadata{'date'}="$timestamp";
106 #parse uploaded file with appropriate plugin
107 $parser = CXGN
::Pedigree
::ParseUpload
->new(chado_schema
=> $chado_schema, filename
=> $archived_filename_with_path);
108 $parser->load_plugin('CrossesExcelFormat');
109 $parsed_data = $parser->parse();
112 my $return_error = '';
114 if (! $parser->has_parse_errors() ){
115 $return_error = "Could not get parsing errors";
116 $c->stash->{rest
} = {error_string
=> $return_error,};
120 $parse_errors = $parser->get_parse_errors();
121 foreach my $error_string (@
{$parse_errors}){
122 $return_error=$return_error.$error_string."<br>";
126 $c->stash->{rest
} = {error_string
=> $return_error,};
130 my $cross_add = CXGN
::Pedigree
::AddCrosses
132 chado_schema
=> $chado_schema,
133 phenome_schema
=> $phenome_schema,
134 metadata_schema
=> $metadata_schema,
136 location
=> $location,
138 crosses
=> $parsed_data->{crosses
},
139 owner_name
=> $owner_name,
142 #validate the crosses
143 if (!$cross_add->validate_crosses()){
144 $c->stash->{rest
} = {error_string
=> "Error validating crosses",};
149 if (!$cross_add->add_crosses()){
150 $c->stash->{rest
} = {error_string
=> "Error adding crosses",};
155 foreach my $cross_name_key (keys %{$parsed_data->{progeny
}}){
156 my $progeny_number = $parsed_data->{progeny
}->{$cross_name_key};
157 my $progeny_increment = 1;
160 #create array of progeny names to add for this cross
161 while ($progeny_increment < $progeny_number + 1) {
162 $progeny_increment = sprintf "%03d", $progeny_increment;
163 my $stock_name = $cross_name_key.$prefix.$progeny_increment.$suffix;
164 push @progeny_names, $stock_name;
165 $progeny_increment++;
168 #add array of progeny to the cross
169 my $progeny_add = CXGN
::Pedigree
::AddProgeny
171 chado_schema
=> $chado_schema,
172 phenome_schema
=> $phenome_schema,
174 cross_name
=> $cross_name_key,
175 progeny_names
=> \
@progeny_names,
176 owner_name
=> $owner_name,
178 if (!$progeny_add->add_progeny()){
179 $c->stash->{rest
} = {error_string
=> "Error adding progeny",};
180 #should delete crosses and other progeny if add progeny fails?
185 #add the number of flowers to crosses
186 foreach my $cross_name_key (keys %{$parsed_data->{flowers
}}) {
187 my $number_of_flowers = $parsed_data->{flowers
}->{$cross_name_key};
188 my $cross_add_info = CXGN
::Pedigree
::AddCrossInfo
->new({ chado_schema
=> $chado_schema, cross_name
=> $cross_name_key} );
189 $cross_add_info->set_number_of_flowers($number_of_flowers);
190 $cross_add_info->add_info();
193 #add the number of seeds to crosses
194 foreach my $cross_name_key (keys %{$parsed_data->{seeds
}}) {
195 my $number_of_seeds = $parsed_data->{seeds
}->{$cross_name_key};
196 my $cross_add_info = CXGN
::Pedigree
::AddCrossInfo
->new({ chado_schema
=> $chado_schema, cross_name
=> $cross_name_key} );
197 $cross_add_info->set_number_of_seeds($number_of_seeds);
198 $cross_add_info->add_info();
201 $c->stash->{rest
} = {success
=> "1",};
205 sub add_cross
: Local
: ActionClass
('REST') { }
207 sub add_cross_POST
:Args
(0) {
209 my $chado_schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
210 my $metadata_schema = $c->dbic_schema("CXGN::Metadata::Schema");
211 my $phenome_schema = $c->dbic_schema("CXGN::Phenome::Schema");
212 my $dbh = $c->dbc->dbh;
213 my $cross_name = $c->req->param('cross_name');
214 my $cross_type = $c->req->param('cross_type');
215 my $program = $c->req->param('program');
216 my $location = $c->req->param('location');
217 my $maternal = $c->req->param('maternal_parent');
218 my $paternal = $c->req->param('paternal_parent');
219 my $prefix = $c->req->param('prefix');
220 my $suffix = $c->req->param('suffix');
221 my $progeny_number = $c->req->param('progeny_number');
222 my $number_of_flowers = $c->req->param('number_of_flowers');
223 my $number_of_seeds = $c->req->param('number_of_seeds');
224 my $visible_to_role = $c->req->param('visible_to_role');
225 my $parent_folder_id = $c->req->param('folder_id');
229 my @array_of_pedigree_objects;
230 my $progeny_increment = 1;
231 my $paternal_parent_not_required;
232 my $number_of_flowers_cvterm;
233 my $number_of_seeds_cvterm;
236 if ($cross_type eq "open" || $cross_type eq "bulk_open") {
237 $paternal_parent_not_required = 1;
240 print STDERR
"Adding Cross... Maternal: $maternal Paternal: $paternal Cross Type: $cross_type\n";
243 print STDERR
"User not logged in... not adding a cross.\n";
244 $c->stash->{rest
} = {error
=> "You need to be logged in to add a cross." };
248 $owner_name = $c->user()->get_object()->get_username();
250 if (!any
{ $_ eq "curator" || $_ eq "submitter" } ($c->user()->roles) ) {
251 print STDERR
"User does not have sufficient privileges.\n";
252 $c->stash->{rest
} = {error
=> "you have insufficient privileges to add a cross." };
256 #check that progeny number is an integer less than maximum allowed
257 my $maximum_progeny_number = 999; #higher numbers break cross name convention
258 if ($progeny_number) {
259 if ((! $progeny_number =~ m/^\d+$/) or ($progeny_number > $maximum_progeny_number) or ($progeny_number < 1)) {
260 $c->stash->{rest
} = {error
=> "progeny number exceeds the maximum of $maximum_progeny_number or is invalid." };
265 #check that maternal name is not blank
266 if ($maternal eq "") {
267 $c->stash->{rest
} = {error
=> "maternal parent name cannot be blank." };
271 #if required, check that paternal parent name is not blank;
272 if ($paternal eq "" && !$paternal_parent_not_required) {
273 $c->stash->{rest
} = {error
=> "paternal parent name cannot be blank." };
277 #check that parents exist in the database
278 if (! $chado_schema->resultset("Stock::Stock")->find({name
=>$maternal,})){
279 $c->stash->{rest
} = {error
=> "maternal parent does not exist." };
283 if (!$paternal_parent_not_required) {
284 if (! $chado_schema->resultset("Stock::Stock")->find({name
=>$paternal,})){
285 $c->stash->{rest
} = {error
=> "paternal parent does not exist." };
290 #check that cross name does not already exist
291 if ($chado_schema->resultset("Stock::Stock")->find({name
=>$cross_name})){
292 $c->stash->{rest
} = {error
=> "cross name already exists." };
296 #check that progeny do not already exist
297 if ($chado_schema->resultset("Stock::Stock")->find({name
=>$cross_name.$prefix.'001'.$suffix,})){
298 $c->stash->{rest
} = {error
=> "progeny already exist." };
302 #objects to store cross information
303 my $cross_to_add = Bio
::GeneticRelationships
::Pedigree
->new(name
=> $cross_name, cross_type
=> $cross_type);
304 my $female_individual = Bio
::GeneticRelationships
::Individual
->new(name
=> $maternal);
305 $cross_to_add->set_female_parent($female_individual);
307 if (!$paternal_parent_not_required){
308 my $male_individual = Bio
::GeneticRelationships
::Individual
->new(name
=> $paternal);
309 $cross_to_add->set_male_parent($male_individual);
313 $cross_to_add->set_cross_type($cross_type);
314 $cross_to_add->set_name($cross_name);
317 #create array of pedigree objects to add, in this case just one pedigree
318 @array_of_pedigree_objects = ($cross_to_add);
319 $cross_add = CXGN
::Pedigree
::AddCrosses
321 chado_schema
=> $chado_schema,
322 phenome_schema
=> $phenome_schema,
323 #metadata_schema => $metadata_schema,
325 location
=> $location,
327 crosses
=> \
@array_of_pedigree_objects,
328 owner_name
=> $owner_name,
329 parent_folder_id
=> $parent_folder_id
334 $cross_add->add_crosses();
337 $c->stash->{rest
} = { error
=> "Error creating the cross: $@" };
342 #create progeny if specified
343 if ($progeny_number) {
345 #create array of progeny names to add for this cross
346 while ($progeny_increment < $progeny_number + 1) {
347 $progeny_increment = sprintf "%03d", $progeny_increment;
348 my $stock_name = $cross_name.$prefix.$progeny_increment.$suffix;
349 push @progeny_names, $stock_name;
350 $progeny_increment++;
353 #add array of progeny to the cross
354 $progeny_add = CXGN
::Pedigree
::AddProgeny
356 chado_schema
=> $chado_schema,
357 phenome_schema
=> $phenome_schema,
359 cross_name
=> $cross_name,
360 progeny_names
=> \
@progeny_names,
361 owner_name
=> $owner_name,
363 $progeny_add->add_progeny();
367 #add number of flowers as an experimentprop if specified
368 if ($number_of_flowers) {
369 my $cross_add_info = CXGN
::Pedigree
::AddCrossInfo
->new({ chado_schema
=> $chado_schema, cross_name
=> $cross_name} );
370 $cross_add_info->set_number_of_flowers($number_of_flowers);
371 $cross_add_info->add_info();
374 #add number of seeds as an experimentprop if specified
375 if ($number_of_seeds) {
376 my $cross_add_info = CXGN
::Pedigree
::AddCrossInfo
->new({ chado_schema
=> $chado_schema, cross_name
=> $cross_name} );
377 $cross_add_info->set_number_of_seeds($number_of_seeds);
378 $cross_add_info->add_info();
383 $c->stash->{rest
} = { error
=> "An error occurred: $@"};
387 $c->stash->{rest
} = { error
=> '', };
390 sub get_cross_relationships
:Path
('/cross/ajax/relationships') :Args
(1) {
393 my $cross_id = shift;
395 my $schema = $c->dbic_schema("Bio::Chado::Schema");
397 my $cross = $schema->resultset("Stock::Stock")->find( { stock_id
=> $cross_id });
399 if ($cross && $cross->type()->name() ne "cross") {
400 $c->stash->{rest
} = { error
=> 'This entry is not of type cross and cannot be displayed using this page.' };
404 my $cross_obj = CXGN
::Cross
->new({bcs_schema
=>$schema, cross_stock_id
=>$cross_id});
405 my ($maternal_parent, $paternal_parent, $progeny) = $cross_obj->get_cross_relationships();
407 $c->stash->{rest
} = {
408 maternal_parent
=> $maternal_parent,
409 paternal_parent
=> $paternal_parent,
415 sub get_cross_properties
:Path
('/cross/ajax/properties') Args
(1) {
418 my $cross_id = shift;
420 my $schema = $c->dbic_schema("Bio::Chado::Schema");
422 my $rs = $schema->resultset("NaturalDiversity::NdExperimentprop")->search( { 'nd_experiment_stocks.stock_id' => $cross_id }, { join => { 'nd_experiment' => 'nd_experiment_stocks' }});
426 print STDERR
"PROPS LEN ".$rs->count()."\n";
428 while (my $prop = $rs->next()) {
429 push @
{$props->{$prop->type->name()}}, [ $prop->get_column('value'), $prop->get_column('nd_experimentprop_id') ];
432 print STDERR Dumper
($props);
433 $c->stash->{rest
} = { props
=> $props };
438 sub save_property_check
:Path
('/cross/property/check') Args
(1) {
441 my $cross_id = shift;
443 my $type = $c->req->param("type");
444 my $value = $c->req->param("value");
447 my $schema = $c->dbic_schema("Bio::Chado::Schema");
448 my $type_row = $schema->resultset('Cv::Cvterm')->find( { name
=> $type } );
451 $c->stash->{rest
} = { error
=> "The type '$type' does not exist in the database" };
455 my $type_id = $type_row->cvterm_id();
457 my %suggested_values = (
458 cross_type
=> { 'biparental'=>1, 'self'=>1, 'open pollinated'=>1, 'bulk'=>1, 'bulk selfed'=>1, 'bulk and open pollinated'=>1, 'doubled haplotype'=>1 },
459 number_of_flowers
=> '\d+',
460 number_of_seeds
=> '\d+',
461 date
=> '\d{4}\\/\d{2}\\/\d{2}',
467 my %example_values = (
468 date
=> '2014/03/29',
470 number_of_flowers
=> 23,
471 number_of_seeds
=> 42,
472 operator
=> 'Alfonso',
473 cross_name
=> 'nextgen_cross',
476 if (ref($suggested_values{$type})) {
477 if (!exists($suggested_values{$type}->{$value})) { # don't make this case insensitive!
478 $c->stash->{rest
} = { message
=> 'The provided value is not in the suggested list of terms. This could affect downstream data processing.' };
483 if ($value !~ m/^$suggested_values{$type}$/) {
484 $c->stash->{rest
} = { error
=> 'The provided value is not of the correct type. Format example: "'.$example_values{$type}.'"' };
488 $c->stash->{rest
} = { success
=> 1 };
491 sub cross_property_save
:Path
('/cross/property/save') Args
(1) {
496 $c->stash->{rest
} = { error
=> "You must be logged in add properties." };
499 if (!($c->user()->has_role('submitter') or $c->user()->has_role('curator'))) {
500 $c->stash->{rest
} = { error
=> "You do not have sufficient privileges to add properties." };
504 my $cross_id = $c->req->param("cross_id");
505 my $type = $c->req->param("type");
506 my $value = $c->req->param("value");
508 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
510 my $exp_id = $schema->resultset("NaturalDiversity::NdExperiment")->search( { 'nd_experiment_stocks.stock_id' => $cross_id }, { join => 'nd_experiment_stocks' })->first()->get_column('nd_experiment_id');
513 my $type_row = $schema->resultset("Cv::Cvterm")->find( { 'me.name' => $type, 'cv.name' => 'nd_experiment_property' }, { join => { 'cv'}});
515 $type_id = $type_row->cvterm_id();
518 $c->stash->{rest
} = { error
=> "The type $type does not exist in the database." };
522 my $rs = $schema->resultset("NaturalDiversity::NdExperimentprop")->search( { 'nd_experiment_stocks.stock_id' => $cross_id, 'me.type_id' => $type_id }, { join => { 'nd_experiment' => { 'nd_experiment_stocks' }}});
524 my $row = $rs->first();
526 $row = $schema->resultset("NaturalDiversity::NdExperimentprop")->create( { 'nd_experiment_stocks.stock_id' => $cross_id, 'me.type_id' => $type_id, 'me.value'=>$value, 'me.nd_experiment_id' => $exp_id }, { join => {'nd_experiment' => {'nd_experiment_stocks' }}});
531 $row->set_column( 'value' => $value );
535 $c->stash->{rest
} = { success
=> 1 };
539 sub add_more_progeny
:Path
('/cross/progeny/add') Args
(1) {
542 my $cross_id = shift;
545 $c->stash->{rest
} = { error
=> "You must be logged in add progeny." };
548 if (!($c->user()->has_role('submitter') or $c->user()->has_role('curator'))) {
549 $c->stash->{rest
} = { error
=> "You do not have sufficient privileges to add progeny." };
553 my $basename = $c->req->param("basename");
554 my $start_number = $c->req->param("start_number");
555 my $progeny_count = $c->req->param("progeny_count");
556 my $cross_name = $c->req->param("cross_name");
558 my @progeny_names = ();
559 foreach my $n (1..$progeny_count) {
560 push @progeny_names, $basename. (sprintf "%03d", $n + $start_number -1);
563 print STDERR Dumper
(\
@progeny_names);
565 my $chado_schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
566 my $metadata_schema = $c->dbic_schema("CXGN::Metadata::Schema");
567 my $phenome_schema = $c->dbic_schema("CXGN::Phenome::Schema");
568 my $dbh = $c->dbc->dbh;
570 my $owner_name = $c->user()->get_object()->get_username();
572 my $progeny_add = CXGN
::Pedigree
::AddProgeny
574 chado_schema
=> $chado_schema,
575 phenome_schema
=> $phenome_schema,
577 cross_name
=> $cross_name,
578 progeny_names
=> \
@progeny_names,
579 owner_name
=> $owner_name,
581 if (!$progeny_add->add_progeny()){
582 $c->stash->{rest
} = {error_string
=> "Error adding progeny. Please change the input parameters and try again.",};
583 #should delete crosses and other progeny if add progeny fails?
587 $c->stash->{rest
} = { success
=> 1};
591 sub get_crosses_with_folders
: Path
('/ajax/breeders/get_crosses_with_folders') Args
(0) {
595 my $schema = $c->dbic_schema("Bio::Chado::Schema");
596 my $p = CXGN
::BreedersToolbox
::Projects
->new( { schema
=> $schema } );
598 my $projects = $p->get_breeding_programs();
601 foreach my $project (@
$projects) {
602 my $folder = CXGN
::Trial
::Folder
->new( { bcs_schema
=> $schema, folder_id
=> $project->[0] });
603 $html .= $folder->get_jstree_html('breeding_program', 'cross');
606 my $dir = catdir
($c->site_cluster_shared_dir, "folder");
607 eval { make_path
($dir) };
609 print "Couldn't create $dir: $@";
611 my $filename = $dir."/entire_crosses_jstree_html.txt";
614 open $OUTFILE, '>', $filename or die "Error opening $filename: $!";
615 print { $OUTFILE } $html or croak
"Cannot write to $filename: $!";
616 close $OUTFILE or croak
"Cannot close $filename: $!";
618 $c->stash->{rest
} = { status
=> 1 };
621 sub get_crosses_with_folders_cached
: Path
('/ajax/breeders/get_crosses_with_folders_cached') Args
(0) {
625 my $dir = catdir
($c->site_cluster_shared_dir, "folder");
626 my $filename = $dir."/entire_crosses_jstree_html.txt";
628 open(my $fh, '<', $filename) or die "cannot open file $filename";
636 $c->stash->{rest
} = { html
=> $html };