From a955bb79000e29dce566ed3f32fa8c481cd4753a Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Wed, 7 May 2025 17:13:43 -0700 Subject: [PATCH 01/17] Let users set and see instrument nicknames instead of serial numbers --- .../targetedms/InstrumentSummaryByFolder.sql | 8 +- .../targetedms/QCInstrumentSummary.sql | 2 + .../queries/targetedms/samplefile/.qview.xml | 3 +- .../postgresql/targetedms-25.003-25.004.sql | 18 ++ .../sqlserver/targetedms-25.003-25.004.sql | 18 ++ resources/schemas/targetedms.xml | 13 ++ .../targetedms/TargetedMSController.java | 220 +++++++++++++----- .../labkey/targetedms/TargetedMSListener.java | 1 + .../labkey/targetedms/TargetedMSManager.java | 102 ++++++++ .../labkey/targetedms/TargetedMSModule.java | 2 +- .../labkey/targetedms/TargetedMSSchema.java | 14 +- .../targetedms/model/InstrumentNickname.java | 62 +++++ .../targetedms/query/SampleFileTable.java | 22 +- src/org/labkey/targetedms/view/nickname.jsp | 79 +++++++ src/org/labkey/targetedms/view/renameRun.jsp | 3 +- webapp/TargetedMS/js/QCSummaryPanel.js | 42 ++-- 16 files changed, 512 insertions(+), 97 deletions(-) create mode 100644 resources/schemas/dbscripts/postgresql/targetedms-25.003-25.004.sql create mode 100644 resources/schemas/dbscripts/sqlserver/targetedms-25.003-25.004.sql create mode 100644 src/org/labkey/targetedms/model/InstrumentNickname.java create mode 100644 src/org/labkey/targetedms/view/nickname.jsp diff --git a/resources/queries/targetedms/InstrumentSummaryByFolder.sql b/resources/queries/targetedms/InstrumentSummaryByFolder.sql index c1517515f..ba0ade63b 100644 --- a/resources/queries/targetedms/InstrumentSummaryByFolder.sql +++ b/resources/queries/targetedms/InstrumentSummaryByFolder.sql @@ -1,11 +1,13 @@ SELECT - COUNT(ReplicateId.RunId) AS SkylineDocumentCount, + COUNT(DISTINCT ReplicateId.RunId) AS SkylineDocumentCount, COUNT(DISTINCT ReplicateId) AS ReplicateCount, MIN(AcquiredTime) AS FirstAcquisition, MAX(AcquiredTime) AS LastAcquisition, ReplicateId.RunId.Container, - InstrumentSerialNumber + InstrumentSerialNumber, + InstrumentNickname FROM targetedms.SampleFile GROUP BY ReplicateId.RunId.Container, - InstrumentSerialNumber \ No newline at end of file + InstrumentSerialNumber, + InstrumentNickname \ No newline at end of file diff --git a/resources/queries/targetedms/QCInstrumentSummary.sql b/resources/queries/targetedms/QCInstrumentSummary.sql index 17a14bd05..6490bc7a6 100644 --- a/resources/queries/targetedms/QCInstrumentSummary.sql +++ b/resources/queries/targetedms/QCInstrumentSummary.sql @@ -1,4 +1,5 @@ SELECT + sf.InstrumentNickname AS Nickname, sf.InstrumentId.model AS InstrumentName, sf.InstrumentSerialNumber AS SerialNumber, MIN(sf.AcquiredTime) AS StartDate, @@ -9,6 +10,7 @@ SELECT FROM targetedms.SampleFile sf INNER JOIN replicate rep ON sf.replicateId = rep.Id GROUP BY + sf.InstrumentNickname, sf.InstrumentSerialNumber, sf.InstrumentId.model, rep.runId \ No newline at end of file diff --git a/resources/queries/targetedms/samplefile/.qview.xml b/resources/queries/targetedms/samplefile/.qview.xml index b59e095bc..95364d908 100644 --- a/resources/queries/targetedms/samplefile/.qview.xml +++ b/resources/queries/targetedms/samplefile/.qview.xml @@ -9,8 +9,7 @@ - - + diff --git a/resources/schemas/dbscripts/postgresql/targetedms-25.003-25.004.sql b/resources/schemas/dbscripts/postgresql/targetedms-25.003-25.004.sql new file mode 100644 index 000000000..7fdd571dd --- /dev/null +++ b/resources/schemas/dbscripts/postgresql/targetedms-25.003-25.004.sql @@ -0,0 +1,18 @@ +CREATE TABLE targetedms.InstrumentNickname +( + Id BIGSERIAL NOT NULL, + + Container entityid NOT NULL, + Created TIMESTAMP, + CreatedBy USERID, + Modified TIMESTAMP, + ModifiedBy USERID, + + SerialNumber VARCHAR(200), + Model VARCHAR(300), + Nickname VARCHAR(200), + + CONSTRAINT PK_InstrumentNickname PRIMARY KEY (Id) +); +CREATE INDEX IDX_InstrumentNickname_Container ON targetedms.InstrumentNickname(Container); + diff --git a/resources/schemas/dbscripts/sqlserver/targetedms-25.003-25.004.sql b/resources/schemas/dbscripts/sqlserver/targetedms-25.003-25.004.sql new file mode 100644 index 000000000..7ca43e850 --- /dev/null +++ b/resources/schemas/dbscripts/sqlserver/targetedms-25.003-25.004.sql @@ -0,0 +1,18 @@ +CREATE TABLE targetedms.InstrumentNickname +( + Id BIGINT IDENTITY(1, 1) NOT NULL, + + Container entityid NOT NULL, + Created TIMESTAMP, + CreatedBy USERID, + Modified TIMESTAMP, + ModifiedBy USERID, + + SerialNumber VARCHAR(200), + Model VARCHAR(300), + Nickname VARCHAR(200), + + CONSTRAINT PK_InstrumentNickname PRIMARY KEY (Id) +); +CREATE INDEX IDX_InstrumentNickname_Container ON targetedms.InstrumentNickname(Container); + diff --git a/resources/schemas/targetedms.xml b/resources/schemas/targetedms.xml index 06e1181be..91f86ce52 100644 --- a/resources/schemas/targetedms.xml +++ b/resources/schemas/targetedms.xml @@ -1920,4 +1920,17 @@ + + + + + + + + + + + + +
diff --git a/src/org/labkey/targetedms/TargetedMSController.java b/src/org/labkey/targetedms/TargetedMSController.java index 8cda66b2d..d4b336a67 100644 --- a/src/org/labkey/targetedms/TargetedMSController.java +++ b/src/org/labkey/targetedms/TargetedMSController.java @@ -20,6 +20,8 @@ import com.keypoint.PngEncoder; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import lombok.Getter; +import lombok.Setter; import org.apache.batik.dom.GenericDOMImplementation; import org.apache.batik.svggen.SVGGeneratorContext; import org.apache.batik.svggen.SVGGraphics2D; @@ -166,6 +168,7 @@ import org.labkey.api.view.PopupMenu; import org.labkey.api.view.Portal; import org.labkey.api.view.RedirectException; +import org.labkey.api.view.UnauthorizedException; import org.labkey.api.view.VBox; import org.labkey.api.view.ViewBackgroundInfo; import org.labkey.api.view.ViewContext; @@ -186,6 +189,7 @@ import org.labkey.targetedms.model.GuideSet; import org.labkey.targetedms.model.GuideSetKey; import org.labkey.targetedms.model.GuideSetStats; +import org.labkey.targetedms.model.InstrumentNickname; import org.labkey.targetedms.model.PeptideOutliers; import org.labkey.targetedms.model.PrecursorChromInfoLitePlus; import org.labkey.targetedms.model.QCPlotFragment; @@ -1020,7 +1024,7 @@ public Object execute(QCSummaryForm form, BindException errors) List> containers = new ArrayList<>(); // include the QC Summary properties for the current container - containers.add(getContainerQCSummaryProperties(getContainer(), getContainer(), false)); + containers.add(getContainerQCSummaryProperties(getContainer(), getContainer(), false, getUser())); // include the QC Summary properties for the direct subfolders, of type QC, that the user has read permission if (form.isIncludeSubfolders()) @@ -1040,7 +1044,7 @@ public Object execute(QCSummaryForm form, BindException errors) folderType = TargetedMSManager.getFolderType(bestContainer); if (bestContainer.hasPermission(getUser(), ReadPermission.class) && folderType == TargetedMSService.FolderType.QC) { - containers.add(getContainerQCSummaryProperties(bestContainer, container, true)); + containers.add(getContainerQCSummaryProperties(bestContainer, container, true, getUser())); } } } @@ -1050,7 +1054,7 @@ public Object execute(QCSummaryForm form, BindException errors) } } - private Map getContainerQCSummaryProperties(Container container, Container instrumentContainer, boolean isSubfolder) + private Map getContainerQCSummaryProperties(Container container, Container instrumentContainer, boolean isSubfolder, User user) { Map properties = new HashMap<>(); SQLFragment sql; @@ -1070,11 +1074,12 @@ private Map getContainerQCSummaryProperties(Container container, properties.put("lastImportDate", valueMap.get("lastImportDate")); // # sample files, count of rows in targetedms.SampleFile - sql = new SQLFragment("SELECT COUNT(s.Id) FROM ").append(TargetedMSManager.getTableInfoSampleFile(), "s"); - sql.append(" JOIN ").append(TargetedMSManager.getTableInfoReplicate(), "re").append(" ON s.ReplicateId = re.Id"); - sql.append(" JOIN ").append(TargetedMSManager.getTableInfoRuns(), "r").append(" ON re.RunId = r.Id"); - sql.append(" WHERE r.Container = ?").add(container.getId()); - properties.put("fileCount", new SqlSelector(TargetedMSSchema.getSchema(), sql).getObject(Integer.class)); + TargetedMSSchema schema = new TargetedMSSchema(user, container); + TableInfo sampleFileTable = schema.getTableOrThrow(TargetedMSSchema.TABLE_SAMPLE_FILE); + List instruments = new TableSelector(sampleFileTable, Collections.singleton("InstrumentNickname")).getArrayList(String.class); + properties.put("fileCount", instruments.size()); + ; + properties.put("distinctInstruments", instruments.stream().filter(Objects::nonNull).distinct().sorted().toList()); // # precursors tracked, count of distinct precursors. Include peptides and small molecules sql = new SQLFragment("SELECT DISTINCT COALESCE(p.ModifiedSequence, "); @@ -1098,7 +1103,6 @@ private Map getContainerQCSummaryProperties(Container container, autoQCPingMap.put("isRecent", lastModified.getTime() >= timeoutMinutesAgo); } properties.put("autoQCPing", autoQCPingMap); - TargetedMSSchema schema = new TargetedMSSchema(getUser(), container); properties.put("metricCount", TargetedMSManager.getEnabledQCMetricConfigurations(schema).size()); return properties; @@ -3225,7 +3229,7 @@ private void addSpectrumViews(TargetedMSRun run, VBox vbox, List 0) + if(!unsupportedLibraries.isEmpty()) { HtmlView view = new HtmlView(DIV("Annotated spectra cannot be displayed from the following unsupported " + (unsupportedLibraries.size() == 1 ? "library" : "libraries") + ": ", @@ -3237,7 +3241,7 @@ private void addSpectrumViews(TargetedMSRun run, VBox vbox, List 0) + if(!specLibErrors.isEmpty()) { HtmlView view = new HtmlView(DOM.LK.ERRORS(specLibErrors.stream().map(e -> new LabKeyError(e.getMessage())).collect(Collectors.toList()))); view.setTitle("Spectrum Library Errors"); @@ -4510,7 +4514,21 @@ protected CalibrationCurvesView createQueryView( public static class InstrumentForm extends QueryViewAction.QueryExportForm { + private long _id; + private String _name; + private String _model; private String _serialNumber; + private String _targetContainerId; + + public String getModel() + { + return _model; + } + + public void setModel(String model) + { + _model = model; + } public String getSerialNumber() { @@ -4521,6 +4539,93 @@ public void setSerialNumber(String serialNumber) { _serialNumber = serialNumber; } + + public String getTargetContainerId() + { + return _targetContainerId; + } + + public void setTargetContainerId(String targetContainerId) + { + _targetContainerId = targetContainerId; + } + + public String getName() + { + return _name; + } + + public void setName(String name) + { + _name = name; + } + + public long getId() + { + return _id; + } + + public void setId(long id) + { + _id = id; + } + } + + @RequiresPermission(UpdatePermission.class) + public static class SaveInstrumentNameAction extends FormHandlerAction + { + @Override + public void validateCommand(InstrumentForm target, Errors errors) + { + } + + @Override + public boolean handlePost(InstrumentForm form, BindException errors) + { + Container targetContainer = ContainerManager.getForId(form.getTargetContainerId()); + if (targetContainer == null || !targetContainer.hasPermission(getUser(), UpdatePermission.class)) + { + throw new UnauthorizedException(); + } + + InstrumentNickname name; + if (form.getId() > 0) + { + name = TargetedMSManager.get().getNickname(form.getId(), getUser()); + if (name == null) + { + throw new NotFoundException(); + } + if (!name.getContainer().hasPermission(getUser(), UpdatePermission.class)) + { + throw new UnauthorizedException(); + } + } + else + { + name = new InstrumentNickname(); + } + if (StringUtils.isEmpty(form.getName()) && name.getId() > 0) + { + TargetedMSManager.get().deleteNickname(name); + } + else + { + name.setNickname(form.getName()); + name.setModel(form.getModel()); + name.setSerialNumber(form.getSerialNumber()); + name.setContainer(targetContainer); + TargetedMSManager.get().saveNickname(name, getUser()); + } + + return true; + } + + @Override + public URLHelper getSuccessURL(InstrumentForm form) + { + return StringUtils.isBlank(form.getName()) ? getContainer().getStartURL(getUser()) : new ActionURL(ShowInstrumentAction.class, getContainer()).addParameter("name", form.getName()); + } } @RequiresPermission(ReadPermission.class) @@ -4538,7 +4643,7 @@ public ShowInstrumentAction() @Override public void addNavTrail(NavTree root) { - root.addChild("Instrument " + (_form == null ? "" : _form.getSerialNumber())); + root.addChild("Instrument " + (_form == null ? "" : _form.getName())); } @Override @@ -4550,7 +4655,7 @@ protected QueryView createQueryView(InstrumentForm form, BindException errors, b Sort sort = new Sort(); sort.appendSortColumn(FieldKey.fromParts("AcquiredTime"), Sort.SortDirection.DESC, false); settings.setBaseSort(sort); - settings.setBaseFilter(new SimpleFilter(FieldKey.fromParts("InstrumentSerialNumber"), form.getSerialNumber())); + settings.setBaseFilter(new SimpleFilter(FieldKey.fromParts("InstrumentNickname"), form.getName())); settings.setContainerFilterName(ContainerFilter.Type.AllFolders.name()); TargetedMSSchema schema = new TargetedMSSchema(getUser(), getContainer()); return schema.createView(getViewContext(), settings, errors); @@ -4558,7 +4663,7 @@ protected QueryView createQueryView(InstrumentForm form, BindException errors, b if (FOLDER_SUMMARY.equalsIgnoreCase(dataRegion)) { QuerySettings settings = new QuerySettings(getViewContext(), FOLDER_SUMMARY, "InstrumentSummaryByFolder"); - settings.setBaseFilter(new SimpleFilter(FieldKey.fromParts("InstrumentSerialNumber"), form.getSerialNumber())); + settings.setBaseFilter(new SimpleFilter(FieldKey.fromParts("InstrumentNickname"), form.getName())); settings.setContainerFilterName(ContainerFilter.Type.AllFolders.name()); TargetedMSSchema schema = new TargetedMSSchema(getUser(), getContainer()); return schema.createView(getViewContext(), settings, errors); @@ -4567,23 +4672,44 @@ protected QueryView createQueryView(InstrumentForm form, BindException errors, b } @Override - public ModelAndView getView(InstrumentForm form, BindException errors) throws Exception + public ModelAndView getView(InstrumentForm form, BindException errors) { - if (form.getSerialNumber() == null) + if (form.getName() == null) { - throw new NotFoundException("No instrument serial number specified"); + throw new NotFoundException("No instrument specified"); } _form = form; + TargetedMSSchema schema = new TargetedMSSchema(getUser(), getContainer()); + List names = TargetedMSManager.get().getNickname(form.getName(), schema); + + if (names.isEmpty()) + { + throw new NotFoundException("No matching instruments found"); + } + + VBox result = new VBox(); + + for (InstrumentNickname name : names) + { + var nameView = new JspView<>("/org/labkey/targetedms/view/nickname.jsp", name); + nameView.setTitle("Instrument Info"); + nameView.setFrame(WebPartView.FrameType.PORTAL); + result.addView(nameView); + } + QueryView folderSummaryView = createQueryView(form, errors, false, FOLDER_SUMMARY); - folderSummaryView.setTitle("Data in this Server"); + folderSummaryView.setTitle("Summary by Folder"); folderSummaryView.setFrame(WebPartView.FrameType.PORTAL); QueryView sampleFileView = createQueryView(form, errors, false, TargetedMSSchema.TABLE_SAMPLE_FILE); - sampleFileView.setTitle("Data from this Instrument"); + sampleFileView.setTitle("Samples from " + form.getName()); sampleFileView.setFrame(WebPartView.FrameType.PORTAL); - return new VBox(folderSummaryView, sampleFileView); + result.addView(folderSummaryView); + result.addView(sampleFileView); + + return result; } } @@ -5322,7 +5448,7 @@ public static Integer addProteinSummaryViews(VBox box, PeptideGroup group, Targe { int seqId = selectedProtein.getSequenceId(); List combinedPeptideCharacteristics = new ArrayList<>(PeptideManager.getCombinedPeptideCharacteristics(group.getId(), replicateId)); - List modifiedPeptideCharacteristics = new ArrayList<>(PeptideManager.getModifiedPeptideCharacteristics(group.getId(), replicateId));; + List modifiedPeptideCharacteristics = new ArrayList<>(PeptideManager.getModifiedPeptideCharacteristics(group.getId(), replicateId)); List replicates = ReplicateManager.getReplicatesForRun(run.getRunId()); List msReplicates = new ArrayList<>(); @@ -5606,7 +5732,7 @@ public class ShowProteinConflictUiAction extends SimpleViewAction conflictProteinList = ConflictResultsManager.getConflictedProteins(getContainer()); - if(conflictProteinList.size() == 0) + if(conflictProteinList.isEmpty()) { errors.reject(ERROR_MSG, "Library folder "+getContainer().getPath()+" does not contain any conflicting proteins."); return new SimpleErrorView(errors, true); @@ -5805,7 +5931,7 @@ public class ShowPrecursorConflictUiAction extends SimpleViewAction conflictPrecursorList = ConflictResultsManager.getConflictedPrecursors(getContainer()); - if(conflictPrecursorList.size() == 0) + if(conflictPrecursorList.isEmpty()) { errors.reject(ERROR_MSG, "Library folder "+getContainer().getPath()+" does not contain any conflicting data."); return new SimpleErrorView(errors, true); @@ -6340,19 +6466,11 @@ public void addNavTrail(NavTree root) // ------------------------------------------------------------------------ // Actions to export chromatogram libraries // ------------------------------------------------------------------------ + @Setter + @Getter public static class DownloadForm { int revision; - - public int getRevision() - { - return revision; - } - - public void setRevision(int revision) - { - this.revision = revision; - } } @RequiresPermission(ReadPermission.class) public static class DownloadChromLibraryAction extends SimpleViewAction @@ -6362,7 +6480,7 @@ public ModelAndView getView(DownloadForm form, BindException errors) throws Exce { // Check if the folder has any representative data List representativeRunIds = TargetedMSManager.getCurrentRepresentativeRunIds(getContainer()); - if(representativeRunIds.size() == 0) + if(representativeRunIds.isEmpty()) { //errors.reject(ERROR_MSG, "Folder "+getContainer().getPath()+" does not contain any representative data."); //return new SimpleErrorView(errors, true); @@ -7288,6 +7406,8 @@ public static long getNumRankedTransitions(Container container) { /* * BEGIN RENAME CODE BLOCK */ + @Setter + @Getter public static class RunForm extends ReturnUrlForm { public enum PARAMS @@ -7298,26 +7418,6 @@ public enum PARAMS int run = 0; String columns; - public void setRun(int run) - { - this.run = run; - } - - public int getRun() - { - return run; - } - - public String getColumns() - { - return columns; - } - - public void setColumns(String columns) - { - this.columns = columns; - } - @Override public ActionURL getReturnActionURL() { @@ -7353,19 +7453,11 @@ public static ActionURL getRenameRunURL(Container c, TargetedMSRun run, ActionUR return url; } + @Setter + @Getter public static class RenameForm extends RunForm { private String description; - - public String getDescription() - { - return description; - } - - public void setDescription(String description) - { - this.description = description; - } } @RequiresPermission(UpdatePermission.class) diff --git a/src/org/labkey/targetedms/TargetedMSListener.java b/src/org/labkey/targetedms/TargetedMSListener.java index a192a0654..1bca6867d 100644 --- a/src/org/labkey/targetedms/TargetedMSListener.java +++ b/src/org/labkey/targetedms/TargetedMSListener.java @@ -70,6 +70,7 @@ public void containerDeleted(Container c, User user) LibSpectrumReader.clearLibCache(c); new SqlExecutor(TargetedMSManager.getSchema()).execute("DELETE FROM " + TargetedMSManager.getTableInfoQCEmailNotifications() + " WHERE Container = ?", c); + new SqlExecutor(TargetedMSManager.getSchema()).execute("DELETE FROM " + TargetedMSManager.getTableInfoInstrumentNickname() + " WHERE Container = ?", c); } @Override diff --git a/src/org/labkey/targetedms/TargetedMSManager.java b/src/org/labkey/targetedms/TargetedMSManager.java index 7706d0fdb..9b2b0c4ca 100644 --- a/src/org/labkey/targetedms/TargetedMSManager.java +++ b/src/org/labkey/targetedms/TargetedMSManager.java @@ -43,6 +43,7 @@ import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; import org.labkey.api.data.dialect.SqlDialect; +import org.labkey.api.data.dialect.StandardDialectStringHandler; import org.labkey.api.data.statistics.MathStat; import org.labkey.api.data.statistics.StatsService; import org.labkey.api.exp.AbstractFileXarSource; @@ -73,6 +74,8 @@ import org.labkey.api.query.UserSchema; import org.labkey.api.security.User; import org.labkey.api.security.permissions.DeletePermission; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.security.permissions.UpdatePermission; import org.labkey.api.targetedms.ITargetedMSRun; import org.labkey.api.targetedms.RepresentativeDataState; import org.labkey.api.targetedms.RunRepresentativeDataState; @@ -91,6 +94,7 @@ import org.labkey.targetedms.model.GuideSetStats; import org.labkey.api.targetedms.model.QCMetricConfiguration; import org.labkey.api.targetedms.model.QCMetricStatus; +import org.labkey.targetedms.model.InstrumentNickname; import org.labkey.targetedms.model.QCTraceMetricValues; import org.labkey.targetedms.model.RawMetricDataSet; import org.labkey.targetedms.model.passport.IKeyword; @@ -136,6 +140,7 @@ import static org.labkey.api.targetedms.TargetedMSService.FolderType.Library; import static org.labkey.api.targetedms.TargetedMSService.FolderType.LibraryProtein; import static org.labkey.api.targetedms.TargetedMSService.MODULE_NAME; +import static org.labkey.targetedms.TargetedMSSchema.TABLE_INSTRUMENT_NICKNAME; public class TargetedMSManager { @@ -458,6 +463,11 @@ public static TableInfo getTableInfoFoldChange() return getSchema().getTable(TargetedMSSchema.TABLE_FOLD_CHANGE); } + public static TableInfo getTableInfoInstrumentNickname() + { + return getSchema().getTable(TABLE_INSTRUMENT_NICKNAME); + } + public static TableInfo getTableInfoiRTPeptide() { return getSchema().getTable(TargetedMSSchema.TABLE_IRT_PEPTIDE); @@ -1131,6 +1141,81 @@ public static void deleteIncludingExperimentWrapper(Container c, User user) } } + public InstrumentNickname getNickname(long id, User user) + { + InstrumentNickname name = new TableSelector(getTableInfoInstrumentNickname()).getObject(id, InstrumentNickname.class); + if (name == null || !name.getContainer().hasPermission(user, ReadPermission.class)) + { + throw new NotFoundException(); + } + return name; + } + + /** @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())); + List matches = new TableSelector(info, new SimpleFilter(FieldKey.fromParts("Nickname"), name), null).getArrayList(InstrumentNickname.class); + + List result = new ArrayList<>(); + // Closest is from the current container + addNameMatch(result, matches, schema.getContainer()); + // Next closest is from the project + @Nullable Container project = schema.getContainer().getProject(); + if (project != null && !project.equals(schema.getContainer())) + { + addNameMatch(result, matches, schema.getContainer().getProject()); + } + Container shared = ContainerManager.getSharedContainer(); + // Furthest is from /Shared + if (!schema.getContainer().equals(shared)) + { + addNameMatch(result, matches, shared); + } + + if (matches.isEmpty()) + { + String sql = "SELECT DISTINCT InstrumentNickname, " + + "InstrumentId.Model AS Model, " + + "InstrumentSerialNumber AS SerialNumber " + + "FROM targetedms.SampleFile WHERE InstrumentNickname = " + + new StandardDialectStringHandler().quoteStringLiteral(name) + + " ORDER By InstrumentNickname, InstrumentId.Model, InstrumentSerialNumber"; + TableSelector selector = QueryService.get().selector(schema, sql); + result = selector.getArrayList(InstrumentNickname.class); + Container targetContainer; + if (shared.hasPermission(schema.getUser(), UpdatePermission.class)) + { + targetContainer = shared; + } + else if (project != null && project.hasPermission(schema.getUser(), UpdatePermission.class)) + { + targetContainer = project; + } + else + { + targetContainer = schema.getContainer(); + } + for (InstrumentNickname instrumentNickname : result) + { + instrumentNickname.setContainer(targetContainer); + } + } + + return result; + } + + private void addNameMatch(List result, List matches, Container container) + { + for (InstrumentNickname match : matches) + { + if (match.getContainer().equals(container)) + { + result.add(match); + } + } + } + /** * Delete just the targetedms run and its child tables * Pulled out into separate method so could be called by itself from data handlers @@ -2851,4 +2936,21 @@ public void clearCachedEnabledQCMetrics(Container container) { getSchema().getScope().addCommitTask(() -> _metricCache.remove(container), DbScope.CommitTaskOption.IMMEDIATE, DbScope.CommitTaskOption.POSTCOMMIT, DbScope.CommitTaskOption.POSTROLLBACK); } + + public void deleteNickname(InstrumentNickname name) + { + Table.delete(getTableInfoInstrumentNickname(), name.getId()); + } + + public void saveNickname(InstrumentNickname name, User user) + { + if (name.getId() > 0) + { + Table.update(user, getTableInfoInstrumentNickname(), name, name.getId()); + } + else + { + Table.insert(user, getTableInfoInstrumentNickname(), name); + } + } } diff --git a/src/org/labkey/targetedms/TargetedMSModule.java b/src/org/labkey/targetedms/TargetedMSModule.java index 88f3cc517..9d6ce0da6 100644 --- a/src/org/labkey/targetedms/TargetedMSModule.java +++ b/src/org/labkey/targetedms/TargetedMSModule.java @@ -233,7 +233,7 @@ public String getName() @Override public Double getSchemaVersion() { - return 25.003; + return 25.004; } @Override diff --git a/src/org/labkey/targetedms/TargetedMSSchema.java b/src/org/labkey/targetedms/TargetedMSSchema.java index 8865bee43..0e204d9b4 100644 --- a/src/org/labkey/targetedms/TargetedMSSchema.java +++ b/src/org/labkey/targetedms/TargetedMSSchema.java @@ -158,6 +158,7 @@ public class TargetedMSSchema extends UserSchema public static final String TABLE_MOLECULE_GROUP = "MoleculeGroup"; public static final String TABLE_PEPTIDE_GROUP_ANNOTATION = "PeptideGroupAnnotation"; public static final String TABLE_INSTRUMENT = "Instrument"; + public static final String TABLE_INSTRUMENT_NICKNAME = "InstrumentNickname"; public static final String TABLE_ISOTOPE_ENRICHMENT = "IsotopeEnrichment"; public static final String TABLE_ISOLATION_SCHEME = "IsolationScheme"; public static final String TABLE_ISOLATION_WINDOW = "IsolationWindow"; @@ -1607,18 +1608,20 @@ public DisplayColumn createRenderer(ColumnInfo colInfo) TABLE_INSTRUMENT_SCHEDULE.equalsIgnoreCase(name) || TABLE_RATE_TYPE.equalsIgnoreCase(name) || TABLE_INSTRUMENT_RATE.equalsIgnoreCase(name) || - TABLE_INSTRUMENT_USAGE_PAYMENT.equalsIgnoreCase(name)) + TABLE_INSTRUMENT_USAGE_PAYMENT.equalsIgnoreCase(name) || + + TABLE_INSTRUMENT_NICKNAME.equalsIgnoreCase(name)) { - var result = new FilteredTable(getSchema().getTable(name), this, cf) + var result = new FilteredTable<>(getSchema().getTable(name), this, cf) { @Override public boolean hasPermission(@NotNull UserPrincipal user, @NotNull Class perm) { - return getContainer().hasPermission(user,perm); + return getContainer().hasPermission(user, perm); } - @Override - public @Nullable QueryUpdateService getUpdateService() + @Override @NotNull + public QueryUpdateService getUpdateService() { return new DefaultQueryUpdateService(this, getRealTable()); } @@ -1749,6 +1752,7 @@ private Set getAllTableNames(boolean caseInsensitive) hs.add(TABLE_REPLICATE); hs.add(TABLE_REPLICATE_ANNOTATION); hs.add(TABLE_INSTRUMENT); + hs.add(TABLE_INSTRUMENT_NICKNAME); hs.add(TABLE_ISOTOPE_ENRICHMENT); hs.add(TABLE_GENERAL_MOLECULE_CHROM_INFO); hs.add(TABLE_GENERAL_MOLECULE_ANNOTATION); diff --git a/src/org/labkey/targetedms/model/InstrumentNickname.java b/src/org/labkey/targetedms/model/InstrumentNickname.java new file mode 100644 index 000000000..0a11602db --- /dev/null +++ b/src/org/labkey/targetedms/model/InstrumentNickname.java @@ -0,0 +1,62 @@ +package org.labkey.targetedms.model; + +import org.labkey.api.data.Container; + +public class InstrumentNickname +{ + private long _id; + private String _nickname; + private String _model; + private String _serialNumber; + private Container _container; + + public long getId() + { + return _id; + } + + public void setId(long id) + { + _id = id; + } + + public String getNickname() + { + return _nickname; + } + + public void setNickname(String nickname) + { + _nickname = nickname; + } + + public String getModel() + { + return _model; + } + + public void setModel(String model) + { + _model = model; + } + + public String getSerialNumber() + { + return _serialNumber; + } + + public void setSerialNumber(String serialNumber) + { + _serialNumber = serialNumber; + } + + public Container getContainer() + { + return _container; + } + + public void setContainer(Container container) + { + _container = container; + } +} diff --git a/src/org/labkey/targetedms/query/SampleFileTable.java b/src/org/labkey/targetedms/query/SampleFileTable.java index 04977f3bd..cdbdafbc1 100644 --- a/src/org/labkey/targetedms/query/SampleFileTable.java +++ b/src/org/labkey/targetedms/query/SampleFileTable.java @@ -117,6 +117,21 @@ public SampleFileTable(TargetedMSSchema schema, ContainerFilter cf, @Nullable Ta ExprColumn excludedColumn = new ExprColumn(this, "Excluded", excludedSQL, JdbcType.BOOLEAN); addColumn(excludedColumn); + SQLFragment nicknameSql = new SQLFragment("(SELECT MIN(COALESCE(Nickname, "); + nicknameSql.append(getSqlDialect().concatenate("i.Model", "' - '", ExprColumn.STR_TABLE_ALIAS + ".InstrumentSerialNumber")); + nicknameSql.append(", ").append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentSerialNumber, i.Model)) FROM (SELECT * FROM "); + nicknameSql.append(TargetedMSManager.getTableInfoInstrument(), "i"); + nicknameSql.append(" WHERE i.Id = "); + nicknameSql.append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentId) i LEFT OUTER JOIN "); + nicknameSql.append(schema.getTableOrThrow(TargetedMSSchema.TABLE_INSTRUMENT_NICKNAME, ContainerFilter.Type.CurrentPlusProjectAndShared.create(schema)), "f"); + nicknameSql.append(" ON (f.SerialNumber = "); + nicknameSql.append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentSerialNumber"); + nicknameSql.append(" OR (f.SerialNumber IS NULL AND "); + nicknameSql.append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentSerialNumber"); + nicknameSql.append(" IS NULL)) AND (f.Model = i.Model OR (f.Model IS NULL AND i.Model IS NULL)))"); + ExprColumn nicknameCol = new ExprColumn(this, "InstrumentNickname", nicknameSql, JdbcType.VARCHAR); + addColumn(nicknameCol); + // Special handling for a sample identifier annotation. Inject it even if this folder doesn't have it configured AnnotatedTargetedMSTable.AnnotationSettingForTyping idSetting = new AnnotatedTargetedMSTable.AnnotationSettingForTyping("SampleIdentifier", @@ -221,8 +236,8 @@ public SampleFileTable(TargetedMSSchema schema, ContainerFilter cf, @Nullable Ta downloadCol.setTextAlign("left"); downloadCol.setDisplayColumnFactory(DownloadLinkColumn::new); - DetailsURL instrumentURL = new DetailsURL(new ActionURL(TargetedMSController.ShowInstrumentAction.class, getContainer()), Collections.singletonMap("serialNumber", "InstrumentSerialNumber")); - getMutableColumn("InstrumentSerialNumber").setURL(instrumentURL); + DetailsURL instrumentURL = new DetailsURL(new ActionURL(TargetedMSController.ShowInstrumentAction.class, getContainer()), Collections.singletonMap("name", "InstrumentNickname")); + getMutableColumnOrThrow("InstrumentNickname").setURL(instrumentURL); } @Override @@ -247,7 +262,7 @@ public List getDefaultVisibleColumns() FieldKey.fromParts("File"), FieldKey.fromParts("Download"), FieldKey.fromParts("AcquiredTime"), - FieldKey.fromParts("InstrumentSerialNumber") + FieldKey.fromParts("InstrumentNickname") )); // Find the columns that have values for the run of interest, and include them in the set of columns in the default @@ -259,7 +274,6 @@ public List getDefaultVisibleColumns() aggregates.add(new Aggregate(FieldKey.fromParts("ReplicateId", "SampleType"), Aggregate.BaseType.MAX)); aggregates.add(new Aggregate(FieldKey.fromParts("ReplicateId", "AnalyteConcentration"), Aggregate.BaseType.MAX)); aggregates.add(new Aggregate(FieldKey.fromParts("ReplicateId", "SampleDilutionFactor"), Aggregate.BaseType.MAX)); - aggregates.add(new Aggregate(FieldKey.fromParts("InstrumentId"), Aggregate.BaseType.MAX)); // Also search for values for any replicate annotations being used in this container for (AnnotatedTargetedMSTable.AnnotationSettingForTyping annotation : getUserSchema().getAnnotationSettings("replicate", ContainerFilter.current(getUserSchema()))) diff --git a/src/org/labkey/targetedms/view/nickname.jsp b/src/org/labkey/targetedms/view/nickname.jsp new file mode 100644 index 000000000..5cb243e9c --- /dev/null +++ b/src/org/labkey/targetedms/view/nickname.jsp @@ -0,0 +1,79 @@ +<% +/* + * Copyright (c) 2013-2019 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. + */ +%> +<%@ page import="org.labkey.api.view.HttpView" %> +<%@ page import="org.labkey.api.view.JspView" %> +<%@ page import="org.labkey.targetedms.TargetedMSController" %> +<%@ page import="org.labkey.targetedms.model.InstrumentNickname" %> +<%@ page import="org.labkey.api.security.permissions.UpdatePermission" %> +<%@ page import="org.labkey.api.data.Container" %> +<%@ page import="org.labkey.api.data.ContainerManager" %> +<%@ page import="java.util.Map" %> +<%@ page import="java.util.LinkedHashMap" %> +<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> +<%@ page extends="org.labkey.api.jsp.JspBase" %> +<% + JspView currentView = HttpView.currentView(); + InstrumentNickname name = currentView.getModelBean(); + Map targetContainers = new LinkedHashMap<>(); + Container shared = ContainerManager.getSharedContainer(); + if (shared.hasPermission(getUser(), UpdatePermission.class)) + { + targetContainers.put(shared.getId(), shared.getPath()); + } + Container project = getContainer().getProject(); + if (project != null && project.hasPermission(getUser(), UpdatePermission.class)) + { + targetContainers.put(project.getId(), project.getPath()); + } + if (getContainer().hasPermission(getUser(), UpdatePermission.class)) + { + targetContainers.put(getContainer().getId(), getContainer().getPath()); + } + %> + + + + + + + + + + + + + + + + + + + <% if (!targetContainers.isEmpty()) { %> + + + + + <% } %> +
Model<%= h(name.getModel()) %>
Serial Number<%= h(name.getSerialNumber()) %>
<% if (targetContainers.isEmpty()) { %><%= h(name.getNickname()) %><% } else { %><% } %>
+ + <%= button("Save").submit(true) %> +
+
+ diff --git a/src/org/labkey/targetedms/view/renameRun.jsp b/src/org/labkey/targetedms/view/renameRun.jsp index 44ecefd6f..446cfbe2e 100644 --- a/src/org/labkey/targetedms/view/renameRun.jsp +++ b/src/org/labkey/targetedms/view/renameRun.jsp @@ -22,7 +22,8 @@ <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <% - RenameBean bean = ((JspView) HttpView.currentView()).getModelBean(); + JspView currentView = HttpView.currentView(); + RenameBean bean = currentView.getModelBean(); %> <%=generateReturnUrlFormField(bean.returnUrl)%> diff --git a/webapp/TargetedMS/js/QCSummaryPanel.js b/webapp/TargetedMS/js/QCSummaryPanel.js index d67ce2270..b319d57a6 100644 --- a/webapp/TargetedMS/js/QCSummaryPanel.js +++ b/webapp/TargetedMS/js/QCSummaryPanel.js @@ -21,13 +21,32 @@ Ext4.define('LABKEY.targetedms.QCSummary', { text: 'Loading...' }) - LABKEY.targetedms.QCMetricConfigLoader.getMetrics(this.initPanel, this); + this.qcPlotPanel.queryQCInstruments(this.getQCSummary, this); this.numSampleFileStats = config ? config.sampleLimit : 3; }, - initPanel : function(metrics) { - this.metricPropArr = metrics; - this.qcPlotPanel.queryQCInstruments(this.getQCSummary, this); + formatInstruments: function(container) { + if (container.distinctInstruments) { + if (container.distinctInstruments.length > 1) { + container.instrument = ' for multiple instruments:
    '; + for (let index = 0; index < container.distinctInstruments.length; index++) { + container.instrument += '
  • ' + this.formatInstrument(container.distinctInstruments[index], container.path) + '
  • '; + } + container.instrument += '
We recommend that each instrument use its own QC folder.'; + } + else if (container.distinctInstruments.length === 1 && container.distinctInstruments[0]) { + container.instrument = ' for ' + this.formatInstrument(container.distinctInstruments[0], container.path); + } + } + }, + + formatInstrument: function(name, containerPath) { + let result = Ext4.util.Format.htmlEncode(name ? name : 'unknown instrument'); + if (name) + result = '' + result + '' + return result; }, getQCSummary: function () { @@ -57,25 +76,14 @@ Ext4.define('LABKEY.targetedms.QCSummary', { container.showName = hasChildren; container.isParent = true; container.parentOnly = containers.length === 1; - if (this.qcPlotPanel.qcIntrumentsArr) { - if (this.qcPlotPanel.qcIntrumentsArr.length > 1) { - container.instrument = ' for multiple instruments:
    '; - for (let index = 0; index < this.qcPlotPanel.qcIntrumentsArr.length; index++) { - let currentInstrument = this.qcPlotPanel.qcIntrumentsArr[index]; - container.instrument += '
  • ' + Ext4.util.Format.htmlEncode(currentInstrument ? currentInstrument : 'unknown instrument') + '
  • '; - } - container.instrument += '
We recommend that each instrument use its own QC folder.'; - } - else if (this.qcPlotPanel.qcIntrumentsArr.length === 1 && this.qcPlotPanel.qcIntrumentsArr[0]) { - container.instrument = ' for ' + Ext4.util.Format.htmlEncode(this.qcPlotPanel.qcIntrumentsArr[0]); - } - } + this.formatInstruments(container); this.add(this.getContainerSummaryView(container, hasChildren, width)); // Add the set of child containers in an hbox layout if (hasChildren) { for (var i = 1; i < containers.length; i++) { container = containers[i]; + this.formatInstruments(container); container.showName = true; container.parentOnly = false; container.isParent = false; From aad1bf33331427e2be6370d687b52fb601150c51 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Wed, 7 May 2025 18:10:00 -0700 Subject: [PATCH 02/17] Audit logging --- .../targetedms/TargetedMSController.java | 7 ++-- .../labkey/targetedms/TargetedMSManager.java | 33 ++++++++++++++++--- .../labkey/targetedms/TargetedMSSchema.java | 6 +++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/org/labkey/targetedms/TargetedMSController.java b/src/org/labkey/targetedms/TargetedMSController.java index d4b336a67..d47d0285c 100644 --- a/src/org/labkey/targetedms/TargetedMSController.java +++ b/src/org/labkey/targetedms/TargetedMSController.java @@ -113,10 +113,13 @@ import org.labkey.api.protein.ProteinService; import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.DetailsURL; +import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.FieldKey; import org.labkey.api.query.FilteredTable; +import org.labkey.api.query.InvalidKeyException; import org.labkey.api.query.QueryParam; import org.labkey.api.query.QuerySettings; +import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.query.QueryView; import org.labkey.api.query.ValidationException; import org.labkey.api.reports.ReportService; @@ -4580,7 +4583,7 @@ public void validateCommand(InstrumentForm target, Errors errors) } @Override - public boolean handlePost(InstrumentForm form, BindException errors) + public boolean handlePost(InstrumentForm form, BindException errors) throws SQLException, BatchValidationException, QueryUpdateServiceException, InvalidKeyException, DuplicateKeyException { Container targetContainer = ContainerManager.getForId(form.getTargetContainerId()); if (targetContainer == null || !targetContainer.hasPermission(getUser(), UpdatePermission.class)) @@ -4607,7 +4610,7 @@ public boolean handlePost(InstrumentForm form, BindException errors) } if (StringUtils.isEmpty(form.getName()) && name.getId() > 0) { - TargetedMSManager.get().deleteNickname(name); + TargetedMSManager.get().deleteNickname(name, getUser()); } else { diff --git a/src/org/labkey/targetedms/TargetedMSManager.java b/src/org/labkey/targetedms/TargetedMSManager.java index 9b2b0c4ca..5e57b17c9 100644 --- a/src/org/labkey/targetedms/TargetedMSManager.java +++ b/src/org/labkey/targetedms/TargetedMSManager.java @@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable; import org.labkey.api.cache.Cache; import org.labkey.api.cache.CacheManager; +import org.labkey.api.collections.CaseInsensitiveHashMap; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.CompareType; import org.labkey.api.data.Container; @@ -65,11 +66,15 @@ import org.labkey.api.pipeline.PipelineService; import org.labkey.api.pipeline.PipelineValidationException; import org.labkey.api.query.BatchValidationException; +import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.FieldKey; +import org.labkey.api.query.InvalidKeyException; import org.labkey.api.query.QueryDefinition; import org.labkey.api.query.QueryException; import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; +import org.labkey.api.query.QueryUpdateService; +import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.query.SchemaKey; import org.labkey.api.query.UserSchema; import org.labkey.api.security.User; @@ -117,6 +122,7 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.sql.SQLException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -2937,20 +2943,37 @@ public void clearCachedEnabledQCMetrics(Container container) getSchema().getScope().addCommitTask(() -> _metricCache.remove(container), DbScope.CommitTaskOption.IMMEDIATE, DbScope.CommitTaskOption.POSTCOMMIT, DbScope.CommitTaskOption.POSTROLLBACK); } - public void deleteNickname(InstrumentNickname name) + @NotNull + private QueryUpdateService getNicknameUpdateService(User user, Container container) + { + TargetedMSSchema schema = new TargetedMSSchema(user, container); + TableInfo table = schema.getTableOrThrow(TABLE_INSTRUMENT_NICKNAME); + return Objects.requireNonNull(table.getUpdateService()); + } + + public void deleteNickname(InstrumentNickname name, User user) throws SQLException, BatchValidationException, QueryUpdateServiceException, InvalidKeyException { - Table.delete(getTableInfoInstrumentNickname(), name.getId()); + getNicknameUpdateService(user, name.getContainer()). + deleteRows(user, name.getContainer(), Arrays.asList(new CaseInsensitiveHashMap<>(Map.of("id", name.getId()))), null, null); } - public void saveNickname(InstrumentNickname name, User user) + public void saveNickname(InstrumentNickname name, User user) throws SQLException, BatchValidationException, QueryUpdateServiceException, InvalidKeyException, DuplicateKeyException { + Map row = new CaseInsensitiveHashMap<>(); + row.put("nickname", name.getNickname()); + row.put("serialNumber", name.getSerialNumber()); + row.put("model", name.getModel()); + BatchValidationException errors = new BatchValidationException(); if (name.getId() > 0) { - Table.update(user, getTableInfoInstrumentNickname(), name, name.getId()); + row.put("id", name.getId()); + getNicknameUpdateService(user, name.getContainer()). + updateRows(user, name.getContainer(), Arrays.asList(row), Arrays.asList(row), errors, null, null); } else { - Table.insert(user, getTableInfoInstrumentNickname(), name); + getNicknameUpdateService(user, name.getContainer()). + insertRows(user, name.getContainer(), Arrays.asList(row), errors, null, null); } } } diff --git a/src/org/labkey/targetedms/TargetedMSSchema.java b/src/org/labkey/targetedms/TargetedMSSchema.java index 0e204d9b4..4ed9ec1fc 100644 --- a/src/org/labkey/targetedms/TargetedMSSchema.java +++ b/src/org/labkey/targetedms/TargetedMSSchema.java @@ -46,6 +46,7 @@ import org.labkey.api.exp.api.ExperimentService; import org.labkey.api.exp.query.ExpRunTable; import org.labkey.api.exp.query.ExpSchema; +import org.labkey.api.gwt.client.AuditBehaviorType; import org.labkey.api.module.Module; import org.labkey.api.query.CrosstabView; import org.labkey.api.query.CustomView; @@ -1609,7 +1610,6 @@ public DisplayColumn createRenderer(ColumnInfo colInfo) TABLE_RATE_TYPE.equalsIgnoreCase(name) || TABLE_INSTRUMENT_RATE.equalsIgnoreCase(name) || TABLE_INSTRUMENT_USAGE_PAYMENT.equalsIgnoreCase(name) || - TABLE_INSTRUMENT_NICKNAME.equalsIgnoreCase(name)) { var result = new FilteredTable<>(getSchema().getTable(name), this, cf) @@ -1627,6 +1627,10 @@ public QueryUpdateService getUpdateService() } }; result.wrapAllColumns(true); + if (TABLE_INSTRUMENT_NICKNAME.equalsIgnoreCase(name)) + { + result.setAuditBehavior(AuditBehaviorType.DETAILED); + } TargetedMSTable.fixupLookups(result); return result; } From 3d1014e5f03890aea8b67ee900cb5d4e1b06f935 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 9 May 2025 10:15:10 -0700 Subject: [PATCH 03/17] Finish adapting for SQLServer --- .../dbscripts/sqlserver/targetedms-25.003-25.004.sql | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/schemas/dbscripts/sqlserver/targetedms-25.003-25.004.sql b/resources/schemas/dbscripts/sqlserver/targetedms-25.003-25.004.sql index 7ca43e850..f3585ebe9 100644 --- a/resources/schemas/dbscripts/sqlserver/targetedms-25.003-25.004.sql +++ b/resources/schemas/dbscripts/sqlserver/targetedms-25.003-25.004.sql @@ -3,14 +3,14 @@ CREATE TABLE targetedms.InstrumentNickname Id BIGINT IDENTITY(1, 1) NOT NULL, Container entityid NOT NULL, - Created TIMESTAMP, + Created DATETIME, CreatedBy USERID, - Modified TIMESTAMP, + Modified DATETIME, ModifiedBy USERID, - SerialNumber VARCHAR(200), - Model VARCHAR(300), - Nickname VARCHAR(200), + SerialNumber NVARCHAR(200), + Model NVARCHAR(300), + Nickname NVARCHAR(200), CONSTRAINT PK_InstrumentNickname PRIMARY KEY (Id) ); From 311a593b1b4ef367f816b2a18054a1c204c7fddd Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 9 May 2025 16:44:52 -0700 Subject: [PATCH 04/17] SQL Server compatible SQL --- .../targetedms/query/SampleFileTable.java | 19 +++++++++++++++---- src/org/labkey/targetedms/view/nickname.jsp | 8 ++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/org/labkey/targetedms/query/SampleFileTable.java b/src/org/labkey/targetedms/query/SampleFileTable.java index cdbdafbc1..92c472f39 100644 --- a/src/org/labkey/targetedms/query/SampleFileTable.java +++ b/src/org/labkey/targetedms/query/SampleFileTable.java @@ -117,9 +117,10 @@ public SampleFileTable(TargetedMSSchema schema, ContainerFilter cf, @Nullable Ta ExprColumn excludedColumn = new ExprColumn(this, "Excluded", excludedSQL, JdbcType.BOOLEAN); addColumn(excludedColumn); - SQLFragment nicknameSql = new SQLFragment("(SELECT MIN(COALESCE(Nickname, "); - nicknameSql.append(getSqlDialect().concatenate("i.Model", "' - '", ExprColumn.STR_TABLE_ALIAS + ".InstrumentSerialNumber")); - nicknameSql.append(", ").append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentSerialNumber, i.Model)) FROM (SELECT * FROM "); + SQLFragment nicknameSql = new SQLFragment("COALESCE("); + + // Find a nickname if we have one + nicknameSql.append("(SELECT MIN(Nickname) FROM ((SELECT * FROM "); nicknameSql.append(TargetedMSManager.getTableInfoInstrument(), "i"); nicknameSql.append(" WHERE i.Id = "); nicknameSql.append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentId) i LEFT OUTER JOIN "); @@ -128,7 +129,17 @@ public SampleFileTable(TargetedMSSchema schema, ContainerFilter cf, @Nullable Ta nicknameSql.append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentSerialNumber"); nicknameSql.append(" OR (f.SerialNumber IS NULL AND "); nicknameSql.append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentSerialNumber"); - nicknameSql.append(" IS NULL)) AND (f.Model = i.Model OR (f.Model IS NULL AND i.Model IS NULL)))"); + nicknameSql.append(" IS NULL)) AND (f.Model = i.Model OR (f.Model IS NULL AND i.Model IS NULL)))), "); + + // Alternatively, use the default name for the instrument (model - serial number) + nicknameSql.append("(SELECT COALESCE("); + nicknameSql.append(getSqlDialect().concatenate("x.Model", "' - '", ExprColumn.STR_TABLE_ALIAS + ".InstrumentSerialNumber")); + nicknameSql.append(", ").append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentSerialNumber, x.Model)"); + nicknameSql.append(" FROM (SELECT Model FROM "); + nicknameSql.append(TargetedMSManager.getTableInfoInstrument(), "i"); + nicknameSql.append(" WHERE i.Id = "); + nicknameSql.append(ExprColumn.STR_TABLE_ALIAS).append(".InstrumentId) x)"); + nicknameSql.append(") "); ExprColumn nicknameCol = new ExprColumn(this, "InstrumentNickname", nicknameSql, JdbcType.VARCHAR); addColumn(nicknameCol); diff --git a/src/org/labkey/targetedms/view/nickname.jsp b/src/org/labkey/targetedms/view/nickname.jsp index 5cb243e9c..03279695a 100644 --- a/src/org/labkey/targetedms/view/nickname.jsp +++ b/src/org/labkey/targetedms/view/nickname.jsp @@ -33,16 +33,16 @@ Container shared = ContainerManager.getSharedContainer(); if (shared.hasPermission(getUser(), UpdatePermission.class)) { - targetContainers.put(shared.getId(), shared.getPath()); + targetContainers.put(shared.getId(), "Server-wide"); } Container project = getContainer().getProject(); if (project != null && project.hasPermission(getUser(), UpdatePermission.class)) { - targetContainers.put(project.getId(), project.getPath()); + targetContainers.putIfAbsent(project.getId(), "In this project, " + project.getName()); } if (getContainer().hasPermission(getUser(), UpdatePermission.class)) { - targetContainers.put(getContainer().getId(), getContainer().getPath()); + targetContainers.putIfAbsent(getContainer().getId(), "In this folder, " + getContainer().getPath()); } %> @@ -65,7 +65,7 @@ <% if (!targetContainers.isEmpty()) { %> - +