From c298fdc975406d7cd2b9f65dfeeaf8cd45ec01f6 Mon Sep 17 00:00:00 2001 From: labkey-matthewb Date: Fri, 20 Dec 2024 15:23:16 -0800 Subject: [PATCH 01/18] quantity type prototype (just working here, not for 24.11) --- .../org/labkey/api/data/BaseColumnInfo.java | 15 +- .../api/data/ColumnRenderProperties.java | 32 ++ .../api/data/ColumnRenderPropertiesImpl.java | 3 + api/src/org/labkey/api/data/DataColumn.java | 11 +- .../org/labkey/api/data/DisplayColumn.java | 101 +++-- api/src/org/labkey/api/data/Parameter.java | 4 + api/src/org/labkey/api/data/SQLFragment.java | 80 +++- .../org/labkey/api/data/TableViewForm.java | 19 +- .../api/dataiterator/SimpleTranslator.java | 61 +-- .../labkey/api/ontology/KindOfQuantity.java | 114 +++++ api/src/org/labkey/api/ontology/Quantity.java | 429 ++++++++++++++++++ api/src/org/labkey/api/ontology/Unit.java | 171 +++++++ .../api/SampleTypeUpdateServiceDI.java | 7 +- query/src/org/labkey/query/sql/QNumber.java | 40 +- query/src/org/labkey/query/sql/QString.java | 2 +- 15 files changed, 989 insertions(+), 100 deletions(-) create mode 100644 api/src/org/labkey/api/ontology/KindOfQuantity.java create mode 100644 api/src/org/labkey/api/ontology/Quantity.java create mode 100644 api/src/org/labkey/api/ontology/Unit.java diff --git a/api/src/org/labkey/api/data/BaseColumnInfo.java b/api/src/org/labkey/api/data/BaseColumnInfo.java index 81a35776899..abfca991e97 100644 --- a/api/src/org/labkey/api/data/BaseColumnInfo.java +++ b/api/src/org/labkey/api/data/BaseColumnInfo.java @@ -1329,12 +1329,19 @@ public void setSortFieldKeysFromXml(String xml) public static String labelFromName(String name) { - if (name == null) - return null; - - if (name.length() == 0) + if (StringUtils.isBlank(name)) return name; + //UNDONE This is just for testing the idea (let the DataRegion/DataColumn do this) + var index = name.indexOf("__"); + if (index > 0) + { + String unit = name.substring(index + 2); + if (null != org.labkey.api.ontology.Unit.fromName(unit)) + name = name.substring(0, index); + } + //UNDONE + StringBuilder buf = new StringBuilder(name.length() + 10); char[] chars = new char[name.length()]; name.getChars(0, name.length(), chars, 0); diff --git a/api/src/org/labkey/api/data/ColumnRenderProperties.java b/api/src/org/labkey/api/data/ColumnRenderProperties.java index 20dde67a2d0..99c8f8dacbc 100644 --- a/api/src/org/labkey/api/data/ColumnRenderProperties.java +++ b/api/src/org/labkey/api/data/ColumnRenderProperties.java @@ -20,7 +20,9 @@ import org.labkey.api.exp.PropertyType; import org.labkey.api.gwt.client.DefaultScaleType; import org.labkey.api.gwt.client.FacetingBehaviorType; +import org.labkey.api.ontology.KindOfQuantity; import org.labkey.api.ontology.OntologyService; +import org.labkey.api.ontology.Unit; import org.labkey.api.query.FieldKey; import org.labkey.api.util.StringExpression; @@ -245,6 +247,36 @@ default String getConceptLabelColumn() { return null; } + + default KindOfQuantity getKindOfQuantity() + { + var unit = getDisplayUnit(); + if (null == unit) + return null; + return unit.getKindOfQuantity(); + } + + default Unit getDisplayUnit() + { + if (!getJdbcType().isNumeric()) + return null; + String name = getName(); + var index = name.lastIndexOf("__"); + if (index < 0) + return null; + var unitPart = name.substring(index+2); + try + { + return Unit.valueOf(unitPart); + } + catch (IllegalArgumentException x) + { + // pass + } + return null; + } + + /* End properties loaded by OntologyService */ diff --git a/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java b/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java index a8887f6f4fc..cdc63915ca6 100644 --- a/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java +++ b/api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java @@ -25,6 +25,7 @@ import org.labkey.api.gwt.client.DefaultScaleType; import org.labkey.api.gwt.client.DefaultValueType; import org.labkey.api.gwt.client.FacetingBehaviorType; +import org.labkey.api.ontology.Unit; import org.labkey.api.query.FieldKey; import org.labkey.api.util.StringExpression; @@ -785,6 +786,8 @@ public final Class getJavaClass() @Override public Class getJavaClass(boolean isNullable) { + Class ret; + boolean isNumeric; PropertyType pt = getPropertyType(); if (pt != null) return pt.getJavaType(); diff --git a/api/src/org/labkey/api/data/DataColumn.java b/api/src/org/labkey/api/data/DataColumn.java index 62287ca4df1..0d0315fa4a1 100644 --- a/api/src/org/labkey/api/data/DataColumn.java +++ b/api/src/org/labkey/api/data/DataColumn.java @@ -564,7 +564,7 @@ public HtmlString getFormattedHtml(RenderContext ctx) } else { - String formatted = formatValue(ctx, value, getTextExpressionCompiled(ctx), getFormat()); + String formatted = formatValue(ctx, value, getTextExpressionCompiled(ctx), getFormat(), getDisplayUnit()); if (getRequiresHtmlFiltering()) formatted = PageFlowUtil.filter(formatted); @@ -598,7 +598,7 @@ protected boolean isSelectInputSelected(String entryName, Object value, String v if (value instanceof Collection) { // CONSIDER: stringify values in collection? - return ((Collection)value).contains(entryName); + return ((Collection)value).contains(entryName); } return null != entryName && entryName.equals(valueStr); } @@ -915,7 +915,7 @@ protected String h(Object value) @Override public String getSortHandler(RenderContext ctx, Sort.SortDirection sort) { - if (_displayColumn == null || _sortFieldKeys == null || _sortFieldKeys.size() == 0) + if (_displayColumn == null || _sortFieldKeys == null || _sortFieldKeys.isEmpty()) return ""; String regionName = ctx.getCurrentRegion().getName(); @@ -940,7 +940,10 @@ public String getTitle(RenderContext ctx) { if (_caption == null) return null; - return _caption.eval(ctx); + var title = _caption.eval(ctx); + if (null != _displayColumn && null != _displayColumn.getDisplayUnit()) + title += " (" + _displayColumn.getDisplayUnit() + ")"; + return title; } @Override diff --git a/api/src/org/labkey/api/data/DisplayColumn.java b/api/src/org/labkey/api/data/DisplayColumn.java index 8c654821bf0..00dd72f0b9b 100644 --- a/api/src/org/labkey/api/data/DisplayColumn.java +++ b/api/src/org/labkey/api/data/DisplayColumn.java @@ -27,6 +27,8 @@ import org.labkey.api.compliance.PhiTransformedColumnInfo; import org.labkey.api.ontology.Concept; import org.labkey.api.ontology.OntologyService; +import org.labkey.api.ontology.Quantity; +import org.labkey.api.ontology.Unit; import org.labkey.api.query.FieldKey; import org.labkey.api.stats.ColumnAnalyticsProvider; import org.labkey.api.util.DateUtil; @@ -318,7 +320,7 @@ public ColumnInfo getDisplayColumnInfo() public abstract Object getValue(RenderContext ctx); - public abstract Class getValueClass(); + public abstract Class getValueClass(); public Object getJsonValue(RenderContext ctx) { @@ -397,7 +399,7 @@ private Format createFormat(String formatString, @Nullable DecimalFormatSymbols { if (null != formatString) { - Class valueClass = getDisplayValueClass(); + Class valueClass = getDisplayValueClass(); try { @@ -471,14 +473,14 @@ public StringExpression getTextExpressionCompiled(RenderContext ctx) public HtmlString getFormattedHtml(RenderContext ctx) { Format format = getFormat(); - return HtmlString.of(formatValue(ctx, getDisplayValue(ctx), getTextExpressionCompiled(ctx), format)); + Unit unit = getDisplayUnit(); + return HtmlString.of(formatValue(ctx, getDisplayValue(ctx), getTextExpressionCompiled(ctx), format, unit)); } /** * Format the display value as text only if there is a text expression or format configured for * the display column (which includes any project date and number format settings), * otherwise return null. - * * No html encoding should be performed * @see #getFormattedHtml(RenderContext) */ @@ -516,7 +518,7 @@ public String getFormattedText(RenderContext ctx) * any html encoding. */ @NotNull - protected final String formatValue(RenderContext ctx, Object value, StringExpression expr, Format format) + protected final String formatValue(RenderContext ctx, final Object value, StringExpression expr, @Nullable Format format, @Nullable Unit displayUnit) { if (null == value) return ""; @@ -525,22 +527,47 @@ protected final String formatValue(RenderContext ctx, Object value, StringExpres { return expr.eval(ctx); } - else if (null != format) + + @NotNull String formattedString; + if (null != format) { try { - return format.format(value); + if (null != displayUnit && value instanceof Number number) + { + Quantity q = (value instanceof Quantity) ? + (Quantity)value : + displayUnit.getKindOfQuantity().toQuantity(number); + formattedString = q.format(displayUnit, format); + } + else + { + formattedString = format.format(value); + } } catch (IllegalArgumentException e) { LOG.warn("Unable to apply format to {} value \"{}\" for column \"{}\", likely a SQL type mismatch between XML metadata and actual ResultSet", value.getClass().getName(), value, getName()); - return ConvertUtils.convert(value); + formattedString = ConvertUtils.convert(value); } } else if (value instanceof String) - return (String)value; + { + formattedString = (String) value; + } + else + { + formattedString = ConvertUtils.convert(value); + } + + return formattedString; + } + - return ConvertUtils.convert(value); + Unit getDisplayUnit() + { + ColumnInfo col = getDisplayColumnInfo(); + return null != col ? col.getDisplayUnit() : null; } @@ -552,7 +579,8 @@ public String getTsvFormattedValue(RenderContext ctx) { format = getFormat(); } - return formatValue(ctx, getExportCompatibleValue(ctx), getTextExpressionCompiled(ctx), format); + Unit unit = getDisplayUnit(); + return formatValue(ctx, getExportCompatibleValue(ctx), getTextExpressionCompiled(ctx), format, unit); } public Object getExcelCompatibleValue(RenderContext ctx) @@ -560,7 +588,7 @@ public Object getExcelCompatibleValue(RenderContext ctx) return getExportCompatibleValue(ctx); } - public Object getExportCompatibleValue(RenderContext ctx) + public Object getExportCompatibleValue(RenderContext ctx) { Object value = getDisplayValue(ctx); if (null == value) @@ -579,7 +607,7 @@ public Object getExportCompatibleValue(RenderContext ctx) /** * Returns the JSON type name for the column's display value, - * which might be different than its value (e.g., lookup column) + * which might be different from its value (e.g., lookup column) * @return JSON type name */ public String getDisplayJsonTypeName() @@ -596,7 +624,7 @@ public String getJsonTypeName() return getJsonTypeName(getValueClass()); } - public static String getJsonTypeName(Class valueClass) + public static String getJsonTypeName(Class valueClass) { if (String.class.isAssignableFrom(valueClass)) return "string"; @@ -618,20 +646,19 @@ else if (Date.class.isAssignableFrom(valueClass)) return "string"; } - public static Class getClassFromJsonTypeName(String typeName) + public static Class getClassFromJsonTypeName(String typeName) { if (typeName == null) return String.class; - switch (typeName) + return switch (typeName) { - case "boolean": return Boolean.class; - case "int": return Integer.class; - case "float": return Float.class; - case "date": return Date.class; - case "string": - default: return String.class; - } + case "boolean" -> Boolean.class; + case "int" -> Integer.class; + case "float" -> Float.class; + case "date" -> Date.class; + default -> String.class; + }; } @@ -641,7 +668,7 @@ public Object getDisplayValue(RenderContext ctx) return getValue(ctx); } - public Class getDisplayValueClass() + public Class getDisplayValueClass() { return getValueClass(); } @@ -770,7 +797,7 @@ public void renderGridHeaderCell(RenderContext ctx, Writer out, String headerCla { if (!getColumnInfo().getFieldKey().toString().equals(getColumnInfo().getLabel())) { - boolean suffix = tooltip.length() > 0; + boolean suffix = !tooltip.isEmpty(); if (suffix) { tooltip.append(" ("); @@ -792,18 +819,18 @@ public void renderGridHeaderCell(RenderContext ctx, Writer out, String headerCla if (null != concept) conceptDisplay = concept.getLabel() + " (" + conceptDisplay + ")"; } - tooltip.append("\nConcept Annotation: " + conceptDisplay); + tooltip.append("\nConcept Annotation: ").append(conceptDisplay); } if (isPhiProtected()) { - if (tooltip.length() > 0) + if (!tooltip.isEmpty()) tooltip.append("\n"); tooltip.append("(PHI protected data removed)"); } } - if (tooltip.length() > 0) + if (!tooltip.isEmpty()) { out.write(" title=\""); out.write(PageFlowUtil.filter(tooltip.toString())); @@ -823,7 +850,7 @@ public void renderGridHeaderCell(RenderContext ctx, Writer out, String headerCla style += ";width:" + getWidth() + "px;"; out.write("
"); out.write("