Merged dev into feature/meta-data-loaders

This commit is contained in:
2025-03-08 20:05:25 -06:00
346 changed files with 33987 additions and 5327 deletions

View File

@ -68,17 +68,36 @@
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-material-dashboard</artifactId>
<version>0.24.0-SNAPSHOT</version>
<version>0.24.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.220</version>
<scope>test</scope>
</dependency>
<!-- 3rd party deps specifically for this module -->
<!-- none at this time -->
<!-- slf4j, to allow logging for javalin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.6</version>
</dependency>
<!-- deps for frontend (selenium) tests -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.19.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<!-- Common deps for all qqq modules -->
<dependency>

View File

@ -22,7 +22,10 @@
package com.kingsrook.sampleapp.metadata;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -49,8 +52,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QuickSightChartMe
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
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.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.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
@ -59,6 +67,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMet
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
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;
@ -71,9 +80,12 @@ import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.Cardinali
import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.RecordFormat;
import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemBackendMetaData;
import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemTableBackendDetails;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import com.kingsrook.sampleapp.dashboard.widgets.PersonsByCreateDateBarChart;
import com.kingsrook.sampleapp.processes.clonepeople.ClonePeopleTransformStep;
import org.apache.commons.io.IOUtils;
/*******************************************************************************
@ -81,7 +93,7 @@ import com.kingsrook.sampleapp.processes.clonepeople.ClonePeopleTransformStep;
*******************************************************************************/
public class SampleMetaDataProvider extends AbstractQQQApplication
{
public static boolean USE_MYSQL = true;
public static boolean USE_MYSQL = false;
public static final String RDBMS_BACKEND_NAME = "rdbms";
public static final String FILESYSTEM_BACKEND_NAME = "filesystem";
@ -98,6 +110,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
public static final String PROCESS_NAME_SLEEP_INTERACTIVE = "sleepInteractive";
public static final String TABLE_NAME_PERSON = "person";
public static final String TABLE_NAME_PET = "pet";
public static final String TABLE_NAME_CARRIER = "carrier";
public static final String TABLE_NAME_CITY = "city";
@ -109,7 +122,6 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
/***************************************************************************
**
***************************************************************************/
@ -120,6 +132,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
}
/*******************************************************************************
**
*******************************************************************************/
@ -132,6 +145,10 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
qInstance.addBackend(defineFilesystemBackend());
qInstance.addTable(defineTableCarrier());
qInstance.addTable(defineTablePerson());
qInstance.addPossibleValueSource(QPossibleValueSource.newForTable(TABLE_NAME_PERSON));
qInstance.addPossibleValueSource(QPossibleValueSource.newForEnum(PetSpecies.NAME, PetSpecies.values()));
qInstance.addTable(defineTablePet());
qInstance.addJoin(defineTablePersonJoinPet());
qInstance.addTable(defineTableCityFile());
qInstance.addProcess(defineProcessGreetPeople());
qInstance.addProcess(defineProcessGreetPeopleInteractive());
@ -151,6 +168,26 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
/*******************************************************************************
**
*******************************************************************************/
public static void primeTestDatabase(String sqlFileName) throws Exception
{
try(Connection connection = ConnectionManager.getConnection(SampleMetaDataProvider.defineRdbmsBackend()))
{
InputStream primeTestDatabaseSqlStream = SampleMetaDataProvider.class.getResourceAsStream("/" + sqlFileName);
List<String> lines = IOUtils.readLines(primeTestDatabaseSqlStream, StandardCharsets.UTF_8);
lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
String joinedSQL = String.join("\n", lines);
for(String sql : joinedSQL.split(";"))
{
QueryManager.executeUpdate(connection, sql);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -204,6 +241,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
.withIcon(new QIcon().withName("emoji_people"))
.withChild(qInstance.getProcess(PROCESS_NAME_GREET).withIcon(new QIcon().withName("emoji_people")))
.withChild(qInstance.getTable(TABLE_NAME_PERSON).withIcon(new QIcon().withName("person")))
.withChild(qInstance.getTable(TABLE_NAME_PET).withIcon(new QIcon().withName("pets")))
.withChild(qInstance.getTable(TABLE_NAME_CITY).withIcon(new QIcon().withName("location_city")))
.withChild(qInstance.getProcess(PROCESS_NAME_GREET_INTERACTIVE).withIcon(new QIcon().withName("waving_hand")))
.withWidgets(List.of(PersonsByCreateDateBarChart.class.getSimpleName(), QuickSightChartRenderer.class.getSimpleName()))
@ -340,7 +378,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
.withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name").withIsRequired(true))
.withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name").withIsRequired(true))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
.withField(new QFieldMetaData("email", QFieldType.STRING))
.withField(new QFieldMetaData("email", QFieldType.STRING).withIsRequired(true))
.withField(new QFieldMetaData("isEmployed", QFieldType.BOOLEAN).withBackendName("is_employed"))
.withField(new QFieldMetaData("annualSalary", QFieldType.DECIMAL).withBackendName("annual_salary").withDisplayFormat(DisplayFormat.CURRENCY))
.withField(new QFieldMetaData("daysWorked", QFieldType.INTEGER).withBackendName("days_worked").withDisplayFormat(DisplayFormat.COMMAS))
@ -352,11 +390,62 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
QInstanceEnricher.setInferredFieldBackendNames(qTableMetaData);
qTableMetaData.withAssociation(new Association()
.withAssociatedTableName(TABLE_NAME_PET)
.withName("pets")
.withJoinName(QJoinMetaData.makeInferredJoinName(TABLE_NAME_PERSON, TABLE_NAME_PET)));
return (qTableMetaData);
}
/*******************************************************************************
**
*******************************************************************************/
public static QTableMetaData defineTablePet()
{
QTableMetaData qTableMetaData = new QTableMetaData()
.withName(TABLE_NAME_PET)
.withLabel("Pet")
.withBackendName(RDBMS_BACKEND_NAME)
.withPrimaryKeyField("id")
.withRecordLabelFormat("%s %s")
.withRecordLabelFields("name")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date").withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date").withIsEditable(false))
.withField(new QFieldMetaData("name", QFieldType.STRING).withBackendName("name").withIsRequired(true))
.withField(new QFieldMetaData("personId", QFieldType.INTEGER).withBackendName("person_id").withIsRequired(true).withPossibleValueSourceName(TABLE_NAME_PERSON))
.withField(new QFieldMetaData("speciesId", QFieldType.INTEGER).withBackendName("species_id").withIsRequired(true).withPossibleValueSourceName(PetSpecies.NAME))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
.withSection(new QFieldSection("identity", "Identity", new QIcon("badge"), Tier.T1, List.of("id", "name")))
.withSection(new QFieldSection("basicInfo", "Basic Info", new QIcon("dataset"), Tier.T2, List.of("personId", "speciesId", "birthDate")))
.withSection(new QFieldSection("dates", "Dates", new QIcon("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
QInstanceEnricher.setInferredFieldBackendNames(qTableMetaData);
return (qTableMetaData);
}
/***************************************************************************
**
***************************************************************************/
private static QJoinMetaData defineTablePersonJoinPet()
{
return new QJoinMetaData()
.withLeftTable(TABLE_NAME_PERSON)
.withRightTable(TABLE_NAME_PET)
.withInferredName()
.withType(JoinType.ONE_TO_MANY)
.withJoinOn(new JoinOn("id", "personId"));
}
/*******************************************************************************
**
*******************************************************************************/
@ -390,7 +479,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
.withLabel("Greet People")
.withTableName(TABLE_NAME_PERSON)
.withIsHidden(true)
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("prepare")
.withCode(new QCodeReference(MockBackendStep.class))
.withInputData(new QFunctionInputMetaData()
@ -419,16 +508,16 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
.withName(PROCESS_NAME_GREET_INTERACTIVE)
.withTableName(TABLE_NAME_PERSON)
.addStep(LoadInitialRecordsStep.defineMetaData(TABLE_NAME_PERSON))
.withStep(LoadInitialRecordsStep.defineMetaData(TABLE_NAME_PERSON))
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("setup")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
.withFormField(new QFieldMetaData("greetingPrefix", QFieldType.STRING))
.withFormField(new QFieldMetaData("greetingSuffix", QFieldType.STRING))
)
.addStep(new QBackendStepMetaData()
.withStep(new QBackendStepMetaData()
.withName("doWork")
.withCode(new QCodeReference()
.withName(MockBackendStep.class.getName())
@ -447,7 +536,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
.withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING))))
)
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName("results")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.VIEW_FORM))
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.RECORD_LIST))
@ -499,7 +588,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
return new QProcessMetaData()
.withName(PROCESS_NAME_SIMPLE_SLEEP)
.withIsHidden(true)
.addStep(SleeperStep.getMetaData());
.withStep(SleeperStep.getMetaData());
}
@ -511,12 +600,12 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
{
return new QProcessMetaData()
.withName(PROCESS_NAME_SLEEP_INTERACTIVE)
.addStep(new QFrontendStepMetaData()
.withStep(new QFrontendStepMetaData()
.withName(SCREEN_0)
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.VIEW_FORM))
.withFormField(new QFieldMetaData("outputMessage", QFieldType.STRING)))
.addStep(SleeperStep.getMetaData())
.addStep(new QFrontendStepMetaData()
.withStep(SleeperStep.getMetaData())
.withStep(new QFrontendStepMetaData()
.withName(SCREEN_1)
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.VIEW_FORM))
.withFormField(new QFieldMetaData("outputMessage", QFieldType.STRING)));
@ -531,7 +620,7 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
{
return new QProcessMetaData()
.withName(PROCESS_NAME_SIMPLE_THROW)
.addStep(ThrowerStep.getMetaData());
.withStep(ThrowerStep.getMetaData());
}
@ -637,4 +726,53 @@ public class SampleMetaDataProvider extends AbstractQQQApplication
}
}
/***************************************************************************
**
***************************************************************************/
public enum PetSpecies implements PossibleValueEnum<Integer>
{
DOG(1, "Dog"),
CAT(2, "Cat");
private final Integer id;
private final String label;
public static final String NAME = "petSpecies";
/***************************************************************************
**
***************************************************************************/
PetSpecies(int id, String label)
{
this.id = id;
this.label = label;
}
/***************************************************************************
**
***************************************************************************/
@Override
public Integer getPossibleValueId()
{
return (id);
}
/***************************************************************************
**
***************************************************************************/
@Override
public String getPossibleValueLabel()
{
return (label);
}
}
}

View File

@ -42,6 +42,27 @@ INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, a
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 1, 950000, 75);
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 0, 1500000, 1);
DROP TABLE IF EXISTS pet;
CREATE TABLE pet
(
id INT AUTO_INCREMENT primary key ,
create_date TIMESTAMP DEFAULT now(),
modify_date TIMESTAMP DEFAULT now(),
name VARCHAR(80) NOT NULL,
species_id INTEGER NOT NULL,
person_id INTEGER NOT NULL,
birth_date DATE
);
INSERT INTO pet (id, name, species_id, person_id) VALUES (1, 'Charlie', 1, 1);
INSERT INTO pet (id, name, species_id, person_id) VALUES (2, 'Coco', 1, 1);
INSERT INTO pet (id, name, species_id, person_id) VALUES (3, 'Louie', 1, 1);
INSERT INTO pet (id, name, species_id, person_id) VALUES (4, 'Barkley', 1, 1);
INSERT INTO pet (id, name, species_id, person_id) VALUES (5, 'Toby', 1, 2);
INSERT INTO pet (id, name, species_id, person_id) VALUES (6, 'Mae', 2, 3);
DROP TABLE IF EXISTS carrier;
CREATE TABLE carrier
(

View File

@ -0,0 +1,101 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.sampleapp.selenium;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.sampleapp.SampleJavalinServer;
import org.junit.jupiter.api.BeforeEach;
/*******************************************************************************
**
*******************************************************************************/
public class BaseSampleSeleniumTest // extends QBaseSeleniumTest
{
private static final QLogger LOG = QLogger.getLogger(BaseSampleSeleniumTest.class);
public static final Integer DEFAULT_WAIT_SECONDS = 10;
private int port = 8011;
///*******************************************************************************
// **
// *******************************************************************************/
//@Override
//@BeforeEach
//public void beforeEach()
//{
// super.beforeEach();
// qSeleniumLib.withBaseUrl("http://localhost:" + port);
// qSeleniumLib.withWaitSeconds(DEFAULT_WAIT_SECONDS);
//
// new SampleJavalinServer().startJavalinServer(port);
//}
//
//
//
///*******************************************************************************
// **
// *******************************************************************************/
//@Override
//protected boolean useInternalJavalin()
//{
// return (false);
//}
//
//
//
//
///*******************************************************************************
// **
// *******************************************************************************/
//public void clickLeftNavMenuItem(String text)
//{
// qSeleniumLib.waitForSelectorContaining(".MuiDrawer-paperAnchorLeft .MuiListItem-root", text).click();
//}
//
//
//
///*******************************************************************************
// **
// *******************************************************************************/
//public void clickLeftNavMenuItemThenSubItem(String text, String subItemText)
//{
// qSeleniumLib.waitForSelectorContaining(".MuiDrawer-paperAnchorLeft .MuiListItem-root", text).click();
// qSeleniumLib.waitForSelectorContaining(".MuiDrawer-paperAnchorLeft .MuiCollapse-vertical.MuiCollapse-entered .MuiListItem-root", subItemText).click();
//}
//
//
//
///*******************************************************************************
// **
// *******************************************************************************/
//public void goToPathAndWaitForSelectorContaining(String path, String selector, String text)
//{
// driver.get(qSeleniumLib.getBaseUrl() + path);
// qSeleniumLib.waitForSelectorContaining(selector, text);
//}
}

View File

@ -0,0 +1,97 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.sampleapp.selenium;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/*******************************************************************************
**
*******************************************************************************/
public class BulkLoadSeleniumTest extends BaseSampleSeleniumTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
@Disabled("selenium not working in circleci at this time...")
void testSimple() throws IOException
{
String email = "jtkirk@starfleet.com";
String tablePath = "/peopleApp/greetingsApp/person";
////////////////////////////////////
// write a file to be bulk-loaded //
////////////////////////////////////
String path = "/tmp/" + UUID.randomUUID() + ".csv";
String csv = String.format("""
email,firstName,lastName
%s,James T.,Kirk
""", email);
FileUtils.writeStringToFile(new File(path), csv, StandardCharsets.UTF_8);
//goToPathAndWaitForSelectorContaining(tablePath + "/person.bulkInsert", ".MuiTypography-h5", "Person Bulk Insert: Upload File");
//
////////////////////////////////
//// complete the upload form //
////////////////////////////////
//qSeleniumLib.waitForSelector("input[type=file]").sendKeys(path);
//qSeleniumLib.waitForSelectorContaining("button", "next").click();
//
///////////////////////////////////////////
//// proceed through file-mapping screen //
///////////////////////////////////////////
//qSeleniumLib.waitForSelectorContaining("button", "next").click();
//
////////////////////////////////////////////////////
//// confirm data on preview screen, then proceed //
////////////////////////////////////////////////////
//qSeleniumLib.waitForSelectorContaining("form#review .MuiTypography-body2 div", email);
//qSeleniumLib.waitForSelectorContaining("form#review .MuiTypography-body2 div", "Preview 1 of 1");
//qSeleniumLib.waitForSelectorContaining("button", "arrow_forward").click(); // to avoid the record-preview 'next' button
//
/////////////////////////////////////////
//// proceed through validation screen //
/////////////////////////////////////////
//qSeleniumLib.waitForSelectorContaining("button", "submit").click();
//
//////////////////////////////////////////
//// confirm result screen and close it //
//////////////////////////////////////////
//qSeleniumLib.waitForSelectorContaining(".MuiListItemText-root", "1 Person record was inserted");
//qSeleniumLib.waitForSelectorContaining("button", "close").click();
//
//////////////////////////////////////////////
//// go to the order that was just inserted //
//// bonus - also test record-view-by-key //
//////////////////////////////////////////////
//goToPathAndWaitForSelectorContaining(tablePath + "/key?email=" + email, "h5", "Viewing Person");
}
}