diff --git a/LDK/src/org/labkey/ldk/LDKController.java b/LDK/src/org/labkey/ldk/LDKController.java index bfa75aec..7fb264d3 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 static class RedirectStartAction extends SimpleViewAction { diff --git a/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java b/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java index 8cb25982..c49ec53d 100644 --- a/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java +++ b/LDK/src/org/labkey/ldk/query/DefaultTableCustomizer.java @@ -125,7 +125,24 @@ private void setDetailsUrl(AbstractTableInfo ti) List keyFields = ti.getPkColumnNames(); assert !keyFields.isEmpty() : "No key fields found for the table: " + ti.getPublicSchemaName() + "." + ti.getPublicName(); - if (keyFields.size() != 1) + + 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 = Collections.singletonList(alternatePK); + } + + 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; @@ -159,13 +176,30 @@ else if (_settings.isSetEditLinkOverrides()) List keyFields = ti.getPkColumnNames(); assert !keyFields.isEmpty() : "No key fields found for the table: " + ti.getPublicSchemaName() + "." + ti.getPublicName(); - if (keyFields.size() != 1) + + 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 = Collections.singletonList(alternatePK); + } + + 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()))) @@ -444,7 +478,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; @@ -530,6 +565,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); diff --git a/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java b/laboratory/api-src/org/labkey/api/laboratory/LaboratoryService.java index b12ca2dc..636a6340 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; @@ -123,7 +124,13 @@ static public void setInstance(LaboratoryService instance) abstract public @Nullable DemographicsProvider getDemographicsProviderByName(Container c, User u, String name); - public enum NavItemCategory + abstract public void clearDataProviderCache(); + + abstract public void registerTabbedReportFilterProvider(TabbedReportFilterProvider provider); + + abstract public List getTabbedReportFilterProviderProviders(final Container c, final User u); + + public static enum NavItemCategory { samples(), misc(), diff --git a/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java b/laboratory/api-src/org/labkey/api/laboratory/QueryTabbedReportItem.java index e2bbe1cc..dc84f851 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) { @@ -77,12 +88,18 @@ 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()); + + if (getViewName() != null) + { + viewName = getViewName(); + } + if (viewName != null) { json.put("viewName", viewName); 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 new file mode 100644 index 00000000..e5a5f006 --- /dev/null +++ b/laboratory/api-src/org/labkey/api/laboratory/query/TabbedReportFilterProvider.java @@ -0,0 +1,73 @@ +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 +{ + default boolean isAvailable(Container c, User u) + { + return c.getActiveModules().contains(getOwningModule()); + } + + Module getOwningModule(); + + Collection getClientDependencies(); + + String getXType(); + + 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/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/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 71c55b96..643a9dc6 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; @@ -294,7 +295,6 @@ public boolean handlePost(EnsureAssayFieldsForm form, BindException errors) thro } } - @RequiresPermission(AdminPermission.class) public static class SetTableIncrementValueAction extends ConfirmAction { @@ -578,7 +578,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 @@ -1568,15 +1568,22 @@ 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()); + } } } @@ -1792,7 +1799,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); } @@ -1810,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); @@ -2338,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/LaboratoryDataProvider.java b/laboratory/src/org/labkey/laboratory/LaboratoryDataProvider.java index c66c1f92..38ed1dae 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; @@ -50,6 +57,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -62,11 +70,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() { @@ -310,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)); @@ -332,6 +357,236 @@ 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()) + { + return null; + } + + return(m.toNavItem(c, u, cache, owner, this)); + }).filter(Objects::nonNull).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) + { + _log.error("Missing either category, schema or query for laboratory.reports item: " + getContainerPath() + ", rowId: " + getRowId()); + 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/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/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..3a5144bc 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"; @@ -713,4 +715,22 @@ public List getDemographicsProviders(final Container c, fi return null; } + + @Override + public void clearDataProviderCache() + { + LaboratoryDataProvider.clearCache(); + } + + @Override + public void registerTabbedReportFilterProvider(TabbedReportFilterProvider provider) + { + _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 3eb3d893..70beda3c 100644 --- a/laboratory/src/org/labkey/laboratory/view/dataBrowser.jsp +++ b/laboratory/src/org/labkey/laboratory/view/dataBrowser.jsp @@ -15,16 +15,40 @@ * 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(); } %> <% @@ -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