From 1343bffb31f4c1910346137a8d6fbc8ecdf70a32 Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:20:44 +0100 Subject: [PATCH 1/6] LIMS-1550: Add 'Mark Dispensing' button to plate well view (#869) * LIMS-1550: Add 'Mark Dispensing' button to plate well view * LIMS-1550: Dont throw an error if no subsamples exist * LIMS-1550: Use new BLSamplePosition table * LIMS-1550: Label positions as 'dispensing' * LIMS-1550: Change plus signs to crosses when the button text changes --------- Co-authored-by: Mark Williams --- api/src/Database/Type/MySQL.php | 1 + api/src/Page/Sample.php | 40 +++++++++++-- client/src/js/models/sample.js | 20 ++++++- .../js/modules/imaging/views/imageviewer.js | 36 +++++++++++- .../modules/shipment/views/containerplate.js | 57 +++++++++++++++++++ .../shipment/containerplateimage.html | 2 + 6 files changed, 149 insertions(+), 7 deletions(-) diff --git a/api/src/Database/Type/MySQL.php b/api/src/Database/Type/MySQL.php index 32740e89b..fda8c2ecb 100644 --- a/api/src/Database/Type/MySQL.php +++ b/api/src/Database/Type/MySQL.php @@ -75,6 +75,7 @@ class MySQL extends DatabaseParent { 'DewarReport', 'CourierTermsAccepted', + 'BLSamplePosition', 'BLSubSample', 'PDB', 'Protein_has_PDB', diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index 7a79235f0..d28889a3a 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -93,8 +93,8 @@ class Sample extends Page 'scid' => '\d+-\d+', 'BLSAMPLEID' => '\d+', - 'X' => '\d+(.\d+)?', - 'Y' => '\d+(.\d+)?', + 'X' => '\d*(.\d+)?', + 'Y' => '\d*(.\d+)?', 'Z' => '\d+(.\d+)?', 'X2' => '\d+(.\d+)?', 'Y2' => '\d+(.\d+)?', @@ -799,6 +799,9 @@ private function get_sub_samples_query($where, $first_inner_select_where = '', $ po2.posx as x2, po2.posy as y2, po2.posz as z2, + bsp.posx as dispensex, + bsp.posy as dispensey, + bsp.posz as dispensez, IF(cqs.containerqueuesampleid IS NOT NULL AND cqs.containerqueueid IS NULL, 1, 0) as readyforqueue, cq.containerqueueid, count(distinct IF(dc.overlap != 0, @@ -824,7 +827,7 @@ private function get_sub_samples_query($where, $first_inner_select_where = '', $ INNER JOIN shipping sh ON sh.shippingid = d.shippingid INNER JOIN proposal p ON p.proposalid = sh.proposalid - + LEFT OUTER JOIN blsampleposition bsp ON bsp.blsampleid = s.blsampleid LEFT OUTER JOIN containerqueuesample cqs ON cqs.blsubsampleid = ss.blsubsampleid LEFT OUTER JOIN containerqueue cq ON cqs.containerqueueid = cq.containerqueueid AND cq.completedtimestamp IS NULL @@ -1903,11 +1906,12 @@ function _update_sample() if (!$this->has_arg('sid')) $this->_error('No sampleid specified'); - $samp = $this->db->pq("SELECT b.blsampleid, pr.proteinid,cr.crystalid,dp.diffractionplanid + $samp = $this->db->pq("SELECT b.blsampleid, pr.proteinid,cr.crystalid,dp.diffractionplanid,bsp.blsamplepositionid FROM blsample b INNER JOIN crystal cr ON cr.crystalid = b.crystalid INNER JOIN protein pr ON pr.proteinid = cr.proteinid LEFT OUTER JOIN diffractionplan dp on dp.diffractionplanid = b.diffractionplanid + LEFT OUTER JOIN blsampleposition bsp ON bsp.blsampleid = b.blsampleid AND bsp.positiontype='dispensing' WHERE pr.proposalid = :1 AND b.blsampleid = :2", array($this->proposalid, $this->arg('sid'))); if (!sizeof($samp)) @@ -1980,6 +1984,34 @@ function _update_sample() } } } + + if ($this->has_arg('X') && $this->has_arg('Y')) { + $z = $this->has_arg('Z') ? $this->arg('Z') : null; + $pid = $samp['BLSAMPLEPOSITIONID']; + if ($this->arg('X') == '' && $this->arg('Y') == '') { + if (!empty($pid)) { + $this->db->pq( + "UPDATE blsampleposition SET posx=null, posy=null, posz=null, recordtimestamp=CURRENT_TIMESTAMP WHERE blsamplepositionid=:1", + array($pid) + ); + } + } else { + if (empty($pid)) { + $this->db->pq( + "INSERT INTO blsampleposition (blsampleid, posx, posy, posz, positiontype, recordtimestamp) + VALUES (:1, :2, :3, :4, 'dispensing', CURRENT_TIMESTAMP) RETURNING blsamplepositionid INTO :id", + array($this->arg('sid'), $this->arg('X'), $this->arg('Y'), $z) + ); + $pid = $this->db->id(); + } else { + $this->db->pq( + "UPDATE blsampleposition SET posx=:1, posy=:2, posz=:3, recordtimestamp=CURRENT_TIMESTAMP WHERE blsamplepositionid=:4", + array($this->arg('X'), $this->arg('Y'), $z, $pid) + ); + } + } + $this->_output(array('BLSAMPLEPOSITIONID' => $pid)); + } } diff --git a/client/src/js/models/sample.js b/client/src/js/models/sample.js index e0c5f1309..e0267e61c 100644 --- a/client/src/js/models/sample.js +++ b/client/src/js/models/sample.js @@ -18,11 +18,17 @@ define(['backbone', 'collections/components', this.listenTo(this, 'change:RADIATIONSENSITIVITY', this.updateRadSen) this.updateRadSen() + this.listenTo(this, 'change:X', this.updatePosition) + this.listenTo(this, 'change', this.updateHasData) this.updateHasData() }, + updatePosition: function() { + this.save(this.changedAttributes(), { patch: true }) + }, + updateHasData: function() { var hasData = this.get('DC') > 0 || this.get('GR') > 0 || this.get('SC') > 0 if (hasData !== this.get('HASDATA')) this.set('HASDATA', hasData) @@ -98,7 +104,10 @@ define(['backbone', 'collections/components', VOLUME: '', INITIALSAMPLEGROUP: '', COMPONENTIDS: [], - COMPONENTAMOUNTS: [] + COMPONENTAMOUNTS: [], + X: null, + Y: null, + Z: null, }, validation: { @@ -225,6 +234,15 @@ define(['backbone', 'collections/components', required: false, pattern: 'word' }, + X: { + required: false + }, + Y: { + required: false + }, + Z: { + required: false + }, COMPONENTAMOUNTS: function(from_ui, attr, all_values) { var values = all_values.components.pluck('ABUNDANCE') diff --git a/client/src/js/modules/imaging/views/imageviewer.js b/client/src/js/modules/imaging/views/imageviewer.js index 86a1272f1..218184c7a 100644 --- a/client/src/js/modules/imaging/views/imageviewer.js +++ b/client/src/js/modules/imaging/views/imageviewer.js @@ -2,6 +2,7 @@ define(['marionette', 'backbone', 'modules/imaging/collections/inspectionimagescores', + 'models/sample', 'models/subsample', 'collections/subsamples', @@ -18,7 +19,7 @@ define(['marionette', 'backbone-validation', ], function(Marionette, Backbone, - ImageScores, Subsample, Subsamples, ImageHistory, InspectionImage, Attachments, + ImageScores, Sample, Subsample, Subsamples, ImageHistory, InspectionImage, Attachments, Editable, utils, XHRImage, HeatMap, template) { @@ -175,6 +176,10 @@ define(['marionette', }, }) } + + if (this.add_dispensing) { + this.editDispensing(x, y) + } }, setAddSubsample: function(state) { @@ -184,7 +189,21 @@ define(['marionette', setAddSubsampleRegion: function(state) { this.add_region = state }, - + + setAddDispensing: function(state) { + this.add_dispensing = state + }, + + deleteDispensing: function() { + this.editDispensing('', '') + }, + + editDispensing: function(x, y) { + var s = new Sample({ BLSAMPLEID: this.model.get('BLSAMPLEID') }) + s.set({ X: x, Y: y }) + this.trigger('finishdispensing') + this.subsamples.fetch() + }, remSubsample: function() { this.draw() @@ -204,6 +223,7 @@ define(['marionette', initialize: function(options) { this.add_object = false this.add_region = false + this.add_dispensing = false this.plotObjects = _.debounce(this.plotObjects, 200) this.drawDebounce = _.debounce(this.draw, 10) @@ -1006,6 +1026,18 @@ define(['marionette', this.ctx.fillStyle = options.o.get('isSelected') ? 'turquoise' : options.o.get('SOURCE') === 'auto' ? 'darkblue' : 'red' this.ctx.font = parseInt(14*m)+'px Arial' this.ctx.fillText(parseInt(options.o.get('RID'))+1,x-(m*15), y-(m*6)) + + if (options.o.get('DISPENSEX') && options.o.get('DISPENSEY')) { + var disx = parseInt(options.o.get('DISPENSEX')) + var disy = parseInt(options.o.get('DISPENSEY')) + this.ctx.strokeStyle = 'white' + this.ctx.beginPath() + this.ctx.arc(disx, disy, 50, 0, 2*Math.PI) + this.ctx.stroke() + this.ctx.fillStyle = 'white' + this.ctx.fillText('D',disx-5*m, disy+5*m) + this.ctx.closePath() + } }, drawBeam: function(o) { diff --git a/client/src/js/modules/shipment/views/containerplate.js b/client/src/js/modules/shipment/views/containerplate.js index fe5cbdb2b..5a5de4ec5 100644 --- a/client/src/js/modules/shipment/views/containerplate.js +++ b/client/src/js/modules/shipment/views/containerplate.js @@ -242,6 +242,8 @@ define(['marionette', add: '.add_image', ads: 'a.add_point', adr: 'a.add_region', + addis: 'a.add_dispensing', + deldis: 'a.del_dispensing', drop: '.dropimage', prog: '.progress', @@ -275,6 +277,8 @@ define(['marionette', 'change @ui.ins': 'selectInspection', 'click @ui.ads': 'setAddSubsamplePoint', 'click @ui.adr': 'setAddSubsampleRegion', + 'click @ui.addis': 'setAddDispensing', + 'click @ui.deldis': 'deleteDispensing', 'click a.add_inspection': 'showAddInspection', 'click a.view_sched': 'showViewSchedule', 'click @ui.play': 'playInspection', @@ -483,16 +487,24 @@ define(['marionette', this.ui.ads.removeClass('button-highlight') this.image.setAddSubsample(false) this.ui.ads.find('span').text('Mark Point') + this.ui.ads.find('i').removeClass('fa-times').addClass('fa-plus') } else { this.ui.ads.addClass('button-highlight') this.image.setAddSubsample(true) this.ui.ads.find('span').text('Finish') + this.ui.ads.find('i').removeClass('fa-plus').addClass('fa-times') } this.ui.adr.removeClass('button-highlight') + this.ui.addis.removeClass('button-highlight') this.image.setAddSubsampleRegion(false) + this.image.setAddDispensing(false) this.ui.adr.find('span').text('Mark Region') + this.ui.adr.find('i').removeClass('fa-times').addClass('fa-plus') + this.ui.addis.find('span').text('Mark Dispensing') + this.ui.addis.find('i').removeClass('fa-times').addClass('fa-plus') + this.ui.deldis.hide() }, @@ -503,16 +515,59 @@ define(['marionette', this.ui.adr.removeClass('button-highlight') this.image.setAddSubsampleRegion(false) this.ui.adr.find('span').text('Mark Region') + this.ui.adr.find('i').removeClass('fa-times').addClass('fa-plus') } else { this.ui.adr.addClass('button-highlight') this.image.setAddSubsampleRegion(true) this.ui.adr.find('span').text('Finish') + this.ui.adr.find('i').removeClass('fa-plus').addClass('fa-times') } this.ui.ads.removeClass('button-highlight') + this.ui.addis.removeClass('button-highlight') this.image.setAddSubsample(false) + this.image.setAddDispensing(false) this.ui.ads.find('span').text('Mark Point') + this.ui.ads.find('i').removeClass('fa-times').addClass('fa-plus') + this.ui.addis.find('span').text('Mark Dispensing') + this.ui.addis.find('i').removeClass('fa-times').addClass('fa-plus') + this.ui.deldis.hide() + }, + + setAddDispensing: function(e) { + if (e) e.preventDefault() + + if (this.ui.addis.hasClass('button-highlight')) { + this.ui.addis.removeClass('button-highlight') + this.image.setAddDispensing(false) + this.ui.addis.find('span').text('Mark Dispensing') + this.ui.addis.find('i').removeClass('fa-times').addClass('fa-plus') + this.ui.deldis.hide() + + } else { + this.ui.addis.addClass('button-highlight') + this.image.setAddDispensing(true) + this.ui.addis.find('span').text('Cancel') + this.ui.addis.find('i').removeClass('fa-plus').addClass('fa-times') + if (this.subsamples.length && this.subsamples.findWhere({ BLSAMPLEID: this.getSample() }).get('DISPENSEX')) { + this.ui.deldis.show() + } + } + + this.ui.ads.removeClass('button-highlight') + this.ui.adr.removeClass('button-highlight') + this.image.setAddSubsample(false) + this.image.setAddSubsampleRegion(false) + this.ui.ads.find('span').text('Mark Point') + this.ui.ads.find('i').removeClass('fa-times').addClass('fa-plus') + this.ui.adr.find('span').text('Mark Region') + this.ui.adr.find('i').removeClass('fa-times').addClass('fa-plus') + }, + + deleteDispensing: function(e) { + e.preventDefault() + this.image.deleteDispensing() }, @@ -905,12 +960,14 @@ define(['marionette', this.listenTo(this.image, 'image:prev', this.prevImage, this) this.listenTo(this.image, 'image:first', this.firstImage, this) this.listenTo(this.image, 'image:last', this.lastImage, this) + this.listenTo(this.image, 'finishdispensing', this.setAddDispensing, this) if (this.getOption('params').iid) this.ui.ins.val(this.getOption('params').iid) this.selectInspection() this.ui.prog.hide() this.ui.prog.progressbar({ value: 0 }) + this.ui.deldis.hide() this.img.show(this.image) this.sten.show(new ImageHistoryView({ historyimages: this.startendimages, embed: true })) diff --git a/client/src/js/templates/shipment/containerplateimage.html b/client/src/js/templates/shipment/containerplateimage.html index 180d26d58..6650f16dd 100644 --- a/client/src/js/templates/shipment/containerplateimage.html +++ b/client/src/js/templates/shipment/containerplateimage.html @@ -89,6 +89,8 @@

Marked Sub Samples

Mark Point Mark Region + Mark Dispensing + Delete
From e522f01630972ed3d9761c3830161d45d649b424 Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:20:56 +0100 Subject: [PATCH 2/6] LIMS-167: Dont default to mx type for calendar/logistics views (#942) Co-authored-by: Mark Williams --- api/src/Page/Proposal.php | 6 +++++- api/src/Page/Shipment.php | 6 +++++- client/src/js/modules/calendar/views/calendar-view.vue | 2 +- .../modules/shipment/components/dewars-overview-wrapper.vue | 4 ++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/src/Page/Proposal.php b/api/src/Page/Proposal.php index ff765303b..13f8d3e93 100644 --- a/api/src/Page/Proposal.php +++ b/api/src/Page/Proposal.php @@ -389,7 +389,11 @@ function _get_visits($visit = null, $output = true) } if ($this->has_arg('ty')) { - $beamlines = $this->_get_beamlines_from_type($this->arg('ty')); + if ($this->arg('ty') == 'calendar') { + $beamlines = $this->_get_beamlines_from_type($this->ty); + } else { + $beamlines = $this->_get_beamlines_from_type($this->arg('ty')); + } if (!empty($beamlines)) { $bls = implode("', '", $beamlines); diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index 16f8676ac..49033c913 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -1677,7 +1677,11 @@ function _get_dewars() } if ($this->has_arg('ty')) { - $bls_tmp = $this->_get_beamlines_from_type($this->arg('ty')); + if ($this->arg('ty') == 'overview') { + $bls_tmp = $this->_get_beamlines_from_type($this->ty); + } else { + $bls_tmp = $this->_get_beamlines_from_type($this->arg('ty')); + } if (!empty($bls_tmp)) { $bls = implode("', '", $bls_tmp); $where .= " AND se.beamlinename IN ('$bls')"; diff --git a/client/src/js/modules/calendar/views/calendar-view.vue b/client/src/js/modules/calendar/views/calendar-view.vue index e215bd1a8..38dd85181 100644 --- a/client/src/js/modules/calendar/views/calendar-view.vue +++ b/client/src/js/modules/calendar/views/calendar-view.vue @@ -244,7 +244,7 @@ export default { all: 1, } - if (app.staff) queryParams.ty = this.proposalType + if (app.staff) queryParams.ty = 'calendar' if (this.selectedBeamline !== 'all') queryParams.bl = this.selectedBeamline const visitsCollection = new Visits(null, { diff --git a/client/src/js/modules/shipment/components/dewars-overview-wrapper.vue b/client/src/js/modules/shipment/components/dewars-overview-wrapper.vue index 7e8a9bf3e..685b3f4cb 100644 --- a/client/src/js/modules/shipment/components/dewars-overview-wrapper.vue +++ b/client/src/js/modules/shipment/components/dewars-overview-wrapper.vue @@ -54,7 +54,7 @@ export default { } }, proposalType : function() { - return this.$store.state.proposal.proposalType + return 'overview' } }, created: function() { @@ -95,4 +95,4 @@ export default { }, }, } - \ No newline at end of file + From 08dfc932ac0aa1bf3036449a406cfd6cac946d7a Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:21:09 +0100 Subject: [PATCH 3/6] LIMS-1716: Multicrystal processing only shows one DC per group (#938) Co-authored-by: Mark Williams --- api/src/Page/DC.php | 6 +++++- client/src/js/modules/mc/controller.js | 4 ++-- client/src/js/modules/mc/routes.js | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/api/src/Page/DC.php b/api/src/Page/DC.php index 100ac5f44..393a75c34 100644 --- a/api/src/Page/DC.php +++ b/api/src/Page/DC.php @@ -37,6 +37,7 @@ class DC extends Page 'PERSONID' => '\d+', 'AUTOPROCPROGRAMMESSAGEID' => '\d+', 'PROCESSINGJOBID' => '\d+', + 'expandgroups' => '\d', 'debug' => '\d', 'sgid' => '\d+' ); @@ -381,7 +382,7 @@ function _data_collections($single = null) } # Set Count field - if ($this->has_arg('dcg') || $this->has_arg('PROCESSINGJOBID') || $this->has_arg('sgid')) { + if ($this->has_arg('dcg') || $this->has_arg('PROCESSINGJOBID') || $this->has_arg('sgid') || $this->has_arg('expandgroups')) { $count_field = 'dc.datacollectionid'; } else { $count_field = 'distinct dc.datacollectiongroupid'; @@ -616,6 +617,9 @@ function _data_collections($single = null) IFNULL(max(dc.rotationaxis), 'Omega') as rotationaxis, dc.detector2theta"; $groupby = "GROUP BY dc.datacollectiongroupid"; + if ($this->has_arg('expandgroups')) { + $groupby = "GROUP BY dc.datacollectionid"; + } } // We don't want to remove duplicates, since if two counts are equal, one might go uncounted diff --git a/client/src/js/modules/mc/controller.js b/client/src/js/modules/mc/controller.js index b9675e9e9..689f0446f 100644 --- a/client/src/js/modules/mc/controller.js +++ b/client/src/js/modules/mc/controller.js @@ -11,7 +11,7 @@ define(['marionette', dcs: function(visit, page, search) { app.loading() app.cookie(visit.split('-')[0]) - var dcs = new DCs(null, { queryParams: { visit: visit, s: search, t: 'fc' } }) + var dcs = new DCs(null, { queryParams: { visit: visit, s: search, t: 'fc', expandgroups: 1 } }) dcs.setPageSize(app.mobile() ? 5 : 16) page = page ? parseInt(page) : 1 @@ -26,4 +26,4 @@ define(['marionette', } return controller -}) \ No newline at end of file +}) diff --git a/client/src/js/modules/mc/routes.js b/client/src/js/modules/mc/routes.js index ce6e68d1c..905e602f1 100644 --- a/client/src/js/modules/mc/routes.js +++ b/client/src/js/modules/mc/routes.js @@ -18,6 +18,7 @@ const routes = [ visit: route.params.visit, s: route.params.search, t: 'fc', + expandgroups: 1, }, state: { currentPage: route.params.page ? parseInt(route.params.page) : 1} }), From 0e2dc3d204cd5ac6d89dcfd011e0d05ad9bf98cc Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:21:22 +0100 Subject: [PATCH 4/6] LIMS-1718: Display cluster info on multiplex jobs (#937) Co-authored-by: Mark Williams --- api/src/Page/Processing.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/src/Page/Processing.php b/api/src/Page/Processing.php index 24753f690..7a8db3976 100644 --- a/api/src/Page/Processing.php +++ b/api/src/Page/Processing.php @@ -978,6 +978,7 @@ private function _autoprocessing_query_builder($where, $group, $order = '') { (SELECT COUNT(api1.autoprocintegrationid) FROM autoprocintegration api1 WHERE api1.autoprocprogramid = app.autoprocprogramid) as imagesweepcount, app.processingstatus, app.processingmessage, + app.processingenvironment, count(distinct pjis.datacollectionid) as dccount, max(pjis.processingjobid) as processingjobid, (SELECT IFNULL(blsg.name, bls.name) FROM processingjobparameter pjp @@ -1059,6 +1060,9 @@ private function _format_auto_processing_result($table_rows, $messages_result) { if ($row['GROUPNAME']) { $value .= ' ('.$row['GROUPNAME'].')'; } + if ($row['PROCESSINGENVIRONMENT'] && strpos($row['PROCESSINGENVIRONMENT'], 'cluster=') !== false) { + $value .= ' ('.$row['PROCESSINGENVIRONMENT'].')'; + } } From fa086343a5c3c18f55004779eb2ac1528b32f824 Mon Sep 17 00:00:00 2001 From: Mark W <24956497+ndg63276@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:21:30 +0100 Subject: [PATCH 5/6] LIMS-1752: Create view for Ligand Fit pipeline (#943) Co-authored-by: Mark Williams --- api/src/Downstream/Type/LigandFit.php | 73 +++++++++++++++++++ client/src/css/partials/_content.scss | 5 ++ client/src/js/modules/dc/views/downstream.js | 5 +- .../js/modules/dc/views/downstreamwrapper.js | 17 +++-- client/src/js/modules/dc/views/ligandfit.js | 56 ++++++++++++++ client/src/js/templates/dc/dc_ligandfit.html | 27 +++++++ 6 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 api/src/Downstream/Type/LigandFit.php create mode 100644 client/src/js/modules/dc/views/ligandfit.js create mode 100644 client/src/js/templates/dc/dc_ligandfit.html diff --git a/api/src/Downstream/Type/LigandFit.php b/api/src/Downstream/Type/LigandFit.php new file mode 100644 index 000000000..068cb48f4 --- /dev/null +++ b/api/src/Downstream/Type/LigandFit.php @@ -0,0 +1,73 @@ +autoprocprogramid); + $filepath = $this->db->pq( + "SELECT app.filePath, app.filename from autoprocprogramattachment app where autoprocprogramid = :1 and filename like '%.json' ", + $appid + ); + return $filepath; + } + + function _get_ligandfit_results_png() { + $appid = array($this->autoprocprogramid); + $filepath = $this->db->pq( + "SELECT app.filepath, app.filename from autoprocprogramattachment app where autoprocprogramid = :1 and filename like '%.png' ", + $appid + ); + return $filepath; + } + + function _get_model_appaid() { + $appid = array($this->autoprocprogramid); + $filepath = $this->db->pq( + "SELECT app.autoprocprogramattachmentid from autoprocprogramattachment app where autoprocprogramid = :1 and filename like '%.html' ", + $appid + ); + if (sizeof($filepath)) { + return $filepath[0]["AUTOPROCPROGRAMATTACHMENTID"]; + } else { + return; + } + } + + function results() { + $json_filepath = $this->_get_ligandfit_results_json(); + if (sizeof($json_filepath)) { + $json_path = $json_filepath[0]["FILEPATH"] . "/" . $json_filepath[0]["FILENAME"] ; + $json_data = file_get_contents($json_path); + } else { + $json_data = "[]"; + } + $dat = array(); + $appaid = $this->_get_model_appaid(); + $dat['BLOBS'] = $appaid ? 1 : 0; + $dat['MODEL_APPAID'] = $appaid; + $dat['SOLUTIONS'] = json_decode($json_data); + $dat['PARENTAUTOPROCPROGRAM'] = $this->process['PROCESSINGCOMMENTS']; + + $results = new DownstreamResult($this); + $results->data = $dat; + + return $results; + } + + function images($n = 0) { + $png = $this->_get_ligandfit_results_png(); + if (sizeof($png)) { + return $png[0]["FILEPATH"] . "/" . $png[0]["FILENAME"]; + } else { + return; + } + } +} diff --git a/client/src/css/partials/_content.scss b/client/src/css/partials/_content.scss index 6a998ad1c..d7165926e 100644 --- a/client/src/css/partials/_content.scss +++ b/client/src/css/partials/_content.scss @@ -1082,6 +1082,11 @@ li:last-child .visit_users { width: 40.5%; height: 300px; + &.map { + width: 30%; + height: auto; + } + &.image-third { width: 32.4%; diff --git a/client/src/js/modules/dc/views/downstream.js b/client/src/js/modules/dc/views/downstream.js index bdb91c8e0..b4eeb5f6d 100644 --- a/client/src/js/modules/dc/views/downstream.js +++ b/client/src/js/modules/dc/views/downstream.js @@ -8,11 +8,12 @@ define(['backbone', 'marionette', 'modules/dc/views/mrbump', 'modules/dc/views/bigep', 'modules/dc/views/shelxt', + 'modules/dc/views/ligandfit', 'templates/dc/downstreamerror.html' ], function(Backbone, Marionette, TabView, DownStreams, DownstreamWrapper, TableView, - FastEP, DIMPLE, MrBUMP, BigEP, Shelxt, downstreamerror) { + FastEP, DIMPLE, MrBUMP, BigEP, Shelxt, LigandFit, downstreamerror) { var dcPurgedProcessedData = "0"; // dataCollection.PURGEDPROCESSEDDATA via options from DC.js @@ -65,6 +66,7 @@ define(['backbone', 'marionette', 'Crank2': BigEP, 'AutoSHARP': BigEP, 'Shelxt': Shelxt, + 'LigandFit': LigandFit, } if (model.get('PROCESS').PROCESSINGSTATUS != 1) { @@ -125,6 +127,7 @@ define(['backbone', 'marionette', holderWidth: this.getOption('holderWidth'), downstreams: this.getOption('downstreams'), DCID: this.getOption('id'), + mapButton: this.getOption('mapButton'), } }, diff --git a/client/src/js/modules/dc/views/downstreamwrapper.js b/client/src/js/modules/dc/views/downstreamwrapper.js index 15b5dfb57..23b5462cb 100644 --- a/client/src/js/modules/dc/views/downstreamwrapper.js +++ b/client/src/js/modules/dc/views/downstreamwrapper.js @@ -65,17 +65,12 @@ define(['backbone', 'marionette', this.messages.show(new APMessagesView({ messages: new Backbone.Collection(this.model.get('MESSAGES')), embed: true })) - this.wrappedView = new (this.getOption('childView'))({ - model: this.model, - templateHelpers: this.getOption('templateHelpers'), - holderWidth: this.getOption('holderWidth'), - }) - this.wrapper.show(this.wrappedView) if (!this.model.get('AUTOMATIC')) { this.ui.links.html(' ') } + var mapButton = null if (this.getOption('links')) { var links = [ ' Map / Model Viewer', @@ -88,7 +83,17 @@ define(['backbone', 'marionette', } this.ui.links.append(links.join(' ')) + mapButton = this.ui.links.find('a.view') } + + this.wrappedView = new (this.getOption('childView'))({ + model: this.model, + templateHelpers: this.getOption('templateHelpers'), + holderWidth: this.getOption('holderWidth'), + mapButton: mapButton, + }) + this.wrapper.show(this.wrappedView) + }, onDomRefresh: function() { diff --git a/client/src/js/modules/dc/views/ligandfit.js b/client/src/js/modules/dc/views/ligandfit.js new file mode 100644 index 000000000..2e67944d8 --- /dev/null +++ b/client/src/js/modules/dc/views/ligandfit.js @@ -0,0 +1,56 @@ +define([ + 'marionette', 'templates/dc/dc_ligandfit.html', 'utils', 'utils/xhrimage' +], function(Marionette, template, utils, XHRImage) { + + return Marionette.ItemView.extend({ + template: template, + className: 'clearfix', + + ui: { + rstats: '.rstats', + blob: '.map img', + }, + + events: { + 'click a.mapdl': utils.signHandler, + }, + + setMapButton: function(mapButtonElement) { + if (mapButtonElement) { + this.ui.mapbtn = mapButtonElement + this.ui.mapbtn.attr('href', app.apiurl+'/download/ap/attachments/'+this.model.get('MODEL_APPAID')+'/dl/2') + this.ui.mapbtn.on('click', utils.signHandler) + } + }, + + hideMapButton: function(mapButtonElement) { + if (mapButtonElement) { + this.ui.mapbtn = mapButtonElement + this.ui.mapbtn.hide() + } + }, + + showBlob: function() { + this.ui.blob.attr('src', this.blob.src).show() + }, + + onDomRefresh: function() { + this.ui.blob.hide() + if (this.model.get('BLOBS') > 0) { + this.blob = new XHRImage() + this.blob.onload = this.showBlob.bind(this) + this.blob.load(app.apiurl+'/processing/downstream/images/'+this.model.get('AID')) + } + + if (this.model.get('MODEL_APPAID')) { + this.setMapButton(this.options.mapButton) + } else { + this.hideMapButton(this.options.mapButton) + } + + if (!app.mobile()) { + this.ui.rstats.width('68%') + } + }, + }) +}) diff --git a/client/src/js/templates/dc/dc_ligandfit.html b/client/src/js/templates/dc/dc_ligandfit.html new file mode 100644 index 000000000..347aa7aa2 --- /dev/null +++ b/client/src/js/templates/dc/dc_ligandfit.html @@ -0,0 +1,27 @@ +
+
+ + Click to view model + +
+ + + <% if (SOLUTIONS.length) { %> + <% _.each(SOLUTIONS, function(r, i) { %> + + <% _.each(r, function(j) { %> + + <% }) %> + + <% }) %> + <% } else { %> + + + + + + + <% } %> +
<%-j%>
Solutions
No Solutions Found
+ +
From b7d32c147b41a69d6ee7c28d7099be7496800848 Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Mon, 30 Jun 2025 15:15:34 +0100 Subject: [PATCH 6/6] LIMS-1798: Only show the latest dispensing position --- api/src/Page/Sample.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api/src/Page/Sample.php b/api/src/Page/Sample.php index d28889a3a..46254e41f 100644 --- a/api/src/Page/Sample.php +++ b/api/src/Page/Sample.php @@ -827,7 +827,11 @@ private function get_sub_samples_query($where, $first_inner_select_where = '', $ INNER JOIN shipping sh ON sh.shippingid = d.shippingid INNER JOIN proposal p ON p.proposalid = sh.proposalid - LEFT OUTER JOIN blsampleposition bsp ON bsp.blsampleid = s.blsampleid + LEFT OUTER JOIN + (SELECT * FROM blsampleposition bsp1 WHERE bsp1.blSamplePositionId = + (SELECT MAX(blSamplePositionId) FROM BLSamplePosition bsp2 WHERE bsp2.blSampleId = bsp1.blSampleId AND bsp2.positiontype='dispensing') + ) bsp ON bsp.blSampleId = s.blSampleId + LEFT OUTER JOIN containerqueuesample cqs ON cqs.blsubsampleid = ss.blsubsampleid LEFT OUTER JOIN containerqueue cq ON cqs.containerqueueid = cq.containerqueueid AND cq.completedtimestamp IS NULL @@ -1911,7 +1915,10 @@ function _update_sample() INNER JOIN crystal cr ON cr.crystalid = b.crystalid INNER JOIN protein pr ON pr.proteinid = cr.proteinid LEFT OUTER JOIN diffractionplan dp on dp.diffractionplanid = b.diffractionplanid - LEFT OUTER JOIN blsampleposition bsp ON bsp.blsampleid = b.blsampleid AND bsp.positiontype='dispensing' + LEFT OUTER JOIN + (SELECT * FROM BLSamplePosition bsp1 WHERE bsp1.blSamplePositionId = + (SELECT MAX(blSamplePositionId) FROM BLSamplePosition bsp2 WHERE bsp2.blSampleId = bsp1.blSampleId AND bsp2.positiontype='dispensing') + ) bsp ON bsp.blSampleId = b.blSampleId WHERE pr.proposalid = :1 AND b.blsampleid = :2", array($this->proposalid, $this->arg('sid'))); if (!sizeof($samp))