diff --git a/api-src/org/labkey/api/targetedms/model/QCMetricConfiguration.java b/api-src/org/labkey/api/targetedms/model/QCMetricConfiguration.java index 73d10893e..3a9000ac8 100644 --- a/api-src/org/labkey/api/targetedms/model/QCMetricConfiguration.java +++ b/api-src/org/labkey/api/targetedms/model/QCMetricConfiguration.java @@ -33,6 +33,7 @@ public class QCMetricConfiguration implements Comparable private String _yAxisLabel; private Double _upperBound; private Double _lowerBound; + private String _annotationName; public int getId() { @@ -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); @@ -195,6 +206,9 @@ public JSONObject toJSON(){ if (_upperBound != null) { jsonObject.put("upperBound", _upperBound); } + if (_annotationName != null) { + jsonObject.put("annotationName", _annotationName); + } return jsonObject; } diff --git a/resources/queries/targetedms/qcMetricsConfig.sql b/resources/queries/targetedms/qcMetricsConfig.sql index d1f1248ef..bb5d5082d 100644 --- a/resources/queries/targetedms/qcMetricsConfig.sql +++ b/resources/queries/targetedms/qcMetricsConfig.sql @@ -30,7 +30,8 @@ SELECT qmc.MaxTimeValue, qmc.TimeValueOption, qmc.TraceName, - qmc.YAxisLabel + qmc.YAxisLabel, + qmc.AnnotationName FROM qcmetricconfiguration qmc FULL JOIN qcenabledmetrics qem diff --git a/resources/schemas/dbscripts/postgresql/targetedms-26.007-26.008.sql b/resources/schemas/dbscripts/postgresql/targetedms-26.007-26.008.sql new file mode 100644 index 000000000..17c40a29a --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/targetedms-26.007-26.008.sql @@ -0,0 +1 @@ +ALTER TABLE targetedms.QCMetricConfiguration ADD COLUMN AnnotationName VARCHAR(255); diff --git a/resources/schemas/targetedms.xml b/resources/schemas/targetedms.xml index b864b0d66..1ba2d107b 100644 --- a/resources/schemas/targetedms.xml +++ b/resources/schemas/targetedms.xml @@ -1366,6 +1366,7 @@ + diff --git a/resources/views/configureQCMetric.html b/resources/views/configureQCMetric.html index 9a608a2c5..0d65d3017 100644 --- a/resources/views/configureQCMetric.html +++ b/resources/views/configureQCMetric.html @@ -12,6 +12,10 @@ LABKEY.internal = {}; LABKEY.internal.ConfigureQCMetrics = new function () { + const METRIC_TYPE_CUSTOM = 'custom'; + const METRIC_TYPE_TRACE = 'trace'; + const METRIC_TYPE_ANNOTATION = 'annotation'; + let qcMetrics; let qcMetricsTable =''; let configRows = []; @@ -36,7 +40,7 @@ qcMetricsTable += ''; qcMetricsTable += '' + (editLock ? '' : '' ); - qcMetricsTable += ''; + qcMetricsTable += ''; if (row.EffectiveStatus === 'NoData') { qcMetricsTable += ''; @@ -59,11 +63,14 @@ }); qcMetricsTable += '
' + (editLock ? '' : '' ) + LABKEY.Utils.encodeHtml(row.name) + '' + (row.PrecursorScoped ? 'Precursor' : 'Run') + '' + (row.PrecursorScoped ? 'Precursor' : 'Replicate') + 'No data in this folder

' + - '' + - '' + - '' + - '' + + '
' + + '' + + '' + + '' + + '' + + '' + '' + + '
' + '
Edits to queries backing existing custom metrics require a manual cache clearing to display the updated results.

'; jQuery('#qcMetricsTable').html(qcMetricsTable); @@ -77,10 +84,13 @@ LABKEY.internal.ConfigureQCMetrics.resetQCMetrics(); }); jQuery('#createNewCustomMetricButton').click(function() { - LABKEY.internal.ConfigureQCMetrics.addNewMetric('custom'); + LABKEY.internal.ConfigureQCMetrics.addNewMetric(METRIC_TYPE_CUSTOM); }); jQuery('#createNewTraceMetricButton').click(function() { - LABKEY.internal.ConfigureQCMetrics.addNewMetric('trace') + LABKEY.internal.ConfigureQCMetrics.addNewMetric(METRIC_TYPE_TRACE); + }); + jQuery('#createNewAnnotationMetricButton').click(function() { + LABKEY.internal.ConfigureQCMetrics.addNewMetric(METRIC_TYPE_ANNOTATION); }); jQuery('#clearCacheButton').click(function() { jQuery('#qcMetricsError').text('Clearing cached metrics...'); @@ -169,7 +179,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); @@ -181,22 +194,11 @@ }, showCustomMetricWindow: function (op, clickedMetric) { - LABKEY.Query.getSchemas({ - scope: this, - containerPath: LABKEY.container.id, - success: function(schemasInfo) { - const windowConfig = { - parent: this, - schemas: schemasInfo.schemas, - operation: op - }; - - if (clickedMetric) { - windowConfig.metric = clickedMetric; - } - Ext4.create('Panorama.Window.AddCustomMetricWindow', windowConfig).show(); - } - }); + const windowConfig = { operation: op }; + if (clickedMetric) { + windowConfig.metric = clickedMetric; + } + Panorama.Window.AddCustomMetricWindow.show(windowConfig); }, showTraceMetricWindow: function (op, clickedMetric) { @@ -242,13 +244,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') { + if (metricType === METRIC_TYPE_CUSTOM) { this.showCustomMetricWindow(op); } - else if (metricType === 'trace') { - this.showTraceMetricWindow(op) + else if (metricType === METRIC_TYPE_TRACE) { + this.showTraceMetricWindow(op); + } + else if (metricType === METRIC_TYPE_ANNOTATION) { + this.showAnnotationMetricWindow(op); } }, diff --git a/resources/views/configureQCMetric.view.xml b/resources/views/configureQCMetric.view.xml index d10619e34..69c26d2dd 100644 --- a/resources/views/configureQCMetric.view.xml +++ b/resources/views/configureQCMetric.view.xml @@ -7,6 +7,7 @@ + \ No newline at end of file diff --git a/resources/web/PanoramaPremium/window/AddNewAnnotationMetricWindow.js b/resources/web/PanoramaPremium/window/AddNewAnnotationMetricWindow.js new file mode 100644 index 000000000..2d036864b --- /dev/null +++ b/resources/web/PanoramaPremium/window/AddNewAnnotationMetricWindow.js @@ -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($('