From 4d16bc0fc73ca8c8972778501d0438a00ec950b7 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 27 Sep 2022 18:59:58 -0500 Subject: [PATCH] Auto-adorn PVS tables with link-to-record; change query to fetch instants, not LocalDateTimes --- .../core/instances/QInstanceEnricher.java | 73 ++++++++++++++++++- .../core/instances/QInstanceValidator.java | 2 +- .../model/metadata/fields/AdornmentType.java | 3 +- .../core/instances/QInstanceEnricherTest.java | 60 +++++++++++++-- .../rdbms/actions/RDBMSQueryAction.java | 2 +- .../javalin/QJavalinProcessHandler.java | 3 +- 6 files changed, 130 insertions(+), 13 deletions(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java index 7572b4af..8bb956ca 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java @@ -34,12 +34,16 @@ import java.util.Set; import java.util.stream.Collectors; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; +import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType; import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; @@ -73,12 +77,24 @@ public class QInstanceEnricher { private static final Logger LOG = LogManager.getLogger(QInstanceEnricher.class); + private final QInstance qInstance; + /******************************************************************************* ** *******************************************************************************/ - public void enrich(QInstance qInstance) + public QInstanceEnricher(QInstance qInstance) + { + this.qInstance = qInstance; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void enrich() { if(qInstance.getTables() != null) { @@ -210,6 +226,61 @@ public class QInstanceEnricher { field.setLabel(nameToLabel(field.getName())); } + + ///////////////////////////////////////////////////////////////////////// + // if this field has a possibleValueSource // + // and that PVS exists in the instance // + // and it's a table-type PVS and the table name is set // + // and it's a valid table in the instant, and the table is in some app // + // and the field doesn't have a LINK adornment // + // then add a link-to-record-from-table adornment to the field. // + ///////////////////////////////////////////////////////////////////////// + if(StringUtils.hasContent(field.getPossibleValueSourceName())) + { + QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName()); + if(possibleValueSource != null) + { + String tableName = possibleValueSource.getTableName(); + if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()) && StringUtils.hasContent(tableName)) + { + if(qInstance.getTable(tableName) != null && doesAnyAppHaveTable(tableName)) + { + if(field.getAdornments() == null || field.getAdornments().stream().noneMatch(a -> AdornmentType.LINK.equals(a.getType()))) + { + field.withFieldAdornment(new FieldAdornment().withType(AdornmentType.LINK) + .withValue(AdornmentType.LinkValues.TO_RECORD_FROM_TABLE, tableName)); + } + } + } + } + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private boolean doesAnyAppHaveTable(String tableName) + { + if(qInstance.getApps() != null) + { + for(QAppMetaData app : qInstance.getApps().values()) + { + if(app.getChildren() != null) + { + for(QAppChildMetaData child : app.getChildren()) + { + if(child instanceof QTableMetaData && tableName.equals(child.getName())) + { + return (true); + } + } + } + } + } + + return (false); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index 653ce4b8..ed206b5e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -94,7 +94,7 @@ public class QInstanceValidator // before validation, enrich the object (e.g., to fill in values that the user doesn't have to // ///////////////////////////////////////////////////////////////////////////////////////////////// // TODO - possible point of customization (use a different enricher, or none, or pass it options). - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); } catch(Exception e) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java index d476d993..330ccd51 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java @@ -43,7 +43,8 @@ public enum AdornmentType *******************************************************************************/ public interface LinkValues { - String TARGET = "target"; + String TARGET = "target"; + String TO_RECORD_FROM_TABLE = "toRecordFromTable"; } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java index 12b3c694..3a49fd3c 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java @@ -25,9 +25,13 @@ package com.kingsrook.qqq.backend.core.instances; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; +import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier; @@ -39,6 +43,7 @@ import static com.kingsrook.qqq.backend.core.utils.TestUtils.APP_NAME_PEOPLE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /******************************************************************************* @@ -59,7 +64,7 @@ class QInstanceEnricherTest QTableMetaData personTable = qInstance.getTable("person"); personTable.setLabel(null); assertNull(personTable.getLabel()); - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); assertEquals("Person", personTable.getLabel()); } @@ -79,7 +84,7 @@ class QInstanceEnricherTest personTable.setName(null); assertNull(personTable.getLabel()); assertNull(personTable.getName()); - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); assertNull(personTable.getLabel()); assertNull(personTable.getName()); } @@ -97,7 +102,7 @@ class QInstanceEnricherTest QFieldMetaData idField = qInstance.getTable("person").getField("id"); idField.setLabel(null); assertNull(idField.getLabel()); - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); assertEquals("Id", idField.getLabel()); } @@ -118,7 +123,7 @@ class QInstanceEnricherTest .withFieldNames(new ArrayList<>(personTable.getFields().keySet())) )); - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); assertEquals("Test", personTable.getSections().get(0).getLabel()); } @@ -198,7 +203,7 @@ class QInstanceEnricherTest void testGenerateAppSections() { QInstance qInstance = TestUtils.defineInstance(); - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); assertNotNull(qInstance.getApp(APP_NAME_GREETINGS).getSections()); assertEquals(1, qInstance.getApp(APP_NAME_GREETINGS).getSections().size(), "App should automatically have one section"); assertEquals(0, qInstance.getApp(APP_NAME_GREETINGS).getSections().get(0).getTables().size(), "Section should not have tables"); @@ -228,18 +233,57 @@ class QInstanceEnricherTest { QInstance qInstance = TestUtils.defineInstance(); QTableMetaData table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields(new ArrayList<>()); - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); assertNull(table.getRecordLabelFormat()); qInstance = TestUtils.defineInstance(); table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields("firstName"); - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); assertEquals("%s", table.getRecordLabelFormat()); qInstance = TestUtils.defineInstance(); table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields("firstName", "lastName"); - new QInstanceEnricher().enrich(qInstance); + new QInstanceEnricher(qInstance).enrich(); assertEquals("%s %s", table.getRecordLabelFormat()); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testAddTablePvsAdornment() + { + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // first make sure the adornment doesn't get added for favoriteShapeId, because it isn't in any apps // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + { + QInstance qInstance = TestUtils.defineInstance(); + QTableMetaData personTable = qInstance.getTable("person"); + QFieldMetaData favoriteShapeId = personTable.getField("favoriteShapeId"); + new QInstanceEnricher(qInstance).enrich(); + assertNull(favoriteShapeId.getAdornments()); + } + + //////////////////////////////////////////////////////////////////// + // then put shape table in an app, re-run, and see it get adorned // + //////////////////////////////////////////////////////////////////// + { + QInstance qInstance = TestUtils.defineInstance(); + QTableMetaData shapeTable = qInstance.getTable(TestUtils.TABLE_NAME_SHAPE); + QAppMetaData miscApp = qInstance.getApp(APP_NAME_MISCELLANEOUS); + miscApp.addChild(shapeTable); + + QTableMetaData personTable = qInstance.getTable("person"); + QFieldMetaData favoriteShapeId = personTable.getField("favoriteShapeId"); + new QInstanceEnricher(qInstance).enrich(); + assertNotNull(favoriteShapeId.getAdornments()); + Optional optionalAdornment = favoriteShapeId.getAdornments().stream().filter(a -> a.getType().equals(AdornmentType.LINK)).findFirst(); + assertTrue(optionalAdornment.isPresent()); + FieldAdornment adornment = optionalAdornment.get(); + assertEquals("shape", adornment.getValues().get(AdornmentType.LinkValues.TO_RECORD_FROM_TABLE)); + } + } + } diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java index fa08c192..5b44e977 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java @@ -204,7 +204,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf } case DATE_TIME: { - return (QueryManager.getLocalDateTime(resultSet, i)); + return (QueryManager.getInstant(resultSet, i)); } case BOOLEAN: { diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java index 126343c0..160e5553 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java @@ -231,7 +231,8 @@ public class QJavalinProcessHandler if(userFacingException != null) { LOG.info("User-facing exception in process", userFacingException); - resultForCaller.put("error", userFacingException.getMessage()); // todo - put this somewhere else (make error an object w/ user-facing and/or other error?) + resultForCaller.put("error", userFacingException.getMessage()); + resultForCaller.put("userFacingError", userFacingException.getMessage()); } else {