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 ? '' : '' ) + LABKEY.Utils.encodeHtml(row.name) + ' | ' + (editLock ? '' : '' );
- qcMetricsTable += '' + (row.PrecursorScoped ? 'Precursor' : 'Run') + ' | ';
+ qcMetricsTable += '' + (row.PrecursorScoped ? 'Precursor' : 'Replicate') + ' | ';
if (row.EffectiveStatus === 'NoData') {
qcMetricsTable += 'No data in this folder | ';
@@ -59,11 +63,14 @@
});
qcMetricsTable += '
' +
- '' +
- '' +
- '' +
- '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
'' +
+ '
' +
'
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($('