From b94bf0f33fbdd76444d77900e7874b89026353ed Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 23 Oct 2026 15:44:35 -0700 Subject: [PATCH 1/5] Describe TargetedMS usage metrics --- .../labkey/targetedms/TargetedMSModule.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/org/labkey/targetedms/TargetedMSModule.java b/src/org/labkey/targetedms/TargetedMSModule.java index c9e2425c6..3d35b1ee2 100644 --- a/src/org/labkey/targetedms/TargetedMSModule.java +++ b/src/org/labkey/targetedms/TargetedMSModule.java @@ -52,6 +52,7 @@ import org.labkey.api.settings.OptionalFeatureFlag; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.targetedms.TargetedMSService; +import org.labkey.api.usageMetrics.UsageMetricGroup; import org.labkey.api.usageMetrics.UsageMetricsService; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.view.ActionURL; @@ -95,9 +96,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import static org.labkey.api.targetedms.TargetedMSService.FOLDER_TYPE_PROP_NAME; @@ -561,13 +560,12 @@ protected void init() UsageMetricsService svc = UsageMetricsService.get(); if (null != svc) { - svc.registerUsageMetrics(MODULE_NAME, () -> + svc.registerUsageMetrics(MODULE_NAME, metrics -> { - Map metric = new HashMap<>(); DbSchema schema = TargetedMSManager.getSchema(); - metric.put("runCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.Runs WHERE Deleted = ?", Boolean.FALSE).getObject(Long.class)); - metric.put("guideSetCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.GuideSet").getObject(Long.class)); - metric.put("guideSetContainerCount", new SqlSelector(schema, "SELECT COUNT(DISTINCT Container) FROM TargetedMS.GuideSet").getObject(Long.class)); + metrics.put("runCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.Runs WHERE Deleted = ?", Boolean.FALSE).getObject(Long.class), "Skyline document runs across the server, excluding those marked deleted"); + metrics.put("guideSetCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.GuideSet").getObject(Long.class), "QC guide sets across the server"); + metrics.put("guideSetContainerCount", new SqlSelector(schema, "SELECT COUNT(DISTINCT Container) FROM TargetedMS.GuideSet").getObject(Long.class), "Folders with at least one QC guide set"); SQLFragment folderTypeSQL = new SQLFragment("SELECT p.value, COUNT(*) AS FolderCount FROM "); folderTypeSQL.append(PropertySchema.getInstance().getTableInfoProperties(), "p"); @@ -577,25 +575,21 @@ protected void init() folderTypeSQL.append(" AND p.name = ? GROUP BY value"); folderTypeSQL.add(FOLDER_TYPE_PROP_NAME); - Map folderCounts = new HashMap<>(); + UsageMetricGroup folderCounts = metrics.group("folderCounts", "Folders by their TargetedMS folder type setting (Experiment, Library, QC, etc.)"); new SqlSelector(PropertySchema.getInstance().getSchema(), folderTypeSQL).forEach(rs -> folderCounts.put(rs.getString("value"), rs.getLong("FolderCount"))); - metric.put("folderCounts", folderCounts); - SQLFragment annotationFolderCountSQL = new SQLFragment("SELECT at.Name, COUNT(DISTINCT a.Container) AS FolderCount FROM "); annotationFolderCountSQL.append(TargetedMSManager.getTableInfoQCAnnotationType(), "at"); annotationFolderCountSQL.append(" INNER JOIN " ); annotationFolderCountSQL.append(TargetedMSManager.getTableInfoQCAnnotation(), "a"); annotationFolderCountSQL.append(" ON at.Id = a.qcAnnotationTypeId GROUP BY at.Name"); - Map annotationFolderCounts = new HashMap<>(); + UsageMetricGroup annotationFolderCounts = metrics.group("annotationFolderCounts", "Folders with at least one QC annotation of each annotation type, by type name"); new SqlSelector(PropertySchema.getInstance().getSchema(), annotationFolderCountSQL).forEach(rs -> annotationFolderCounts.put(rs.getString("Name"), rs.getLong("FolderCount"))); - metric.put("annotationFolderCounts", annotationFolderCounts); - - return metric; + return metrics; }); } TargetedMSService.setInstance(new TargetedMSServiceImpl()); From fa2bd7d4422519a8431631e0f31bccfd3a24975d Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 23 Oct 2026 18:22:28 -0700 Subject: [PATCH 2/5] Claude review --- .../labkey/targetedms/TargetedMSModule.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/org/labkey/targetedms/TargetedMSModule.java b/src/org/labkey/targetedms/TargetedMSModule.java index 3d35b1ee2..cf0b10b7f 100644 --- a/src/org/labkey/targetedms/TargetedMSModule.java +++ b/src/org/labkey/targetedms/TargetedMSModule.java @@ -52,6 +52,7 @@ import org.labkey.api.settings.OptionalFeatureFlag; import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.targetedms.TargetedMSService; +import org.labkey.api.usageMetrics.SimpleMetricsService; import org.labkey.api.usageMetrics.UsageMetricGroup; import org.labkey.api.usageMetrics.UsageMetricsService; import org.labkey.api.util.PageFlowUtil; @@ -563,7 +564,7 @@ protected void init() svc.registerUsageMetrics(MODULE_NAME, metrics -> { DbSchema schema = TargetedMSManager.getSchema(); - metrics.put("runCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.Runs WHERE Deleted = ?", Boolean.FALSE).getObject(Long.class), "Skyline document runs across the server, excluding those marked deleted"); + metrics.put("runCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.Runs WHERE Deleted = ? AND StatusId = ?", Boolean.FALSE, SkylineDocImporter.STATUS_SUCCESS).getObject(Long.class), "Successfully imported Skyline document runs across the server, excluding those marked deleted"); metrics.put("guideSetCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.GuideSet").getObject(Long.class), "QC guide sets across the server"); metrics.put("guideSetContainerCount", new SqlSelector(schema, "SELECT COUNT(DISTINCT Container) FROM TargetedMS.GuideSet").getObject(Long.class), "Folders with at least one QC guide set"); @@ -572,21 +573,26 @@ protected void init() folderTypeSQL.append(" INNER JOIN "); folderTypeSQL.append(PropertySchema.getInstance().getTableInfoPropertySets(), "ps"); folderTypeSQL.append(" ON p.\"set\" = ps.\"set\" WHERE ps.category = 'moduleProperties.TargetedMS' "); - folderTypeSQL.append(" AND p.name = ? GROUP BY value"); + // The root holds the site-wide default, not a folder's setting + folderTypeSQL.append(" AND p.name = ? AND p.value IS NOT NULL AND ps.objectid <> ? GROUP BY value"); folderTypeSQL.add(FOLDER_TYPE_PROP_NAME); + folderTypeSQL.add(ContainerManager.getRoot().getId()); UsageMetricGroup folderCounts = metrics.group("folderCounts", "Folders by their TargetedMS folder type setting (Experiment, Library, QC, etc.)"); new SqlSelector(PropertySchema.getInstance().getSchema(), folderTypeSQL).forEach(rs -> folderCounts.put(rs.getString("value"), rs.getLong("FolderCount"))); - SQLFragment annotationFolderCountSQL = new SQLFragment("SELECT at.Name, COUNT(DISTINCT a.Container) AS FolderCount FROM "); + // Built-in types live in /Shared; custom names are user-entered and can repeat across folders + SQLFragment annotationFolderCountSQL = new SQLFragment("SELECT CASE WHEN at.Container = ? THEN at.Name ELSE 'custom' END AS Name, COUNT(DISTINCT a.Container) AS FolderCount FROM "); + annotationFolderCountSQL.add(ContainerManager.getSharedContainer().getId()); annotationFolderCountSQL.append(TargetedMSManager.getTableInfoQCAnnotationType(), "at"); annotationFolderCountSQL.append(" INNER JOIN " ); annotationFolderCountSQL.append(TargetedMSManager.getTableInfoQCAnnotation(), "a"); - annotationFolderCountSQL.append(" ON at.Id = a.qcAnnotationTypeId GROUP BY at.Name"); + annotationFolderCountSQL.append(" ON at.Id = a.qcAnnotationTypeId GROUP BY CASE WHEN at.Container = ? THEN at.Name ELSE 'custom' END"); + annotationFolderCountSQL.add(ContainerManager.getSharedContainer().getId()); - UsageMetricGroup annotationFolderCounts = metrics.group("annotationFolderCounts", "Folders with at least one QC annotation of each annotation type, by type name"); - new SqlSelector(PropertySchema.getInstance().getSchema(), annotationFolderCountSQL).forEach(rs -> + UsageMetricGroup annotationFolderCounts = metrics.group("annotationFolderCounts", "Folders with at least one QC annotation of each annotation type, by built-in type name, with all custom types under 'custom'"); + new SqlSelector(schema, annotationFolderCountSQL).forEach(rs -> annotationFolderCounts.put(rs.getString("Name"), rs.getLong("FolderCount"))); return metrics; @@ -620,6 +626,8 @@ protected void init() @Override protected void startupAfterSpringConfig(ModuleContext moduleContext) { + SimpleMetricsService.get().describeClientSideFeatureArea("panoramaQCPlot", "QC trend plot y-axis zoom use: yAxisZoom when a user zooms an axis, yAxisZoomReset when a zoom is reset"); + PipelineService service = PipelineService.get(); service.registerPipelineProvider(new TargetedMSPipelineProvider(this)); From 0189b5795b1dc491599644c596041bec0293881f Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Sun, 25 Oct 2026 15:16:48 -0700 Subject: [PATCH 3/5] Fix the QC annotation usage metric query's GROUP BY so Postgres accepts it --- src/org/labkey/targetedms/TargetedMSModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/labkey/targetedms/TargetedMSModule.java b/src/org/labkey/targetedms/TargetedMSModule.java index cf0b10b7f..bab18b35b 100644 --- a/src/org/labkey/targetedms/TargetedMSModule.java +++ b/src/org/labkey/targetedms/TargetedMSModule.java @@ -588,8 +588,8 @@ protected void init() annotationFolderCountSQL.append(TargetedMSManager.getTableInfoQCAnnotationType(), "at"); annotationFolderCountSQL.append(" INNER JOIN " ); annotationFolderCountSQL.append(TargetedMSManager.getTableInfoQCAnnotation(), "a"); - annotationFolderCountSQL.append(" ON at.Id = a.qcAnnotationTypeId GROUP BY CASE WHEN at.Container = ? THEN at.Name ELSE 'custom' END"); - annotationFolderCountSQL.add(ContainerManager.getSharedContainer().getId()); + // Repeating the CASE here would bind a second parameter, which Postgres won't match to the SELECT's expression + annotationFolderCountSQL.append(" ON at.Id = a.qcAnnotationTypeId GROUP BY 1"); UsageMetricGroup annotationFolderCounts = metrics.group("annotationFolderCounts", "Folders with at least one QC annotation of each annotation type, by built-in type name, with all custom types under 'custom'"); new SqlSelector(schema, annotationFolderCountSQL).forEach(rs -> From 470909d37aa0d50138ac79f407e25366040d0ec7 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Mon, 26 Oct 2026 18:49:28 -0700 Subject: [PATCH 4/5] Skip usage metric queries when only collecting descriptions --- src/org/labkey/targetedms/TargetedMSModule.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/labkey/targetedms/TargetedMSModule.java b/src/org/labkey/targetedms/TargetedMSModule.java index bab18b35b..9b91cfe0f 100644 --- a/src/org/labkey/targetedms/TargetedMSModule.java +++ b/src/org/labkey/targetedms/TargetedMSModule.java @@ -564,9 +564,9 @@ protected void init() svc.registerUsageMetrics(MODULE_NAME, metrics -> { DbSchema schema = TargetedMSManager.getSchema(); - metrics.put("runCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.Runs WHERE Deleted = ? AND StatusId = ?", Boolean.FALSE, SkylineDocImporter.STATUS_SUCCESS).getObject(Long.class), "Successfully imported Skyline document runs across the server, excluding those marked deleted"); - metrics.put("guideSetCount", new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.GuideSet").getObject(Long.class), "QC guide sets across the server"); - metrics.put("guideSetContainerCount", new SqlSelector(schema, "SELECT COUNT(DISTINCT Container) FROM TargetedMS.GuideSet").getObject(Long.class), "Folders with at least one QC guide set"); + metrics.put("runCount", () -> new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.Runs WHERE Deleted = ? AND StatusId = ?", Boolean.FALSE, SkylineDocImporter.STATUS_SUCCESS).getObject(Long.class), "Successfully imported Skyline document runs across the server, excluding those marked deleted"); + metrics.put("guideSetCount", () -> new SqlSelector(schema, "SELECT COUNT(*) FROM TargetedMS.GuideSet").getObject(Long.class), "QC guide sets across the server"); + metrics.put("guideSetContainerCount", () -> new SqlSelector(schema, "SELECT COUNT(DISTINCT Container) FROM TargetedMS.GuideSet").getObject(Long.class), "Folders with at least one QC guide set"); SQLFragment folderTypeSQL = new SQLFragment("SELECT p.value, COUNT(*) AS FolderCount FROM "); folderTypeSQL.append(PropertySchema.getInstance().getTableInfoProperties(), "p"); From 0d829f1e7d890ee1585e9c14e330f0e429a71165 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Tue, 27 Oct 2026 10:58:45 -0700 Subject: [PATCH 5/5] Claude review --- .../labkey/targetedms/TargetedMSModule.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/org/labkey/targetedms/TargetedMSModule.java b/src/org/labkey/targetedms/TargetedMSModule.java index 9b91cfe0f..c69791637 100644 --- a/src/org/labkey/targetedms/TargetedMSModule.java +++ b/src/org/labkey/targetedms/TargetedMSModule.java @@ -53,7 +53,6 @@ import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.targetedms.TargetedMSService; import org.labkey.api.usageMetrics.SimpleMetricsService; -import org.labkey.api.usageMetrics.UsageMetricGroup; import org.labkey.api.usageMetrics.UsageMetricsService; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.view.ActionURL; @@ -97,7 +96,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import static org.labkey.api.targetedms.TargetedMSService.FOLDER_TYPE_PROP_NAME; @@ -578,9 +579,12 @@ protected void init() folderTypeSQL.add(FOLDER_TYPE_PROP_NAME); folderTypeSQL.add(ContainerManager.getRoot().getId()); - UsageMetricGroup folderCounts = metrics.group("folderCounts", "Folders by their TargetedMS folder type setting (Experiment, Library, QC, etc.)"); - new SqlSelector(PropertySchema.getInstance().getSchema(), folderTypeSQL).forEach(rs -> - folderCounts.put(rs.getString("value"), rs.getLong("FolderCount"))); + metrics.put("folderCounts", () -> { + Map folderCounts = new LinkedHashMap<>(); + new SqlSelector(PropertySchema.getInstance().getSchema(), folderTypeSQL).forEach(rs -> + folderCounts.put(rs.getString("value"), rs.getLong("FolderCount"))); + return folderCounts; + }, "Folders by their TargetedMS folder type setting (Experiment, Library, QC, etc.)"); // Built-in types live in /Shared; custom names are user-entered and can repeat across folders SQLFragment annotationFolderCountSQL = new SQLFragment("SELECT CASE WHEN at.Container = ? THEN at.Name ELSE 'custom' END AS Name, COUNT(DISTINCT a.Container) AS FolderCount FROM "); @@ -591,9 +595,12 @@ protected void init() // Repeating the CASE here would bind a second parameter, which Postgres won't match to the SELECT's expression annotationFolderCountSQL.append(" ON at.Id = a.qcAnnotationTypeId GROUP BY 1"); - UsageMetricGroup annotationFolderCounts = metrics.group("annotationFolderCounts", "Folders with at least one QC annotation of each annotation type, by built-in type name, with all custom types under 'custom'"); - new SqlSelector(schema, annotationFolderCountSQL).forEach(rs -> - annotationFolderCounts.put(rs.getString("Name"), rs.getLong("FolderCount"))); + metrics.put("annotationFolderCounts", () -> { + Map annotationFolderCounts = new LinkedHashMap<>(); + new SqlSelector(schema, annotationFolderCountSQL).forEach(rs -> + annotationFolderCounts.put(rs.getString("Name"), rs.getLong("FolderCount"))); + return annotationFolderCounts; + }, "Folders with at least one QC annotation of each annotation type, by built-in type name, with all custom types under 'custom'"); return metrics; });