From e9c8bd44c17392796b192910315af7df2346b30d Mon Sep 17 00:00:00 2001 From: ankurjuneja Date: Mon, 23 Feb 2026 17:20:38 -0800 Subject: [PATCH 1/6] associate instrument nickname with shareable qcannotations --- .../postgresql/targetedms-26.002-26.003.sql | 2 + resources/schemas/targetedms.xml | 2 + .../labkey/targetedms/TargetedMSManager.java | 263 +++++++++++------- .../labkey/targetedms/TargetedMSModule.java | 2 +- .../targetedms/query/QCAnnotationTable.java | 41 +++ 5 files changed, 203 insertions(+), 107 deletions(-) create mode 100644 resources/schemas/dbscripts/postgresql/targetedms-26.002-26.003.sql diff --git a/resources/schemas/dbscripts/postgresql/targetedms-26.002-26.003.sql b/resources/schemas/dbscripts/postgresql/targetedms-26.002-26.003.sql new file mode 100644 index 000000000..2ae3b39af --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/targetedms-26.002-26.003.sql @@ -0,0 +1,2 @@ +ALTER TABLE targetedms.QCAnnotationType ADD COLUMN isShareable BOOLEAN DEFAULT FALSE; +ALTER TABLE targetedms.QCAnnotation ADD COLUMN instrument VARCHAR(255); \ No newline at end of file diff --git a/resources/schemas/targetedms.xml b/resources/schemas/targetedms.xml index 2bd32a2d8..961f17963 100644 --- a/resources/schemas/targetedms.xml +++ b/resources/schemas/targetedms.xml @@ -1262,6 +1262,7 @@ + @@ -1307,6 +1308,7 @@ Annotation Type The category of the event + diff --git a/src/org/labkey/targetedms/TargetedMSManager.java b/src/org/labkey/targetedms/TargetedMSManager.java index 0295ce763..fc231a971 100644 --- a/src/org/labkey/targetedms/TargetedMSManager.java +++ b/src/org/labkey/targetedms/TargetedMSManager.java @@ -207,7 +207,8 @@ public static TargetedMSManager get() public static List getSampleFileChromInfos(SampleFile sampleFile) { - return new TableSelector(getTableInfoSampleFileChromInfo(), new SimpleFilter(FieldKey.fromParts("SampleFileId"), sampleFile.getId()), new Sort("TextId")).getArrayList(SampleFileChromInfo.class); } + return new TableSelector(getTableInfoSampleFileChromInfo(), new SimpleFilter(FieldKey.fromParts("SampleFileId"), sampleFile.getId()), new Sort("TextId")).getArrayList(SampleFileChromInfo.class); + } public static SampleFileChromInfo getSampleFileChromInfo(int id, Container c) { @@ -356,7 +357,7 @@ public static TableInfo getTableInfoGeneralMoleculeChromInfo() public static TableInfo getTableInfoPeptideAreaRatio() { - return getSchema().getTable(TargetedMSSchema.TABLE_PEPTIDE_AREA_RATIO); + return getSchema().getTable(TargetedMSSchema.TABLE_PEPTIDE_AREA_RATIO); } public static TableInfo getTableInfoInstrument() @@ -499,6 +500,7 @@ public static TableInfo getTableInfoQCEmailNotifications() { return getSchema().getTable(TargetedMSSchema.TABLE_QC_EMAIL_NOTIFICATIONS); } + public static TableInfo getTableInfoAnnotationSettings() { return getSchema().getTable(TargetedMSSchema.TABLE_ANNOTATION_SETTINGS); @@ -543,12 +545,14 @@ public static TableInfo getTableInfoQCAnnotation() { return getSchema().getTable(TargetedMSSchema.TABLE_QC_ANNOTATION); } + public static TableInfo getTableInfoQuantificationSettings() { return getSchema().getTable(TargetedMSSchema.TABLE_QUANTIIFICATION_SETTINGS); } - public static TableInfo getTableInfoCalibrationCurve() { + public static TableInfo getTableInfoCalibrationCurve() + { return getSchema().getTable(TargetedMSSchema.TABLE_CALIBRATION_CURVE); } @@ -612,7 +616,9 @@ public static TableInfo getTableInfoSkylineRunAuditLogEntry() return getSchema().getTable(TargetedMSSchema.TABLE_SKYLINE_RUN_AUDITLOG_ENTRY); } - /** View that's a CTE to pull in the RunId */ + /** + * View that's a CTE to pull in the RunId + */ public static TableInfo getTableInfoSkylineAuditLog() { return getSchema().getTable(TargetedMSSchema.TABLE_SKYLINE_AUDITLOG); @@ -623,19 +629,23 @@ public static TableInfo getTableInfoSkylineAuditLogMessage() return getSchema().getTable(TargetedMSSchema.TABLE_SKYLINE_AUDITLOG_MESSAGE); } - public static TableInfo getTableInfoListDefinition() { + public static TableInfo getTableInfoListDefinition() + { return getSchema().getTable(TargetedMSSchema.TABLE_LIST_DEFINITION); } - public static TableInfo getTableInfoListColumnDefinition() { + public static TableInfo getTableInfoListColumnDefinition() + { return getSchema().getTable(TargetedMSSchema.TABLE_LIST_COLUMN_DEFINITION); } - public static TableInfo getTableInfoListItem() { + public static TableInfo getTableInfoListItem() + { return getSchema().getTable(TargetedMSSchema.TABLE_LIST_ITEM); } - public static TableInfo getTableInfoListItemValue() { + public static TableInfo getTableInfoListItemValue() + { return getSchema().getTable(TargetedMSSchema.TABLE_LIST_ITEM_VALUE); } @@ -704,20 +714,22 @@ public static TableInfo getTableInfoInstrumentUsagePayment() return getSchema().getTable(TargetedMSSchema.TABLE_INSTRUMENT_USAGE_PAYMENT); } - /** @return rowId for pipeline job that will perform the import asynchronously */ + /** + * @return rowId for pipeline job that will perform the import asynchronously + */ public static Long addRunToQueue(ViewBackgroundInfo info, - final Path path) throws XarFormatException, PipelineValidationException + final Path path) throws XarFormatException, PipelineValidationException { String description = "Skyline document import - " + FileUtil.getFileName(path); XarContext xarContext = new XarContext(description, info.getContainer(), info.getUser()); - User user = info.getUser(); + User user = info.getUser(); Container container = info.getContainer(); // If an entry does not already exist for this data file in exp.data create it now. - // This should happen only if a file was copied to the pipeline directory instead - // of being uploaded via the files browser. + // This should happen only if a file was copied to the pipeline directory instead + // of being uploaded via the files browser. ExpData expData = ExperimentService.get().getExpDataByURL(path, container); - if(expData == null) + if (expData == null) { XarSource source = new AbstractFileXarSource("Wrap Targeted MS Run", container, user) { @@ -785,7 +797,7 @@ private static void wrapRun(TargetedMSRun run, User user, Long jobId) throws Exp // Make sure that we have a protocol in this folder String protocolPrefix = run.isZipFile() ? TargetedMSModule.IMPORT_SKYZIP_PROTOCOL_OBJECT_PREFIX : - TargetedMSModule.IMPORT_SKYDOC_PROTOCOL_OBJECT_PREFIX; + TargetedMSModule.IMPORT_SKYDOC_PROTOCOL_OBJECT_PREFIX; Lsid lsid = new Lsid("Protocol.Folder-" + container.getRowId(), protocolPrefix); ExpProtocol protocol = ExperimentService.get().getExpProtocol(lsid.toString()); @@ -810,11 +822,11 @@ private static void wrapRun(TargetedMSRun run, User user, Long jobId) throws Exp outputDatas.put(expData, "sky"); ExpData expSkydData = null; - if(run.getSkydDataId() != null) + if (run.getSkydDataId() != null) { expSkydData = ExperimentService.get().getExpData(run.getSkydDataId()); } - if(expSkydData != null) + if (expSkydData != null) { outputDatas.put(expSkydData, "skyd"); } @@ -831,12 +843,12 @@ private static void wrapRun(TargetedMSRun run, User user, Long jobId) throws Exp } expRun = ExperimentService.get().saveSimpleExperimentRun(expRun, - Collections.emptyMap(), - inputDatas, - Collections.emptyMap(), - outputDatas, - Collections.emptyMap(), - info, _log, false); + Collections.emptyMap(), + inputDatas, + Collections.emptyMap(), + outputDatas, + Collections.emptyMap(), + info, _log, false); run.setExperimentRunLSID(expRun.getLSID()); TargetedMSManager.updateRun(run, user); @@ -903,7 +915,7 @@ public static TargetedMSRun getRunByDataId(long dataId) public static TargetedMSRun getRunByDataId(long dataId, Container c) { TargetedMSRun[] runs; - if(c != null) + if (c != null) { runs = getRuns("DataId = ? AND Deleted = ? AND Container = ?", dataId, Boolean.FALSE, c.getId()); } @@ -911,11 +923,11 @@ public static TargetedMSRun getRunByDataId(long dataId, Container c) { runs = getRuns("DataId = ? AND Deleted = ?", dataId, Boolean.FALSE); } - if(runs.length == 0) + if (runs.length == 0) { return null; } - if(runs.length == 1) + if (runs.length == 1) { return runs[0]; } @@ -930,7 +942,7 @@ public static TargetedMSRun getRunBySkydDataId(long skydDataId) public static TargetedMSRun getRunBySkydDataId(long skydDataId, Container c) { TargetedMSRun[] runs; - if(c != null) + if (c != null) { runs = getRuns("SkydDataId = ? AND Deleted = ? AND Container = ?", skydDataId, Boolean.FALSE, c.getId()); } @@ -938,11 +950,11 @@ public static TargetedMSRun getRunBySkydDataId(long skydDataId, Container c) { runs = getRuns("SkydDataId = ? AND Deleted = ?", skydDataId, Boolean.FALSE); } - if(runs.length == 0) + if (runs.length == 0) { return null; } - if(runs.length == 1) + if (runs.length == 1) { return runs[0]; } @@ -953,15 +965,15 @@ public static TargetedMSRun getRunBySkydDataId(long skydDataId, Container c) public static TargetedMSRun getRunByLsid(String lsid, Container c) { TargetedMSRun[] runs = getRuns("experimentrunlsid = ? AND container = ?", lsid, c.getId()); - if(runs.length == 0) + if (runs.length == 0) { return null; } - if(runs.length == 1) + if (runs.length == 1) { return runs[0]; } - throw new IllegalArgumentException("More than one TargetedMS runs found for LSID "+lsid); + throw new IllegalArgumentException("More than one TargetedMS runs found for LSID " + lsid); } public static boolean updateSkydDataId(ITargetedMSRun run, ExpData newSkydData, User user) @@ -1046,11 +1058,11 @@ public static void markRunsNotRepresentative(Container container, RunRepresentat { Collection representativeRunIds = Collections.emptyList(); - if(representativeState == RunRepresentativeDataState.Representative_Protein) + if (representativeState == RunRepresentativeDataState.Representative_Protein) { representativeRunIds = getProteinRepresentativeRunIds(container); } - else if(representativeState == RunRepresentativeDataState.Representative_Peptide) + else if (representativeState == RunRepresentativeDataState.Representative_Peptide) { representativeRunIds = getPeptideRepresentativeRunIds(container); } @@ -1061,7 +1073,7 @@ else if(representativeState == RunRepresentativeDataState.Representative_Peptide } SQLFragment updateSql = new SQLFragment(); - updateSql.append("UPDATE "+TargetedMSManager.getTableInfoRuns()); + updateSql.append("UPDATE " + TargetedMSManager.getTableInfoRuns()); updateSql.append(" SET RepresentativeDataState = ?"); updateSql.add(RunRepresentativeDataState.NotRepresentative.ordinal()); updateSql.append(" WHERE Container = ?"); @@ -1073,7 +1085,7 @@ else if(representativeState == RunRepresentativeDataState.Representative_Peptide // for the documents not yet imported. updateSql.append(" AND StatusId = ? "); updateSql.add(SkylineDocImporter.STATUS_SUCCESS); - updateSql.append(" AND Id NOT IN ("+StringUtils.join(representativeRunIds, ",")+")"); + updateSql.append(" AND Id NOT IN (" + StringUtils.join(representativeRunIds, ",") + ")"); new SqlExecutor(TargetedMSManager.getSchema()).execute(updateSql); } @@ -1081,11 +1093,11 @@ else if(representativeState == RunRepresentativeDataState.Representative_Peptide public static List getCurrentRepresentativeRunIds(Container container) { List representativeRunIds = null; - if(getFolderType(container) == TargetedMSService.FolderType.LibraryProtein) + if (getFolderType(container) == TargetedMSService.FolderType.LibraryProtein) { representativeRunIds = getCurrentProteinRepresentativeRunIds(container); } - else if(getFolderType(container) == TargetedMSService.FolderType.Library) + else if (getFolderType(container) == TargetedMSService.FolderType.Library) { representativeRunIds = getCurrentPeptideRepresentativeRunIds(container); } @@ -1101,8 +1113,8 @@ private static List getCurrentProteinRepresentativeRunIds(Container contai private static List getProteinRepresentativeRunIds(Container container) { return getProteinRepresentativeRunIds(container, RepresentativeDataState.Representative.ordinal(), - RepresentativeDataState.Deprecated.ordinal(), - RepresentativeDataState.Conflicted.ordinal()); + RepresentativeDataState.Deprecated.ordinal(), + RepresentativeDataState.Conflicted.ordinal()); } private static List getProteinRepresentativeRunIds(Container container, int... stateArray) @@ -1130,8 +1142,8 @@ private static List getCurrentPeptideRepresentativeRunIds(Container contai private static List getPeptideRepresentativeRunIds(Container container) { return getPeptideRepresentativeRunIds(container, RepresentativeDataState.Representative.ordinal(), - RepresentativeDataState.Deprecated.ordinal(), - RepresentativeDataState.Conflicted.ordinal()); + RepresentativeDataState.Deprecated.ordinal(), + RepresentativeDataState.Conflicted.ordinal()); } private static List getPeptideRepresentativeRunIds(Container container, int... stateArray) @@ -1164,7 +1176,9 @@ public static void updateRun(TargetedMSRun run, User user) Table.update(user, getTableInfoRuns(), run, run.getRunId()); } - /** Delete all of the TargetedMS runs in the container, including their experiment run wrappers */ + /** + * Delete all of the TargetedMS runs in the container, including their experiment run wrappers + */ public static void deleteIncludingExperimentWrapper(Container c, User user) { List runIds = new SqlSelector(getSchema(), "SELECT Id FROM " + getTableInfoRuns() + " WHERE Container = ?", c).getArrayList(Long.class); @@ -1210,9 +1224,13 @@ public InstrumentNickname getNickname(long id, User user) return name; } - private record NicknameKey(String serialNumber, String model) {} + private record NicknameKey(String serialNumber, String model) + { + } - /** @return the matches in order of closest to furthest match, injecting a virtual option if the list is empty */ + /** + * @return the matches in order of closest to furthest match, injecting a virtual option if the list is empty + */ public List getNickname(String name, TargetedMSSchema schema) { TableInfo info = schema.getTableOrThrow(TABLE_INSTRUMENT_NICKNAME, new ContainerFilter.CurrentPlusProjectAndShared(schema.getContainer(), schema.getUser())); @@ -1235,7 +1253,7 @@ public List getNickname(String name, TargetedMSSchema schema } List result = new ArrayList<>(dedupeAcrossContainers.values()); - + if (matches.isEmpty()) { String sql = "SELECT DISTINCT InstrumentNickname, " + @@ -1283,13 +1301,14 @@ private void addNameMatch(Map result, List runIds, Container c, User user, boolean containerDeletion) { List cantDelete = new ArrayList<>(); List runsToDelete = new ArrayList<>(); - for (long runId: runIds) + for (long runId : runIds) { TargetedMSRun run = getRun(runId); if (run == null || run.isDeleted()) @@ -1298,7 +1317,7 @@ public static void deleteRuns(List runIds, Container c, User user, boolean } if (!run.getContainer().equals(c) && !run.getContainer().hasPermission(user, DeletePermission.class)) { - cantDelete.add(runId); + cantDelete.add(runId); } runsToDelete.add(run); } @@ -1371,22 +1390,22 @@ public static void deleteRuns(List runIds, Container c, User user, boolean public static TargetedMSRun getRunForPrecursor(long precursorId) { - String sql = "SELECT run.* FROM "+ - getTableInfoRuns()+" AS run, "+ - getTableInfoPeptideGroup()+" AS pg, "+ - getTableInfoGeneralMolecule()+" AS gm, "+ - getTableInfoGeneralPrecursor()+" AS gp "+ - "WHERE run.Id=pg.RunId "+ - "AND pg.Id=gm.PeptideGroupId "+ - "AND gm.Id=gp.GeneralMoleculeId "+ - "AND gp.Id=?"; + String sql = "SELECT run.* FROM " + + getTableInfoRuns() + " AS run, " + + getTableInfoPeptideGroup() + " AS pg, " + + getTableInfoGeneralMolecule() + " AS gm, " + + getTableInfoGeneralPrecursor() + " AS gp " + + "WHERE run.Id=pg.RunId " + + "AND pg.Id=gm.PeptideGroupId " + + "AND gm.Id=gp.GeneralMoleculeId " + + "AND gp.Id=?"; SQLFragment sf = new SQLFragment(sql); sf.add(precursorId); TargetedMSRun run = new SqlSelector(getSchema(), sf).getObject(TargetedMSRun.class); - if(run == null) + if (run == null) { - throw new NotFoundException("No run found for precursor: "+precursorId); + throw new NotFoundException("No run found for precursor: " + precursorId); } return run; } @@ -1394,18 +1413,18 @@ public static TargetedMSRun getRunForPrecursor(long precursorId) @NotNull public static TargetedMSRun getRunForGeneralMolecule(long id) { - String sql = "SELECT run.* FROM "+ - getTableInfoRuns()+" AS run, "+ - getTableInfoPeptideGroup()+" AS pg, "+ - getTableInfoGeneralMolecule()+" AS gm "+ - "WHERE run.Id=pg.RunId "+ - "AND pg.Id=gm.PeptideGroupId "+ - "AND gm.Id=?"; + String sql = "SELECT run.* FROM " + + getTableInfoRuns() + " AS run, " + + getTableInfoPeptideGroup() + " AS pg, " + + getTableInfoGeneralMolecule() + " AS gm " + + "WHERE run.Id=pg.RunId " + + "AND pg.Id=gm.PeptideGroupId " + + "AND gm.Id=?"; SQLFragment sf = new SQLFragment(sql); sf.add(id); TargetedMSRun run = new SqlSelector(getSchema(), sf).getObject(TargetedMSRun.class); - if(run == null) + if (run == null) { throw new NotFoundException("No run found for general molecule: " + id); } @@ -1506,7 +1525,7 @@ private static boolean fileIsReferenced(@NotNull URI uri) sql.append(getTableInfoRuns(), "r"); sql.append(", "); sql.append(ExperimentService.get().getTinfoData(), "d"); - sql.append( " WHERE rep.Id = sf.ReplicateId AND rep.RunId = r.Id AND d.RowId = r.DataId AND d.DataFileUrl = ?"); + sql.append(" WHERE rep.Id = sf.ReplicateId AND rep.RunId = r.Id AND d.RowId = r.DataId AND d.DataFileUrl = ?"); sql.add(FileUtil.uriToString(uri)); return new SqlSelector(getSchema(), sql).exists(); @@ -1648,7 +1667,7 @@ public static String getSampleFileUploadFile(long sampleFileId) var map = new SqlSelector(getSchema(), sql).getMap(); String filePath = null; - if(map != null) + if (map != null) { filePath = (String) map.get("dataFileUrl"); } @@ -1735,7 +1754,7 @@ public static void purgeDeletedSampleFiles(List sampleFileIds) private static String createTempChromInfoIdsTable(TableInfo tableInfo, String tempTableNamePrefix, SQLFragment whereClause) { final String suffix = StringUtilsLabKey.getPaddedUniquifier(9); - final String tempTableName = TargetedMSManager.getSqlDialect().getTempTablePrefix() + tempTableNamePrefix + suffix; + final String tempTableName = TargetedMSManager.getSqlDialect().getTempTablePrefix() + tempTableNamePrefix + suffix; new SqlExecutor(TargetedMSSchema.getSchema()).execute("CREATE " + TargetedMSManager.getSqlDialect().getTempTableKeyword() + " TABLE " + tempTableName + " ( Id BIGINT NOT NULL PRIMARY KEY )"); @@ -1759,7 +1778,9 @@ private static SQLFragment getTempChromInfoIdsDependentDeleteSql(TableInfo fromT return new SQLFragment("DELETE FROM " + fromTable + " WHERE " + fromFk + " IN (SELECT Id FROM " + tempIdsTable).append(")"); } - /** Actually delete runs that have been marked as deleted from the database */ + /** + * Actually delete runs that have been marked as deleted from the database + */ private static void purgeDeletedRuns() { // Delete from FoldChange @@ -1918,7 +1939,7 @@ private static void removeCachedResults() // Get a list of deleted runs SQLFragment sql = new SQLFragment("SELECT Id FROM " + getTableInfoRuns() + " WHERE Deleted = ?", true); List deletedRunIds = new SqlSelector(getSchema(), sql).getArrayList(Long.class); - if(!deletedRunIds.isEmpty()) + if (!deletedRunIds.isEmpty()) { ModificationManager.removeRunCachedResults(deletedRunIds); PeptideManager.removeRunCachedResults(deletedRunIds); @@ -1940,7 +1961,7 @@ public static void deleteTransitionChromInfoDependent(TableInfo tableInfo, SQLFr public static void deletePrecursorChromInfoDependent(TableInfo tableInfo) { execute(" DELETE FROM " + tableInfo + - " WHERE PrecursorChromInfoId IN (SELECT pci.Id FROM " + getTableInfoPrecursorChromInfo() + " pci "+ + " WHERE PrecursorChromInfoId IN (SELECT pci.Id FROM " + getTableInfoPrecursorChromInfo() + " pci " + " INNER JOIN " + getTableInfoSampleFile() + " s ON pci.SampleFileId = s.Id " + " INNER JOIN " + getTableInfoReplicate() + " rep ON s.ReplicateId = rep.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON rep.RunId = r.Id " + @@ -1950,7 +1971,7 @@ public static void deletePrecursorChromInfoDependent(TableInfo tableInfo) public static void deleteGeneralMoleculeChromInfoDependent(TableInfo tableInfo) { execute(" DELETE FROM " + tableInfo + - " WHERE PeptideChromInfoId IN (SELECT mci.Id FROM " + getTableInfoGeneralMoleculeChromInfo() + " mci "+ + " WHERE PeptideChromInfoId IN (SELECT mci.Id FROM " + getTableInfoGeneralMoleculeChromInfo() + " mci " + " INNER JOIN " + getTableInfoSampleFile() + " s ON mci.SampleFileId = s.Id " + " INNER JOIN " + getTableInfoReplicate() + " rep ON s.ReplicateId = rep.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON rep.RunId = r.Id " + @@ -1960,8 +1981,8 @@ public static void deleteGeneralMoleculeChromInfoDependent(TableInfo tableInfo) public static void deleteGeneralTransitionDependent(TableInfo tableInfo, String colName, SQLFragment whereClause) { execute(new SQLFragment(" DELETE FROM " + tableInfo + - " WHERE " + colName + " IN (SELECT gt.Id FROM " + getTableInfoGeneralTransition() + " gt "+ - " INNER JOIN " + getTableInfoGeneralPrecursor() + " gp ON gt.GeneralPrecursorId = gp.Id "+ + " WHERE " + colName + " IN (SELECT gt.Id FROM " + getTableInfoGeneralTransition() + " gt " + + " INNER JOIN " + getTableInfoGeneralPrecursor() + " gp ON gt.GeneralPrecursorId = gp.Id " + " INNER JOIN " + getTableInfoGeneralMolecule() + " gm ON gp.GeneralMoleculeId = gm.Id " + " INNER JOIN " + getTableInfoPeptideGroup() + " pg ON gm.PeptideGroupId = pg.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON pg.RunId = r.Id ").append(whereClause).append(")")); @@ -1979,7 +2000,7 @@ public static void deleteSampleFileDependent(TableInfo tableInfo, SQLFragment wh private static void deleteGeneralPrecursorDependent(TableInfo tableInfo, String colName) { execute(" DELETE FROM " + tableInfo + - " WHERE " + colName + " IN (SELECT gp.Id FROM " + getTableInfoGeneralPrecursor() + " gp "+ + " WHERE " + colName + " IN (SELECT gp.Id FROM " + getTableInfoGeneralPrecursor() + " gp " + " INNER JOIN " + getTableInfoGeneralMolecule() + " gm ON gp.GeneralMoleculeId = gm.Id " + " INNER JOIN " + getTableInfoPeptideGroup() + " pg ON gm.PeptideGroupId = pg.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON pg.RunId = r.Id " + @@ -1989,7 +2010,7 @@ private static void deleteGeneralPrecursorDependent(TableInfo tableInfo, String private static void deleteGeneralMoleculeDependent(TableInfo tableInfo, String colName) { execute(" DELETE FROM " + tableInfo + - " WHERE " + colName + " IN (SELECT gm.Id FROM " + getTableInfoGeneralMolecule() + " gm "+ + " WHERE " + colName + " IN (SELECT gm.Id FROM " + getTableInfoGeneralMolecule() + " gm " + " INNER JOIN " + getTableInfoPeptideGroup() + " pg ON gm.PeptideGroupId = pg.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON pg.RunId = r.Id " + " WHERE r.Deleted = ?)", true); @@ -1998,7 +2019,7 @@ private static void deleteGeneralMoleculeDependent(TableInfo tableInfo, String c private static void deletePeptideGroupDependent(TableInfo tableInfo) { execute(" DELETE FROM " + tableInfo + - " WHERE PeptideGroupId IN (SELECT pg.Id FROM " + getTableInfoPeptideGroup() + " pg "+ + " WHERE PeptideGroupId IN (SELECT pg.Id FROM " + getTableInfoPeptideGroup() + " pg " + " INNER JOIN " + getTableInfoRuns() + " r ON pg.RunId = r.Id " + " WHERE r.Deleted = ?)", true); } @@ -2012,7 +2033,7 @@ private static void deleteRunDependent(TableInfo tableInfo) private static void deleteReplicateDependent(TableInfo tableInfo) { execute(" DELETE FROM " + tableInfo + - " WHERE ReplicateId IN (SELECT rep.Id FROM " + getTableInfoReplicate() + " rep "+ + " WHERE ReplicateId IN (SELECT rep.Id FROM " + getTableInfoReplicate() + " rep " + " INNER JOIN " + getTableInfoRuns() + " r ON rep.RunId = r.Id " + " WHERE r.Deleted = ?)", true); } @@ -2025,9 +2046,9 @@ private static void deleteSampleFileDependent(TableInfo tableInfo) private static void deleteTransitionPredictionSettingsDependent() { execute("DELETE FROM " + getTableInfoPredictorSettings() + " WHERE PredictorId IN (SELECT Id FROM " + - getTableInfoPredictor() + " WHERE " + - "Id IN (SELECT CePredictorId FROM " + getTableInfoTransitionPredictionSettings() + " tps, " + getTableInfoRuns() + " r WHERE r.Id = tps.RunId AND r.Deleted = ?)" + - "OR Id IN (SELECT DpPredictorId FROM " + getTableInfoTransitionPredictionSettings() + " tps, " + getTableInfoRuns() + " r WHERE r.Id = tps.RunId AND r.Deleted = ?))" + getTableInfoPredictor() + " WHERE " + + "Id IN (SELECT CePredictorId FROM " + getTableInfoTransitionPredictionSettings() + " tps, " + getTableInfoRuns() + " r WHERE r.Id = tps.RunId AND r.Deleted = ?)" + + "OR Id IN (SELECT DpPredictorId FROM " + getTableInfoTransitionPredictionSettings() + " tps, " + getTableInfoRuns() + " r WHERE r.Id = tps.RunId AND r.Deleted = ?))" , true, true); List predictorsToDelete = getPredictorsToDelete(); @@ -2101,7 +2122,7 @@ public static void deleteiRTscales(Container c) public static List getIrtScaleIds(Container c) { SimpleFilter conFil = SimpleFilter.createContainerFilter(c); - return new TableSelector(TargetedMSManager.getTableInfoiRTScale().getColumn(FieldKey.fromParts("id")), conFil , null).getArrayList(Integer.class); + return new TableSelector(TargetedMSManager.getTableInfoiRTScale().getColumn(FieldKey.fromParts("id")), conFil, null).getArrayList(Integer.class); } // return the ModuleProperty value for "TARGETED_MS_FOLDER_TYPE" @@ -2144,7 +2165,7 @@ public static void renameRun(long runId, String newDescription, User user) throw return; new SqlExecutor(getSchema()).execute("UPDATE " + getTableInfoRuns() + " SET Description=? WHERE Id = ?", - newDescription, runId); + newDescription, runId); TargetedMSRun run = getRun(runId); if (run != null) { @@ -2158,7 +2179,9 @@ public static void renameRun(long runId, String newDescription, User user) throw } } - /** @return the sample file if it has already been imported in the container */ + /** + * @return the sample file if it has already been imported in the container + */ @Nullable public static Replicate getReplicate(long replicateId, Container container) { @@ -2166,19 +2189,23 @@ public static Replicate getReplicate(long replicateId, Container container) sql.append(getTableInfoReplicate(), "rep"); sql.append(", "); sql.append(getTableInfoRuns(), "r"); - sql.append( " WHERE r.Id = rep.RunId AND rep.Id = ? AND r.Container = ? "); + sql.append(" WHERE r.Id = rep.RunId AND rep.Id = ? AND r.Container = ? "); sql.add(replicateId); sql.add(container); return new SqlSelector(getSchema(), sql).getObject(Replicate.class); } - /** @return the sample file if it has already been imported in the container */ + /** + * @return the sample file if it has already been imported in the container + */ public static List getSampleFile(String filePath, Date acquiredTime, Container container) { return getSampleFile(filePath, acquiredTime, container, true); } - /** @return the sample file if it has already been imported in the container */ + /** + * @return the sample file if it has already been imported in the container + */ @Nullable public static SampleFile getSampleFile(long id, Container container) { @@ -2225,13 +2252,13 @@ public static List getSampleFiles(Container container, @Nullable SQL */ public static List getMatchingSampleFiles(@NotNull SampleFile sampleFile, Container container) { - if(sampleFile.getAcquiredTime() != null) + if (sampleFile.getAcquiredTime() != null) { // Issue 38270. A file may have been imported from a different path in a previous document. // If SampleFile has an acquired time check for a file with same name and acquired time. String filePath = sampleFile.getFilePath(); String fileName = FilenameUtils.getName(filePath); - if(!StringUtils.isBlank(fileName) && fileName.length() < filePath.length()) + if (!StringUtils.isBlank(fileName) && fileName.length() < filePath.length()) { fileName = filePath.substring(filePath.indexOf(fileName) - 1); // Include the separator char } @@ -2246,7 +2273,7 @@ public static List getMatchingSampleFiles(@NotNull SampleFile sample private static List getSampleFile(String filePath, Date acquiredTime, Container container, boolean fullPath) { SQLFragment sql = new SQLFragment(); - if(fullPath) + if (fullPath) { sql.append(" sf.FilePath = ? "); sql.add(filePath); @@ -2256,7 +2283,7 @@ private static List getSampleFile(String filePath, Date acquiredTime sql.append(" sf.FilePath LIKE ? "); sql.add("%" + getSqlDialect().encodeLikeOpSearchString(filePath)); } - if(acquiredTime == null) + if (acquiredTime == null) sql.append(" AND sf.AcquiredTime IS NULL"); else { @@ -2326,13 +2353,13 @@ public static Map> getClustergrammerQuery(User user, Map> intensities = new HashMap<>(); - for (Map rowMap:new TableSelector(table).getMapArray()) + for (Map rowMap : new TableSelector(table).getMapArray()) { - List columns = table.getColumns(); + List columns = table.getColumns(); List values = new ArrayList<>(); - for(ColumnInfo column : columns) + for (ColumnInfo column : columns) { //Skip pivot column and row name column String colName = column.getName(); @@ -2355,10 +2382,10 @@ public static Map> getClustergrammerQuery(User user, MathStat stats = StatsService.get().getStats(primitiveValues); - String proteinName = (String)rowMap.get(rowHeadingColumnName); + String proteinName = (String) rowMap.get(rowHeadingColumnName); Map intensityMap = new TreeMap<>(); boolean hasValue = false; - for(ColumnInfo column : columns) + for (ColumnInfo column : columns) { String colName = column.getName(); if (colName.compareToIgnoreCase(intensityColumnName) == 0 || colName.compareToIgnoreCase(rowHeadingColumnName) == 0) @@ -2397,7 +2424,7 @@ public static Map> getClustergrammerQuery(User user, private static Double getValue(Object o) { - Double value = (Double)JdbcType.DOUBLE.convert(o); + Double value = (Double) JdbcType.DOUBLE.convert(o); if (value == null || value == 0.0) { return null; @@ -2410,6 +2437,7 @@ public static List getAllQCMetricConfigurations(TargetedM { return _metricCache.get(schema.getContainer(), schema, null); } + public static List getEnabledQCMetricConfigurations(TargetedMSSchema schema) { List result = new ArrayList<>(); @@ -2427,7 +2455,7 @@ public List getSampleFileInfos(Container container, User user, I { TargetedMSSchema schema = new TargetedMSSchema(user, container); List enabledQCMetricConfigurations = getEnabledQCMetricConfigurations(schema); - if(!enabledQCMetricConfigurations.isEmpty()) + if (!enabledQCMetricConfigurations.isEmpty()) { List guideSets = TargetedMSManager.getGuideSets(container, user); Map metricMap = enabledQCMetricConfigurations.stream().collect(Collectors.toMap(QCMetricConfiguration::getId, Function.identity())); @@ -2633,7 +2661,7 @@ public static boolean containerHasLists(Container container) public static boolean containerHasDocVersions(Container container) { ExperimentService svc = ExperimentService.get(); - return new SqlSelector(svc.getSchema(), new SQLFragment("SELECT r.rowId FROM ", Boolean.FALSE, container ) + return new SqlSelector(svc.getSchema(), new SQLFragment("SELECT r.rowId FROM ", Boolean.FALSE, container) .append(svc.getTinfoExperimentRun(), "r") .append(" INNER JOIN ").append(TargetedMSManager.getTableInfoRuns(), "tRuns") .append(" ON (tRuns.ExperimentRunLSID = r.lsid AND tRuns.Deleted = ?)") @@ -2645,7 +2673,7 @@ public static void updateModifiedAreaProportions(@Nullable Logger log, @NotNull final String suffix = StringUtilsLabKey.getPaddedUniquifier(9); final String precursorGroupingsTableName = getSqlDialect().getTempTablePrefix() + "PrecursorGroupings" + suffix; final String moleculeGroupingsTableName = getSqlDialect().getTempTablePrefix() + "MoleculeGroupings" + suffix; - final String areasTableName = getSqlDialect().getTempTablePrefix() + "Areas" + suffix; + final String areasTableName = getSqlDialect().getTempTablePrefix() + "Areas" + suffix; if (log != null) { @@ -2697,7 +2725,7 @@ public static void updateModifiedAreaProportions(@Nullable Logger log, @NotNull .append("WHERE g.PrecursorId = targetedms.precursorchrominfo.PrecursorId AND \n") .append("a.SampleFileId = targetedms.precursorchrominfo.SampleFileId) X) "); updatePrecursorSQL.append(" WHERE PrecursorId IN \n") - .append("(SELECT PrecursorId FROM ").append(precursorGroupingsTableName).append(")"); + .append("(SELECT PrecursorId FROM ").append(precursorGroupingsTableName).append(")"); if (log != null) { @@ -2714,7 +2742,7 @@ public static void updateModifiedAreaProportions(@Nullable Logger log, @NotNull .append("WHERE g.GeneralMoleculeId = targetedms.generalmoleculechrominfo.GeneralMoleculeId AND \n") .append("a.SampleFileId = targetedms.generalmoleculechrominfo.SampleFileId) X)"); updateMoleculeSQL.append(" WHERE GeneralMoleculeId IN \n") - .append("(SELECT GeneralMoleculeId FROM ").append(moleculeGroupingsTableName).append(")"); + .append("(SELECT GeneralMoleculeId FROM ").append(moleculeGroupingsTableName).append(")"); if (log != null) { @@ -2901,7 +2929,7 @@ else if (traceValue != null && values[i] >= traceValue) if (!valuesToStore.isEmpty()) { - valuesToStore.forEach((key,val) -> { + valuesToStore.forEach((key, val) -> { QCTraceMetricValues qcTraceMetricValues = new QCTraceMetricValues(); qcTraceMetricValues.setMetric(qcMetricConfiguration.getId()); qcTraceMetricValues.setSampleFileId(key.getSampleFileId()); @@ -2940,7 +2968,7 @@ public static List getKeywords(long sequenceId) return keywords; } - public static Map getQCFolderDateRange(Container container) + public static Map getQCFolderDateRange(Container container) { var sql = new SQLFragment("SELECT MIN(sf.AcquiredTime) AS startDate, MAX(sf.AcquiredTime) AS endDate "); sql.append(" FROM ").append(getTableInfoSampleFile(), "sf"); @@ -2977,6 +3005,18 @@ private QueryUpdateService getNicknameUpdateService(User user, Container contain return Objects.requireNonNull(table.getUpdateService()); } + + public static String getInstrumentNickName(Container container) + { + SimpleFilter filter = SimpleFilter.createContainerFilter(container); + List nicknames = new TableSelector(getTableInfoInstrumentNickname(), filter, null).getArrayList(InstrumentNickname.class); + return nicknames.stream() + .map(InstrumentNickname::getNickname) + .distinct() + .collect(Collectors.joining(",")); + } + + public void deleteNickname(InstrumentNickname name, User user) throws SQLException, BatchValidationException, QueryUpdateServiceException, InvalidKeyException { getNicknameUpdateService(user, name.getContainer()). @@ -3002,4 +3042,15 @@ public void saveNickname(InstrumentNickname name, User user) throws SQLException insertRows(user, name.getContainer(), Arrays.asList(row), errors, null, null); } } + + public static boolean isQCAnnotationTypeShareable(int qcAnnotationTypeId) + { + SQLFragment sql = new SQLFragment("SELECT IsShareable FROM "); + sql.append(getTableInfoQCAnnotationType()); + sql.append(" WHERE Id = ?"); + sql.add(qcAnnotationTypeId); + + Boolean isShareable = new SqlSelector(getSchema(), sql).getObject(Boolean.class); + return isShareable != null && isShareable; + } } diff --git a/src/org/labkey/targetedms/TargetedMSModule.java b/src/org/labkey/targetedms/TargetedMSModule.java index f613c4fbe..921e46202 100644 --- a/src/org/labkey/targetedms/TargetedMSModule.java +++ b/src/org/labkey/targetedms/TargetedMSModule.java @@ -231,7 +231,7 @@ public String getName() @Override public Double getSchemaVersion() { - return 26.001; + return 26.003; } @Override diff --git a/src/org/labkey/targetedms/query/QCAnnotationTable.java b/src/org/labkey/targetedms/query/QCAnnotationTable.java index bac7b231e..715886975 100644 --- a/src/org/labkey/targetedms/query/QCAnnotationTable.java +++ b/src/org/labkey/targetedms/query/QCAnnotationTable.java @@ -15,13 +15,25 @@ */ package org.labkey.targetedms.query; +import org.labkey.api.data.Container; import org.labkey.api.data.ContainerFilter; +import org.labkey.api.data.TableInfo; import org.labkey.api.gwt.client.AuditBehaviorType; +import org.labkey.api.query.DefaultQueryUpdateService; +import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.QueryForeignKey; +import org.labkey.api.query.QueryUpdateService; +import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.query.SimpleUserSchema; +import org.labkey.api.query.ValidationException; +import org.labkey.api.security.User; import org.labkey.targetedms.TargetedMSManager; import org.labkey.targetedms.TargetedMSSchema; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + import static org.labkey.targetedms.query.GuideSetTable.appendFormatLabel; /** @@ -44,4 +56,33 @@ public QCAnnotationTable(TargetedMSSchema schema, ContainerFilter cf) appendFormatLabel(getMutableColumn("EndDate")); setAuditBehavior(AuditBehaviorType.DETAILED); } + + @Override + public QueryUpdateService getUpdateService() + { + TableInfo table = getRealTable(); + if (table != null) + { + return new DefaultQueryUpdateService(this, getRealTable()) + { + @Override + protected Map insertRow(User user, Container container, Map row) throws SQLException, ValidationException, QueryUpdateServiceException, DuplicateKeyException + { + // Check if the QCAnnotationType is shareable + int qcAnnotationTypeId = (Integer) row.get("QCAnnotationTypeId"); + boolean isShareable = TargetedMSManager.isQCAnnotationTypeShareable(qcAnnotationTypeId); + + if (isShareable) + { + // Check if the current container has an instrument and include its nickname + var instrumentNickName = TargetedMSManager.getInstrumentNickName(getContainer()); + row.put("instrument", instrumentNickName); + } + + return super.insertRow(user, container, row); + } + }; + } + return null; + } } From 0d84db36fced63caa3012a6a33b01bc85022d352 Mon Sep 17 00:00:00 2001 From: ankurjuneja Date: Thu, 26 Feb 2026 10:13:35 -0800 Subject: [PATCH 2/6] display shared annotations across containers --- webapp/TargetedMS/js/QCTrendPlotPanel.js | 32 ++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/webapp/TargetedMS/js/QCTrendPlotPanel.js b/webapp/TargetedMS/js/QCTrendPlotPanel.js index acbf03751..2f02fc0b1 100644 --- a/webapp/TargetedMS/js/QCTrendPlotPanel.js +++ b/webapp/TargetedMS/js/QCTrendPlotPanel.js @@ -1303,7 +1303,21 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { var config = this.getReportConfig(); - var annotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color FROM qcannotation qca JOIN qcannotationtype qcat ON qcat.Id = qca.QCAnnotationTypeId"; + // First, get the current container's instrument nickname to filter shareable annotations + LABKEY.Query.executeSql({ + schemaName: 'targetedms', + sql: "SELECT DISTINCT Nickname FROM InstrumentNickname", + scope: this, + success: function(data) { + var instrumentNickname = data.rows.length > 0 ? data.rows[0]['Nickname'] : null; + this.fetchAnnotations(config, instrumentNickname); + }, + failure: this.failureHandler + }); + }, + + fetchAnnotations: function(config, instrumentNickname) { + var annotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color, qca.Container FROM qcannotation qca JOIN qcannotationtype qcat ON qcat.Id = qca.QCAnnotationTypeId"; // Filter on start/end dates var separator = " WHERE "; @@ -1313,13 +1327,27 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { } if (config.EndDate) { annotationSql += separator + "CAST(Date AS Date) <= '" + config.EndDate + "'"; + separator = " AND "; } + // Filter logic: + // 1. All annotations from the shared container + // 2. Annotations from the current container + // 3. Shareable annotations from any container with a matching instrument + var sharedContainer = LABKEY.Security.getSharedContainer(); + + var containerClause = "qca.Container.Name = '" + sharedContainer + "'"; + containerClause += " OR (qca.Container = '" + LABKEY.Security.currentContainer.id + "')"; + if (instrumentNickname) { + containerClause += " OR (qcat.IsShareable = TRUE AND qca.instrument = '" + instrumentNickname + "')"; + } + annotationSql += separator + "(" + containerClause + ")"; + LABKEY.Query.executeSql({ schemaName: 'targetedms', sql: annotationSql, sort: 'Date', - containerFilter: LABKEY.Query.containerFilter.currentPlusProjectAndShared, + containerFilter: LABKEY.Query.containerFilter.allFolders, scope: this, success: this.processAnnotationData, failure: this.failureHandler From 565e44fd9680e6b4478d20cc7316e21c2ac9af8f Mon Sep 17 00:00:00 2001 From: ankurjuneja Date: Wed, 4 Mar 2026 18:07:46 -0800 Subject: [PATCH 3/6] Share instrument annotations across QC folders --- .../postgresql/targetedms-26.002-26.003.sql | 3 +- resources/schemas/targetedms.xml | 3 +- .../labkey/targetedms/TargetedMSManager.java | 154 +++++++++++++++++- .../targetedms/query/QCAnnotationTable.java | 53 ++++-- webapp/TargetedMS/js/QCTrendPlotPanel.js | 119 ++++++++++---- 5 files changed, 284 insertions(+), 48 deletions(-) diff --git a/resources/schemas/dbscripts/postgresql/targetedms-26.002-26.003.sql b/resources/schemas/dbscripts/postgresql/targetedms-26.002-26.003.sql index 2ae3b39af..d82c6dba8 100644 --- a/resources/schemas/dbscripts/postgresql/targetedms-26.002-26.003.sql +++ b/resources/schemas/dbscripts/postgresql/targetedms-26.002-26.003.sql @@ -1,2 +1,3 @@ ALTER TABLE targetedms.QCAnnotationType ADD COLUMN isShareable BOOLEAN DEFAULT FALSE; -ALTER TABLE targetedms.QCAnnotation ADD COLUMN instrument VARCHAR(255); \ No newline at end of file +ALTER TABLE targetedms.QCAnnotation ADD COLUMN instrumentModel VARCHAR(255); +ALTER TABLE targetedms.QCAnnotation ADD COLUMN instrumentSerialNumber VARCHAR(255); \ No newline at end of file diff --git a/resources/schemas/targetedms.xml b/resources/schemas/targetedms.xml index 7a631b1fa..11f0d83d0 100644 --- a/resources/schemas/targetedms.xml +++ b/resources/schemas/targetedms.xml @@ -1308,7 +1308,8 @@ Annotation Type The category of the event - + +
diff --git a/src/org/labkey/targetedms/TargetedMSManager.java b/src/org/labkey/targetedms/TargetedMSManager.java index 17186b4f3..8fe94796c 100644 --- a/src/org/labkey/targetedms/TargetedMSManager.java +++ b/src/org/labkey/targetedms/TargetedMSManager.java @@ -17,6 +17,8 @@ package org.labkey.targetedms; import com.google.common.base.Joiner; +import lombok.Getter; +import lombok.Setter; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; @@ -3074,15 +3076,163 @@ private QueryUpdateService getNicknameUpdateService(User user, Container contain return Objects.requireNonNull(table.getUpdateService()); } + public static class InstrumentDetails + { + @Getter @Setter + private String instrumentSerialNumber; + @Getter @Setter + private String model; + @Getter @Setter + private Long instrumentId; + + public InstrumentDetails() + { + } + } - public static String getInstrumentNickName(Container container) + public static List getInstrumentDetails(Container container) + { + SQLFragment sql = new SQLFragment("SELECT DISTINCT sf.InstrumentSerialNumber, i.Model, i.Id AS InstrumentId FROM "); + sql.append(getTableInfoSampleFile(), "sf"); + sql.append(" INNER JOIN "); + sql.append(getTableInfoInstrument(), "i"); + sql.append(" ON sf.InstrumentId = i.Id "); + sql.append(" INNER JOIN "); + sql.append(getTableInfoReplicate(), "rep"); + sql.append(" ON sf.ReplicateId = rep.Id "); + sql.append(" INNER JOIN "); + sql.append(getTableInfoRuns(), "r"); + sql.append(" ON rep.RunId = r.Id "); + sql.append(" WHERE r.Container = ?"); + sql.add(container); + + return new SqlSelector(getSchema(), sql).getArrayList(InstrumentDetails.class); + + } + + public static List getInstrumentNickName(Container container) { SimpleFilter filter = SimpleFilter.createContainerFilter(container); + List nicknames = new TableSelector(getTableInfoInstrumentNickname(), filter, null).getArrayList(InstrumentNickname.class); return nicknames.stream() .map(InstrumentNickname::getNickname) .distinct() - .collect(Collectors.joining(",")); + .collect(Collectors.toList()); + } + + /** + * Returns the instrument nickname for the given instrument, similar to the logic in SampleFileTable. + * First checks for a custom nickname matching the serial number and model, then falls back to a default name. + * + * @param container The container to search in (including project and shared containers) + * @param user The user for permission checking + * @param instrumentId The instrument ID + * @return The resolved nickname, or null if no instrument is found + */ + @Nullable + public static String getInstrumentNickName(@NotNull Container container, @NotNull User user, + @Nullable Long instrumentId) + { + if (instrumentId == null) + { + return null; + } + + TargetedMSSchema schema = new TargetedMSSchema(user, container); + + // Get the serial number and model from the sample file table + SQLFragment sampleFileSQL = new SQLFragment("SELECT DISTINCT sf.InstrumentSerialNumber, i.Model FROM "); + sampleFileSQL.append(getTableInfoSampleFile(), "sf"); + sampleFileSQL.append(" INNER JOIN "); + sampleFileSQL.append(getTableInfoInstrument(), "i"); + sampleFileSQL.append(" ON sf.InstrumentId = i.Id WHERE i.Id = ?").add(instrumentId); + + Map sampleFileData = new SqlSelector(getSchema(), sampleFileSQL).getMap(); + String serialNumber = sampleFileData != null ? (String) sampleFileData.get("InstrumentSerialNumber") : null; + String instrumentModel = sampleFileData != null ? (String) sampleFileData.get("Model") : null; + + // First, try to find a custom nickname matching the serial number and model + // Join conditions match the logic in SampleFileTable: + // - SerialNumber matches or both are NULL + // - Model matches or both are NULL + SimpleFilter filter = new SimpleFilter(); + + // Build a filter for nickname lookup with proper NULL handling + if (serialNumber != null) + { + filter.addCondition(FieldKey.fromParts("SerialNumber"), serialNumber); + } + else + { + filter.addCondition(FieldKey.fromParts("SerialNumber"), null, CompareType.ISBLANK); + } + + if (instrumentModel != null) + { + filter.addCondition(new SimpleFilter.OrClause( + new CompareType.EqualsCompareClause(FieldKey.fromParts("Model"), CompareType.EQUAL, instrumentModel), + new CompareType.CompareClause(FieldKey.fromParts("Model"), CompareType.ISBLANK, null) + )); + } + else + { + filter.addCondition(FieldKey.fromParts("Model"), null, CompareType.ISBLANK); + } + + // Search in current container, project, and shared containers, prioritizing by container proximity + TableInfo nicknameTable = schema.getTableOrThrow(TABLE_INSTRUMENT_NICKNAME, + ContainerFilter.Type.CurrentPlusProjectAndShared.create(container, user)); + + List matches = new TableSelector(nicknameTable, filter, null).getArrayList(InstrumentNickname.class); + + // Sort by container proximity: current > project > shared + Container shared = ContainerManager.getSharedContainer(); + Container project = container.getProject(); + + matches.sort((a, b) -> { + int aScore = getContainerScore(a.getContainer(), container, project, shared); + int bScore = getContainerScore(b.getContainer(), container, project, shared); + return Integer.compare(bScore, aScore); // Higher score first + }); + + if (!matches.isEmpty()) + { + return matches.get(0).getNickname(); + } + + // Fall back to default naming: COALESCE(Model + ' - ' + SerialNumber, SerialNumber, Model) + if (instrumentModel != null && serialNumber != null) + { + return instrumentModel + " - " + serialNumber; + } + else if (serialNumber != null) + { + return serialNumber; + } + else if (instrumentModel != null) + { + return instrumentModel; + } + + return null; + } + + private static int getContainerScore(Container c, Container current, Container project, Container shared) + { + if (c.equals(current)) + { + return 3; + } + else if (project != null && c.equals(project)) + { + return 2; + } + else if (shared != null && c.equals(shared)) + { + return 1; + } + return 0; } diff --git a/src/org/labkey/targetedms/query/QCAnnotationTable.java b/src/org/labkey/targetedms/query/QCAnnotationTable.java index 715886975..5af2f2484 100644 --- a/src/org/labkey/targetedms/query/QCAnnotationTable.java +++ b/src/org/labkey/targetedms/query/QCAnnotationTable.java @@ -15,10 +15,12 @@ */ package org.labkey.targetedms.query; +import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerFilter; import org.labkey.api.data.TableInfo; import org.labkey.api.gwt.client.AuditBehaviorType; +import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.DefaultQueryUpdateService; import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.QueryForeignKey; @@ -66,20 +68,51 @@ public QueryUpdateService getUpdateService() return new DefaultQueryUpdateService(this, getRealTable()) { @Override - protected Map insertRow(User user, Container container, Map row) throws SQLException, ValidationException, QueryUpdateServiceException, DuplicateKeyException + public List> insertRows(User user, Container container, List> rows, BatchValidationException errors, @Nullable Map configParameters, @Nullable Map extraScriptContext) throws SQLException, QueryUpdateServiceException, DuplicateKeyException { - // Check if the QCAnnotationType is shareable - int qcAnnotationTypeId = (Integer) row.get("QCAnnotationTypeId"); - boolean isShareable = TargetedMSManager.isQCAnnotationTypeShareable(qcAnnotationTypeId); - - if (isShareable) + List> resultRows = new java.util.ArrayList<>(); + for (Map row : rows) { - // Check if the current container has an instrument and include its nickname - var instrumentNickName = TargetedMSManager.getInstrumentNickName(getContainer()); - row.put("instrument", instrumentNickName); + try + { + // Check if the QCAnnotationType is shareable + int qcAnnotationTypeId = (Integer) row.get("QCAnnotationTypeId"); + boolean isShareable = TargetedMSManager.isQCAnnotationTypeShareable(qcAnnotationTypeId); + + if (isShareable) + { + List instruments = TargetedMSManager.getInstrumentDetails(getContainer()); + if (instruments.isEmpty()) + { + resultRows.add(super.insertRow(user, container, row)); + } + else + { + for (TargetedMSManager.InstrumentDetails instrument : instruments) + { + Map newRow = new java.util.HashMap<>(row); + newRow.put("instrumentModel", instrument.getModel()); + newRow.put("instrumentSerialNumber", instrument.getInstrumentSerialNumber()); + newRow.put("Container", getContainer().getId()); + resultRows.add(super.insertRow(user, container, newRow)); + } + } + } + else + { + resultRows.add(super.insertRow(user, container, row)); + } + } + catch (ValidationException e) + { + errors.addRowError(e); + } } - return super.insertRow(user, container, row); + if (errors.hasErrors()) + return null; + + return resultRows; } }; } diff --git a/webapp/TargetedMS/js/QCTrendPlotPanel.js b/webapp/TargetedMS/js/QCTrendPlotPanel.js index 2f02fc0b1..dc1c5adf1 100644 --- a/webapp/TargetedMS/js/QCTrendPlotPanel.js +++ b/webapp/TargetedMS/js/QCTrendPlotPanel.js @@ -1301,55 +1301,106 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { getAnnotationData: function() { this.setLoadingMsg(); - var config = this.getReportConfig(); + let config = this.getReportConfig(); - // First, get the current container's instrument nickname to filter shareable annotations - LABKEY.Query.executeSql({ - schemaName: 'targetedms', - sql: "SELECT DISTINCT Nickname FROM InstrumentNickname", - scope: this, - success: function(data) { - var instrumentNickname = data.rows.length > 0 ? data.rows[0]['Nickname'] : null; - this.fetchAnnotations(config, instrumentNickname); - }, - failure: this.failureHandler - }); - }, - - fetchAnnotations: function(config, instrumentNickname) { - var annotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color, qca.Container FROM qcannotation qca JOIN qcannotationtype qcat ON qcat.Id = qca.QCAnnotationTypeId"; + let annotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color FROM qcannotation qca JOIN qcannotationtype qcat ON qcat.Id = qca.QCAnnotationTypeId"; // Filter on start/end dates - var separator = " WHERE "; + let dateFilter = ""; if (config.StartDate) { - annotationSql += separator + "CAST(Date AS Date) >= '" + config.StartDate + "'"; - separator = " AND "; + dateFilter += " AND CAST(Date AS Date) >= '" + config.StartDate + "'"; } if (config.EndDate) { - annotationSql += separator + "CAST(Date AS Date) <= '" + config.EndDate + "'"; - separator = " AND "; + dateFilter += " AND CAST(Date AS Date) <= '" + config.EndDate + "'"; } + annotationSql += " WHERE 1=1 " + dateFilter; - // Filter logic: - // 1. All annotations from the shared container - // 2. Annotations from the current container - // 3. Shareable annotations from any container with a matching instrument - var sharedContainer = LABKEY.Security.getSharedContainer(); + let handleAnnotationData = function(data) { + let annotationData = data ? data.rows : []; - var containerClause = "qca.Container.Name = '" + sharedContainer + "'"; - containerClause += " OR (qca.Container = '" + LABKEY.Security.currentContainer.id + "')"; - if (instrumentNickname) { - containerClause += " OR (qcat.IsShareable = TRUE AND qca.instrument = '" + instrumentNickname + "')"; - } - annotationSql += separator + "(" + containerClause + ")"; + // Check if there is an instrument attached to the current container from samplefile table. + LABKEY.Query.executeSql({ + schemaName: 'targetedms', + sql: "SELECT DISTINCT InstrumentId.Model, InstrumentSerialNumber, InstrumentNickname FROM samplefile", + scope: this, + success: function(instrumentData) { + if (instrumentData && instrumentData.rows && instrumentData.rows.length > 0) { + let instrumentFilter = ""; + let separator = ""; + for (let i = 0; i < instrumentData.rows.length; i++) { + let row = instrumentData.rows[i]; + let model = row["Model"]; + let serial = row["InstrumentSerialNumber"]; + let nickname = row["InstrumentNickname"]; + + instrumentFilter += separator + "("; + let innerSep = ""; + if (model) { + instrumentFilter += "(qca.instrumentModel = '" + model + "'"; + innerSep = " AND "; + } else { + instrumentFilter += "(qca.instrumentModel IS NULL"; + innerSep = " AND "; + } + + if (serial) { + instrumentFilter += innerSep + "qca.instrumentSerialNumber = '" + serial + "')"; + } else { + instrumentFilter += innerSep + "qca.instrumentSerialNumber IS NULL)"; + } + + if (nickname) { + instrumentFilter += " OR qca.instrumentNickName = '" + nickname + "'"; + } + instrumentFilter += ")"; + separator = " OR "; + } + + let sharedAnnotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color " + + "FROM qcannotation qca " + + "JOIN qcannotationtype qcat ON qcat.Id = qca.QCAnnotationTypeId " + + "WHERE qcat.IsShareable = true AND (" + instrumentFilter + ")" + dateFilter; + + LABKEY.Query.executeSql({ + schemaName: 'targetedms', + sql: sharedAnnotationSql, + containerFilter: LABKEY.Query.containerFilter.allFolders, + scope: this, + success: function(sharedData) { + if (sharedData && sharedData.rows) { + // add shared annotations but avoid duplicates if they were already in the first list + let existingIds = {}; + for (let j = 0; j < annotationData.length; j++) { + existingIds[annotationData[j].qcAnnotationId] = true; + } + for (let k = 0; k < sharedData.rows.length; k++) { + if (!existingIds[sharedData.rows[k].qcAnnotationId]) { + annotationData.push(sharedData.rows[k]); + } + } + } + this.processAnnotationData({rows: annotationData}); + }, + failure: this.failureHandler + }); + } else { + this.processAnnotationData({rows: annotationData}); + } + }, + failure: function() { + // if instrument fetch fails, just proceed with what we have + this.processAnnotationData({rows: annotationData}); + } + }); + }; LABKEY.Query.executeSql({ schemaName: 'targetedms', sql: annotationSql, sort: 'Date', - containerFilter: LABKEY.Query.containerFilter.allFolders, + containerFilter: LABKEY.Query.containerFilter.currentPlusProjectAndShared, scope: this, - success: this.processAnnotationData, + success: handleAnnotationData, failure: this.failureHandler }); }, From ba9b09a4e2f25ab5b9b473feb406cdc7c352f926 Mon Sep 17 00:00:00 2001 From: ankurjuneja Date: Wed, 4 Mar 2026 18:13:38 -0800 Subject: [PATCH 4/6] remove unused methods --- .../labkey/targetedms/TargetedMSManager.java | 345 +++++------------- 1 file changed, 99 insertions(+), 246 deletions(-) diff --git a/src/org/labkey/targetedms/TargetedMSManager.java b/src/org/labkey/targetedms/TargetedMSManager.java index 8fe94796c..0a4ad113c 100644 --- a/src/org/labkey/targetedms/TargetedMSManager.java +++ b/src/org/labkey/targetedms/TargetedMSManager.java @@ -359,7 +359,7 @@ public static TableInfo getTableInfoGeneralMoleculeChromInfo() public static TableInfo getTableInfoPeptideAreaRatio() { - return getSchema().getTable(TargetedMSSchema.TABLE_PEPTIDE_AREA_RATIO); + return getSchema().getTable(TargetedMSSchema.TABLE_PEPTIDE_AREA_RATIO); } public static TableInfo getTableInfoInstrument() @@ -502,7 +502,6 @@ public static TableInfo getTableInfoQCEmailNotifications() { return getSchema().getTable(TargetedMSSchema.TABLE_QC_EMAIL_NOTIFICATIONS); } - public static TableInfo getTableInfoAnnotationSettings() { return getSchema().getTable(TargetedMSSchema.TABLE_ANNOTATION_SETTINGS); @@ -547,7 +546,6 @@ public static TableInfo getTableInfoQCAnnotation() { return getSchema().getTable(TargetedMSSchema.TABLE_QC_ANNOTATION); } - public static TableInfo getTableInfoQuantificationSettings() { return getSchema().getTable(TargetedMSSchema.TABLE_QUANTIIFICATION_SETTINGS); @@ -623,9 +621,7 @@ public static TableInfo getTableInfoSkylineRunAuditLogEntry() return getSchema().getTable(TargetedMSSchema.TABLE_SKYLINE_RUN_AUDITLOG_ENTRY); } - /** - * View that's a CTE to pull in the RunId - */ + /** View that's a CTE to pull in the RunId */ public static TableInfo getTableInfoSkylineAuditLog() { return getSchema().getTable(TargetedMSSchema.TABLE_SKYLINE_AUDITLOG); @@ -721,22 +717,20 @@ public static TableInfo getTableInfoInstrumentUsagePayment() return getSchema().getTable(TargetedMSSchema.TABLE_INSTRUMENT_USAGE_PAYMENT); } - /** - * @return rowId for pipeline job that will perform the import asynchronously - */ + /** @return rowId for pipeline job that will perform the import asynchronously */ public static Long addRunToQueue(ViewBackgroundInfo info, - final Path path) throws XarFormatException, PipelineValidationException + final Path path) throws XarFormatException, PipelineValidationException { String description = "Skyline document import - " + FileUtil.getFileName(path); XarContext xarContext = new XarContext(description, info.getContainer(), info.getUser()); - User user = info.getUser(); + User user = info.getUser(); Container container = info.getContainer(); // If an entry does not already exist for this data file in exp.data create it now. - // This should happen only if a file was copied to the pipeline directory instead - // of being uploaded via the files browser. + // This should happen only if a file was copied to the pipeline directory instead + // of being uploaded via the files browser. ExpData expData = ExperimentService.get().getExpDataByURL(path, container); - if (expData == null) + if(expData == null) { XarSource source = new AbstractFileXarSource("Wrap Targeted MS Run", container, user) { @@ -804,7 +798,7 @@ private static void wrapRun(TargetedMSRun run, User user, Long jobId) throws Exp // Make sure that we have a protocol in this folder String protocolPrefix = run.isZipFile() ? TargetedMSModule.IMPORT_SKYZIP_PROTOCOL_OBJECT_PREFIX : - TargetedMSModule.IMPORT_SKYDOC_PROTOCOL_OBJECT_PREFIX; + TargetedMSModule.IMPORT_SKYDOC_PROTOCOL_OBJECT_PREFIX; Lsid lsid = new Lsid("Protocol.Folder-" + container.getRowId(), protocolPrefix); ExpProtocol protocol = ExperimentService.get().getExpProtocol(lsid.toString()); @@ -829,11 +823,11 @@ private static void wrapRun(TargetedMSRun run, User user, Long jobId) throws Exp outputDatas.put(expData, "sky"); ExpData expSkydData = null; - if (run.getSkydDataId() != null) + if(run.getSkydDataId() != null) { expSkydData = ExperimentService.get().getExpData(run.getSkydDataId()); } - if (expSkydData != null) + if(expSkydData != null) { outputDatas.put(expSkydData, "skyd"); } @@ -850,12 +844,12 @@ private static void wrapRun(TargetedMSRun run, User user, Long jobId) throws Exp } expRun = ExperimentService.get().saveSimpleExperimentRun(expRun, - Collections.emptyMap(), - inputDatas, - Collections.emptyMap(), - outputDatas, - Collections.emptyMap(), - info, _log, false); + Collections.emptyMap(), + inputDatas, + Collections.emptyMap(), + outputDatas, + Collections.emptyMap(), + info, _log, false); run.setExperimentRunLSID(expRun.getLSID()); TargetedMSManager.updateRun(run, user); @@ -922,7 +916,7 @@ public static TargetedMSRun getRunByDataId(long dataId) public static TargetedMSRun getRunByDataId(long dataId, Container c) { TargetedMSRun[] runs; - if (c != null) + if(c != null) { runs = getRuns("DataId = ? AND Deleted = ? AND Container = ?", dataId, Boolean.FALSE, c.getId()); } @@ -930,11 +924,11 @@ public static TargetedMSRun getRunByDataId(long dataId, Container c) { runs = getRuns("DataId = ? AND Deleted = ?", dataId, Boolean.FALSE); } - if (runs.length == 0) + if(runs.length == 0) { return null; } - if (runs.length == 1) + if(runs.length == 1) { return runs[0]; } @@ -949,7 +943,7 @@ public static TargetedMSRun getRunBySkydDataId(long skydDataId) public static TargetedMSRun getRunBySkydDataId(long skydDataId, Container c) { TargetedMSRun[] runs; - if (c != null) + if(c != null) { runs = getRuns("SkydDataId = ? AND Deleted = ? AND Container = ?", skydDataId, Boolean.FALSE, c.getId()); } @@ -957,11 +951,11 @@ public static TargetedMSRun getRunBySkydDataId(long skydDataId, Container c) { runs = getRuns("SkydDataId = ? AND Deleted = ?", skydDataId, Boolean.FALSE); } - if (runs.length == 0) + if(runs.length == 0) { return null; } - if (runs.length == 1) + if(runs.length == 1) { return runs[0]; } @@ -972,15 +966,15 @@ public static TargetedMSRun getRunBySkydDataId(long skydDataId, Container c) public static TargetedMSRun getRunByLsid(String lsid, Container c) { TargetedMSRun[] runs = getRuns("experimentrunlsid = ? AND container = ?", lsid, c.getId()); - if (runs.length == 0) + if(runs.length == 0) { return null; } - if (runs.length == 1) + if(runs.length == 1) { return runs[0]; } - throw new IllegalArgumentException("More than one TargetedMS runs found for LSID " + lsid); + throw new IllegalArgumentException("More than one TargetedMS runs found for LSID "+lsid); } public static boolean updateSkydDataId(ITargetedMSRun run, ExpData newSkydData, User user) @@ -1071,11 +1065,11 @@ public static void markRunsNotRepresentative(Container container, RunRepresentat { Collection representativeRunIds = Collections.emptyList(); - if (representativeState == RunRepresentativeDataState.Representative_Protein) + if(representativeState == RunRepresentativeDataState.Representative_Protein) { representativeRunIds = getProteinRepresentativeRunIds(container); } - else if (representativeState == RunRepresentativeDataState.Representative_Peptide) + else if(representativeState == RunRepresentativeDataState.Representative_Peptide) { representativeRunIds = getPeptideRepresentativeRunIds(container); } @@ -1086,7 +1080,7 @@ else if (representativeState == RunRepresentativeDataState.Representative_Peptid } SQLFragment updateSql = new SQLFragment(); - updateSql.append("UPDATE " + TargetedMSManager.getTableInfoRuns()); + updateSql.append("UPDATE "+TargetedMSManager.getTableInfoRuns()); updateSql.append(" SET RepresentativeDataState = ?"); updateSql.add(RunRepresentativeDataState.NotRepresentative.ordinal()); updateSql.append(" WHERE Container = ?"); @@ -1098,7 +1092,7 @@ else if (representativeState == RunRepresentativeDataState.Representative_Peptid // for the documents not yet imported. updateSql.append(" AND StatusId = ? "); updateSql.add(SkylineDocImporter.STATUS_SUCCESS); - updateSql.append(" AND Id NOT IN (" + StringUtils.join(representativeRunIds, ",") + ")"); + updateSql.append(" AND Id NOT IN ("+StringUtils.join(representativeRunIds, ",")+")"); new SqlExecutor(TargetedMSManager.getSchema()).execute(updateSql); } @@ -1106,11 +1100,11 @@ else if (representativeState == RunRepresentativeDataState.Representative_Peptid public static List getCurrentRepresentativeRunIds(Container container) { List representativeRunIds = null; - if (getFolderType(container) == TargetedMSService.FolderType.LibraryProtein) + if(getFolderType(container) == TargetedMSService.FolderType.LibraryProtein) { representativeRunIds = getCurrentProteinRepresentativeRunIds(container); } - else if (getFolderType(container) == TargetedMSService.FolderType.Library) + else if(getFolderType(container) == TargetedMSService.FolderType.Library) { representativeRunIds = getCurrentPeptideRepresentativeRunIds(container); } @@ -1126,8 +1120,8 @@ private static List getCurrentProteinRepresentativeRunIds(Container contai private static List getProteinRepresentativeRunIds(Container container) { return getProteinRepresentativeRunIds(container, RepresentativeDataState.Representative.ordinal(), - RepresentativeDataState.Deprecated.ordinal(), - RepresentativeDataState.Conflicted.ordinal()); + RepresentativeDataState.Deprecated.ordinal(), + RepresentativeDataState.Conflicted.ordinal()); } private static List getProteinRepresentativeRunIds(Container container, int... stateArray) @@ -1155,8 +1149,8 @@ private static List getCurrentPeptideRepresentativeRunIds(Container contai private static List getPeptideRepresentativeRunIds(Container container) { return getPeptideRepresentativeRunIds(container, RepresentativeDataState.Representative.ordinal(), - RepresentativeDataState.Deprecated.ordinal(), - RepresentativeDataState.Conflicted.ordinal()); + RepresentativeDataState.Deprecated.ordinal(), + RepresentativeDataState.Conflicted.ordinal()); } private static List getPeptideRepresentativeRunIds(Container container, int... stateArray) @@ -1189,9 +1183,7 @@ public static void updateRun(TargetedMSRun run, User user) Table.update(user, getTableInfoRuns(), run, run.getRunId()); } - /** - * Delete all of the TargetedMS runs in the container, including their experiment run wrappers - */ + /** Delete all of the TargetedMS runs in the container, including their experiment run wrappers */ public static void deleteIncludingExperimentWrapper(Container c, User user) { List runIds = new SqlSelector(getSchema(), "SELECT Id FROM " + getTableInfoRuns() + " WHERE Container = ?", c).getArrayList(Long.class); @@ -1237,13 +1229,9 @@ public InstrumentNickname getNickname(long id, User user) return name; } - private record NicknameKey(String serialNumber, String model) - { - } + private record NicknameKey(String serialNumber, String model) {} - /** - * @return the matches in order of closest to furthest match, injecting a virtual option if the list is empty - */ + /** @return the matches in order of closest to furthest match, injecting a virtual option if the list is empty */ public List getNickname(String name, TargetedMSSchema schema) { TableInfo info = schema.getTableOrThrow(TABLE_INSTRUMENT_NICKNAME, new ContainerFilter.CurrentPlusProjectAndShared(schema.getContainer(), schema.getUser())); @@ -1314,14 +1302,13 @@ private void addNameMatch(Map result, List runIds, Container c, User user, boolean containerDeletion) { List cantDelete = new ArrayList<>(); List runsToDelete = new ArrayList<>(); - for (long runId : runIds) + for (long runId: runIds) { TargetedMSRun run = getRun(runId); if (run == null || run.isDeleted()) @@ -1330,7 +1317,7 @@ public static void deleteRuns(List runIds, Container c, User user, boolean } if (!run.getContainer().equals(c) && !run.getContainer().hasPermission(user, DeletePermission.class)) { - cantDelete.add(runId); + cantDelete.add(runId); } runsToDelete.add(run); } @@ -1403,22 +1390,22 @@ public static void deleteRuns(List runIds, Container c, User user, boolean public static TargetedMSRun getRunForPrecursor(long precursorId) { - String sql = "SELECT run.* FROM " + - getTableInfoRuns() + " AS run, " + - getTableInfoPeptideGroup() + " AS pg, " + - getTableInfoGeneralMolecule() + " AS gm, " + - getTableInfoGeneralPrecursor() + " AS gp " + - "WHERE run.Id=pg.RunId " + - "AND pg.Id=gm.PeptideGroupId " + - "AND gm.Id=gp.GeneralMoleculeId " + - "AND gp.Id=?"; + String sql = "SELECT run.* FROM "+ + getTableInfoRuns()+" AS run, "+ + getTableInfoPeptideGroup()+" AS pg, "+ + getTableInfoGeneralMolecule()+" AS gm, "+ + getTableInfoGeneralPrecursor()+" AS gp "+ + "WHERE run.Id=pg.RunId "+ + "AND pg.Id=gm.PeptideGroupId "+ + "AND gm.Id=gp.GeneralMoleculeId "+ + "AND gp.Id=?"; SQLFragment sf = new SQLFragment(sql); sf.add(precursorId); TargetedMSRun run = new SqlSelector(getSchema(), sf).getObject(TargetedMSRun.class); - if (run == null) + if(run == null) { - throw new NotFoundException("No run found for precursor: " + precursorId); + throw new NotFoundException("No run found for precursor: "+precursorId); } return run; } @@ -1426,18 +1413,18 @@ public static TargetedMSRun getRunForPrecursor(long precursorId) @NotNull public static TargetedMSRun getRunForGeneralMolecule(long id) { - String sql = "SELECT run.* FROM " + - getTableInfoRuns() + " AS run, " + - getTableInfoPeptideGroup() + " AS pg, " + - getTableInfoGeneralMolecule() + " AS gm " + - "WHERE run.Id=pg.RunId " + - "AND pg.Id=gm.PeptideGroupId " + - "AND gm.Id=?"; + String sql = "SELECT run.* FROM "+ + getTableInfoRuns()+" AS run, "+ + getTableInfoPeptideGroup()+" AS pg, "+ + getTableInfoGeneralMolecule()+" AS gm "+ + "WHERE run.Id=pg.RunId "+ + "AND pg.Id=gm.PeptideGroupId "+ + "AND gm.Id=?"; SQLFragment sf = new SQLFragment(sql); sf.add(id); TargetedMSRun run = new SqlSelector(getSchema(), sf).getObject(TargetedMSRun.class); - if (run == null) + if(run == null) { throw new NotFoundException("No run found for general molecule: " + id); } @@ -1538,7 +1525,7 @@ private static boolean fileIsReferenced(@NotNull URI uri) sql.append(getTableInfoRuns(), "r"); sql.append(", "); sql.append(ExperimentService.get().getTinfoData(), "d"); - sql.append(" WHERE rep.Id = sf.ReplicateId AND rep.RunId = r.Id AND d.RowId = r.DataId AND d.DataFileUrl = ?"); + sql.append( " WHERE rep.Id = sf.ReplicateId AND rep.RunId = r.Id AND d.RowId = r.DataId AND d.DataFileUrl = ?"); sql.add(FileUtil.uriToString(uri)); return new SqlSelector(getSchema(), sql).exists(); @@ -1680,7 +1667,7 @@ public static String getSampleFileUploadFile(long sampleFileId) var map = new SqlSelector(getSchema(), sql).getMap(); String filePath = null; - if (map != null) + if(map != null) { filePath = (String) map.get("dataFileUrl"); } @@ -1767,7 +1754,7 @@ public static void purgeDeletedSampleFiles(List sampleFileIds) private static String createTempChromInfoIdsTable(TableInfo tableInfo, String tempTableNamePrefix, SQLFragment whereClause) { final String suffix = StringUtilsLabKey.getPaddedUniquifier(9); - final String tempTableName = TargetedMSManager.getSqlDialect().getTempTablePrefix() + tempTableNamePrefix + suffix; + final String tempTableName = TargetedMSManager.getSqlDialect().getTempTablePrefix() + tempTableNamePrefix + suffix; new SqlExecutor(TargetedMSSchema.getSchema()).execute("CREATE " + TargetedMSManager.getSqlDialect().getTempTableKeyword() + " TABLE " + tempTableName + " ( Id BIGINT NOT NULL PRIMARY KEY )"); @@ -1791,9 +1778,7 @@ private static SQLFragment getTempChromInfoIdsDependentDeleteSql(TableInfo fromT return new SQLFragment("DELETE FROM " + fromTable + " WHERE " + fromFk + " IN (SELECT Id FROM " + tempIdsTable).append(")"); } - /** - * Actually delete runs that have been marked as deleted from the database - */ + /** Actually delete runs that have been marked as deleted from the database */ private static void purgeDeletedRuns() { deleteRunDependent(getTableInfoPTMPercentsGroupedPrepivotCache()); @@ -1953,7 +1938,7 @@ private static void removeCachedResults() // Get a list of deleted runs SQLFragment sql = new SQLFragment("SELECT Id FROM " + getTableInfoRuns() + " WHERE Deleted = ?", true); List deletedRunIds = new SqlSelector(getSchema(), sql).getArrayList(Long.class); - if (!deletedRunIds.isEmpty()) + if(!deletedRunIds.isEmpty()) { ModificationManager.removeRunCachedResults(deletedRunIds); PeptideManager.removeRunCachedResults(deletedRunIds); @@ -1975,7 +1960,7 @@ public static void deleteTransitionChromInfoDependent(TableInfo tableInfo, SQLFr public static void deletePrecursorChromInfoDependent(TableInfo tableInfo) { execute(" DELETE FROM " + tableInfo + - " WHERE PrecursorChromInfoId IN (SELECT pci.Id FROM " + getTableInfoPrecursorChromInfo() + " pci " + + " WHERE PrecursorChromInfoId IN (SELECT pci.Id FROM " + getTableInfoPrecursorChromInfo() + " pci "+ " INNER JOIN " + getTableInfoSampleFile() + " s ON pci.SampleFileId = s.Id " + " INNER JOIN " + getTableInfoReplicate() + " rep ON s.ReplicateId = rep.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON rep.RunId = r.Id " + @@ -1985,7 +1970,7 @@ public static void deletePrecursorChromInfoDependent(TableInfo tableInfo) public static void deleteGeneralMoleculeChromInfoDependent(TableInfo tableInfo) { execute(" DELETE FROM " + tableInfo + - " WHERE PeptideChromInfoId IN (SELECT mci.Id FROM " + getTableInfoGeneralMoleculeChromInfo() + " mci " + + " WHERE PeptideChromInfoId IN (SELECT mci.Id FROM " + getTableInfoGeneralMoleculeChromInfo() + " mci "+ " INNER JOIN " + getTableInfoSampleFile() + " s ON mci.SampleFileId = s.Id " + " INNER JOIN " + getTableInfoReplicate() + " rep ON s.ReplicateId = rep.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON rep.RunId = r.Id " + @@ -1995,8 +1980,8 @@ public static void deleteGeneralMoleculeChromInfoDependent(TableInfo tableInfo) public static void deleteGeneralTransitionDependent(TableInfo tableInfo, String colName, SQLFragment whereClause) { execute(new SQLFragment(" DELETE FROM " + tableInfo + - " WHERE " + colName + " IN (SELECT gt.Id FROM " + getTableInfoGeneralTransition() + " gt " + - " INNER JOIN " + getTableInfoGeneralPrecursor() + " gp ON gt.GeneralPrecursorId = gp.Id " + + " WHERE " + colName + " IN (SELECT gt.Id FROM " + getTableInfoGeneralTransition() + " gt "+ + " INNER JOIN " + getTableInfoGeneralPrecursor() + " gp ON gt.GeneralPrecursorId = gp.Id "+ " INNER JOIN " + getTableInfoGeneralMolecule() + " gm ON gp.GeneralMoleculeId = gm.Id " + " INNER JOIN " + getTableInfoPeptideGroup() + " pg ON gm.PeptideGroupId = pg.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON pg.RunId = r.Id ").append(whereClause).append(")")); @@ -2014,7 +1999,7 @@ public static void deleteSampleFileDependent(TableInfo tableInfo, SQLFragment wh private static void deleteGeneralPrecursorDependent(TableInfo tableInfo, String colName) { execute(" DELETE FROM " + tableInfo + - " WHERE " + colName + " IN (SELECT gp.Id FROM " + getTableInfoGeneralPrecursor() + " gp " + + " WHERE " + colName + " IN (SELECT gp.Id FROM " + getTableInfoGeneralPrecursor() + " gp "+ " INNER JOIN " + getTableInfoGeneralMolecule() + " gm ON gp.GeneralMoleculeId = gm.Id " + " INNER JOIN " + getTableInfoPeptideGroup() + " pg ON gm.PeptideGroupId = pg.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON pg.RunId = r.Id " + @@ -2024,7 +2009,7 @@ private static void deleteGeneralPrecursorDependent(TableInfo tableInfo, String private static void deleteGeneralMoleculeDependent(TableInfo tableInfo, String colName) { execute(" DELETE FROM " + tableInfo + - " WHERE " + colName + " IN (SELECT gm.Id FROM " + getTableInfoGeneralMolecule() + " gm " + + " WHERE " + colName + " IN (SELECT gm.Id FROM " + getTableInfoGeneralMolecule() + " gm "+ " INNER JOIN " + getTableInfoPeptideGroup() + " pg ON gm.PeptideGroupId = pg.Id " + " INNER JOIN " + getTableInfoRuns() + " r ON pg.RunId = r.Id " + " WHERE r.Deleted = ?)", true); @@ -2033,7 +2018,7 @@ private static void deleteGeneralMoleculeDependent(TableInfo tableInfo, String c private static void deletePeptideGroupDependent(TableInfo tableInfo) { execute(" DELETE FROM " + tableInfo + - " WHERE PeptideGroupId IN (SELECT pg.Id FROM " + getTableInfoPeptideGroup() + " pg " + + " WHERE PeptideGroupId IN (SELECT pg.Id FROM " + getTableInfoPeptideGroup() + " pg "+ " INNER JOIN " + getTableInfoRuns() + " r ON pg.RunId = r.Id " + " WHERE r.Deleted = ?)", true); } @@ -2047,7 +2032,7 @@ private static void deleteRunDependent(TableInfo tableInfo) private static void deleteReplicateDependent(TableInfo tableInfo) { execute(" DELETE FROM " + tableInfo + - " WHERE ReplicateId IN (SELECT rep.Id FROM " + getTableInfoReplicate() + " rep " + + " WHERE ReplicateId IN (SELECT rep.Id FROM " + getTableInfoReplicate() + " rep "+ " INNER JOIN " + getTableInfoRuns() + " r ON rep.RunId = r.Id " + " WHERE r.Deleted = ?)", true); } @@ -2060,9 +2045,9 @@ private static void deleteSampleFileDependent(TableInfo tableInfo) private static void deleteTransitionPredictionSettingsDependent() { execute("DELETE FROM " + getTableInfoPredictorSettings() + " WHERE PredictorId IN (SELECT Id FROM " + - getTableInfoPredictor() + " WHERE " + - "Id IN (SELECT CePredictorId FROM " + getTableInfoTransitionPredictionSettings() + " tps, " + getTableInfoRuns() + " r WHERE r.Id = tps.RunId AND r.Deleted = ?)" + - "OR Id IN (SELECT DpPredictorId FROM " + getTableInfoTransitionPredictionSettings() + " tps, " + getTableInfoRuns() + " r WHERE r.Id = tps.RunId AND r.Deleted = ?))" + getTableInfoPredictor() + " WHERE " + + "Id IN (SELECT CePredictorId FROM " + getTableInfoTransitionPredictionSettings() + " tps, " + getTableInfoRuns() + " r WHERE r.Id = tps.RunId AND r.Deleted = ?)" + + "OR Id IN (SELECT DpPredictorId FROM " + getTableInfoTransitionPredictionSettings() + " tps, " + getTableInfoRuns() + " r WHERE r.Id = tps.RunId AND r.Deleted = ?))" , true, true); List predictorsToDelete = getPredictorsToDelete(); @@ -2136,7 +2121,7 @@ public static void deleteiRTscales(Container c) public static List getIrtScaleIds(Container c) { SimpleFilter conFil = SimpleFilter.createContainerFilter(c); - return new TableSelector(TargetedMSManager.getTableInfoiRTScale().getColumn(FieldKey.fromParts("id")), conFil, null).getArrayList(Integer.class); + return new TableSelector(TargetedMSManager.getTableInfoiRTScale().getColumn(FieldKey.fromParts("id")), conFil , null).getArrayList(Integer.class); } // return the ModuleProperty value for "TARGETED_MS_FOLDER_TYPE" @@ -2179,7 +2164,7 @@ public static void renameRun(long runId, String newDescription, User user) throw return; new SqlExecutor(getSchema()).execute("UPDATE " + getTableInfoRuns() + " SET Description=? WHERE Id = ?", - newDescription, runId); + newDescription, runId); TargetedMSRun run = getRun(runId); if (run != null) { @@ -2193,9 +2178,7 @@ public static void renameRun(long runId, String newDescription, User user) throw } } - /** - * @return the sample file if it has already been imported in the container - */ + /** @return the sample file if it has already been imported in the container */ @Nullable public static Replicate getReplicate(long replicateId, Container container) { @@ -2203,23 +2186,19 @@ public static Replicate getReplicate(long replicateId, Container container) sql.append(getTableInfoReplicate(), "rep"); sql.append(", "); sql.append(getTableInfoRuns(), "r"); - sql.append(" WHERE r.Id = rep.RunId AND rep.Id = ? AND r.Container = ? "); + sql.append( " WHERE r.Id = rep.RunId AND rep.Id = ? AND r.Container = ? "); sql.add(replicateId); sql.add(container); return new SqlSelector(getSchema(), sql).getObject(Replicate.class); } - /** - * @return the sample file if it has already been imported in the container - */ + /** @return the sample file if it has already been imported in the container */ public static List getSampleFile(String filePath, Date acquiredTime, Container container) { return getSampleFile(filePath, acquiredTime, container, true); } - /** - * @return the sample file if it has already been imported in the container - */ + /** @return the sample file if it has already been imported in the container */ @Nullable public static SampleFile getSampleFile(long id, Container container) { @@ -2266,13 +2245,13 @@ public static List getSampleFiles(Container container, @Nullable SQL */ public static List getMatchingSampleFiles(@NotNull SampleFile sampleFile, Container container) { - if (sampleFile.getAcquiredTime() != null) + if(sampleFile.getAcquiredTime() != null) { // Issue 38270. A file may have been imported from a different path in a previous document. // If SampleFile has an acquired time check for a file with same name and acquired time. String filePath = sampleFile.getFilePath(); String fileName = FilenameUtils.getName(filePath); - if (!StringUtils.isBlank(fileName) && fileName.length() < filePath.length()) + if(!StringUtils.isBlank(fileName) && fileName.length() < filePath.length()) { fileName = filePath.substring(filePath.indexOf(fileName) - 1); // Include the separator char } @@ -2287,7 +2266,7 @@ public static List getMatchingSampleFiles(@NotNull SampleFile sample private static List getSampleFile(String filePath, Date acquiredTime, Container container, boolean fullPath) { SQLFragment sql = new SQLFragment(); - if (fullPath) + if(fullPath) { sql.append(" sf.FilePath = ? "); sql.add(filePath); @@ -2297,7 +2276,7 @@ private static List getSampleFile(String filePath, Date acquiredTime sql.append(" sf.FilePath LIKE ? "); sql.add("%" + getSqlDialect().encodeLikeOpSearchString(filePath)); } - if (acquiredTime == null) + if(acquiredTime == null) sql.append(" AND sf.AcquiredTime IS NULL"); else { @@ -2367,13 +2346,13 @@ public static Map> getClustergrammerQuery(User user, Map> intensities = new HashMap<>(); - for (Map rowMap : new TableSelector(table).getMapArray()) + for (Map rowMap:new TableSelector(table).getMapArray()) { - List columns = table.getColumns(); + List columns = table.getColumns(); List values = new ArrayList<>(); - for (ColumnInfo column : columns) + for(ColumnInfo column : columns) { //Skip pivot column and row name column String colName = column.getName(); @@ -2396,10 +2375,10 @@ public static Map> getClustergrammerQuery(User user, MathStat stats = StatsService.get().getStats(primitiveValues); - String proteinName = (String) rowMap.get(rowHeadingColumnName); + String proteinName = (String)rowMap.get(rowHeadingColumnName); Map intensityMap = new TreeMap<>(); boolean hasValue = false; - for (ColumnInfo column : columns) + for(ColumnInfo column : columns) { String colName = column.getName(); if (colName.compareToIgnoreCase(intensityColumnName) == 0 || colName.compareToIgnoreCase(rowHeadingColumnName) == 0) @@ -2438,7 +2417,7 @@ public static Map> getClustergrammerQuery(User user, private static Double getValue(Object o) { - Double value = (Double) JdbcType.DOUBLE.convert(o); + Double value = (Double)JdbcType.DOUBLE.convert(o); if (value == null || value == 0.0) { return null; @@ -2451,7 +2430,6 @@ public static List getAllQCMetricConfigurations(TargetedM { return _metricCache.get(schema.getContainer(), schema, null); } - public static List getEnabledQCMetricConfigurations(TargetedMSSchema schema) { List result = new ArrayList<>(); @@ -2469,7 +2447,7 @@ public List getSampleFileInfos(Container container, User user, I { TargetedMSSchema schema = new TargetedMSSchema(user, container); List enabledQCMetricConfigurations = getEnabledQCMetricConfigurations(schema); - if (!enabledQCMetricConfigurations.isEmpty()) + if(!enabledQCMetricConfigurations.isEmpty()) { List guideSets = TargetedMSManager.getGuideSets(container, user); Map metricMap = enabledQCMetricConfigurations.stream().collect(Collectors.toMap(QCMetricConfiguration::getId, Function.identity())); @@ -2675,7 +2653,7 @@ public static boolean containerHasLists(Container container) public static boolean containerHasDocVersions(Container container) { ExperimentService svc = ExperimentService.get(); - return new SqlSelector(svc.getSchema(), new SQLFragment("SELECT r.rowId FROM ", Boolean.FALSE, container) + return new SqlSelector(svc.getSchema(), new SQLFragment("SELECT r.rowId FROM ", Boolean.FALSE, container ) .append(svc.getTinfoExperimentRun(), "r") .append(" INNER JOIN ").append(TargetedMSManager.getTableInfoRuns(), "tRuns") .append(" ON (tRuns.ExperimentRunLSID = r.lsid AND tRuns.Deleted = ?)") @@ -2687,7 +2665,7 @@ public static void updateModifiedAreaProportions(@Nullable Logger log, @NotNull final String suffix = StringUtilsLabKey.getPaddedUniquifier(9); final String precursorGroupingsTableName = getSqlDialect().getTempTablePrefix() + "PrecursorGroupings" + suffix; final String moleculeGroupingsTableName = getSqlDialect().getTempTablePrefix() + "MoleculeGroupings" + suffix; - final String areasTableName = getSqlDialect().getTempTablePrefix() + "Areas" + suffix; + final String areasTableName = getSqlDialect().getTempTablePrefix() + "Areas" + suffix; if (log != null) { @@ -2739,7 +2717,7 @@ public static void updateModifiedAreaProportions(@Nullable Logger log, @NotNull .append("WHERE g.PrecursorId = targetedms.precursorchrominfo.PrecursorId AND \n") .append("a.SampleFileId = targetedms.precursorchrominfo.SampleFileId) X) "); updatePrecursorSQL.append(" WHERE PrecursorId IN \n") - .append("(SELECT PrecursorId FROM ").append(precursorGroupingsTableName).append(")"); + .append("(SELECT PrecursorId FROM ").append(precursorGroupingsTableName).append(")"); if (log != null) { @@ -2756,7 +2734,7 @@ public static void updateModifiedAreaProportions(@Nullable Logger log, @NotNull .append("WHERE g.GeneralMoleculeId = targetedms.generalmoleculechrominfo.GeneralMoleculeId AND \n") .append("a.SampleFileId = targetedms.generalmoleculechrominfo.SampleFileId) X)"); updateMoleculeSQL.append(" WHERE GeneralMoleculeId IN \n") - .append("(SELECT GeneralMoleculeId FROM ").append(moleculeGroupingsTableName).append(")"); + .append("(SELECT GeneralMoleculeId FROM ").append(moleculeGroupingsTableName).append(")"); if (log != null) { @@ -3000,7 +2978,7 @@ else if (traceValue != null && values[i] >= traceValue) if (!valuesToStore.isEmpty()) { - valuesToStore.forEach((key, val) -> { + valuesToStore.forEach((key,val) -> { QCTraceMetricValues qcTraceMetricValues = new QCTraceMetricValues(); qcTraceMetricValues.setMetric(qcMetricConfiguration.getId()); qcTraceMetricValues.setSampleFileId(key.getSampleFileId()); @@ -3039,7 +3017,7 @@ public static List getKeywords(long sequenceId) return keywords; } - public static Map getQCFolderDateRange(Container container) + public static Map getQCFolderDateRange(Container container) { var sql = new SQLFragment("SELECT MIN(sf.AcquiredTime) AS startDate, MAX(sf.AcquiredTime) AS endDate "); sql.append(" FROM ").append(getTableInfoSampleFile(), "sf"); @@ -3110,131 +3088,6 @@ public static List getInstrumentDetails(Container container) } - public static List getInstrumentNickName(Container container) - { - SimpleFilter filter = SimpleFilter.createContainerFilter(container); - - List nicknames = new TableSelector(getTableInfoInstrumentNickname(), filter, null).getArrayList(InstrumentNickname.class); - return nicknames.stream() - .map(InstrumentNickname::getNickname) - .distinct() - .collect(Collectors.toList()); - } - - /** - * Returns the instrument nickname for the given instrument, similar to the logic in SampleFileTable. - * First checks for a custom nickname matching the serial number and model, then falls back to a default name. - * - * @param container The container to search in (including project and shared containers) - * @param user The user for permission checking - * @param instrumentId The instrument ID - * @return The resolved nickname, or null if no instrument is found - */ - @Nullable - public static String getInstrumentNickName(@NotNull Container container, @NotNull User user, - @Nullable Long instrumentId) - { - if (instrumentId == null) - { - return null; - } - - TargetedMSSchema schema = new TargetedMSSchema(user, container); - - // Get the serial number and model from the sample file table - SQLFragment sampleFileSQL = new SQLFragment("SELECT DISTINCT sf.InstrumentSerialNumber, i.Model FROM "); - sampleFileSQL.append(getTableInfoSampleFile(), "sf"); - sampleFileSQL.append(" INNER JOIN "); - sampleFileSQL.append(getTableInfoInstrument(), "i"); - sampleFileSQL.append(" ON sf.InstrumentId = i.Id WHERE i.Id = ?").add(instrumentId); - - Map sampleFileData = new SqlSelector(getSchema(), sampleFileSQL).getMap(); - String serialNumber = sampleFileData != null ? (String) sampleFileData.get("InstrumentSerialNumber") : null; - String instrumentModel = sampleFileData != null ? (String) sampleFileData.get("Model") : null; - - // First, try to find a custom nickname matching the serial number and model - // Join conditions match the logic in SampleFileTable: - // - SerialNumber matches or both are NULL - // - Model matches or both are NULL - SimpleFilter filter = new SimpleFilter(); - - // Build a filter for nickname lookup with proper NULL handling - if (serialNumber != null) - { - filter.addCondition(FieldKey.fromParts("SerialNumber"), serialNumber); - } - else - { - filter.addCondition(FieldKey.fromParts("SerialNumber"), null, CompareType.ISBLANK); - } - - if (instrumentModel != null) - { - filter.addCondition(new SimpleFilter.OrClause( - new CompareType.EqualsCompareClause(FieldKey.fromParts("Model"), CompareType.EQUAL, instrumentModel), - new CompareType.CompareClause(FieldKey.fromParts("Model"), CompareType.ISBLANK, null) - )); - } - else - { - filter.addCondition(FieldKey.fromParts("Model"), null, CompareType.ISBLANK); - } - - // Search in current container, project, and shared containers, prioritizing by container proximity - TableInfo nicknameTable = schema.getTableOrThrow(TABLE_INSTRUMENT_NICKNAME, - ContainerFilter.Type.CurrentPlusProjectAndShared.create(container, user)); - - List matches = new TableSelector(nicknameTable, filter, null).getArrayList(InstrumentNickname.class); - - // Sort by container proximity: current > project > shared - Container shared = ContainerManager.getSharedContainer(); - Container project = container.getProject(); - - matches.sort((a, b) -> { - int aScore = getContainerScore(a.getContainer(), container, project, shared); - int bScore = getContainerScore(b.getContainer(), container, project, shared); - return Integer.compare(bScore, aScore); // Higher score first - }); - - if (!matches.isEmpty()) - { - return matches.get(0).getNickname(); - } - - // Fall back to default naming: COALESCE(Model + ' - ' + SerialNumber, SerialNumber, Model) - if (instrumentModel != null && serialNumber != null) - { - return instrumentModel + " - " + serialNumber; - } - else if (serialNumber != null) - { - return serialNumber; - } - else if (instrumentModel != null) - { - return instrumentModel; - } - - return null; - } - - private static int getContainerScore(Container c, Container current, Container project, Container shared) - { - if (c.equals(current)) - { - return 3; - } - else if (project != null && c.equals(project)) - { - return 2; - } - else if (shared != null && c.equals(shared)) - { - return 1; - } - return 0; - } - public void deleteNickname(InstrumentNickname name, User user) throws SQLException, BatchValidationException, QueryUpdateServiceException, InvalidKeyException { From 6fe089f1359b756339e78a137bcbfc5a50a85c23 Mon Sep 17 00:00:00 2001 From: ankurjuneja Date: Thu, 5 Mar 2026 16:12:37 -0800 Subject: [PATCH 5/6] update annotation hover tooltip implementation with tippy and add shared from container path to tooltip --- webapp/TargetedMS/js/QCTrendPlotPanel.js | 56 ++++++++++++++++-------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/webapp/TargetedMS/js/QCTrendPlotPanel.js b/webapp/TargetedMS/js/QCTrendPlotPanel.js index dc1c5adf1..5aa66774a 100644 --- a/webapp/TargetedMS/js/QCTrendPlotPanel.js +++ b/webapp/TargetedMS/js/QCTrendPlotPanel.js @@ -1303,7 +1303,7 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { let config = this.getReportConfig(); - let annotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color FROM qcannotation qca JOIN qcannotationtype qcat ON qcat.Id = qca.QCAnnotationTypeId"; + let annotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color, qca.container.Path AS ContainerPath FROM qcannotation qca JOIN qcannotationtype qcat ON qcat.Id = qca.QCAnnotationTypeId"; // Filter on start/end dates let dateFilter = ""; @@ -1356,7 +1356,7 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { separator = " OR "; } - let sharedAnnotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color " + + let sharedAnnotationSql = "SELECT qca.Id AS qcAnnotationId, qca.Date, qca.Description, qca.Created, qca.CreatedBy.DisplayName, qcat.Id AS qcAnnotationTypeId, qcat.Name, qcat.Color, qca.container.Path AS ContainerPath " + "FROM qcannotation qca " + "JOIN qcannotationtype qcat ON qcat.Id = qca.QCAnnotationTypeId " + "WHERE qcat.IsShareable = true AND (" + instrumentFilter + ")" + dateFilter; @@ -1988,23 +1988,37 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { .attr("d", this.annotationShape(4)).attr('transform', transformAcc) .style("fill", colorAcc).style("stroke", colorAcc); - // add hover text for the annotation details - annotations.append("title") - .text(function(d) { - return "Created By: " + d['DisplayName'] + ", " - + "\nType: " + d['Name'] + ", " - + "\nDate: " + me.formatDate(new Date(d['Date']), true) + ", " - + "\nDescription: " + d['Description']; - }); - - // add some mouseover effects for fun - var mouseOn = function(pt, strokeWidth) { + // add mouseover effects for fun + let mouseOn = function(pt, strokeWidth, d) { d3.select(pt).transition().duration(800).attr("stroke-width", strokeWidth).ease("elastic"); + + if (!pt._tippy) { + let content = "Created By: " + d['DisplayName'] + ", " + + "
Type: " + d['Name'] + ", " + + "
Date: " + me.formatDate(new Date(d['Date']), true) + ", " + + "
Description: " + d['Description']; + + if (d['ContainerPath'] && d['ContainerPath'] !== LABKEY.ActionURL.getContainer()) { + let containerPath = d['ContainerPath']; + if (containerPath.startsWith('/')) { + containerPath = containerPath.substring(1); + } + content += ",
Shared From: " + containerPath; + } + + tippy(pt, { + content: content, + allowHTML: true, + arrow: true, + theme: 'light-border', + placement: 'top' + }); + } }; var mouseOff = function(pt) { d3.select(pt).transition().duration(800).attr("stroke-width", 1).ease("elastic"); }; - annotations.on("mouseover", function(){ return mouseOn(this, 3); }); + annotations.on("mouseover", function(d){ return mouseOn(this, 3, d); }); annotations.on("mouseout", function(){ return mouseOff(this); }); if (this.canUserEdit()) { @@ -2041,10 +2055,7 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { .style("opacity", 0); // Add mouseover effects for add-annotations - nonAnnotationGroups.append("title") - .text("Add annotation"); - - nonAnnotationGroups.on("mouseover", function () { + nonAnnotationGroups.on("mouseover", function (d) { d3.select(this).select(".add-annotation-background") .transition().duration(300) .style("opacity", 0) @@ -2053,6 +2064,15 @@ Ext4.define('LABKEY.targetedms.QCTrendPlotPanel', { .transition().duration(300) .style("opacity", 1) .style("cursor", "pointer"); + + if (!this._tippy) { + tippy(this, { + content: "Add annotation", + arrow: true, + theme: 'light-border', + placement: 'top' + }); + } }); nonAnnotationGroups.on("mouseout", function () { d3.select(this).select(".add-annotation-background") From 08d3a2bfd7ed7ca1facb9c0913011348849b7753 Mon Sep 17 00:00:00 2001 From: ankurjuneja Date: Thu, 5 Mar 2026 16:35:39 -0800 Subject: [PATCH 6/6] add new columns to the views --- resources/queries/targetedms/qcannotation/.qview.xml | 2 ++ resources/queries/targetedms/qcannotationtype/.qview.xml | 1 + 2 files changed, 3 insertions(+) diff --git a/resources/queries/targetedms/qcannotation/.qview.xml b/resources/queries/targetedms/qcannotation/.qview.xml index 402f7a544..c593875bf 100644 --- a/resources/queries/targetedms/qcannotation/.qview.xml +++ b/resources/queries/targetedms/qcannotation/.qview.xml @@ -4,6 +4,8 @@ + + diff --git a/resources/queries/targetedms/qcannotationtype/.qview.xml b/resources/queries/targetedms/qcannotationtype/.qview.xml index 920ef55d0..c2bdf5bb7 100644 --- a/resources/queries/targetedms/qcannotationtype/.qview.xml +++ b/resources/queries/targetedms/qcannotationtype/.qview.xml @@ -4,6 +4,7 @@ +