diff --git a/data/templates/instrument_list_textual.tt2 b/data/templates/instrument_list_textual.tt2 index 458f4eed..688cbbf9 100644 --- a/data/templates/instrument_list_textual.tt2 +++ b/data/templates/instrument_list_textual.tt2 @@ -13,10 +13,17 @@ Current Status Status Comment Laboratory + Staging Volume (last) + Staging Volume (previous) -[% FOREACH instrument = model.instruments %] +[% FOREACH instrument = model.instruments; + volumes = []; + FOREACH description = instrument.recent_staging_volumes; + volumes.push(description.volume _ ' ' _ description.maxdate); + END; +-%] [% USE String(instrument.id_instrument); String.format('%04d') %][% instrument.name %] [% instrument.model %] @@ -26,6 +33,8 @@ [% instrument.current_instrument_status.instrument_status_dict.description %] [% instrument.current_instrument_status.comment %] [% instrument.lab %] + [% volumes.item(0) IF volumes.defined(0) %] + [% volumes.item(1) IF volumes.defined(1) %] [% END %] diff --git a/lib/npg/model/instrument.pm b/lib/npg/model/instrument.pm index bb23139c..6b6ec03d 100644 --- a/lib/npg/model/instrument.pm +++ b/lib/npg/model/instrument.pm @@ -2,9 +2,15 @@ package npg::model::instrument; use strict; use warnings; -use base qw(npg::model); use English qw(-no_match_vars); +use File::Spec; use Carp; +use DateTime; +use DateTime::Duration; +use DateTime::Format::MySQL; +use List::MoreUtils qw(any); +use base qw(npg::model); + use npg::model::user; use npg::model::run; use npg::model::instrument_format; @@ -15,10 +21,6 @@ use npg::model::instrument_annotation; use npg::model::annotation; use npg::model::instrument_designation; use npg::model::designation; -use DateTime; -use DateTime::Duration; -use DateTime::Format::MySQL; -use List::MoreUtils qw/any/; our $VERSION = '0'; @@ -380,6 +382,45 @@ sub latest_annotation { return $self->{latest_annotation}; } +sub recent_staging_volumes { + my $self = shift; + + my $run_status = q[run in progress]; + my $key_field = q[folder_path_glob]; + my $date_field = q[maxdate]; + # Using some MySQL-specific syntax... + my $query = qq[SELECT $key_field, MAX(run_status.date) $date_field + FROM instrument + JOIN run USING(id_instrument) + JOIN run_status USING(id_run) + JOIN run_status_dict USING(id_run_status_dict) + WHERE $key_field IS NOT NULL + AND LENGTH($key_field) != 0 + AND id_instrument=? AND description=? + GROUP BY $key_field + ORDER BY $date_field DESC]; + my $dbh = $self->util->dbh(); + my $rows = $dbh->selectall_arrayref($query, + {RaiseError => 1, MaxRows => 2}, # get two rows only + $self->id_instrument(), $run_status); + + my @volumes = (); + foreach my $row (@{$rows}) { + my $area = $row->[0]; + my @dirs = File::Spec->splitdir($area); + # Current (June 2024) folder path globs look like + # /{export,nfs}/esa-sv-20201215-03/IL_seq_data/*/ + ##no critic (ValuesAndExpressions::ProhibitMagicNumbers) + $area = (@dirs >= 3 and $dirs[0] eq q[]) ? $dirs[2] : $area; + ##use critic + my ($date) = $row->[1] =~ /\A([\d|-]+)[ ]/smx; # Get the date part only. + $date or croak 'Failed to parse date ' . $row->[1]; + push @volumes, {'volume' => $area, $date_field => $date}; + } + + return \@volumes; +} + sub does_sequencing { my $self = shift; return ($self->instrument_format->model && @@ -595,9 +636,9 @@ npg::model::instrument =head1 DESCRIPTION Clearpress model for an instrument. - To be replaced by DBIx model. Contains duplicates of functions in - npg_tracking::Schema::Result::Instrument. When editing the code - of this module consider if any changes are meeded in the other module. + Contains duplicates of functions in npg_tracking::Schema::Result::Instrument. + When editing the code of this module consider if any changes are needed in + the other module. =head1 SUBROUTINES/METHODS @@ -621,11 +662,6 @@ npg::model::instrument my $arAllInstruments = $oInstrument->instruments(); -=head2 instrument_by_ipaddr - npg::model::instrument by its IP address - - my $oInstrument = $oInstrument->instrument_by_ipaddr('127.0.0.1'); -instrument_by_instrument_comp - =head2 instrument_by_instrument_comp - npg::model::instrument by its instrument_comp name =head2 current_instruments - arrayref of all npg::model::instruments with iscurrent=1 @@ -637,16 +673,6 @@ instrument_by_instrument_comp my $lab = 'Sulston'; my $arCurrentSulstonInstruments = $oInstrument->current_instruments_from_lab($lab); -=head2 last_wash_instrument_status - npg::model::instrument_status (or undef) corresponding to the last 'wash performed' state - - my $oInstrumentStatus = $oInstrument->last_wash_instrument_status(); - -=head2 check_wash_status - boolean whether this instrument needs washing - -Has a side-effect of updating an instrument's current instrument_status to 'wash required' - - $bNeedAWash = $oInstrument->check_wash_status(); - =head2 runs - arrayref of npg::model::runs for this instrument my $arRuns = $oInstrument->runs(); @@ -719,6 +745,13 @@ Has a side-effect of updating an instrument's current instrument_status to 'wash =head2 fc_slots2blocking_runs - a hash reference mapping instrument flowcell slots to blocking runs; tags for slots are used as keys +=head2 recent_staging_volumes - returns an array of hash references containing +information about the volumes this instrument recently transferred data to. +The array might be empty. It is guaranteed not to contain more than two members. +Under the 'volume' key each hash has the name of the volume, under the 'maxdate' +key - the most recent date this volume was used by this instrument. The first +array member describes the volume that was used most recently. + =head2 does_sequencing - returns true is the instrument does sequencing, false otherwise =head2 is_two_slot_instrument - returns true if this instrument has two slots, false otherwise @@ -729,7 +762,7 @@ returns true if the instrument is a MiSeq, false otherwise =head2 is_cbot_instrument - returns true if this instrument is CBot, false otherwise -=head2 current_run_by_id - returns one of current runs with teh argument id or nothing if a list of current runs does not contain a run with this id +=head2 current_run_by_id - returns one of current runs with the argument id or nothing if a list of current runs does not contain a run with this id my $id_run = 22; my $run = $oInstrument->current_run_by_id($id_run); @@ -761,27 +794,21 @@ returns true if the instrument is a MiSeq, false otherwise =item base -=item npg::model +=item File::Spec =item English =item Carp -=item npg::model::user - -=item npg::model::run - -=item npg::model::instrument_format - -=item npg::model::instrument_status +=item Readonly -=item npg::model::instrument_status_dict +=item DateTime -=item npg::model::instrument_mod +=item DateTime::Duration -=item DateTime +=item DateTime::Format::MySQL -=item Readonly +=item List::MoreUtils =back @@ -793,7 +820,7 @@ returns true if the instrument is a MiSeq, false otherwise =over -=item Roger Pettett, Ermp@sanger.ac.ukE +=item Roger Pettett =item Marina Gourtovaia @@ -801,7 +828,7 @@ returns true if the instrument is a MiSeq, false otherwise =head1 LICENSE AND COPYRIGHT -Copyright (C) 2006,2008,2013,2014,2016,2018,2021 Genome Research Ltd. +Copyright (C) 2006,2008,2013,2014,2016,2018,2021,2024 Genome Research Ltd. This file is part of NPG. diff --git a/t/10-model-instrument.t b/t/10-model-instrument.t index 2449dc7f..44a21f1f 100644 --- a/t/10-model-instrument.t +++ b/t/10-model-instrument.t @@ -1,7 +1,7 @@ use strict; use warnings; use t::util; -use Test::More tests => 141; +use Test::More tests => 142; use Test::Deep; use Test::Exception; @@ -191,7 +191,6 @@ my $util = t::util->new({ fixtures => 1 }); qq[status changed automatically to "$auto_status"]); } - { my $instr = npg::model::instrument->new({ util => $util, @@ -312,7 +311,6 @@ lives_ok {$util->fixtures_path(q[t/data/fixtures]); $util->load_fixtures;} 'a fr ok (!$model->autochange_status_if_needed('analysis in progress'), 'no autochange status'); } - { my $model = npg::model::instrument->new({util => $util, id_instrument => 36,}); ok($model->is_two_slot_instrument, 'is two_slot instrument'); @@ -351,7 +349,6 @@ lives_ok {$util->fixtures_path(q[t/data/fixtures]); $util->load_fixtures;} 'a fr cmp_deeply($model->fc_slots2blocking_runs, {fc_slotA => [], fc_slotB => [],}, 'empty mapping of both slots to blocking runs'); } - { my $run = npg::model::run->new({util => $util, id_run => 9950,}); $run->id_instrument(36); @@ -421,4 +418,97 @@ lives_ok {$util->fixtures_path(q[t/data/fixtures]); $util->load_fixtures;} 'a fr is (scalar @{$model->instrument_statuses()}, 3, 'number of statuses in total'); } +subtest 'recent staging volumes list' => sub { + plan tests => 23; + + my $util4updates = t::util->new(); # need a new db handle + lives_ok {$util4updates->load_fixtures;} 'a fresh set of fixtures is loaded'; + my $dbh = $util4updates->dbh; + $dbh->{AutoCommit} = 1; + $dbh->{RaiseError} = 1; + + my $status = 'run in progress'; + + my $model = npg::model::instrument->new({ + util => $util, + id_instrument => 3, + }); + my @volumes = @{$model->recent_staging_volumes()}; + is (@volumes, 1, 'one record is returned'); + is ($volumes[0]->{'volume'}, q[esa-sv-20201215-03], + qq[volume name for a single run that is associated with the "$status" status]); + is ($volumes[0]->{'maxdate'}, '2007-06-05', 'the date is correct'); + + $model = npg::model::instrument->new({ + util => $util, + id_instrument => 14, + }); + is (scalar @{$model->recent_staging_volumes()}, 0, + 'empty list since no glob is available for a run that is associated with ' . + qq[the "$status" status]); + + $model = npg::model::instrument->new({ + util => $util, + id_instrument => 13, + }); + @volumes = @{$model->recent_staging_volumes()}; + is (@volumes, 1, 'one record is returned'); + is ($volumes[0]->{'volume'}, 'esa-sv-20201215-02', 'volume name is correct'); + is ($volumes[0]->{'maxdate'}, '2007-06-05', 'the date is correct'); + + my $new_glob = q[{export,nfs}/esa-sv-20201215-02/IL_seq_data/*/]; + my $update = qq[update run set folder_path_glob='$new_glob' where id_run=15]; + ok($dbh->do($update), 'folder path glob is updated'); + $model = npg::model::instrument->new({ + util => $util, + id_instrument => 13, + }); + @volumes = @{$model->recent_staging_volumes()}; + is ($volumes[0]->{'volume'}, $new_glob, 'a full glob is returned'); + + $new_glob = q[/{export,nfs}]; + $update = qq[update run set folder_path_glob='$new_glob' where id_run=15]; + ok($dbh->do($update), 'folder path glob is updated'); + $model = npg::model::instrument->new({ + util => $util, + id_instrument => 13, + }); + @volumes = @{$model->recent_staging_volumes()}; + is ($volumes[0]->{'volume'}, $new_glob, 'a full glob is returned'); + + $update = q[update run set folder_path_glob='' where id_run=15]; + ok($dbh->do($update), 'folder path glob is updated'); + $model = npg::model::instrument->new({ + util => $util, + id_instrument => 13, + }); + @volumes = @{$model->recent_staging_volumes()}; + is (@volumes, 0, 'an empty list is returned for a zero length glob'); + + $update = q[update run_status set id_run_status_dict=2 where ] . + q[id_run in (3,4,5) and id_run_status_dict=4]; + ok($dbh->do($update), 'run statuses are updated'); + $update = q[update run set folder_path_glob='/{export,nfs}' where id_run=15]; + ok($dbh->do($update), 'folder path glob is updated'); + my $new_date = '2024-05-11 11:23:45'; + $update = qq[update run_status set date='$new_date' where id_run=15 and ] . + q[id_run_status_dict=2]; + ok($dbh->do($update), 'update the date'); + $update = qq[update run set id_instrument=3 where id_run=15]; + ok($dbh->do($update), 'assign one more run to the instrument'); + + $model = npg::model::instrument->new({ + util => $util, + id_instrument => 3, + }); + @volumes = @{$model->recent_staging_volumes()}; + is (@volumes, 2, 'data for two volumes'); + is ($volumes[1]->{'volume'}, q[esa-sv-20201215-03], 'previous volume'); + is ($volumes[1]->{'maxdate'}, '2007-06-05', 'the date is correct'); + is ($volumes[0]->{'volume'}, q[/{export,nfs}], 'latest volume'); + is ($volumes[0]->{'maxdate'}, '2024-05-11', 'the date is correct'); + + $dbh->disconnect; +}; + 1; diff --git a/t/data/fixtures/300-run.yml b/t/data/fixtures/300-run.yml index b676f2a8..984ee32f 100644 --- a/t/data/fixtures/300-run.yml +++ b/t/data/fixtures/300-run.yml @@ -10,6 +10,7 @@ priority: 1 flowcell_id: id_instrument_format: 1 + folder_path_glob: ~ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 0 @@ -20,6 +21,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs}/sf44/ILorHSany_sf44/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 0 @@ -30,6 +32,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs}/sf44/ILorHSany_sf44/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 0 @@ -40,6 +43,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs}/sf55/ILorHSany_sf55/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 31 @@ -50,6 +54,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs} - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 33 @@ -60,6 +65,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: sf58 - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 33 @@ -70,6 +76,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs}/esa-sv-20201215-02/IL_seq_data/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 35 @@ -80,6 +87,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs}/esa-sv-20201215-02/IL_seq_data/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 35 @@ -90,6 +98,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs}/esa-sv-20201215-01/IL_seq_data/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 35 @@ -100,6 +109,7 @@ is_paired: 0 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs}/esa-sv-20201215-01/IL_seq_data/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 35 @@ -110,6 +120,7 @@ is_paired: 1 priority: 1 id_instrument_format: 1 + folder_path_glob: /{export,nfs}/esa-sv-20201215-03/IL_seq_data/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 35 @@ -150,6 +161,7 @@ is_paired: 1 priority: 1 id_instrument_format: 4 + folder_path_glob: /{export,nfs}/esa-sv-20201215-02/IL_seq_data/*/ - actual_cycle_count: 0 batch_id: 0 expected_cycle_count: 35