diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 461b2a0e..5816605b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,6 @@ name: Build DISCVR on: + workflow_dispatch: push: pull_request: jobs: diff --git a/LDK/api-src/org/labkey/api/ldk/table/ContainerScopedTable.java b/LDK/api-src/org/labkey/api/ldk/table/ContainerScopedTable.java index 9a946cfe..d1145f73 100644 --- a/LDK/api-src/org/labkey/api/ldk/table/ContainerScopedTable.java +++ b/LDK/api-src/org/labkey/api/ldk/table/ContainerScopedTable.java @@ -287,7 +287,8 @@ public Object call() Object pesudoPkVal = it.getInputColumnValue(inputColMap.get(_pseudoPk)); if (pesudoPkVal != null) { - if (_context.getInsertOption() != QueryUpdateService.InsertOption.MERGE && keyManager.rowExists(c, pesudoPkVal)) + // NOTE: this code is called for both inserts and updates: + if (_context.getInsertOption() == QueryUpdateService.InsertOption.INSERT && keyManager.rowExists(c, pesudoPkVal)) { _context.getErrors().addRowError(new ValidationException("A record is already present with value: " + pesudoPkVal)); } diff --git a/LDK/src/org/labkey/ldk/LDKController.java b/LDK/src/org/labkey/ldk/LDKController.java index 3f40d163..168ac090 100644 --- a/LDK/src/org/labkey/ldk/LDKController.java +++ b/LDK/src/org/labkey/ldk/LDKController.java @@ -847,14 +847,10 @@ public ModelAndView getView(QueryForm form, BindException errors) throws Excepti if (keyField != null) { + // Note: the ContainerContext will need to be set within QueryView DetailsURL importUrl = DetailsURL.fromString("/query/importData.view?schemaName=" + schemaName + "&query.queryName=" + queryName + "&keyField=" + keyField); - importUrl.setContainerContext(getContainer()); - DetailsURL updateUrl = DetailsURL.fromString("/ldk/manageRecord.view?schemaName=" + schemaName + "&query.queryName=" + queryName + "&keyField=" + keyField + "&key=${" + keyField + "}"); - updateUrl.setContainerContext(getContainer()); - DetailsURL deleteUrl = DetailsURL.fromString("/query/deleteQueryRows.view?schemaName=" + schemaName + "&query.queryName=" + queryName); - deleteUrl.setContainerContext(getContainer()); url.addParameter("importURL", importUrl.toString()); url.addParameter("updateURL", updateUrl.toString()); @@ -866,7 +862,7 @@ public ModelAndView getView(QueryForm form, BindException errors) throws Excepti url.addParameter("queryName", queryName); url.addParameter("allowChooseQuery", false); - WebPartFactory factory = Portal.getPortalPartCaseInsensitive("Query"); + WebPartFactory factory = Portal.getPortalPart("Query"); Portal.WebPart part = factory.createWebPart(); part.setProperties(url.getQueryString()); diff --git a/LDK/test/src/org/labkey/test/tests/external/labModules/LabModulesTest.java b/LDK/test/src/org/labkey/test/tests/external/labModules/LabModulesTest.java index 23cdbdf6..59f673a4 100644 --- a/LDK/test/src/org/labkey/test/tests/external/labModules/LabModulesTest.java +++ b/LDK/test/src/org/labkey/test/tests/external/labModules/LabModulesTest.java @@ -16,12 +16,15 @@ package org.labkey.test.tests.external.labModules; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.labkey.api.util.DateUtil; import org.labkey.remoteapi.CommandException; import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.collections.CaseInsensitiveHashMap; @@ -1415,6 +1418,7 @@ private void samplesTableTest() throws Exception columnLabels.add(getColumnLabel(srr, name)); } + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); List> rows = new ArrayList<>(); for (Map row : srr.getRows()) { @@ -1425,7 +1429,7 @@ private void samplesTableTest() throws Exception String val = row.get(name) == null ? "" : String.valueOf(row.get(name)); if (name.toLowerCase().contains("date")) { - val = StringUtils.isEmpty(val) ? "" : ExcelHelper.getDateTimeFormat().format(new Date(val)); + val = StringUtils.isEmpty(val) ? "" : dateFormat.format(Date.parse(val)); } target.add(val); @@ -1440,10 +1444,10 @@ private void samplesTableTest() throws Exception List> lines = ExcelHelper.getFirstNRows(sheet, 5); Assert.assertEquals(columnLabels, lines.get(0)); - Assert.assertEquals(rows.get(0), lines.get(1)); - Assert.assertEquals(rows.get(0), lines.get(2)); - Assert.assertEquals(rows.get(1), lines.get(3)); - Assert.assertEquals(rows.get(1), lines.get(4)); + Assert.assertEquals("Row did not match. ExcelHelper pattern: " + ExcelHelper.getDateTimeFormat().toPattern(), rows.get(0), lines.get(1)); + Assert.assertEquals("Row did not match. ExcelHelper pattern: " + ExcelHelper.getDateTimeFormat().toPattern(), rows.get(0), lines.get(2)); + Assert.assertEquals("Row did not match. ExcelHelper pattern: " + ExcelHelper.getDateTimeFormat().toPattern(), rows.get(1), lines.get(3)); + Assert.assertEquals("Row did not match. ExcelHelper pattern: " + ExcelHelper.getDateTimeFormat().toPattern(), rows.get(1), lines.get(4)); } refresh(); diff --git a/laboratory/api-src/org/labkey/api/laboratory/query/ContainerIncrementingTable.java b/laboratory/api-src/org/labkey/api/laboratory/query/ContainerIncrementingTable.java index bc3e7843..0845cbc6 100644 --- a/laboratory/api-src/org/labkey/api/laboratory/query/ContainerIncrementingTable.java +++ b/laboratory/api-src/org/labkey/api/laboratory/query/ContainerIncrementingTable.java @@ -59,7 +59,7 @@ public ContainerIncrementingTable(UserSchema us, TableInfo st, ContainerFilter c } @Override - public SimpleUserSchema.SimpleTable init() + public SimpleUserSchema.SimpleTable init() { super.init(); diff --git a/laboratory/src/org/labkey/laboratory/query/LaboratoryTableCustomizer.java b/laboratory/src/org/labkey/laboratory/query/LaboratoryTableCustomizer.java index d37f31e2..fbbad913 100644 --- a/laboratory/src/org/labkey/laboratory/query/LaboratoryTableCustomizer.java +++ b/laboratory/src/org/labkey/laboratory/query/LaboratoryTableCustomizer.java @@ -7,6 +7,7 @@ import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; import org.labkey.api.data.AbstractTableInfo; +import org.labkey.api.data.BaseColumnInfo; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerFilter; @@ -18,7 +19,6 @@ import org.labkey.api.data.SQLFragment; import org.labkey.api.data.TableCustomizer; import org.labkey.api.data.TableInfo; -import org.labkey.api.data.WrappedColumn; import org.labkey.api.gwt.client.FacetingBehaviorType; import org.labkey.api.laboratory.LaboratoryService; import org.labkey.api.ldk.LDKService; @@ -183,13 +183,17 @@ public void customizeColumns(AbstractTableInfo ti) { container.setHidden(true); - WrappedColumn wrappedContainer = new WrappedColumn(container, "workbook"); + ExprColumn wrappedContainer = new ExprColumn(ti, FieldKey.fromString("workbook"), container.getValueSql(ExprColumn.STR_TABLE_ALIAS), container.getJdbcType(), container); wrappedContainer.setLabel("Workbook"); + wrappedContainer.setName("workbook"); + wrappedContainer.setCalculated(true); + wrappedContainer.setShownInInsertView(false); + wrappedContainer.setShownInUpdateView(false); wrappedContainer.setFk(QueryForeignKey .from(ti.getUserSchema(), ti.getContainerFilter()) .schema(us) .to("workbooks", LaboratoryWorkbooksTable.WORKBOOK_COL, "workbookId")); - wrappedContainer.setURL(DetailsURL.fromString("/project/start.view")); + wrappedContainer.setURL(DetailsURL.fromString("/project/begin.view")); wrappedContainer.setShownInDetailsView(true); wrappedContainer.setFacetingBehaviorType(FacetingBehaviorType.ALWAYS_OFF); wrappedContainer.setDisplayColumnFactory(new DisplayColumnFactory() @@ -273,8 +277,8 @@ private void appendDemographicsCols(final UserSchema us, AbstractTableInfo ti, C { for (final DemographicsSource qd : qds) { - Container targetContainer = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); - TableInfo target = qd.getTableInfo(targetContainer, us.getUser()); + Container parentContainer = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); + TableInfo target = qd.getTableInfo(parentContainer, us.getUser()); //TODO: push this into LaboratoryService and also cache them? if (target != null) @@ -286,17 +290,20 @@ private void appendDemographicsCols(final UserSchema us, AbstractTableInfo ti, C } String name = ColumnInfo.legalNameFromName(qd.getLabel()); - if (ti.getColumn(name) != null) continue; - WrappedColumn col = new WrappedColumn(subjectCol, name); - col.setLabel(qd.getLabel()); + ExprColumn col = new ExprColumn(ti, FieldKey.fromString(name), subjectCol.getValueSql(ExprColumn.STR_TABLE_ALIAS), JdbcType.INTEGER, subjectCol); + col.setName(name); + col.setCalculated(true); + col.setShownInInsertView(false); + col.setShownInUpdateView(false); col.setReadOnly(true); col.setIsUnselectable(true); col.setUserEditable(false); + col.setKeyField(false); - UserSchema targetSchema = qd.getTableInfo(targetContainer, us.getUser()).getUserSchema(); + final UserSchema targetSchema = target.getUserSchema(); col.setFk(new QueryForeignKey(us, ti.getContainerFilter(), targetSchema, null, qd.getQueryName(), qd.getTargetColumn(), qd.getTargetColumn()) { @Override @@ -366,12 +373,17 @@ private void appendMajorEventsCol(final UserSchema us, AbstractTableInfo ds, fin final String pkColSelectName = pk.getFieldKey().toSQLString(); final String pkColRawName = pk.getName(); - MutableColumnInfo col = new WrappedColumn(pk, name); + BaseColumnInfo col = new ExprColumn(ds, FieldKey.fromString(name), pk.getValueSql(ExprColumn.STR_TABLE_ALIAS), pk.getJdbcType(), pk); col.setLabel("Major Events"); col.setDescription("This column shows all major events recorded in this subject's history and will calculate the time elapsed between the current sample and these dates."); + col.setName(name); + col.setCalculated(true); + col.setShownInInsertView(false); + col.setShownInUpdateView(false); col.setReadOnly(true); col.setIsUnselectable(true); col.setUserEditable(false); + col.setKeyField(false); final String schemaName = ds.getUserSchema().getSchemaPath().toSQLString(); final String subjectSelectName = ds.getSqlDialect().makeLegalIdentifier(subjectColName); @@ -384,11 +396,10 @@ private void appendMajorEventsCol(final UserSchema us, AbstractTableInfo ds, fin @Override public TableInfo getLookupTableInfo() { - Container target = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); - UserSchema effectiveUs = us.getContainer().isWorkbookOrTab() ? QueryService.get().getUserSchema(us.getUser(), target, us.getSchemaPath()) : us; - QueryDefinition qd = QueryService.get().createQueryDef(us.getUser(), target, effectiveUs, colName); + Container parentContainer = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); + QueryDefinition qd = createQueryDef(us, colName); - qd.setSql(getMajorEventsSql(target, schemaName, querySelectName, pkColSelectName, subjectSelectName, dateSelectName)); + qd.setSql(getMajorEventsSql(parentContainer, schemaName, querySelectName, pkColSelectName, subjectSelectName, dateSelectName)); qd.setIsTemporary(true); List errors = new ArrayList<>(); @@ -444,21 +455,25 @@ private void appendOverlapingProjectsCol(final UserSchema us, AbstractTableInfo final String subjectSelectName = ds.getSqlDialect().makeLegalIdentifier(subjectColName); final String dateSelectName = dateColName == null ? null : ds.getSqlDialect().makeLegalIdentifier(dateColName); - WrappedColumn col = new WrappedColumn(pk, name); + BaseColumnInfo col = new ExprColumn(ds, FieldKey.fromString(name), pk.getValueSql(ExprColumn.STR_TABLE_ALIAS), pk.getJdbcType(), pk); col.setLabel("Overlapping Groups"); col.setDescription("This column shows all groups to which this subject belonged at the time of this sample."); + col.setName(name); + col.setCalculated(true); + col.setShownInInsertView(false); + col.setShownInUpdateView(false); col.setReadOnly(true); col.setIsUnselectable(true); col.setUserEditable(false); + col.setKeyField(false); col.setFk(new LookupForeignKey(){ @Override public TableInfo getLookupTableInfo() { - Container target = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); - UserSchema effectiveUs = us.getContainer().isWorkbookOrTab() ? QueryService.get().getUserSchema(us.getUser(), target, us.getSchemaPath()) : us; - QueryDefinition qd = QueryService.get().createQueryDef(us.getUser(), target, effectiveUs, colName); + Container parentContainer = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); + QueryDefinition qd = createQueryDef(us, colName); - qd.setSql(getOverlapSql(target, schemaName, querySelectName, pkColSelectName, subjectSelectName, dateSelectName)); + qd.setSql(getOverlapSql(parentContainer, schemaName, querySelectName, pkColSelectName, subjectSelectName, dateSelectName)); qd.setIsTemporary(true); List errors = new ArrayList<>(); @@ -490,20 +505,25 @@ public TableInfo getLookupTableInfo() //add pivot column String pivotColName = "overlappingProjectsPivot"; - WrappedColumn col2 = new WrappedColumn(pk, pivotColName); + BaseColumnInfo col2 = new ExprColumn(ds, FieldKey.fromString(pivotColName), pk.getValueSql(ExprColumn.STR_TABLE_ALIAS), pk.getJdbcType(), pk); col2.setLabel("Overlapping Group List"); + col2.setName(pivotColName); + col2.setCalculated(true); + col2.setShownInInsertView(false); + col2.setShownInUpdateView(false); col2.setDescription("Shows groups to which this subject belonged at the time of this sample."); col2.setHidden(true); col2.setReadOnly(true); col2.setIsUnselectable(true); col2.setUserEditable(false); + col2.setKeyField(false); final String lookupColName = ds.getName() + "_overlappingProjectsPivot"; col2.setFk(new LookupForeignKey(){ @Override public TableInfo getLookupTableInfo() { Container target = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); - QueryDefinition qd = QueryService.get().createQueryDef(us.getUser(), target, us, lookupColName); + QueryDefinition qd = createQueryDef(us, lookupColName); qd.setSql(getOverlapPivotSql(target, schemaName, querySelectName, pkColSelectName, subjectColName, dateColName)); qd.setIsTemporary(true); @@ -555,21 +575,25 @@ public void appendProjectsCol(final UserSchema us, AbstractTableInfo ds, final S final String publicTableName = ds.getPublicName(); final String colName = ds.getName() + "_allProjects"; - WrappedColumn col = new WrappedColumn(pk, name); + BaseColumnInfo col = new ExprColumn(ds, FieldKey.fromString(name), pk.getValueSql(ExprColumn.STR_TABLE_ALIAS), pk.getJdbcType(), pk); col.setLabel("Groups"); + col.setName(name); + col.setCalculated(true); + col.setShownInInsertView(false); + col.setShownInUpdateView(false); col.setDescription("This column shows all groups to which this subject has ever been a member, regardless of whether that assignment overlaps with the current data point"); col.setReadOnly(true); col.setIsUnselectable(true); col.setUserEditable(false); + col.setKeyField(false); col.setFk(new LookupForeignKey(){ @Override public TableInfo getLookupTableInfo() { - Container target = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); - UserSchema effectiveUs = us.getContainer().isWorkbookOrTab() ? QueryService.get().getUserSchema(us.getUser(), target, us.getSchemaPath()) : us; - QueryDefinition qd = QueryService.get().createQueryDef(us.getUser(), target, effectiveUs, colName); + Container parentContainer = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); + QueryDefinition qd = createQueryDef(us, colName); - qd.setSql(getOverlapSql(target, schemaName, querySelectName, pkColSelectName, subjectSelectName, null)); + qd.setSql(getOverlapSql(parentContainer, schemaName, querySelectName, pkColSelectName, subjectSelectName, null)); qd.setIsTemporary(true); List errors = new ArrayList<>(); @@ -602,20 +626,24 @@ public TableInfo getLookupTableInfo() //add pivot column String pivotColName = "allProjectsPivot"; final String lookupName = ds.getName() + "_allProjectsPivot"; - WrappedColumn col2 = new WrappedColumn(pk, pivotColName); + BaseColumnInfo col2 = new ExprColumn(ds, FieldKey.fromString(pivotColName), pk.getValueSql(ExprColumn.STR_TABLE_ALIAS), pk.getJdbcType(), pk); col2.setLabel("Group Summary List"); + col2.setName(pivotColName); + col2.setCalculated(true); + col2.setShownInInsertView(false); + col2.setShownInUpdateView(false); col2.setDescription("Shows groups to which this subject belonged at any point in time."); col2.setHidden(true); col2.setReadOnly(true); col2.setIsUnselectable(true); col2.setUserEditable(false); + col2.setKeyField(false); col2.setFk(new LookupForeignKey(){ @Override public TableInfo getLookupTableInfo() { Container target = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); - UserSchema effectiveUs = us.getContainer().isWorkbookOrTab() ? QueryService.get().getUserSchema(us.getUser(), target, us.getSchemaPath()) : us; - QueryDefinition qd = QueryService.get().createQueryDef(us.getUser(), target, effectiveUs, lookupName); + QueryDefinition qd = createQueryDef(us, lookupName); qd.setSql(getOverlapPivotSql(target, schemaName, querySelectName, pkColSelectName, subjectSelectName, null)); qd.setIsTemporary(true); @@ -749,11 +777,16 @@ private void appendRelativeDatesCol(final UserSchema us, AbstractTableInfo ds, f final String pkColSelectName = pk.getFieldKey().toSQLString(); final String pkColRawName = pk.getName(); - WrappedColumn col = new WrappedColumn(pk, name); + BaseColumnInfo col = new ExprColumn(ds, FieldKey.fromString(name), pk.getValueSql(ExprColumn.STR_TABLE_ALIAS), pk.getJdbcType(), pk); col.setLabel("Relative Dates"); + col.setName(name); + col.setCalculated(true); + col.setShownInInsertView(false); + col.setShownInUpdateView(false); col.setReadOnly(true); col.setIsUnselectable(true); col.setUserEditable(false); + col.setKeyField(false); final String colName = ds.getName() + "_relativeDates"; final String schemaName = ds.getUserSchema().getSchemaPath().toSQLString(); @@ -765,9 +798,8 @@ private void appendRelativeDatesCol(final UserSchema us, AbstractTableInfo ds, f @Override public TableInfo getLookupTableInfo() { - Container target = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); - UserSchema effectiveUs = us.getContainer().isWorkbookOrTab() ? QueryService.get().getUserSchema(us.getUser(), target, us.getSchemaPath()) : us; - QueryDefinition qd = QueryService.get().createQueryDef(us.getUser(), target, effectiveUs, colName); + Container parentContainer = us.getContainer().isWorkbookOrTab() ? us.getContainer().getParent() : us.getContainer(); + QueryDefinition qd = createQueryDef(us, colName); qd.setSql("SELECT\n" + "t." + pkColSelectName + ",\n" + @@ -795,7 +827,7 @@ public TableInfo getLookupTableInfo() "ROUND(CONVERT(age_in_months(p.startdate, s." + dateSelectName + "), DOUBLE) / 12, 1) AS YearsPostStartDecimal,\n" + "\n" + "FROM " + schemaName + "." + publicTableName + " s\n" + - "JOIN \"" + target.getPath() + "\".laboratory.project_usage p\n" + + "JOIN \"" + parentContainer.getPath() + "\".laboratory.project_usage p\n" + "ON (s." + subjectSelectName + " = p.subjectId AND CONVERT(p.startdate, DATE) <= CONVERT(s." + dateSelectName + ", DATE) AND CONVERT(s." + dateSelectName + ", DATE) <= CONVERT(COALESCE(p.enddate, {fn curdate()}), DATE))\n" + "WHERE s." + dateSelectName + " IS NOT NULL and s." + subjectSelectName + " IS NOT NULL\n" + "\n" + @@ -830,6 +862,28 @@ public TableInfo getLookupTableInfo() ds.addColumn(col); } + private QueryDefinition createQueryDef(UserSchema us, String queryName) + { + if (!us.getContainer().isWorkbook()) + { + return QueryService.get().createQueryDef(us.getUser(), us.getContainer(), us, queryName); + } + + // The rationale is that if we are querying from a workbook, preferentially translate to the parent US + // However, there are situations like workbook-scoped lists, where that query might not exist on the parent + UserSchema parentUserSchema = QueryService.get().getUserSchema(us.getUser(), us.getContainer().getParent(), us.getSchemaPath()); + assert parentUserSchema != null; + + if (parentUserSchema.getTableNames().contains(queryName)) + { + return QueryService.get().createQueryDef(parentUserSchema.getUser(), parentUserSchema.getContainer(), parentUserSchema, queryName); + } + else + { + return QueryService.get().createQueryDef(us.getUser(), us.getContainer(), us, queryName); + } + } + public UserSchema getUserSchema(AbstractTableInfo ds, String name) { UserSchema us = ds.getUserSchema();