feat: always keep fields that are part of any record locks on a table… #3
No files matched your search
+118
-28
@@ -37,6 +37,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.CustomizableTable;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.CustomizableTableViewsFieldMetaData;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.FieldAccessLevel;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.TableView;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.TableViewField;
|
||||
@@ -70,6 +71,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
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.security.MultiRecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
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.session.QUser;
|
||||
@@ -262,7 +266,7 @@ public class CustomizableTableViewsTablePersonalizer implements TableMetaDataPer
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
private Set<String> getFieldsToKeepFromJoinTable(String joinTableName, AbstractTableActionInput tableActionInput, Map<String, Set<String>> getFieldsToKeepFromJoinTableCache)
|
||||
protected Set<String> getFieldsToKeepFromJoinTable(String joinTableName, AbstractTableActionInput tableActionInput, Map<String, Set<String>> getFieldsToKeepFromJoinTableCache)
|
||||
{
|
||||
return (getFieldsToKeepFromJoinTableCache.computeIfAbsent(joinTableName, k ->
|
||||
{
|
||||
@@ -298,7 +302,7 @@ public class CustomizableTableViewsTablePersonalizer implements TableMetaDataPer
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
private static Map<String, QFieldMetaData> getFieldsToKeepForTable(TableView tableView, QTableMetaData cloneTable, AbstractTableActionInput tableActionInput)
|
||||
protected Map<String, QFieldMetaData> getFieldsToKeepForTable(TableView tableView, QTableMetaData cloneTable, AbstractTableActionInput tableActionInput)
|
||||
{
|
||||
Map<String, QFieldMetaData> cloneFields = cloneTable.getFields();
|
||||
if(cloneFields == null)
|
||||
@@ -339,43 +343,129 @@ public class CustomizableTableViewsTablePersonalizer implements TableMetaDataPer
|
||||
/////////////////////////////////////////////
|
||||
// next look for ones that always get kept //
|
||||
/////////////////////////////////////////////
|
||||
boolean isInsertOrUpdate = (tableActionInput instanceof InsertInput) || (tableActionInput instanceof UpdateInput);
|
||||
for(QFieldMetaData field : cloneTable.getFields().values())
|
||||
{
|
||||
String fieldName = field.getName();
|
||||
if(fieldsToKeep.containsKey(fieldName))
|
||||
if(!fieldsToKeep.containsKey(fieldName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DynamicDefaultValueBehavior x = null;
|
||||
boolean keep = false;
|
||||
if(field.getIsRequired() || Objects.equals(fieldName, cloneTable.getPrimaryKeyField()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////
|
||||
// always keep the primary key field and required fields //
|
||||
///////////////////////////////////////////////////////////
|
||||
keep = true;
|
||||
}
|
||||
else if(isInsertOrUpdate && field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class) != null)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if this is for an insert or update, then keep any fields that have a DynamicDefaultValueBehavior //
|
||||
// e.g., createDates, modifyDates, things that capture userId //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
keep = true;
|
||||
}
|
||||
|
||||
if(keep)
|
||||
{
|
||||
fieldsToKeep.put(fieldName, cloneFields.get(fieldName));
|
||||
if(shouldKeepFieldDueToRules(cloneTable, field, tableActionInput))
|
||||
{
|
||||
fieldsToKeep.put(fieldName, cloneFields.get(fieldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fieldsToKeep;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* check if any system-defined rules make it so that a field should always
|
||||
* be kept.
|
||||
*
|
||||
* <p>built-in rules are:</p>
|
||||
* <ul>
|
||||
* <li>field is required</li>
|
||||
* <li>field is primary key</li>
|
||||
* <li>field is part of table's security locks</li>
|
||||
* <li>there's supplemental metadata from this qbit that says to keep the field</li>
|
||||
* <li>this is an insert or update, and there's a {@link DynamicDefaultValueBehavior}</li>
|
||||
* </ul>
|
||||
*
|
||||
* also - an application could override this method in a subclass to add
|
||||
* additional rules.
|
||||
***************************************************************************/
|
||||
protected boolean shouldKeepFieldDueToRules(QTableMetaData cloneTable, QFieldMetaData field, AbstractTableActionInput tableActionInput)
|
||||
{
|
||||
if(field.getIsRequired() || Objects.equals(field.getName(), cloneTable.getPrimaryKeyField()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////
|
||||
// always keep the primary key field and required fields //
|
||||
///////////////////////////////////////////////////////////
|
||||
return (true);
|
||||
}
|
||||
|
||||
if(doRecordSecurityLocksContainField(cloneTable.getRecordSecurityLocks(), field))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////
|
||||
// keep fields that are part of the table's security locks //
|
||||
/////////////////////////////////////////////////////////////
|
||||
return (true);
|
||||
}
|
||||
|
||||
if(doesSupplementalMetaDataSayToKeepField(field))
|
||||
{
|
||||
//////////////////////////////////////////////////////////
|
||||
// keep a field if it's marked as being always-included //
|
||||
//////////////////////////////////////////////////////////
|
||||
return (true);
|
||||
}
|
||||
|
||||
boolean isInsertOrUpdate = (tableActionInput instanceof InsertInput) || (tableActionInput instanceof UpdateInput);
|
||||
if(isInsertOrUpdate && field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class) != null)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if this is for an insert or update, then keep any fields that have a DynamicDefaultValueBehavior //
|
||||
// e.g., createDates, modifyDates, things that capture userId //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (true);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// by default, no rule matched, so say no //
|
||||
////////////////////////////////////////////
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Check if the supplemental meta data from this qbit for a field says
|
||||
* that the field should always be kept.
|
||||
***************************************************************************/
|
||||
protected boolean doesSupplementalMetaDataSayToKeepField(QFieldMetaData field)
|
||||
{
|
||||
QSupplementalFieldMetaData supplementalMetaData = field.getSupplementalMetaData(CustomizableTableViewsFieldMetaData.TYPE);
|
||||
if(supplementalMetaData instanceof CustomizableTableViewsFieldMetaData customizableTableViewsFieldMetaData)
|
||||
{
|
||||
if(CustomizableTableViewsFieldMetaData.Rule.ALWAYS_KEEP_FIELD.equals(customizableTableViewsFieldMetaData.getRule()))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Check if a list of record security locks include a field, recursively
|
||||
* processing {@link MultiRecordSecurityLock}s
|
||||
***************************************************************************/
|
||||
protected boolean doRecordSecurityLocksContainField(List<RecordSecurityLock> locks, QFieldMetaData field)
|
||||
{
|
||||
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(locks))
|
||||
{
|
||||
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
|
||||
{
|
||||
if(doRecordSecurityLocksContainField(multiRecordSecurityLock.getLocks(), field))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
else if(field.getName().equals(recordSecurityLock.getFieldName()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* the assumption is, that this method is called AFTER we've identified that
|
||||
* a table is customizable (and customizaztion is active) - so - if we fail
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qbits.customizabletableviews.model;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QSupplementalFieldMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class CustomizableTableViewsFieldMetaData extends QSupplementalFieldMetaData
|
||||
{
|
||||
public static final String TYPE = CustomizableTableViewsFieldMetaData.class.getName();
|
||||
|
||||
private Rule rule;
|
||||
|
||||
public enum Rule
|
||||
{
|
||||
ALWAYS_KEEP_FIELD
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getType()
|
||||
{
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Getter for rule
|
||||
* @see #withRule(Rule)
|
||||
*******************************************************************************/
|
||||
public Rule getRule()
|
||||
{
|
||||
return (this.rule);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Setter for rule
|
||||
* @see #withRule(Rule)
|
||||
*******************************************************************************/
|
||||
public void setRule(Rule rule)
|
||||
{
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Fluent setter for rule
|
||||
*
|
||||
* @param rule
|
||||
* TODO document this property
|
||||
*
|
||||
* @return this
|
||||
*******************************************************************************/
|
||||
public CustomizableTableViewsFieldMetaData withRule(Rule rule)
|
||||
{
|
||||
this.rule = rule;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+154
@@ -34,6 +34,7 @@ import java.util.stream.Collectors;
|
||||
import com.kingsrook.qbits.customizabletableviews.BaseTest;
|
||||
import com.kingsrook.qbits.customizabletableviews.QFieldMetaDataAssert;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.CustomizableTable;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.CustomizableTableViewsFieldMetaData;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.FieldAccessLevel;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.TableView;
|
||||
import com.kingsrook.qbits.customizabletableviews.model.TableViewField;
|
||||
@@ -61,6 +62,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
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;
|
||||
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.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
@@ -548,6 +551,157 @@ class CustomizableTableViewsTablePersonalizerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testFieldsInSecurityLocks()
|
||||
{
|
||||
///////////////////////////
|
||||
// empty with empty case //
|
||||
///////////////////////////
|
||||
QTableMetaData baseTable = new QTableMetaData()
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.STRING).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("a", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("b", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("c", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("d", QFieldType.STRING));
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// with no security field, only id should come back //
|
||||
//////////////////////////////////////////////////////
|
||||
{
|
||||
QTableMetaData tableMetaData = new CustomizableTableViewsTablePersonalizer().applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
baseTable.clone(), new QueryInput());
|
||||
|
||||
assertEquals(1, tableMetaData.getFields().size());
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("id")).isNotNull();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// with a simple security field, it should come back //
|
||||
///////////////////////////////////////////////////////
|
||||
baseTable.setRecordSecurityLocks(List.of(
|
||||
new RecordSecurityLock().withFieldName("a")
|
||||
));
|
||||
{
|
||||
QTableMetaData tableMetaData = new CustomizableTableViewsTablePersonalizer().applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
baseTable.clone(), new QueryInput());
|
||||
|
||||
assertEquals(2, tableMetaData.getFields().size());
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("id")).isNotNull();
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("a")).isNotNull();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// with a multi-lock, all included fields should come back //
|
||||
/////////////////////////////////////////////////////////////
|
||||
baseTable.setRecordSecurityLocks(List.of(
|
||||
new MultiRecordSecurityLock()
|
||||
.withOperator(MultiRecordSecurityLock.BooleanOperator.OR)
|
||||
.withLocks(List.of(
|
||||
new RecordSecurityLock().withFieldName("b"),
|
||||
new MultiRecordSecurityLock()
|
||||
.withOperator(MultiRecordSecurityLock.BooleanOperator.AND)
|
||||
.withLocks(List.of(
|
||||
new RecordSecurityLock().withFieldName("c"),
|
||||
new RecordSecurityLock().withFieldName("d")
|
||||
))
|
||||
))
|
||||
));
|
||||
{
|
||||
QTableMetaData tableMetaData = new CustomizableTableViewsTablePersonalizer().applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
baseTable.clone(), new QueryInput());
|
||||
|
||||
assertEquals(4, tableMetaData.getFields().size());
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("id")).isNotNull();
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("b")).isNotNull();
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("c")).isNotNull();
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("d")).isNotNull();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// make sure lock field from join table doesn't break us //
|
||||
///////////////////////////////////////////////////////////
|
||||
baseTable.setRecordSecurityLocks(List.of(
|
||||
new RecordSecurityLock().withFieldName("join.a")
|
||||
));
|
||||
{
|
||||
QTableMetaData tableMetaData = new CustomizableTableViewsTablePersonalizer().applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
baseTable.clone(), new QueryInput());
|
||||
|
||||
assertEquals(1, tableMetaData.getFields().size());
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("id")).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testFieldsWithSupplementalMetaData()
|
||||
{
|
||||
///////////////////////////
|
||||
// empty with empty case //
|
||||
///////////////////////////
|
||||
QTableMetaData baseTable = new QTableMetaData()
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.STRING).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("a", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("b", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("c", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("d", QFieldType.STRING));
|
||||
|
||||
//////////////////////////////////
|
||||
// base case, should just be id //
|
||||
//////////////////////////////////
|
||||
{
|
||||
QTableMetaData tableMetaData = new CustomizableTableViewsTablePersonalizer().applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
baseTable.clone(), new QueryInput());
|
||||
|
||||
assertEquals(1, tableMetaData.getFields().size());
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("id")).isNotNull();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// supplemental meta data, but without a rule should not be included //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
baseTable.getField("a").withSupplementalMetaData(new CustomizableTableViewsFieldMetaData());
|
||||
{
|
||||
QTableMetaData tableMetaData = new CustomizableTableViewsTablePersonalizer().applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
baseTable.clone(), new QueryInput());
|
||||
|
||||
assertEquals(1, tableMetaData.getFields().size());
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("id")).isNotNull();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// supplemental meta data, with the include rule should be included //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
baseTable.getField("b").withSupplementalMetaData(new CustomizableTableViewsFieldMetaData()
|
||||
.withRule(CustomizableTableViewsFieldMetaData.Rule.ALWAYS_KEEP_FIELD));
|
||||
{
|
||||
QTableMetaData tableMetaData = new CustomizableTableViewsTablePersonalizer().applyViewToTable(
|
||||
new TableView().withFields(List.of()),
|
||||
baseTable.clone(), new QueryInput());
|
||||
|
||||
assertEquals(2, tableMetaData.getFields().size());
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("id")).isNotNull();
|
||||
QFieldMetaDataAssert.assertThat(tableMetaData.getField("b")).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
||||
Reference in new issue
Block a user