feat(virtualFields): add baseline support for virtualFields #4
No files matched your search
+53
@@ -72,6 +72,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueB
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAndJoinTable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QSupplementalFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QVirtualFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.MultiRecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
@@ -178,6 +179,9 @@ public class CustomizableTableViewsTablePersonalizer implements TableMetaDataPer
|
||||
Map<String, QFieldMetaData> fieldsToKeep = getFieldsToKeepForTable(tableView, cloneTable, tableActionInput);
|
||||
cloneTable.setFields(fieldsToKeep);
|
||||
|
||||
Map<String, QVirtualFieldMetaData> virtualFieldsToKeep = getVirtualFieldsToKeepForTable(tableView, cloneTable, tableActionInput);
|
||||
cloneTable.setVirtualFields(virtualFieldsToKeep);
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// remove field names which aren't present from sections //
|
||||
///////////////////////////////////////////////////////////
|
||||
@@ -360,6 +364,55 @@ public class CustomizableTableViewsTablePersonalizer implements TableMetaDataPer
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
protected Map<String, QVirtualFieldMetaData> getVirtualFieldsToKeepForTable(TableView tableView, QTableMetaData cloneTable, AbstractTableActionInput tableActionInput)
|
||||
{
|
||||
Map<String, QVirtualFieldMetaData> cloneFields = cloneTable.getVirtualFields();
|
||||
if(cloneFields == null)
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, QVirtualFieldMetaData> fieldsToKeep = new LinkedHashMap<>();
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// figure out which fields the user gets //
|
||||
// start with ones in the user's TableView //
|
||||
/////////////////////////////////////////////
|
||||
for(TableViewField tableViewField : CollectionUtils.nonNullList(tableView.getFields()))
|
||||
{
|
||||
try
|
||||
{
|
||||
String fieldName = tableViewField.getFieldName().split("\\.")[1];
|
||||
QVirtualFieldMetaData fieldMetaData = cloneFields.get(fieldName);
|
||||
if(fieldMetaData != null)
|
||||
{
|
||||
FieldAccessLevel fieldAccessLevel = FieldAccessLevel.getById(tableViewField.getAccessLevel());
|
||||
if(fieldAccessLevel != null)
|
||||
{
|
||||
fieldAccessLevel.apply(fieldMetaData);
|
||||
}
|
||||
|
||||
fieldsToKeep.put(fieldName, fieldMetaData);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error processing tableViewField", e, logPair("fieldName", tableViewField.getFieldName()));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// unlike normal fields, there are no 'always keep' rules for virtual fields //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
return fieldsToKeep;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* check if any system-defined rules make it so that a field should always
|
||||
* be kept.
|
||||
|
||||
+13
-1
@@ -96,6 +96,12 @@ public class CustomizableTableFieldPVS implements QCustomPossibleValueProvider<S
|
||||
{
|
||||
|
|
||||
return (new QPossibleValue<>(idString, fieldMetaData.getLabel()));
|
||||
}
|
||||
|
||||
fieldMetaData = table.getVirtualFields() == null ? null : table.getVirtualFields().get(fieldName);
|
||||
if(fieldMetaData != null)
|
||||
{
|
||||
return (new QPossibleValue<>(idString, fieldMetaData.getLabel()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +134,13 @@ public class CustomizableTableFieldPVS implements QCustomPossibleValueProvider<S
|
||||
|
||||
if(table != null)
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
List<QFieldMetaData> fields = new ArrayList<>(table.getFields().values());
|
||||
if(table.getVirtualFields() != null)
|
||||
{
|
||||
fields.addAll(table.getVirtualFields().values());
|
||||
}
|
||||
|
||||
for(QFieldMetaData field : fields)
|
||||
{
|
||||
if(field.getName().equals(table.getPrimaryKeyField()))
|
||||
{
|
||||
|
||||
+2
-1
@@ -24,6 +24,7 @@ package com.kingsrook.qbits.customizabletableviews.model;
|
||||
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QVirtualFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingPossibleValueEnum;
|
||||
|
||||
@@ -204,7 +205,7 @@ public enum FieldAccessLevel implements PossibleValueEnum<String>
|
||||
return ("This field is defined as hidden by the system - you may not make it " + getLabel());
|
||||
}
|
||||
|
||||
if(!fieldMetaData.getIsEditable())
|
||||
if(!fieldMetaData.getIsEditable() || fieldMetaData instanceof QVirtualFieldMetaData)
|
||||
{
|
||||
if(this == READ_ONLY)
|
||||
{
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ public class TableViewFieldCustomizer implements TableCustomizerInterface
|
||||
String tableName = parts[0];
|
||||
fieldName = parts[1];
|
||||
|
||||
QFieldMetaData field = QContext.getQInstance().getTable(tableName).getField(fieldName);
|
||||
QFieldMetaData field = QContext.getQInstance().getTable(tableName).getFieldOrVirtualField(fieldName);
|
||||
|
||||
FieldAccessLevel fieldAccessLevel = FieldAccessLevel.getById(accessLevelId);
|
||||
if(fieldAccessLevel != null)
|
||||
|
||||
+80
@@ -59,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
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.fields.QVirtualFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
@@ -499,6 +500,85 @@ class CustomizableTableViewsTablePersonalizerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testApplyViewToTableWithVirtualFields()
|
||||
{
|
||||
CustomizableTableViewsTablePersonalizer personalizer = new CustomizableTableViewsTablePersonalizer();
|
||||
TableMetaDataInput tableActionInput = new TableMetaDataInput();
|
||||
|
||||
QTableMetaData baseTable = new QTableMetaData()
|
||||
.withName("vfTable")
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.STRING).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("optional", QFieldType.STRING))
|
||||
.withVirtualField(new QVirtualFieldMetaData("computed", QFieldType.STRING).withLabel("Computed"))
|
||||
.withVirtualField(new QVirtualFieldMetaData("derived", QFieldType.STRING).withLabel("Derived"))
|
||||
.withSection(SectionFactory.defaultT1("id").withName("s0"))
|
||||
.withSection(SectionFactory.defaultT2("optional").withName("s1"));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// empty view - no virtual fields kept (no "always keep" rules for virtuals) //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
QTableMetaData tableMetaData = personalizer.applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
baseTable.clone(), tableActionInput);
|
||||
|
||||
assertEquals(1, tableMetaData.getFields().size());
|
||||
assertNotNull(tableMetaData.getField("id"));
|
||||
assertTrue(tableMetaData.getVirtualFields().isEmpty());
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// view references one virtual field - only that one is kept //
|
||||
///////////////////////////////////////////////////////////////
|
||||
tableMetaData = personalizer.applyViewToTable(
|
||||
new TableView().withFields(List.of(
|
||||
new TableViewField().withFieldName("vfTable.computed").withAccessLevel(READ_ONLY)
|
||||
)),
|
||||
baseTable.clone(), tableActionInput);
|
||||
|
||||
assertEquals(1, tableMetaData.getFields().size());
|
||||
assertEquals(1, tableMetaData.getVirtualFields().size());
|
||||
assertNotNull(tableMetaData.getVirtualFields().get("computed"));
|
||||
assertNull(tableMetaData.getVirtualFields().get("derived"));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// view references both virtual fields and a regular field - all are kept //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
tableMetaData = personalizer.applyViewToTable(
|
||||
new TableView().withFields(List.of(
|
||||
new TableViewField().withFieldName("vfTable.optional").withAccessLevel(EDITABLE_OPTIONAL),
|
||||
new TableViewField().withFieldName("vfTable.computed").withAccessLevel(READ_ONLY),
|
||||
new TableViewField().withFieldName("vfTable.derived").withAccessLevel(READ_ONLY)
|
||||
)),
|
||||
baseTable.clone(), tableActionInput);
|
||||
|
||||
assertEquals(2, tableMetaData.getFields().size()); // id + optional
|
||||
assertEquals(2, tableMetaData.getVirtualFields().size()); // computed + derived
|
||||
|
||||
assertEquals(List.of("optional"), tableMetaData.getSection("s1").getFieldNames());
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// table with no virtual fields at all - should not break //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
QTableMetaData noVfTable = new QTableMetaData()
|
||||
.withName("noVfTable")
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.STRING).withIsEditable(false))
|
||||
.withSection(SectionFactory.defaultT1("id").withName("s0"));
|
||||
|
||||
tableMetaData = personalizer.applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
noVfTable.clone(), tableActionInput);
|
||||
|
||||
assertEquals(1, tableMetaData.getFields().size());
|
||||
assertTrue(tableMetaData.getVirtualFields() == null || tableMetaData.getVirtualFields().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
||||
+60
@@ -32,11 +32,15 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
|
||||
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.fields.QVirtualFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -84,4 +88,60 @@ class CustomizableTableFieldPVSTest extends BaseTest
|
||||
assertEquals("Optional", possibleValues.get(1).getLabel());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testWithVirtualFields() throws QException
|
||||
{
|
||||
QTableMetaData testTable = new QTableMetaData()
|
||||
.withName("testTable")
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.STRING).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("optional", QFieldType.STRING).withLabel("Optional"))
|
||||
.withVirtualField(new QVirtualFieldMetaData("computed", QFieldType.STRING).withLabel("Computed"))
|
||||
.withVirtualField(new QVirtualFieldMetaData("hiddenVirtual", QFieldType.STRING).withIsHidden(true).withLabel("Hidden Virtual"))
|
||||
.withSection(SectionFactory.defaultT1("id").withName("s0"))
|
||||
.withSection(SectionFactory.defaultT2("optional").withName("s1"));
|
||||
QContext.getQInstance().addTable(testTable);
|
||||
|
||||
new InsertAction().execute(new InsertInput(CustomizableTable.TABLE_NAME).withRecordEntities(List.of(
|
||||
new CustomizableTable().withTableName(testTable.getName()).withIsActive(true))));
|
||||
|
||||
Integer tableViewId = 17;
|
||||
new InsertAction().execute(new InsertInput(TableView.TABLE_NAME).withRecordEntities(List.of(
|
||||
new TableView().withId(tableViewId).withCustomizableTableId(1).withName("a"))));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// search should include the non-hidden virtual field in results //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
List<QPossibleValue<String>> possibleValues = new CustomizableTableFieldPVS().search(new SearchPossibleValueSourceInput().withOtherValues(Map.of("tableViewId", tableViewId)));
|
||||
assertEquals(2, possibleValues.size());
|
||||
assertThat(possibleValues)
|
||||
.anyMatch(pv1 -> "testTable.computed".equals(pv1.getId()) && "Computed".equals(pv1.getLabel()))
|
||||
.anyMatch(pv1 -> "testTable.optional".equals(pv1.getId()) && "Optional".equals(pv1.getLabel()));
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// getPossibleValue should resolve a virtual field by table.name //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
QPossibleValue<String> pv = new CustomizableTableFieldPVS().getPossibleValue("testTable.computed");
|
||||
assertNotNull(pv);
|
||||
assertEquals("testTable.computed", pv.getId());
|
||||
assertEquals("Computed", pv.getLabel());
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// getPossibleValue for a normal field still works //
|
||||
/////////////////////////////////////////////////////
|
||||
QPossibleValue<String> pvNormal = new CustomizableTableFieldPVS().getPossibleValue("testTable.optional");
|
||||
assertNotNull(pvNormal);
|
||||
assertEquals("Optional", pvNormal.getLabel());
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// getPossibleValue for a nonexistent field returns null //
|
||||
/////////////////////////////////////////////////////////
|
||||
assertNull(new CustomizableTableFieldPVS().getPossibleValue("testTable.doesNotExist"));
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -25,12 +25,16 @@ package com.kingsrook.qbits.customizabletableviews.model;
|
||||
import java.util.function.BiFunction;
|
||||
import com.kingsrook.qbits.customizabletableviews.BaseTest;
|
||||
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.fields.QVirtualFieldMetaData;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static com.kingsrook.qbits.customizabletableviews.QFieldMetaDataAssert.assertThat;
|
||||
import static com.kingsrook.qbits.customizabletableviews.model.FieldAccessLevel.EDITABLE_OPTIONAL;
|
||||
import static com.kingsrook.qbits.customizabletableviews.model.FieldAccessLevel.EDITABLE_REQUIRED;
|
||||
import static com.kingsrook.qbits.customizabletableviews.model.FieldAccessLevel.READ_ONLY;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -122,4 +126,31 @@ class FieldAccessLevelTest extends BaseTest
|
||||
assertThat(doApply.apply(READ_ONLY, hiddenField))/*....*/.isHidden().isNotRequired().isEditable(); // no changes
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testValidateForVirtualField()
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// a virtual field (even if editable) should be treated as read-only by validate //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
QVirtualFieldMetaData editableVirtualField = new QVirtualFieldMetaData("vf", QFieldType.STRING).withIsEditable(true);
|
||||
|
||||
assertNull(READ_ONLY.validateForField(editableVirtualField));
|
||||
assertNotNull(EDITABLE_REQUIRED.validateForField(editableVirtualField));
|
||||
assertNotNull(EDITABLE_OPTIONAL.validateForField(editableVirtualField));
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// a non-editable virtual field - same results //
|
||||
/////////////////////////////////////////////////
|
||||
QVirtualFieldMetaData readOnlyVirtualField = new QVirtualFieldMetaData("vf", QFieldType.STRING).withIsEditable(false);
|
||||
|
||||
assertNull(READ_ONLY.validateForField(readOnlyVirtualField));
|
||||
assertNotNull(EDITABLE_REQUIRED.validateForField(readOnlyVirtualField));
|
||||
assertNotNull(EDITABLE_OPTIONAL.validateForField(readOnlyVirtualField));
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -32,6 +32,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
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.fields.QVirtualFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -74,6 +75,27 @@ class TableViewFieldCustomizerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testVirtualField() throws QException
|
||||
{
|
||||
QContext.getQInstance().addTable(new QTableMetaData()
|
||||
.withName("testTable2")
|
||||
.withField(new QFieldMetaData("id", QFieldType.STRING))
|
||||
.withVirtualField(new QVirtualFieldMetaData("virtualField", QFieldType.STRING).withLabel("Virtual")));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// virtual fields are treated as read-only, so only READ_ONLY should pass. //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
assertNoError(new TableViewField().withFieldName("testTable2.virtualField").withAccessLevel(FieldAccessLevel.READ_ONLY));
|
||||
assertError(new TableViewField().withFieldName("testTable2.virtualField").withAccessLevel(FieldAccessLevel.EDITABLE_REQUIRED));
|
||||
assertError(new TableViewField().withFieldName("testTable2.virtualField").withAccessLevel(FieldAccessLevel.EDITABLE_OPTIONAL));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
Reference in new issue
Block a user
Possible NPE:
table.getVirtualFields()can be null for tables without virtual fields, so this line will throw instead of returning null for unknown ids (e.g.,"tableName.doesNotExist"). Please null-check before.get(fieldName).