diff --git a/js/source/legacy/CXGN/Trial.js b/js/source/legacy/CXGN/Trial.js
index 2980c91a4b..db3b53e14e 100644
--- a/js/source/legacy/CXGN/Trial.js
+++ b/js/source/legacy/CXGN/Trial.js
@@ -221,10 +221,10 @@ function delete_field_map() {
jQuery('#working_modal').modal("hide");
if (response.error) {
- alert("Error Deleting Field Map: "+response.error);
+ alert("Error deleting field map: "+response.error);
} else {
- //alert("Field map deletion Successful...");
- jQuery('#delete_field_map_dialog_message').dialog("open");
+ alert("Field map deletion successful.");
+ location.reload();
}
},
error: function () {
@@ -493,7 +493,11 @@ delete_field_map();
jQuery('#delete_field_map_hm_link').click(function () {
- jQuery('#delete_field_map_dialog').dialog("open");
+ if (confirm("Are you sure you want to delete the spatial layout of this trial?")) {
+ delete_field_map();
+ } else {
+ return;
+ }
});
jQuery("#delete_field_map_dialog_message").dialog({
@@ -635,34 +639,43 @@ jQuery(document).ready(function ($) {
$("#trial_coord_upload_spreadsheet_info_dialog" ).modal("show");
});
- $('#upload_trial_coordinates_form').iframePostForm({
- json: true,
- post: function () {
+ $('#upload_trial_coordinates_form').iframePostForm({
+ json: true,
+ post: function () {
var uploadedtrialcoordFile = $("#trial_coordinates_uploaded_file").val();
- $('#working_modal').modal("show");
+ $('#working_modal').modal("show");
if (uploadedtrialcoordFile === '') {
- $('#working_modal').modal("hide");
- alert("No file selected");
+ $('#working_modal').modal("hide");
+ alert("No file selected");
}
- },
- complete: function (response) {
- $('#working_modal').modal("hide");
- if (response.error_string) {
- $("#upload_trial_coord_error_display tbody").html('');
- $("#upload_trial_coord_error_display tbody").append(response.error_string);
- jQuery('#upload_trial_coord_error_display').modal('show');
+ },
+ complete: function (response) {
+ $('#working_modal').modal("hide");
- return;
+ // Check if response exists and is valid
+ if (!response || typeof response !== 'object') {
+ alert("Error: No response received from server. Please try again.");
+ return;
}
+
if (response.error) {
- alert(response.error);
- return;
+ $("#upload_trial_coord_error_display tbody").html('');
+ $("#upload_trial_coord_error_display tbody").append(response.error);
+ jQuery('#upload_trial_coord_error_display').modal('show');
+ return;
}
+
if (response.success) {
- $('#trial_coord_upload_success_dialog_message').modal("show");
- //alert("File uploaded successfully");
+ $('#trial_coord_upload_success_dialog_message').modal("show");
+ //alert("File uploaded successfully");
+ return;
}
- }
+
+ // Catch-all for unexpected responses
+ console.error("Unexpected server response:", response);
+ alert("An unexpected error occurred. The server response was not in the expected format. Please check the console for details or contact support.");
+
+ }
});
function upload_trial_coord_file() {
diff --git a/lib/CXGN/Job.pm b/lib/CXGN/Job.pm
index 04f0b0de1e..9c717f617c 100644
--- a/lib/CXGN/Job.pm
+++ b/lib/CXGN/Job.pm
@@ -217,7 +217,7 @@ The command submitted to be run.
=cut
-has 'cmd' => (isa => 'Str', is => 'rw');
+has 'cmd' => (isa => 'Maybe[Str]', is => 'rw');
=head2 logfile()
diff --git a/lib/CXGN/Trial/ParseUpload/Plugin/TrialSpatialLayoutGeneric.pm b/lib/CXGN/Trial/ParseUpload/Plugin/TrialSpatialLayoutGeneric.pm
new file mode 100644
index 0000000000..b8cedcbd7c
--- /dev/null
+++ b/lib/CXGN/Trial/ParseUpload/Plugin/TrialSpatialLayoutGeneric.pm
@@ -0,0 +1,180 @@
+package CXGN::Trial::ParseUpload::Plugin::TrialSpatialLayoutGeneric;
+
+use Moose::Role;
+use List::MoreUtils qw(uniq);
+use CXGN::File::Parse;
+use SGN::Model::Cvterm;
+use CXGN::List::Validate;
+use CXGN::Stock;
+use CXGN::Project;
+use Data::Dumper;
+
+my @REQUIRED_COLUMNS = qw|plot_name row_number col_number|;
+my @OPTIONAL_COLUMNS = qw||;
+# Any additional columns are unsupported and will return an error
+
+sub _validate_with_plugin {
+ my $self = shift;
+ my $filename = $self->get_filename();
+ my $schema = $self->get_chado_schema();
+ my $trial_id = $self->get_trial_id();
+
+ # List validator
+ my $validator = CXGN::List::Validate->new();
+
+ # Encountered Error and Warning Messages
+ my %errors;
+ my @error_messages;
+ my %warnings;
+ my @warning_messages;
+
+ # Read and parse the upload file
+ my $parser = CXGN::File::Parse->new(
+ file => $filename,
+ required_columns => \@REQUIRED_COLUMNS,
+ optional_columns => \@OPTIONAL_COLUMNS
+ );
+ my $parsed = $parser->parse();
+ my $parsed_errors = $parsed->{'errors'};
+ my $parsed_data = $parsed->{'data'};
+ my $parsed_values = $parsed->{'values'};
+ my $additional_columns = $parsed->{'additional_columns'};
+
+ # Return file parsing errors
+ if ( $parsed_errors && scalar(@$parsed_errors) > 0 ) {
+ $errors{'error_messages'} = $parsed_errors;
+ $self->_set_parse_errors(\%errors);
+ return;
+ }
+
+ # Unsupported column headers
+ if ( $additional_columns && scalar(@$additional_columns) > 0 ) {
+ $errors{'error_messages'} = [ 'The following column headers are not supported: ' . join(', ', @$additional_columns) ];
+ $self->_set_parse_errors(\%errors);
+ return;
+ }
+
+ # Maps to track row/col positions
+ my %seen_positions; # check that each row_number/col_number pair is used only once
+
+ foreach my $data (@$parsed_data) {
+ my $row = $data->{'_row'};
+ my $plot_name = $data->{'plot_name'};
+ my $row_number = $data->{'row_number'};
+ my $col_number = $data->{'col_number'};
+
+ # Row Number: must be a positive integer
+ if (!($row_number =~ /^\d+?$/)) {
+ push @error_messages, "Row $row: row_number $row_number must be a positive integer.";
+ }
+
+ # Col Number: must be a positive integer
+ if (!($col_number =~ /^\d+?$/)) {
+ push @error_messages, "Row $row: col_number $col_number must be a positive integer.";
+ }
+
+ # Track row/col positions to check for duplicates
+ my $position_key = "$row_number-$col_number";
+ if ( !exists $seen_positions{$position_key} ) {
+ $seen_positions{$position_key} = [$plot_name];
+ }
+ else {
+ push @{$seen_positions{$position_key}}, $plot_name;
+ }
+ }
+
+ # Plots must exist
+ my @plot_names = @{$parsed_values->{'plot_name'}};
+ my @missing_plots = @{$validator->validate($schema, 'plots', \@plot_names)->{'missing'}};
+
+ # Report missing plots
+ if (scalar(@missing_plots) > 0) {
+ push @error_messages, "Plot name(s) ".join(', ',@missing_plots)." do not exist in the database as plots.";
+ }
+
+ # Check that all plots belong to the same trial
+ my $trial = CXGN::Project->new({
+ bcs_schema => $schema,
+ trial_id => $trial_id
+ });
+
+ my %trial_plots = map {$_->[1] => 1} @{$trial->get_plots()};
+
+ my @mismatched_plots;
+
+ foreach my $plot (@plot_names) {
+ if (!defined($trial_plots{$plot})) {
+ push @mismatched_plots, $plot;
+ }
+ }
+
+ if (scalar(@mismatched_plots) > 0) {
+ push @error_messages, "All plots must belong to ".$trial->get_name().". ";
+ }
+
+ # Check for unique row/col positions
+ foreach my $position_key (keys %seen_positions) {
+ my $plots = $seen_positions{$position_key};
+ my $count = scalar(@$plots);
+ if ( $count > 1 ) {
+ my @pos = split('-', $position_key);
+ push @error_messages, "Position row=" . $pos[0] . " col=" . $pos[1] . " is assigned to multiple plots: " . join(', ', @$plots) . ". Each position can only be occupied once.";
+ }
+ }
+
+ if (scalar(@error_messages) >= 1) {
+ $errors{'error_messages'} = \@error_messages;
+ $self->_set_parse_errors(\%errors);
+ return;
+ }
+
+ $self->_set_validated_data($parsed);
+ return 1; #returns true if validation is passed
+}
+
+sub _parse_with_plugin {
+ my $self = shift;
+ my $schema = $self->get_chado_schema();
+ my $parsed = $self->_get_validated_data();
+ my $data = $parsed->{'data'};
+
+ # Get plot stock type cvterm
+ my $plot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'plot', 'stock_type')->cvterm_id();
+
+ my %spatial_layout_data;
+
+ foreach my $d (@$data) {
+ my $plot_name = $d->{'plot_name'};
+ my $row_number = $d->{'row_number'};
+ my $col_number = $d->{'col_number'};
+
+ # Get plot stock_id from plot_name
+ my $rs = $schema->resultset("Stock::Stock")->search({
+ 'uniquename' => $plot_name,
+ 'type_id' => $plot_cvterm_id,
+ 'is_obsolete' => { '!=' => 't' }
+ });
+
+ if ($rs->count() > 0) {
+ my $plot_stock = $rs->first();
+ my $plot_id = $plot_stock->stock_id();
+
+ # Store the spatial layout information
+ $spatial_layout_data{$plot_id} = {
+ plot_name => $plot_name,
+ row_number => $row_number,
+ col_number => $col_number
+ };
+ }
+ }
+
+ # print STDERR Dumper \%spatial_layout_data;
+
+ $self->_set_parsed_data({
+ spatial_layout => \%spatial_layout_data
+ });
+
+ return 1;
+}
+
+1;
diff --git a/lib/SGN/Controller/AJAX/TrialMetadata.pm b/lib/SGN/Controller/AJAX/TrialMetadata.pm
index 8380dc732a..e030871f1d 100644
--- a/lib/SGN/Controller/AJAX/TrialMetadata.pm
+++ b/lib/SGN/Controller/AJAX/TrialMetadata.pm
@@ -40,6 +40,8 @@ use List::Util 'sum';
use CXGN::Trial::TrialLayout;
use CXGN::BreedersToolbox::Projects;
use Sort::Key::Natural qw(natkeysort);
+use CXGN::Trial::ParseUpload;
+use CXGN::Job;
BEGIN { extends 'Catalyst::Controller::REST' }
@@ -3676,41 +3678,95 @@ sub upload_trial_coordinates : Path('/ajax/breeders/trial/coordsupload') Args(0)
return;
}
+ # print STDERR "Proceeding with $archived_filename_with_path \n";
+
$md5 = $uploader->get_md5($archived_filename_with_path);
unlink $upload_tempfile;
- my $error_string = '';
- # open file and remove return of line
- open(my $F, "< :encoding(UTF-8)", $archived_filename_with_path) || die "Can't open archive file $archived_filename_with_path";
my $schema = $c->dbic_schema("Bio::Chado::Schema", undef, $user_id);
- my $header = <$F>;
- while (<$F>) {
- chomp;
- $_ =~ s/\r//g;
- my ($plot,$row,$col) = split /\t/ ;
- my $rs = $schema->resultset("Stock::Stock")->search({uniquename=> $plot });
- if ($rs->count()== 1) {
- my $r = $rs->first();
- print STDERR "The plots $plot was found.\n Loading row $row col $col\n";
- $r->create_stockprops({row_number => $row, col_number => $col});
- }
- else {
- print STDERR "WARNING! $plot was not found in the database.\n";
- $error_string .= "WARNING! $plot was not found in the database.";
- }
- }
- if ($error_string){
- $c->stash->{rest} = {error_string => $error_string};
+ my $trial_obj = CXGN::Project->new({
+ bcs_schema => $schema,
+ trial_id => $trial_id
+ });
+
+ my $job = CXGN::Job->new({
+ sp_person_id => $user_id,
+ schema => $schema,
+ people_schema => $c->dbic_schema("CXGN::People::Schema"),
+ name => $trial_obj->get_name()." spatial layout upload",
+ job_type => 'upload',
+ cmd => "",
+ finish_logfile => $c->config->{job_finish_log},
+ submit_page => $c->request->headers->referer,
+ results_page => $c->request->headers->referer
+ });
+
+ $job->update_status("submitted");
+
+ my $parser = CXGN::Trial::ParseUpload->new({
+ chado_schema => $schema,
+ trial_id => $trial_id,
+ filename => $archived_filename_with_path
+ });
+ $parser->load_plugin('TrialSpatialLayoutGeneric');
+
+ my $parsed_data = $parser->parse();
+
+ if (!$parsed_data || $parser->has_parse_errors()) {
+ my $return_error = '';
+
+ if (! $parser->has_parse_errors() ){
+ $return_error = "Parsing failed, but we could not get parsing errors...";
+ }
+ else {
+ my $parse_errors = $parser->get_parse_errors();
+
+ foreach my $error_string (@{$parse_errors->{'error_messages'}}){
+ $return_error=$return_error.$error_string."
";
+ }
+ }
+
+ $c->stash->{rest} = {error => $return_error};
+ $job->additional_args({
+ error => $return_error
+ });
+ $job->update_status("failed");
$c->detach();
+ return;
+ }
+
+ # store all row/column data here
+ eval {
+ foreach my $plot_id (keys(%{$parsed_data->{spatial_layout}})) {
+ my $plot_name = $parsed_data->{spatial_layout}->{$plot_id}->{plot_name};
+ my $row = $parsed_data->{spatial_layout}->{$plot_id}->{row_number};
+ my $col = $parsed_data->{spatial_layout}->{$plot_id}->{col_number};
+
+ my $rs = $schema->resultset("Stock::Stock")->search({uniquename=> $plot_name });
+ my $r = $rs->first();
+ $r->create_stockprops({row_number => $row, col_number => $col});
+ }
+ };
+ if ($@) {
+ $c->stash->{rest} = {error => "The upload was successful, but an error occurred trying to save the row and column data: $@\n"};
+ $job->additional_args({
+ error => $@
+ });
+ $job->update_status("failed");
+ return;
}
+ # print STDERR Dumper $parsed_data->{spatial_layout};
+
my $dbh = $c->dbc->dbh();
my $bs = CXGN::BreederSearch->new( { dbh=>$dbh, dbname=>$c->config->{dbname}, } );
- my $refresh = $bs->refresh_matviews($c->config->{dbhost}, $c->config->{dbname}, $c->config->{dbuser}, $c->config->{dbpass}, 'phenotypes', 'concurrent', $c->config->{basepath});
+ my $refresh = $bs->refresh_matviews($c->config->{dbhost}, $c->config->{dbname}, $c->config->{dbuser}, $c->config->{dbpass}, 'stockprop', 'concurrent', $c->config->{basepath});
my $trial_layout = CXGN::Trial::TrialLayout->new({ schema => $c->dbic_schema("Bio::Chado::Schema", undef, $user_id), trial_id => $trial_id, experiment_type => 'field_layout' });
$trial_layout->generate_and_cache_layout();
+ $job->update_status("finished");
+
$c->stash->{rest} = {success => 1};
}
diff --git a/mason/breeders_toolbox/trial.mas b/mason/breeders_toolbox/trial.mas
index c73c5c75e0..f19869e230 100644
--- a/mason/breeders_toolbox/trial.mas
+++ b/mason/breeders_toolbox/trial.mas
@@ -171,9 +171,9 @@ $project_id => undef
% my $subtitle = 'View and edit the spatial layout of the experiment. Also view a heatmap for phenotyped traits.';
% my $layout_buttons = '';
% if ($has_col_and_row_numbers){
-% $layout_buttons = '