diff --git a/resources/queries/protein/Sequences.query.xml b/resources/queries/protein/Sequences.query.xml new file mode 100644 index 000000000..92a99ef3a --- /dev/null +++ b/resources/queries/protein/Sequences.query.xml @@ -0,0 +1,13 @@ + + + + + + + 200 + + +
+
+
+
diff --git a/resources/queries/targetedms/Protein/.qview.xml b/resources/queries/targetedms/Protein/.qview.xml new file mode 100644 index 000000000..5659c1ee6 --- /dev/null +++ b/resources/queries/targetedms/Protein/.qview.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/resources/schemas/dbscripts/postgresql/targetedms-26.006-26.007.sql b/resources/schemas/dbscripts/postgresql/targetedms-26.006-26.007.sql new file mode 100644 index 000000000..3ee9b11ed --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/targetedms-26.006-26.007.sql @@ -0,0 +1,34 @@ +ALTER TABLE targetedms.Runs ADD COLUMN ProteinCount INT; + +UPDATE targetedms.Runs r +SET ProteinCount = ( + SELECT COUNT(*) + FROM targetedms.Protein p + JOIN targetedms.PeptideGroup pg ON p.PeptideGroupId = pg.Id + WHERE pg.RunId = r.Id +); + +-- Redefine PeptideGroupCount: groups containing at least one peptide +UPDATE targetedms.Runs r +SET PeptideGroupCount = ( + SELECT COUNT(DISTINCT pg.Id) + FROM targetedms.PeptideGroup pg + JOIN targetedms.GeneralMolecule gm ON gm.PeptideGroupId = pg.Id + JOIN targetedms.Peptide p ON p.Id = gm.Id + WHERE pg.RunId = r.Id +); + +ALTER TABLE targetedms.Runs ADD COLUMN MoleculeGroupCount INT; + +UPDATE targetedms.Runs r +SET MoleculeGroupCount = ( + SELECT COUNT(DISTINCT pg.Id) + FROM targetedms.PeptideGroup pg + JOIN targetedms.GeneralMolecule gm ON gm.PeptideGroupId = pg.Id + JOIN targetedms.Molecule m ON m.Id = gm.Id + WHERE pg.RunId = r.Id +); + +ALTER TABLE targetedms.Runs ALTER COLUMN MoleculeGroupCount SET NOT NULL; +ALTER TABLE targetedms.Runs ALTER COLUMN PeptideGroupCount SET NOT NULL; +ALTER TABLE targetedms.Runs ALTER COLUMN ProteinCount SET NOT NULL; diff --git a/resources/schemas/targetedms.xml b/resources/schemas/targetedms.xml index 8fddaf6bf..b864b0d66 100644 --- a/resources/schemas/targetedms.xml +++ b/resources/schemas/targetedms.xml @@ -179,15 +179,23 @@ #,### - /targetedms-showPrecursorList.view?id=${Id} + /targetedms-showPeptideGroupList.view?id=${Id} + + + #,### + /targetedms-showMoleculeGroupList.view?id=${Id} + + + #,### + /targetedms-showProteinList.view?id=${Id} #,### - /targetedms-showPrecursorList.view?id=${Id} + /targetedms-showPeptideList.view?id=${Id} #,### - /targetedms-showPrecursorList.view?id=${Id} + /targetedms-showMoleculeList.view?id=${Id} #,### diff --git a/src/org/labkey/targetedms/SkylineDocImporter.java b/src/org/labkey/targetedms/SkylineDocImporter.java index 8a1e1afd5..6a971d237 100644 --- a/src/org/labkey/targetedms/SkylineDocImporter.java +++ b/src/org/labkey/targetedms/SkylineDocImporter.java @@ -144,6 +144,9 @@ public class SkylineDocImporter protected static final Logger _systemLog = LogHelper.getLogger(SkylineDocImporter.class, "Imports Skyline documents"); protected final XarContext _context; private int blankLabelIndex; + private int _importedPeptideGroupCount = 0; + private int _importedMoleculeGroupCount = 0; + private int _importedProteinCount = 0; private final LocalDirectory _localDirectory; private final PipeRoot _pipeRoot; @@ -450,7 +453,9 @@ private void importSkylineDoc(TargetedMSRun run, File f) throws XMLStreamExcepti int auditLogEntriesCount = importer.importAuditLogFile(_user, _auditLogFile, parser.getDocumentGUID(), run); run.setAuditLogEntriesCount(auditLogEntriesCount); - run.setPeptideGroupCount(parser.getPeptideGroupCount()); + run.setPeptideGroupCount(_importedPeptideGroupCount); + run.setMoleculeGroupCount(_importedMoleculeGroupCount); + run.setProteinCount(_importedProteinCount); run.setPeptideCount(parser.getPeptideCount()); run.setSmallMoleculeCount(parser.getSmallMoleculeCount()); run.setPrecursorCount(parser.getPrecursorCount()); @@ -1357,10 +1362,12 @@ else if(!pepGroup.isDecoy()) if(peptideCount > 0) { _log.debug(String.format("Total peptides inserted: %d", peptideCount)); + _importedPeptideGroupCount++; } if(moleculeCount > 0) { _log.debug(String.format("Total molecules inserted: %d", moleculeCount)); + _importedMoleculeGroupCount++; } } @@ -1381,6 +1388,7 @@ private void insertProtein(Protein protein) proteinService.ensureIdentifiers(seqId, identifierMap); } Table.insert(null, TargetedMSManager.getTableInfoProtein(), protein); + _importedProteinCount++; } private static final String SKYLINE_IDENT_TYPE = "Skyline"; diff --git a/src/org/labkey/targetedms/TargetedMSController.java b/src/org/labkey/targetedms/TargetedMSController.java index 6acf8c302..7ae73decc 100644 --- a/src/org/labkey/targetedms/TargetedMSController.java +++ b/src/org/labkey/targetedms/TargetedMSController.java @@ -258,9 +258,11 @@ import org.labkey.targetedms.view.CalibrationCurvesView; import org.labkey.targetedms.view.ChromatogramGridView; import org.labkey.targetedms.view.ChromatogramsDataRegion; +import org.labkey.targetedms.view.DocumentGeneralMoleculesView; +import org.labkey.targetedms.view.DocumentPeptideGroupView; +import org.labkey.targetedms.view.DocumentProteinView; import org.labkey.targetedms.view.DocumentPrecursorsView; import org.labkey.targetedms.view.DocumentTransitionsView; -import org.labkey.targetedms.view.DocumentView; import org.labkey.targetedms.view.FiguresOfMeritView; import org.labkey.targetedms.view.GroupComparisonView; import org.labkey.targetedms.view.InstrumentSummaryWebPart; @@ -4377,7 +4379,7 @@ public void addNavTrail(NavTree root, TargetedMSRun run) // Action to display a document's transition or precursor list, with both proteomics and small molecule views // ------------------------------------------------------------------------ @RequiresPermission(ReadPermission.class) - public abstract class ShowRunSplitDetailsAction extends AbstractShowRunDetailsAction + public abstract class ShowRunSplitDetailsAction extends AbstractShowRunDetailsAction { public ShowRunSplitDetailsAction() { @@ -4496,8 +4498,8 @@ public ShowPrecursorListAction(ViewContext ctx) @Override protected DocumentPrecursorsView createQueryView(RunDetailsForm form, BindException errors, boolean forExport, String dataRegion) { - DocumentPrecursorsView view; - if(PeptidePrecursorsView.DATAREGION_NAME.equals(dataRegion)) + DocumentPrecursorsView view = null; + if (PeptidePrecursorsView.DATAREGION_NAME.equals(dataRegion)) { view = new PeptidePrecursorsView(getViewContext(), new TargetedMSSchema(getUser(), getContainer()), @@ -4505,7 +4507,7 @@ protected DocumentPrecursorsView createQueryView(RunDetailsForm form, BindExcept form.getId(), forExport); } - else + else if (SmallMoleculePrecursorsView.DATAREGION_NAME.equals(dataRegion)) { view = new SmallMoleculePrecursorsView(getViewContext(), new TargetedMSSchema(getUser(), getContainer()), @@ -4514,9 +4516,12 @@ protected DocumentPrecursorsView createQueryView(RunDetailsForm form, BindExcept forExport); } - view.setShowExportButtons(true); - view.setShowDetailsColumn(false); - view.setButtonBarPosition(DataRegion.ButtonBarPosition.BOTH); + if (view != null) + { + view.setShowExportButtons(true); + view.setShowDetailsColumn(false); + view.setButtonBarPosition(DataRegion.ButtonBarPosition.BOTH); + } return view; } @@ -4534,6 +4539,102 @@ public String getDataRegionNameSmallMolecule() } } + @RequiresPermission(ReadPermission.class) + public class ShowPeptideGroupListAction extends ShowRunSingleDetailsAction + { + public ShowPeptideGroupListAction() + { + super(RunDetailsForm.class, "Peptide Groups", TargetedMSSchema.TABLE_PEPTIDE_GROUP); + } + + @Override + protected QueryView createQueryView(RunDetailsForm form, BindException errors, boolean forExport, String dataRegion) + { + TargetedMSSchema schema = new TargetedMSSchema(getUser(), getContainer()); + DocumentPeptideGroupView view = new DocumentPeptideGroupView(getViewContext(), schema, form.getId(), + TargetedMSSchema.TABLE_PEPTIDE_GROUP, "Protein Groups"); + view.setShowDetailsColumn(false); + view.setShowFilterDescription(false); + return view; + } + } + + @RequiresPermission(ReadPermission.class) + public class ShowMoleculeGroupListAction extends ShowRunSingleDetailsAction + { + public ShowMoleculeGroupListAction() + { + super(RunDetailsForm.class, "Molecule Lists", TargetedMSSchema.TABLE_MOLECULE_GROUP); + } + + @Override + protected QueryView createQueryView(RunDetailsForm form, BindException errors, boolean forExport, String dataRegion) + { + TargetedMSSchema schema = new TargetedMSSchema(getUser(), getContainer()); + DocumentPeptideGroupView view = new DocumentPeptideGroupView(getViewContext(), schema, form.getId(), + TargetedMSSchema.TABLE_MOLECULE_GROUP, "Molecule Lists"); + view.setShowDetailsColumn(false); + view.setShowFilterDescription(false); + return view; + } + } + + @RequiresPermission(ReadPermission.class) + public class ShowProteinListAction extends ShowRunSingleDetailsAction + { + public ShowProteinListAction() + { + super(RunDetailsForm.class, "Proteins", TargetedMSSchema.TABLE_PROTEIN); + } + + @Override + protected QueryView createQueryView(RunDetailsForm form, BindException errors, boolean forExport, String dataRegion) + { + TargetedMSSchema schema = new TargetedMSSchema(getUser(), getContainer()); + DocumentProteinView view = new DocumentProteinView(getViewContext(), schema, form.getId()); + view.setShowDetailsColumn(false); + return view; + } + } + + @RequiresPermission(ReadPermission.class) + public class ShowPeptideListAction extends ShowRunSingleDetailsAction + { + public ShowPeptideListAction() + { + super(RunDetailsForm.class, "Peptides", TargetedMSSchema.TABLE_PEPTIDE); + } + + @Override + protected QueryView createQueryView(RunDetailsForm form, BindException errors, boolean forExport, String dataRegion) + { + TargetedMSSchema schema = new TargetedMSSchema(getUser(), getContainer()); + DocumentGeneralMoleculesView view = new DocumentGeneralMoleculesView(getViewContext(), schema, form.getId(), + TargetedMSSchema.TABLE_PEPTIDE, "Peptides"); + view.setShowDetailsColumn(false); + return view; + } + } + + @RequiresPermission(ReadPermission.class) + public class ShowMoleculeListAction extends ShowRunSingleDetailsAction + { + public ShowMoleculeListAction() + { + super(RunDetailsForm.class, "Small Molecules", TargetedMSSchema.TABLE_MOLECULE); + } + + @Override + protected QueryView createQueryView(RunDetailsForm form, BindException errors, boolean forExport, String dataRegion) + { + TargetedMSSchema schema = new TargetedMSSchema(getUser(), getContainer()); + DocumentGeneralMoleculesView view = new DocumentGeneralMoleculesView(getViewContext(), schema, form.getId(), + TargetedMSSchema.TABLE_MOLECULE, "Small Molecules"); + view.setShowDetailsColumn(false); + return view; + } + } + @RequiresPermission(ReadPermission.class) public class ShowGroupComparisonAction extends ShowRunSplitDetailsAction { diff --git a/src/org/labkey/targetedms/TargetedMSManager.java b/src/org/labkey/targetedms/TargetedMSManager.java index c9e643dde..f52fa3b62 100644 --- a/src/org/labkey/targetedms/TargetedMSManager.java +++ b/src/org/labkey/targetedms/TargetedMSManager.java @@ -2660,6 +2660,16 @@ public static boolean containerHasPeptides(Container container) return new SqlSelector(TargetedMSManager.getSchema(), new SQLFragment("SELECT Id FROM ", container, false).append(TargetedMSManager.getTableInfoRuns(), "r").append(" WHERE PeptideCount > 0 AND Container = ? AND Deleted = ?")).exists(); } + public static boolean containerHasMoleculeGroups(Container container) + { + return new SqlSelector(TargetedMSManager.getSchema(), new SQLFragment("SELECT Id FROM ", container, false).append(TargetedMSManager.getTableInfoRuns(), "r").append(" WHERE MoleculeGroupCount > 0 AND Container = ? AND Deleted = ?")).exists(); + } + + public static boolean containerHasProteins(Container container) + { + return new SqlSelector(TargetedMSManager.getSchema(), new SQLFragment("SELECT Id FROM ", container, false).append(TargetedMSManager.getTableInfoRuns(), "r").append(" WHERE ProteinCount > 0 AND Container = ? AND Deleted = ?")).exists(); + } + public static boolean containerHasCalibrationCurves(Container container) { return new SqlSelector(TargetedMSManager.getSchema(), new SQLFragment("SELECT Id FROM ", container, false).append(TargetedMSManager.getTableInfoRuns(), "r").append(" WHERE CalibrationCurveCount > 0 AND Container = ? AND Deleted = ?")).exists(); diff --git a/src/org/labkey/targetedms/TargetedMSModule.java b/src/org/labkey/targetedms/TargetedMSModule.java index 581f67f52..b3bf9a4a7 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.006; + return 26.007; } @Override diff --git a/src/org/labkey/targetedms/TargetedMSRun.java b/src/org/labkey/targetedms/TargetedMSRun.java index a1319b6ec..47c64c56e 100644 --- a/src/org/labkey/targetedms/TargetedMSRun.java +++ b/src/org/labkey/targetedms/TargetedMSRun.java @@ -51,6 +51,8 @@ public class TargetedMSRun implements Serializable, ITargetedMSRun protected RunRepresentativeDataState _representativeDataState = NotRepresentative; protected int _peptideGroupCount; + protected int _moleculeGroupCount; + protected int _proteinCount; protected int _peptideCount; protected int _smallMoleculeCount; protected int _precursorCount; @@ -249,6 +251,26 @@ public void setPeptideGroupCount(int peptideGroupCount) _peptideGroupCount = peptideGroupCount; } + public int getMoleculeGroupCount() + { + return _moleculeGroupCount; + } + + public void setMoleculeGroupCount(int moleculeGroupCount) + { + _moleculeGroupCount = moleculeGroupCount; + } + + public int getProteinCount() + { + return _proteinCount; + } + + public void setProteinCount(int proteinCount) + { + _proteinCount = proteinCount; + } + public int getPeptideCount() { return _peptideCount; diff --git a/src/org/labkey/targetedms/TargetedMSSchema.java b/src/org/labkey/targetedms/TargetedMSSchema.java index a863332d8..0a53853aa 100644 --- a/src/org/labkey/targetedms/TargetedMSSchema.java +++ b/src/org/labkey/targetedms/TargetedMSSchema.java @@ -78,6 +78,7 @@ import org.labkey.api.util.SafeToRender; import org.labkey.api.util.UnexpectedException; import org.labkey.api.view.ActionURL; +import org.labkey.api.view.NotFoundException; import org.labkey.api.view.PopupMenu; import org.labkey.api.view.ViewContext; import org.labkey.api.view.template.ClientDependency; @@ -304,6 +305,8 @@ public class TargetedMSSchema extends UserSchema // Cache this info at the schema level to make it easy to reuse private Boolean _hasSmallMolecules; private Boolean _hasPeptides; + private Boolean _hasMoleculeGroups; + private Boolean _hasProteins; static public void register(Module module) { @@ -787,7 +790,8 @@ public ExpRunTable getTargetedMSRunsTable(ContainerFilter cf) boolean hasSmallMolecules = hasSmallMolecules(); boolean hasPeptides = hasPeptides(); - String peptideGroupColName = (hasSmallMolecules ? COL_LIST : COL_PROTEIN) + "s"; + boolean hasMoleculeGroups = hasMoleculeGroups(); + boolean hasProteins = hasProteins(); skyDocDetailColumn.setFk(new LookupForeignKey(url, "id", "Id", "Description") { @@ -866,7 +870,9 @@ public boolean isSortable() } }); - result.addWrapColumn(peptideGroupColName, result.getRealTable().getColumn("PeptideGroupCount")); + result.addWrapColumn("PeptideGroups", result.getRealTable().getColumn("PeptideGroupCount")); + result.addWrapColumn("MoleculeLists", result.getRealTable().getColumn("MoleculeGroupCount")); + result.addWrapColumn("Proteins", result.getRealTable().getColumn("ProteinCount")); result.addWrapColumn("Peptides", result.getRealTable().getColumn("PeptideCount")); result.addWrapColumn("SmallMolecules", result.getRealTable().getColumn("SmallMoleculeCount")); result.addWrapColumn("Precursors", result.getRealTable().getColumn("PrecursorCount")); @@ -1002,7 +1008,13 @@ private List init() _fieldKeys.add(2, FieldKey.fromParts("File")); _fieldKeys.add(3, FieldKey.fromParts("File", "Download")); - _fieldKeys.add(FieldKey.fromParts("File", peptideGroupColName)); + + if (hasPeptides) + _fieldKeys.add(FieldKey.fromParts("File", "PeptideGroups")); + if (hasMoleculeGroups) + _fieldKeys.add(FieldKey.fromParts("File", "MoleculeLists")); + if (hasProteins) + _fieldKeys.add(FieldKey.fromParts("File", "Proteins")); // Omit peptides or small molecules if we don't have any in this container if (hasPeptides || !hasSmallMolecules) @@ -1067,11 +1079,29 @@ private boolean hasPeptides() { if (_hasPeptides == null) { - _hasPeptides = TargetedMSManager.containerHasSmallMolecules(getContainer()); + _hasPeptides = TargetedMSManager.containerHasPeptides(getContainer()); } return _hasPeptides.booleanValue(); } + private boolean hasMoleculeGroups() + { + if (_hasMoleculeGroups == null) + { + _hasMoleculeGroups = TargetedMSManager.containerHasMoleculeGroups(getContainer()); + } + return _hasMoleculeGroups.booleanValue(); + } + + private boolean hasProteins() + { + if (_hasProteins == null) + { + _hasProteins = TargetedMSManager.containerHasProteins(getContainer()); + } + return _hasProteins.booleanValue(); + } + @Override public TableInfo createTable(String name, ContainerFilter cf) { @@ -1149,144 +1179,7 @@ public TableInfo createTable(String name, ContainerFilter cf) // Tables that have a FK directly to targetedms.Runs if (TABLE_PEPTIDE_GROUP.equalsIgnoreCase(name) || TABLE_MOLECULE_GROUP.equalsIgnoreCase(name)) { - boolean proteomics = TABLE_PEPTIDE_GROUP.equalsIgnoreCase(name); - TargetedMSTable result = new AnnotatedTargetedMSTable(getSchema().getTable(TABLE_PEPTIDE_GROUP), - this, cf, - ContainerJoinType.RunFK, - TargetedMSManager.getTableInfoPeptideGroupAnnotation(), - "PeptideGroupId", - proteomics ? COL_PROTEIN : COL_LIST, - "protein") - { - @Override - protected Class getDetailsActionClass() - { - return TargetedMSController.ShowProteinAction.class; - } - }; - var labelColumn = result.getMutableColumnOrThrow("Label"); - labelColumn.setURL(result.getDetailsURL(null, null)); - if (proteomics) - { - // Figure out if we have at least 3 replicates marked as QCs, and we have a "Day" or "SampleGroup" annotation, or a value for BatchName - SQLFragment reproducibilitySQL = new SQLFragment("(SELECT CASE WHEN COUNT(DISTINCT r.Id) > 2 THEN COUNT(DISTINCT r.Id) END FROM "); - reproducibilitySQL.append(TargetedMSManager.getTableInfoReplicate(), "r"); - reproducibilitySQL.append(" LEFT OUTER JOIN "); - reproducibilitySQL.append(TargetedMSManager.getTableInfoReplicateAnnotation(), "ra"); - reproducibilitySQL.append(" ON ra.ReplicateId = r.Id AND (LOWER(ra.Name) = 'day' OR LOWER(ra.Name) = 'samplegroup') WHERE r.RunId = "); - reproducibilitySQL.append(ExprColumn.STR_TABLE_ALIAS); - reproducibilitySQL.append(".RunId AND (r.SampleType IS NULL OR r.SampleType IN ('qc')) AND (ra.ReplicateId IS NOT NULL OR r.BatchName IS NOT NULL)"); - reproducibilitySQL.append(")"); - - // Render a link to the reproducibility report - ExprColumn reproducibilityCol = new ExprColumn(result, "Reproducibility", reproducibilitySQL, JdbcType.INTEGER, result.getColumn("Id")); - result.addColumn(reproducibilityCol); - reproducibilityCol.setURL(DetailsURL.fromString("passport-protein.view?proteinId=${Id}")); - - reproducibilityCol.setDisplayColumnFactory(colInfo -> new FontAwesomeLinkColumn(colInfo, "fa-th", "Reproducibility Report")); - - // Create SQL to see if we have one or more calibration curves. - // If there's a single match, the value will be the id of the curve's row. If there are multiple - // curves, the value will be the negative value of the run's row so that we can send the user - // to the full list of curves to let the user choose which peptide to view - SQLFragment calCurveSQL = new SQLFragment("(SELECT CASE WHEN COUNT(*) > 1 THEN "); - calCurveSQL.append(ExprColumn.STR_TABLE_ALIAS); - calCurveSQL.append(".RunId * -1 WHEN COUNT(*) = 1 THEN MIN(cc.Id) END FROM "); - calCurveSQL.append(TargetedMSManager.getTableInfoCalibrationCurve(), "cc"); - calCurveSQL.append(" INNER JOIN "); - calCurveSQL.append(TargetedMSManager.getTableInfoGeneralMolecule(), "gm"); - calCurveSQL.append(" ON cc.GeneralMoleculeId = gm.Id AND gm.PeptideGroupId = "); - calCurveSQL.append(ExprColumn.STR_TABLE_ALIAS); - calCurveSQL.append(".Id)"); - - ExprColumn calCurvesCol = new ExprColumn(result, "CalibrationCurves", calCurveSQL, JdbcType.INTEGER, result.getColumn("Id")); - result.addColumn(calCurvesCol); - - calCurvesCol.setDisplayColumnFactory(colInfo -> new FontAwesomeLinkColumn(colInfo, "fa-line-chart", "Calibration Curves") - { - @Override - protected String renderURLorValueURL(RenderContext ctx) - { - Number value = (Number)getValue(ctx); - if (value != null) - { - // If value is positive, it means there's a single curve and we can link directly to it - if (value.intValue() > 0) - { - return new ActionURL(TargetedMSController.ShowCalibrationCurveAction.class, getContainer()).addParameter("calibrationCurveId", value.intValue()).getLocalURIString(); - } - Long groupId = ctx.get(new FieldKey(getColumnInfo().getFieldKey().getParent(), "Id"), Long.class); - // If the value is negative, it's the run ID. Use it to send the user to the listing - if (value.intValue() < 0 && groupId != null) - { - return new ActionURL(TargetedMSController.ShowCalibrationCurvesAction.class, getContainer()). - addParameter("id", value.intValue() * -1). - addParameter("calibration_curves.GeneralMoleculeId/PeptideGroupId~eq", groupId). - getLocalURIString(); - } - } - return null; - } - }); - - labelColumn.setDisplayColumnFactory(new DisplayColumnFactory() - { - @Override - public DisplayColumn createRenderer(ColumnInfo colInfo) - { - FieldKey seqIdFK = new FieldKey(colInfo.getFieldKey().getParent(), "Id"); - Map params = new HashMap<>(); - params.put("id", seqIdFK); - JSONObject props = new JSONObject(); - props.put("width", 450); - props.put("title", "Protein Details"); - FieldKey containerFieldKey = result.getContainerFieldKey(); - return new AJAXDetailsDisplayColumn(colInfo, new ActionURL(TargetedMSController.ShowProteinAJAXAction.class, getContainer()), params, props, containerFieldKey) - { - @Override - public @NotNull Set getClientDependencies() - { - Set result = super.getClientDependencies(); - result.add(ClientDependency.fromPath("protein/ProteinCoverageMap.css")); - result.add(ClientDependency.fromPath("protein/ProteinCoverageMap.js")); - result.add(ClientDependency.fromPath("util.js")); - return result; - } - }; - } - }); - } - else - { - labelColumn.setLabel(TargetedMSSchema.COL_LIST); - } - result.getMutableColumnOrThrow("RunId").setFk(QueryForeignKey.from(this, cf).to(TABLE_TARGETED_MS_RUNS, "File", null)); - result.getMutableColumnOrThrow("RepresentativeDataState").setFk(QueryForeignKey.from(this, cf).to(TargetedMSSchema.TABLE_REPRESENTATIVE_DATA_STATE, "RowId", null)); - result.getMutableColumnOrThrow("RepresentativeDataState").setHidden(true); - - SQLFragment libPrecursorCountSQL; - if (TargetedMSManager.isLibraryFolder(getContainer())) - { - // In a protein library folder, peptide groups that are in the library, and all their precursors, are marked as "representative". - // In a peptide library, however, only precursors are marked as "representative". So, applying a filter on the "representative" - // state of the peptide groups in a peptide library folder will display an empty grid. - // Add a "RepresentativePrecursorCount" column for the number of precursors in a peptide group that are marked as "representative". - // This count can be used as a filter to display peptide groups that are in the current library in both protein and peptide library folders. - libPrecursorCountSQL = new SQLFragment(" (SELECT COUNT(p.Id) FROM ") - .append(TargetedMSManager.getTableInfoGeneralPrecursor(), "p") - .append(" INNER JOIN ").append(TargetedMSManager.getTableInfoGeneralMolecule(), "gm").append(" ON p.generalmoleculeid = gm.id ") - .append(" WHERE gm.peptidegroupid = ").append(ExprColumn.STR_TABLE_ALIAS).append(".id ") - .append(" AND p.RepresentativeDataState = ? ").add(RepresentativeDataState.Representative.ordinal()) - .append(") "); - } - else - { - libPrecursorCountSQL = new SQLFragment(" (SELECT 0) "); - } - ExprColumn currentLibPrecursorCountCol = new ExprColumn(result, "RepresentativePrecursorCount", libPrecursorCountSQL, JdbcType.INTEGER); - currentLibPrecursorCountCol.setHidden(true); - result.addColumn(currentLibPrecursorCountCol); - return result; + return createGroupTable(name, cf); } if (TABLE_REPLICATE.equalsIgnoreCase(name)) @@ -1382,6 +1275,33 @@ public DisplayColumn createRenderer(ColumnInfo colInfo) FieldKey.fromParts("Description") )); + var labelColumn = result.getMutableColumnOrThrow("Label"); + Map labelUrlParams = new HashMap<>(); + labelUrlParams.put("id", FieldKey.fromParts("PeptideGroupId")); + labelUrlParams.put("proteinId", FieldKey.fromParts("Id")); + labelColumn.setURL(new DetailsURL(new ActionURL(TargetedMSController.ShowProteinAction.class, getContainer()), labelUrlParams)); + FieldKey containerFieldKey = result.getContainerFieldKey(); + labelColumn.setDisplayColumnFactory(colInfo -> { + Map params = new HashMap<>(); + params.put("id", new FieldKey(colInfo.getFieldKey().getParent(), "PeptideGroupId")); + params.put("proteinId", new FieldKey(colInfo.getFieldKey().getParent(), "Id")); + JSONObject props = new JSONObject(); + props.put("width", 450); + props.put("title", "Protein Details"); + return new AJAXDetailsDisplayColumn(colInfo, new ActionURL(TargetedMSController.ShowProteinAJAXAction.class, getContainer()), params, props, containerFieldKey) + { + @Override + public @NotNull Set getClientDependencies() + { + Set deps = super.getClientDependencies(); + deps.add(ClientDependency.fromPath("protein/ProteinCoverageMap.css")); + deps.add(ClientDependency.fromPath("protein/ProteinCoverageMap.js")); + deps.add(ClientDependency.fromPath("util.js")); + return deps; + } + }; + }); + return result; } if (TABLE_PEPTIDE_GROUP_ANNOTATION.equalsIgnoreCase(name)) @@ -1705,6 +1625,168 @@ else if (TABLE_INSTRUMENT_USAGE_PAYMENT.equalsIgnoreCase(name)) return null; } + private @NotNull TargetedMSTable createGroupTable(String name, ContainerFilter cf) + { + boolean proteomics = TABLE_PEPTIDE_GROUP.equalsIgnoreCase(name); + TargetedMSTable result = new AnnotatedTargetedMSTable(getSchema().getTable(TABLE_PEPTIDE_GROUP), + this, cf, + ContainerJoinType.RunFK, + TargetedMSManager.getTableInfoPeptideGroupAnnotation(), + "PeptideGroupId", + proteomics ? COL_PROTEIN : COL_LIST, + "protein") + { + @Override + protected Class getDetailsActionClass() + { + return TargetedMSController.ShowProteinAction.class; + } + }; + var labelColumn = result.getMutableColumnOrThrow("Label"); + labelColumn.setURL(result.getDetailsURL(null, null)); + if (proteomics) + { + SQLFragment peptideCountSQL = new SQLFragment("(SELECT COUNT(p.Id) FROM ") + .append(TargetedMSManager.getTableInfoPeptide(), "p") + .append(" INNER JOIN ").append(TargetedMSManager.getTableInfoGeneralMolecule(), "gm") + .append(" ON p.Id = gm.Id WHERE gm.PeptideGroupId = ") + .append(ExprColumn.STR_TABLE_ALIAS).append(".Id)"); + ExprColumn peptideCountCol = new ExprColumn(result, "PeptideCount", peptideCountSQL, JdbcType.INTEGER, result.getColumn("Id")); + peptideCountCol.setLabel("Peptides"); + peptideCountCol.setURL(result.getDetailsURL(null, null)); + result.addColumn(peptideCountCol); + + // Figure out if we have at least 3 replicates marked as QCs, and we have a "Day" or "SampleGroup" annotation, or a value for BatchName + SQLFragment reproducibilitySQL = new SQLFragment("(SELECT CASE WHEN COUNT(DISTINCT r.Id) > 2 THEN COUNT(DISTINCT r.Id) END FROM "); + reproducibilitySQL.append(TargetedMSManager.getTableInfoReplicate(), "r"); + reproducibilitySQL.append(" LEFT OUTER JOIN "); + reproducibilitySQL.append(TargetedMSManager.getTableInfoReplicateAnnotation(), "ra"); + reproducibilitySQL.append(" ON ra.ReplicateId = r.Id AND (LOWER(ra.Name) = 'day' OR LOWER(ra.Name) = 'samplegroup') WHERE r.RunId = "); + reproducibilitySQL.append(ExprColumn.STR_TABLE_ALIAS); + reproducibilitySQL.append(".RunId AND (r.SampleType IS NULL OR r.SampleType IN ('qc')) AND (ra.ReplicateId IS NOT NULL OR r.BatchName IS NOT NULL)"); + reproducibilitySQL.append(")"); + + // Render a link to the reproducibility report + ExprColumn reproducibilityCol = new ExprColumn(result, "Reproducibility", reproducibilitySQL, JdbcType.INTEGER, result.getColumn("Id")); + result.addColumn(reproducibilityCol); + reproducibilityCol.setURL(DetailsURL.fromString("passport-protein.view?proteinId=${Id}")); + + reproducibilityCol.setDisplayColumnFactory(colInfo -> new FontAwesomeLinkColumn(colInfo, "fa-th", "Reproducibility Report")); + + // Create SQL to see if we have one or more calibration curves. + // If there's a single match, the value will be the id of the curve's row. If there are multiple + // curves, the value will be the negative value of the run's row so that we can send the user + // to the full list of curves to let the user choose which peptide to view + SQLFragment calCurveSQL = new SQLFragment("(SELECT CASE WHEN COUNT(*) > 1 THEN "); + calCurveSQL.append(ExprColumn.STR_TABLE_ALIAS); + calCurveSQL.append(".RunId * -1 WHEN COUNT(*) = 1 THEN MIN(cc.Id) END FROM "); + calCurveSQL.append(TargetedMSManager.getTableInfoCalibrationCurve(), "cc"); + calCurveSQL.append(" INNER JOIN "); + calCurveSQL.append(TargetedMSManager.getTableInfoGeneralMolecule(), "gm"); + calCurveSQL.append(" ON cc.GeneralMoleculeId = gm.Id AND gm.PeptideGroupId = "); + calCurveSQL.append(ExprColumn.STR_TABLE_ALIAS); + calCurveSQL.append(".Id)"); + + ExprColumn calCurvesCol = new ExprColumn(result, "CalibrationCurves", calCurveSQL, JdbcType.INTEGER, result.getColumn("Id")); + result.addColumn(calCurvesCol); + + calCurvesCol.setDisplayColumnFactory(colInfo -> new FontAwesomeLinkColumn(colInfo, "fa-line-chart", "Calibration Curves") + { + @Override + protected String renderURLorValueURL(RenderContext ctx) + { + Number value = (Number)getValue(ctx); + if (value != null) + { + // If value is positive, it means there's a single curve and we can link directly to it + if (value.intValue() > 0) + { + return new ActionURL(TargetedMSController.ShowCalibrationCurveAction.class, getContainer()).addParameter("calibrationCurveId", value.intValue()).getLocalURIString(); + } + Long groupId = ctx.get(new FieldKey(getColumnInfo().getFieldKey().getParent(), "Id"), Long.class); + // If the value is negative, it's the run ID. Use it to send the user to the listing + if (value.intValue() < 0 && groupId != null) + { + return new ActionURL(TargetedMSController.ShowCalibrationCurvesAction.class, getContainer()). + addParameter("id", value.intValue() * -1). + addParameter("calibration_curves.GeneralMoleculeId/PeptideGroupId~eq", groupId). + getLocalURIString(); + } + } + return null; + } + }); + + labelColumn.setDisplayColumnFactory(new DisplayColumnFactory() + { + @Override + public DisplayColumn createRenderer(ColumnInfo colInfo) + { + FieldKey seqIdFK = new FieldKey(colInfo.getFieldKey().getParent(), "Id"); + Map params = new HashMap<>(); + params.put("id", seqIdFK); + JSONObject props = new JSONObject(); + props.put("width", 450); + props.put("title", "Protein Details"); + FieldKey containerFieldKey = result.getContainerFieldKey(); + return new AJAXDetailsDisplayColumn(colInfo, new ActionURL(TargetedMSController.ShowProteinAJAXAction.class, getContainer()), params, props, containerFieldKey) + { + @Override + public @NotNull Set getClientDependencies() + { + Set result = super.getClientDependencies(); + result.add(ClientDependency.fromPath("protein/ProteinCoverageMap.css")); + result.add(ClientDependency.fromPath("protein/ProteinCoverageMap.js")); + result.add(ClientDependency.fromPath("util.js")); + return result; + } + }; + } + }); + } + else + { + labelColumn.setLabel(TargetedMSSchema.COL_LIST); + + SQLFragment moleculeCountSQL = new SQLFragment("(SELECT COUNT(m.Id) FROM ") + .append(TargetedMSManager.getTableInfoMolecule(), "m") + .append(" INNER JOIN ").append(TargetedMSManager.getTableInfoGeneralMolecule(), "gm") + .append(" ON m.Id = gm.Id WHERE gm.PeptideGroupId = ") + .append(ExprColumn.STR_TABLE_ALIAS).append(".Id)"); + ExprColumn moleculeCountCol = new ExprColumn(result, "MoleculeCount", moleculeCountSQL, JdbcType.INTEGER, result.getColumn("Id")); + moleculeCountCol.setLabel("Molecules"); + moleculeCountCol.setURL(result.getDetailsURL(null, null)); + result.addColumn(moleculeCountCol); + } + result.getMutableColumnOrThrow("RunId").setFk(QueryForeignKey.from(this, cf).to(TABLE_TARGETED_MS_RUNS, "File", null)); + result.getMutableColumnOrThrow("RepresentativeDataState").setFk(QueryForeignKey.from(this, cf).to(TargetedMSSchema.TABLE_REPRESENTATIVE_DATA_STATE, "RowId", null)); + result.getMutableColumnOrThrow("RepresentativeDataState").setHidden(true); + + SQLFragment libPrecursorCountSQL; + if (TargetedMSManager.isLibraryFolder(getContainer())) + { + // In a protein library folder, peptide groups that are in the library, and all their precursors, are marked as "representative". + // In a peptide library, however, only precursors are marked as "representative". So, applying a filter on the "representative" + // state of the peptide groups in a peptide library folder will display an empty grid. + // Add a "RepresentativePrecursorCount" column for the number of precursors in a peptide group that are marked as "representative". + // This count can be used as a filter to display peptide groups that are in the current library in both protein and peptide library folders. + libPrecursorCountSQL = new SQLFragment(" (SELECT COUNT(p.Id) FROM ") + .append(TargetedMSManager.getTableInfoGeneralPrecursor(), "p") + .append(" INNER JOIN ").append(TargetedMSManager.getTableInfoGeneralMolecule(), "gm").append(" ON p.generalmoleculeid = gm.id ") + .append(" WHERE gm.peptidegroupid = ").append(ExprColumn.STR_TABLE_ALIAS).append(".id ") + .append(" AND p.RepresentativeDataState = ? ").add(RepresentativeDataState.Representative.ordinal()) + .append(") "); + } + else + { + libPrecursorCountSQL = new SQLFragment(" (SELECT 0) "); + } + ExprColumn currentLibPrecursorCountCol = new ExprColumn(result, "RepresentativePrecursorCount", libPrecursorCountSQL, JdbcType.INTEGER); + currentLibPrecursorCountCol.setHidden(true); + result.addColumn(currentLibPrecursorCountCol); + return result; + } + @Override protected QuerySettings createQuerySettings(String dataRegionName, String queryName, String viewName) { @@ -1934,7 +2016,7 @@ private QueryDefinition createRunScopedPTMQuery(String prefix, String baseQueryN long runId = Long.parseLong(runIdString); if (TargetedMSManager.getFolderType(getContainer()) != TargetedMSService.FolderType.ExperimentMAM) { - throw new IllegalStateException("PTM queries are only supported in ExperimentMAM folders"); + throw new NotFoundException("PTM queries are only supported in ExperimentMAM folders"); } QueryDefinition queryDef = Objects.requireNonNull(getQueryDef(baseQueryName)); diff --git a/src/org/labkey/targetedms/query/AnnotatedTargetedMSTable.java b/src/org/labkey/targetedms/query/AnnotatedTargetedMSTable.java index f462dc497..5e0da5664 100644 --- a/src/org/labkey/targetedms/query/AnnotatedTargetedMSTable.java +++ b/src/org/labkey/targetedms/query/AnnotatedTargetedMSTable.java @@ -56,6 +56,7 @@ public class AnnotatedTargetedMSTable extends TargetedMSTable { private static final String ANNOT_NAME_VALUE_SEPARATOR = ": "; private static final String ANNOT_DELIMITER = "\n"; + public static final String NOTE_ANNOTATIONS_COLUMN_NAME = "NoteAnnotations"; public AnnotatedTargetedMSTable(TableInfo table, TargetedMSSchema schema, @@ -160,7 +161,7 @@ protected void addAnnotationsColumns(TableInfo annotationTableInfo, String annot annotationsColumn.setDisplayColumnFactory(AnnotationsDisplayColumn::new); - var noteAnnotation = WrappedColumnInfo.wrapAsCopy(this, FieldKey.fromParts("NoteAnnotations"), annotationsColumn, labelPrefix + " Note/Annotations", null); + var noteAnnotation = WrappedColumnInfo.wrapAsCopy(this, FieldKey.fromParts(NOTE_ANNOTATIONS_COLUMN_NAME), annotationsColumn, labelPrefix + " Note/Annotations", null); noteAnnotation.setDisplayColumnFactory(AnnotationUIDisplayColumn::new); addColumn(noteAnnotation); } diff --git a/src/org/labkey/targetedms/query/MoleculePrecursorTableInfo.java b/src/org/labkey/targetedms/query/MoleculePrecursorTableInfo.java index 71e0d3d70..fcb0652c9 100644 --- a/src/org/labkey/targetedms/query/MoleculePrecursorTableInfo.java +++ b/src/org/labkey/targetedms/query/MoleculePrecursorTableInfo.java @@ -57,16 +57,16 @@ public MoleculePrecursorTableInfo(final TableInfo tableInfo, String tableName, f ArrayList visibleColumns = new ArrayList<>(); visibleColumns.add(FieldKey.fromParts("MoleculeId", "PeptideGroupId", "Label")); visibleColumns.add(FieldKey.fromParts("MoleculeId", "PeptideGroupId", "Description")); - visibleColumns.add(FieldKey.fromParts("MoleculeId", "PeptideGroupId", "NoteAnnotations")); + visibleColumns.add(FieldKey.fromParts("MoleculeId", "PeptideGroupId", NOTE_ANNOTATIONS_COLUMN_NAME)); visibleColumns.add(FieldKey.fromParts("MoleculeId", "Molecule")); visibleColumns.add(FieldKey.fromParts("MoleculeId", "IonFormula")); - visibleColumns.add(FieldKey.fromParts("MoleculeId", "NoteAnnotations")); + visibleColumns.add(FieldKey.fromParts("MoleculeId", NOTE_ANNOTATIONS_COLUMN_NAME)); visibleColumns.add(FieldKey.fromParts("MoleculeId", "MassAverage")); visibleColumns.add(FieldKey.fromParts("MoleculeId", "MassMonoisotopic")); visibleColumns.add(FieldKey.fromParts("CustomIonName")); - visibleColumns.add(FieldKey.fromParts("NoteAnnotations")); + visibleColumns.add(FieldKey.fromParts(NOTE_ANNOTATIONS_COLUMN_NAME)); visibleColumns.add(FieldKey.fromParts("Charge")); visibleColumns.add(FieldKey.fromParts("Mz")); visibleColumns.add(FieldKey.fromParts("MassAverage")); diff --git a/src/org/labkey/targetedms/query/PrecursorTableInfo.java b/src/org/labkey/targetedms/query/PrecursorTableInfo.java index 67a825a12..a247e232f 100644 --- a/src/org/labkey/targetedms/query/PrecursorTableInfo.java +++ b/src/org/labkey/targetedms/query/PrecursorTableInfo.java @@ -64,16 +64,16 @@ public PrecursorTableInfo(final TableInfo tableInfo, ContainerFilter cf, String visibleColumns.add(FieldKey.fromParts("PeptideId", "PeptideGroupId", "Label")); visibleColumns.add(FieldKey.fromParts("PeptideId", "PeptideGroupId", "Description")); - visibleColumns.add(FieldKey.fromParts("PeptideId", "PeptideGroupId", "NoteAnnotations")); + visibleColumns.add(FieldKey.fromParts("PeptideId", "PeptideGroupId", NOTE_ANNOTATIONS_COLUMN_NAME)); visibleColumns.add(FieldKey.fromParts("PeptideId", ModifiedSequenceDisplayColumn.PEPTIDE_COLUMN_NAME)); - visibleColumns.add(FieldKey.fromParts("PeptideId", "NoteAnnotations")); + visibleColumns.add(FieldKey.fromParts("PeptideId", NOTE_ANNOTATIONS_COLUMN_NAME)); visibleColumns.add(FieldKey.fromParts("PeptideId", "NumMissedCleavages")); visibleColumns.add(FieldKey.fromParts("PeptideId", "CalcNeutralMass")); visibleColumns.add(FieldKey.fromParts("PeptideId", "Rank")); visibleColumns.add(FieldKey.fromParts(ModifiedSequenceDisplayColumn.PRECURSOR_COLUMN_NAME)); - visibleColumns.add(FieldKey.fromParts("NoteAnnotations")); + visibleColumns.add(FieldKey.fromParts(NOTE_ANNOTATIONS_COLUMN_NAME)); visibleColumns.add(FieldKey.fromParts("IsotopeLabelId", "Name")); visibleColumns.add(FieldKey.fromParts("Charge")); visibleColumns.add(FieldKey.fromParts("Mz")); diff --git a/src/org/labkey/targetedms/view/DocumentGeneralMoleculesView.java b/src/org/labkey/targetedms/view/DocumentGeneralMoleculesView.java new file mode 100644 index 000000000..adf38d4c8 --- /dev/null +++ b/src/org/labkey/targetedms/view/DocumentGeneralMoleculesView.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.targetedms.view; + +import org.labkey.api.data.CompareType; +import org.labkey.api.data.TableInfo; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QueryView; +import org.labkey.api.view.ViewContext; +import org.labkey.targetedms.TargetedMSSchema; +import org.labkey.targetedms.query.TargetedMSTable; + +import java.util.ArrayList; +import java.util.List; + +import static org.labkey.targetedms.query.AnnotatedTargetedMSTable.NOTE_ANNOTATIONS_COLUMN_NAME; + +public class DocumentGeneralMoleculesView extends QueryView +{ + private final TargetedMSSchema _schema; + private final long _runId; + + public DocumentGeneralMoleculesView(ViewContext ctx, TargetedMSSchema schema, long runId, + String tableName, String title) + { + super(schema, schema.getSettings(ctx, tableName, tableName), null); + _schema = schema; + _runId = runId; + setTitle(title); + } + + @Override + public TableInfo createTable() + { + TargetedMSTable tinfo = (TargetedMSTable) _schema.getTable(getSettings().getQueryName(), null, true, true); + tinfo.addContainerTableFilter(new CompareType.EqualsCompareClause(FieldKey.fromParts("Id"), CompareType.EQUAL, _runId)); + List cols = new ArrayList<>(tinfo.getDefaultVisibleColumns()); + cols.remove(FieldKey.fromParts("PeptideGroupId", "RunId", "Folder", "Path")); + cols.remove(FieldKey.fromParts("PeptideGroupId", "RunId", "File")); + cols.remove(FieldKey.fromParts(NOTE_ANNOTATIONS_COLUMN_NAME)); + cols.remove(FieldKey.fromParts("NormalizationMethod")); + cols.remove(FieldKey.fromParts("InternalStandardConcentration")); + cols.remove(FieldKey.fromParts("AttributeGroupId")); + cols.remove(FieldKey.fromParts("ConcentrationMultiplier")); + + cols.remove(FieldKey.fromParts("MoleculeId")); + + cols.remove(FieldKey.fromParts("PeptideModifiedSequence")); + cols.remove(FieldKey.fromParts("Sequence")); + tinfo.setDefaultVisibleColumns(cols); + tinfo.setLocked(true); + return tinfo; + } +} diff --git a/src/org/labkey/targetedms/view/DocumentPeptideGroupView.java b/src/org/labkey/targetedms/view/DocumentPeptideGroupView.java new file mode 100644 index 000000000..89b333f06 --- /dev/null +++ b/src/org/labkey/targetedms/view/DocumentPeptideGroupView.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.targetedms.view; + +import org.labkey.api.data.CompareType; +import org.labkey.api.data.TableInfo; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QueryView; +import org.labkey.api.view.ViewContext; +import org.labkey.targetedms.TargetedMSSchema; +import org.labkey.targetedms.query.TargetedMSTable; + +import java.util.ArrayList; +import java.util.List; + +import static org.labkey.targetedms.query.AnnotatedTargetedMSTable.NOTE_ANNOTATIONS_COLUMN_NAME; + +public class DocumentPeptideGroupView extends QueryView +{ + private final TargetedMSSchema _schema; + private final long _runId; + + public DocumentPeptideGroupView(ViewContext ctx, TargetedMSSchema schema, long runId, + String tableName, String title) + { + super(schema, schema.getSettings(ctx, tableName, tableName), null); + _schema = schema; + _runId = runId; + setTitle(title); + } + + @Override + public TableInfo createTable() + { + TargetedMSTable tinfo = (TargetedMSTable) _schema.getTable(getSettings().getQueryName(), null, true, true); + tinfo.addContainerTableFilter(new CompareType.EqualsCompareClause(FieldKey.fromParts("Id"), CompareType.EQUAL, _runId)); + String queryName = getSettings().getQueryName(); + if (TargetedMSSchema.TABLE_PEPTIDE_GROUP.equalsIgnoreCase(queryName)) + getSettings().getBaseFilter().addCondition(FieldKey.fromParts("PeptideCount"), 0, CompareType.GT); + else if (TargetedMSSchema.TABLE_MOLECULE_GROUP.equalsIgnoreCase(queryName)) + getSettings().getBaseFilter().addCondition(FieldKey.fromParts("MoleculeCount"), 0, CompareType.GT); + List cols = new ArrayList<>(tinfo.getDefaultVisibleColumns()); + cols.remove(FieldKey.fromParts("RunId")); + cols.remove(FieldKey.fromParts(NOTE_ANNOTATIONS_COLUMN_NAME)); + cols.remove(FieldKey.fromParts("Modified")); + tinfo.setDefaultVisibleColumns(cols); + tinfo.setLocked(true); + return tinfo; + } +} diff --git a/src/org/labkey/targetedms/view/DocumentPrecursorsView.java b/src/org/labkey/targetedms/view/DocumentPrecursorsView.java index 604db0400..4732c8d19 100644 --- a/src/org/labkey/targetedms/view/DocumentPrecursorsView.java +++ b/src/org/labkey/targetedms/view/DocumentPrecursorsView.java @@ -36,5 +36,4 @@ public DocumentPrecursorsView(ViewContext ctx, TargetedMSSchema schema, String q _targetedMsSchema = schema; _tableName = queryName; } - } diff --git a/src/org/labkey/targetedms/view/DocumentProteinView.java b/src/org/labkey/targetedms/view/DocumentProteinView.java new file mode 100644 index 000000000..aa17e2cc7 --- /dev/null +++ b/src/org/labkey/targetedms/view/DocumentProteinView.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.labkey.targetedms.view; + +import org.labkey.api.data.CompareType; +import org.labkey.api.data.TableInfo; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QueryView; +import org.labkey.api.view.ViewContext; +import org.labkey.targetedms.TargetedMSSchema; +import org.labkey.targetedms.query.TargetedMSTable; + +public class DocumentProteinView extends QueryView +{ + private final TargetedMSSchema _schema; + private final long _runId; + + public DocumentProteinView(ViewContext ctx, TargetedMSSchema schema, long runId) + { + super(schema, schema.getSettings(ctx, TargetedMSSchema.TABLE_PROTEIN, TargetedMSSchema.TABLE_PROTEIN), null); + _schema = schema; + _runId = runId; + setTitle("Proteins"); + } + + @Override + public TableInfo createTable() + { + TargetedMSTable tinfo = (TargetedMSTable) _schema.getTable(TargetedMSSchema.TABLE_PROTEIN, null, true, true); + tinfo.addContainerTableFilter(new CompareType.EqualsCompareClause(FieldKey.fromParts("Id"), CompareType.EQUAL, _runId)); + tinfo.setLocked(true); + return tinfo; + } +} diff --git a/src/org/labkey/targetedms/view/runSummaryView.jsp b/src/org/labkey/targetedms/view/runSummaryView.jsp index ba61ad3b5..3d068102c 100644 --- a/src/org/labkey/targetedms/view/runSummaryView.jsp +++ b/src/org/labkey/targetedms/view/runSummaryView.jsp @@ -24,9 +24,7 @@ <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.template.ClientDependencies" %> <%@ page import="org.labkey.targetedms.TargetedMSController" %> -<%@ page import="org.labkey.targetedms.TargetedMSManager" %> <%@ page import="org.labkey.targetedms.TargetedMSRun" %> -<%@ page import="org.labkey.targetedms.TargetedMSSchema" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%! @@ -52,6 +50,15 @@ ActionURL precursorListAction = new ActionURL(TargetedMSController.ShowPrecursorListAction.class, getContainer()); precursorListAction.addParameter("id", run.getId()); + ActionURL peptideGroupListAction = new ActionURL(TargetedMSController.ShowPeptideGroupListAction.class, getContainer()); + peptideGroupListAction.addParameter("id", run.getId()); + + ActionURL moleculeGroupListAction = new ActionURL(TargetedMSController.ShowMoleculeGroupListAction.class, getContainer()); + moleculeGroupListAction.addParameter("id", run.getId()); + + ActionURL proteinListAction = new ActionURL(TargetedMSController.ShowProteinListAction.class, getContainer()); + proteinListAction.addParameter("id", run.getId()); + ActionURL ptmReportAction = new ActionURL(TargetedMSController.ShowPTMReportAction.class, getContainer()); ptmReportAction.addParameter("id", run.getId()); @@ -67,6 +74,12 @@ ActionURL calibrationCurveListAction = new ActionURL(TargetedMSController.ShowCalibrationCurvesAction.class, getContainer()); calibrationCurveListAction.addParameter("id", run.getId()); + ActionURL peptideListAction = new ActionURL(TargetedMSController.ShowPeptideListAction.class, getContainer()); + peptideListAction.addParameter("id", run.getId()); + + ActionURL moleculeListAction = new ActionURL(TargetedMSController.ShowMoleculeListAction.class, getContainer()); + moleculeListAction.addParameter("id", run.getId()); + ActionURL listAction = new ActionURL(TargetedMSController.ShowListsAction.class, getContainer()); listAction.addParameter("id", run.getId()); @@ -80,7 +93,6 @@ if(c.hasPermission(getUser(), UpdatePermission.class)) renameAction = TargetedMSController.getRenameRunURL(c, run, getActionURL()); - String peptideGroupLabel = TargetedMSManager.containerHasSmallMolecules(getContainer()) ? TargetedMSSchema.COL_LIST.toLowerCase() : TargetedMSSchema.COL_PROTEIN.toLowerCase(); %> @@ -94,9 +106,12 @@  
- <%= h(StringUtilsLabKey.pluralize(run.getPeptideGroupCount(), peptideGroupLabel))%>, - <% if (run.getPeptideCount() > 0) { %><%= h(StringUtilsLabKey.pluralize(run.getPeptideCount(), "peptide"))%>,<% } %> - <% if (run.getSmallMoleculeCount() > 0) { %>"><%= h(StringUtilsLabKey.pluralize(run.getSmallMoleculeCount(), "small molecule"))%>,<% } %> + <% if (run.getPeptideGroupCount() > 0) { %><%= h(StringUtilsLabKey.pluralize(run.getPeptideGroupCount(), "protein group"))%><% + if (run.getProteinCount() > 0) { %> (with <%= h(StringUtilsLabKey.pluralize(run.getProteinCount(), "protein"))%>)<% } %>, + <% } %> + <% if (run.getMoleculeGroupCount() > 0) { %><%= h(StringUtilsLabKey.pluralize(run.getMoleculeGroupCount(), "molecule list"))%>,<% } %> + <% if (run.getPeptideCount() > 0) { %><%= h(StringUtilsLabKey.pluralize(run.getPeptideCount(), "peptide"))%>,<% } %> + <% if (run.getSmallMoleculeCount() > 0) { %><%= h(StringUtilsLabKey.pluralize(run.getSmallMoleculeCount(), "small molecule"))%>,<% } %> <%= h(StringUtilsLabKey.pluralize(run.getPrecursorCount(), "precursor"))%>, <%= h(StringUtilsLabKey.pluralize(run.getTransitionCount(), "transition"))%>  -  diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSExperimentTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSExperimentTest.java index 2f70382b3..6430a5336 100644 --- a/test/src/org/labkey/test/tests/targetedms/TargetedMSExperimentTest.java +++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSExperimentTest.java @@ -90,6 +90,7 @@ public void testSteps() throws IOException, CommandException verifyImportedSmallMoleculeData(); verifyAttributeGroupIdCalcs(); testRawFileLinks(SKY_FILE_SMALLMOL_PEP); + verifyRunDetailLinks(); // SKYD version 14 importData(SKY_FILE_SKYD_14, ++jobCount); @@ -165,7 +166,7 @@ protected void verifyImportedPeptideData() { clickAndWait(Locator.linkContainingText("Panorama Dashboard")); clickAndWait(Locator.linkContainingText(SKY_FILE)); - verifyRunSummaryCountsPep(24,44,0, 88,296, 1, 0, 0); + verifyRunSummaryCountsPep(24, 24, 44, 0, 88, 296, 1, 0, 0); verifyDocumentDetails(false); verifyPeptide(); } @@ -175,7 +176,7 @@ protected void verifyImportedSmallMoleculeData() { clickAndWait(Locator.linkContainingText("Panorama Dashboard")); clickAndWait(Locator.linkContainingText(SKY_FILE_SMALLMOL_PEP)); - verifyRunSummaryCountsSmallMol(27, 44, 98, 186, 394, 5, 0, 0); // Number of protein (groups), peptides, precursors, transitions, small molecules + verifyRunSummaryCountsMixed(24, 3, 24, 44, 98, 186, 394, 5, 0, 0); verifyDocumentDetails(true); verifyMolecule(); } @@ -895,4 +896,54 @@ private void verifyNoGuestAccessMessage(boolean selfSignupEnabled) assertFalse(fullBodyText.contains("Don't have an account? Register")); } } + + @LogMethod + protected void verifyRunDetailLinks() + { + // From the runs listing (Panorama Dashboard), clicking a run's name navigates to the default + // run view. A mixed proteomics + small molecule document shows both precursor grids there. + // precursors_view is nested by protein group (24 total groups, 10 shown per page due to maxRows). + // getDataRowCount() returns outer rows + even-indexed expanded rows: 5+5+5=15 for 10 groups. + // small_mol_precursors_view has 3 molecule groups (all fit on one page): 2+1+2=5. + goToDashboard(); + clickAndWait(Locator.linkContainingText(SKY_FILE_SMALLMOL_PEP)); + DataRegionTable precursorsView = DataRegion(getDriver()).withName("precursors_view").find(); + assertEquals(15, precursorsView.getDataRowCount()); + DataRegionTable smallMolPrecursorsView = DataRegion(getDriver()).withName("small_mol_precursors_view").find(); + assertEquals(5, smallMolPrecursorsView.getDataRowCount()); + + // Test each header link from runSummaryView.jsp while on the default run view. + // Each link must reach its own dedicated list page, not the combined precursor list. + + // "24 protein groups" → protein group list: one row per peptide-bearing group + clickAndWait(Locator.linkContainingText("24 protein groups")); + DataRegionTable peptideGroupView = DataRegion(getDriver()).withName("PeptideGroup").find(); + assertEquals(24, peptideGroupView.getDataRowCount()); + + // "3 molecule lists" → molecule group list: one row per molecule-bearing group + clickAndWait(Locator.linkContainingText("3 molecule lists")); + DataRegionTable moleculeGroupView = DataRegion(getDriver()).withName("MoleculeGroup").find(); + assertEquals(3, moleculeGroupView.getDataRowCount()); + + // "44 peptides" → peptide list page: one row per peptide + clickAndWait(Locator.linkContainingText("44 peptides")); + DataRegionTable peptideView = DataRegion(getDriver()).withName("Peptide").find(); + assertEquals(44, peptideView.getDataRowCount()); + + // "98 small molecules" → small molecule list page: one row per molecule + clickAndWait(Locator.linkContainingText("98 small molecules")); + DataRegionTable moleculeView = DataRegion(getDriver()).withName("Molecule").find(); + assertEquals(98, moleculeView.getDataRowCount()); + + // Verify the runSummaryView.jsp header links also work when on a non-default run detail page. + // The header is embedded on every run detail view, not just the precursor list. + // From the small molecule list page, click "24 protein groups". + clickAndWait(Locator.linkContainingText("24 protein groups")); + peptideGroupView = DataRegion(getDriver()).withName("PeptideGroup").find(); + assertEquals(24, peptideGroupView.getDataRowCount()); + // From the protein group list page, click "44 peptides". + clickAndWait(Locator.linkContainingText("44 peptides")); + peptideView = DataRegion(getDriver()).withName("Peptide").find(); + assertEquals(44, peptideView.getDataRowCount()); + } } diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSLibraryTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSLibraryTest.java index ea4057fe6..0ede73415 100644 --- a/test/src/org/labkey/test/tests/targetedms/TargetedMSLibraryTest.java +++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSLibraryTest.java @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.stream.IntStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -111,7 +112,7 @@ protected void verifyRevision1() // Verify proteins in the library // Proteins in SKY_FILE1: "CTCF", "TAF11", "MAX", "QPrEST_CystC_HPRR5000001", "HPRR1440042", "DifferentProteinSameLabel", "iRT-C18 Standard Peptides" List proteins = Arrays.asList("CTCF", "TAF11", "MAX", "QPrEST_CystC_HPRR5000001", "HPRR1440042", "DifferentProteinSameLabel", "iRT-C18 Standard Peptides"); - List files = new ArrayList(); + List files = new ArrayList<>(); for (int i = 0; i < proteins.size(); i++) { files.add(SKY_FILE1); // All proteins are from the same file in version 1. @@ -132,7 +133,9 @@ private void verifyLibraryProteins(List proteins, List files, in int i = 0; for(String protein: proteins) { - int idx = proteinsTable.getRowIndex("Label", protein); + int idx = IntStream.range(0, proteinsTable.getDataRowCount()) + .filter(row -> protein.equals(proteinsTable.getDataAsText(row, "Label"))) + .findFirst().orElse(-1); assertTrue("Expected protein " + protein + " not found in table", idx != -1); ListfileName = proteinsTable.getRowDataAsText(idx, "RunId/File"); assertEquals(1, fileName.size()); @@ -275,8 +278,8 @@ private void verifyAndResolveConflicts() var proteinName = "MAX"; var oldProteinCb = getCheckBox(proteinName, true).waitForElement(shortWait()); var newProteinCb = getCheckBox(proteinName, false).waitForElement(shortWait()); - assertTrue(isChecked(newProteinCb)); // checkbox for the protein in the new document should be checked - assertFalse(isChecked(oldProteinCb)); // checkbox for the protein in the old document should be unchecked + assertTrue(newProteinCb.isSelected()); // checkbox for the protein in the new document should be checked + assertFalse(oldProteinCb.isSelected()); // checkbox for the protein in the old document should be unchecked // check, uncheck and check again (See Issue 44424) changeChecked(oldProteinCb, newProteinCb , true, proteinName); changeChecked(oldProteinCb, newProteinCb , false, proteinName); @@ -290,13 +293,13 @@ private void changeChecked(WebElement oldProteinCb, WebElement newProteinCb, boo setCheckbox(oldProteinCb, selectOld); if (selectOld) { - assertFalse("Expected " + proteinName + " from the new document to be unchecked", isChecked(newProteinCb)); // new protein unchecked - assertTrue("Expected " + proteinName + " from the old document to be checked", isChecked(oldProteinCb)); // old protein checked + assertFalse("Expected " + proteinName + " from the new document to be unchecked", newProteinCb.isSelected()); // new protein unchecked + assertTrue("Expected " + proteinName + " from the old document to be checked", oldProteinCb.isSelected()); // old protein checked } else { - assertTrue("Expected " + proteinName + " from the new document to be checked", isChecked(newProteinCb)); // new protein checked - assertFalse("Expected " + proteinName + " from the old document to be unchecked", isChecked(oldProteinCb)); // old protein unchecked + assertTrue("Expected " + proteinName + " from the new document to be checked", newProteinCb.isSelected()); // new protein checked + assertFalse("Expected " + proteinName + " from the old document to be unchecked", oldProteinCb.isSelected()); // old protein unchecked } } diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSListTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSListTest.java index 8ef5ee315..61c180dcd 100644 --- a/test/src/org/labkey/test/tests/targetedms/TargetedMSListTest.java +++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSListTest.java @@ -53,13 +53,13 @@ public void testSteps() throws IOException, CommandException clickAndWait(Locator.linkContainingText("Panorama Dashboard")); clickAndWait(Locator.linkContainingText(LIST_SKY_FILE_1)); - verifyRunSummaryCountsPep(2,4,0, 5,53, 1, 0, 6); + verifyRunSummaryCountsPep(2, 2, 4, 0, 5, 53, 1, 0, 6); clickAndWait(Locator.linkContainingText("6 lists")); assertTextPresent("DocumentProperties", "Lorem Ipsum", "Protein Descriptions"); clickAndWait(Locator.linkContainingText("Protein Descriptions")); // Check that the document header remains - verifyRunSummaryCountsPep(2,4,0, 5,53, 1, 0, 6); + verifyRunSummaryCountsPep(2, 2, 4, 0, 5, 53, 1, 0, 6); assertTextPresent("ALBU_BOVIN", "main protein of plasma"); List queryNames = validateSampleInfo(1, Set.of("Mickey"), 5, 12); diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSMAMTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSMAMTest.java index 4b9beb6b1..5b8aa5fec 100644 --- a/test/src/org/labkey/test/tests/targetedms/TargetedMSMAMTest.java +++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSMAMTest.java @@ -50,7 +50,7 @@ public void testSteps() clickAndWait(Locator.linkContainingText("Panorama Dashboard")); clickAndWait(Locator.linkContainingText(SKY_FILE)); - verifyRunSummaryCountsPep(125,158,0, 160,628, 1, 0, 0); + verifyRunSummaryCountsPep(125, 124, 158, 0, 160, 628, 1, 0, 0); clickAndWait(Locator.linkContainingText("PTM Report")); @@ -83,7 +83,7 @@ public void testCrossLinkedPeptideMap() clickAndWait(Locator.linkContainingText("Panorama Dashboard")); clickAndWait(Locator.linkContainingText(CROSS_LINKED_SKY_FILE)); - verifyRunSummaryCountsPep(2,2,0, 2,2, 1, 0, 0); + verifyRunSummaryCountsPep(2, 2, 2, 0, 2, 2, 1, 0, 0); clickAndWait(Locator.linkContainingText("Peptide Map")); assertTextPresentInThisOrder("121-124", "342-345", "142-145"); diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSProteinGroupingTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSProteinGroupingTest.java index dd285d028..f61286f6c 100644 --- a/test/src/org/labkey/test/tests/targetedms/TargetedMSProteinGroupingTest.java +++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSProteinGroupingTest.java @@ -46,6 +46,7 @@ public void testProteinGrouping() goToProjectHome(); clickAndWait(Locator.linkWithText(SKY_FILE)); + verifyRunSummaryCountsPep(31, 47, 33, 0, 33, 198, 4, 0, 0); clickAndWait(Locator.linkWithText(group)); log("Verifying protein matches for peptide"); diff --git a/test/src/org/labkey/test/tests/targetedms/TargetedMSTest.java b/test/src/org/labkey/test/tests/targetedms/TargetedMSTest.java index 2b1bf0650..f60b1519f 100644 --- a/test/src/org/labkey/test/tests/targetedms/TargetedMSTest.java +++ b/test/src/org/labkey/test/tests/targetedms/TargetedMSTest.java @@ -19,6 +19,7 @@ import org.junit.BeforeClass; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; +import org.labkey.test.WebDriverWrapper; import org.labkey.test.ModulePropertyValue; import org.labkey.test.TestFileUtils; import org.labkey.test.TestProperties; @@ -37,9 +38,8 @@ import org.labkey.test.util.LoggedParam; import org.labkey.test.util.ReflectionUtils; import org.labkey.test.util.UIContainerHelper; -import org.openqa.selenium.WebElement; +import org.labkey.test.util.targetedms.TargetedMSHelper; -import java.nio.file.Paths; import java.util.Arrays; import java.util.List; @@ -62,6 +62,7 @@ public abstract class TargetedMSTest extends BaseWebDriverTest protected static final String SAMPLE_FILE_CHROM_INFO = "SampleFileChromInfo.sky.zip"; protected static final String USER = "qcuser@targetedms.test"; private static ConfiguresSite siteConfigurer; + protected final TargetedMSHelper _targetedMSHelper = new TargetedMSHelper(this); protected enum SvgShapes { @@ -85,7 +86,7 @@ public enum FolderType { Experiment { @Override - public void chooseFolderType(TargetedMSTest test) + public void chooseFolderType(WebDriverWrapper test) { test.click(Locator.radioButtonById("experimentalData")); // click the first radio button - Experimental Data } @@ -93,21 +94,21 @@ public void chooseFolderType(TargetedMSTest test) ExperimentMAM { @Override - public void chooseFolderType(TargetedMSTest test) + public void chooseFolderType(WebDriverWrapper test) { test.click(Locator.radioButtonById("multiAttributeMethod")); // click the second radio button - Experimental Data } }, Library { @Override - public void chooseFolderType(TargetedMSTest test) + public void chooseFolderType(WebDriverWrapper test) { test.click(Locator.radioButtonById("chromatogramLibrary")); // click the 3rd radio button - Library } }, LibraryProtein { @Override - public void chooseFolderType(TargetedMSTest test) + public void chooseFolderType(WebDriverWrapper test) { test.click(Locator.radioButtonById("chromatogramLibrary")); // click the 3rd radio button - Library test.click(Locator.checkboxByName("precursorNormalized")); // check the normalization checkbox. @@ -115,13 +116,13 @@ public void chooseFolderType(TargetedMSTest test) }, QC { @Override - public void chooseFolderType(TargetedMSTest test) + public void chooseFolderType(WebDriverWrapper test) { test.click(Locator.radioButtonById("QC")); // click the 4th radio button - QC } }; - public abstract void chooseFolderType(TargetedMSTest test); + public abstract void chooseFolderType(WebDriverWrapper test); } public TargetedMSTest() @@ -181,10 +182,7 @@ protected void setupFolder(FolderType folderType) protected void setUpFolder(String folderName, FolderType folderType ) { - _containerHelper.createProject(folderName, "Panorama"); - waitForElement(Locator.linkContainingText("Save")); - clickAndWait(Locator.linkContainingText("Next")); - selectFolderType(folderType); + _targetedMSHelper.setupFolder(folderName, folderType); getSiteConfigurer().configureProject(getProjectName()); } @@ -227,20 +225,7 @@ protected void importData(@LoggedParam String file, int jobCount, boolean expect @LogMethod protected void importData(@LoggedParam String file, int jobCount, boolean expectError, boolean doDbMaintenance) { - Locator.XPathLocator importButtonLoc = Locator.lkButton("Process and Import Data"); - WebElement importButton = importButtonLoc.findElementOrNull(getDriver()); - if (null == importButton) - { - goToModule("Pipeline"); - importButton = importButtonLoc.findElement(getDriver()); - } - clickAndWait(importButton); - String fileName = Paths.get(file).getFileName().toString(); - if (!_fileBrowserHelper.fileIsPresent(fileName)) - _fileBrowserHelper.uploadFile(TestFileUtils.getSampleData("TargetedMS/" + file)); - _fileBrowserHelper.importFile(fileName, "Import Skyline Results"); - waitForText("Skyline document import"); - waitForPipelineJobsToComplete(jobCount, file, expectError); + _targetedMSHelper.importData(file, jobCount, expectError); if (doDbMaintenance) { @@ -273,22 +258,32 @@ private void runDbMaintenance() } } - protected void verifyRunSummaryCountsSmallMol(int proteinCount, int peptideCount, int moleculeCount, int precursorCount, int transitionCount, int replicateCount, int calibrationCount, int listCount) + protected void verifyRunSummaryCountsSmallMol(int moleculeGroupCount, int proteinCount, int peptideCount, int moleculeCount, int precursorCount, int transitionCount, int replicateCount, int calibrationCount, int listCount) + { + verifyRunSummaryCounts(0, moleculeGroupCount, proteinCount, peptideCount, moleculeCount, precursorCount, transitionCount, replicateCount, calibrationCount, listCount); + } + + protected void verifyRunSummaryCountsPep(int peptideGroupCount, int proteinCount, int peptideCount, int moleculeCount, int precursorCount, int transitionCount, int replicateCount, int calibrationCount, int listCount) { - verifyRunSummaryCounts(proteinCount, peptideCount, moleculeCount, precursorCount, transitionCount, replicateCount, calibrationCount, listCount, "molecule lists"); + verifyRunSummaryCounts(peptideGroupCount, 0, proteinCount, peptideCount, moleculeCount, precursorCount, transitionCount, replicateCount, calibrationCount, listCount); } - protected void verifyRunSummaryCountsPep(int proteinCount, int peptideCount, int moleculeCount, int precursorCount, int transitionCount, int replicateCount, int calibrationCount, int listCount) + protected void verifyRunSummaryCountsMixed(int proteinGroupCount, int moleculeGroupCount, int proteinCount, int peptideCount, int moleculeCount, int precursorCount, int transitionCount, int replicateCount, int calibrationCount, int listCount) { - verifyRunSummaryCounts(proteinCount, peptideCount, moleculeCount, precursorCount, transitionCount, replicateCount, calibrationCount, listCount, "proteins"); + verifyRunSummaryCounts(proteinGroupCount, moleculeGroupCount, proteinCount, peptideCount, moleculeCount, precursorCount, transitionCount, replicateCount, calibrationCount, listCount); } @LogMethod - protected void verifyRunSummaryCounts(int proteinCount, int peptideCount, int moleculeCount, int precursorCount, int transitionCount, - int replicateCount, int calibrationCount, int listCount, String peptideGroupLabel) + protected void verifyRunSummaryCounts(int proteinGroupCount, int moleculeGroupCount, int proteinCount, int peptideCount, int moleculeCount, int precursorCount, int transitionCount, + int replicateCount, int calibrationCount, int listCount) { log("Verifying expected summary counts"); - waitForElement(Locator.linkContainingText(proteinCount + " " + peptideGroupLabel)); + if (proteinGroupCount > 0) + waitForElement(Locator.linkContainingText(proteinGroupCount + " protein group" + (proteinGroupCount == 1 ? "" : "s"))); + if (moleculeGroupCount > 0) + assertElementPresent(Locator.linkContainingText(moleculeGroupCount + " molecule list" + (moleculeGroupCount == 1 ? "" : "s"))); + if (proteinCount > 0) + assertElementPresent(Locator.linkContainingText(proteinCount + " protein" + (proteinCount == 1 ? "" : "s"))); if (peptideCount > 0) { assertElementPresent(Locator.linkContainingText(peptideCount + " peptides")); @@ -327,9 +322,7 @@ protected void verifyRunSummaryCounts(int proteinCount, int peptideCount, int mo @LogMethod protected void selectFolderType(FolderType folderType) { - log("Select Folder Type: " + folderType); - folderType.chooseFolderType(this); - clickButton("Finish"); + _targetedMSHelper.selectFolderType(folderType); } /** Verify that the comparison plots have been AJAX'd into place */ diff --git a/test/src/org/labkey/test/tests/targetedms/upgrade/TargetedMSUpgradeTest.java b/test/src/org/labkey/test/tests/targetedms/upgrade/TargetedMSUpgradeTest.java new file mode 100644 index 000000000..8220294af --- /dev/null +++ b/test/src/org/labkey/test/tests/targetedms/upgrade/TargetedMSUpgradeTest.java @@ -0,0 +1,91 @@ +package org.labkey.test.tests.targetedms.upgrade; + +import org.junit.Assume; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.labkey.remoteapi.query.SelectRowsCommand; +import org.labkey.remoteapi.query.SelectRowsResponse; +import org.labkey.test.tests.targetedms.TargetedMSTest.FolderType; +import org.labkey.test.tests.upgrade.BaseUpgradeTest; +import org.labkey.test.util.UIContainerHelper; +import org.labkey.test.util.targetedms.TargetedMSHelper; + +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +/** + * Verifies that the targetedms-26.006-26.007 upgrade script correctly populates PeptideGroupCount, + * MoleculeGroupCount, and ProteinCount on existing runs after the schema migration. + */ +@Category({}) +public class TargetedMSUpgradeTest extends BaseUpgradeTest +{ + private static final String SKY_FILE = "smallmol_plus_peptides.sky.zip"; + + public TargetedMSUpgradeTest() + { + setContainerHelper(new UIContainerHelper(this)); + } + + @Override + protected String getProjectName() + { + return "TargetedMS Upgrade Test"; + } + + @Override + protected void doSetup() throws Exception + { + TargetedMSHelper helper = new TargetedMSHelper(this); + helper.setupFolder(getProjectName(), FolderType.Experiment); + helper.importData(SKY_FILE); + } + + @Test + @EarliestVersion("25.11") + public void testPreUpgradeCounts() throws Exception + { + SelectRowsCommand cmd = new SelectRowsCommand("targetedms", "Runs"); + cmd.setColumns(List.of("PeptideCount", "SmallMoleculeCount", "ReplicateCount")); + SelectRowsResponse response = cmd.execute(createDefaultConnection(), getProjectName()); + + List> rows = response.getRows(); + assertEquals("Expected exactly one run", 1, rows.size()); + Map run = rows.getFirst(); + assertEquals("PeptideCount", 44, ((Number) run.get("PeptideCount")).intValue()); + assertEquals("SmallMoleculeCount", 98, ((Number) run.get("SmallMoleculeCount")).intValue()); + assertEquals("ReplicateCount", 5, ((Number) run.get("ReplicateCount")).intValue()); + } + + @Test + @EarliestVersion("26.3") + public void testPostUpgradeCounts() throws Exception + { + Assume.assumeFalse("Skipping post-upgrade count checks during setup phase", isUpgradeSetupPhase); + + SelectRowsCommand cmd = new SelectRowsCommand("targetedms", "Runs"); + cmd.setColumns(List.of("PeptideGroupCount", "MoleculeGroupCount", "ProteinCount")); + SelectRowsResponse response = cmd.execute(createDefaultConnection(), getProjectName()); + + List> rows = response.getRows(); + assertEquals("Expected exactly one run", 1, rows.size()); + Map run = rows.getFirst(); + assertEquals("PeptideGroupCount", 24, ((Number) run.get("PeptideGroupCount")).intValue()); + assertEquals("MoleculeGroupCount", 3, ((Number) run.get("MoleculeGroupCount")).intValue()); + assertEquals("ProteinCount", 24, ((Number) run.get("ProteinCount")).intValue()); + } + + @Override + protected void doCleanup(boolean afterTest) + { + _containerHelper.deleteProject(getProjectName(), afterTest); + } + + @Override + public List getAssociatedModules() + { + return List.of("targetedms"); + } +} diff --git a/test/src/org/labkey/test/util/targetedms/TargetedMSHelper.java b/test/src/org/labkey/test/util/targetedms/TargetedMSHelper.java new file mode 100644 index 000000000..36797c54e --- /dev/null +++ b/test/src/org/labkey/test/util/targetedms/TargetedMSHelper.java @@ -0,0 +1,68 @@ +package org.labkey.test.util.targetedms; + +import org.labkey.test.BaseWebDriverTest; +import org.labkey.test.Locator; +import org.labkey.test.TestFileUtils; +import org.labkey.test.tests.targetedms.TargetedMSTest.FolderType; +import org.labkey.test.util.LogMethod; +import org.labkey.test.util.LoggedParam; +import org.openqa.selenium.WebElement; + +import java.nio.file.Paths; + +/** Setup and import utilities to share between standard and upgrade tests for TargetedMS */ +public class TargetedMSHelper +{ + private final BaseWebDriverTest _test; + + public TargetedMSHelper(BaseWebDriverTest test) + { + _test = test; + } + + public void setupFolder(String projectName, FolderType folderType) + { + _test._containerHelper.createProject(projectName, "Panorama"); + _test.waitForElement(Locator.linkContainingText("Save")); + _test.clickAndWait(Locator.linkContainingText("Next")); + selectFolderType(folderType); + } + + @LogMethod + public void selectFolderType(@LoggedParam FolderType folderType) + { + _test.log("Select Folder Type: " + folderType); + folderType.chooseFolderType(_test); + _test.clickButton("Finish"); + } + + public void importData(String file) + { + importData(file, 1); + } + + @LogMethod + public void importData(@LoggedParam String file, int jobCount) + { + importData(file, jobCount, false); + } + + @LogMethod + public void importData(@LoggedParam String file, int jobCount, boolean expectError) + { + Locator.XPathLocator importButtonLoc = Locator.lkButton("Process and Import Data"); + WebElement importButton = importButtonLoc.findElementOrNull(_test.getDriver()); + if (null == importButton) + { + _test.goToModule("Pipeline"); + importButton = importButtonLoc.findElement(_test.getDriver()); + } + _test.clickAndWait(importButton); + String fileName = Paths.get(file).getFileName().toString(); + if (!_test._fileBrowserHelper.fileIsPresent(fileName)) + _test._fileBrowserHelper.uploadFile(TestFileUtils.getSampleData("TargetedMS/" + file)); + _test._fileBrowserHelper.importFile(fileName, "Import Skyline Results"); + _test.waitForText("Skyline document import"); + _test.waitForPipelineJobsToComplete(jobCount, file, expectError); + } +}