diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/lib/QueryScreenLib.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/lib/QueryScreenLib.java
index d355f38..fe6d2fb 100644
--- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/lib/QueryScreenLib.java
+++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/lib/QueryScreenLib.java
@@ -261,9 +261,7 @@ public class QueryScreenLib
if(StringUtils.hasContent(value))
{
qSeleniumLib.waitForSelector(".filterValuesColumn INPUT").click();
- // todo - no, not in a listbox/LI here...
- qSeleniumLib.waitForSelectorContaining(".MuiAutocomplete-listbox LI", value).click();
- System.out.println(value);
+ qSeleniumLib.waitForSelector(".filterValuesColumn INPUT").sendKeys(value);
}
qSeleniumLib.clickBackdrop();
@@ -271,6 +269,21 @@ public class QueryScreenLib
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void setBasicBooleanFilter(String fieldLabel, String operatorLabel)
+ {
+ qSeleniumLib.waitForSelectorContaining("BUTTON", fieldLabel).click();
+ qSeleniumLib.waitForMillis(250);
+ qSeleniumLib.waitForSelector("#criteriaOperator").click();
+ qSeleniumLib.waitForSelectorContaining("LI", operatorLabel).click();
+
+ qSeleniumLib.clickBackdrop();
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/SavedReportTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/SavedReportTest.java
new file mode 100755
index 0000000..4d493c3
--- /dev/null
+++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/SavedReportTest.java
@@ -0,0 +1,172 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. 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.frontend.materialdashboard.selenium.tests;
+
+
+import java.util.List;
+import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QBaseSeleniumTest;
+import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QSeleniumLib;
+import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QueryScreenLib;
+import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.javalin.QSeleniumJavalin;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebElement;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+
+/*******************************************************************************
+ ** Test for Saved Report screen (table has some special behaviors)
+ *******************************************************************************/
+public class SavedReportTest extends QBaseSeleniumTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
+ {
+ super.addJavalinRoutes(qSeleniumJavalin);
+ qSeleniumJavalin
+ .withRouteToFile("/metaData/table/savedReport", "metaData/table/savedReport.json")
+ .withRouteToFile("/widget/reportSetupWidget", "widget/reportSetupWidget.json")
+ .withRouteToFile("/widget/pivotTableSetupWidget", "widget/pivotTableSetupWidget.json")
+ .withRouteToFile("/data/savedReport/possibleValues/tableName", "data/savedReport/possibleValues/tableName.json")
+
+ .withRouteToFile("/data/person/count", "data/person/count.json")
+ .withRouteToFile("/data/person/query", "data/person/index.json")
+ ;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testCreate()
+ {
+ qSeleniumLib.gotoAndWaitForBreadcrumbHeaderToContain("/userCustomizations/savedReport/create", "Creating New Saved Report");
+
+ //////////////////////////////////////////////////////////////
+ // make sure things are disabled before a table is selected //
+ //////////////////////////////////////////////////////////////
+ WebElement webElement = qSeleniumLib.waitForSelectorContaining("button", "Edit Filters and Columns");
+ assertEquals("true", webElement.getAttribute("disabled"));
+
+ qSeleniumLib.waitForSelector("#label").click();
+ qSeleniumLib.waitForSelector("#label").sendKeys("My Report");
+
+ qSeleniumLib.waitForSelector("#tableName").click();
+ qSeleniumLib.waitForSelector("#tableName").sendKeys("Person" + Keys.DOWN + Keys.ENTER);
+
+ //////////////////////////////////
+ // make sure things enabled now //
+ //////////////////////////////////
+ webElement = qSeleniumLib.waitForSelectorContaining("button", "Edit Filters and Columns");
+ assertNull(webElement.getAttribute("disabled"));
+
+ ////////////////////////////////////////////////////
+ // open query-screen popup, wait for query to run //
+ ////////////////////////////////////////////////////
+ qSeleniumJavalin.beginCapture();
+ qSeleniumLib.waitForSelectorContaining("button", "Edit Filters and Columns").click();
+ qSeleniumJavalin.waitForCapturedPath("/data/person/count");
+ qSeleniumJavalin.waitForCapturedPath("/data/person/query");
+ qSeleniumJavalin.endCapture();
+
+ QueryScreenLib queryScreenLib = new QueryScreenLib(qSeleniumLib);
+ queryScreenLib.setBasicFilter("First Name", "contains", "Darin");
+
+ ////////////////////////
+ // close query screen //
+ ////////////////////////
+ qSeleniumLib.waitForSelectorContaining("button", "OK").click();
+
+ //////////////////////////////////////////////////////
+ // make sure query things appear on edit screen now //
+ //////////////////////////////////////////////////////
+ qSeleniumLib.waitForSelectorContaining(".advancedQueryString", "First Name");
+ qSeleniumLib.waitForSelectorContaining(".advancedQueryString", "contains");
+ qSeleniumLib.waitForSelectorContaining(".advancedQueryString", "Darin");
+ List columns = qSeleniumLib.waitForSelectorContaining("h5", "Columns")
+ .findElement(QSeleniumLib.PARENT)
+ .findElements(By.cssSelector("DIV"));
+
+ assertThat(columns)
+ .hasSizeGreaterThanOrEqualTo(5) // at least this many
+ .anyMatch(we -> we.getText().equals("Home City")); // a few fields are found
+
+ ///////////////////
+ // turn on pivot //
+ ///////////////////
+ qSeleniumLib.waitForSelectorContaining("label", "Use Pivot Table").click();
+ qSeleniumLib.waitForSelectorContaining("button", "Edit Pivot Table").click();
+ qSeleniumLib.waitForSelectorContaining("h3", "Edit Pivot Table");
+
+ ///////////////
+ // add a row //
+ ///////////////
+ qSeleniumLib.waitForSelectorContaining(".MuiModal-root button", "Add new row").click();
+ WebElement row0Input = qSeleniumLib.waitForSelector("#rows-0");
+ row0Input.click();
+ row0Input.sendKeys("Last Name" + Keys.ENTER);
+
+ //////////////////
+ // add a column //
+ //////////////////
+ qSeleniumLib.waitForSelectorContaining(".MuiModal-root button", "Add new column").click();
+ WebElement column0Input = qSeleniumLib.waitForSelector("#columns-0");
+ column0Input.click();
+ column0Input.sendKeys("Home City" + Keys.ENTER);
+
+ /////////////////
+ // add a value //
+ /////////////////
+ qSeleniumLib.waitForSelectorContaining(".MuiModal-root button", "Add new value").click();
+ WebElement value0Input = qSeleniumLib.waitForSelector("#values-field-0");
+ value0Input.click();
+ value0Input.sendKeys("Id" + Keys.ENTER);
+
+ /////////////////////////////////////////
+ // try to submit - but expect an error //
+ /////////////////////////////////////////
+ qSeleniumLib.waitForSelectorContaining("button", "OK").click();
+ qSeleniumLib.waitForSelectorContaining(".MuiAlert-standard", "Missing value in 1 field.").click();
+
+ ///////////////////////////
+ // now select a function //
+ ///////////////////////////
+ WebElement function0Input = qSeleniumLib.waitForSelector("#values-function-0");
+ function0Input.click();
+ function0Input.sendKeys("Count" + Keys.ENTER);
+
+ qSeleniumLib.waitForSelectorContaining("button", "OK").click();
+ qSeleniumLib.waitForSelectorContainingToNotExist("h3", "Edit Pivot Table");
+
+ // qSeleniumLib.waitForever();
+ }
+
+}
diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenTest.java
index 3dbf608..0cb7ce4 100755
--- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenTest.java
+++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenTest.java
@@ -153,16 +153,16 @@ public class QueryScreenTest extends QBaseSeleniumTest
queryScreenLib.addBasicFilter("Is Employed");
- testBasicCriteria(queryScreenLib, "Is Employed", "equals yes", null, "(?s).*Is Employed:.*yes.*", """
+ testBasicBooleanCriteria(queryScreenLib, "Is Employed", "equals yes", "(?s).*Is Employed:.*yes.*", """
{"fieldName":"isEmployed","operator":"EQUALS","values":[true]}""");
- testBasicCriteria(queryScreenLib, "Is Employed", "equals no", null, "(?s).*Is Employed:.*no.*", """
+ testBasicBooleanCriteria(queryScreenLib, "Is Employed", "equals no", "(?s).*Is Employed:.*no.*", """
{"fieldName":"isEmployed","operator":"EQUALS","values":[false]}""");
- testBasicCriteria(queryScreenLib, "Is Employed", "is empty", null, "(?s).*Is Employed:.*is empty.*", """
+ testBasicBooleanCriteria(queryScreenLib, "Is Employed", "is empty", "(?s).*Is Employed:.*is empty.*", """
{"fieldName":"isEmployed","operator":"IS_BLANK","values":[]}""");
- testBasicCriteria(queryScreenLib, "Is Employed", "is not empty", null, "(?s).*Is Employed:.*is not empty.*", """
+ testBasicBooleanCriteria(queryScreenLib, "Is Employed", "is not empty", "(?s).*Is Employed:.*is not empty.*", """
{"fieldName":"isEmployed","operator":"IS_NOT_BLANK","values":[]}""");
}
@@ -203,10 +203,10 @@ public class QueryScreenTest extends QBaseSeleniumTest
/*******************************************************************************
**
*******************************************************************************/
- private void testBasicCriteria(QueryScreenLib queryScreenLib, String fieldLabel, String operatorLabel, String value, String expectButtonStringRegex, String expectFilterJsonContains)
+ private void testBasicBooleanCriteria(QueryScreenLib queryScreenLib, String fieldLabel, String operatorLabel, String expectButtonStringRegex, String expectFilterJsonContains)
{
qSeleniumJavalin.beginCapture();
- queryScreenLib.setBasicFilter(fieldLabel, operatorLabel, value);
+ queryScreenLib.setBasicBooleanFilter(fieldLabel, operatorLabel);
queryScreenLib.waitForBasicFilterButtonMatchingRegex(expectButtonStringRegex);
qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectFilterJsonContains);
qSeleniumJavalin.endCapture();
diff --git a/src/test/resources/fixtures/data/savedReport/possibleValues/tableName.json b/src/test/resources/fixtures/data/savedReport/possibleValues/tableName.json
new file mode 100644
index 0000000..2f642c6
--- /dev/null
+++ b/src/test/resources/fixtures/data/savedReport/possibleValues/tableName.json
@@ -0,0 +1,16 @@
+{
+ "options": [
+ {
+ "id": "person",
+ "label": "Person"
+ },
+ {
+ "id": "city",
+ "label": "City"
+ },
+ {
+ "id": "savedReport",
+ "label": "Saved Report"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/fixtures/metaData/index.json b/src/test/resources/fixtures/metaData/index.json
index 9fb571f..401fcda 100644
--- a/src/test/resources/fixtures/metaData/index.json
+++ b/src/test/resources/fixtures/metaData/index.json
@@ -189,6 +189,26 @@
"insertPermission": true,
"editPermission": true,
"deletePermission": true
+ },
+ "savedReport": {
+ "name": "savedReport",
+ "label": "Saved Report",
+ "isHidden": false,
+ "iconName": "article",
+ "capabilities": [
+ "TABLE_COUNT",
+ "TABLE_GET",
+ "TABLE_QUERY",
+ "QUERY_STATS",
+ "TABLE_UPDATE",
+ "TABLE_INSERT",
+ "TABLE_DELETE"
+ ],
+ "readPermission": true,
+ "insertPermission": true,
+ "editPermission": true,
+ "deletePermission": true,
+ "usesVariants": false
}
},
"processes": {
@@ -420,6 +440,40 @@
}
]
},
+ "userCustomizations": {
+ "name": "userCustomizations",
+ "label": "User Customizations",
+ "iconName": "article",
+ "widgets": [],
+ "children": [
+ {
+ "type": "TABLE",
+ "name": "savedReport",
+ "label": "Saved Report",
+ "iconName": "article"
+ }
+ ],
+ "childMap": {
+ "savedReport": {
+ "type": "TABLE",
+ "name": "savedReport",
+ "label": "Saved Report",
+ "iconName": "article"
+ }
+ },
+ "sections": [
+ {
+ "name": "userCustomizations",
+ "label": "User Customizations",
+ "icon": {
+ "name": "badge"
+ },
+ "tables": [
+ "savedReport"
+ ]
+ }
+ ]
+ },
"miscellaneous": {
"name": "miscellaneous",
"label": "Miscellaneous",
@@ -730,6 +784,20 @@
}
],
"iconName": "data_object"
+ },
+ {
+ "type": "APP",
+ "name": "userCustomizations",
+ "label": "User Customizations",
+ "children": [
+ {
+ "type": "TABLE",
+ "name": "savedReport",
+ "label": "Saved Report",
+ "iconName": "article"
+ }
+ ],
+ "iconName": "data_object"
}
],
"branding": {
@@ -750,6 +818,28 @@
"isCard": true,
"storeDropdownSelections": false,
"hasPermission": true
+ },
+ "reportSetupWidget": {
+ "name": "reportSetupWidget",
+ "label": "Filters and Columns",
+ "type": "reportSetup",
+ "isCard": true,
+ "storeDropdownSelections": false,
+ "showReloadButton": true,
+ "showExportButton": false,
+ "defaultValues": {},
+ "hasPermission": true
+ },
+ "pivotTableSetupWidget": {
+ "name": "pivotTableSetupWidget",
+ "label": "Pivot Table",
+ "type": "pivotTableSetup",
+ "isCard": true,
+ "storeDropdownSelections": false,
+ "showReloadButton": true,
+ "showExportButton": false,
+ "defaultValues": {},
+ "hasPermission": true
}
},
"environmentValues": {
diff --git a/src/test/resources/fixtures/metaData/table/savedReport.json b/src/test/resources/fixtures/metaData/table/savedReport.json
new file mode 100644
index 0000000..71e256b
--- /dev/null
+++ b/src/test/resources/fixtures/metaData/table/savedReport.json
@@ -0,0 +1,218 @@
+{
+ "table": {
+ "name": "savedReport",
+ "label": "Saved Report",
+ "isHidden": false,
+ "primaryKeyField": "id",
+ "iconName": "article",
+ "fields": {
+ "queryFilterJson": {
+ "name": "queryFilterJson",
+ "label": "Query Filter",
+ "type": "STRING",
+ "isRequired": false,
+ "isEditable": true,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ },
+ "columnsJson": {
+ "name": "columnsJson",
+ "label": "Columns",
+ "type": "STRING",
+ "isRequired": false,
+ "isEditable": true,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ },
+ "inputFieldsJson": {
+ "name": "inputFieldsJson",
+ "label": "Input Fields",
+ "type": "STRING",
+ "isRequired": false,
+ "isEditable": true,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ },
+ "pivotTableJson": {
+ "name": "pivotTableJson",
+ "label": "Pivot Table",
+ "type": "STRING",
+ "isRequired": false,
+ "isEditable": true,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ },
+ "modifyDate": {
+ "name": "modifyDate",
+ "label": "Modify Date",
+ "type": "DATE_TIME",
+ "isRequired": false,
+ "isEditable": false,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ },
+ "label": {
+ "name": "label",
+ "label": "Report Name",
+ "type": "STRING",
+ "isRequired": true,
+ "isEditable": true,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ },
+ "id": {
+ "name": "id",
+ "label": "Id",
+ "type": "INTEGER",
+ "isRequired": false,
+ "isEditable": false,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ },
+ "userId": {
+ "name": "userId",
+ "label": "User Id",
+ "type": "STRING",
+ "isRequired": false,
+ "isEditable": true,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ },
+ "tableName": {
+ "name": "tableName",
+ "label": "Table",
+ "type": "STRING",
+ "isRequired": true,
+ "isEditable": true,
+ "isHeavy": false,
+ "possibleValueSourceName": "tables",
+ "displayFormat": "%s"
+ },
+ "createDate": {
+ "name": "createDate",
+ "label": "Create Date",
+ "type": "DATE_TIME",
+ "isRequired": false,
+ "isEditable": false,
+ "isHeavy": false,
+ "displayFormat": "%s"
+ }
+ },
+ "sections": [
+ {
+ "name": "identity",
+ "label": "Identity",
+ "tier": "T1",
+ "fieldNames": [
+ "id",
+ "label",
+ "tableName"
+ ],
+ "icon": {
+ "name": "badge"
+ },
+ "isHidden": false
+ },
+ {
+ "name": "filtersAndColumns",
+ "label": "Filters and Columns",
+ "tier": "T2",
+ "widgetName": "reportSetupWidget",
+ "icon": {
+ "name": "table_chart"
+ },
+ "isHidden": false
+ },
+ {
+ "name": "pivotTable",
+ "label": "Pivot Table",
+ "tier": "T2",
+ "widgetName": "pivotTableSetupWidget",
+ "icon": {
+ "name": "pivot_table_chart"
+ },
+ "isHidden": false
+ },
+ {
+ "name": "data",
+ "label": "Data",
+ "tier": "T2",
+ "fieldNames": [
+ "queryFilterJson",
+ "columnsJson",
+ "pivotTableJson"
+ ],
+ "icon": {
+ "name": "text_snippet"
+ },
+ "isHidden": true
+ },
+ {
+ "name": "hidden",
+ "label": "Hidden",
+ "tier": "T2",
+ "fieldNames": [
+ "inputFieldsJson",
+ "userId"
+ ],
+ "icon": {
+ "name": "text_snippet"
+ },
+ "isHidden": true
+ },
+ {
+ "name": "dates",
+ "label": "Dates",
+ "tier": "T3",
+ "fieldNames": [
+ "createDate",
+ "modifyDate"
+ ],
+ "icon": {
+ "name": "calendar_month"
+ },
+ "isHidden": false
+ }
+ ],
+ "exposedJoins": [],
+ "supplementalTableMetaData": {
+ "materialDashboard": {
+ "fieldRules": [
+ {
+ "trigger": "ON_CHANGE",
+ "sourceField": "tableName",
+ "action": "CLEAR_TARGET_FIELD",
+ "targetField": "queryFilterJson"
+ },
+ {
+ "trigger": "ON_CHANGE",
+ "sourceField": "tableName",
+ "action": "CLEAR_TARGET_FIELD",
+ "targetField": "columnsJson"
+ },
+ {
+ "trigger": "ON_CHANGE",
+ "sourceField": "tableName",
+ "action": "CLEAR_TARGET_FIELD",
+ "targetField": "pivotTableJson"
+ }
+ ],
+ "type": "materialDashboard"
+ }
+ },
+ "capabilities": [
+ "TABLE_COUNT",
+ "TABLE_GET",
+ "TABLE_QUERY",
+ "QUERY_STATS",
+ "TABLE_UPDATE",
+ "TABLE_INSERT",
+ "TABLE_DELETE"
+ ],
+ "readPermission": true,
+ "insertPermission": true,
+ "editPermission": true,
+ "deletePermission": true,
+ "usesVariants": false
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/fixtures/widget/pivotTableSetupWidget.json b/src/test/resources/fixtures/widget/pivotTableSetupWidget.json
new file mode 100644
index 0000000..938d365
--- /dev/null
+++ b/src/test/resources/fixtures/widget/pivotTableSetupWidget.json
@@ -0,0 +1 @@
+{"type":"pivotTableSetup"}
\ No newline at end of file
diff --git a/src/test/resources/fixtures/widget/reportSetupWidget.json b/src/test/resources/fixtures/widget/reportSetupWidget.json
new file mode 100644
index 0000000..9fa1cb1
--- /dev/null
+++ b/src/test/resources/fixtures/widget/reportSetupWidget.json
@@ -0,0 +1 @@
+{"type":"reportSetup"}
\ No newline at end of file