diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProvider.java new file mode 100644 index 00000000..af4b3791 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProvider.java @@ -0,0 +1,111 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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 . + */ + +package com.kingsrook.qqq.backend.core.model.tables; + + +import java.io.Serializable; +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; +import com.kingsrook.qqq.backend.core.actions.tables.GetAction; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.actions.values.BasicCustomPossibleValueProvider; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** possible-value source provider for the `QQQ Table` PVS - a list of all tables + ** in an application/qInstance (that you have permission to see) + *******************************************************************************/ +public class QQQTableCustomPossibleValueProvider extends BasicCustomPossibleValueProvider +{ + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected QPossibleValue makePossibleValue(QRecord sourceObject) + { + return (new QPossibleValue<>(sourceObject.getValueInteger("id"), sourceObject.getValueString("label"))); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected QRecord getSourceObject(Serializable id) throws QException + { + QRecord qqqTableRecord = GetAction.execute(QQQTable.TABLE_NAME, id); + if(qqqTableRecord == null) + { + return (null); + } + + QTableMetaData table = QContext.getQInstance().getTable(qqqTableRecord.getValueString("name")); + return isTableAllowed(table) ? qqqTableRecord : null; + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + protected List getAllSourceObjects() throws QException + { + return (QueryAction.execute(QQQTable.TABLE_NAME, null)); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + private boolean isTableAllowed(QTableMetaData table) + { + if(table == null) + { + return (false); + } + + if(table.getIsHidden()) + { + return (false); + } + + PermissionCheckResult permissionCheckResult = PermissionsHelper.getPermissionCheckResult(new QueryInput(table.getName()), table); + if(!PermissionCheckResult.ALLOW.equals(permissionCheckResult)) + { + return (false); + } + + return (true); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java index e880a230..91712ab9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/tables/QQQTablesMetaDataProvider.java @@ -27,6 +27,9 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel; import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields; 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.tables.Capability; @@ -125,10 +128,11 @@ public class QQQTablesMetaDataProvider public QPossibleValueSource defineQQQTablePossibleValueSource() { return (new QPossibleValueSource() - .withType(QPossibleValueSourceType.TABLE) .withName(QQQTable.TABLE_NAME) - .withTableName(QQQTable.TABLE_NAME)) - .withOrderByField("label"); + .withType(QPossibleValueSourceType.CUSTOM) + .withIdType(QFieldType.INTEGER) + .withCustomCodeReference(new QCodeReference(QQQTableCustomPossibleValueProvider.class)) + .withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)); } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProviderTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProviderTest.java new file mode 100644 index 00000000..a573b2d6 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/tables/QQQTableCustomPossibleValueProviderTest.java @@ -0,0 +1,172 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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 . + */ + +package com.kingsrook.qqq.backend.core.model.tables; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +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.permissions.PermissionLevel; +import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules; +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.TablesCustomPossibleValueProvider; +import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.BeforeEach; +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; + + +/******************************************************************************* + ** Unit test for QQQTableCustomPossibleValueProvider + *******************************************************************************/ +class QQQTableCustomPossibleValueProviderTest extends BaseTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + void beforeEach() throws QException + { + QInstance qInstance = TestUtils.defineInstance(); + + qInstance.addTable(new QTableMetaData() + .withName("hidden") + .withIsHidden(true) + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER))); + + qInstance.addTable(new QTableMetaData() + .withName("restricted") + .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION)) + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER))); + + new QQQTablesMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, TestUtils.MEMORY_BACKEND_NAME, null); + + QContext.init(qInstance, newSession()); + + for(String tableName : qInstance.getTables().keySet()) + { + QQQTableTableManager.getQQQTableId(qInstance, tableName); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGetPossibleValue() throws QException + { + Integer personTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), TestUtils.TABLE_NAME_PERSON); + + QQQTableCustomPossibleValueProvider provider = new QQQTableCustomPossibleValueProvider(); + + QPossibleValue possibleValue = provider.getPossibleValue(personTableId); + assertEquals(personTableId, possibleValue.getId()); + assertEquals("Person", possibleValue.getLabel()); + + assertNull(provider.getPossibleValue(-1)); + + Integer hiddenTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), "hidden"); + assertNull(provider.getPossibleValue(hiddenTableId)); + + Integer restrictedTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), "restricted"); + assertNull(provider.getPossibleValue(restrictedTableId)); + + QContext.getQSession().withPermission("restricted.hasAccess"); + assertNotNull(provider.getPossibleValue(restrictedTableId)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testSearchPossibleValue() throws QException + { + TablesCustomPossibleValueProvider provider = new TablesCustomPossibleValueProvider(); + + List> list = provider.search(new SearchPossibleValueSourceInput() + .withPossibleValueSourceName(TablesPossibleValueSourceMetaDataProvider.NAME)); + assertThat(list).anyMatch(p -> p.getId().equals(TestUtils.TABLE_NAME_PERSON)); + assertThat(list).noneMatch(p -> p.getId().equals("no-such-table")); + assertThat(list).noneMatch(p -> p.getId().equals("hidden")); + assertThat(list).noneMatch(p -> p.getId().equals("restricted")); + assertNull(provider.getPossibleValue("restricted")); + + list = provider.search(new SearchPossibleValueSourceInput() + .withPossibleValueSourceName(TablesPossibleValueSourceMetaDataProvider.NAME) + .withIdList(List.of(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_SHAPE, "hidden"))); + assertEquals(2, list.size()); + assertThat(list).anyMatch(p -> p.getId().equals(TestUtils.TABLE_NAME_PERSON)); + assertThat(list).anyMatch(p -> p.getId().equals(TestUtils.TABLE_NAME_SHAPE)); + assertThat(list).noneMatch(p -> p.getId().equals("hidden")); + + list = provider.search(new SearchPossibleValueSourceInput() + .withPossibleValueSourceName(TablesPossibleValueSourceMetaDataProvider.NAME) + .withLabelList(List.of("Person", "Shape", "Restricted"))); + assertEquals(2, list.size()); + assertThat(list).anyMatch(p -> p.getId().equals(TestUtils.TABLE_NAME_PERSON)); + assertThat(list).anyMatch(p -> p.getId().equals(TestUtils.TABLE_NAME_SHAPE)); + assertThat(list).noneMatch(p -> p.getId().equals("restricted")); + + list = provider.search(new SearchPossibleValueSourceInput() + .withPossibleValueSourceName(TablesPossibleValueSourceMetaDataProvider.NAME) + .withSearchTerm("restricted")); + assertEquals(0, list.size()); + + ///////////////////////////////////////// + // add permission for restricted table // + ///////////////////////////////////////// + QContext.getQSession().withPermission("restricted.hasAccess"); + list = provider.search(new SearchPossibleValueSourceInput() + .withPossibleValueSourceName(TablesPossibleValueSourceMetaDataProvider.NAME) + .withSearchTerm("restricted")); + assertEquals(1, list.size()); + + list = provider.search(new SearchPossibleValueSourceInput() + .withPossibleValueSourceName(TablesPossibleValueSourceMetaDataProvider.NAME) + .withLabelList(List.of("Person", "Shape", "Restricted"))); + assertEquals(3, list.size()); + assertThat(list).anyMatch(p -> p.getId().equals(TestUtils.TABLE_NAME_PERSON)); + assertThat(list).anyMatch(p -> p.getId().equals(TestUtils.TABLE_NAME_SHAPE)); + assertThat(list).anyMatch(p -> p.getId().equals("restricted")); + + } + +} \ No newline at end of file