diff --git a/docs/generated/UNDERLAY_CONFIG.md b/docs/generated/UNDERLAY_CONFIG.md index 4d9f85d9a..482a2cb89 100644 --- a/docs/generated/UNDERLAY_CONFIG.md +++ b/docs/generated/UNDERLAY_CONFIG.md @@ -126,6 +126,15 @@ If unspecified and exporting queries against the source data is supported for th Field or column name in the [all instances SQL file](#szentityallinstancessqlfile) that maps to the value of this attribute. If unset, we assume the field name is the same as the attribute name. +### SZSourceQuery.emptyValueDisplay +**optional** String + +String to display for empty values. Defaults to "N/A". + +This is the string displayed for fields that do not have values, such as null value fields or empty repeated datatype fields. This is for consistent UI display only and does not impact the actual value of the field. + +*Default value:* `N/A` + ## SZAttributeSearch diff --git a/indexer/src/main/java/bio/terra/tanagra/indexing/job/bigquery/WriteEntityLevelDisplayHints.java b/indexer/src/main/java/bio/terra/tanagra/indexing/job/bigquery/WriteEntityLevelDisplayHints.java index 5397a4b15..6a2093cd3 100644 --- a/indexer/src/main/java/bio/terra/tanagra/indexing/job/bigquery/WriteEntityLevelDisplayHints.java +++ b/indexer/src/main/java/bio/terra/tanagra/indexing/job/bigquery/WriteEntityLevelDisplayHints.java @@ -31,6 +31,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -242,7 +243,9 @@ private List> buildRowOfLiteralsValueDisplay( : Literal.forInt64(null); Literal stringField = attribute.isValueDisplay() - ? Literal.forString(enumCount.getKey().getDisplay()) + ? Literal.forString( + Optional.ofNullable(enumCount.getKey().getDisplay()) + .orElse(attribute.getEmptyValueDisplay())) : enumCount.getKey().getValue(); List rowOfLiterals = new ArrayList<>(); @@ -268,18 +271,22 @@ private List> buildRowOfLiteralsRepeatedString( return enumCounts.stream() .map( enumCount -> { + Literal stringField = + enumCount.getKey().getStringVal() == null + ? Literal.forString(attribute.getEmptyValueDisplay()) + : enumCount.getKey(); List rowOfLiterals = new ArrayList<>(); rowOfLiterals.add(Literal.forString(attribute.getName())); rowOfLiterals.add(Literal.forDouble(null)); rowOfLiterals.add(Literal.forDouble(null)); rowOfLiterals.add(Literal.forInt64(null)); - rowOfLiterals.add(enumCount.getKey()); + rowOfLiterals.add(stringField); rowOfLiterals.add(Literal.forInt64(enumCount.getValue())); LOGGER.info( "Enum repeated-string hint: {}, {}, {}, {}", attribute.getName(), null, - enumCount.getKey(), + stringField, enumCount.getValue()); return rowOfLiterals; }) diff --git a/service/src/main/java/bio/terra/tanagra/service/UnderlayService.java b/service/src/main/java/bio/terra/tanagra/service/UnderlayService.java index 6bd5fb115..4113b8e88 100644 --- a/service/src/main/java/bio/terra/tanagra/service/UnderlayService.java +++ b/service/src/main/java/bio/terra/tanagra/service/UnderlayService.java @@ -271,7 +271,7 @@ public HintQueryResult getEntityLevelHints( String enumDisplay = sqlRowResult.get(ENUM_DISP_ALIAS, DataType.STRING).getStringVal(); if (enumDisplay == null) { - enumDisplay = "N/A"; + enumDisplay = attribute.getEmptyValueDisplay(); } Long enumCount = sqlRowResult.get(ENUM_COUNT_ALIAS, DataType.INT64).getInt64Val(); @@ -299,7 +299,7 @@ public HintQueryResult getEntityLevelHints( sqlRowResult -> { Literal enumVal = sqlRowResult.get(ENUM_VAL_ALIAS, DataType.STRING); if (enumVal.getStringVal() == null) { - enumVal = Literal.forString("N/A"); + enumVal = Literal.forString(attribute.getEmptyValueDisplay()); } Long enumCount = sqlRowResult.get(ENUM_COUNT_ALIAS, DataType.INT64).getInt64Val(); diff --git a/ui/src/tanagra-underlay/underlayConfig.ts b/ui/src/tanagra-underlay/underlayConfig.ts index de4c87747..8ae0777a9 100644 --- a/ui/src/tanagra-underlay/underlayConfig.ts +++ b/ui/src/tanagra-underlay/underlayConfig.ts @@ -11,6 +11,7 @@ export type SZAttribute = { runtimeSqlFunctionWrapper?: string; sourceQuery?: SZSourceQuery; valueFieldName?: string; + emptyValueDisplay?: string; }; export type SZAttributeSearch = { diff --git a/underlay/src/main/java/bio/terra/tanagra/filterbuilder/impl/core/utils/AttributeSchemaUtils.java b/underlay/src/main/java/bio/terra/tanagra/filterbuilder/impl/core/utils/AttributeSchemaUtils.java index 82e91bbf0..4f736ccf6 100644 --- a/underlay/src/main/java/bio/terra/tanagra/filterbuilder/impl/core/utils/AttributeSchemaUtils.java +++ b/underlay/src/main/java/bio/terra/tanagra/filterbuilder/impl/core/utils/AttributeSchemaUtils.java @@ -55,15 +55,12 @@ public static EntityFilter buildForEntity( .forEach( selected -> { Value selectedValue = selected.getValue(); - if (selectedValue.hasStringValue() - || selectedValue.hasInt64Value() - || selectedValue.hasBoolValue() - || selectedValue.hasTimestampValue()) { - enumLiterals.add(toLiteral(selectedValue, attribute.getDataType())); - } else { - // 'n/a' is selected: entries with no value for this attribute + if (attribute.getEmptyValueDisplay().equals(selectedValue.getStringValue())) { + // empty value is selected: entries with no value for this attribute filtersToOr.add( new AttributeFilter(underlay, entity, attribute, UnaryOperator.IS_NULL)); + } else { + enumLiterals.add(toLiteral(selectedValue, attribute.getDataType())); } }); diff --git a/underlay/src/main/java/bio/terra/tanagra/underlay/Underlay.java b/underlay/src/main/java/bio/terra/tanagra/underlay/Underlay.java index 93748d408..7aad8fc62 100644 --- a/underlay/src/main/java/bio/terra/tanagra/underlay/Underlay.java +++ b/underlay/src/main/java/bio/terra/tanagra/underlay/Underlay.java @@ -378,6 +378,7 @@ public static Entity fromConfigEntity(SZEntity szEntity, String primaryEntityNam szAttribute.runtimeSqlFunctionWrapper, ConfigReader.deserializeDataType(szAttribute.runtimeDataType), szAttribute.isComputeDisplayHint, + szAttribute.emptyValueDisplay, szAttribute.isSuppressedForExport, szEntity.temporalQuery != null && szAttribute.name.equals(szEntity.temporalQuery.visitDateAttribute), diff --git a/underlay/src/main/java/bio/terra/tanagra/underlay/entitymodel/Attribute.java b/underlay/src/main/java/bio/terra/tanagra/underlay/entitymodel/Attribute.java index ca5f449aa..828c14796 100644 --- a/underlay/src/main/java/bio/terra/tanagra/underlay/entitymodel/Attribute.java +++ b/underlay/src/main/java/bio/terra/tanagra/underlay/entitymodel/Attribute.java @@ -13,6 +13,7 @@ public final class Attribute { private final String runtimeSqlFunctionWrapper; private final DataType runtimeDataType; private final boolean isComputeDisplayHint; + private final String emptyValueDisplay; private final boolean isSuppressedForExport; private final boolean isVisitDateForTemporalQuery; private final boolean isVisitIdForTemporalQuery; @@ -28,6 +29,7 @@ public Attribute( String runtimeSqlFunctionWrapper, DataType runtimeDataType, boolean isComputeDisplayHint, + String emptyValueDisplay, boolean isSuppressedForExport, boolean isVisitDateForTemporalQuery, boolean isVisitIdForTemporalQuery, @@ -42,6 +44,7 @@ public Attribute( this.isVisitDateForTemporalQuery = isVisitDateForTemporalQuery; this.isVisitIdForTemporalQuery = isVisitIdForTemporalQuery; this.isComputeDisplayHint = isComputeDisplayHint && !isId; + this.emptyValueDisplay = emptyValueDisplay; this.isSuppressedForExport = isSuppressedForExport; this.sourceQuery = sourceQuery; } @@ -86,6 +89,10 @@ public boolean isComputeDisplayHint() { return isComputeDisplayHint; } + public String getEmptyValueDisplay() { + return emptyValueDisplay; + } + public boolean isSuppressedForExport() { return isSuppressedForExport; } @@ -114,6 +121,7 @@ public boolean equals(Object o) { return isValueDisplay == attribute.isValueDisplay && isId == attribute.isId && isComputeDisplayHint == attribute.isComputeDisplayHint + && emptyValueDisplay.equals(attribute.emptyValueDisplay) && isSuppressedForExport == attribute.isSuppressedForExport && isVisitDateForTemporalQuery == attribute.isVisitDateForTemporalQuery && isVisitIdForTemporalQuery == attribute.isVisitIdForTemporalQuery @@ -136,6 +144,7 @@ public int hashCode() { runtimeSqlFunctionWrapper, runtimeDataType, isComputeDisplayHint, + emptyValueDisplay, isSuppressedForExport, isVisitDateForTemporalQuery, isVisitIdForTemporalQuery, diff --git a/underlay/src/main/java/bio/terra/tanagra/underlay/serialization/SZAttribute.java b/underlay/src/main/java/bio/terra/tanagra/underlay/serialization/SZAttribute.java index 54748d802..d2e695060 100644 --- a/underlay/src/main/java/bio/terra/tanagra/underlay/serialization/SZAttribute.java +++ b/underlay/src/main/java/bio/terra/tanagra/underlay/serialization/SZAttribute.java @@ -122,6 +122,17 @@ public class SZAttribute { optional = true) public Double displayHintRangeMax; + @AnnotatedField( + name = "SZSourceQuery.emptyValueDisplay", + markdown = + "String to display for empty values. Defaults to \"N/A\".\n\n" + + "This is the string displayed for fields that do not have values, such as " + + "null value fields or empty repeated datatype fields. This is for consistent " + + "UI display only and does not impact the actual value of the field.", + defaultValue = "N/A", + optional = true) + public String emptyValueDisplay; + @AnnotatedField( name = "SZAttribute.isSuppressedForExport", markdown =