diff --git a/api/gwtsrc/org/labkey/api/gwt/client/model/GWTDomain.java b/api/gwtsrc/org/labkey/api/gwt/client/model/GWTDomain.java index eecd41589ee..fc2e69896d7 100644 --- a/api/gwtsrc/org/labkey/api/gwt/client/model/GWTDomain.java +++ b/api/gwtsrc/org/labkey/api/gwt/client/model/GWTDomain.java @@ -42,6 +42,7 @@ public class GWTDomain implements IsSer @Getter @Setter private boolean allowAttachmentProperties; @Getter @Setter private boolean allowFlagProperties; @Getter @Setter private boolean allowTextChoiceProperties; + @Getter @Setter private boolean allowMultiChoiceProperties; @Getter @Setter private boolean allowSampleSubjectProperties; @Getter @Setter private boolean allowTimepointProperties; @Getter @Setter private boolean allowUniqueConstraintProperties; @@ -90,6 +91,7 @@ public GWTDomain(GWTDomain src) this.allowAttachmentProperties = src.allowAttachmentProperties; this.allowFlagProperties = src.allowFlagProperties; this.allowTextChoiceProperties = src.allowTextChoiceProperties; + this.allowMultiChoiceProperties = src.allowMultiChoiceProperties; this.allowSampleSubjectProperties = src.allowSampleSubjectProperties; this.allowTimepointProperties = src.allowTimepointProperties; this.allowUniqueConstraintProperties = src.allowUniqueConstraintProperties; diff --git a/api/schemas/queryCustomView.xsd b/api/schemas/queryCustomView.xsd index b0abe2a107d..3c0ce4e96ec 100644 --- a/api/schemas/queryCustomView.xsd +++ b/api/schemas/queryCustomView.xsd @@ -93,6 +93,13 @@ + + + + + + + diff --git a/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java b/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java index 06c4dcd35f8..1025106138f 100644 --- a/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java +++ b/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java @@ -48,6 +48,7 @@ public abstract class ColumnRenderPropertiesImpl implements MutableColumnRenderP public static final String STORAGE_UNIQUE_ID_CONCEPT_URI = "http://www.labkey.org/types#storageUniqueId"; public static final String STORAGE_UNIQUE_ID_SEQUENCE_PREFIX = "org.labkey.api.StorageUniqueId"; public static final String TEXT_CHOICE_CONCEPT_URI = "http://www.labkey.org/types#textChoice"; + //public static final String MULTI_VALUE_TEXT_CHOICE_CONCEPT_URI = "http://www.labkey.org/types#mvTextChoice"; public static final String NON_NEGATIVE_NUMBER_CONCEPT_URI = "http://www.labkey.org/types#nonNegativeNumber"; protected SortDirection _sortDirection = SortDirection.ASC; diff --git a/api/src/org/labkey/api/data/CompareType.java b/api/src/org/labkey/api/data/CompareType.java index 8d20eb3a243..b8f1961f0d5 100644 --- a/api/src/org/labkey/api/data/CompareType.java +++ b/api/src/org/labkey/api/data/CompareType.java @@ -808,6 +808,349 @@ public QClause createFilterClause(@NotNull FieldKey fieldKey, Object value) } }; + public Collection getCollectionParam(Object value) + { + if (value instanceof Collection) + { + return (Collection)value; + } + else + { + List values = new ArrayList<>(); + if (value != null) + { + if (value.toString().trim().isEmpty()) + { + values.add(null); + } + else + { + values.addAll(parseParams(value, getValueSeparator(), isNewLineSeparatorAllowed())); + } + } + return values; + } + } + /** TODO: + * + * + */ + public static final CompareType ARRAY_CONTAINS_ALL = new CompareType("Contains All", "arraycontainsall", "ARRAYCONTAINSALL", true, null, OperatorType.ARRAYCONTAINSALL) + { + @Override + public ArrayContainsAllClause createFilterClause(@NotNull FieldKey fieldKey, Object value) + { + return new ArrayContainsAllClause(fieldKey, getCollectionParam(value)); + } + + @Override + public boolean meetsCriteria(ColumnRenderProperties col, Object value, Object[] filterValues) + { + throw new UnsupportedOperationException("Conditional formatting not yet supported for Multi Choices"); + } + + @Override + public String getValueSeparator() + { + return ArrayClause.ARRAY_VALUE_SEPARATOR; + } + }; + + public static final CompareType ARRAY_CONTAINS_ANY = new CompareType("Contains Any", "arraycontainsany", "ARRAYCONTAINSANY", true, null, OperatorType.ARRAYCONTAINSANY) + { + @Override + public ArrayContainsAnyClause createFilterClause(@NotNull FieldKey fieldKey, Object value) + { + return new ArrayContainsAnyClause(fieldKey, getCollectionParam(value)); + } + + @Override + public boolean meetsCriteria(ColumnRenderProperties col, Object value, Object[] filterValues) + { + throw new UnsupportedOperationException("Conditional formatting not yet supported for Multi Choices"); + } + + @Override + public String getValueSeparator() + { + return ArrayClause.ARRAY_VALUE_SEPARATOR; + } + }; + + public static final CompareType ARRAY_CONTAINS_NONE = new CompareType("Contains None", "arraycontainsnone", "ARRAYCONTAINSNONE", true, null, OperatorType.ARRAYCONTAINSNONE) + { + @Override + public ArrayContainsNoneClause createFilterClause(@NotNull FieldKey fieldKey, Object value) + { + return new ArrayContainsNoneClause(fieldKey, getCollectionParam(value)); + } + + @Override + public boolean meetsCriteria(ColumnRenderProperties col, Object value, Object[] filterValues) + { + throw new UnsupportedOperationException("Conditional formatting not yet supported for Multi Choices"); + } + + @Override + public String getValueSeparator() + { + return ArrayClause.ARRAY_VALUE_SEPARATOR; + } + }; + + public static final CompareType ARRAY_MATCHES = new CompareType("Contains Exactly", "arraymatches", "ARRAYMATCHES", true, null, OperatorType.ARRAYMATCHES) + { + @Override + public ArrayMatchesClause createFilterClause(@NotNull FieldKey fieldKey, Object value) + { + return new ArrayMatchesClause(fieldKey, getCollectionParam(value)); + } + + @Override + public boolean meetsCriteria(ColumnRenderProperties col, Object value, Object[] filterValues) + { + throw new UnsupportedOperationException("Conditional formatting not yet supported for Multi Choices"); + } + + @Override + public String getValueSeparator() + { + return ArrayClause.ARRAY_VALUE_SEPARATOR; + } + }; + + public static final CompareType ARRAY_NOT_MATCHES = new CompareType("Does Not Contain Exactly", "arraynotmatches", "ARRAYNOTMATCHES", true, null, OperatorType.ARRAYNOTMATCHES) + { + @Override + public ArrayNotMatchesClause createFilterClause(@NotNull FieldKey fieldKey, Object value) + { + return new ArrayNotMatchesClause(fieldKey, getCollectionParam(value)); + } + + @Override + public boolean meetsCriteria(ColumnRenderProperties col, Object value, Object[] filterValues) + { + throw new UnsupportedOperationException("Conditional formatting not yet supported for Multi Choices"); + } + + @Override + public String getValueSeparator() + { + return ArrayClause.ARRAY_VALUE_SEPARATOR; + } + }; + + public static abstract class ArrayClause extends SimpleFilter.MultiValuedFilterClause + { + public static final String ARRAY_VALUE_SEPARATOR = ","; + + public ArrayClause(@NotNull FieldKey fieldKey, CompareType comparison, Collection params, boolean negated) + { + super(fieldKey, comparison, params, negated); + } + + public SQLFragment[] getParamSQLFragments(SqlDialect dialect) + { + Object[] params = getParamVals(); + SQLFragment[] fragments = new SQLFragment[params.length]; + + JdbcType type = null; + // Try to infer the type from the first non-null parameter + for (Object param : params) + { + if (param != null) + { + type = JdbcType.valueOf(param.getClass()); + break; + } + } + + for (int i = 0; i < params.length; i++) + fragments[i] = new SQLFragment().append(escapeLabKeySqlValue(params[i], type)); + + return fragments; + } + + public Pair getSqlFragments(Map columnMap, SqlDialect dialect) + { + SQLFragment[] paramValues = getParamSQLFragments(dialect); + + if (paramValues == null || paramValues.length == 0) + return null; + + ColumnInfo colInfo = columnMap != null ? columnMap.get(_fieldKey) : null; + var alias = SimpleFilter.getAliasForColumnFilter(dialect, colInfo, _fieldKey); + + SQLFragment valuesFragment = dialect.array_construct(paramValues); + SQLFragment columnFragment = new SQLFragment().appendIdentifier(alias); + + return new Pair<>(valuesFragment, columnFragment); + } + + } + + private static class ArrayContainsAllClause extends ArrayClause + { + + public ArrayContainsAllClause(@NotNull FieldKey fieldKey, Collection params) + { + super(fieldKey, CompareType.ARRAY_CONTAINS_ALL, params, false); + } + + @Override + public SQLFragment toSQLFragment(Map columnMap, SqlDialect dialect) + { + Pair valueFieldSql = getSqlFragments(columnMap, dialect); + if (valueFieldSql == null) + return new SQLFragment("1=2"); + + return dialect.array_all_in_array(valueFieldSql.first, valueFieldSql.second); + } + + @Override + public String getLabKeySQLWhereClause(Map columnMap) + { + return "array_contains_all(" + getLabKeySQLColName(_fieldKey) + ", " + getParamVals()[0] + ")"; + } + + @Override + public void appendFilterText(StringBuilder sb, ColumnNameFormatter formatter) + { + Object[] params = getParamVals(); + sb.append("contains all of ").append(Arrays.toString(params)); + } + + } + + private static class ArrayContainsAnyClause extends ArrayClause + { + + public ArrayContainsAnyClause(@NotNull FieldKey fieldKey, CompareType comparison, Collection params, boolean negated) + { + super(fieldKey, comparison, params, negated); + } + + public ArrayContainsAnyClause(@NotNull FieldKey fieldKey, Collection params) + { + this(fieldKey, CompareType.ARRAY_CONTAINS_ANY, params, false); + } + + @Override + public SQLFragment toSQLFragment(Map columnMap, SqlDialect dialect) + { + Pair valueFieldSql = getSqlFragments(columnMap, dialect); + if (valueFieldSql == null) + return new SQLFragment("1=2"); + + SQLFragment sql = dialect.array_some_in_array(valueFieldSql.first, valueFieldSql.second); + if (!_negated) + return sql; + return new SQLFragment(" NOT (").append(sql).append(")"); + } + + @Override + public String getLabKeySQLWhereClause(Map columnMap) + { + return "array_contains_any(" + getLabKeySQLColName(_fieldKey) + ", " + getParamVals()[0] + ")"; + } + + @Override + public void appendFilterText(StringBuilder sb, ColumnNameFormatter formatter) + { + Object[] params = getParamVals(); + sb.append("contains at least one of ").append(Arrays.toString(params)); + } + + } + + private static class ArrayContainsNoneClause extends ArrayContainsAnyClause + { + + public ArrayContainsNoneClause(@NotNull FieldKey fieldKey, Collection params) + { + super(fieldKey, CompareType.ARRAY_CONTAINS_NONE, params, true); + } + + @Override + public String getLabKeySQLWhereClause(Map columnMap) + { + return "array_contains_none(" + getLabKeySQLColName(_fieldKey) + ", " + getParamVals()[0] + ")"; + } + + @Override + public void appendFilterText(StringBuilder sb, ColumnNameFormatter formatter) + { + Object[] params = getParamVals(); + sb.append("contains none of ").append(Arrays.toString(params)); + } + + } + + private static class ArrayMatchesClause extends ArrayClause + { + + public ArrayMatchesClause(@NotNull FieldKey fieldKey, CompareType comparison, Collection params, boolean negated) + { + super(fieldKey, comparison, params, negated); + } + + public ArrayMatchesClause(@NotNull FieldKey fieldKey, Collection params) + { + this(fieldKey, CompareType.ARRAY_MATCHES, params, false); + } + + @Override + public SQLFragment toSQLFragment(Map columnMap, SqlDialect dialect) + { + Pair valueFieldSql = getSqlFragments(columnMap, dialect); + if (valueFieldSql == null) + return new SQLFragment("1=2"); + + SQLFragment sql = dialect.array_same_array(valueFieldSql.first, valueFieldSql.second); + if (!_negated) + return sql; + return new SQLFragment(" NOT (").append(sql).append(")"); + } + + @Override + public String getLabKeySQLWhereClause(Map columnMap) + { + return "array_is_same(" + getLabKeySQLColName(_fieldKey) + ", " + getParamVals()[0] + ")"; + } + + @Override + public void appendFilterText(StringBuilder sb, ColumnNameFormatter formatter) + { + Object[] params = getParamVals(); + sb.append("contains the same elements as ").append(Arrays.toString(params)); + } + + } + + private static class ArrayNotMatchesClause extends ArrayMatchesClause + { + + public ArrayNotMatchesClause(@NotNull FieldKey fieldKey, Collection params) + { + super(fieldKey, CompareType.ARRAY_NOT_MATCHES, params, true); + } + + @Override + public String getLabKeySQLWhereClause(Map columnMap) + { + return "NOT array_is_same(" + getLabKeySQLColName(_fieldKey) + ", " + getParamVals()[0] + ")"; + } + + @Override + public void appendFilterText(StringBuilder sb, ColumnNameFormatter formatter) + { + Object[] params = getParamVals(); + sb.append("does not contain the same elements as ").append(Arrays.toString(params)); + } + + } + + private static class QClause extends CompareType.CompareClause { private List _queryColumns = null; diff --git a/api/src/org/labkey/api/data/ConnectionWrapper.java b/api/src/org/labkey/api/data/ConnectionWrapper.java index bf81461c15f..ee11f5ca5c2 100644 --- a/api/src/org/labkey/api/data/ConnectionWrapper.java +++ b/api/src/org/labkey/api/data/ConnectionWrapper.java @@ -975,7 +975,7 @@ public Array createArrayOf(String unused, Object[] array) throws SQLException try { SqlDialect dialect = _scope.getSqlDialect(); - String typeName = dialect.getJDBCArrayType(array[0]); + String typeName = dialect.getJDBCArrayType(array); return _connection.createArrayOf(typeName, array); } catch (SQLException e) diff --git a/api/src/org/labkey/api/data/ConvertHelper.java b/api/src/org/labkey/api/data/ConvertHelper.java index a8918d2bc9a..9f55824144a 100644 --- a/api/src/org/labkey/api/data/ConvertHelper.java +++ b/api/src/org/labkey/api/data/ConvertHelper.java @@ -76,6 +76,7 @@ import java.io.File; import java.math.BigDecimal; import java.math.BigInteger; +import java.sql.Array; import java.sql.Blob; import java.sql.Clob; import java.sql.SQLException; @@ -189,6 +190,7 @@ protected void register() _register(new JSONTypeConverter(), JSONObject.class); _register(new ShortURLRecordConverter(), ShortURLRecord.class); _register(new ColumnHeaderType.Converter(), ColumnHeaderType.class); + _register(MultiChoice.Converter.getInstance(), MultiChoice.class); } diff --git a/api/src/org/labkey/api/data/JdbcType.java b/api/src/org/labkey/api/data/JdbcType.java index e427d25382a..480694853a6 100644 --- a/api/src/org/labkey/api/data/JdbcType.java +++ b/api/src/org/labkey/api/data/JdbcType.java @@ -21,6 +21,7 @@ import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.json.JSONArray; import org.junit.Assert; import org.junit.Test; import org.labkey.api.collections.IntHashMap; @@ -34,6 +35,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -300,7 +302,36 @@ protected Collection getSqlTypes() } }, - ARRAY(Types.ARRAY, Array.class), + ARRAY(Types.ARRAY, Array.class) + { + @Override + public Object convert(Object o) throws ConversionException + { + if ((o instanceof java.sql.Array array)) + return array; + if (o instanceof JSONArray jsonArray) + { + // convert jsonArray to array + Object[] elements = new Object[jsonArray.length()]; + for (int i = 0; i < jsonArray.length(); i++) + { + elements[i] = jsonArray.get(i); + } + return new MultiChoice.Array(Arrays.stream(elements)); + } + if (o instanceof Collection collection) + { + return new MultiChoice.Array(collection.stream().map(String::valueOf)); + } + if (o != null) + { + String s = String.valueOf(o); + return new MultiChoice.Array(Arrays.stream(new String[] {s})); + } + + return null; + } + }, NULL(Types.NULL, Object.class), diff --git a/api/src/org/labkey/api/data/MultiChoice.java b/api/src/org/labkey/api/data/MultiChoice.java index 7dda1715437..036a63ccf7a 100644 --- a/api/src/org/labkey/api/data/MultiChoice.java +++ b/api/src/org/labkey/api/data/MultiChoice.java @@ -21,6 +21,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; +import java.text.Format; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -56,6 +57,11 @@ public DisplayColumn(ColumnInfo col) @Override public Object getValue(RenderContext ctx) + { + return getArrayValue(ctx); + } + + public Array getArrayValue(RenderContext ctx) { Object v = super.getValue(ctx); if (!(v instanceof java.sql.Array array)) @@ -129,6 +135,29 @@ public void renderInputCell(RenderContext ctx, HtmlWriter out) DIV(array.stream().map(v -> SPAN(at(style,"border:solid 1px black; border-radius:3px;"), v)) .collect(new JoinRenderable(HtmlString.SP)))); } + + @Override + public String getTsvFormattedValue(RenderContext ctx) + { + Array values = getArrayValue(ctx); + if (null != values && !values.isEmpty()) + { + return PageFlowUtil.joinValuesToStringForExport(values); + } + return null; + } + + @Override + public Object getExcelCompatibleValue(RenderContext ctx) + { + return getTsvFormattedValue(ctx); + } + + @Override + public Object getExportCompatibleValue(RenderContext ctx) + { + return getTsvFormattedValue(ctx); + } } diff --git a/api/src/org/labkey/api/data/PropertyStorageSpec.java b/api/src/org/labkey/api/data/PropertyStorageSpec.java index 9003a17fae0..db20b5052d3 100644 --- a/api/src/org/labkey/api/data/PropertyStorageSpec.java +++ b/api/src/org/labkey/api/data/PropertyStorageSpec.java @@ -169,6 +169,7 @@ public PropertyStorageSpec(PropertyDescriptor propertyDescriptor) setMvEnabled(propertyDescriptor.isMvEnabled()); setDescription(propertyDescriptor.getDescription()); setImportAliases(propertyDescriptor.getImportAliases()); + setTypeURI(propertyDescriptor.getRangeURI()); if (null != propertyDescriptor.getDatabaseDefaultValue()) setDefaultValue(propertyDescriptor.getDatabaseDefaultValue()); diff --git a/api/src/org/labkey/api/data/SimpleConnectionWrapper.java b/api/src/org/labkey/api/data/SimpleConnectionWrapper.java index 8d8a4402d84..68004fdd162 100644 --- a/api/src/org/labkey/api/data/SimpleConnectionWrapper.java +++ b/api/src/org/labkey/api/data/SimpleConnectionWrapper.java @@ -40,7 +40,7 @@ public SimpleConnectionWrapper(Connection connection, DbScope scope) public Array createArrayOf(String unused, Object[] array) throws SQLException { SqlDialect dialect = _scope.getSqlDialect(); - String typeName = dialect.getJDBCArrayType(array[0]); + String typeName = dialect.getJDBCArrayType(array); return _connection.createArrayOf(typeName, array); } diff --git a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java index f193447804b..c7e38673c4c 100644 --- a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java @@ -42,6 +42,7 @@ import org.labkey.api.data.TableInfo; import org.labkey.api.data.dialect.LimitRowsSqlGenerator.LimitRowsCustomizer; import org.labkey.api.data.dialect.LimitRowsSqlGenerator.StandardLimitRowsCustomizer; +import org.labkey.api.exp.PropertyType; import org.labkey.api.util.ExceptionUtil; import org.labkey.api.util.HtmlString; import org.labkey.api.util.StringUtilsLabKey; @@ -189,6 +190,8 @@ protected void addSqlTypeInts(Map sqlTypeIntMap) sqlTypeIntMap.put(Types.TIMESTAMP, "TIMESTAMP"); sqlTypeIntMap.put(Types.DOUBLE, "DOUBLE PRECISION"); sqlTypeIntMap.put(Types.FLOAT, "DOUBLE PRECISION"); + + sqlTypeIntMap.put(Types.ARRAY, "text[]"); // only support text arrays for now } @Override @@ -765,6 +768,10 @@ else if (prop.getJdbcType() == JdbcType.VARCHAR && (prop.getSize() == -1 || prop { return getSqlTypeName(JdbcType.LONGVARCHAR); } + else if (PropertyType.MULTI_CHOICE.getTypeUri().equals(prop.getTypeURI()) && prop.getJdbcType() == JdbcType.ARRAY) + { + return "text[]"; + } else { return getSqlTypeName(prop.getJdbcType()); diff --git a/api/src/org/labkey/api/data/dialect/SqlDialect.java b/api/src/org/labkey/api/data/dialect/SqlDialect.java index 390adcc71a9..c68b4268bc4 100644 --- a/api/src/org/labkey/api/data/dialect/SqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/SqlDialect.java @@ -1300,6 +1300,55 @@ public String getJDBCArrayType(Object object) return StringUtils.lowerCase(getSqlTypeNameFromObject(object)); } + public String getJDBCArrayType(Object[] array) + { + String typeName; + if (array.length == 0) + { + // Handle empty arrays by inferring the SQL element type from the Java component type. + // Primary target is String[0], but handle a reasonable set of common types defensively. + Class componentType = array.getClass().getComponentType(); + if (String.class.equals(componentType)) + { + // Use dialect mapping for a String instance + typeName = getJDBCArrayType(""); + } + else if (Integer.class.equals(componentType)) + { + typeName = getJDBCArrayType(Integer.valueOf(0)); + } + else if (Long.class.equals(componentType)) + { + typeName = getJDBCArrayType(Long.valueOf(0L)); + } + else if (Double.class.equals(componentType)) + { + typeName = getJDBCArrayType(Double.valueOf(0.0d)); + } + else if (Float.class.equals(componentType)) + { + typeName = getJDBCArrayType(Float.valueOf(0.0f)); + } + else if (Boolean.class.equals(componentType)) + { + typeName = getJDBCArrayType(Boolean.FALSE); + } + else + { + // Fallback to VARCHAR which is the safest for most text use-cases + typeName = getSqlTypeName(JdbcType.VARCHAR); + if (typeName != null) + typeName = typeName.toLowerCase(); + } + } + else + { + typeName = getJDBCArrayType(array[0]); + } + + return typeName; + } + public Collection getScriptWarnings(String name, String sql) { return Collections.emptyList(); diff --git a/api/src/org/labkey/api/exp/ObjectProperty.java b/api/src/org/labkey/api/exp/ObjectProperty.java index a0d4a0f1e2f..72a61647493 100644 --- a/api/src/org/labkey/api/exp/ObjectProperty.java +++ b/api/src/org/labkey/api/exp/ObjectProperty.java @@ -19,10 +19,12 @@ import org.labkey.api.data.BeanObjectFactory; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.MultiChoice; import org.labkey.api.data.MvUtil; import org.labkey.api.util.GUID; import java.io.File; +import java.sql.Array; import java.util.Collections; import java.util.Date; import java.util.Map; @@ -49,6 +51,7 @@ public class ObjectProperty extends OntologyManager.PropertyRow // ObjectProperty protected Identifiable objectValue; private Map _childProperties; + protected MultiChoice.Array arrayValue; // Don't delete this -- it's accessed via introspection public ObjectProperty() diff --git a/api/src/org/labkey/api/exp/PropertyType.java b/api/src/org/labkey/api/exp/PropertyType.java index a30ac08695f..50840fcda29 100644 --- a/api/src/org/labkey/api/exp/PropertyType.java +++ b/api/src/org/labkey/api/exp/PropertyType.java @@ -36,6 +36,8 @@ import java.io.File; import java.math.BigDecimal; import java.nio.ByteBuffer; +import java.sql.Array; +import java.sql.SQLException; import java.sql.Time; import java.text.DateFormat; import java.text.ParseException; @@ -234,13 +236,16 @@ protected void init(PropertyRow row, Object value) @Override protected void setValue(ObjectProperty property, Object value) { - throw new UnsupportedOperationException("TODO MultiChoice"); + if ((value instanceof java.sql.Array array)) + property.arrayValue = MultiChoice.Array.from(array); + else if (value != null) + property.arrayValue = MultiChoice.Array.from(new Object[]{value}); } @Override protected Object getValue(ObjectProperty property) { - throw new UnsupportedOperationException("TODO MultiChoice"); + return property.arrayValue; } @Override diff --git a/api/src/org/labkey/api/exp/api/SampleTypeDomainKind.java b/api/src/org/labkey/api/exp/api/SampleTypeDomainKind.java index da47258672a..6b06f23ebb0 100644 --- a/api/src/org/labkey/api/exp/api/SampleTypeDomainKind.java +++ b/api/src/org/labkey/api/exp/api/SampleTypeDomainKind.java @@ -206,6 +206,12 @@ public boolean allowFileLinkProperties() return true; } + @Override + public boolean allowMultiChoiceProperties() + { + return true; + } + @Override public boolean allowTimepointProperties() { diff --git a/api/src/org/labkey/api/exp/property/DomainKind.java b/api/src/org/labkey/api/exp/property/DomainKind.java index f2a8ff5aad9..1de58f1c673 100644 --- a/api/src/org/labkey/api/exp/property/DomainKind.java +++ b/api/src/org/labkey/api/exp/property/DomainKind.java @@ -323,7 +323,7 @@ public boolean matchesTemplateXML(String templateName, DomainTemplateType templa public boolean allowAttachmentProperties() { return false; } public boolean allowFlagProperties() { return true; } public boolean allowTextChoiceProperties() { return true; } - public boolean allowMultiTextChoiceProperties() { return false; } + public boolean allowMultiChoiceProperties() { return false; } public boolean allowSampleSubjectProperties() { return true; } public boolean allowTimepointProperties() { return false; } public boolean allowUniqueConstraintProperties() { return false; } diff --git a/api/src/org/labkey/api/exp/property/DomainUtil.java b/api/src/org/labkey/api/exp/property/DomainUtil.java index af2309f42c8..cf9f540bf25 100644 --- a/api/src/org/labkey/api/exp/property/DomainUtil.java +++ b/api/src/org/labkey/api/exp/property/DomainUtil.java @@ -35,6 +35,7 @@ import org.labkey.api.data.ContainerFilter; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.ContainerService; +import org.labkey.api.data.CoreSchema; import org.labkey.api.data.NameGenerator; import org.labkey.api.data.PHI; import org.labkey.api.data.PropertyStorageSpec; @@ -78,6 +79,8 @@ import org.labkey.api.query.UserSchema; import org.labkey.api.query.ValidationException; import org.labkey.api.security.User; +import org.labkey.api.settings.AppProps; +import org.labkey.api.settings.OptionalFeatureService; import org.labkey.api.util.DateUtil; import org.labkey.api.util.GUID; import org.labkey.api.util.JdbcUtil; @@ -428,6 +431,15 @@ public static List getCalculatedFieldsForDefaultView(@NotNull TableInf return calculatedFieldKeys; } + public static boolean allowMultiChoice(DomainKind kind) + { + if (!kind.allowMultiChoiceProperties()) + return false; + if (!OptionalFeatureService.get().isFeatureEnabled(AppProps.MULTI_VALUE_TEXT_CHOICE)) + return false; + return CoreSchema.getInstance().getSqlDialect().isPostgreSQL(); + } + private static GWTDomain getDomain(Domain dd) { GWTDomain gwtDomain = new GWTDomain<>(); @@ -447,6 +459,7 @@ private static GWTDomain getDomain(Domain dd) gwtDomain.setAllowFileLinkProperties(kind.allowFileLinkProperties()); gwtDomain.setAllowFlagProperties(kind.allowFlagProperties()); gwtDomain.setAllowTextChoiceProperties(kind.allowTextChoiceProperties()); + gwtDomain.setAllowMultiChoiceProperties(allowMultiChoice(kind)); gwtDomain.setAllowSampleSubjectProperties(kind.allowSampleSubjectProperties()); gwtDomain.setAllowTimepointProperties(kind.allowTimepointProperties()); gwtDomain.setAllowUniqueConstraintProperties(kind.allowUniqueConstraintProperties()); @@ -466,6 +479,7 @@ public static GWTDomain getTemplateDomainForDomainKind(Do gwtDomain.setAllowFileLinkProperties(kind.allowFileLinkProperties()); gwtDomain.setAllowFlagProperties(kind.allowFlagProperties()); gwtDomain.setAllowTextChoiceProperties(kind.allowTextChoiceProperties()); + gwtDomain.setAllowMultiChoiceProperties(allowMultiChoice(kind)); gwtDomain.setAllowSampleSubjectProperties(kind.allowSampleSubjectProperties()); gwtDomain.setAllowTimepointProperties(kind.allowTimepointProperties()); gwtDomain.setShowDefaultValueSettings(kind.showDefaultValueSettings()); diff --git a/api/src/org/labkey/api/reader/TabLoader.java b/api/src/org/labkey/api/reader/TabLoader.java index 865abbf6091..7ebf7585b8b 100644 --- a/api/src/org/labkey/api/reader/TabLoader.java +++ b/api/src/org/labkey/api/reader/TabLoader.java @@ -26,6 +26,7 @@ import org.junit.Assert; import org.junit.Test; import org.labkey.api.data.Container; +import org.labkey.api.data.MultiChoice; import org.labkey.api.dataiterator.HashDataIterator; import org.labkey.api.iterator.BeanIterator; import org.labkey.api.iterator.CloseableIterator; @@ -405,6 +406,16 @@ private String[] readFields(TabBufferedReader r, @Nullable ColumnDescriptor[] co char ch = buf.charAt(start); char chQuote = '"'; + boolean isArrayColumn = false; + if (columns != null) + { + var column = columns[colIndex]; + if (column != null) + isArrayColumn = column.clazz == MultiChoice.class || column.clazz == MultiChoice.Array.class; + } + + boolean parseEnclosedQuotes = _parseEnclosedQuotes || isArrayColumn; + colIndex++; boolean isDelimiterOrQuote = false; @@ -431,7 +442,7 @@ else if (ch == chQuote) if (nextLine == null) { // We've reached the end of the input, so there's nothing else to append - if (_parseEnclosedQuotes) + if (parseEnclosedQuotes) isDelimiterOrQuote = false; break; } @@ -446,7 +457,7 @@ else if (ch == chQuote) // " a, " b should be parsed as [" a, " b], not [a, b] // if the next quote is before the end of the buffer and the next non-blank character is not the delimiter, // retain the quote as a mid-field value. - if (_parseEnclosedQuotes && end != buf.length() - 1 && (fieldEnd == -1 || !_whitespacePattern.matcher(buf.substring(end+1, fieldEnd)).matches())) + if (parseEnclosedQuotes && end != buf.length() - 1 && (fieldEnd == -1 || !_whitespacePattern.matcher(buf.substring(end+1, fieldEnd)).matches())) isDelimiterOrQuote = false; break; } diff --git a/api/src/org/labkey/api/settings/AppProps.java b/api/src/org/labkey/api/settings/AppProps.java index 6395044aeb2..ba493df5f8d 100644 --- a/api/src/org/labkey/api/settings/AppProps.java +++ b/api/src/org/labkey/api/settings/AppProps.java @@ -51,6 +51,7 @@ public interface AppProps String QUANTITY_COLUMN_SUFFIX_TESTING = "quantityColumnSuffixTesting"; String GENERATE_CONTROLLER_FIRST_URLS = "generateControllerFirstUrls"; String REJECT_CONTROLLER_FIRST_URLS = "rejectControllerFirstUrls"; + String MULTI_VALUE_TEXT_CHOICE = "multiChoiceDataType"; String UNKNOWN_VERSION = "Unknown Release Version"; diff --git a/api/webapp/clientapi/ext3/FilterDialog.js b/api/webapp/clientapi/ext3/FilterDialog.js index 7e85ba94490..b4c25180a66 100644 --- a/api/webapp/clientapi/ext3/FilterDialog.js +++ b/api/webapp/clientapi/ext3/FilterDialog.js @@ -916,6 +916,8 @@ LABKEY.FilterDialog.View.Default = Ext.extend(LABKEY.FilterDialog.ViewPanel, { }, validateMultiValueInput : function(inputValues, multiValueSeparator, minOccurs, maxOccurs) { + if (!inputValues) + return true; // Used when "Equals One Of.." or "Between" is selected. Calls validateInputField on each value entered. const sep = inputValues.indexOf('\n') > 0 ? '\n' : multiValueSeparator; var values = inputValues.split(sep); diff --git a/assay/package-lock.json b/assay/package-lock.json index 4843a841ff3..155452c9304 100644 --- a/assay/package-lock.json +++ b/assay/package-lock.json @@ -8,7 +8,7 @@ "name": "assay", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.12.0" + "@labkey/components": "7.13.0-fb-mvtc.4" }, "devDependencies": { "@labkey/build": "8.7.0", @@ -2482,9 +2482,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.44.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.44.1.tgz", - "integrity": "sha512-VUS4KLfwAsE45A3MnJUU3j97ei0ncQHv6OVVAN3kitID0xe8+mZ7B39zETVye3Dqgwa8TbYvsCp2t46QmBmwVQ==", + "version": "1.45.0-fb-mvtc.2", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.45.0-fb-mvtc.2.tgz", + "integrity": "sha512-26xRLDWTZTOIGvoseGCeLVzMlwftHs6oxOlft6uhdmJpYBdm6+AyK2w+jU0Fns5SaEtaZAGofrBJFi8zCXttCg==", "license": "Apache-2.0" }, "node_modules/@labkey/build": { @@ -2525,13 +2525,13 @@ } }, "node_modules/@labkey/components": { - "version": "7.12.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.12.0.tgz", - "integrity": "sha512-iwB+3m7JWcwJ7sPA8V+lPXeW6J0tSq/uueiRJ7EzzuaBubXgYwX4ISpZNVt4MzxtAybImzOGLU4ef4+2NFyVRw==", + "version": "7.13.0-fb-mvtc.4", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.13.0-fb-mvtc.4.tgz", + "integrity": "sha512-eMcwnnL0LugsMWQpxh76H1RN5rW0lH3iCpk69oUOPRi/DqSGthA0xJsWbjYmrlK3yQQQiS1Pj+Efn35haKay+w==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", - "@labkey/api": "1.44.1", + "@labkey/api": "1.45.0-fb-mvtc.2", "@testing-library/dom": "~10.4.1", "@testing-library/jest-dom": "~6.9.1", "@testing-library/react": "~16.3.0", diff --git a/assay/package.json b/assay/package.json index bba449b3803..a6da49980b9 100644 --- a/assay/package.json +++ b/assay/package.json @@ -12,7 +12,7 @@ "clean": "rimraf resources/web/assay/gen && rimraf resources/views/gen && rimraf resources/web/gen" }, "dependencies": { - "@labkey/components": "7.12.0" + "@labkey/components": "7.13.0-fb-mvtc.4" }, "devDependencies": { "@labkey/build": "8.7.0", diff --git a/core/package-lock.json b/core/package-lock.json index 5c3d14eb20d..40f9b507d54 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -8,7 +8,7 @@ "name": "labkey-core", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.12.0", + "@labkey/components": "7.13.0-fb-mvtc.4", "@labkey/themes": "1.5.0" }, "devDependencies": { @@ -3504,9 +3504,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.44.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.44.1.tgz", - "integrity": "sha512-VUS4KLfwAsE45A3MnJUU3j97ei0ncQHv6OVVAN3kitID0xe8+mZ7B39zETVye3Dqgwa8TbYvsCp2t46QmBmwVQ==", + "version": "1.45.0-fb-mvtc.2", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.45.0-fb-mvtc.2.tgz", + "integrity": "sha512-26xRLDWTZTOIGvoseGCeLVzMlwftHs6oxOlft6uhdmJpYBdm6+AyK2w+jU0Fns5SaEtaZAGofrBJFi8zCXttCg==", "license": "Apache-2.0" }, "node_modules/@labkey/build": { @@ -3547,13 +3547,13 @@ } }, "node_modules/@labkey/components": { - "version": "7.12.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.12.0.tgz", - "integrity": "sha512-iwB+3m7JWcwJ7sPA8V+lPXeW6J0tSq/uueiRJ7EzzuaBubXgYwX4ISpZNVt4MzxtAybImzOGLU4ef4+2NFyVRw==", + "version": "7.13.0-fb-mvtc.4", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.13.0-fb-mvtc.4.tgz", + "integrity": "sha512-eMcwnnL0LugsMWQpxh76H1RN5rW0lH3iCpk69oUOPRi/DqSGthA0xJsWbjYmrlK3yQQQiS1Pj+Efn35haKay+w==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", - "@labkey/api": "1.44.1", + "@labkey/api": "1.45.0-fb-mvtc.2", "@testing-library/dom": "~10.4.1", "@testing-library/jest-dom": "~6.9.1", "@testing-library/react": "~16.3.0", diff --git a/core/package.json b/core/package.json index 49e27874a49..cc3e4bfcc32 100644 --- a/core/package.json +++ b/core/package.json @@ -53,7 +53,7 @@ } }, "dependencies": { - "@labkey/components": "7.12.0", + "@labkey/components": "7.13.0-fb-mvtc.4", "@labkey/themes": "1.5.0" }, "devDependencies": { diff --git a/experiment/package-lock.json b/experiment/package-lock.json index d09c9ad5605..c18b82920f4 100644 --- a/experiment/package-lock.json +++ b/experiment/package-lock.json @@ -8,7 +8,7 @@ "name": "experiment", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.12.0" + "@labkey/components": "7.13.0-fb-mvtc.4" }, "devDependencies": { "@labkey/build": "8.7.0", @@ -3271,9 +3271,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.44.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.44.1.tgz", - "integrity": "sha512-VUS4KLfwAsE45A3MnJUU3j97ei0ncQHv6OVVAN3kitID0xe8+mZ7B39zETVye3Dqgwa8TbYvsCp2t46QmBmwVQ==", + "version": "1.45.0-fb-mvtc.2", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.45.0-fb-mvtc.2.tgz", + "integrity": "sha512-26xRLDWTZTOIGvoseGCeLVzMlwftHs6oxOlft6uhdmJpYBdm6+AyK2w+jU0Fns5SaEtaZAGofrBJFi8zCXttCg==", "license": "Apache-2.0" }, "node_modules/@labkey/build": { @@ -3314,13 +3314,13 @@ } }, "node_modules/@labkey/components": { - "version": "7.12.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.12.0.tgz", - "integrity": "sha512-iwB+3m7JWcwJ7sPA8V+lPXeW6J0tSq/uueiRJ7EzzuaBubXgYwX4ISpZNVt4MzxtAybImzOGLU4ef4+2NFyVRw==", + "version": "7.13.0-fb-mvtc.4", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.13.0-fb-mvtc.4.tgz", + "integrity": "sha512-eMcwnnL0LugsMWQpxh76H1RN5rW0lH3iCpk69oUOPRi/DqSGthA0xJsWbjYmrlK3yQQQiS1Pj+Efn35haKay+w==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", - "@labkey/api": "1.44.1", + "@labkey/api": "1.45.0-fb-mvtc.2", "@testing-library/dom": "~10.4.1", "@testing-library/jest-dom": "~6.9.1", "@testing-library/react": "~16.3.0", diff --git a/experiment/package.json b/experiment/package.json index d1aacc6542c..304af0c1255 100644 --- a/experiment/package.json +++ b/experiment/package.json @@ -13,7 +13,7 @@ "test-integration": "cross-env NODE_ENV=test jest --ci --runInBand -c test/js/jest.config.integration.js" }, "dependencies": { - "@labkey/components": "7.12.0" + "@labkey/components": "7.13.0-fb-mvtc.4" }, "devDependencies": { "@labkey/build": "8.7.0", diff --git a/experiment/src/org/labkey/experiment/ExperimentModule.java b/experiment/src/org/labkey/experiment/ExperimentModule.java index ab459df94f7..4f70bc8b967 100644 --- a/experiment/src/org/labkey/experiment/ExperimentModule.java +++ b/experiment/src/org/labkey/experiment/ExperimentModule.java @@ -269,6 +269,8 @@ protected void init() { OptionalFeatureService.get().addExperimentalFeatureFlag(NameGenerator.EXPERIMENTAL_ALLOW_GAP_COUNTER, "Allow gap with withCounter and rootSampleCount expression", "Check this option if gaps in the count generated by withCounter or rootSampleCount name expression are allowed.", true); + OptionalFeatureService.get().addExperimentalFeatureFlag(AppProps.MULTI_VALUE_TEXT_CHOICE, "Allow multi-value Text Choice properties", + "Support selecting more than one values for text choice fields", false); } OptionalFeatureService.get().addExperimentalFeatureFlag(AppProps.QUANTITY_COLUMN_SUFFIX_TESTING, "Quantity column suffix testing", "If a column name contains a \"__\" suffix, this feature allows for testing it as a Quantity display column", false); @@ -855,6 +857,7 @@ SELECT COUNT(DISTINCT DD.DomainURI) FROM WHERE DD.storageSchemaName = ? AND D.rangeURI = ?""", DataClassDomainKind.PROVISIONED_SCHEMA_NAME, PropertyType.BOOLEAN.getTypeUri()).getObject(Long.class)); results.put("textChoiceColumnCount", new SqlSelector(schema, "SELECT COUNT(*) FROM exp.propertydescriptor WHERE concepturi = ?", TEXT_CHOICE_CONCEPT_URI).getObject(Long.class)); + results.put("multiValueTextChoiceColumnCount", new SqlSelector(schema, "SELECT COUNT(*) FROM exp.propertydescriptor WHERE rangeuri = ?", PropertyType.MULTI_CHOICE.getTypeUri()).getObject(Long.class)); results.put("domainsWithDateTimeColumnCount", new SqlSelector(schema, """ SELECT COUNT(DISTINCT DD.DomainURI) FROM diff --git a/experiment/src/org/labkey/experiment/api/DataClassDomainKind.java b/experiment/src/org/labkey/experiment/api/DataClassDomainKind.java index e440a6fb275..af1d3f26651 100644 --- a/experiment/src/org/labkey/experiment/api/DataClassDomainKind.java +++ b/experiment/src/org/labkey/experiment/api/DataClassDomainKind.java @@ -204,6 +204,12 @@ public boolean allowAttachmentProperties() return true; } + @Override + public boolean allowMultiChoiceProperties() + { + return true; + } + @Override public ActionURL urlCreateDefinition(String schemaName, String queryName, Container container, User user) { diff --git a/list/src/org/labkey/list/model/ListDomainKind.java b/list/src/org/labkey/list/model/ListDomainKind.java index acf70af615a..8ae2881d322 100644 --- a/list/src/org/labkey/list/model/ListDomainKind.java +++ b/list/src/org/labkey/list/model/ListDomainKind.java @@ -153,6 +153,12 @@ public boolean allowAttachmentProperties() return true; } + @Override + public boolean allowMultiChoiceProperties() + { + return true; + } + @Override public boolean showDefaultValueSettings() { diff --git a/pipeline/package-lock.json b/pipeline/package-lock.json index 7ba89586f45..921467d259a 100644 --- a/pipeline/package-lock.json +++ b/pipeline/package-lock.json @@ -8,7 +8,7 @@ "name": "pipeline", "version": "0.0.0", "dependencies": { - "@labkey/components": "7.12.0" + "@labkey/components": "7.13.0-fb-mvtc.4" }, "devDependencies": { "@labkey/build": "8.7.0", @@ -2716,9 +2716,9 @@ } }, "node_modules/@labkey/api": { - "version": "1.44.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.44.1.tgz", - "integrity": "sha512-VUS4KLfwAsE45A3MnJUU3j97ei0ncQHv6OVVAN3kitID0xe8+mZ7B39zETVye3Dqgwa8TbYvsCp2t46QmBmwVQ==", + "version": "1.45.0-fb-mvtc.2", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/api/-/@labkey/api-1.45.0-fb-mvtc.2.tgz", + "integrity": "sha512-26xRLDWTZTOIGvoseGCeLVzMlwftHs6oxOlft6uhdmJpYBdm6+AyK2w+jU0Fns5SaEtaZAGofrBJFi8zCXttCg==", "license": "Apache-2.0" }, "node_modules/@labkey/build": { @@ -2759,13 +2759,13 @@ } }, "node_modules/@labkey/components": { - "version": "7.12.0", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.12.0.tgz", - "integrity": "sha512-iwB+3m7JWcwJ7sPA8V+lPXeW6J0tSq/uueiRJ7EzzuaBubXgYwX4ISpZNVt4MzxtAybImzOGLU4ef4+2NFyVRw==", + "version": "7.13.0-fb-mvtc.4", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-7.13.0-fb-mvtc.4.tgz", + "integrity": "sha512-eMcwnnL0LugsMWQpxh76H1RN5rW0lH3iCpk69oUOPRi/DqSGthA0xJsWbjYmrlK3yQQQiS1Pj+Efn35haKay+w==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", - "@labkey/api": "1.44.1", + "@labkey/api": "1.45.0-fb-mvtc.2", "@testing-library/dom": "~10.4.1", "@testing-library/jest-dom": "~6.9.1", "@testing-library/react": "~16.3.0", diff --git a/pipeline/package.json b/pipeline/package.json index f4f0a663aba..8e4b629b05f 100644 --- a/pipeline/package.json +++ b/pipeline/package.json @@ -14,7 +14,7 @@ "build-prod": "npm run clean && cross-env NODE_ENV=production PROD_SOURCE_MAP=source-map webpack --config node_modules/@labkey/build/webpack/prod.config.js --color --progress --profile" }, "dependencies": { - "@labkey/components": "7.12.0" + "@labkey/components": "7.13.0-fb-mvtc.4" }, "devDependencies": { "@labkey/build": "8.7.0", diff --git a/query/src/org/labkey/query/QueryServiceImpl.java b/query/src/org/labkey/query/QueryServiceImpl.java index 45749c18d19..d44943d4f2a 100644 --- a/query/src/org/labkey/query/QueryServiceImpl.java +++ b/query/src/org/labkey/query/QueryServiceImpl.java @@ -309,6 +309,11 @@ public void moduleChanged(Module module) CompareType.NONBLANK, CompareType.MV_INDICATOR, CompareType.NO_MV_INDICATOR, + CompareType.ARRAY_CONTAINS_ALL, + CompareType.ARRAY_CONTAINS_ANY, + CompareType.ARRAY_CONTAINS_NONE, + CompareType.ARRAY_MATCHES, + CompareType.ARRAY_NOT_MATCHES, CompareType.Q, WHERE, INDESCENDANTSOF, diff --git a/study/src/org/labkey/study/model/DatasetDomainKind.java b/study/src/org/labkey/study/model/DatasetDomainKind.java index be77b3f371d..c55fb877f22 100644 --- a/study/src/org/labkey/study/model/DatasetDomainKind.java +++ b/study/src/org/labkey/study/model/DatasetDomainKind.java @@ -257,6 +257,12 @@ public boolean allowFileLinkProperties() return true; } + @Override + public boolean allowMultiChoiceProperties() + { + return true; + } + @Override public boolean showDefaultValueSettings() {