From 96b38d51da218b136af4ac363ea3d232053e03d3 Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 18 Jun 2025 10:50:43 -0700 Subject: [PATCH 1/9] Read laboratory reports from the database as well --- .../api/laboratory/LaboratoryService.java | 2 + .../api/laboratory/QueryTabbedReportItem.java | 18 ++ laboratory/resources/schemas/laboratory.xml | 1 + .../laboratory/LaboratoryDataProvider.java | 250 +++++++++++++++++- .../labkey/laboratory/LaboratorySchema.java | 1 + .../laboratory/LaboratoryServiceImpl.java | 6 + 6 files changed, 277 insertions(+), 1 deletion(-) diff --git a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java index f94f6c3d..c2345ad4 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java +++ b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java @@ -123,6 +123,8 @@ static public void setInstance(LaboratoryService instance) abstract public @Nullable DemographicsProvider getDemographicsProviderByName(Container c, User u, String name); + abstract public void clearDataProviderCache(); + public static enum NavItemCategory { samples(), diff --git a/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java b/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java index e2bbe1cc..11a0a883 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java +++ b/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java @@ -30,6 +30,7 @@ public class QueryTabbedReportItem extends TabbedReportItem { private String _schemaName; private String _queryName; + private String _viewName; public QueryTabbedReportItem(QueryCache cache, DataProvider provider, String schemaName, String queryName, String label, String reportCategory) { @@ -67,6 +68,16 @@ public void setQueryName(String queryName) _queryName = queryName; } + public String getViewName() + { + return _viewName; + } + + public void setViewName(String viewName) + { + _viewName = viewName; + } + @Override public JSONObject toJSON(Container c, User u) { @@ -83,6 +94,13 @@ public JSONObject toJSON(Container c, User u) json.put("schemaName", getSchemaName()); json.put("queryName", getQueryName()); String viewName = getDefaultViewName(c, getOwnerKey()); + + // TODO: should we always override here? + if (getViewName() != null) + { + viewName = getViewName(); + } + if (viewName != null) { json.put("viewName", viewName); diff --git a/laboratory/resources/schemas/laboratory.xml b/laboratory/resources/schemas/laboratory.xml index 8c435647..ccd03a46 100644 --- a/laboratory/resources/schemas/laboratory.xml +++ b/laboratory/resources/schemas/laboratory.xml @@ -633,6 +633,7 @@ rowid + Extra Reports DETAILED diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java b/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java index fb324676..858cfec6 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java @@ -15,17 +15,23 @@ */ package org.labkey.laboratory; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.json.JSONObject; +import org.labkey.api.cache.Cache; +import org.labkey.api.cache.CacheManager; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; import org.labkey.api.laboratory.AbstractDataProvider; +import org.labkey.api.laboratory.DataProvider; import org.labkey.api.laboratory.DetailsUrlWithoutLabelNavItem; import org.labkey.api.laboratory.JSTabbedReportItem; import org.labkey.api.laboratory.LaboratoryService; +import org.labkey.api.laboratory.NavItem; import org.labkey.api.laboratory.QueryCountNavItem; import org.labkey.api.laboratory.QueryImportNavItem; import org.labkey.api.laboratory.QueryTabbedReportItem; @@ -33,16 +39,17 @@ import org.labkey.api.laboratory.SimpleSettingsItem; import org.labkey.api.laboratory.SummaryNavItem; import org.labkey.api.laboratory.TabbedReportItem; -import org.labkey.api.laboratory.NavItem; import org.labkey.api.ldk.table.QueryCache; import org.labkey.api.module.Module; import org.labkey.api.query.DetailsURL; +import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryAction; import org.labkey.api.query.QueryService; import org.labkey.api.query.UserSchema; import org.labkey.api.security.User; import org.labkey.api.study.Study; import org.labkey.api.study.StudyService; +import org.labkey.api.util.logging.LogHelper; import org.labkey.api.view.ActionURL; import org.labkey.api.view.ViewContext; import org.labkey.api.view.template.ClientDependency; @@ -62,11 +69,23 @@ public class LaboratoryDataProvider extends AbstractDataProvider public static final String NAME = "Laboratory"; private final Module _module; + private static final Logger _log = LogHelper.getLogger(LaboratoryDataProvider.class, "Messages from LaboratoryDataProvider"); + + private static Cache> _cache = null; + public LaboratoryDataProvider(Module m) { _module = m; } + public static void clearCache() + { + if (_cache != null) + { + _cache.clear(); + } + } + @Override public String getName() { @@ -332,6 +351,235 @@ public List getTabbedReportItems(Container c, User u) item2.setOwnerKey(owner.getPropertyManagerKey()); items.add(item2); + List models = getTabbedReports(c, u); + if (!models.isEmpty()) + { + items.addAll(models.stream().map(m -> { + if (m.isValid()) + { + _log.error("Invalid tabbed report in container: " + c.getPath() + ", rowId: " + m.getRowId()); + } + + return(m.toNavItem(c, u, cache, owner, this)); + }).toList()); + } + return Collections.unmodifiableList(items); } + + private synchronized Cache> getCache() + { + if (_cache == null) + { + _cache = CacheManager.getStringKeyCache(1000, CacheManager.UNLIMITED, "LaboratoryDataProviderCache"); + } + + return _cache; + } + + private synchronized List getTabbedReports(Container c, User u) + { + c = c.isWorkbookOrTab() ? c.getParent() : c; + + List models = getCache().get(c.getId()); + if (models != null) + return models; + + UserSchema us = QueryService.get().getUserSchema(u, c, LaboratoryModule.SCHEMA_NAME); + if (us == null) + { + return Collections.emptyList(); + } + + models = new ArrayList<>(new TableSelector(us.getTable(LaboratorySchema.TABLE_REPORTS)).getArrayList(TabbedReportModel.class)); + models = Collections.unmodifiableList(models); + getCache().put(c.getId(), models); + + return models; + } + + public static class TabbedReportModel + { + private int _rowId; + private String _category; + private String _reportType; + private String _reportTitle; + private String _sortOrder; + private String _containerPath; + private String _schemaName; + private String _queryName; + private String _viewName; + private String _reportPath; + private String _subjectFieldName; + private String _description; + + public boolean isValid() + { + if (getCategory() == null || getSchemaName() == null || getQueryName() == null) + { + return false; + } + + if (getContainerPath() != null) + { + Container target = ContainerManager.getForPath(getContainerPath()); + if (target == null) + { + _log.error("Unknown container for saved TabbedReportItem: " + getContainerPath() + ", rowId: " + getRowId()); + return false; + } + } + + return true; + } + + public TabbedReportItem toNavItem(Container c, User u, QueryCache cache, NavItem owner, DataProvider provider) + { + QueryTabbedReportItem report = new QueryTabbedReportItem(cache, provider, getSchemaName(), getQueryName(), getReportTitle() == null ? getQueryName() : getReportTitle(), getCategory()); + report.setQueryCache(cache); + if (getSubjectFieldName() != null) + { + report.setSubjectIdFieldKey(FieldKey.fromString(getSubjectFieldName())); + } + + if (getContainerPath() != null) + { + Container target = ContainerManager.getForPath(getContainerPath()); + if (target != null) + { + report.setTargetContainer(target); + } + } + + if (getViewName() != null) + { + report.setViewName(getViewName()); + } + + report.setVisible(owner.isVisible(c, u)); + report.setOwnerKey(owner.getPropertyManagerKey()); + + return report; + } + + public int getRowId() + { + return _rowId; + } + + public void setRowId(int rowId) + { + _rowId = rowId; + } + + public String getCategory() + { + return _category; + } + + public void setCategory(String category) + { + _category = category; + } + + public String getReportType() + { + return _reportType; + } + + public void setReportType(String reportType) + { + _reportType = reportType; + } + + public String getReportTitle() + { + return _reportTitle; + } + + public void setReportTitle(String reportTitle) + { + _reportTitle = reportTitle; + } + + public String getSortOrder() + { + return _sortOrder; + } + + public void setSortOrder(String sortOrder) + { + _sortOrder = sortOrder; + } + + public String getContainerPath() + { + return _containerPath; + } + + public void setContainerPath(String containerPath) + { + _containerPath = containerPath; + } + + public String getSchemaName() + { + return _schemaName; + } + + public void setSchemaName(String schemaName) + { + _schemaName = schemaName; + } + + public String getQueryName() + { + return _queryName; + } + + public void setQueryName(String queryName) + { + _queryName = queryName; + } + + public String getViewName() + { + return _viewName; + } + + public void setViewName(String viewName) + { + _viewName = viewName; + } + + public String getReportPath() + { + return _reportPath; + } + + public void setReportPath(String reportPath) + { + _reportPath = reportPath; + } + + public String getSubjectFieldName() + { + return _subjectFieldName; + } + + public void setSubjectFieldName(String subjectFieldName) + { + _subjectFieldName = subjectFieldName; + } + + public String getDescription() + { + return _description; + } + + public void setDescription(String description) + { + _description = description; + } + } } diff --git a/laboratory/src/org/labkey/laboratory/LaboratorySchema.java b/laboratory/src/org/labkey/laboratory/LaboratorySchema.java index 5c9d38b8..57067697 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratorySchema.java +++ b/laboratory/src/org/labkey/laboratory/LaboratorySchema.java @@ -39,6 +39,7 @@ public class LaboratorySchema public static final String TABLE_WORKBOOK_TAGS = "workbook_tags"; public static final String TABLE_ASSAY_RUN_TEMPLATES = "assay_run_templates"; public static final String TABLE_SPECIES = "species"; + public static final String TABLE_REPORTS = "reports"; public static LaboratorySchema getInstance() { diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java b/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java index 6e55124b..f848a7fb 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java @@ -713,4 +713,10 @@ public List getDemographicsProviders(final Container c, fi return null; } + + @Override + public void clearDataProviderCache() + { + LaboratoryDataProvider.clearCache(); + } } From 6df8b8c868e46d316dbcdf327eaaab5277ab3c43 Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 18 Jun 2025 12:42:45 -0700 Subject: [PATCH 2/9] Cleanup in NavItems / TabbedReports --- .../src/org/labkey/laboratory/LaboratoryDataProvider.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java b/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java index 858cfec6..cf31dc29 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java @@ -57,6 +57,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -355,13 +356,13 @@ public List getTabbedReportItems(Container c, User u) if (!models.isEmpty()) { items.addAll(models.stream().map(m -> { - if (m.isValid()) + if (!m.isValid()) { - _log.error("Invalid tabbed report in container: " + c.getPath() + ", rowId: " + m.getRowId()); + return null; } return(m.toNavItem(c, u, cache, owner, this)); - }).toList()); + }).filter(Objects::nonNull).toList()); } return Collections.unmodifiableList(items); @@ -417,6 +418,7 @@ public boolean isValid() { if (getCategory() == null || getSchemaName() == null || getQueryName() == null) { + _log.error("Missing either category, schema or query for laboratory.reports item: " + getContainerPath() + ", rowId: " + getRowId()); return false; } From 54f3bb2ec707d0b2b0ae9646ae0bef15cbb57f8f Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 18 Jun 2025 13:17:48 -0700 Subject: [PATCH 3/9] More cleanup in NavItems / TabbedReports --- .../org/labkey/laboratory/LaboratoryController.java | 12 +++++++++++- .../labkey/laboratory/LaboratoryDataProvider.java | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryController.java b/laboratory/src/org/labkey/laboratory/LaboratoryController.java index 0f85b4fe..66772f1d 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryController.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryController.java @@ -1792,7 +1792,17 @@ public ApiResponse execute(GetDataItemsForm form, BindException errors) ensureModuleActive(item); if (form.isIncludeAll() || item.isVisible(getContainer(), getUser())) - json.add(item.toJSON(getContainer(), getUser())); + { + JSONObject jo = item.toJSON(getContainer(), getUser()); + if (jo == null) + { + _log.error("Invalid JSON for tabbedReport item: " + item.getPropertyManagerKey()); + } + else + { + json.add(jo); + } + } } results.put(LaboratoryService.NavItemCategory.tabbedReports.name(), json); } diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java b/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java index cf31dc29..1304e96a 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java @@ -330,10 +330,15 @@ public List getSubjectIdSummary(Container c, User u, String subjectId) @Override public List getTabbedReportItems(Container c, User u) { + if (!c.getActiveModules().contains(getOwningModule())) + { + return Collections.emptyList(); + } + List items = new ArrayList<>(); QueryCache cache = new QueryCache(); - NavItem nav = new QueryImportNavItem(this, LaboratoryModule.SCHEMA_NAME, "Samples", LaboratoryService.NavItemCategory.samples, "Samples", cache); + NavItem nav = getReportItems(c, u).get(0); TabbedReportItem item = new QueryTabbedReportItem(cache, this, LaboratoryModule.SCHEMA_NAME, "Samples", "Samples", "Samples"); item.setQueryCache(cache); item.setVisible(nav.isVisible(c, u)); From ef0158aeaf9e9b648bc8cf24d80db053dd6953b2 Mon Sep 17 00:00:00 2001 From: bbimber Date: Tue, 24 Jun 2025 12:13:41 -0700 Subject: [PATCH 4/9] Refinements to customizer code, and many new SIV-related ETLs --- .../ldk/query/DefaultTableCustomizer.java | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java b/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java index db2bc47d..b1e1dab9 100644 --- a/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java +++ b/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java @@ -125,7 +125,21 @@ private void setDetailsUrl(AbstractTableInfo ti) assert queryName != null; List keyFields = ti.getPkColumnNames(); - assert keyFields.size() > 0 : "No key fields found for the table: " + ti.getPublicSchemaName() + "." + ti.getPublicName(); + assert !keyFields.isEmpty() : "No key fields found for the table: " + ti.getPublicSchemaName() + "." + ti.getPublicName(); + + if (_settings.getPrimaryKeyField() != null) + { + String alternatePK = _settings.getPrimaryKeyField(); + if (!keyFields.contains(alternatePK)) + { + _log.error("Table: " + ti.getUserSchema().getSchemaName() + "." + ti.getPublicName() + " supplied an alternate primaryKeyField that doesnt match actual PKs: " + alternatePK); + return; + } + + keyFields.clear(); + keyFields.add(alternatePK); + } + if (keyFields.size() != 1) { _log.error("Table: " + ti.getUserSchema().getSchemaName() + "." + ti.getPublicName() + " has more than 1 PK: " + StringUtils.join(keyFields, ";") + ", cannot apply custom links - please update the TableCustomizer properties"); @@ -159,14 +173,28 @@ else if (_settings.isSetEditLinkOverrides()) assert queryName != null; List keyFields = ti.getPkColumnNames(); - assert keyFields.size() > 0 : "No key fields found for the table: " + ti.getPublicSchemaName() + "." + ti.getPublicName(); + assert !keyFields.isEmpty() : "No key fields found for the table: " + ti.getPublicSchemaName() + "." + ti.getPublicName(); + + if (_settings.getPrimaryKeyField() != null) + { + String alternatePK = _settings.getPrimaryKeyField(); + if (!keyFields.contains(alternatePK)) + { + _log.error("Table: " + ti.getUserSchema().getSchemaName() + "." + ti.getPublicName() + " supplied an alternate primaryKeyField that doesnt match actual PKs: " + alternatePK); + return; + } + + keyFields.clear(); + keyFields.add(alternatePK); + } + if (keyFields.size() != 1) { _log.error("Table: " + schemaName + "." + queryName + " has more than 1 PK: " + StringUtils.join(keyFields, ";") + ", cannot apply custom links - please update the TableCustomizer properties"); return; } - if (schemaName != null && queryName != null && keyFields.size() > 0) + if (schemaName != null && queryName != null && !keyFields.isEmpty()) { String keyField = keyFields.get(0); if (!AbstractTableInfo.LINK_DISABLER_ACTION_URL.equals(ti.getImportDataURL(ti.getUserSchema().getContainer()))) @@ -445,7 +473,8 @@ public enum PROPERIES setEditLinkOverrides(Boolean.class, true), auditMode(String.class, AuditBehaviorType.DETAILED.name()), disableFacetingForNumericCols(Boolean.class, true), - overrideDetailsUrl(Boolean.class, true); + overrideDetailsUrl(Boolean.class, true), + primaryKeyField(String.class, null); private final Class _clazz; private final Object _defaultVal; @@ -531,6 +560,17 @@ public AuditBehaviorType getAuditMode() return AuditBehaviorType.DETAILED; } + public String getPrimaryKeyField() + { + Object fieldName = getProperty(PROPERIES.primaryKeyField); + if (fieldName != null) + { + return fieldName.toString(); + } + + return null; + } + public boolean isDisableFacetingForNumericCols() { return (boolean)getProperty(PROPERIES.disableFacetingForNumericCols); From db741a2cbbee1d532bed8c0568f2a36ce0d05fe8 Mon Sep 17 00:00:00 2001 From: bbimber Date: Tue, 24 Jun 2025 12:58:09 -0700 Subject: [PATCH 5/9] Expand SIV Queries --- LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java b/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java index b1e1dab9..dd10cc06 100644 --- a/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java +++ b/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java @@ -136,8 +136,7 @@ private void setDetailsUrl(AbstractTableInfo ti) return; } - keyFields.clear(); - keyFields.add(alternatePK); + keyFields = Collections.singletonList(alternatePK); } if (keyFields.size() != 1) @@ -184,8 +183,7 @@ else if (_settings.isSetEditLinkOverrides()) return; } - keyFields.clear(); - keyFields.add(alternatePK); + keyFields = Collections.singletonList(alternatePK); } if (keyFields.size() != 1) From 2e83678920c9e8ae153b6061b76fca9bb25c2f21 Mon Sep 17 00:00:00 2001 From: bbimber Date: Tue, 24 Jun 2025 21:08:40 -0700 Subject: [PATCH 6/9] Create annotation to track UtilityActions and Admin Console page to list them --- LDK/src/org/labkey/ldk/LDKController.java | 129 ------------------ .../laboratory/LaboratoryController.java | 3 +- 2 files changed, 1 insertion(+), 131 deletions(-) diff --git a/LDK/src/org/labkey/ldk/LDKController.java b/LDK/src/org/labkey/ldk/LDKController.java index 168ac090..6e850aba 100644 --- a/LDK/src/org/labkey/ldk/LDKController.java +++ b/LDK/src/org/labkey/ldk/LDKController.java @@ -889,135 +889,6 @@ public void addNavTrail(NavTree root) } } - @RequiresPermission(AdminPermission.class) - public static class MoveWorkbookAction extends ConfirmAction - { - private Container _movedWb = null; - - @Override - public void validateCommand(MoveWorkbookForm form, Errors errors) - { - - } - - @Override - public ModelAndView getConfirmView(MoveWorkbookForm form, BindException errors) throws Exception - { - if (!getContainer().isWorkbook()) - { - errors.reject(ERROR_MSG, "This is only supported for workbooks"); - return new SimpleErrorView(errors); - } - - String sb = "This will move this workbook to the selected folder, renaming this workbook to match the series in that container. Note: there are many reasons this can be problematic, so please do this with great care

" + - ""; - - return new HtmlView(sb); - } - - @Override - public boolean handlePost(MoveWorkbookForm form, BindException errors) throws Exception - { - Container toMove = getContainer(); - if (!toMove.isWorkbook()) - { - errors.reject(ERROR_MSG, "This is only supported for workbooks"); - return false; - } - - if (StringUtils.trimToNull(form.getTargetContainer()) == null) - { - errors.reject(ERROR_MSG, "Must provide target container"); - return false; - } - - Container target = ContainerManager.getForPath(StringUtils.trimToNull(form.getTargetContainer())); - if (target == null) - { - target = ContainerManager.getForId(StringUtils.trimToNull(form.getTargetContainer())); - } - - if (target == null) - { - errors.reject(ERROR_MSG, "Unknown container: " + form.getTargetContainer()); - return false; - } - - if (target.isWorkbook()) - { - errors.reject(ERROR_MSG, "Target cannot be a workbook: " + form.getTargetContainer()); - return false; - } - - if (ContainerManager.isSystemContainer(target)) - { - errors.reject(ERROR_MSG, "Cannot move to system containers: " + form.getTargetContainer()); - return false; - } - - if (target.equals(toMove.getParent())) - { - errors.reject(ERROR_MSG, "Cannot move the workbook to its current parent: " + form.getTargetContainer()); - return false; - } - - //NOTE: transaction causing problems for larger sites? - //try (DbScope.Transaction transaction = CoreSchema.getInstance().getSchema().getScope().ensureTransaction()) - //{ - //first rename workbook to make unique - String tempName = new GUID().toString(); - int sortOrder = (int)DbSequenceManager.get(target, ContainerManager.WORKBOOK_DBSEQUENCE_NAME).next(); - _log.info("renaming workbook to in preparation for move from: " + toMove.getPath() + " to: " + tempName); - ContainerManager.rename(toMove, getUser(), tempName); - toMove = ContainerManager.getForId(toMove.getId()); - - //then move parent - _log.info("moving workbook from: " + toMove.getPath() + " to: " + target.getPath()); - ContainerManager.move(toMove, target, getUser()); - toMove = ContainerManager.getForId(toMove.getId()); - - //finally move to correct name - _log.info("renaming workbook from: " + toMove.getPath() + " to: " + sortOrder); - ContainerManager.rename(toMove, getUser(), String.valueOf(sortOrder)); - toMove.setSortOrder(sortOrder); - new SqlExecutor(CoreSchema.getInstance().getSchema()).execute("UPDATE core.containers SET SortOrder = ? WHERE EntityId = ?", toMove.getSortOrder(), toMove.getId()); - toMove = ContainerManager.getForId(toMove.getId()); - - //transaction.commit(); - _log.info("workbook move finished"); - - _movedWb = toMove; - //} - - return true; - } - - @NotNull - @Override - public URLHelper getSuccessURL(MoveWorkbookForm moveWorkbookForm) - { - if (_movedWb == null) - return getContainer().getStartURL(getUser()); - else - return _movedWb.getStartURL(getUser()); - } - } - - public static class MoveWorkbookForm - { - private String _targetContainer; - - public String getTargetContainer() - { - return _targetContainer; - } - - public void setTargetContainer(String targetContainer) - { - _targetContainer = targetContainer; - } - } - @RequiresNoPermission public class RedirectStartAction extends SimpleViewAction { diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryController.java b/laboratory/src/org/labkey/laboratory/LaboratoryController.java index 66772f1d..67501674 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryController.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryController.java @@ -294,7 +294,6 @@ public boolean handlePost(EnsureAssayFieldsForm form, BindException errors) thro } } - @RequiresPermission(AdminPermission.class) public class SetTableIncrementValueAction extends ConfirmAction { @@ -578,7 +577,7 @@ public void validateCommand(Object form, Errors errors) @Override public ModelAndView getConfirmView(Object form, BindException errors) throws Exception { - return new HtmlView("This action will webparts and tabs for the current folder and all children to the default Laboratory FolderType, if these folders are either Laboratory Folders or Expt Workbooks"); + return new HtmlView("This action will reset webparts and tabs for the current folder and all children to the default Laboratory FolderType, if these folders are either Laboratory Folders or Expt Workbooks"); } @Override From 2e96c5640294a6549a554c98c8877437ec34bda7 Mon Sep 17 00:00:00 2001 From: bbimber Date: Tue, 24 Jun 2025 22:36:01 -0700 Subject: [PATCH 7/9] Add many default views --- .../ldk/query/DefaultTableCustomizer.java | 14 ++++++++++--- .../api/laboratory/LaboratoryService.java | 3 +++ .../query/TabbedReportFilterProvider.java | 20 +++++++++++++++++++ .../laboratory/LaboratoryServiceImpl.java | 13 ++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java diff --git a/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java b/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java index dd10cc06..d91f99e1 100644 --- a/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java +++ b/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java @@ -139,7 +139,11 @@ private void setDetailsUrl(AbstractTableInfo ti) keyFields = Collections.singletonList(alternatePK); } - if (keyFields.size() != 1) + if (keyFields.isEmpty()) + { + return; + } + else if (keyFields.size() > 1) { _log.error("Table: " + ti.getUserSchema().getSchemaName() + "." + ti.getPublicName() + " has more than 1 PK: " + StringUtils.join(keyFields, ";") + ", cannot apply custom links - please update the TableCustomizer properties"); return; @@ -186,13 +190,17 @@ else if (_settings.isSetEditLinkOverrides()) keyFields = Collections.singletonList(alternatePK); } - if (keyFields.size() != 1) + if (keyFields.isEmpty()) + { + return; + } + else if (keyFields.size() != 1) { _log.error("Table: " + schemaName + "." + queryName + " has more than 1 PK: " + StringUtils.join(keyFields, ";") + ", cannot apply custom links - please update the TableCustomizer properties"); return; } - if (schemaName != null && queryName != null && !keyFields.isEmpty()) + if (schemaName != null && queryName != null) { String keyField = keyFields.get(0); if (!AbstractTableInfo.LINK_DISABLER_ACTION_URL.equals(ti.getImportDataURL(ti.getUserSchema().getContainer()))) diff --git a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java index c2345ad4..48fd1dea 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java +++ b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java @@ -26,6 +26,7 @@ import org.labkey.api.exp.api.ExpProtocol; import org.labkey.api.exp.api.ExpRun; import org.labkey.api.laboratory.assay.AssayDataProvider; +import org.labkey.api.laboratory.query.TabbedReportFilterProvider; import org.labkey.api.ldk.table.ButtonConfigFactory; import org.labkey.api.module.Module; import org.labkey.api.query.ValidationException; @@ -125,6 +126,8 @@ static public void setInstance(LaboratoryService instance) abstract public void clearDataProviderCache(); + abstract public void registerTabbedReportFilterProvider(TabbedReportFilterProvider provider); + public static enum NavItemCategory { samples(), diff --git a/laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java b/laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java new file mode 100644 index 00000000..a47118b6 --- /dev/null +++ b/laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java @@ -0,0 +1,20 @@ +package org.labkey.api.laboratory.query; + +import org.labkey.api.data.Container; +import org.labkey.api.security.User; +import org.labkey.api.view.template.ClientDependency; + +import java.util.Collection; + +public interface TabbedReportFilterProvider +{ + boolean isAvailable(Container c, User u); + + Collection getClientDependencies(); + + String getXType(); + + String getLabel(); + + String getInputValue(); +} diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java b/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java index f848a7fb..747ae62e 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java @@ -43,6 +43,7 @@ import org.labkey.api.laboratory.TabbedReportItem; import org.labkey.api.laboratory.assay.AssayDataProvider; import org.labkey.api.laboratory.assay.SimpleAssayDataProvider; +import org.labkey.api.laboratory.query.TabbedReportFilterProvider; import org.labkey.api.ldk.table.ButtonConfigFactory; import org.labkey.api.module.Module; import org.labkey.api.module.ModuleLoader; @@ -85,6 +86,7 @@ public class LaboratoryServiceImpl extends LaboratoryService private final Map _dataProviders = new HashMap<>(); private final Map>>>> _tableCustomizers = new CaseInsensitiveHashMap<>(); private final List _demographicsProviders = new ArrayList<>(); + private final List _tabbedReportFilterProviders = new ArrayList<>(); public static final String DEMOGRAPHICS_PROPERTY_CATEGORY = "laboratory.demographicsSource"; public static final String DATASOURCE_PROPERTY_CATEGORY = "laboratory.additionalDataSource"; @@ -719,4 +721,15 @@ public void clearDataProviderCache() { LaboratoryDataProvider.clearCache(); } + + @Override + public void registerTabbedReportFilterProvider(TabbedReportFilterProvider provider) + { + _tabbedReportFilterProviders.add(provider); + } + + public List getTabbedReportFilterProviderProviders(final Container c, final User u) + { + return _tabbedReportFilterProviders.stream().filter(d -> d.isAvailable(c, u)).toList(); + } } From c39e17f0bdb670793721dc816b846e7ff1b992c7 Mon Sep 17 00:00:00 2001 From: bbimber Date: Wed, 25 Jun 2025 09:39:34 -0700 Subject: [PATCH 8/9] Refactor TabbedReport to allow a distributed provider model (#49) * Refactor TabbedReport to allow a distributed provider model * Allow admin to turn tabbed report filter types on/off --- .../api/laboratory/LaboratoryService.java | 2 + .../api/laboratory/QueryTabbedReportItem.java | 3 +- .../api/laboratory/TabbedReportItem.java | 58 ++++--------- .../query/TabbedReportFilterProvider.java | 55 ++++++++++++- .../laboratory/panel/ItemVisibilityPanel.js | 45 +++++++++- .../web/laboratory/panel/ProjectFilterType.js | 8 +- .../laboratory/LaboratoryController.java | 40 ++++++--- .../labkey/laboratory/LaboratoryModule.java | 6 +- .../laboratory/LaboratoryServiceImpl.java | 1 + .../query/ProjectGroupFilterProvider.java | 82 +++++++++++++++++++ .../labkey/laboratory/view/dataBrowser.jsp | 34 ++++++-- 11 files changed, 263 insertions(+), 71 deletions(-) create mode 100644 laboratory/src/org/labkey/laboratory/query/ProjectGroupFilterProvider.java diff --git a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java index 48fd1dea..636a6340 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java +++ b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java @@ -128,6 +128,8 @@ static public void setInstance(LaboratoryService instance) abstract public void registerTabbedReportFilterProvider(TabbedReportFilterProvider provider); + abstract public List getTabbedReportFilterProviderProviders(final Container c, final User u); + public static enum NavItemCategory { samples(), diff --git a/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java b/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java index 11a0a883..dc84f851 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java +++ b/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java @@ -88,14 +88,13 @@ public JSONObject toJSON(Container c, User u) return null; } - inferColumnsFromTable(ti); + inferColumnsFromTable(ti, c, u); JSONObject json = super.toJSON(c, u); json.put("schemaName", getSchemaName()); json.put("queryName", getQueryName()); String viewName = getDefaultViewName(c, getOwnerKey()); - // TODO: should we always override here? if (getViewName() != null) { viewName = getViewName(); diff --git a/laboratory/api-src/org/labkey/api/laboratory/TabbedReportItem.java b/laboratory/api-src/org/labkey/api/laboratory/TabbedReportItem.java index 96507dfe..8859393f 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/TabbedReportItem.java +++ b/laboratory/api-src/org/labkey/api/laboratory/TabbedReportItem.java @@ -17,16 +17,16 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.json.JSONArray; import org.json.JSONObject; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.Container; import org.labkey.api.data.PropertyManager; import org.labkey.api.data.TableInfo; +import org.labkey.api.laboratory.query.TabbedReportFilterProvider; import org.labkey.api.query.FieldKey; import org.labkey.api.security.User; -import org.labkey.api.util.PageFlowUtil; +import java.util.HashMap; import java.util.Map; /** @@ -43,10 +43,11 @@ public class TabbedReportItem extends AbstractNavItem protected FieldKey _subjectIdFieldKey = null; protected FieldKey _sampleDateFieldKey = null; - protected FieldKey _overlappingProjectsFieldKey = null; - protected FieldKey _allProjectsFieldKey = null; + private final Map _additionalKeys = new HashMap<>(); public static final String OVERRIDES_PROP_KEY = "laboratory.tabItemOverride"; + public static final String FILTER_PROP_KEY = "laboratory.tabItemFilterOverride"; + protected static final Logger _log = LogManager.getLogger(TabbedReportItem.class); public TabbedReportItem(DataProvider provider, String name, String label, String reportCategory) @@ -94,24 +95,18 @@ public JSONObject toJSON(Container c, User u) if (_sampleDateFieldKey != null) json.put("dateFieldName", _sampleDateFieldKey); - if (_overlappingProjectsFieldKey != null) - { - json.put("overlappingProjectsFieldName", _overlappingProjectsFieldKey.toString()); - json.put("overlappingProjectsFieldKeyArray", new JSONArray(_overlappingProjectsFieldKey.getParts())); - } - - if (_allProjectsFieldKey != null) - { - json.put("allProjectsFieldName", _allProjectsFieldKey.toString()); - json.put("allProjectsFieldKeyArray", new JSONArray(_allProjectsFieldKey.getParts())); - } + JSONObject keys = new JSONObject(); + _additionalKeys.forEach((name, fk) -> { + keys.put(name, fk.toString()); + }); + json.put("additionalFieldKeys", keys); json.put("reportType", getReportType()); return json; } - protected void inferColumnsFromTable(TableInfo ti) + protected void inferColumnsFromTable(TableInfo ti, Container c, User u) { for (ColumnInfo ci : ti.getColumns()) { @@ -125,17 +120,9 @@ else if (_sampleDateFieldKey == null && LaboratoryService.SAMPLEDATE_CONCEPT_URI } } - if (_overlappingProjectsFieldKey == null || _allProjectsFieldKey == null) + for (TabbedReportFilterProvider p : LaboratoryService.get().getTabbedReportFilterProviderProviders(c, u)) { - FieldKey overlapKey = FieldKey.fromString("overlappingProjectsPivot"); - FieldKey allKey = FieldKey.fromString("allProjectsPivot"); - - Map colMap = _queryCache.getColumns(ti, PageFlowUtil.set(overlapKey, allKey)); - if (_overlappingProjectsFieldKey == null && colMap.containsKey(overlapKey)) - _overlappingProjectsFieldKey = colMap.get(overlapKey).getFieldKey(); - - if (_allProjectsFieldKey == null && colMap.containsKey(allKey)) - _allProjectsFieldKey = colMap.get(allKey).getFieldKey(); + _additionalKeys.putAll(p.getAdditionalFieldKeys(ti, this, _additionalKeys)); } } @@ -169,24 +156,9 @@ public void setSampleDateFieldKey(FieldKey sampleDateFieldKey) _sampleDateFieldKey = sampleDateFieldKey; } - public FieldKey getOverlappingProjectsFieldKey() - { - return _overlappingProjectsFieldKey; - } - - public void setOverlappingProjectsFieldKey(FieldKey overlappingProjectsFieldKey) - { - _overlappingProjectsFieldKey = overlappingProjectsFieldKey; - } - - public FieldKey getAllProjectsFieldKey() - { - return _allProjectsFieldKey; - } - - public void setAllProjectsFieldKey(FieldKey allProjectsFieldKey) + public void setKeyOverride(String name, FieldKey key) { - _allProjectsFieldKey = allProjectsFieldKey; + _additionalKeys.put(name, key); } public void setVisible(boolean visible) diff --git a/laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java b/laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java index a47118b6..e5a5f006 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java +++ b/laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java @@ -1,14 +1,30 @@ package org.labkey.api.laboratory.query; +import org.jetbrains.annotations.NotNull; +import org.json.JSONObject; +import org.labkey.api.collections.CaseInsensitiveHashMap; import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerType; +import org.labkey.api.data.PropertyManager; +import org.labkey.api.data.TableInfo; +import org.labkey.api.laboratory.NavItem; +import org.labkey.api.laboratory.TabbedReportItem; +import org.labkey.api.module.Module; +import org.labkey.api.query.FieldKey; import org.labkey.api.security.User; import org.labkey.api.view.template.ClientDependency; import java.util.Collection; +import java.util.Map; public interface TabbedReportFilterProvider { - boolean isAvailable(Container c, User u); + default boolean isAvailable(Container c, User u) + { + return c.getActiveModules().contains(getOwningModule()); + } + + Module getOwningModule(); Collection getClientDependencies(); @@ -17,4 +33,41 @@ public interface TabbedReportFilterProvider String getLabel(); String getInputValue(); + + default JSONObject toJSON(Container c, User u) + { + JSONObject ret = new JSONObject(); + ret.put("xtype", getXType()); + ret.put("label", getLabel()); + ret.put("inputValue", getInputValue()); + ret.put("isAvailable", isAvailable(c, u)); + ret.put("isVisible", isVisible(c, u)); + ret.put("key", getPropertyManagerKey()); + + return ret; + } + + default boolean isVisible(Container c, User u) + { + Container targetContainer = c.getContainerFor(ContainerType.DataType.navVisibility); + if (getOwningModule() != null) + { + if (!targetContainer.getActiveModules().contains(getOwningModule())) + return false; + } + + Map map = new CaseInsensitiveHashMap<>(PropertyManager.getProperties(targetContainer, NavItem.PROPERTY_CATEGORY)); + if (map.containsKey(getPropertyManagerKey())) + return Boolean.parseBoolean(map.get(getPropertyManagerKey())); + + return true; + } + + default String getPropertyManagerKey() + { + return "tabReportFilterProvider||" + getClass().getSimpleName() + "||" + getLabel(); + } + + @NotNull + Map getAdditionalFieldKeys(TableInfo ti, TabbedReportItem tri, Map overrides); } diff --git a/laboratory/resources/web/laboratory/panel/ItemVisibilityPanel.js b/laboratory/resources/web/laboratory/panel/ItemVisibilityPanel.js index 9cd65920..54134a13 100644 --- a/laboratory/resources/web/laboratory/panel/ItemVisibilityPanel.js +++ b/laboratory/resources/web/laboratory/panel/ItemVisibilityPanel.js @@ -50,16 +50,55 @@ Ext4.define('Laboratory.panel.ItemVisibilityPanel', { this.results = results; for (var i in Laboratory.ITEM_CATEGORY){ - if (Laboratory.ITEM_CATEGORY[i].name == Laboratory.ITEM_CATEGORY.settings.name) + if (Laboratory.ITEM_CATEGORY[i].name === Laboratory.ITEM_CATEGORY.settings.name) continue; this.renderSection(Laboratory.ITEM_CATEGORY[i].name, Laboratory.ITEM_CATEGORY[i].label); } + + if (this.results.tabbedReportFilterProviderProviders) { + this.renderTabbedReportFilterProviderProviders(this.results.tabbedReportFilterProviderProviders) + } + }, + + renderTabbedReportFilterProviderProviders: function(items) { + this.remove(this.down('#loading')); + + var cfg = { + xtype: 'container', + border: false, + defaults: { + border: false + }, + style: 'margin-bottom: 20px;', + itemCategory: 'tabbedReportFilterTypes', + items: [{ + html: 'Tabbed Report Filter Types', + style: 'padding-bottom: 5px;' + }] + } + + var sectionItems = []; + Ext4.each(items, function(item){ + sectionItems.push({ + xtype: 'checkbox', + width: 800, + style: 'margin-left: 5px;', + navItem: item, + boxLabel: item.label, + checked: item.isVisible, + itemCategory: 'tabbedReportFilterTypes' + }); + }, this); + + sectionItems = LDK.Utils.sortByProperty(sectionItems, 'boxLabel'); + cfg.items = cfg.items.concat(sectionItems); + this.add(cfg); }, renderSection: function(name, label){ var items = this.results[name]; - var showReportCategory = name == 'tabbedReports'; + var showReportCategory = name === 'tabbedReports'; this.remove(this.down('#loading')); @@ -92,7 +131,7 @@ Ext4.define('Laboratory.panel.ItemVisibilityPanel', { //if items are marked as children of this item, toggle their visibility. //this is primarily used for reports field.up('form').getForm().getFields().each(function(item){ - if (item.navItem && item.navItem.ownerKey == field.navItem.key){ + if (item.navItem && item.navItem.ownerKey === field.navItem.key){ item.setValue(val); } }, this); diff --git a/laboratory/resources/web/laboratory/panel/ProjectFilterType.js b/laboratory/resources/web/laboratory/panel/ProjectFilterType.js index 43250fa0..41ba1ce1 100644 --- a/laboratory/resources/web/laboratory/panel/ProjectFilterType.js +++ b/laboratory/resources/web/laboratory/panel/ProjectFilterType.js @@ -83,10 +83,10 @@ Ext4.define('Laboratory.panel.ProjectFilterType', { var filters = this.getFilters(); var report = tab.report; - var projectFieldName = (filters.projectFilterMode === 'overlappingProjects') ? report.overlappingProjectsFieldName : report.allProjectsFieldName; + var projectFieldName = (filters.projectFilterMode === 'overlappingProjects') ? report.additionalFieldKeys?.overlappingProjectsFieldKey : report.additionalFieldKeys?.allProjectsFieldKey; if (!projectFieldName){ - if (filters.projectFilterMode === 'overlappingProjects' && !report.overlappingProjectsFieldName){ - projectFieldName = report.allProjectsFieldName; + if (filters.projectFilterMode === 'overlappingProjects' && !report.additionalFieldKeys?.overlappingProjectsFieldKey){ + projectFieldName = report.additionalFieldKeys?.allProjectsFieldKey; if (projectFieldName) Ext4.Msg.alert('Warning', 'This reports supports project filtering, but cannot filter by overlapping projects, since it lacks a properly configured date field. All animals assigned to the project will be shown'); @@ -125,7 +125,7 @@ Ext4.define('Laboratory.panel.ProjectFilterType', { }, validateReportForFilterType: function(report){ - if (!report.allProjectsFieldName){ + if (!report.additionalFieldKeys?.allProjectsFieldKey){ return 'This report cannot be used with the selected filter type, because the report does not contain a field with project information'; } diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryController.java b/laboratory/src/org/labkey/laboratory/LaboratoryController.java index 67501674..9dcd76f8 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryController.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryController.java @@ -59,6 +59,7 @@ import org.labkey.api.laboratory.assay.AssayImportMethod; import org.labkey.api.laboratory.assay.AssayParser; import org.labkey.api.laboratory.query.ContainerIncrementingTable; +import org.labkey.api.laboratory.query.TabbedReportFilterProvider; import org.labkey.api.laboratory.security.LaboratoryAdminPermission; import org.labkey.api.module.Module; import org.labkey.api.module.ModuleHtmlView; @@ -1567,22 +1568,29 @@ public ApiResponse execute(JsonDataForm form, BindException errors) for (String key : json.keySet()) { String providerName = AbstractNavItem.inferDataProviderNameFromKey(key); - DataProvider provider = LaboratoryService.get().getDataProvider(providerName); - - //for some types, no DataProvider, was explicitly registered, such as many assays - //in these cases we cannot infer the owning module. - if (provider != null && provider.getOwningModule() != null) + if ("tabReportFilterProvider".equals(providerName)) + { + // Ignore + } + else { - if (!activeModules.contains(provider.getOwningModule())) + DataProvider provider = LaboratoryService.get().getDataProvider(providerName); + + //for some types, no DataProvider, was explicitly registered, such as many assays + //in these cases we cannot infer the owning module. + if (provider != null && provider.getOwningModule() != null) { - toActivate.add(provider.getOwningModule()); + if (!activeModules.contains(provider.getOwningModule())) + { + toActivate.add(provider.getOwningModule()); + } } } map.put(key, json.get(key) == null ? null : String.valueOf(json.get(key))); } - if (toActivate.size() > 0) + if (!toActivate.isEmpty()) { toActivate.addAll(activeModules); getContainer().setActiveModules(toActivate); @@ -1819,6 +1827,16 @@ public ApiResponse execute(GetDataItemsForm form, BindException errors) results.put(LaboratoryService.NavItemCategory.misc.name(), json); } + List json = new ArrayList<>(); + for (TabbedReportFilterProvider item : LaboratoryService.get().getTabbedReportFilterProviderProviders(getContainer(), getUser())) + { + if (form.isIncludeAll() || item.isAvailable(getContainer(), getUser())) + { + json.add(item.toJSON(getContainer(), getUser())); + } + } + results.put("tabbedReportFilterProviderProviders", json); + results.put("success", true); return new ApiSimpleResponse(results); @@ -2347,8 +2365,10 @@ public ModelAndView getView(Object form, BindException errors) throws Exception JspView view = new JspView<>("/org/labkey/laboratory/view/dataBrowser.jsp", form); view.setTitle("Data Browser"); view.setHidePageTitle(true); - //view.setFrame(WebPartView.FrameType.NONE); - //getPageConfig().setTemplate(PageConfig.Template.None); + + LaboratoryServiceImpl.get().getTabbedReportFilterProviderProviders(getContainer(), getUser()).forEach(p -> { + p.getClientDependencies().forEach(view::addClientDependency); + }); return view; } diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryModule.java b/laboratory/src/org/labkey/laboratory/LaboratoryModule.java index 1721e500..6cb8a7d8 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryModule.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryModule.java @@ -49,6 +49,7 @@ import org.labkey.api.writer.ContainerUser; import org.labkey.laboratory.notification.LabSummaryNotification; import org.labkey.laboratory.query.LaboratoryDemographicsProvider; +import org.labkey.laboratory.query.ProjectGroupFilterProvider; import org.labkey.laboratory.query.WorkbookModel; import org.labkey.laboratory.security.LaboratoryAdminRole; @@ -119,7 +120,9 @@ public WebPartView getWebPartView(@NotNull ViewContext portalCtx, @NotNull Porta { JspView view = new JspView<>("/org/labkey/laboratory/view/dataBrowser.jsp", new Object()); view.setTitle("Laboratory Data Browser"); - //view.setFrame(WebPartView.FrameType.NONE); + LaboratoryServiceImpl.get().getTabbedReportFilterProviderProviders(portalCtx.getContainer(), portalCtx.getUser()).forEach(p -> { + p.getClientDependencies().forEach(view::addClientDependency); + }); if (portalCtx.getContainer().hasPermission(portalCtx.getUser(), LaboratoryAdminPermission.class) || portalCtx.getContainer().hasPermission(portalCtx.getUser(), AdminPermission.class)) { @@ -164,6 +167,7 @@ protected void doStartupAfterSpringConfig(ModuleContext moduleContext) LaboratoryService.get().registerDataProvider(new SampleTypeDataProvider()); LaboratoryService.get().registerDataProvider(new ExtraDataSourcesDataProvider(this)); LaboratoryService.get().registerDemographicsProvider(new LaboratoryDemographicsProvider()); + LaboratoryService.get().registerTabbedReportFilterProvider(new ProjectGroupFilterProvider()); DetailsURL details = DetailsURL.fromString("/laboratory/siteLabSettings.view"); details.setContainerContext(ContainerManager.getSharedContainer()); diff --git a/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java b/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java index 747ae62e..3a5144bc 100644 --- a/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java +++ b/laboratory/src/org/labkey/laboratory/LaboratoryServiceImpl.java @@ -728,6 +728,7 @@ public void registerTabbedReportFilterProvider(TabbedReportFilterProvider provid _tabbedReportFilterProviders.add(provider); } + @Override public List getTabbedReportFilterProviderProviders(final Container c, final User u) { return _tabbedReportFilterProviders.stream().filter(d -> d.isAvailable(c, u)).toList(); diff --git a/laboratory/src/org/labkey/laboratory/query/ProjectGroupFilterProvider.java b/laboratory/src/org/labkey/laboratory/query/ProjectGroupFilterProvider.java new file mode 100644 index 00000000..918bc617 --- /dev/null +++ b/laboratory/src/org/labkey/laboratory/query/ProjectGroupFilterProvider.java @@ -0,0 +1,82 @@ +package org.labkey.laboratory.query; + +import org.jetbrains.annotations.NotNull; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.TableInfo; +import org.labkey.api.laboratory.TabbedReportItem; +import org.labkey.api.laboratory.query.TabbedReportFilterProvider; +import org.labkey.api.module.Module; +import org.labkey.api.module.ModuleLoader; +import org.labkey.api.query.FieldKey; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.view.template.ClientDependency; +import org.labkey.laboratory.LaboratoryModule; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ProjectGroupFilterProvider implements TabbedReportFilterProvider +{ + @Override + public Module getOwningModule() + { + return ModuleLoader.getInstance().getModule(LaboratoryModule.class); + } + + @Override + public Collection getClientDependencies() + { + return List.of(ClientDependency.fromPath("laboratory/panel/ProjectFilterType.js")); + } + + @Override + public String getXType() + { + return "laboratory-projectfiltertype"; + } + + @Override + public String getLabel() + { + return "Subject Groups"; + } + + @Override + public String getInputValue() + { + return "projects"; + } + + @Override + public @NotNull Map getAdditionalFieldKeys(TableInfo ti, TabbedReportItem tri, Map overrides) + { + Map ret = new HashMap<>(); + FieldKey parent = tri.getSubjectIdFieldKey(); + if (parent != null) + { + parent = parent.getParent(); + } + + if (overrides.get("overlappingProjectsFieldKey") == null) + { + FieldKey overlapKey = FieldKey.fromString(parent, "overlappingProjectsPivot"); + + Map colMap = tri.getQueryCache().getColumns(ti, PageFlowUtil.set(overlapKey)); + if (colMap.containsKey(overlapKey)) + ret.put("overlappingProjectsFieldKey", colMap.get(overlapKey).getFieldKey()); + } + + if (overrides.get("allProjectsFieldKey") == null) + { + FieldKey allKey = FieldKey.fromString(parent, "allProjectsPivot"); + + Map colMap = tri.getQueryCache().getColumns(ti, PageFlowUtil.set(allKey)); + if (colMap.containsKey(allKey)) + ret.put("allProjectsFieldKey", colMap.get(allKey).getFieldKey()); + } + + return ret; + } +} diff --git a/laboratory/src/org/labkey/laboratory/view/dataBrowser.jsp b/laboratory/src/org/labkey/laboratory/view/dataBrowser.jsp index e059779f..70beda3c 100644 --- a/laboratory/src/org/labkey/laboratory/view/dataBrowser.jsp +++ b/laboratory/src/org/labkey/laboratory/view/dataBrowser.jsp @@ -15,20 +15,44 @@ * limitations under the License. */ %> +<%@ page import="org.json.JSONObject" %> +<%@ page import="org.labkey.api.laboratory.query.TabbedReportFilterProvider" %> <%@ page import="org.labkey.api.view.HttpView" %> <%@ page import="org.labkey.api.view.JspView" %> <%@ page import="org.labkey.api.view.template.ClientDependencies" %> +<%@ page import="org.labkey.laboratory.LaboratoryServiceImpl" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%! @Override public void addClientDependencies(ClientDependencies dependencies) { dependencies.add("laboratory.context"); - dependencies.add("laboratory/panel/ProjectFilterType.js"); + } + + private String getFilterConfig() + { + StringBuilder ret = new StringBuilder(); + + for (TabbedReportFilterProvider p : LaboratoryServiceImpl.get().getTabbedReportFilterProviderProviders(getContainer(), getUser())) + { + if (!p.isVisible(getContainer(), getUser())) + { + continue; + } + + JSONObject config = new JSONObject(); + config.put("xtype", h(p.getXType())); + config.put("inputValue", h(p.getInputValue())); + config.put("label", h(p.getLabel())); + + ret.append(config).append(","); + } + + return ret.toString(); } %> <% - JspView me = (JspView) HttpView.currentView(); + JspView me = HttpView.currentView(); String wpId = "wp_" + me.getWebPartRowId(); %> @@ -90,11 +114,7 @@ xtype: 'ldk-multisubjectfiltertype', inputValue: LDK.panel.MultiSubjectFilterType.filterName, label: LDK.panel.MultiSubjectFilterType.label - },{ - xtype: 'laboratory-projectfiltertype', - inputValue: Laboratory.panel.ProjectFilterType.filterName, - label: Laboratory.panel.ProjectFilterType.label - },{ + },<%=unsafe(getFilterConfig())%>{ xtype: 'ldk-nofiltersfiltertype', inputValue: LDK.panel.NoFiltersFilterType.filterName, label: LDK.panel.NoFiltersFilterType.label From 67766a2f79e50e0e538b5eb2b0d878636ceca548 Mon Sep 17 00:00:00 2001 From: bbimber Date: Mon, 30 Jun 2025 10:15:48 -0700 Subject: [PATCH 9/9] Update LaboratoryService.java --- .../api-src/org/labkey/api/laboratory/LaboratoryService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java index 39f0bf54..636a6340 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java +++ b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java @@ -1,4 +1,4 @@ -cd L/* +/* * Copyright (c) 2012-2018 LabKey Corporation * * Licensed under the Apache License, Version 2.0 (the "License");