Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 53 additions & 22 deletions api/src/Page/Sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -1966,15 +1966,19 @@ function _ligands()
array_push($args, $this->arg('lid'));
}

$tot = $this->db->pq("SELECT count(distinct l.ligandid) as tot FROM ligand l INNER JOIN proposal p ON p.proposalid = l.proposalid WHERE $where", $args);
$tot = intval($tot[0]['TOT']);

if ($this->has_arg('s')) {
$st = sizeof($args) + 1;
$where .= " AND l.name LIKE CONCAT('%',:" . $st . ", '%')";
array_push($args, $this->arg('s'));
$where .= " AND (
l.name LIKE CONCAT('%',:" . $st . ", '%')
OR l.libraryname LIKE CONCAT('%',:" . ($st + 1) . ", '%')
OR l.librarybatchnumber LIKE CONCAT('%',:" . ($st + 2) . ", '%')
OR l.platebarcode LIKE CONCAT('%',:" . ($st + 3) . ", '%')
)";
array_push($args, $this->arg('s'), $this->arg('s'), $this->arg('s'), $this->arg('s'));
}

$tot = $this->db->pq("SELECT count(distinct l.ligandid) as tot FROM ligand l WHERE $where", $args);
$tot = intval($tot[0]['TOT']);

$start = 0;
$pp = $this->has_arg('per_page') ? $this->arg('per_page') : 15;
Expand All @@ -1990,12 +1994,18 @@ function _ligands()
array_push($args, $end);

$order = 'l.ligandid DESC';

$group = 'l.ligandid';

if ($this->has_arg('sort_by')) {
$cols = array(
'NAME' => 'l.name',
'SMILES' => 'l.smiles',
'LIBRARYNAME' => 'l.libraryname',
'LIBRARYBATCHNUMBER' => 'l.librarybatchnumber',
'PLATEBARCODE' => 'l.platebarcode',
'SOURCEWELL' => 'l.sourcewell',
'SCOUNT' => 'scount',
'DCOUNT' => 'dcount',
);
$dir = $this->has_arg('order') ? ($this->arg('order') == 'asc' ? 'ASC' : 'DESC') : 'ASC';
if (array_key_exists($this->arg('sort_by'), $cols))
Expand All @@ -2006,7 +2016,6 @@ function _ligands()
COUNT(DISTINCT dc.datacollectionid) AS dcount,
COUNT(DISTINCT b.blsampleid) AS scount
FROM ligand l
INNER JOIN proposal p ON p.proposalid = l.proposalid
LEFT OUTER JOIN ligand_has_pdb lhp ON lhp.ligandid = l.ligandid
LEFT OUTER JOIN blsample_has_ligand bhl ON bhl.ligandid = l.ligandid
LEFT OUTER JOIN blsample b ON b.blsampleid = bhl.blsampleid
Expand Down Expand Up @@ -2081,29 +2090,51 @@ function _add_ligand()
{
if (!$this->has_arg('prop'))
$this->_error('No proposal specified');
if (!$this->has_arg('NAME'))
$this->_error('No ligand name');

$smiles = $this->has_arg('SMILES') ? $this->arg('SMILES') : '';
$ligs = array();
$lids = array();

$libname = $this->has_arg('LIBRARYNAME') ? $this->arg('LIBRARYNAME') : null;
$libbatch = $this->has_arg('LIBRARYBATCHNUMBER') ? $this->arg('LIBRARYBATCHNUMBER') : null;
$barcode = $this->has_arg('PLATEBARCODE') ? $this->arg('PLATEBARCODE') : null;
$well = $this->has_arg('SOURCEWELL') ? $this->arg('SOURCEWELL') : null;

$chk = $this->db->pq("SELECT name FROM ligand
WHERE proposalid=:1 AND name=:2", array($this->proposalid, $this->arg('NAME')));
if (sizeof($chk))
$this->_error('That ligand name already exists in this proposal');
$json = $this->request['json'] ?? null;
if ($json && is_array($json)) {

$this->db->pq(
'INSERT INTO ligand (proposalid,name,smiles,libraryname,librarybatchnumber,platebarcode,sourcewell)
VALUES (:1,:2,:3,:4,:5,:6,:7) RETURNING ligandid INTO :id',
array($this->proposalid, $this->arg('NAME'), $smiles, $libname, $libbatch, $barcode, $well)
);
foreach ($json as $row) {
if (isset($row->NAME)) {
$name = $row->NAME;
$smiles = isset($row->SMILES) ? $row->SMILES : null;
$well = isset($row->SOURCEWELL) ? $row->SOURCEWELL : null;
array_push($ligs, array($this->proposalid, $name, $smiles, $libname, $libbatch, $barcode, $well));
}
}
} else {
if (!$this->has_arg('NAME'))
$this->_error('No ligand name');

$smiles = $this->has_arg('SMILES') ? $this->arg('SMILES') : null;
$well = $this->has_arg('SOURCEWELL') ? $this->arg('SOURCEWELL') : null;

$lid = $this->db->id();
array_push($ligs, array($this->proposalid, $this->arg('NAME'), $smiles, $libname, $libbatch, $barcode, $well));

$chk = $this->db->pq("SELECT name FROM ligand
WHERE proposalid=:1 AND name=:2", array($this->proposalid, $this->arg('NAME')));
if (sizeof($chk))
$this->_error('That ligand name already exists in this proposal');
}

foreach ($ligs as $lig) {
$this->db->pq(
'INSERT INTO ligand (proposalid,name,smiles,libraryname,librarybatchnumber,platebarcode,sourcewell)
VALUES (:1,:2,:3,:4,:5,:6,:7) RETURNING ligandid INTO :id',
$lig
);

array_push($lids, $this->db->id());
}

$this->_output(array('LIGANDID' => $lid));
$this->_output(array('LIGANDIDS' => $lids));
}

# ------------------------------------------------------------------------
Expand Down
4 changes: 3 additions & 1 deletion client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"jquery.flot.tooltip": "^0.9.0",
"lodash-es": "^4.17.21",
"markdown": "^0.5.0",
"papaparse": "^5.4.1",
"plotly.js": "^1.52.2",
"portal-vue": "2.1.7",
"promise": "^8.0.3",
Expand Down
4 changes: 4 additions & 0 deletions client/src/js/modules/samples/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ app.addInitializer(function() {
app.navigate('/ligands/lid/'+lid)
})

app.on('ligands:viewall', function() {
app.navigate('/ligands')
})

app.on('phases:view', function(pid) {
app.navigate('/phases/pid/'+pid)
})
Expand Down
130 changes: 124 additions & 6 deletions client/src/js/modules/samples/views/ligandadd.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,142 @@
define(['marionette', 'views/form',
define(['marionette',
'papaparse',
'views/form',
'models/ligand',
'templates/samples/ligandadd.html'],
function(Marionette, TableView, Ligand, template) {


function(Marionette, Papa, TableView, Ligand, template) {

return FormView.extend({
template: template,

ui: {
fileRadio: 'input[name="fileupload"]',
single: '.single',
csv: '.csv',
},

events: {
'change @ui.fileRadio': 'toggleInputMode',
'change input[type="file"]': 'validateCSV'
},

onRender: function() {
this.toggleInputMode()
},

toggleInputMode: function() {
let isFileUpload = this.ui.fileRadio.filter(':checked').val() === 'true'

if (isFileUpload) {
this.ui.single.hide()
this.ui.csv.show()
this.model.validation.NAME.required = false
this.model.validation.LIBRARYNAME.required = true
this.model.validation.LIBRARYBATCHNUMBER.required = true
this.model.validation.PLATEBARCODE.required = true
} else {
this.ui.single.show()
this.ui.csv.hide()
this.model.validation.NAME.required = true
this.model.validation.LIBRARYNAME.required = false
this.model.validation.LIBRARYBATCHNUMBER.required = false
this.model.validation.PLATEBARCODE.required = false
}
},

validateCSV: function(e) {
const file = e.target.files[0]
if (!file) return
let seen = new Set()

Papa.parse(file, {
header: true,
skipEmptyLines: true,
transformHeader: function(h, i) {
if (i === 0) {
seen = new Set()
}
const header = h.trim().toUpperCase()
// Exact matches
if (header === 'NAME') {
seen.add('NAME')
return 'NAME'
}
if (header === 'SMILES') {
seen.add('SMILES')
return 'SMILES'
}
if (header === 'SOURCEWELL' || header === 'SOURCE WELL' || header === 'WELL') {
seen.add('SOURCEWELL')
return 'SOURCEWELL'
}
// Partial matches only return the target if that slot hasn't been taken.
if (header.includes('NAME') && !header.includes('SALT') && !seen.has('NAME')) {
seen.add('NAME')
return 'NAME'
}
if (header.startsWith('SMILE') && !seen.has('SMILES')) {
seen.add('SMILES')
return 'SMILES'
}
if (header.startsWith('WELL') && !header.includes('VOLUME') && !seen.has('SOURCEWELL')) {
seen.add('SOURCEWELL')
return 'SOURCEWELL'
}
return header
},
complete: (results) => {
let valid = true
if (results.data.length === 0) {
app.alert({ message: 'Invalid CSV: No data found' })
valid = false
} else if (results.errors.length > 0) {
app.alert({ message: 'Invalid CSV: '+results.errors[0].message })
valid = false
}
for (let i = 0; i < results.data.length; i++) {
let rowdata = results.data[i]
let rownum = i+2
if (!rowdata.NAME) {
app.alert({ message: 'Invalid CSV: row '+rownum+' has no NAME value' })
valid = false
}
if (!rowdata.SMILES) {
app.alert({ message: 'Invalid CSV: row '+rownum+' has no SMILES value' })
valid = false
}
if (!rowdata.SOURCEWELL) {
app.alert({ message: 'Invalid CSV: row '+rownum+' has no WELL value' })
valid = false
}
if (!valid) break
}
if (valid) {
this.model.set('json', results.data)
} else {
e.target.value = ''
}
}
})
},

createModel: function() {
this.model = new Ligand()
},

success: function(model, response, options) {
console.log('success from ligand add', this.model)
app.trigger('ligands:view', model.get('LIGANDID'))
let ligandid = model.get('LIGANDIDS')
if (ligandid.length === 1) {
app.trigger('ligands:view', ligandid[0])
} else {
app.message({ title: 'Ligands added successfully', message: ligandid.length+' ligands added successfully' })
app.trigger('ligands:viewall')
}
},

failure: function(model, xhr, options) {
console.log(arguments)
json = null
let json = null
if (xhr.responseText) {
try {
json = $.parseJSON(xhr.responseText)
Expand Down
23 changes: 18 additions & 5 deletions client/src/js/templates/samples/ligandadd.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,29 @@ <h1 data-testid="new-ligand-header">New Ligand</h1>

<div class="form">
<ul>
<input id="file" type="radio" name="fileupload" value="true" checked="checked">
<label for="file">CSV Upload for Multiple Ligands</label>
<br />
<input id="form" type="radio" name="fileupload" value="false">
<label for="form">Add a Single Ligand</label>
<br />

<li>
<li class="csv">
<span class="file">
<input type="file" name="csv_file" accept=".csv">
</span>
<p>CSV files must contain 3 comma separated columns with the following mandatory headers on the first row: Name,SMILES,Well. <button type="button" onclick="window.open('/assets/files/ligand_template.csv');return false">Download CSV Template</button></p>
</li>

<li class="single">
<label>Name
</label>
<span class="name">
<input data-testid="new-ligand-name" type="text" name="NAME" />
</span>
</li>

<li>
<li class="single">
<label>SMILES Code
</label>
<span class="SMILES"><input data-testid="new-ligand-smiles" type="text" name="SMILES" /></span>
Expand All @@ -38,13 +51,13 @@ <h1 data-testid="new-ligand-header">New Ligand</h1>
<span class="PLATEBARCODE"><input data-testid="new-ligand-plate-barcode" type="text" name="PLATEBARCODE" /></span>
</li>

<li>
<li class="single">
<label>Source Well
</label>
<span class="SOURCEWELL"><input data-testid="new-ligand-source-well" type="text" name="SOURCEWELL" /></span>
</li>

<li>
<li class="single">
<label>
PDB File
<span class="small">
Expand All @@ -56,7 +69,7 @@ <h1 data-testid="new-ligand-header">New Ligand</h1>
</li>
</ul>

<button name="submit" value="1" type="submit" class="button submit" data-testid="add-ligand">Add Ligand</button>
<button name="submit" value="1" type="submit" class="button submit" data-testid="add-ligand">Add Ligand(s)</button>
</div>

</form>
2 changes: 1 addition & 1 deletion client/src/js/templates/samples/ligandlist.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ <h1 data-testid="ligand-title"><%-title%>s</h1>
<p class="help">This page lists all <%-title.toLowerCase()%>s associated with the currently selected proposal.</p>

<div class="ra">
<a class="button" href="/<%-url%>s/add"><i class="fa fa-plus"></i> Add <%-title%></a>
<a class="button" href="/<%-url%>s/add"><i class="fa fa-plus"></i> Add <%-title%>(s)</a>
</div>

<div class="wrapper"></div>
Loading