Skip to content
Open
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
14 changes: 14 additions & 0 deletions api-src/org/labkey/api/targetedms/model/QCMetricConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class QCMetricConfiguration implements Comparable<QCMetricConfiguration>
private String _yAxisLabel;
private Double _upperBound;
private Double _lowerBound;
private String _annotationName;

public int getId()
{
Expand Down Expand Up @@ -164,6 +165,16 @@ public void setLowerBound(Double lowerBound)
_lowerBound = lowerBound;
}

public String getAnnotationName()
{
return _annotationName;
}

public void setAnnotationName(String annotationName)
{
_annotationName = annotationName;
}

public JSONObject toJSON(){
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", _id);
Expand Down Expand Up @@ -195,6 +206,9 @@ public JSONObject toJSON(){
if (_upperBound != null) {
jsonObject.put("upperBound", _upperBound);
}
if (_annotationName != null) {
jsonObject.put("annotationName", _annotationName);
}

return jsonObject;
}
Expand Down
3 changes: 2 additions & 1 deletion resources/queries/targetedms/qcMetricsConfig.sql
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ SELECT
qmc.MaxTimeValue,
qmc.TimeValueOption,
qmc.TraceName,
qmc.YAxisLabel
qmc.YAxisLabel,
qmc.AnnotationName
FROM
qcmetricconfiguration qmc
FULL JOIN qcenabledmetrics qem
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE targetedms.QCMetricConfiguration ADD COLUMN AnnotationName VARCHAR(200);
1 change: 1 addition & 0 deletions resources/schemas/targetedms.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,7 @@
<column columnName="TimeValueOption"/>
<column columnName="TraceName"/>
<column columnName="YAxisLabel"/>
<column columnName="AnnotationName"/>
</columns>
</table>
<table tableName="QCMetricExclusion" tableDbType="TABLE">
Expand Down
37 changes: 30 additions & 7 deletions resources/views/configureQCMetric.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@
});

qcMetricsTable += '</table><br>' +
'<button type="button" class="labkey-button primary" id="saveButton" style="margin-right: 20px;">Save</button>' +
'<button type="button" class="labkey-button" id="cancelButton" style="margin-right: 20px;">Cancel</button>' +
'<button type="button" class="labkey-button" id="createNewCustomMetricButton" style="margin-right: 20px;">Add New Custom Metric</button>' +
'<button type="button" class="labkey-button" id="createNewTraceMetricButton" style="margin-right: 20px;">Add New Trace Metric</button>' +
'<div style="display:flex;flex-wrap:wrap;gap:8px;">' +
'<button type="button" class="labkey-button primary" id="saveButton">Save</button>' +
'<button type="button" class="labkey-button" id="cancelButton">Cancel</button>' +
'<button type="button" class="labkey-button" id="createNewCustomMetricButton">Add New Custom Metric</button>' +
'<button type="button" class="labkey-button" id="createNewTraceMetricButton">Add New Trace Metric</button>' +
'<button type="button" class="labkey-button" id="createNewAnnotationMetricButton">Add Annotation-Backed Metric</button>' +
'<button type="button" class="labkey-button" id="clearCacheButton">Clear Cached Metric Values</button>' +
'</div>' +
'</form><br>Edits to queries backing existing custom metrics require a manual cache clearing to display the updated results.</p>';

jQuery('#qcMetricsTable').html(qcMetricsTable);
Expand All @@ -80,7 +83,10 @@
LABKEY.internal.ConfigureQCMetrics.addNewMetric('custom');
});
jQuery('#createNewTraceMetricButton').click(function() {
LABKEY.internal.ConfigureQCMetrics.addNewMetric('trace')
LABKEY.internal.ConfigureQCMetrics.addNewMetric('trace');
});
jQuery('#createNewAnnotationMetricButton').click(function() {
LABKEY.internal.ConfigureQCMetrics.addNewMetric('annotation');
});
jQuery('#clearCacheButton').click(function() {
jQuery('#qcMetricsError').text('Clearing cached metrics...');
Expand Down Expand Up @@ -169,7 +175,10 @@

const op = 'update';
if (clickedQcMetricConfig.TraceName) {
LABKEY.internal.ConfigureQCMetrics.showTraceMetricWindow(op, clickedQcMetricConfig)
LABKEY.internal.ConfigureQCMetrics.showTraceMetricWindow(op, clickedQcMetricConfig);
}
else if (clickedQcMetricConfig.AnnotationName) {
LABKEY.internal.ConfigureQCMetrics.showAnnotationMetricWindow(op, clickedQcMetricConfig);
}
else {
LABKEY.internal.ConfigureQCMetrics.showCustomMetricWindow(op, clickedQcMetricConfig);
Expand Down Expand Up @@ -242,13 +251,27 @@
});
},

showAnnotationMetricWindow: function (op, clickedMetric) {
const windowConfig = {
parent: this,
operation: op
};
if (clickedMetric) {
windowConfig.metric = clickedMetric;
}
Panorama.Window.AddAnnotationMetricWindow.show(windowConfig);
},

addNewMetric: function (metricType) {
const op = 'insert';
if (metricType === 'custom') {
this.showCustomMetricWindow(op);
}
else if (metricType === 'trace') {
this.showTraceMetricWindow(op)
this.showTraceMetricWindow(op);
}
else if (metricType === 'annotation') {
this.showAnnotationMetricWindow(op);
}
},

Expand Down
1 change: 1 addition & 0 deletions resources/views/configureQCMetric.view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<dependency path="Ext4"/>
<dependency path="PanoramaPremium/window/AddNewMetricWindow.js"/>
<dependency path="PanoramaPremium/window/AddNewTraceMetricWindow.js"/>
<dependency path="PanoramaPremium/window/AddNewAnnotationMetricWindow.js"/>
<dependency path="TargetedMS/js/misc.js"/>
</dependencies>
</view>
226 changes: 226 additions & 0 deletions resources/web/PanoramaPremium/window/AddNewAnnotationMetricWindow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
* Copyright (c) 2025 LabKey Corporation. All rights reserved. No portion of this work may be reproduced in
* any form or by any electronic or mechanical means without written permission from LabKey Corporation.
*/

(function($) {
window.Panorama = window.Panorama || {};
window.Panorama.Window = window.Panorama.Window || {};

const DIALOG_ID = 'lk-annotation-metric-dialog';
let _config = null;
let _allAnnotations = [];

function closeDialog() {
$('#' + DIALOG_ID).remove();
}

function showError(msg) {
$('#lk-annotation-metric-error').text(msg).show();
}

function clearErrors() {
$('#lk-annotation-metric-error').hide().text('');
$('#lk-annotation-metric-name, #lk-annotation-metric-ylabel, #lk-annotation-name-select')
.css('border-color', '');
}

function markInvalid($field) {
$field.css('border-color', 'red');
}

function validate() {
clearErrors();
let isValid = true;

if (!$('#lk-annotation-metric-name').val().trim()) {
markInvalid($('#lk-annotation-metric-name'));
isValid = false;
}
if (!$('#lk-annotation-metric-ylabel').val().trim()) {
markInvalid($('#lk-annotation-metric-ylabel'));
isValid = false;
}
if (!$('#lk-annotation-name-select').val()) {
markInvalid($('#lk-annotation-name-select'));
isValid = false;
}

if (!isValid) {
showError('Please fill in all required fields.');
}
return isValid;
}

function getAnnotationTarget() {
return $('input[name="annotationType"]:checked').val() === 'precursor'
? 'precursor_result'
: 'replicate';
}

function getFilteredAnnotations() {
const target = getAnnotationTarget();
const seen = {};
const result = [];
_allAnnotations.forEach(function(row) {
const targets = (row['Targets'] || '').split(',').map(function(s) { return s.trim(); });
if (targets.indexOf(target) >= 0 && !seen[row['Name']]) {
seen[row['Name']] = true;
result.push(row['Name']);
}
});
result.sort();
return result;
}

function refreshAnnotationsSelect() {
const $select = $('#lk-annotation-name-select');
const currentVal = $select.val();
$select.empty().append($('<option>').val('').text('-- Select annotation --'));
getFilteredAnnotations().forEach(function(name) {
$select.append($('<option>').val(name).text(name));
});

const metric = _config && _config.metric;
if (_config.operation === 'update' && metric && metric.AnnotationName) {
$select.val(metric.AnnotationName);
} else if (currentVal) {
$select.val(currentVal);
}
}

function checkMetricNameExists(metricName, callback) {
const filterArray = [LABKEY.Filter.create('Name', metricName, LABKEY.Filter.Types.EQUAL)];
if (_config.operation === 'update' && _config.metric) {
filterArray.push(LABKEY.Filter.create('id', _config.metric.id, LABKEY.Filter.Types.NOT_EQUAL));
}
LABKEY.Query.selectRows({
containerPath: LABKEY.container.id,
schemaName: 'targetedms',
queryName: 'qcmetricconfiguration',
filterArray: filterArray,
success: function(data) { callback(data.rows.length > 0); },
failure: function() { callback(false); }
});
}

function save() {
if (!validate()) return;

const metricName = $('#lk-annotation-metric-name').val().trim();
checkMetricNameExists(metricName, function(exists) {
if (exists) {
showError('A metric with the name "' + LABKEY.Utils.encodeHtml(metricName) + '" already exists. Please choose a different name.');
markInvalid($('#lk-annotation-metric-name'));
return;
}

const newMetric = {
Name: metricName,
QueryName: 'QCAnnotationMetric',
YAxisLabel: $('#lk-annotation-metric-ylabel').val().trim(),
PrecursorScoped: $('input[name="annotationType"]:checked').val() === 'precursor',
AnnotationName: $('#lk-annotation-name-select').val()
};
if (_config.operation === 'update') {
newMetric.id = _config.metric.id;
}

LABKEY.Query.saveRows({
containerPath: LABKEY.container.id,
commands: [{ schemaName: 'targetedms', queryName: 'qcmetricconfiguration', command: _config.operation, rows: [newMetric] }],
method: 'POST',
success: function() { window.location.reload(); },
failure: function(response) {
showError((response && (response.exception || response.message)) || 'Error saving metric');
}
});
});
}

function deleteMetric() {
if (!confirm('This will delete the "' + _config.metric.name + '" metric. Are you sure?')) return;

LABKEY.Query.saveRows({
containerPath: LABKEY.container.id,
commands: [
{ schemaName: 'targetedms', queryName: 'qcenabledmetrics', command: 'delete', rows: [{ metric: _config.metric.id }] },
{ schemaName: 'targetedms', queryName: 'qcmetricconfiguration', command: 'delete', rows: [{ id: _config.metric.id }] }
],
method: 'POST',
success: function() { window.location.reload(); },
failure: function(response) {
showError((response && (response.exception || response.message)) || 'Error deleting metric');
}
});
}

function buildDialogHtml() {
const op = _config.operation;
const metric = _config.metric || {};
const isPrecursor = op === 'update' && metric.PrecursorScoped;
const title = op === 'insert' ? 'Add Annotation-Backed Metric' : 'Edit Annotation-Backed Metric';

return '<div id="' + DIALOG_ID + '" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;display:flex;align-items:center;justify-content:center;">'
+ '<div class="x4-window x4-window-default" style="min-width:480px;max-width:580px;">'
+ '<div class="x4-window-header x4-window-header-default x4-window-header-default-top" style="padding:4px 8px;border:none;">'
+ '<p class="x4-window-header-text-container-default" style="font-size:14px;margin:0;">' + LABKEY.Utils.encodeHtml(title) + '</p>'
+ '</div>'
+ '<div class="x4-window-body" style="background:white;padding:10px 12px;">'
+ '<table style="border-collapse:collapse;width:100%;">'
+ '<tr><td style="padding:5px 10px 5px 0;white-space:nowrap;"><label for="lk-annotation-metric-name">Metric Name *</label></td>'
+ '<td style="padding:5px 0;"><input type="text" id="lk-annotation-metric-name" style="width:100%;box-sizing:border-box;" value="' + LABKEY.Utils.encodeHtml(metric.name || '') + '"/></td></tr>'
+ '<tr><td style="padding:5px 10px 5px 0;white-space:nowrap;"><label for="lk-annotation-metric-ylabel">Y-Axis Label *</label></td>'
+ '<td style="padding:5px 0;"><input type="text" id="lk-annotation-metric-ylabel" style="width:100%;box-sizing:border-box;" value="' + LABKEY.Utils.encodeHtml(metric.YAxisLabel || '') + '"/></td></tr>'
+ '<tr><td style="padding:5px 10px 5px 0;white-space:nowrap;">Annotation Type</td>'
+ '<td style="padding:5px 0;">'
+ '<label style="margin-right:16px;"><input type="radio" name="annotationType" value="replicate"' + (!isPrecursor ? ' checked' : '') + '> Replicate</label>'
+ '<label><input type="radio" name="annotationType" value="precursor"' + (isPrecursor ? ' checked' : '') + '> Precursor</label>'
+ '</td></tr>'
+ '<tr><td style="padding:5px 10px 5px 0;white-space:nowrap;"><label for="lk-annotation-name-select">Annotation *</label></td>'
+ '<td style="padding:5px 0;"><select id="lk-annotation-name-select" style="width:100%;box-sizing:border-box;"><option value="">Loading...</option></select></td></tr>'
+ '</table>'
+ '<div id="lk-annotation-metric-error" class="labkey-error" style="display:none;margin-top:8px;"></div>'
+ '<div style="margin-top:12px;text-align:right;">'
+ '<button type="button" class="labkey-button" id="lk-annotation-metric-cancel">Cancel</button>'
+ (op === 'update' ? ' <button type="button" class="labkey-button" id="lk-annotation-metric-delete">Delete</button>' : '')
+ ' <button type="button" class="labkey-button primary" id="lk-annotation-metric-save">Save</button>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</div>';
}

window.Panorama.Window.AddAnnotationMetricWindow = {
show: function(config) {
_config = config;
_allAnnotations = [];

$('#' + DIALOG_ID).remove();
$('body').append(buildDialogHtml());

$('#lk-annotation-metric-cancel').on('click', closeDialog);
$('#lk-annotation-metric-save').on('click', save);
if (config.operation === 'update') {
$('#lk-annotation-metric-delete').on('click', deleteMetric);
}
$('input[name="annotationType"]').on('change', refreshAnnotationsSelect);

// close on overlay click
$('#' + DIALOG_ID).on('click', function(e) {
if (e.target === this) closeDialog();
});

LABKEY.Query.selectRows({
schemaName: 'targetedms',
queryName: 'AnnotationSettings',
columns: ['Name', 'Targets', 'Type'],
filterArray: [LABKEY.Filter.create('Type', 'number', LABKEY.Filter.Types.EQUAL)],
success: function(data) {
_allAnnotations = data.rows || [];
refreshAnnotationsSelect();
}
});
}
};
})(jQuery);
2 changes: 1 addition & 1 deletion src/org/labkey/targetedms/TargetedMSModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public String getName()
@Override
public Double getSchemaVersion()
{
return 26.006;
return 26.008;
}

@Override
Expand Down
Loading
Loading