From 9649c4da17d790732067520055faf2b472f76d21 Mon Sep 17 00:00:00 2001 From: Marina Gourtovaia Date: Thu, 30 May 2024 16:07:21 +0100 Subject: [PATCH] Enhanced a textual listing of instruments. Added a column for recently used staging servers so that the loaders can easily spot instruments, which write to the same server. --- data/templates/instrument_list_textual.tt2 | 2 + lib/npg/model/instrument.pm | 100 +++++++++++++-------- t/10-model-instrument.t | 85 +++++++++++++++++- t/data/fixtures/300-run.yml | 12 +++ 4 files changed, 157 insertions(+), 42 deletions(-) diff --git a/data/templates/instrument_list_textual.tt2 b/data/templates/instrument_list_textual.tt2 index 458f4eed..780d4f12 100644 --- a/data/templates/instrument_list_textual.tt2 +++ b/data/templates/instrument_list_textual.tt2 @@ -13,6 +13,7 @@ Current Status Status Comment Laboratory + Staging Server @@ -26,6 +27,7 @@ [% instrument.current_instrument_status.instrument_status_dict.description %] [% instrument.current_instrument_status.comment %] [% instrument.lab %] + [% instrument.join(', ') %] [% END %] diff --git a/lib/npg/model/instrument.pm b/lib/npg/model/instrument.pm index bb23139c..22b91f76 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 uniq); +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,46 @@ sub latest_annotation { return $self->{latest_annotation}; } +sub recent_staging_servers { + my $self = shift; + + my $run_status = q[run in progress]; + my $query = q[SELECT folder_path_glob FROM instrument + JOIN run USING(id_instrument) + JOIN run_status USING(id_run) + JOIN run_status_dict USING(id_run_status_dict) + WHERE folder_path_glob IS NOT NULL + AND LENGTH(folder_path_glob) != 0 + AND id_instrument=? AND description=? + ORDER BY date DESC]; + my $dbh = $self->util->dbh(); + # Four rather than two latest runs since NovaSeq(X) instruments have + # two sides, which write to the same staging server. + my $rows = $dbh->selectall_arrayref($query, {RaiseError => 1, MaxRows => 4}, + $self->id_instrument(), $run_status); + my @globs = uniq map { $_->[0] } @{$rows}; + # We do not need more that two unique globs. + while (scalar @globs > 2) { + pop @globs + } + + my @servers = (); + foreach my $area (@globs) { + my @dirs = File::Spec->splitdir($area); + # Current (May 2024) folder path globs look like + # /{export,nfs}/esa-sv-20201215-03/IL_seq_data/*/ + ##no critic (ValuesAndExpressions::ProhibitMagicNumbers) + if (@dirs >= 3 and $dirs[0] eq q[]) { + push @servers, $dirs[2]; + } else { + push @servers, $area; + } + ##use critic + } + + return @servers; +} + sub does_sequencing { my $self = shift; return ($self->instrument_format->model && @@ -595,9 +637,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 +663,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 +674,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 +746,9 @@ 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_servers - returns a list of names of staging servers +this instrument most recently transferred data to, most recent server first. + =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 +759,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 +791,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 +817,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 +825,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..9bd479e0 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,84 @@ 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 servers list' => sub { + plan tests => 14; + + 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, + }); + is (join(q[ ], $model->recent_staging_servers()), q[esa-sv-20201215-03], + qq[server name for a single run that is associated with the "$status" status]); + + $model = npg::model::instrument->new({ + util => $util, + id_instrument => 14, + }); + is (scalar $model->recent_staging_servers(), 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, + }); + is (join(q[,], $model->recent_staging_servers()), 'esa-sv-20201215-02', + 'a list with one server name'); + + 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, + }); + is (join(q[,], $model->recent_staging_servers()), $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, + }); + is (join(q[,], $model->recent_staging_servers()), $new_glob, + 'a full glob is returned'); + + $update = qq[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, + }); + is (scalar $model->recent_staging_servers(), 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 = qq[update run set folder_path_glob='server5' where id_run=15]; + ok($dbh->do($update), 'folder path glob is updated'); + $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, + }); + is (join(q[, ], $model->recent_staging_servers()), q[server5, /{export,nfs}], + 'latest servers list'); + + $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