Skip to content

Commit a9b1bee

Browse files
committed
Merge branch 'develop' into fb_mvtc_assay
2 parents 30a41a9 + 0a40d45 commit a9b1bee

2 files changed

Lines changed: 96 additions & 4 deletions

File tree

api/src/org/labkey/api/exp/property/DomainUtil.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@
110110
import java.util.Optional;
111111
import java.util.Set;
112112
import java.util.regex.Matcher;
113+
import java.util.regex.Pattern;
113114
import java.util.stream.Collectors;
114115

116+
import static org.labkey.api.data.ColumnRenderPropertiesImpl.TEXT_CHOICE_CONCEPT_URI;
115117
import static org.labkey.api.dataiterator.DetailedAuditLogDataIterator.AuditConfigs.AuditBehavior;
116118
import static org.labkey.api.gwt.client.ui.PropertyType.CALCULATED_CONCEPT_URI;
117119
import static org.labkey.api.util.StringExpressionFactory.SUBSTITUTION_EXP_PATTERN;
@@ -1430,6 +1432,38 @@ private static boolean _copyValidator(IPropertyValidator pv, GWTPropertyValidato
14301432
return hasChange;
14311433
}
14321434

1435+
// GitHub Issue 955: limit option length to 200
1436+
private static final int TEXT_CHOICE_MAX_VALUE_LENGTH = 200;
1437+
// GitHub Issue 988: don't allow json array like values for text choice options
1438+
private static final Pattern JSON_FILTER_VALUE_PATTERN = Pattern.compile("\\{json:\\s*\\[.*]}", Pattern.DOTALL);
1439+
1440+
/**
1441+
* Validate text choice options for a field. Returns an error message if invalid, or null if valid.
1442+
*/
1443+
private static @Nullable String validateTextChoiceOptions(GWTPropertyDescriptor field)
1444+
{
1445+
for (GWTPropertyValidator validator : field.getPropertyValidators())
1446+
{
1447+
if (PropertyValidatorType.TextChoice.equals(validator.getType()))
1448+
{
1449+
String expression = validator.getExpression();
1450+
List<String> options = PageFlowUtil.splitStringToValues(expression != null ? expression : "", '|');
1451+
for (String option : options)
1452+
{
1453+
if (option.length() > TEXT_CHOICE_MAX_VALUE_LENGTH)
1454+
{
1455+
return "Text choice value for field '" + field.getName() + "' must not exceed " + TEXT_CHOICE_MAX_VALUE_LENGTH + " characters: '" + StringUtils.abbreviate(option, 50) + "'";
1456+
}
1457+
if (JSON_FILTER_VALUE_PATTERN.matcher(option).matches())
1458+
{
1459+
return "Text choice value for field '" + field.getName() + "' must not use the reserved format '{json:[...]}': '" + StringUtils.abbreviate(option, 50) + "'";
1460+
}
1461+
}
1462+
}
1463+
}
1464+
return null;
1465+
}
1466+
14331467
private static String getDomainErrorMessage(@Nullable GWTDomain<?> domain, String message)
14341468
{
14351469
if (domain != null && domain.getName() != null)
@@ -1477,6 +1511,16 @@ public static ValidationException validateProperties(@Nullable Domain domain, @N
14771511
continue;
14781512
}
14791513

1514+
if (PropertyType.MULTI_CHOICE.getTypeUri().equals(field.getRangeURI()) || TEXT_CHOICE_CONCEPT_URI.equals(field.getConceptURI()))
1515+
{
1516+
String textChoiceError = validateTextChoiceOptions(field);
1517+
if (textChoiceError != null)
1518+
{
1519+
exception.addFieldError(name, getDomainErrorMessage(updates, textChoiceError));
1520+
continue;
1521+
}
1522+
}
1523+
14801524
Matcher expMatcher = SUBSTITUTION_EXP_PATTERN.matcher(name);
14811525
if (expMatcher.find())
14821526
{

core/webapp/internal/ViewDesigner/field/FilterTextValueUtil.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,18 @@ Ext4.define('LABKEY.internal.ViewDesigner.field.FilterTextValueUtil', {
1616
// convert the filter value into a user-editable string using filter.getURLParameterValue()
1717
var valueString = filter.getURLParameterValue();
1818

19-
// replace ; with \n on UI
19+
// Display multi-valued filters with one value per line in the textarea.
20+
// getURLParameterValue() returns {json:[...]} when values contain the ';' separator,
21+
// so we need to parse that format before converting to newline-separated display.
2022
if (filterType.isMultiValued() && (urlSuffix !== 'notbetween' && urlSuffix !== 'between')) {
21-
if (typeof valueString === 'string' && valueString.indexOf('\n') === -1 && valueString.indexOf(';') > 0)
22-
valueString = valueString.replaceAll(';', '\n');
23+
if (typeof valueString === 'string') {
24+
var parsed = this._parseJsonFilterValue(valueString);
25+
if (parsed !== null) {
26+
valueString = parsed.join('\n');
27+
} else if (valueString.indexOf('\n') === -1 && valueString.indexOf(';') > 0) {
28+
valueString = valueString.replaceAll(';', '\n');
29+
}
30+
}
2331
}
2432

2533
this.setValue(valueString);
@@ -48,15 +56,55 @@ Ext4.define('LABKEY.internal.ViewDesigner.field.FilterTextValueUtil', {
4856

4957
setRecordValue : function (valueString) {
5058
// parse the value string into parts for multi-value filters
59+
var filterValue;
5160
try {
61+
// For multi-valued filters (excluding between/notbetween), values are displayed
62+
// one per line in the textarea. When saving, split by newline only and encode
63+
// using {json:[...]} if any value contains the separator character (e.g. ';')
64+
// to prevent parseValue from incorrectly splitting those values.
65+
var op = this.record.get("items")[this.clauseIndex].op;
66+
var filterType = LABKEY.Filter.getFilterTypeForURLSuffix(op);
67+
var urlSuffix = filterType ? filterType.getURLSuffix() : null;
68+
69+
if (filterType && filterType.isMultiValued() && typeof valueString === 'string'
70+
&& urlSuffix !== 'notbetween' && urlSuffix !== 'between') {
71+
var sep = filterType.getMultiValueSeparator();
72+
var values = valueString.split('\n');
73+
if (sep && values.some(function(v) { return v.indexOf(sep) !== -1; })) {
74+
valueString = '{json:' + JSON.stringify(values) + '}';
75+
} else {
76+
valueString = values.join(sep);
77+
}
78+
}
79+
5280
var filter = this.createFilter(valueString);
53-
var filterValue = filter.getValue();
81+
filterValue = filter.getValue();
5482
}
5583
catch (e) {
5684
console.warn("Error parsing filter value: " + valueString);
5785
filterValue = valueString;
5886
}
5987

6088
this.record.get("items")[this.clauseIndex].value = filterValue;
89+
},
90+
91+
/**
92+
* GitHub Issue 947: Multi value text choice values with semicolon mangled in LKS grid view editor
93+
* Parse a {json:[...]} encoded filter value string.
94+
* Returns the parsed array, or null if the string is not in {json:...} format.
95+
*/
96+
_parseJsonFilterValue : function (valueString) {
97+
if (typeof valueString === 'string'
98+
&& valueString.indexOf('{json:') === 0
99+
&& valueString.lastIndexOf('}') === valueString.length - 1) {
100+
try {
101+
var parsed = JSON.parse(valueString.substring('{json:'.length, valueString.length - 1));
102+
if (Array.isArray(parsed))
103+
return parsed;
104+
} catch (e) {
105+
// Not valid JSON, return null to fall through to default handling
106+
}
107+
}
108+
return null;
61109
}
62110
});

0 commit comments

Comments
 (0)