From b8aeda5fa67bdc4e839b95c946275cf81a9cf96b Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:00:12 +0100 Subject: [PATCH 01/10] LIMS-59: Never write null to DewarTransportHistory.storageLocation (#597) * LIMS-59: Never write null to DewarTransportHistory.storageLocation * LIMS-59: Write a location to DTH when marking a dewar as unprocessing * LIMS-59: Check for nulls before doing inserts * LIMS-59: Ensure tests pass * Update test * tidy --------- Co-authored-by: Mark Williams Co-authored-by: John Holt --- api/src/Model/Services/AssignData.php | 10 +++-- api/src/Page/Shipment.php | 31 ++++++++------ api/tests/Model/Services/AssignDataTest.php | 11 +++-- api/tests/TestUtils.php | 47 +++++++++++++++++++++ 4 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 api/tests/TestUtils.php diff --git a/api/src/Model/Services/AssignData.php b/api/src/Model/Services/AssignData.php index f2d681a41..ed3e4bbaf 100644 --- a/api/src/Model/Services/AssignData.php +++ b/api/src/Model/Services/AssignData.php @@ -2,6 +2,8 @@ namespace SynchWeb\Model\Services; +use SynchWeb\Utils; + class AssignData { private $db; @@ -85,9 +87,10 @@ function updateDewar($dewarId, $status) function deactivateDewar($dewarId) { - $this->updateDewarHistory($dewarId, 'unprocessing'); + $location = $this->db->pq("SELECT storagelocation FROM dewar WHERE dewarid=:1", array($dewarId)); + $this->updateDewarHistory($dewarId, 'unprocessing', $location[0]['STORAGELOCATION']); - $conts = $this->db->pq("SELECT containerid as id FROM container WHERE dewarid=:1", array($dewarId)); + $conts = $this->db->pq("SELECT containerid FROM container WHERE dewarid=:1", array($dewarId)); foreach ($conts as $container) { $this->updateContainerAndHistory($container['CONTAINERID'], 'at facility', '', ''); @@ -99,9 +102,10 @@ function updateDewarHistory($did, $status, $beamline = null, $additionalStatusDe $st = $status; if ($additionalStatusDetail) $st .= ' (' . $additionalStatusDetail . ')'; + $loc = Utils::getValueOrDefault($beamline, ''); $this->db->pq("INSERT INTO dewartransporthistory (dewarid, dewarstatus, storagelocation, arrivaldate) - VALUES (:1, :2, :3, CURRENT_TIMESTAMP)", array($did, $st, $beamline)); + VALUES (:1, :2, :3, CURRENT_TIMESTAMP)", array($did, $st, $loc)); $this->updateDewar($did, $status); } diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 50be8ea2c..8958261a6 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -858,6 +858,8 @@ function _transfer_dewar() global $transfer_email; if (!$this->has_arg('DEWARID')) $this->_error('No dewar specified'); + if (!$this->has_arg('LOCATION')) + $this->_error('No location specified'); $dew = $this->db->pq("SELECT d.dewarid,s.shippingid FROM dewar d @@ -1046,7 +1048,7 @@ function _dispatch_dewar() $dewar_location = $last_history['STORAGELOCATION']; } else { // Use the current location of the dewar instead if no history - $dewar_location = $dew['STORAGELOCATION']; + $dewar_location = Utils::getValueOrDefault($dew['STORAGELOCATION'], ''); } } // Check if the last history storage location is an EBIC prefix or not @@ -1561,7 +1563,7 @@ function _send_shipment() } $this->db->pq("UPDATE shipping SET shippingstatus='sent to facility' where shippingid=:1", array($ship['SHIPPINGID'])); - $this->db->pq("UPDATE dewar SET dewarstatus='sent to facility' where shippingid=:1", array($ship['SHIPPINGID'])); + $this->db->pq("UPDATE dewar SET dewarstatus='sent to facility', storagelocation='off-site' where shippingid=:1", array($ship['SHIPPINGID'])); $dewars = $this->db->pq("SELECT d.dewarid, s.visit_number as vn, s.beamlinename as bl, TO_CHAR(s.startdate, 'DD-MM-YYYY HH24:MI') as startdate FROM dewar d @@ -1569,8 +1571,8 @@ function _send_shipment() WHERE d.shippingid=:1", array($ship['SHIPPINGID'])); foreach ($dewars as $d) { $this->db->pq( - "INSERT INTO dewartransporthistory (dewartransporthistoryid,dewarid,dewarstatus,arrivaldate) - VALUES (s_dewartransporthistory.nextval,:1,'sent to facility',CURRENT_TIMESTAMP) RETURNING dewartransporthistoryid INTO :id", + "INSERT INTO dewartransporthistory (dewartransporthistoryid,dewarid,dewarstatus,storagelocation,arrivaldate) + VALUES (s_dewartransporthistory.nextval,:1,'sent to facility','off-site',CURRENT_TIMESTAMP) RETURNING dewartransporthistoryid INTO :id", array($d['DEWARID']) ); } @@ -2878,11 +2880,11 @@ function _create_awb() continue; $p = $awb['pieces'][$i]; - $this->db->pq("UPDATE dewar SET $tno=:1, deliveryAgent_barcode=:2, dewarstatus='awb created' WHERE dewarid=:3", array($awb['awb'], $p['licenseplate'], $d['DEWARID'])); + $this->db->pq("UPDATE dewar SET $tno=:1, deliveryAgent_barcode=:2, dewarstatus='awb created', storagelocation='off-site' WHERE dewarid=:3", array($awb['awb'], $p['licenseplate'], $d['DEWARID'])); $this->db->pq( - "INSERT INTO dewartransporthistory (dewartransporthistoryid,dewarid,dewarstatus,arrivaldate) - VALUES (s_dewartransporthistory.nextval,:1,'awb created',CURRENT_TIMESTAMP) RETURNING dewartransporthistoryid INTO :id", + "INSERT INTO dewartransporthistory (dewartransporthistoryid,dewarid,dewarstatus,storagelocation,arrivaldate) + VALUES (s_dewartransporthistory.nextval,:1,'awb created','off-site',CURRENT_TIMESTAMP) RETURNING dewartransporthistoryid INTO :id", array($d['DEWARID']) ); } @@ -3035,10 +3037,10 @@ function _do_request_pickup($options) WHERE shippingid=:4", array($pickup['confirmationnumber'], $pickup['readybytime'], $pickup['callintime'], $options['shippingid'])); foreach ($options['dewars'] as $i => $d) { - $this->db->pq("UPDATE dewar SET dewarstatus='pickup booked' WHERE dewarid=:1", array($d['DEWARID'])); + $this->db->pq("UPDATE dewar SET dewarstatus='pickup booked', storagelocation='off-site' WHERE dewarid=:1", array($d['DEWARID'])); $this->db->pq( - "INSERT INTO dewartransporthistory (dewartransporthistoryid,dewarid,dewarstatus,arrivaldate) - VALUES (s_dewartransporthistory.nextval,:1,'pickup booked',CURRENT_TIMESTAMP) RETURNING dewartransporthistoryid INTO :id", + "INSERT INTO dewartransporthistory (dewartransporthistoryid,dewarid,dewarstatus,storagelocation,arrivaldate) + VALUES (s_dewartransporthistory.nextval,:1,'pickup booked','off-site',CURRENT_TIMESTAMP) RETURNING dewartransporthistoryid INTO :id", array($d['DEWARID']) ); } @@ -3170,7 +3172,7 @@ function _cancel_pickup() $this->_error('No such lab contact'); $cont = $cont[0]; - $dewars = $this->db->pq("SELECT d.dewarid + $dewars = $this->db->pq("SELECT d.dewarid, d.storagelocation FROM dewar d WHERE d.shippingid=:1 AND d.deliveryagent_barcode IS NOT NULL", array($this->arg('sid'))); @@ -3191,10 +3193,11 @@ function _cancel_pickup() foreach ($dewars as $i => $d) { $this->db->pq("UPDATE dewar SET dewarstatus='pickup cancelled' WHERE dewarid=:1", array($d['DEWARID'])); + $loc = Utils::getValueOrDefault($d['STORAGELOCATION'], ''); $this->db->pq( - "INSERT INTO dewartransporthistory (dewarid,dewarstatus,arrivaldate) - VALUES (:1,'pickup cancelled',CURRENT_TIMESTAMP)", - array($d['DEWARID']) + "INSERT INTO dewartransporthistory (dewarid,dewarstatus,storagelocation,arrivaldate) + VALUES (:1,'pickup cancelled',:2,CURRENT_TIMESTAMP)", + array($d['DEWARID'], $loc) ); } } catch (\Exception $e) { diff --git a/api/tests/Model/Services/AssignDataTest.php b/api/tests/Model/Services/AssignDataTest.php index 064a69d87..297474e2e 100644 --- a/api/tests/Model/Services/AssignDataTest.php +++ b/api/tests/Model/Services/AssignDataTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use SynchWeb\Model\Services\AssignData; use SynchWeb\Database\Type\MySQL; +use Tests\TestUtils; /** * @runTestsInSeparateProcesses // Needed to allow db mocking @@ -20,6 +21,7 @@ final class AssignDataTest extends TestCase private $db; private $assignData; private $insertId; + private $stmtStub; protected function setUp(): void { @@ -29,7 +31,7 @@ protected function setUp(): void $this->db = new MySQL($connStub); $this->assignData = new AssignData($this->db); - $stmtStub = $this->getMockBuilder(\mysqli_stmt::class) + $this->stmtStub = $this->getMockBuilder(\mysqli_stmt::class) ->disableOriginalConstructor() ->onlyMethods(['bind_param', 'execute', 'get_result', 'close']) ->getMock(); @@ -40,8 +42,8 @@ protected function setUp(): void $this->insertId->expects($this->any())->willReturn(666); } - $stmtStub->method('execute')->willReturn(true); - $connStub->method('prepare')->willReturn($stmtStub); + $this->stmtStub->method('execute')->willReturn(true); + $connStub->method('prepare')->willReturn($this->stmtStub); } public function testGetContainerCreatesCorrectSql(): void @@ -108,8 +110,9 @@ public function testUpdateDewarWithUnprocessingStatusCreatesCorrectSql(): void public function testDeactivateDewarCreatesCorrectSql(): void { + TestUtils::mockDBReturnsResult($this->stmtStub, [['STORAGELOCATION'=> "current location"], ]); $this->assignData->deactivateDewar('testDewarId'); - $this->assertEquals("SELECT containerid as id FROM Container WHERE dewarid='testDewarId'", $this->db->getLastQuery()); + $this->assertEquals("SELECT containerid FROM Container WHERE dewarid='testDewarId'", $this->db->getLastQuery()); } public function testUpdateDewarHistoryCreatesCorrectSql(): void diff --git a/api/tests/TestUtils.php b/api/tests/TestUtils.php new file mode 100644 index 000000000..5e4883891 --- /dev/null +++ b/api/tests/TestUtils.php @@ -0,0 +1,47 @@ +method('get_result')->willReturn(new MockTestResult($returnRows)); + } + +} + +/** + * Class to help with returning mock results from sql + */ +class MockTestResult +{ + public $num_rows; + private $returnRows; + private $row_index = -1; + + function __construct($returnRows){ + $this->returnRows = $returnRows; + $this->num_rows = $returnRows? count($returnRows) : 0; + } + + function fetch_assoc() { + + $this->row_index++; + if ($this->row_index < $this->num_rows) { + return $this->returnRows[$this->row_index]; + } + return null; + + } +} From fc4295e25e7cb082c2d56e10d63d85ce3812da6f Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:20:13 +0100 Subject: [PATCH 02/10] LIMS-95: Show max score for each drop in a plate (#621) * LIMS-95: Show max score for each drop in a plate * Update UI to use radio buttons * Update label --------- Co-authored-by: Mark Williams Co-authored-by: John Holt --- api/src/Page/Imaging.php | 13 ++++- client/src/css/partials/_utility.scss | 12 ++++ .../js/modules/imaging/views/imageviewer.js | 5 +- .../modules/shipment/views/containerplate.js | 55 ++++++++++-------- client/src/js/modules/shipment/views/plate.js | 22 ++++++-- .../shipment/containerplateimage.html | 56 +++++++++++++------ 6 files changed, 115 insertions(+), 48 deletions(-) diff --git a/api/src/Page/Imaging.php b/api/src/Page/Imaging.php index 09d704bce..db495ba96 100644 --- a/api/src/Page/Imaging.php +++ b/api/src/Page/Imaging.php @@ -557,7 +557,7 @@ function _add_adhoc_inspection() - # Get a list of sample images avaialble for an inspection + # Get a list of sample images available for an inspection function _get_inspection_images() { $where = ''; @@ -578,8 +578,7 @@ function _get_inspection_images() array_push($args, $this->arg('sid')); } - - $images = $this->db->pq("SELECT i.containerid, si.containerinspectionid, ROUND(TIMESTAMPDIFF('HOUR', min(i2.bltimestamp), i.bltimestamp)/24,1) as delta, si.blsampleimageid, si.blsampleid, si.micronsperpixelx, si.micronsperpixely, si.blsampleimagescoreid, si.comments, TO_CHAR(si.bltimestamp, 'DD-MM-YYYY HH24:MI') as bltimestamp, sc.name as scorename, sc.score, sc.colour as scorecolour + $images = $this->db->pq("SELECT i.containerid, si.containerinspectionid, ROUND(TIMESTAMPDIFF('HOUR', min(i2.bltimestamp), i.bltimestamp)/24,1) as delta, si.blsampleimageid, si.blsampleid, si.micronsperpixelx, si.micronsperpixely, si.blsampleimagescoreid, si.comments, TO_CHAR(si.bltimestamp, 'DD-MM-YYYY HH24:MI') as bltimestamp, sc.name as scorename, sc.score, sc.colour as scorecolour, max.maxscore, scorecolours.colour as maxscorecolour FROM blsampleimage si LEFT OUTER JOIN blsampleimagescore sc ON sc.blsampleimagescoreid = si.blsampleimagescoreid INNER JOIN containerinspection i ON i.containerinspectionid = si.containerinspectionid @@ -589,6 +588,14 @@ function _get_inspection_images() INNER JOIN dewar d ON d.dewarid = c.dewarid INNER JOIN shipping s ON s.shippingid = d.shippingid INNER JOIN proposal p ON p.proposalid = s.proposalid + + LEFT OUTER JOIN (SELECT blsampleid, max(score) as maxscore + FROM BLSampleImageScore sc + INNER JOIN BLSampleImage si ON sc.blsampleimagescoreid = si.blsampleimagescoreid + INNER JOIN ContainerInspection i ON i.containerinspectionid = si.containerinspectionid + GROUP BY blSampleid) as max ON max.blsampleid = si.blsampleid + LEFT OUTER JOIN (SELECT score, colour FROM BLSampleImageScore) as scorecolours ON scorecolours.score = max.maxscore + WHERE p.proposalid = :1 $where GROUP BY i.containerid, si.containerinspectionid, i.bltimestamp, si.blsampleimageid, si.blsampleid, si.micronsperpixelx, si.micronsperpixely, si.blsampleimagescoreid, si.comments, TO_CHAR(si.bltimestamp, 'DD-MM-YYYY HH24:MI'), sc.name, sc.score, sc.colour ORDER BY i.bltimestamp", $args); diff --git a/client/src/css/partials/_utility.scss b/client/src/css/partials/_utility.scss index 99bc953cd..54f59304a 100644 --- a/client/src/css/partials/_utility.scss +++ b/client/src/css/partials/_utility.scss @@ -60,6 +60,18 @@ text-align: right; } +.flex { + display: flex; +} + +.flex_take_space { + flex: 1 1 auto +} + +.flex_dont_change { + flex: 0 0 auto +} + /* TODO: If we can ever get rid of this class, we can also remove the work-around in plotly support which needs to circumvent it */ .active { diff --git a/client/src/js/modules/imaging/views/imageviewer.js b/client/src/js/modules/imaging/views/imageviewer.js index 9b8050834..6751db344 100644 --- a/client/src/js/modules/imaging/views/imageviewer.js +++ b/client/src/js/modules/imaging/views/imageviewer.js @@ -315,6 +315,9 @@ define(['marionette', var sc = this.scores.findWhere({ BLSAMPLEIMAGESCOREID: this.ui.score.val() }) this.model.set('BLSAMPLEIMAGESCOREID', this.ui.score.val()) this.model.set('SCORECOLOUR', sc.get('COLOUR')) + if (sc.get('SCORE') > this.model.get('MAXSCORE')) { + this.model.set('MAXSCORECOLOUR', sc.get('COLOUR')) + } this.model.save({ BLSAMPLEIMAGESCOREID: this.ui.score.val() }, { patch: true }) this.trigger('image:scored') }, @@ -1188,4 +1191,4 @@ define(['marionette', }) -}) \ No newline at end of file +}) diff --git a/client/src/js/modules/shipment/views/containerplate.js b/client/src/js/modules/shipment/views/containerplate.js index 2cc1c6101..aa5fd157f 100644 --- a/client/src/js/modules/shipment/views/containerplate.js +++ b/client/src/js/modules/shipment/views/containerplate.js @@ -255,12 +255,14 @@ define(['marionette', ret: 'div.return', adh: 'span.adhoc', que: 'div.queue', - ss: 'input[name=sample_status]', + sampleStatusData: 'input[id=sample_status_data]', + sampleStatusMax: 'input[id=sample_status_max]', + sampleStatusCurrent: 'input[id=sample_status_current]', param: 'select[name=param]', rank: 'input[name=rank]', - auto: 'input[name=auto]', + sampleStatusAuto: 'input[id=sample_status_auto]', schema: 'select[name=schema]', class: 'select[name=class]', }, @@ -280,12 +282,14 @@ define(['marionette', 'click a.adhoc': 'requestAdhoc', 'click a.return': 'requestReturn', - 'change @ui.ss': 'toggleSampleStatus', + 'change @ui.sampleStatusData': 'setSampleStatusShown', + 'change @ui.sampleStatusMax': 'setSampleStatusShown', + 'change @ui.sampleStatusCurrent': 'setSampleStatusShown', 'click @ui.rank': 'setRankStatus', - 'change @ui.param': 'setRankStatus', + 'change @ui.param': 'setParamValue', - 'click @ui.auto': 'setAutoStatus', + 'click @ui.sampleStatusAuto': 'setSampleStatusShown', 'change @ui.class': 'setAutoStatus', 'change @ui.schema': 'selectSchema', @@ -295,15 +299,19 @@ define(['marionette', 'change:QUEUED': 'updatedQueued', }, - toggleSampleStatus: function() { - this.ui.auto.prop('checked', false) - this.plateView.setAutoStatus(false) - this.plateView.setShowSampleStatus(this.ui.ss.is(':checked')) - }, + setSampleStatusShown: function() { + + const showCurrentScore = this.ui.sampleStatusCurrent.is(':checked') + const showMaxScore = this.ui.sampleStatusMax.is(':checked') + const showDataStatus = this.ui.sampleStatusData.is(':checked') + const showAutoScore = this.ui.sampleStatusAuto.is(':checked') + this.plateView.setShowSampleStatus(showDataStatus, showCurrentScore || showAutoScore, showMaxScore) + this.plateView.setAutoStatus(showAutoScore && this.ui.class.val()) - setRankStatus: function() { + // set ranked options var opt = this.ui.param.find('option:selected') - var options = this.ui.rank.is(':checked') ? { + const is_rank_by = this.ui.rank.is(':checked') + var options = is_rank_by ? { value: opt.attr('value'), min: opt.data('min'), check: opt.data('check'), @@ -313,22 +321,23 @@ define(['marionette', this.plateView.setRankStatus(options) this.image.setRankStatus(options) + }, + + setParamValue: function() { + this.ui.rank.prop('checked', true) + this.setRankStatus() + }, + + setRankStatus: function() { if (this.ui.rank.is(':checked')) { - if (!this.ui.ss.is(':checked')) { - this.ui.ss.prop('checked', true) - this.toggleSampleStatus() - } + this.ui.sampleStatusData.prop('checked', true) } + this.setSampleStatusShown() }, setAutoStatus: function() { - this.ui.ss.prop('checked', false) - this.ui.rank.prop('checked', false) - this.plateView.setShowSampleStatus(false) - - var enabled = this.ui.auto.is(':checked') - console.log('setAutoStatus', enabled, this.ui.class.val(), enabled && this.ui.class.val()) - this.plateView.setAutoStatus(enabled && this.ui.class.val()) + this.ui.sampleStatusAuto.prop('checked', true) + this.setSampleStatusShown() }, updateAdhoc: function() { diff --git a/client/src/js/modules/shipment/views/plate.js b/client/src/js/modules/shipment/views/plate.js index 9e4e0f333..62d6b0a90 100644 --- a/client/src/js/modules/shipment/views/plate.js +++ b/client/src/js/modules/shipment/views/plate.js @@ -45,6 +45,7 @@ define(['marionette', 'backbone', 'utils', 'backbone-validation'], function(Mari this.hover = {} this.showImageStatus = this.getOption('showImageStatus') this.showSampleStatus = this.getOption('showSampleStatus') + this.showMaxScore = false Backbone.Validation.bind(this, { collection: this.collection @@ -61,9 +62,10 @@ define(['marionette', 'backbone', 'utils', 'backbone-validation'], function(Mari this.autoscores = scores }, - setShowSampleStatus: function(status) { - this.showSampleStatus = status - this.showImageStatus = !status + setShowSampleStatus: function(sampleStatus, imageStatus, showMaxScore) { + this.showSampleStatus = sampleStatus + this.showImageStatus = imageStatus + this.showMaxScore = showMaxScore this.drawPlate() }, @@ -191,7 +193,7 @@ define(['marionette', 'backbone', 'utils', 'backbone-validation'], function(Mari var sampleid = i*this.pt.dropTotal()+did+1 var sample = this.collection.findWhere({ LOCATION: sampleid.toString() }) - if (sample && this.showImageStatus && this.inspectionimages) var im = this.inspectionimages.findWhere({ BLSAMPLEID: sample.get('BLSAMPLEID') }) + if (sample && (this.showImageStatus || this.showMaxScore) && this.inspectionimages) var im = this.inspectionimages.findWhere({ BLSAMPLEID: sample.get('BLSAMPLEID') }) else var im = null this.ctx.beginPath() @@ -280,7 +282,17 @@ define(['marionette', 'backbone', 'utils', 'backbone-validation'], function(Mari this.ctx.fill() } } + } + // Show max image score + if (sample && this.showMaxScore) { + if (im) { + var isc = im.get('MAXSCORECOLOUR') + if (isc){ + this.ctx.fillStyle = isc + this.ctx.fill() + } + } } // Auto image scores @@ -338,4 +350,4 @@ define(['marionette', 'backbone', 'utils', 'backbone-validation'], function(Mari }) -}) \ No newline at end of file +}) diff --git a/client/src/js/templates/shipment/containerplateimage.html b/client/src/js/templates/shipment/containerplateimage.html index ccd7e94c6..780ec2a49 100644 --- a/client/src/js/templates/shipment/containerplateimage.html +++ b/client/src/js/templates/shipment/containerplateimage.html @@ -11,23 +11,47 @@

Container: <%-NAME%>

-
- Show Data Status - : - - -
- Show Auto Scores - - Class: +
+
+
+ Status Shows +
+ +
+
+ +
+
+ + : + +
+
+ + + + Class: +
+
-
From 269cd3db7f2cd639d8a3065f6a81d74073b67f7e Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Wed, 18 Oct 2023 09:59:46 +0100 Subject: [PATCH 03/10] LIMS-95: Change label to match others (#682) Co-authored-by: Mark Williams --- client/src/js/templates/shipment/containerplateimage.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/js/templates/shipment/containerplateimage.html b/client/src/js/templates/shipment/containerplateimage.html index 780ec2a49..028a8988d 100644 --- a/client/src/js/templates/shipment/containerplateimage.html +++ b/client/src/js/templates/shipment/containerplateimage.html @@ -12,9 +12,10 @@

Container: <%-NAME%>

-
+
+ Display +
- Status Shows