mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 14:10:44 +00:00
Compare commits
10 Commits
feature/wo
...
feature/bu
Author | SHA1 | Date | |
---|---|---|---|
d9f5b5ac72 | |||
eb48f9cc87 | |||
90e702112a | |||
1fa4d72e99 | |||
2560744a59 | |||
809c2ca92e | |||
7711e6eb35 | |||
a3328635aa | |||
eda411c074 | |||
0a236b8c36 |
@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
############################################################################
|
||||
## Script to collect all JaCoCo reports from different modules into a
|
||||
## single directory for easier artifact storage in CI.
|
||||
############################################################################
|
||||
|
||||
mkdir -p /home/circleci/jacoco-reports/
|
||||
|
||||
##############################################################
|
||||
## Find all module directories that have target/site/jacoco ##
|
||||
##############################################################
|
||||
for module_dir in */; do
|
||||
if [ -d "${module_dir}target/site/jacoco" ]; then
|
||||
module_name=$(basename "${module_dir%/}")
|
||||
target_dir="/home/circleci/jacoco-reports/${module_name}"
|
||||
|
||||
echo "Collecting JaCoCo reports for module: ${module_name}"
|
||||
|
||||
cp -r "${module_dir}target/site/jacoco" "${target_dir}"
|
||||
|
||||
echo "Copied JaCoCo reports for ${module_name} to ${target_dir}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All JaCoCo reports collected to /home/circleci/jacoco-reports/"
|
@ -1,48 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
############################################################################
|
||||
## Script to concatenate all .txt files in the surefire-reports directory
|
||||
## into a single artifact that can be stored in CI.
|
||||
############################################################################
|
||||
|
||||
mkdir -p /home/circleci/test-output-artifacts/
|
||||
|
||||
###################################################################
|
||||
## Find all module directories that have target/surefire-reports ##
|
||||
###################################################################
|
||||
for module_dir in */; do
|
||||
if [ -d "${module_dir}target/surefire-reports" ]; then
|
||||
module_name=$(basename "${module_dir%/}")
|
||||
output_file="/home/circleci/test-output-artifacts/${module_name}-test-output.txt"
|
||||
|
||||
echo "Processing module: ${module_name}"
|
||||
echo "Output file: ${output_file}"
|
||||
|
||||
##################################################################
|
||||
## Concatenate all .txt files in the surefire-reports directory ##
|
||||
##################################################################
|
||||
if [ -n "$(find "${module_dir}target/surefire-reports" -name "*.txt" -type f)" ]; then
|
||||
echo "=== Test Output for ${module_name} ===" > "${output_file}"
|
||||
echo "Generated at: $(date)" >> "${output_file}"
|
||||
echo "==========================================" >> "${output_file}"
|
||||
echo "" >> "${output_file}"
|
||||
|
||||
##############################################
|
||||
## Sort files to ensure consistent ordering ##
|
||||
##############################################
|
||||
find "${module_dir}target/surefire-reports" -name "*.txt" -type f | sort | while read -r txt_file; do
|
||||
echo "--- File: $(basename "${txt_file}") ---" >> "${output_file}"
|
||||
cat "${txt_file}" >> "${output_file}"
|
||||
echo "" >> "${output_file}"
|
||||
echo "--- End of $(basename "${txt_file}") ---" >> "${output_file}"
|
||||
echo "" >> "${output_file}"
|
||||
echo "" >> "${output_file}"
|
||||
echo "" >> "${output_file}"
|
||||
done
|
||||
|
||||
echo "Concatenated test output for ${module_name} to ${output_file}"
|
||||
else
|
||||
echo "No .txt files found in ${module_dir}target/surefire-reports"
|
||||
fi
|
||||
fi
|
||||
done
|
@ -5,7 +5,35 @@ orbs:
|
||||
browser-tools: circleci/browser-tools@1.4.7
|
||||
|
||||
commands:
|
||||
mvn_build:
|
||||
store_jacoco_site:
|
||||
parameters:
|
||||
module:
|
||||
type: string
|
||||
steps:
|
||||
- store_artifacts:
|
||||
path: << parameters.module >>/target/site/jacoco/index.html
|
||||
when: always
|
||||
- store_artifacts:
|
||||
path: << parameters.module >>/target/site/jacoco/jacoco-resources
|
||||
when: always
|
||||
|
||||
install_java17:
|
||||
steps:
|
||||
- run:
|
||||
name: Install Java 17
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt install -y openjdk-17-jdk
|
||||
sudo rm /etc/alternatives/java
|
||||
sudo ln -s /usr/lib/jvm/java-17-openjdk-amd64/bin/java /etc/alternatives/java
|
||||
- run:
|
||||
## used by jacoco uncovered class reporting in pom.xml
|
||||
name: Install html2text
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y html2text
|
||||
|
||||
mvn_verify:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
@ -17,41 +45,30 @@ commands:
|
||||
name: Write .env
|
||||
command: |
|
||||
echo "RDBMS_PASSWORD=$RDBMS_PASSWORD" >> qqq-sample-project/.env
|
||||
- run:
|
||||
name: Run Maven Compile
|
||||
command: |
|
||||
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress compile
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: v1-dependencies-{{ checksum "pom.xml" }}
|
||||
|
||||
mvn_verify:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "pom.xml" }}
|
||||
- run:
|
||||
name: Run Maven Verify
|
||||
command: |
|
||||
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress verify
|
||||
- run:
|
||||
name: Collect JaCoCo reports
|
||||
command: .circleci/collect-jacoco-reports.sh
|
||||
when: always
|
||||
- store_artifacts:
|
||||
path: /home/circleci/jacoco-reports
|
||||
destination: jacoco-reports
|
||||
when: always
|
||||
- run:
|
||||
name: Concatenate test output files
|
||||
command: .circleci/concatenate-test-output.sh
|
||||
when: always
|
||||
- store_artifacts:
|
||||
path: /home/circleci/test-output-artifacts
|
||||
destination: test-output
|
||||
when: always
|
||||
mvn -s .circleci/mvn-settings.xml -T4 verify
|
||||
- store_jacoco_site:
|
||||
module: qqq-backend-core
|
||||
- store_jacoco_site:
|
||||
module: qqq-backend-module-filesystem
|
||||
- store_jacoco_site:
|
||||
module: qqq-backend-module-rdbms
|
||||
- store_jacoco_site:
|
||||
module: qqq-backend-module-api
|
||||
- store_jacoco_site:
|
||||
module: qqq-middleware-api
|
||||
- store_jacoco_site:
|
||||
module: qqq-middleware-javalin
|
||||
- store_jacoco_site:
|
||||
module: qqq-middleware-picocli
|
||||
- store_jacoco_site:
|
||||
module: qqq-middleware-slack
|
||||
- store_jacoco_site:
|
||||
module: qqq-language-support-javascript
|
||||
- store_jacoco_site:
|
||||
module: qqq-sample-project
|
||||
- run:
|
||||
name: Save test results
|
||||
command: |
|
||||
@ -60,6 +77,10 @@ commands:
|
||||
when: always
|
||||
- store_test_results:
|
||||
path: ~/test-results
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: v1-dependencies-{{ checksum "pom.xml" }}
|
||||
|
||||
check_middleware_api_versions:
|
||||
steps:
|
||||
@ -70,8 +91,8 @@ commands:
|
||||
- run:
|
||||
name: Build and Run ValidateApiVersions
|
||||
command: |
|
||||
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress install -DskipTests
|
||||
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress -pl qqq-middleware-javalin package appassembler:assemble -DskipTests
|
||||
mvn -s .circleci/mvn-settings.xml -T4 install -DskipTests
|
||||
mvn -s .circleci/mvn-settings.xml -pl qqq-middleware-javalin package appassembler:assemble -DskipTests
|
||||
qqq-middleware-javalin/target/appassembler/bin/ValidateApiVersions -r $(pwd)
|
||||
|
||||
mvn_jar_deploy:
|
||||
@ -87,7 +108,7 @@ commands:
|
||||
- run:
|
||||
name: Run Maven Jar Deploy
|
||||
command: |
|
||||
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress flatten:flatten jar:jar deploy:deploy
|
||||
mvn -s .circleci/mvn-settings.xml -T4 flatten:flatten jar:jar deploy:deploy
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
@ -114,25 +135,19 @@ commands:
|
||||
when: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
executor: localstack/default
|
||||
steps:
|
||||
- mvn_build
|
||||
|
||||
test:
|
||||
mvn_test:
|
||||
executor: localstack/default
|
||||
steps:
|
||||
## - localstack/startup
|
||||
- install_java17
|
||||
- mvn_verify
|
||||
|
||||
api_version_check:
|
||||
executor: localstack/default
|
||||
steps:
|
||||
- check_middleware_api_versions
|
||||
|
||||
mvn_deploy:
|
||||
executor: localstack/default
|
||||
steps:
|
||||
- mvn_build
|
||||
## - localstack/startup
|
||||
- install_java17
|
||||
- mvn_verify
|
||||
- check_middleware_api_versions
|
||||
- mvn_jar_deploy
|
||||
@ -146,31 +161,13 @@ jobs:
|
||||
workflows:
|
||||
test_only:
|
||||
jobs:
|
||||
- build:
|
||||
- mvn_test:
|
||||
context: [ qqq-maven-registry-credentials, build-qqq-sample-app ]
|
||||
filters:
|
||||
branches:
|
||||
ignore: /(dev|integration.*)/
|
||||
tags:
|
||||
ignore: /(version|snapshot)-.*/
|
||||
- test:
|
||||
context: [ qqq-maven-registry-credentials, build-qqq-sample-app ]
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
ignore: /(dev|integration.*)/
|
||||
tags:
|
||||
ignore: /(version|snapshot)-.*/
|
||||
- api_version_check:
|
||||
context: [ qqq-maven-registry-credentials, build-qqq-sample-app ]
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
ignore: /(dev|integration.*)/
|
||||
tags:
|
||||
ignore: /(version|snapshot)-.*/
|
||||
|
||||
deploy:
|
||||
jobs:
|
||||
|
14
README.md
14
README.md
@ -30,20 +30,6 @@ There are a few useful IntelliJ settings files, under `qqq-dev-tools/intellij`:
|
||||
One will likely also want the [Kingsrook Commentator
|
||||
Plugin](https://plugins.jetbrains.com/plugin/19325-kingsrook-commentator).
|
||||
|
||||
## Test Logging
|
||||
By default, when ran from the command line, mvn surefire will make each test's
|
||||
output (e.g., System.out, err, printStackTrace, and all logger calls) go into a
|
||||
file under target/surefire-reports/${className}.txt.
|
||||
|
||||
The system property `-DtestOutputToFile=false` can be given on the command line
|
||||
to get all of this output on the console.
|
||||
|
||||
In the IDE (e.g,. IntelliJ), output goes to the Console.
|
||||
|
||||
In CircleCI, output goes to files, and those files are concatenated together and
|
||||
stored as artifacts.
|
||||
|
||||
|
||||
## License
|
||||
QQQ - Low-code Application Framework for Engineers. \
|
||||
Copyright (C) 2020-2024. Kingsrook, LLC \
|
||||
|
32
pom.xml
32
pom.xml
@ -59,7 +59,6 @@
|
||||
<coverage.instructionCoveredRatioMinimum>0.80</coverage.instructionCoveredRatioMinimum>
|
||||
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>
|
||||
<plugin.shade.phase>none</plugin.shade.phase>
|
||||
<testOutputToFile>true</testOutputToFile>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
@ -142,8 +141,6 @@
|
||||
<configuration>
|
||||
<!-- Sets the VM argument line used when integration tests are run. -->
|
||||
<argLine>@{jaCoCoArgLine}</argLine>
|
||||
<!-- Reduce console output for cleaner JUnit output -->
|
||||
<redirectTestOutputToFile>${testOutputToFile}</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@ -247,29 +244,30 @@ if [ ! -e target/site/jacoco/index.html ]; then
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Jacoco coverage summary report for module: ${project.artifactId}"
|
||||
echo "Jacoco coverage summary report:"
|
||||
echo " See also target/site/jacoco/index.html"
|
||||
echo " and https://www.jacoco.org/jacoco/trunk/doc/counters.html"
|
||||
echo "------------------------------------------------------------"
|
||||
|
||||
# Parse Jacoco HTML coverage summary
|
||||
if [ -f target/site/jacoco/index.html ]; then
|
||||
echo -e "Instructions Missed\nInstruction Coverage\nBranches Missed\nBranch Coverage\nComplexity Missed\nComplexity Hit\nLines Missed\nLines Hit\nMethods Missed\nMethods Hit\nClasses Missed\nClasses Hit\n" > /tmp/$$.headers
|
||||
sed 's/<\/\w\+>/&\n/g' target/site/jacoco/index.html | grep -A 12 '<tfoot>' | grep '<td' | sed 's/<td class="\w\+\d*">\([^<]*\)<\/td>/\1/' | grep -v Total > /tmp/$$.values
|
||||
if which xpath > /dev/null 2>&1; then
|
||||
echo "Element\nInstructions Missed\nInstruction Coverage\nBranches Missed\nBranch Coverage\nComplexity Missed\nComplexity Hit\nLines Missed\nLines Hit\nMethods Missed\nMethods Hit\nClasses Missed\nClasses Hit\n" > /tmp/$$.headers
|
||||
xpath -n -q -e '/html/body/table/tfoot/tr[1]/td/text()' target/site/jacoco/index.html > /tmp/$$.values
|
||||
paste /tmp/$$.headers /tmp/$$.values | tail +2 | awk -v FS='\t' '{printf("%-20s %s\n",$1,$2)}'
|
||||
rm /tmp/$$.headers /tmp/$$.values
|
||||
else
|
||||
echo "Jacoco coverage summary was not found.";
|
||||
echo "xpath is not installed. Jacoco coverage summary will not be produced here...";
|
||||
fi
|
||||
echo "-----------------------------"
|
||||
echo
|
||||
|
||||
echo "Untested classes, per Jacoco for module: ${project.artifactId}"
|
||||
echo "-----------------------------"
|
||||
# Parse Jacoco XML reports directly to find classes with 0% coverage
|
||||
sed 's/<classs .*\?>/&\n/g;s/<\/class>/&\n/g' target/site/jacoco/jacoco.xml | grep -v 'counter type="CLASS" missed="0"' | sed 's/>.*//;s/.*\///;s/".*//'
|
||||
echo "-----------------------------"
|
||||
echo
|
||||
if which html2text > /dev/null 2>&1; then
|
||||
echo "Untested classes, per Jacoco:"
|
||||
echo "-----------------------------"
|
||||
for i in target/site/jacoco/*/index.html; do
|
||||
html2text -width 500 -nobs $i | sed '1,/^Total/d;' | grep -v Created | sed 's/ \+/ /g' | sed 's/ [[:digit:]]$//' | grep -v 0$ | cut -d' ' -f1;
|
||||
done;
|
||||
echo
|
||||
else
|
||||
echo "html2text is not installed. Untested classes from Jacoco will not be printed here...";
|
||||
fi
|
||||
|
||||
]]>
|
||||
</argument>
|
||||
|
@ -121,11 +121,6 @@
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.commonmark</groupId>
|
||||
<artifactId>commonmark</artifactId>
|
||||
<version>0.25.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- adding to help FastExcel -->
|
||||
<dependency>
|
||||
|
@ -291,7 +291,6 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
/////////////////////////////
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName("audit");
|
||||
insertInput.setTransaction(input.getTransaction());
|
||||
insertInput.setRecords(auditRecords);
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
|
||||
@ -319,7 +318,6 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
{
|
||||
insertInput = new InsertInput();
|
||||
insertInput.setTableName("auditDetail");
|
||||
insertInput.setTransaction(input.getTransaction());
|
||||
insertInput.setRecords(auditDetailRecords);
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
|
@ -124,7 +124,6 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
String contextSuffix = getContentSuffix(input);
|
||||
|
||||
AuditInput auditInput = new AuditInput();
|
||||
auditInput.setTransaction(input.getTransaction());
|
||||
if(auditLevel.equals(AuditLevel.RECORD) || (auditLevel.equals(AuditLevel.FIELD) && !dmlType.supportsFields))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface to be implemented by one that wishes to execute custom table triggers
|
||||
*******************************************************************************/
|
||||
public interface CustomTableTriggerRecordAutomationHandler extends RecordAutomationHandlerInterface
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean handlesThisInput(RecordAutomationInput recordAutomationInput) throws QException;
|
||||
}
|
@ -22,11 +22,19 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base class for custom-codes to run as an automation action
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "0.26.0 - when RecordAutomationHandlerInterface was introduced")
|
||||
public abstract class RecordAutomationHandler implements RecordAutomationHandlerInterface
|
||||
public abstract class RecordAutomationHandler
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract void execute(RecordAutomationInput recordAutomationInput) throws QException;
|
||||
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. 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.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for custom-codes to run as an automation action
|
||||
*******************************************************************************/
|
||||
public interface RecordAutomationHandlerInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void execute(RecordAutomationInput recordAutomationInput) throws QException;
|
||||
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** RecordAutomationHandler implementation that is called by automation runner
|
||||
** that doesn't know to deal with a TableTrigger record that it received.
|
||||
**
|
||||
** e.g., if an app has altered that table (e.g., workflows-qbit).
|
||||
*******************************************************************************/
|
||||
public class RunCustomTableTriggerRecordAutomationHandler implements RecordAutomationHandlerInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(RunCustomTableTriggerRecordAutomationHandler.class);
|
||||
|
||||
private static Map<String, QCodeReference> handlers = new LinkedHashMap<>();
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void registerHandler(String name, QCodeReference codeReference)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's already a value mapped for this name, warn about it (unless it's for the same code reference) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(handlers.containsKey(name))
|
||||
{
|
||||
if(handlers.get(name).getName().equals(codeReference.getName()))
|
||||
{
|
||||
LOG.warn("Registering a CustomTableTriggerRecordAutomationHandler for a name that is already registered", logPair("name", name));
|
||||
}
|
||||
}
|
||||
|
||||
handlers.put(name, codeReference);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void execute(RecordAutomationInput recordAutomationInput) throws QException
|
||||
{
|
||||
for(QCodeReference codeReference : handlers.values())
|
||||
{
|
||||
CustomTableTriggerRecordAutomationHandler customHandler = QCodeLoader.getAdHoc(CustomTableTriggerRecordAutomationHandler.class, codeReference);
|
||||
if(customHandler.handlesThisInput(recordAutomationInput))
|
||||
{
|
||||
customHandler.execute(recordAutomationInput);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw (new QException("No custom record automation handler was found for " + recordAutomationInput));
|
||||
}
|
||||
|
||||
}
|
@ -51,7 +51,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class RunRecordScriptAutomationHandler implements RecordAutomationHandlerInterface
|
||||
public class RunRecordScriptAutomationHandler extends RecordAutomationHandler
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(RunRecordScriptAutomationHandler.class);
|
||||
|
||||
|
@ -33,9 +33,8 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandlerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RunCustomTableTriggerRecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RunRecordScriptAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
|
||||
@ -443,33 +442,15 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
TableAutomationAction tableAutomationAction = new TableAutomationAction()
|
||||
rs.add(new TableAutomationAction()
|
||||
.withName("Script:" + tableTrigger.getScriptId())
|
||||
.withFilter(filter)
|
||||
.withTriggerEvent(triggerEvent)
|
||||
.withPriority(tableTrigger.getPriority())
|
||||
.withIncludeRecordAssociations(true);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the table trigger has a script id on it, then we know how to run that here in qqq-backend-core //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(tableTrigger.getScriptId() != null)
|
||||
{
|
||||
rs.add(tableAutomationAction
|
||||
.withName("Script:" + tableTrigger.getScriptId())
|
||||
.withValues(MapBuilder.of("scriptId", tableTrigger.getScriptId()))
|
||||
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class)));
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// but - the app may have added an extension to the TableTrigger table (e.g., workflows qbit) //
|
||||
// so, defer to RunCustomRecordAutomationHandler for unrecognized triggers //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
rs.add(tableAutomationAction
|
||||
.withName("Custom Trigger:" + tableTrigger.getScriptId())
|
||||
.withValues(MapBuilder.of("tableTriggerId", tableTrigger.getId()))
|
||||
.withCodeReference(new QCodeReference(RunCustomTableTriggerRecordAutomationHandler.class)));
|
||||
}
|
||||
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
|
||||
.withValues(MapBuilder.of("scriptId", tableTrigger.getScriptId()))
|
||||
.withIncludeRecordAssociations(true)
|
||||
);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -545,7 +526,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
// note - this method - will re-query the objects, so we should have confidence that their data is fresh... //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> matchingQRecords = getRecordsMatchingActionFilter(table, records, action);
|
||||
LOG.debug("Of the [" + records.size() + "] records that were pending automations, [" + matchingQRecords.size() + "] of them match the filter on the action:" + action);
|
||||
LOG.debug("Of the [" + records.size() + "] records that were pending automations, [" + matchingQRecords.size() + "] of them match the filter on the action:" + action);
|
||||
if(CollectionUtils.nullSafeHasContents(matchingQRecords))
|
||||
{
|
||||
LOG.debug(" Processing " + matchingQRecords.size() + " records in " + table + " for action " + action);
|
||||
@ -668,7 +649,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
input.setRecordList(records);
|
||||
input.setAction(action);
|
||||
|
||||
RecordAutomationHandlerInterface recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandlerInterface.class, action.getCodeReference());
|
||||
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandler.class, action.getCodeReference());
|
||||
recordAutomationHandler.execute(input);
|
||||
}
|
||||
}
|
||||
|
@ -1,226 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Implementation of TableCustomizerInterface that runs multiple other customizers
|
||||
*******************************************************************************/
|
||||
public class MultiCustomizer implements InitializableViaCodeReference, TableCustomizerInterface
|
||||
{
|
||||
private static final String KEY_CODE_REFERENCES = "codeReferences";
|
||||
|
||||
private List<TableCustomizerInterface> customizers = new ArrayList<>();
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Factory method that builds a {@link QCodeReferenceWithProperties} that will
|
||||
* allow this multi-customizer to be assigned to a table, and to track
|
||||
* in that code ref's properties, the "sub" QCodeReferences to be used.
|
||||
*
|
||||
* Added to a table as in:
|
||||
* <pre>
|
||||
* table.withCustomizer(TableCustomizers.POST_INSERT_RECORD,
|
||||
* MultiCustomizer.of(QCodeReference(x), QCodeReference(y)));
|
||||
* </pre>
|
||||
*
|
||||
* @param codeReferences
|
||||
* one or more {@link QCodeReference objects} to run when this customizer
|
||||
* runs. note that they will run in the order provided in this list.
|
||||
***************************************************************************/
|
||||
public static QCodeReferenceWithProperties of(QCodeReference... codeReferences)
|
||||
{
|
||||
ArrayList<QCodeReference> list = new ArrayList<>(Arrays.stream(codeReferences).toList());
|
||||
return (new QCodeReferenceWithProperties(MultiCustomizer.class, MapBuilder.of(KEY_CODE_REFERENCES, list)));
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Add an additional table customizer code reference to an existing
|
||||
* codeReference, e.g., constructed by the `of` factory method.
|
||||
*
|
||||
* @see #of(QCodeReference...)
|
||||
***************************************************************************/
|
||||
public static void addTableCustomizer(QCodeReferenceWithProperties existingMultiCustomizerCodeReference, QCodeReference codeReference)
|
||||
{
|
||||
ArrayList<QCodeReference> list = (ArrayList<QCodeReference>) existingMultiCustomizerCodeReference.getProperties().computeIfAbsent(KEY_CODE_REFERENCES, key -> new ArrayList<>());
|
||||
list.add(codeReference);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* When this class is instantiated by the QCodeLoader, initialize the
|
||||
* sub-customizer objects.
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void initialize(QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference instanceof QCodeReferenceWithProperties codeReferenceWithProperties)
|
||||
{
|
||||
Serializable codeReferencesPropertyValue = codeReferenceWithProperties.getProperties().get(KEY_CODE_REFERENCES);
|
||||
if(codeReferencesPropertyValue instanceof List<?> list)
|
||||
{
|
||||
for(Object o : list)
|
||||
{
|
||||
if(o instanceof QCodeReference reference)
|
||||
{
|
||||
TableCustomizerInterface customizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, reference);
|
||||
customizers.add(customizer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Property KEY_CODE_REFERENCES [" + KEY_CODE_REFERENCES + "] must be a List<QCodeReference>.");
|
||||
}
|
||||
}
|
||||
|
||||
if(customizers.isEmpty())
|
||||
{
|
||||
LOG.info("No TableCustomizers were specified for MultiCustomizer.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run postQuery method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.postQuery(queryInput, records);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run preInsert method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.preInsert(insertInput, records, isPreview);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run postInsert method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.postInsert(insertInput, records);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run preUpdate method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.preUpdate(updateInput, records, isPreview, oldRecordList);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run postUpdate method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.postUpdate(updateInput, records, oldRecordList);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run preDelete method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.preDelete(deleteInput, records, isPreview);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run postDelete method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.postDelete(deleteInput, records);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.TypeTolerantKeyMap;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** utility class to help table customizers working with the oldRecordList.
|
||||
** Usage is just 2 lines:
|
||||
** outside of loop-over-records:
|
||||
** - OldRecordHelper oldRecordHelper = new OldRecordHelper(updateInput.getTableName(), oldRecordList);
|
||||
** then inside the record loop:
|
||||
** - Optional<QRecord> oldRecord = oldRecordHelper.getOldRecord(record);
|
||||
*******************************************************************************/
|
||||
public class OldRecordHelper
|
||||
{
|
||||
private String primaryKeyField;
|
||||
private QFieldType primaryKeyType;
|
||||
|
||||
private Optional<List<QRecord>> oldRecordList;
|
||||
private Map<Serializable, QRecord> oldRecordMap;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public OldRecordHelper(String tableName, Optional<List<QRecord>> oldRecordList)
|
||||
{
|
||||
this.primaryKeyField = QContext.getQInstance().getTable(tableName).getPrimaryKeyField();
|
||||
this.primaryKeyType = QContext.getQInstance().getTable(tableName).getField(primaryKeyField).getType();
|
||||
|
||||
this.oldRecordList = oldRecordList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public Optional<QRecord> getOldRecord(QRecord record)
|
||||
{
|
||||
if(oldRecordMap == null)
|
||||
{
|
||||
if(oldRecordList.isPresent())
|
||||
{
|
||||
oldRecordMap = new TypeTolerantKeyMap<>(primaryKeyType);
|
||||
oldRecordList.get().forEach(r -> oldRecordMap.put(r.getValue(primaryKeyField), r));
|
||||
}
|
||||
else
|
||||
{
|
||||
oldRecordMap = Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
return (Optional.ofNullable(oldRecordMap.get(record.getValue(primaryKeyField))));
|
||||
}
|
||||
}
|
@ -161,7 +161,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
public static String linkTableCreateWithDefaultValues(RenderWidgetInput input, String tableName, Map<String, Serializable> defaultValues) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
return (tablePath + "/create#defaultValues=" + URLEncoder.encode(JsonUtils.toJson(defaultValues), StandardCharsets.UTF_8));
|
||||
return (tablePath + "/create#defaultValues=" + URLEncoder.encode(JsonUtils.toJson(defaultValues), Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
|
||||
@ -229,7 +229,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
|
||||
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,14 +25,11 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
import java.io.Serializable;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
@ -48,7 +45,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
@ -69,7 +65,6 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
@ -181,18 +176,6 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Builder withOmitFieldNames(List<String> omitFieldNames)
|
||||
{
|
||||
ArrayList<String> arrayList = CollectionUtils.useOrWrap(omitFieldNames, new TypeToken<>() {});
|
||||
widgetMetaData.withDefaultValue("omitFieldNames", arrayList);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -212,25 +195,14 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
QTableMetaData leftTable = QContext.getQInstance().getTable(join.getLeftTable());
|
||||
QTableMetaData rightTable = QContext.getQInstance().getTable(join.getRightTable());
|
||||
|
||||
Map<String, Serializable> widgetMetaDataDefaultValues = input.getWidgetMetaData().getDefaultValues();
|
||||
List<String> omitFieldNames = (List<String>) widgetMetaDataDefaultValues.get("omitFieldNames");
|
||||
if(omitFieldNames == null)
|
||||
{
|
||||
omitFieldNames = new ArrayList<>();
|
||||
}
|
||||
else
|
||||
{
|
||||
omitFieldNames = new MutableList<>(omitFieldNames);
|
||||
}
|
||||
|
||||
Integer maxRows = null;
|
||||
if(StringUtils.hasContent(input.getQueryParams().get("maxRows")))
|
||||
{
|
||||
maxRows = ValueUtils.getValueAsInteger(input.getQueryParams().get("maxRows"));
|
||||
}
|
||||
else if(widgetMetaDataDefaultValues.containsKey("maxRows"))
|
||||
else if(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"))
|
||||
{
|
||||
maxRows = ValueUtils.getValueAsInteger(widgetMetaDataDefaultValues.get("maxRows"));
|
||||
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().get("maxRows"));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -262,19 +234,8 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(primaryRecord.getValue(joinOn.getLeftField()))));
|
||||
omitFieldNames.add(joinOn.getRightField());
|
||||
}
|
||||
|
||||
Serializable orderBy = widgetMetaDataDefaultValues.get("orderBy");
|
||||
if(orderBy instanceof List orderByList && !orderByList.isEmpty() && orderByList.get(0) instanceof QFilterOrderBy)
|
||||
{
|
||||
filter.setOrderBys(orderByList);
|
||||
}
|
||||
else
|
||||
{
|
||||
filter.setOrderBys(join.getOrderBys());
|
||||
}
|
||||
|
||||
filter.setOrderBys(join.getOrderBys());
|
||||
filter.setLimit(maxRows);
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
@ -301,10 +262,9 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
|
||||
String tablePath = QContext.getQInstance().getTablePath(rightTable.getName());
|
||||
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
|
||||
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
|
||||
ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, rightTable, tablePath, viewAllLink, totalRows);
|
||||
widgetData.setOmitFieldNames(omitFieldNames);
|
||||
|
||||
if(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("canAddChildRecord"))))
|
||||
{
|
||||
@ -324,10 +284,11 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
|
||||
widgetData.setDefaultValuesForNewChildRecords(defaultValuesForNewChildRecords);
|
||||
|
||||
if(widgetMetaDataDefaultValues.containsKey("disabledFieldsForNewChildRecords"))
|
||||
Map<String, Serializable> widgetValues = input.getWidgetMetaData().getDefaultValues();
|
||||
if(widgetValues.containsKey("disabledFieldsForNewChildRecords"))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> disabledFieldsForNewChildRecords = (Set<String>) widgetMetaDataDefaultValues.get("disabledFieldsForNewChildRecords");
|
||||
Set<String> disabledFieldsForNewChildRecords = (Set<String>) widgetValues.get("disabledFieldsForNewChildRecords");
|
||||
widgetData.setDisabledFieldsForNewChildRecords(disabledFieldsForNewChildRecords);
|
||||
}
|
||||
else
|
||||
@ -347,10 +308,10 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
}
|
||||
|
||||
if(widgetMetaDataDefaultValues.containsKey("defaultValuesForNewChildRecordsFromParentFields"))
|
||||
if(widgetValues.containsKey("defaultValuesForNewChildRecordsFromParentFields"))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> defaultValuesForNewChildRecordsFromParentFields = (Map<String, String>) widgetMetaDataDefaultValues.get("defaultValuesForNewChildRecordsFromParentFields");
|
||||
Map<String, String> defaultValuesForNewChildRecordsFromParentFields = (Map<String, String>) widgetValues.get("defaultValuesForNewChildRecordsFromParentFields");
|
||||
widgetData.setDefaultValuesForNewChildRecordsFromParentFields(defaultValuesForNewChildRecordsFromParentFields);
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
@ -195,7 +194,7 @@ public class RecordListWidgetRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
|
||||
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
|
||||
ChildRecordListData widgetData = new ChildRecordListData(input.getQueryParams().get("widgetLabel"), queryOutput, table, tablePath, viewAllLink, totalRows);
|
||||
|
||||
|
@ -299,8 +299,6 @@ public class MetaDataAction
|
||||
|
||||
metaDataOutput.setHelpContents(Objects.requireNonNullElse(QContext.getQInstance().getHelpContent(), Collections.emptyMap()));
|
||||
|
||||
metaDataOutput.setSupplementalInstanceMetaData(QContext.getQInstance().getSupplementalMetaData());
|
||||
|
||||
try
|
||||
{
|
||||
customizer.postProcess(metaDataOutput);
|
||||
@ -331,25 +329,23 @@ public class MetaDataAction
|
||||
if(metaDataActionCustomizerReference != null)
|
||||
{
|
||||
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataActionCustomizerReference);
|
||||
LOG.debug("Using new meta-data actionCustomizer of type: " + actionCustomizer.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
if(actionCustomizer == null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// check if QInstance is still using the now-deprecated getMetaDataFilter approach //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@SuppressWarnings("deprecation")
|
||||
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
|
||||
if(metaDataFilterReference != null)
|
||||
{
|
||||
LOG.warn("QInstance.metaDataFilter is deprecated in favor of metaDataActionCustomizer.");
|
||||
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataFilterReference);
|
||||
LOG.debug("Using new meta-data actionCustomizer (via metaDataFilter reference) of type: " + actionCustomizer.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
if(actionCustomizer == null)
|
||||
{
|
||||
actionCustomizer = new DefaultNoopMetaDataActionCustomizer();
|
||||
LOG.debug("Using new default (allow-all) meta-data actionCustomizer");
|
||||
}
|
||||
|
||||
return (actionCustomizer);
|
||||
|
@ -57,7 +57,7 @@ public class BulkTableActionProcessPermissionChecker implements CustomPermission
|
||||
switch(bulkActionName)
|
||||
{
|
||||
case "bulkInsert" -> PermissionsHelper.checkTablePermissionThrowing(tableActionInput, TablePermissionSubType.INSERT);
|
||||
case "bulkEdit" -> PermissionsHelper.checkTablePermissionThrowing(tableActionInput, TablePermissionSubType.EDIT);
|
||||
case "bulkEdit", "bulkEditWithFile" -> PermissionsHelper.checkTablePermissionThrowing(tableActionInput, TablePermissionSubType.EDIT);
|
||||
case "bulkDelete" -> PermissionsHelper.checkTablePermissionThrowing(tableActionInput, TablePermissionSubType.DELETE);
|
||||
default -> LOG.warn("Unexpected bulk action name when checking permissions for process: " + processName);
|
||||
}
|
||||
|
@ -198,10 +198,10 @@ public class RunBackendStepAction
|
||||
//////////////////////////////////////////////////
|
||||
// look for record ids in the input data values //
|
||||
//////////////////////////////////////////////////
|
||||
String recordIds = runBackendStepInput.getValueString("recordIds");
|
||||
String recordIds = (String) runBackendStepInput.getValue("recordIds");
|
||||
if(recordIds == null)
|
||||
{
|
||||
recordIds = runBackendStepInput.getValueString("recordId");
|
||||
recordIds = (String) runBackendStepInput.getValue("recordId");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
@ -246,7 +246,6 @@ public class DeleteAction
|
||||
{
|
||||
DMLAuditInput dmlAuditInput = new DMLAuditInput()
|
||||
.withTableActionInput(deleteInput)
|
||||
.withTransaction(deleteInput.getTransaction())
|
||||
.withAuditContext(deleteInput.getAuditContext());
|
||||
oldRecordList.ifPresent(l -> dmlAuditInput.setRecordList(l));
|
||||
new DMLAuditAction().execute(dmlAuditInput);
|
||||
@ -402,7 +401,6 @@ public class DeleteAction
|
||||
if(CollectionUtils.nullSafeHasContents(associatedKeys))
|
||||
{
|
||||
DeleteInput nextLevelDeleteInput = new DeleteInput();
|
||||
nextLevelDeleteInput.setFlags(deleteInput.getFlags());
|
||||
nextLevelDeleteInput.setTransaction(deleteInput.getTransaction());
|
||||
nextLevelDeleteInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelDeleteInput.setPrimaryKeys(associatedKeys);
|
||||
|
@ -34,6 +34,7 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
@ -53,7 +54,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
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;
|
||||
@ -157,7 +157,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
//////////////////////////////////////////////////
|
||||
// insert any associations in the input records //
|
||||
//////////////////////////////////////////////////
|
||||
manageAssociations(table, insertOutput.getRecords(), insertInput);
|
||||
manageAssociations(table, insertOutput.getRecords(), insertInput.getTransaction());
|
||||
|
||||
//////////////////
|
||||
// do the audit //
|
||||
@ -170,26 +170,13 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
{
|
||||
new DMLAuditAction().execute(new DMLAuditInput()
|
||||
.withTableActionInput(insertInput)
|
||||
.withTransaction(insertInput.getTransaction())
|
||||
.withAuditContext(insertInput.getAuditContext())
|
||||
.withRecordList(insertOutput.getRecords()));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// finally, run the post-insert customizers, if there are any //
|
||||
////////////////////////////////////////////////////////////////
|
||||
runPostInsertCustomizers(insertInput, table, insertOutput);
|
||||
|
||||
return insertOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void runPostInsertCustomizers(InsertInput insertInput, QTableMetaData table, InsertOutput insertOutput)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////
|
||||
// finally, run the post-insert customizer, if there is one //
|
||||
//////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> postInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
||||
if(postInsertCustomizer.isPresent())
|
||||
{
|
||||
@ -206,25 +193,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// run all of the instance-level customizers //
|
||||
///////////////////////////////////////////////
|
||||
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.POST_INSERT_RECORD);
|
||||
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
|
||||
insertOutput.setRecords(tableCustomizer.postInsert(insertInput, insertOutput.getRecords()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
for(QRecord record : insertOutput.getRecords())
|
||||
{
|
||||
record.addWarning(new QWarningMessage("An error occurred after the insert: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return insertOutput;
|
||||
}
|
||||
|
||||
|
||||
@ -339,19 +308,6 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
insertInput.setRecords(preInsertCustomizer.get().preInsert(insertInput, insertInput.getRecords(), isPreview));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// run all of the instance-level customizers //
|
||||
///////////////////////////////////////////////
|
||||
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.PRE_INSERT_RECORD);
|
||||
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
|
||||
{
|
||||
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
|
||||
if(whenToRun.equals(tableCustomizer.whenToRunPreInsert(insertInput, isPreview)))
|
||||
{
|
||||
insertInput.setRecords(tableCustomizer.preInsert(insertInput, insertInput.getRecords(), isPreview));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -386,7 +342,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords, InsertInput insertInput) throws QException
|
||||
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
@ -419,8 +375,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
if(CollectionUtils.nullSafeHasContents(nextLevelInserts))
|
||||
{
|
||||
InsertInput nextLevelInsertInput = new InsertInput();
|
||||
nextLevelInsertInput.withFlags(insertInput.getFlags());
|
||||
nextLevelInsertInput.setTransaction(insertInput.getTransaction());
|
||||
nextLevelInsertInput.setTransaction(transaction);
|
||||
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelInsertInput.setRecords(nextLevelInserts);
|
||||
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
|
||||
|
@ -126,7 +126,6 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(table.getName());
|
||||
insertInput.setRecords(insertList);
|
||||
insertInput.withFlags(input.getFlags());
|
||||
insertInput.setTransaction(transaction);
|
||||
insertInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
@ -136,7 +135,6 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(table.getName());
|
||||
updateInput.setRecords(updateList);
|
||||
updateInput.withFlags(input.getFlags());
|
||||
updateInput.setTransaction(transaction);
|
||||
updateInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
@ -153,7 +151,6 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(table.getName());
|
||||
deleteInput.setQueryFilter(deleteFilter);
|
||||
deleteInput.withFlags(input.getFlags());
|
||||
deleteInput.setTransaction(transaction);
|
||||
deleteInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
|
@ -57,7 +57,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
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.FieldBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@ -190,7 +189,6 @@ public class UpdateAction
|
||||
else
|
||||
{
|
||||
DMLAuditInput dmlAuditInput = new DMLAuditInput()
|
||||
.withTransaction(updateInput.getTransaction())
|
||||
.withTableActionInput(updateInput)
|
||||
.withRecordList(updateOutput.getRecords())
|
||||
.withAuditContext(updateInput.getAuditContext());
|
||||
@ -201,18 +199,6 @@ public class UpdateAction
|
||||
//////////////////////////////////////////////////////////////
|
||||
// finally, run the post-update customizer, if there is one //
|
||||
//////////////////////////////////////////////////////////////
|
||||
runPostUpdateCustomizers(updateInput, table, updateOutput, oldRecordList);
|
||||
|
||||
return updateOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void runPostUpdateCustomizers(UpdateInput updateInput, QTableMetaData table, UpdateOutput updateOutput, Optional<List<QRecord>> oldRecordList)
|
||||
{
|
||||
Optional<TableCustomizerInterface> postUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_UPDATE_RECORD.getRole());
|
||||
if(postUpdateCustomizer.isPresent())
|
||||
{
|
||||
@ -229,49 +215,7 @@ public class UpdateAction
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// run all of the instance-level customizers //
|
||||
///////////////////////////////////////////////
|
||||
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.POST_UPDATE_RECORD);
|
||||
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
|
||||
updateOutput.setRecords(tableCustomizer.postUpdate(updateInput, updateOutput.getRecords(), oldRecordList));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
for(QRecord record : updateOutput.getRecords())
|
||||
{
|
||||
record.addWarning(new QWarningMessage("An error occurred after the update: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void runPreUpdateCustomizers(UpdateInput updateInput, QTableMetaData table, Optional<List<QRecord>> oldRecordList, boolean isPreview) throws QException
|
||||
{
|
||||
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// run all of the instance-level customizers //
|
||||
///////////////////////////////////////////////
|
||||
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.PRE_UPDATE_RECORD);
|
||||
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
|
||||
{
|
||||
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
|
||||
updateInput.setRecords(tableCustomizer.preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
|
||||
}
|
||||
return updateOutput;
|
||||
}
|
||||
|
||||
|
||||
@ -334,7 +278,11 @@ public class UpdateAction
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-update customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
runPreUpdateCustomizers(updateInput, table, oldRecordList, isPreview);
|
||||
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -457,7 +405,7 @@ public class UpdateAction
|
||||
QFieldType fieldType = table.getField(lock.getFieldName()).getType();
|
||||
Serializable lockValue = ValueUtils.getValueAsFieldType(fieldType, oldRecord.getValue(lock.getFieldName()));
|
||||
|
||||
List<QErrorMessage> errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap(), QContext.getQSession());
|
||||
List<QErrorMessage> errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap());
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
errors.forEach(e -> record.addError(e));
|
||||
@ -606,7 +554,6 @@ public class UpdateAction
|
||||
{
|
||||
LOG.debug("Deleting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", queryOutput.getRecords().size()));
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setFlags(updateInput.getFlags());
|
||||
deleteInput.setTransaction(updateInput.getTransaction());
|
||||
deleteInput.setTableName(association.getAssociatedTableName());
|
||||
deleteInput.setPrimaryKeys(queryOutput.getRecords().stream().map(r -> r.getValue(associatedTable.getPrimaryKeyField())).collect(Collectors.toList()));
|
||||
@ -619,7 +566,6 @@ public class UpdateAction
|
||||
LOG.debug("Updating associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
|
||||
UpdateInput nextLevelUpdateInput = new UpdateInput();
|
||||
nextLevelUpdateInput.setTransaction(updateInput.getTransaction());
|
||||
nextLevelUpdateInput.setFlags(updateInput.getFlags());
|
||||
nextLevelUpdateInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelUpdateInput.setRecords(nextLevelUpdates);
|
||||
UpdateOutput nextLevelUpdateOutput = new UpdateAction().execute(nextLevelUpdateInput);
|
||||
@ -630,7 +576,6 @@ public class UpdateAction
|
||||
LOG.debug("Inserting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
|
||||
InsertInput nextLevelInsertInput = new InsertInput();
|
||||
nextLevelInsertInput.setTransaction(updateInput.getTransaction());
|
||||
nextLevelInsertInput.setFlags(updateInput.getFlags());
|
||||
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelInsertInput.setRecords(nextLevelInserts);
|
||||
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
|
||||
|
@ -41,7 +41,6 @@ import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
@ -570,7 +569,7 @@ public class QueryActionCacheHelper
|
||||
|
||||
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
sourceQueryInput.setFilter(filter);
|
||||
((QueryOrGetInputInterface) sourceQueryInput).setCommonParamsFrom(cacheQueryInput);
|
||||
sourceQueryInput.setCommonParamsFrom(cacheQueryInput);
|
||||
|
||||
for(List<Serializable> uniqueKeyValue : uniqueKeyValues)
|
||||
{
|
||||
|
@ -50,7 +50,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.PermissionDeniedMessage;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QErrorMessage;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
@ -103,7 +102,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
// actually check lock values //
|
||||
////////////////////////////////
|
||||
Map<Serializable, RecordWithErrors> errorRecords = new HashMap<>();
|
||||
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction, QContext.getQSession());
|
||||
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction);
|
||||
|
||||
/////////////////////////////////
|
||||
// propagate errors to records //
|
||||
@ -125,29 +124,6 @@ public class ValidateRecordSecurityLockHelper
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** return boolean if given session can read given record
|
||||
***************************************************************************/
|
||||
public static boolean allowedToReadRecord(QTableMetaData table, QRecord record, QSession qSession, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
MultiRecordSecurityLock locksToCheck = getRecordSecurityLocks(table, Action.SELECT);
|
||||
if(locksToCheck == null || CollectionUtils.nullSafeIsEmpty(locksToCheck.getLocks()))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
Map<Serializable, RecordWithErrors> errorRecords = new HashMap<>();
|
||||
evaluateRecordLocks(table, List.of(record), Action.SELECT, locksToCheck, errorRecords, new ArrayList<>(), Collections.emptyMap(), transaction, qSession);
|
||||
if(errorRecords.containsKey(record.getValue(table.getPrimaryKeyField())))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of `records` from a `table`, and a given `action`, evaluate a
|
||||
** `recordSecurityLock` (which may be a multi-lock) - populating the input map
|
||||
@ -166,7 +142,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
** BUT - WRITE locks - in their case, we read the record no matter what, and in
|
||||
** here we need to verify we have a key that allows us to WRITE the record.
|
||||
*******************************************************************************/
|
||||
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, Map<Serializable, QRecord> madeUpPrimaryKeys, QBackendTransaction transaction, QSession qSession) throws QException
|
||||
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, Map<Serializable, QRecord> madeUpPrimaryKeys, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
|
||||
{
|
||||
@ -177,7 +153,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
for(RecordSecurityLock childLock : CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks()))
|
||||
{
|
||||
treePosition.add(i);
|
||||
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction, qSession);
|
||||
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction);
|
||||
treePosition.remove(treePosition.size() - 1);
|
||||
i++;
|
||||
}
|
||||
@ -189,7 +165,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
// if this lock has an all-access key, and the user has that key, then there can't be any errors here, so return early //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && qSession.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -217,7 +193,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
}
|
||||
|
||||
Serializable recordSecurityValue = record.getValue(field.getName());
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys, qSession);
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys);
|
||||
if(CollectionUtils.nullSafeHasContents(recordErrors))
|
||||
{
|
||||
errorRecords.computeIfAbsent(record.getValue(primaryKeyField), (k) -> new RecordWithErrors(record)).addAll(recordErrors, treePosition);
|
||||
@ -363,7 +339,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
|
||||
for(QRecord inputRecord : inputRecords)
|
||||
{
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys, qSession);
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys);
|
||||
if(CollectionUtils.nullSafeHasContents(recordErrors))
|
||||
{
|
||||
errorRecords.computeIfAbsent(inputRecord.getValue(primaryKeyField), (k) -> new RecordWithErrors(inputRecord)).addAll(recordErrors, treePosition);
|
||||
@ -470,7 +446,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QErrorMessage> validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map<Serializable, QRecord> madeUpPrimaryKeys, QSession qSession)
|
||||
public static List<QErrorMessage> validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map<Serializable, QRecord> madeUpPrimaryKeys)
|
||||
{
|
||||
if(recordSecurityValue == null || (madeUpPrimaryKeys != null && madeUpPrimaryKeys.containsKey(recordSecurityValue)))
|
||||
{
|
||||
@ -485,7 +461,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!qSession.hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
|
||||
if(!QContext.getQSession().hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
|
||||
{
|
||||
|
@ -47,12 +47,12 @@ public abstract class BasicCustomPossibleValueProvider<S, ID extends Serializabl
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected abstract S getSourceObject(Serializable id) throws QException;
|
||||
protected abstract S getSourceObject(Serializable id);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected abstract List<S> getAllSourceObjects() throws QException;
|
||||
protected abstract List<S> getAllSourceObjects();
|
||||
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ public abstract class BasicCustomPossibleValueProvider<S, ID extends Serializabl
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QPossibleValue<ID> getPossibleValue(Serializable idValue) throws QException
|
||||
public QPossibleValue<ID> getPossibleValue(Serializable idValue)
|
||||
{
|
||||
S sourceObject = getSourceObject(idValue);
|
||||
if(sourceObject == null)
|
||||
|
@ -45,7 +45,7 @@ public interface QCustomPossibleValueProvider<T extends Serializable>
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QPossibleValue<T> getPossibleValue(Serializable idValue) throws QException;
|
||||
QPossibleValue<T> getPossibleValue(Serializable idValue);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -310,19 +310,6 @@ public class QValueFormatter
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of records, set their recordLabels and display values - including
|
||||
** record label (e.g., from the table meta data).
|
||||
*******************************************************************************/
|
||||
public static void setDisplayValuesInRecordsIncludingPossibleValueTranslations(QTableMetaData table, List<QRecord> records)
|
||||
{
|
||||
QPossibleValueTranslator possibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
||||
possibleValueTranslator.translatePossibleValuesInRecords(table, records);
|
||||
setDisplayValuesInRecords(table, records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of records, set their recordLabels and display values - including
|
||||
** record label (e.g., from the table meta data).
|
||||
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* interface that can be added to a QSupplementalInstanceMetaData, to receive
|
||||
* QHelpContent records during instance boot or upon updates in the help content
|
||||
* table.
|
||||
*******************************************************************************/
|
||||
public interface QHelpContentPlugin
|
||||
{
|
||||
/***************************************************************************
|
||||
* accept a single helpContent record, and apply its data to some data in the
|
||||
* qInstance
|
||||
*
|
||||
* @param qInstance the active qInstance, that the content should be applied to
|
||||
* @param helpContent entity with values from HelpContent table
|
||||
* @param nameValuePairs parsed string -> string map from the help content key.
|
||||
***************************************************************************/
|
||||
void acceptHelpContent(QInstance qInstance, QHelpContent helpContent, Map<String, String> nameValuePairs);
|
||||
}
|
@ -31,9 +31,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -45,12 +43,11 @@ import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.BulkTableActionProcessPermissionChecker;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.instances.enrichment.plugins.QInstanceEnricherPluginInterface;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.bulk.TableKeyFieldsPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
@ -59,7 +56,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueB
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||
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.QSupplementalFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
@ -215,37 +211,6 @@ public class QInstanceEnricher
|
||||
***************************************************************************/
|
||||
private void enrichInstance()
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// enriching some objects may cause additional ones to be added to the qInstance! //
|
||||
// this caused concurrent modification exceptions, when we just iterated. //
|
||||
// we could make a copy of the map and just process that, but then we wouldn't //
|
||||
// enrich any new objects that do get added, so, use this technique instead. //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
Set<QSupplementalInstanceMetaData> toEnrich = new LinkedHashSet<>(qInstance.getSupplementalMetaData().values());
|
||||
Set<QSupplementalInstanceMetaData> enriched = new HashSet<>();
|
||||
int count = 0;
|
||||
while(!toEnrich.isEmpty())
|
||||
{
|
||||
Iterator<QSupplementalInstanceMetaData> iterator = toEnrich.iterator();
|
||||
QSupplementalInstanceMetaData supplementalInstanceMetaData = iterator.next();
|
||||
iterator.remove();
|
||||
|
||||
supplementalInstanceMetaData.enrich(qInstance);
|
||||
enriched.add(supplementalInstanceMetaData);
|
||||
|
||||
for(QSupplementalInstanceMetaData possiblyNew : qInstance.getSupplementalMetaData().values())
|
||||
{
|
||||
if(!toEnrich.contains(possiblyNew) && !enriched.contains(possiblyNew))
|
||||
{
|
||||
if(count++ > 100)
|
||||
{
|
||||
throw (new QRuntimeException("Too many new QSupplementalInstanceMetaData objects were added while enriching others. This probably indicates a bug in enrichment code. Throwing to prevent infinite loop."));
|
||||
}
|
||||
toEnrich.add(possiblyNew);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runPlugins(QInstance.class, qInstance, qInstance);
|
||||
}
|
||||
|
||||
@ -618,14 +583,6 @@ public class QInstanceEnricher
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// enrich any supplemental meta data on the field //
|
||||
////////////////////////////////////////////////////
|
||||
for(QSupplementalFieldMetaData supplementalFieldMetaData : CollectionUtils.nonNullMap(field.getSupplementalMetaData()).values())
|
||||
{
|
||||
supplementalFieldMetaData.enrich(qInstance, field);
|
||||
}
|
||||
|
||||
runPlugins(QFieldMetaData.class, field, qInstance);
|
||||
}
|
||||
|
||||
@ -901,6 +858,11 @@ public class QInstanceEnricher
|
||||
*******************************************************************************/
|
||||
private void defineTableBulkProcesses(QInstance qInstance)
|
||||
{
|
||||
if(qInstance.getPossibleValueSource(TableKeyFieldsPossibleValueSource.NAME) == null)
|
||||
{
|
||||
qInstance.addPossibleValueSource(defineTableKeyFieldsPossibleValueSource());
|
||||
}
|
||||
|
||||
for(QTableMetaData table : qInstance.getTables().values())
|
||||
{
|
||||
if(table.getFields() == null)
|
||||
@ -924,6 +886,12 @@ public class QInstanceEnricher
|
||||
defineTableBulkEdit(qInstance, table, bulkEditProcessName);
|
||||
}
|
||||
|
||||
String bulkEditWithFileProcessName = table.getName() + ".bulkEditWithFile";
|
||||
if(qInstance.getProcess(bulkEditWithFileProcessName) == null)
|
||||
{
|
||||
defineTableBulkEditWithFile(qInstance, table, bulkEditWithFileProcessName);
|
||||
}
|
||||
|
||||
String bulkDeleteProcessName = table.getName() + ".bulkDelete";
|
||||
if(qInstance.getProcess(bulkDeleteProcessName) == null)
|
||||
{
|
||||
@ -1097,13 +1065,129 @@ public class QInstanceEnricher
|
||||
Fields whose switches are off will not be updated."""))
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_EDIT_FORM));
|
||||
|
||||
process.withStep(0, editScreen);
|
||||
process.addStep(0, editScreen);
|
||||
process.getFrontendStep("review").setRecordListFields(editableFields);
|
||||
qInstance.addProcess(process);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void defineTableBulkEditWithFile(QInstance qInstance, QTableMetaData table, String processName)
|
||||
{
|
||||
Map<String, Serializable> values = new HashMap<>();
|
||||
values.put(StreamedETLWithFrontendProcess.FIELD_DESTINATION_TABLE, table.getName());
|
||||
values.put(StreamedETLWithFrontendProcess.FIELD_PREVIEW_MESSAGE, "This is a preview of the records that will be updated.");
|
||||
|
||||
QProcessMetaData process = StreamedETLWithFrontendProcess.defineProcessMetaData(
|
||||
BulkInsertExtractStep.class,
|
||||
BulkInsertTransformStep.class,
|
||||
BulkEditLoadStep.class,
|
||||
values
|
||||
)
|
||||
.withName(processName)
|
||||
.withLabel(table.getLabel() + " Bulk Edit With File")
|
||||
.withTableName(table.getName())
|
||||
.withIsHidden(true)
|
||||
.withPermissionRules(qInstance.getDefaultPermissionRules().clone()
|
||||
.withCustomPermissionChecker(new QCodeReference(BulkTableActionProcessPermissionChecker.class)));
|
||||
|
||||
List<QFieldMetaData> editableFields = table.getFields().values().stream()
|
||||
.filter(QFieldMetaData::getIsEditable)
|
||||
.filter(f -> !f.getType().equals(QFieldType.BLOB))
|
||||
.toList();
|
||||
|
||||
QBackendStepMetaData prepareFileUploadStep = new QBackendStepMetaData()
|
||||
.withName("prepareFileUpload")
|
||||
.withCode(new QCodeReference(BulkInsertPrepareFileUploadStep.class));
|
||||
|
||||
QFrontendStepMetaData uploadScreen = new QFrontendStepMetaData()
|
||||
.withName("upload")
|
||||
.withLabel("Upload File")
|
||||
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB)
|
||||
.withFieldAdornment(FileUploadAdornment.newFieldAdornment()
|
||||
.withValue(FileUploadAdornment.formatDragAndDrop())
|
||||
.withValue(FileUploadAdornment.widthFull()))
|
||||
.withLabel(table.getLabel() + " File")
|
||||
.withIsRequired(true))
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.HTML))
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM));
|
||||
|
||||
QBackendStepMetaData prepareFileMappingStep = new QBackendStepMetaData()
|
||||
.withName("prepareFileMapping")
|
||||
.withCode(new QCodeReference(BulkInsertPrepareFileMappingStep.class));
|
||||
|
||||
QFrontendStepMetaData fileMappingScreen = new QFrontendStepMetaData()
|
||||
.withName("fileMapping")
|
||||
.withLabel("File Mapping")
|
||||
.withBackStepName("prepareFileUpload")
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_FILE_MAPPING_FORM))
|
||||
.withFormField(new QFieldMetaData("hasHeaderRow", QFieldType.BOOLEAN))
|
||||
.withFormField(new QFieldMetaData("layout", QFieldType.STRING)) // is actually PVS, but, this field is only added to help support helpContent, so :shrug:
|
||||
.withFormField(new QFieldMetaData("tableKeyFields", QFieldType.STRING).withPossibleValueSourceName(TableKeyFieldsPossibleValueSource.NAME));
|
||||
|
||||
QBackendStepMetaData receiveFileMappingStep = new QBackendStepMetaData()
|
||||
.withName("receiveFileMapping")
|
||||
.withCode(new QCodeReference(BulkInsertReceiveFileMappingStep.class));
|
||||
|
||||
QBackendStepMetaData prepareValueMappingStep = new QBackendStepMetaData()
|
||||
.withName("prepareValueMapping")
|
||||
.withCode(new QCodeReference(BulkInsertPrepareValueMappingStep.class));
|
||||
|
||||
QFrontendStepMetaData valueMappingScreen = new QFrontendStepMetaData()
|
||||
.withName("valueMapping")
|
||||
.withLabel("Value Mapping")
|
||||
.withBackStepName("prepareFileMapping")
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_VALUE_MAPPING_FORM));
|
||||
|
||||
QBackendStepMetaData receiveValueMappingStep = new QBackendStepMetaData()
|
||||
.withName("receiveValueMapping")
|
||||
.withCode(new QCodeReference(BulkInsertReceiveValueMappingStep.class));
|
||||
|
||||
int i = 0;
|
||||
process.withStep(i++, prepareFileUploadStep);
|
||||
process.withStep(i++, uploadScreen);
|
||||
|
||||
process.withStep(i++, prepareFileMappingStep);
|
||||
process.withStep(i++, fileMappingScreen);
|
||||
process.withStep(i++, receiveFileMappingStep);
|
||||
|
||||
process.withStep(i++, prepareValueMappingStep);
|
||||
process.withStep(i++, valueMappingScreen);
|
||||
process.withStep(i++, receiveValueMappingStep);
|
||||
|
||||
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW).setRecordListFields(editableFields);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// put the bulk-load profile form (e.g., for saving it) on the review & result screens) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW)
|
||||
.withBackStepName("prepareFileMapping")
|
||||
.getComponents().add(0, new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_PROFILE_FORM));
|
||||
|
||||
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_RESULT)
|
||||
.getComponents().add(0, new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_PROFILE_FORM));
|
||||
|
||||
qInstance.addProcess(process);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QPossibleValueSource defineTableKeyFieldsPossibleValueSource()
|
||||
{
|
||||
return (new QPossibleValueSource()
|
||||
.withName(TableKeyFieldsPossibleValueSource.NAME)
|
||||
.withType(QPossibleValueSourceType.CUSTOM)
|
||||
.withCustomCodeReference(new QCodeReference(TableKeyFieldsPossibleValueSource.class)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -1448,10 +1532,10 @@ public class QInstanceEnricher
|
||||
if(possibleValueSource.getIdType() == null)
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
|
||||
if(table != null && table.getFields() != null)
|
||||
if(table != null)
|
||||
{
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
QFieldMetaData primaryKeyFieldMetaData = CollectionUtils.nonNullMap(table.getFields()).get(primaryKeyField);
|
||||
QFieldMetaData primaryKeyFieldMetaData = table.getFields().get(primaryKeyField);
|
||||
if(primaryKeyFieldMetaData != null)
|
||||
{
|
||||
possibleValueSource.setIdType(primaryKeyFieldMetaData.getType());
|
||||
@ -1485,7 +1569,7 @@ public class QInstanceEnricher
|
||||
{
|
||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||
|
||||
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getMethod("getPossibleValue", Serializable.class);
|
||||
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
|
||||
Type returnType = getPossibleValueMethod.getGenericReturnType();
|
||||
Type idType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
|
||||
|
||||
@ -1521,18 +1605,7 @@ public class QInstanceEnricher
|
||||
if(enrichMethod.isPresent())
|
||||
{
|
||||
Class<?> parameterType = enrichMethod.get().getParameterTypes()[0];
|
||||
|
||||
Set<String> existingPluginIdentifiers = enricherPlugins.getOrDefault(parameterType, Collections.emptyList())
|
||||
.stream().map(p -> p.getPluginIdentifier())
|
||||
.collect(Collectors.toSet());
|
||||
if(existingPluginIdentifiers.contains(plugin.getPluginIdentifier()))
|
||||
{
|
||||
LOG.debug("Enricher plugin is already registered - not re-adding it", logPair("pluginIdentifer", plugin.getPluginIdentifier()));
|
||||
}
|
||||
else
|
||||
{
|
||||
enricherPlugins.add(parameterType, plugin);
|
||||
}
|
||||
enricherPlugins.add(parameterType, plugin);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1552,17 +1625,6 @@ public class QInstanceEnricher
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for enricherPlugins
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static ListingHash<Class<?>, QInstanceEnricherPluginInterface<?>> getEnricherPlugins()
|
||||
{
|
||||
return enricherPlugins;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** scan the classpath for classes in the specified package name which
|
||||
** implement the QInstanceEnricherPluginInterface - any found get added
|
||||
|
@ -36,7 +36,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpFormat;
|
||||
@ -164,23 +163,6 @@ public class QInstanceHelpContentManager
|
||||
{
|
||||
processHelpContentForInstance(qInstance, key, slotName, roles, helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(QSupplementalInstanceMetaData supplementalInstanceMetaData : qInstance.getSupplementalMetaData().values())
|
||||
{
|
||||
if(supplementalInstanceMetaData instanceof QHelpContentPlugin helpContentPlugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
helpContentPlugin.acceptHelpContent(qInstance, helpContent, nameValuePairs);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error processing a helpContent record in a helpContentPlugin", e, logPair("pluginName", supplementalInstanceMetaData.getName()), logPair("id", record.getValue("id")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -41,8 +41,7 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandlerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||
@ -73,7 +72,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
|
||||
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.ValueTooLongBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||
@ -241,10 +239,8 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* this method still supports the deprecated MetaDataFilter (plus its
|
||||
* replacement, MetaDataActionCustomizer
|
||||
**
|
||||
***************************************************************************/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void validateInstanceAttributes(QInstance qInstance)
|
||||
{
|
||||
if(qInstance.getMetaDataFilter() != null)
|
||||
@ -256,17 +252,6 @@ public class QInstanceValidator
|
||||
{
|
||||
validateSimpleCodeReference("Instance metaDataActionCustomizer ", qInstance.getMetaDataActionCustomizer(), MetaDataActionCustomizerInterface.class);
|
||||
}
|
||||
|
||||
if(qInstance.getTableCustomizers() != null)
|
||||
{
|
||||
for(Map.Entry<String, List<QCodeReference>> entry : qInstance.getTableCustomizers().entrySet())
|
||||
{
|
||||
for(QCodeReference codeReference : CollectionUtils.nonNullList(entry.getValue()))
|
||||
{
|
||||
validateSimpleCodeReference("Instance tableCustomizer of type " + entry.getKey() + ": ", codeReference, TableCustomizerInterface.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -298,18 +283,7 @@ public class QInstanceValidator
|
||||
if(validateMethod.isPresent())
|
||||
{
|
||||
Class<?> parameterType = validateMethod.get().getParameterTypes()[0];
|
||||
|
||||
Set<String> existingPluginIdentifiers = validatorPlugins.getOrDefault(parameterType, Collections.emptyList())
|
||||
.stream().map(p -> p.getPluginIdentifier())
|
||||
.collect(Collectors.toSet());
|
||||
if(existingPluginIdentifiers.contains(plugin.getPluginIdentifier()))
|
||||
{
|
||||
LOG.debug("Validator plugin is already registered - not re-adding it", logPair("pluginIdentifer", plugin.getPluginIdentifier()));
|
||||
}
|
||||
else
|
||||
{
|
||||
validatorPlugins.add(parameterType, plugin);
|
||||
}
|
||||
validatorPlugins.add(parameterType, plugin);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -329,17 +303,6 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for validatorPlugins
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static ListingHash<Class<?>, QInstanceValidatorPluginInterface<?>> getValidatorPlugins()
|
||||
{
|
||||
return validatorPlugins;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -1185,21 +1148,6 @@ public class QInstanceValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateFieldSupplementalMetaData(field, qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void validateFieldSupplementalMetaData(QFieldMetaData field, QInstance qInstance)
|
||||
{
|
||||
for(QSupplementalFieldMetaData supplementalFieldMetaData : CollectionUtils.nonNullMap(field.getSupplementalMetaData()).values())
|
||||
{
|
||||
supplementalFieldMetaData.validate(qInstance, field, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1392,7 +1340,7 @@ public class QInstanceValidator
|
||||
numberSet++;
|
||||
if(preAssertionsForCodeReference(action.getCodeReference(), actionPrefix))
|
||||
{
|
||||
validateSimpleCodeReference(actionPrefix + "code reference: ", action.getCodeReference(), RecordAutomationHandlerInterface.class);
|
||||
validateSimpleCodeReference(actionPrefix + "code reference: ", action.getCodeReference(), RecordAutomationHandler.class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1753,8 +1701,6 @@ public class QInstanceValidator
|
||||
|
||||
validateSimpleCodeReference("Process " + processName + " code reference:", codeReference, expectedClass);
|
||||
}
|
||||
|
||||
validateFieldSupplementalMetaData(fieldMetaData, qInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2292,7 +2238,8 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?>... anyOfExpectedClasses)
|
||||
@SafeVarargs
|
||||
private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?>... anyOfExpectedClasses)
|
||||
{
|
||||
if(!preAssertionsForCodeReference(codeReference, prefix))
|
||||
{
|
||||
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.assessment;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** marker for an object which can be processed by the QInstanceAssessor.
|
||||
*******************************************************************************/
|
||||
public interface Assessable
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void assess(QInstanceAssessor qInstanceAssessor, QInstance qInstance);
|
||||
}
|
@ -1,219 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.assessment;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** POC of a class that is meant to review meta-data for accuracy vs. real backends.
|
||||
*******************************************************************************/
|
||||
public class QInstanceAssessor
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QInstanceAssessor.class);
|
||||
|
||||
private final QInstance qInstance;
|
||||
|
||||
private List<String> errors = new ArrayList<>();
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
private List<String> suggestions = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstanceAssessor(QInstance qInstance)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void assess()
|
||||
{
|
||||
for(QBackendMetaData backend : qInstance.getBackends().values())
|
||||
{
|
||||
if(backend instanceof Assessable assessable)
|
||||
{
|
||||
assessable.assess(this, qInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
|
||||
public String getSummary()
|
||||
{
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
///////////////////////////
|
||||
// print header & errors //
|
||||
///////////////////////////
|
||||
if(CollectionUtils.nullSafeIsEmpty(errors))
|
||||
{
|
||||
rs.append("Assessment passed with no errors! \uD83D\uDE0E\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
rs.append("Assessment found the following ").append(StringUtils.plural(errors, "error", "errors")).append(": \uD83D\uDE32\n");
|
||||
|
||||
for(String error : errors)
|
||||
{
|
||||
rs.append(" - ").append(error).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// print warnings if there are any //
|
||||
/////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(warnings))
|
||||
{
|
||||
rs.append("\nAssessment found the following ").append(StringUtils.plural(warnings, "warning", "warnings")).append(": \uD83E\uDD28\n");
|
||||
|
||||
for(String warning : warnings)
|
||||
{
|
||||
rs.append(" - ").append(warning).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// print suggestions, if there were any //
|
||||
//////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(suggestions))
|
||||
{
|
||||
rs.append("\nThe following ").append(StringUtils.plural(suggestions, "fix is", "fixes are")).append(" suggested: \uD83E\uDD13\n");
|
||||
|
||||
for(String suggestion : suggestions)
|
||||
{
|
||||
rs.append("\n").append(suggestion).append("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for qInstance
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstance getInstance()
|
||||
{
|
||||
return qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for errors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getErrors()
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for warnings
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getWarnings()
|
||||
{
|
||||
return warnings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addError(String errorMessage)
|
||||
{
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addWarning(String warningMessage)
|
||||
{
|
||||
warnings.add(warningMessage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addError(String errorMessage, Exception e)
|
||||
{
|
||||
addError(errorMessage + " : " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addSuggestion(String message)
|
||||
{
|
||||
suggestions.add(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public int getExitCode()
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
return (1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,13 +37,4 @@ public interface QInstanceEnricherPluginInterface<T>
|
||||
*******************************************************************************/
|
||||
void enrich(T object, QInstance qInstance);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default String getPluginIdentifier()
|
||||
{
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,13 +38,4 @@ public interface QInstanceValidatorPluginInterface<T>
|
||||
*******************************************************************************/
|
||||
void validate(T object, QInstance qInstance, QInstanceValidator qInstanceValidator);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default String getPluginIdentifier()
|
||||
{
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.audits;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
|
||||
|
||||
@ -37,8 +36,6 @@ public class AuditInput extends AbstractActionInput implements Serializable
|
||||
{
|
||||
private List<AuditSingleInput> auditSingleInputList = new ArrayList<>();
|
||||
|
||||
private QBackendTransaction transaction;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -95,42 +92,4 @@ public class AuditInput extends AbstractActionInput implements Serializable
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Getter for transaction
|
||||
* @see #withTransaction(QBackendTransaction)
|
||||
*******************************************************************************/
|
||||
public QBackendTransaction getTransaction()
|
||||
{
|
||||
return (this.transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Setter for transaction
|
||||
* @see #withTransaction(QBackendTransaction)
|
||||
*******************************************************************************/
|
||||
public void setTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Fluent setter for transaction
|
||||
*
|
||||
* @param transaction
|
||||
* transaction upon which the audits will be inserted.
|
||||
*
|
||||
* @return this
|
||||
*******************************************************************************/
|
||||
public AuditInput withTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.model.actions.audits;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -39,8 +38,6 @@ public class DMLAuditInput extends AbstractActionInput implements Serializable
|
||||
private List<QRecord> oldRecordList;
|
||||
private AbstractTableActionInput tableActionInput;
|
||||
|
||||
private QBackendTransaction transaction;
|
||||
|
||||
private String auditContext = null;
|
||||
|
||||
|
||||
@ -167,43 +164,4 @@ public class DMLAuditInput extends AbstractActionInput implements Serializable
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Getter for transaction
|
||||
* @see #withTransaction(QBackendTransaction)
|
||||
*******************************************************************************/
|
||||
public QBackendTransaction getTransaction()
|
||||
{
|
||||
return (this.transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Setter for transaction
|
||||
* @see #withTransaction(QBackendTransaction)
|
||||
*******************************************************************************/
|
||||
public void setTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Fluent setter for transaction
|
||||
*
|
||||
* @param transaction
|
||||
* transaction that will be used for inserting the audits, where (presumably)
|
||||
* the DML against the record occurred as well
|
||||
*
|
||||
* @return this
|
||||
*******************************************************************************/
|
||||
public DMLAuditInput withTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.metadata;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
|
||||
@ -42,13 +41,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
*******************************************************************************/
|
||||
public class MetaDataOutput extends AbstractActionOutput
|
||||
{
|
||||
private Map<String, QFrontendTableMetaData> tables;
|
||||
private Map<String, QFrontendProcessMetaData> processes;
|
||||
private Map<String, QFrontendReportMetaData> reports;
|
||||
private Map<String, QFrontendAppMetaData> apps;
|
||||
private Map<String, QFrontendWidgetMetaData> widgets;
|
||||
private Map<String, String> environmentValues;
|
||||
private Map<String, QSupplementalInstanceMetaData> supplementalInstanceMetaData;
|
||||
private Map<String, QFrontendTableMetaData> tables;
|
||||
private Map<String, QFrontendProcessMetaData> processes;
|
||||
private Map<String, QFrontendReportMetaData> reports;
|
||||
private Map<String, QFrontendAppMetaData> apps;
|
||||
private Map<String, QFrontendWidgetMetaData> widgets;
|
||||
private Map<String, String> environmentValues;
|
||||
|
||||
private List<AppTreeNode> appTree;
|
||||
private QBrandingMetaData branding;
|
||||
@ -232,28 +230,6 @@ public class MetaDataOutput extends AbstractActionOutput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for supplementalInstanceMetaData
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QSupplementalInstanceMetaData> getSupplementalInstanceMetaData()
|
||||
{
|
||||
return supplementalInstanceMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for supplementalInstanceMetaData
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSupplementalInstanceMetaData(Map<String, QSupplementalInstanceMetaData> supplementalInstanceMetaData)
|
||||
{
|
||||
this.supplementalInstanceMetaData = supplementalInstanceMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for helpContents
|
||||
**
|
||||
|
@ -23,11 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -35,45 +31,6 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
*******************************************************************************/
|
||||
public interface ProcessSummaryLineInterface extends Serializable
|
||||
{
|
||||
QLogger LOG = QLogger.getLogger(ProcessSummaryLineInterface.class);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static void log(String message, Serializable summaryLines, List<LogPair> additionalLogPairs)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(summaryLines instanceof List)
|
||||
{
|
||||
List<ProcessSummaryLineInterface> list = (List<ProcessSummaryLineInterface>) summaryLines;
|
||||
|
||||
List<LogPair> logPairs = new ArrayList<>();
|
||||
for(ProcessSummaryLineInterface processSummaryLineInterface : list)
|
||||
{
|
||||
LogPair logPair = processSummaryLineInterface.toLogPair();
|
||||
logPair.setKey(logPair.getKey() + logPairs.size());
|
||||
logPairs.add(logPair);
|
||||
}
|
||||
|
||||
if(additionalLogPairs != null)
|
||||
{
|
||||
logPairs.addAll(0, additionalLogPairs);
|
||||
}
|
||||
logPairs.add(0, logPair("message", message));
|
||||
|
||||
LOG.info(logPairs);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Unrecognized type for summaryLines (expected List)", logPair("processSummary", summaryLines));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error logging a process summary", e, logPair("processSummary", summaryLines));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for status
|
||||
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface to mark enums (presumably classes too, but the original intent is
|
||||
** enums) that can be added to insert/update/delete action inputs to flag behaviors
|
||||
*******************************************************************************/
|
||||
public interface ActionFlag extends Serializable
|
||||
{
|
||||
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.qqq.backend.core.model.actions.tables;
|
||||
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Common getters & setters, shared by both QueryInput and CountInput.
|
||||
**
|
||||
** Original impetus for this class is the setCommonParamsFrom() method - for cases
|
||||
** where we need to change a Query to a Get, or vice-versa, and we want to copy over
|
||||
** all of those input params.
|
||||
*******************************************************************************/
|
||||
public interface QueryOrCountInputInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Set in THIS, the "common params" (e.g., common to both Query & Count inputs)
|
||||
** from the parameter SOURCE object.
|
||||
*******************************************************************************/
|
||||
default void setCommonParamsFrom(QueryOrCountInputInterface source)
|
||||
{
|
||||
this.setTransaction(source.getTransaction());
|
||||
this.setFilter(source.getFilter());
|
||||
this.setTableName(source.getTableName());
|
||||
this.setQueryJoins(source.getQueryJoins());
|
||||
this.setTimeoutSeconds(source.getTimeoutSeconds());
|
||||
this.setQueryHints(source.getQueryHints());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String getTableName();
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void setTableName(String tableName);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QQueryFilter getFilter();
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void setFilter(QQueryFilter filter);
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transaction
|
||||
*******************************************************************************/
|
||||
QBackendTransaction getTransaction();
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for transaction
|
||||
*******************************************************************************/
|
||||
void setTransaction(QBackendTransaction transaction);
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryJoins
|
||||
*******************************************************************************/
|
||||
List<QueryJoin> getQueryJoins();
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryJoins
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setQueryJoins(List<QueryJoin> queryJoins);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
Integer getTimeoutSeconds();
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void setTimeoutSeconds(Integer timeoutSeconds);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryHints
|
||||
*******************************************************************************/
|
||||
EnumSet<QueryHint> getQueryHints();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryHints
|
||||
*******************************************************************************/
|
||||
void setQueryHints(EnumSet<QueryHint> queryHints);
|
||||
}
|
@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.aggregate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -38,8 +37,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
*******************************************************************************/
|
||||
public class AggregateInput extends AbstractTableActionInput
|
||||
{
|
||||
private QBackendTransaction transaction;
|
||||
|
||||
private QQueryFilter filter;
|
||||
private List<Aggregate> aggregates;
|
||||
private List<GroupBy> groupBys = new ArrayList<>();
|
||||
@ -407,35 +404,4 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
|
||||
return (queryHints.contains(queryHint));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transaction
|
||||
*******************************************************************************/
|
||||
public QBackendTransaction getTransaction()
|
||||
{
|
||||
return (this.transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for transaction
|
||||
*******************************************************************************/
|
||||
public void setTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for transaction
|
||||
*******************************************************************************/
|
||||
public AggregateInput withTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -25,10 +25,8 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.count;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrCountInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
|
||||
@ -37,10 +35,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
** Input data for the Count action
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class CountInput extends AbstractTableActionInput implements QueryOrCountInputInterface
|
||||
public class CountInput extends AbstractTableActionInput
|
||||
{
|
||||
private QBackendTransaction transaction;
|
||||
private QQueryFilter filter;
|
||||
private QQueryFilter filter;
|
||||
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
@ -288,35 +285,4 @@ public class CountInput extends AbstractTableActionInput implements QueryOrCount
|
||||
|
||||
return (queryHints.contains(queryHint));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transaction
|
||||
*******************************************************************************/
|
||||
public QBackendTransaction getTransaction()
|
||||
{
|
||||
return (this.transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for transaction
|
||||
*******************************************************************************/
|
||||
public void setTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for transaction
|
||||
*******************************************************************************/
|
||||
public CountInput withTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
this.transaction = transaction;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -24,12 +24,9 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.delete;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -50,8 +47,6 @@ public class DeleteInput extends AbstractTableActionInput
|
||||
private boolean omitDmlAudit = false;
|
||||
private String auditContext = null;
|
||||
|
||||
private Set<ActionFlag> flags;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -300,65 +295,4 @@ public class DeleteInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for flags
|
||||
*******************************************************************************/
|
||||
public Set<ActionFlag> getFlags()
|
||||
{
|
||||
return (this.flags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for flags
|
||||
*******************************************************************************/
|
||||
public void setFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for flags
|
||||
*******************************************************************************/
|
||||
public DeleteInput withFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public DeleteInput withFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
this.flags = new HashSet<>();
|
||||
}
|
||||
this.flags.add(flag);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (this.flags.contains(flag));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,12 +23,9 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.insert;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -51,8 +48,6 @@ public class InsertInput extends AbstractTableActionInput
|
||||
private boolean omitDmlAudit = false;
|
||||
private String auditContext = null;
|
||||
|
||||
private Set<ActionFlag> flags;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -321,65 +316,4 @@ public class InsertInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for flags
|
||||
*******************************************************************************/
|
||||
public Set<ActionFlag> getFlags()
|
||||
{
|
||||
return (this.flags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for flags
|
||||
*******************************************************************************/
|
||||
public void setFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for flags
|
||||
*******************************************************************************/
|
||||
public InsertInput withFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public InsertInput withFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
this.flags = new HashSet<>();
|
||||
}
|
||||
this.flags.add(flag);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (this.flags.contains(flag));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -659,21 +659,6 @@ public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// recursively process sub filters! //
|
||||
//////////////////////////////////////
|
||||
for(QQueryFilter subFilter : CollectionUtils.nonNullList(getSubFilters()))
|
||||
{
|
||||
try
|
||||
{
|
||||
subFilter.interpretValues(inputValues, useCase);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
caughtExceptions.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
if(!caughtExceptions.isEmpty())
|
||||
{
|
||||
String message = "Error interpreting filter values: " + StringUtils.joinWithCommasAndAnd(caughtExceptions.stream().map(e -> e.getMessage()).toList());
|
||||
@ -839,7 +824,6 @@ public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for subFilterSetOperator
|
||||
*******************************************************************************/
|
||||
@ -870,7 +854,6 @@ public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
|
@ -32,7 +32,6 @@ import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrCountInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
|
||||
|
||||
@ -43,7 +42,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterf
|
||||
** CountInput, and AggregateInput}, with common attributes for all of these
|
||||
** "read" operations (like, queryHints,
|
||||
*******************************************************************************/
|
||||
public class QueryInput extends AbstractTableActionInput implements QueryOrGetInputInterface, QueryOrCountInputInterface, Cloneable
|
||||
public class QueryInput extends AbstractTableActionInput implements QueryOrGetInputInterface, Cloneable
|
||||
{
|
||||
private QBackendTransaction transaction;
|
||||
private QQueryFilter filter;
|
||||
|
@ -22,12 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.replace;
|
||||
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
@ -42,14 +39,12 @@ public class ReplaceInput extends AbstractTableActionInput
|
||||
private UniqueKey key;
|
||||
private List<QRecord> records;
|
||||
private QQueryFilter filter;
|
||||
private boolean performDeletes = true;
|
||||
private boolean allowNullKeyValuesToEqual = false;
|
||||
private boolean setPrimaryKeyInInsertedRecords = false;
|
||||
private boolean performDeletes = true;
|
||||
private boolean allowNullKeyValuesToEqual = false;
|
||||
private boolean setPrimaryKeyInInsertedRecords = false;
|
||||
|
||||
private boolean omitDmlAudit = false;
|
||||
|
||||
private Set<ActionFlag> flags;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -308,65 +303,4 @@ public class ReplaceInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for flags
|
||||
*******************************************************************************/
|
||||
public Set<ActionFlag> getFlags()
|
||||
{
|
||||
return (this.flags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for flags
|
||||
*******************************************************************************/
|
||||
public void setFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for flags
|
||||
*******************************************************************************/
|
||||
public ReplaceInput withFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ReplaceInput withFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
this.flags = new HashSet<>();
|
||||
}
|
||||
this.flags.add(flag);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (this.flags.contains(flag));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,12 +23,9 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.update;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -59,8 +56,6 @@ public class UpdateInput extends AbstractTableActionInput
|
||||
private boolean omitModifyDateUpdate = false;
|
||||
private String auditContext = null;
|
||||
|
||||
private Set<ActionFlag> flags;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -390,65 +385,4 @@ public class UpdateInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for flags
|
||||
*******************************************************************************/
|
||||
public Set<ActionFlag> getFlags()
|
||||
{
|
||||
return (this.flags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for flags
|
||||
*******************************************************************************/
|
||||
public void setFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for flags
|
||||
*******************************************************************************/
|
||||
public UpdateInput withFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public UpdateInput withFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
this.flags = new HashSet<>();
|
||||
}
|
||||
this.flags.add(flag);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (this.flags.contains(flag));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,13 +35,13 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
*******************************************************************************/
|
||||
public class SearchPossibleValueSourceInput extends AbstractActionInput implements Cloneable
|
||||
{
|
||||
private String possibleValueSourceName;
|
||||
private QQueryFilter defaultQueryFilter;
|
||||
private String searchTerm;
|
||||
private List<Serializable> idList;
|
||||
private List<String> labelList;
|
||||
|
||||
private Map<String, Serializable> otherValues;
|
||||
private String possibleValueSourceName;
|
||||
private QQueryFilter defaultQueryFilter;
|
||||
private String searchTerm;
|
||||
private List<Serializable> idList;
|
||||
private List<String> labelList;
|
||||
private Map<String, String> pathParamMap;
|
||||
private Map<String, List<String>> queryParamMap;
|
||||
|
||||
private Integer skip = 0;
|
||||
private Integer limit = 250;
|
||||
@ -320,31 +320,62 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for otherValues
|
||||
** Getter for pathParamMap
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getOtherValues()
|
||||
public Map<String, String> getPathParamMap()
|
||||
{
|
||||
return (this.otherValues);
|
||||
return (this.pathParamMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for otherValues
|
||||
** Setter for pathParamMap
|
||||
*******************************************************************************/
|
||||
public void setOtherValues(Map<String, Serializable> otherValues)
|
||||
public void setPathParamMap(Map<String, String> pathParamMap)
|
||||
{
|
||||
this.otherValues = otherValues;
|
||||
this.pathParamMap = pathParamMap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for otherValues
|
||||
** Fluent setter for pathParamMap
|
||||
*******************************************************************************/
|
||||
public SearchPossibleValueSourceInput withOtherValues(Map<String, Serializable> otherValues)
|
||||
public SearchPossibleValueSourceInput withPathParamMap(Map<String, String> pathParamMap)
|
||||
{
|
||||
this.otherValues = otherValues;
|
||||
this.pathParamMap = pathParamMap;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryParamMap
|
||||
*******************************************************************************/
|
||||
public Map<String, List<String>> getQueryParamMap()
|
||||
{
|
||||
return (this.queryParamMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryParamMap
|
||||
*******************************************************************************/
|
||||
public void setQueryParamMap(Map<String, List<String>> queryParamMap)
|
||||
{
|
||||
this.queryParamMap = queryParamMap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryParamMap
|
||||
*******************************************************************************/
|
||||
public SearchPossibleValueSourceInput withQueryParamMap(Map<String, List<String>> queryParamMap)
|
||||
{
|
||||
this.queryParamMap = queryParamMap;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TablesSupportingAutomationsPossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
|
||||
@ -48,16 +48,16 @@ public class TableTrigger extends QRecordEntity
|
||||
@QField(isEditable = false)
|
||||
private Instant modifyDate;
|
||||
|
||||
@QField(possibleValueSourceName = TablesSupportingAutomationsPossibleValueSourceMetaDataProvider.NAME, isRequired = true)
|
||||
@QField(possibleValueSourceName = TablesPossibleValueSourceMetaDataProvider.NAME)
|
||||
private String tableName;
|
||||
|
||||
@QField(possibleValueSourceName = SavedView.TABLE_NAME)
|
||||
private Integer filterId;
|
||||
|
||||
@QField(possibleValueSourceName = Script.TABLE_NAME, isRequired = true)
|
||||
@QField(possibleValueSourceName = Script.TABLE_NAME)
|
||||
private Integer scriptId;
|
||||
|
||||
@QField(defaultValue = "500")
|
||||
@QField()
|
||||
private Integer priority;
|
||||
|
||||
@QField()
|
||||
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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.qqq.backend.core.model.bulk;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
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.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TableKeyFieldsPossibleValueSource implements QCustomPossibleValueProvider<String>
|
||||
{
|
||||
public static final String NAME = "tableKeyFields";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QPossibleValue<String> getPossibleValue(Serializable tableAndKey)
|
||||
{
|
||||
QPossibleValue<String> possibleValue = null;
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// keys are in the format <tableName>-<key1>|<key2>|<key3> //
|
||||
/////////////////////////////////////////////////////////////
|
||||
String[] keyParts = tableAndKey.toString().split("-");
|
||||
String tableName = keyParts[0];
|
||||
String key = keyParts[1];
|
||||
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
if(table.getPrimaryKeyField().equals(key))
|
||||
{
|
||||
String id = table.getPrimaryKeyField();
|
||||
String label = table.getField(table.getPrimaryKeyField()).getLabel();
|
||||
possibleValue = new QPossibleValue<>(id, label);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(UniqueKey uniqueKey : table.getUniqueKeys())
|
||||
{
|
||||
String potentialMatch = getIdFromUniqueKey(uniqueKey);
|
||||
if(potentialMatch.equals(key))
|
||||
{
|
||||
String id = potentialMatch;
|
||||
String label = getLabelFromUniqueKey(table, uniqueKey);
|
||||
possibleValue = new QPossibleValue<>(id, label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (possibleValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QPossibleValue<String>> search(SearchPossibleValueSourceInput input) throws QException
|
||||
{
|
||||
List<QPossibleValue<String>> rs = new ArrayList<>();
|
||||
if(!CollectionUtils.nonNullMap(input.getPathParamMap()).containsKey("processName") || input.getPathParamMap().get("processName") == null || input.getPathParamMap().get("processName").isEmpty())
|
||||
{
|
||||
throw (new QException("Path Param of processName was not found."));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// process name will be like tnt.bulkEditWithFile //
|
||||
////////////////////////////////////////////////////
|
||||
String processName = input.getPathParamMap().get("processName");
|
||||
String tableName = processName.split("\\.")[0];
|
||||
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
for(UniqueKey uniqueKey : CollectionUtils.nonNullList(table.getUniqueKeys()))
|
||||
{
|
||||
String id = getIdFromUniqueKey(uniqueKey);
|
||||
String label = getLabelFromUniqueKey(table, uniqueKey);
|
||||
if(!StringUtils.hasContent(input.getSearchTerm()) || input.getSearchTerm().equals(id))
|
||||
{
|
||||
rs.add(new QPossibleValue<>(id, label));
|
||||
}
|
||||
}
|
||||
rs.sort(Comparator.comparing(QPossibleValue::getLabel));
|
||||
|
||||
///////////////////////////////
|
||||
// put the primary key first //
|
||||
///////////////////////////////
|
||||
if(!StringUtils.hasContent(input.getSearchTerm()) || input.getSearchTerm().equals(table.getPrimaryKeyField()))
|
||||
{
|
||||
rs.add(0, new QPossibleValue<>(table.getPrimaryKeyField(), table.getField(table.getPrimaryKeyField()).getLabel()));
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getIdFromUniqueKey(UniqueKey uniqueKey)
|
||||
{
|
||||
return (StringUtils.join("|", uniqueKey.getFieldNames()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getLabelFromUniqueKey(QTableMetaData tableMetaData, UniqueKey uniqueKey)
|
||||
{
|
||||
List<String> fieldLabels = new ArrayList<>(uniqueKey.getFieldNames().stream().map(f -> tableMetaData.getField(f).getLabel()).toList());
|
||||
fieldLabels.sort(Comparator.naturalOrder());
|
||||
return (StringUtils.joinWithCommasAndAnd(fieldLabels));
|
||||
}
|
||||
}
|
@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
@ -53,7 +52,6 @@ public class ChildRecordListData extends QWidgetData
|
||||
private Map<String, Serializable> defaultValuesForNewChildRecords;
|
||||
private Set<String> disabledFieldsForNewChildRecords;
|
||||
private Map<String, String> defaultValuesForNewChildRecordsFromParentFields;
|
||||
private List<String> omitFieldNames;
|
||||
|
||||
|
||||
|
||||
@ -557,37 +555,6 @@ public class ChildRecordListData extends QWidgetData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for omitFieldNames
|
||||
*******************************************************************************/
|
||||
public List<String> getOmitFieldNames()
|
||||
{
|
||||
return (this.omitFieldNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for omitFieldNames
|
||||
*******************************************************************************/
|
||||
public void setOmitFieldNames(List<String> omitFieldNames)
|
||||
{
|
||||
this.omitFieldNames = omitFieldNames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for omitFieldNames
|
||||
*******************************************************************************/
|
||||
public ChildRecordListData withOmitFieldNames(List<String> omitFieldNames)
|
||||
{
|
||||
this.omitFieldNames = omitFieldNames;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -35,15 +35,7 @@ public class FilterAndColumnsSetupData extends QWidgetData
|
||||
private Boolean allowVariables = false;
|
||||
private Boolean hideColumns = false;
|
||||
private Boolean hidePreview = false;
|
||||
private Boolean hideSortBy = false;
|
||||
private Boolean overrideIsEditable;
|
||||
private List<String> filterDefaultFieldNames;
|
||||
private List<String> omitExposedJoins;
|
||||
|
||||
private Boolean isApiVersioned = false;
|
||||
private String apiName;
|
||||
private String apiPath;
|
||||
private String apiVersion;
|
||||
|
||||
private String filterFieldName = "queryFilterJson";
|
||||
private String columnFieldName = "columnsJson";
|
||||
@ -298,227 +290,4 @@ public class FilterAndColumnsSetupData extends QWidgetData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for overrideIsEditable
|
||||
*******************************************************************************/
|
||||
public Boolean getOverrideIsEditable()
|
||||
{
|
||||
return (this.overrideIsEditable);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for overrideIsEditable
|
||||
*******************************************************************************/
|
||||
public void setOverrideIsEditable(Boolean overrideIsEditable)
|
||||
{
|
||||
this.overrideIsEditable = overrideIsEditable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for overrideIsEditable
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withOverrideIsEditable(Boolean overrideIsEditable)
|
||||
{
|
||||
this.overrideIsEditable = overrideIsEditable;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for hideSortBy
|
||||
*******************************************************************************/
|
||||
public Boolean getHideSortBy()
|
||||
{
|
||||
return (this.hideSortBy);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hideSortBy
|
||||
*******************************************************************************/
|
||||
public void setHideSortBy(Boolean hideSortBy)
|
||||
{
|
||||
this.hideSortBy = hideSortBy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for hideSortBy
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withHideSortBy(Boolean hideSortBy)
|
||||
{
|
||||
this.hideSortBy = hideSortBy;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isApiVersioned
|
||||
*******************************************************************************/
|
||||
public Boolean getIsApiVersioned()
|
||||
{
|
||||
return (this.isApiVersioned);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isApiVersioned
|
||||
*******************************************************************************/
|
||||
public void setIsApiVersioned(Boolean isApiVersioned)
|
||||
{
|
||||
this.isApiVersioned = isApiVersioned;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isApiVersioned
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withIsApiVersioned(Boolean isApiVersioned)
|
||||
{
|
||||
this.isApiVersioned = isApiVersioned;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for apiName
|
||||
*******************************************************************************/
|
||||
public String getApiName()
|
||||
{
|
||||
return (this.apiName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for apiName
|
||||
*******************************************************************************/
|
||||
public void setApiName(String apiName)
|
||||
{
|
||||
this.apiName = apiName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for apiName
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withApiName(String apiName)
|
||||
{
|
||||
this.apiName = apiName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for apiPath
|
||||
*******************************************************************************/
|
||||
public String getApiPath()
|
||||
{
|
||||
return (this.apiPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for apiPath
|
||||
*******************************************************************************/
|
||||
public void setApiPath(String apiPath)
|
||||
{
|
||||
this.apiPath = apiPath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for apiPath
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withApiPath(String apiPath)
|
||||
{
|
||||
this.apiPath = apiPath;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for apiVersion
|
||||
*******************************************************************************/
|
||||
public String getApiVersion()
|
||||
{
|
||||
return (this.apiVersion);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for apiVersion
|
||||
*******************************************************************************/
|
||||
public void setApiVersion(String apiVersion)
|
||||
{
|
||||
this.apiVersion = apiVersion;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for apiVersion
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withApiVersion(String apiVersion)
|
||||
{
|
||||
this.apiVersion = apiVersion;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Getter for omitExposedJoins
|
||||
* @see #withOmitExposedJoins(List)
|
||||
*******************************************************************************/
|
||||
public List<String> getOmitExposedJoins()
|
||||
{
|
||||
return (this.omitExposedJoins);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Setter for omitExposedJoins
|
||||
* @see #withOmitExposedJoins(List)
|
||||
*******************************************************************************/
|
||||
public void setOmitExposedJoins(List<String> omitExposedJoins)
|
||||
{
|
||||
this.omitExposedJoins = omitExposedJoins;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Fluent setter for omitExposedJoins
|
||||
*
|
||||
* @param omitExposedJoins
|
||||
* list of tableNames of exposed joins that shouldn't be available in the filter.
|
||||
* @return this
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withOmitExposedJoins(List<String> omitExposedJoins)
|
||||
{
|
||||
this.omitExposedJoins = omitExposedJoins;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -209,7 +209,6 @@ public abstract class QRecordEntity
|
||||
try
|
||||
{
|
||||
QRecord qRecord = new QRecord();
|
||||
qRecord.setTableName(tableName());
|
||||
|
||||
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
|
||||
{
|
||||
|
@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.data;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -258,39 +257,4 @@ public class QRecordWithJoinedRecords extends QRecord
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Given an object of this type (`this`), add a list of join-records to it,
|
||||
* producing a new list, which is `this` × joinRecordList.
|
||||
* One may want to use this in a loop to build a larger cross product - more
|
||||
* to come here in that spirit.
|
||||
* @param joinTable name of the join table (e.g., to prefix the join fields)
|
||||
* @param joinRecordList list of join records. may not be null. may be 0+ size.
|
||||
* @return list of new QRecordWithJoinedRecords, based on `this` with each
|
||||
* joinRecord added (e.g., output list is same size as joinRecordList).
|
||||
* Note that does imply an 'inner' style join - where - if the joinRecordList
|
||||
* is empty, you'll get back an empty list!
|
||||
***************************************************************************/
|
||||
public List<QRecordWithJoinedRecords> buildCrossProduct(String joinTable, List<QRecord> joinRecordList)
|
||||
{
|
||||
List<QRecordWithJoinedRecords> rs = new ArrayList<>();
|
||||
|
||||
for(QRecord joinRecord : joinRecordList)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////
|
||||
// essentially clone the existing QRecordWithJoinedRecords //
|
||||
/////////////////////////////////////////////////////////////
|
||||
QRecordWithJoinedRecords newRecord = new QRecordWithJoinedRecords(mainRecord);
|
||||
components.forEach((k, v) -> newRecord.addJoinedRecordValues(k, v));
|
||||
|
||||
///////////////////////////////////////////
|
||||
// now add the new join record to it too //
|
||||
///////////////////////////////////////////
|
||||
newRecord.addJoinedRecordValues(joinTable, joinRecord);
|
||||
rs.add(newRecord);
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
}
|
||||
|
@ -177,18 +177,6 @@ public class MetaDataProducerHelper
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// sort them by sort order, then by the type that they return, as set up in the static map //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
sortMetaDataProducers(producers);
|
||||
|
||||
return (producers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void sortMetaDataProducers(List<MetaDataProducerInterface<?>> producers)
|
||||
{
|
||||
producers.sort(Comparator
|
||||
.comparing((MetaDataProducerInterface<?> p) -> p.getSortOrder())
|
||||
.thenComparing((MetaDataProducerInterface<?> p) ->
|
||||
@ -203,8 +191,9 @@ public class MetaDataProducerHelper
|
||||
return (0);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return (producers);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -428,7 +417,7 @@ public class MetaDataProducerHelper
|
||||
return (null);
|
||||
}
|
||||
|
||||
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName, childTable.childJoin().orderBy(), childTable.childJoin().isOneToOne());
|
||||
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName, childTable.childJoin().orderBy());
|
||||
producer.setSourceClass(entityClass);
|
||||
return producer;
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ public class MetaDataProducerMultiOutput implements MetaDataProducerOutput, Sour
|
||||
{
|
||||
List<T> rs = new ArrayList<>();
|
||||
|
||||
for(MetaDataProducerOutput content : CollectionUtils.nonNullList(contents))
|
||||
for(MetaDataProducerOutput content : contents)
|
||||
{
|
||||
if(content instanceof MetaDataProducerMultiOutput multiOutput)
|
||||
{
|
||||
@ -145,36 +145,4 @@ public class MetaDataProducerMultiOutput implements MetaDataProducerOutput, Sour
|
||||
setSourceQBitName(sourceQBitName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* get a typed and named meta-data object out of this output container.
|
||||
*
|
||||
* @param <C> the type of the object to return, e.g., QTableMetaData
|
||||
* @param outputClass the class for the type to return
|
||||
* @param name the name of the object, e.g., a table or process name.
|
||||
* @return the requested TopLevelMetaDataInterface object (in the requested
|
||||
* type), or null if not found.
|
||||
***************************************************************************/
|
||||
public <C extends TopLevelMetaDataInterface> C get(Class<C> outputClass, String name)
|
||||
{
|
||||
for(MetaDataProducerOutput content : CollectionUtils.nonNullList(contents))
|
||||
{
|
||||
if(content instanceof MetaDataProducerMultiOutput multiOutput)
|
||||
{
|
||||
C c = multiOutput.get(outputClass, name);
|
||||
if(c != null)
|
||||
{
|
||||
return (c);
|
||||
}
|
||||
}
|
||||
else if(outputClass.isInstance(content) && name.equals(((TopLevelMetaDataInterface)content).getName()))
|
||||
{
|
||||
return (C) content;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@ -31,7 +30,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -67,7 +65,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import io.github.cdimascio.dotenv.DotenvEntry;
|
||||
@ -119,8 +116,6 @@ public class QInstance
|
||||
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
|
||||
private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone();
|
||||
|
||||
private ListingHash<String, QCodeReference> tableCustomizers;
|
||||
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
private QCodeReference metaDataFilter = null;
|
||||
|
||||
@ -1628,76 +1623,4 @@ public class QInstance
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableCustomizers
|
||||
*******************************************************************************/
|
||||
public ListingHash<String, QCodeReference> getTableCustomizers()
|
||||
{
|
||||
return (this.tableCustomizers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableCustomizers
|
||||
*******************************************************************************/
|
||||
public void setTableCustomizers(ListingHash<String, QCodeReference> tableCustomizers)
|
||||
{
|
||||
this.tableCustomizers = tableCustomizers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableCustomizers
|
||||
*******************************************************************************/
|
||||
public QInstance withTableCustomizers(ListingHash<String, QCodeReference> tableCustomizers)
|
||||
{
|
||||
this.tableCustomizers = tableCustomizers;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstance withTableCustomizer(String role, QCodeReference customizer)
|
||||
{
|
||||
if(this.tableCustomizers == null)
|
||||
{
|
||||
this.tableCustomizers = new ListingHash<>();
|
||||
}
|
||||
|
||||
this.tableCustomizers.add(role, customizer);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstance withTableCustomizer(TableCustomizers tableCustomizer, QCodeReference customizer)
|
||||
{
|
||||
return (withTableCustomizer(tableCustomizer.getRole(), customizer));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableCustomizers
|
||||
*******************************************************************************/
|
||||
public List<QCodeReference> getTableCustomizers(TableCustomizers tableCustomizer)
|
||||
{
|
||||
if(this.tableCustomizers == null)
|
||||
{
|
||||
return (Collections.emptyList());
|
||||
}
|
||||
|
||||
return (this.tableCustomizers.getOrDefault(tableCustomizer.getRole(), Collections.emptyList()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -36,7 +37,7 @@ public interface QSupplementalInstanceMetaData extends TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default void enrich(QInstance qInstance)
|
||||
default void enrich(QTableMetaData table)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
|
@ -22,10 +22,6 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base-class for field-level meta-data defined by some supplemental module, etc,
|
||||
** outside of qqq core
|
||||
@ -33,43 +29,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
public abstract class QSupplementalFieldMetaData
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean includeInFrontendMetaData()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public abstract String getType();
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void enrich(QInstance qInstance, QFieldMetaData fieldMetaData)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance, QFieldMetaData fieldMetaData, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,18 +24,14 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehaviorForFrontend;
|
||||
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.QSupplementalFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
@ -55,7 +51,6 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
private boolean isRequired;
|
||||
private boolean isEditable;
|
||||
private boolean isHeavy;
|
||||
private boolean isHidden;
|
||||
private Integer gridColumns;
|
||||
private String possibleValueSourceName;
|
||||
private String displayFormat;
|
||||
@ -65,10 +60,8 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
private List<FieldAdornment> adornments;
|
||||
private List<QHelpContent> helpContents;
|
||||
private QPossibleValueSource inlinePossibleValueSource;
|
||||
private QQueryFilter possibleValueSourceFilter;
|
||||
|
||||
private List<FieldBehaviorForFrontend> behaviors;
|
||||
private Map<String, QSupplementalFieldMetaData> supplementalFieldMetaData;
|
||||
private List<FieldBehaviorForFrontend> behaviors;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// do not add setters. take values from the source-object in the constructor!! //
|
||||
@ -86,7 +79,6 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
this.isRequired = fieldMetaData.getIsRequired();
|
||||
this.isEditable = fieldMetaData.getIsEditable();
|
||||
this.isHeavy = fieldMetaData.getIsHeavy();
|
||||
this.isHidden = fieldMetaData.getIsHidden();
|
||||
this.gridColumns = fieldMetaData.getGridColumns();
|
||||
this.possibleValueSourceName = fieldMetaData.getPossibleValueSourceName();
|
||||
this.displayFormat = fieldMetaData.getDisplayFormat();
|
||||
@ -95,7 +87,6 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
this.helpContents = fieldMetaData.getHelpContents();
|
||||
this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource();
|
||||
this.maxLength = fieldMetaData.getMaxLength();
|
||||
this.possibleValueSourceFilter = fieldMetaData.getPossibleValueSourceFilter();
|
||||
|
||||
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
|
||||
{
|
||||
@ -108,19 +99,6 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
behaviors.add(fbff);
|
||||
}
|
||||
}
|
||||
|
||||
for(Map.Entry<String, QSupplementalFieldMetaData> entry : CollectionUtils.nonNullMap(fieldMetaData.getSupplementalMetaData()).entrySet())
|
||||
{
|
||||
QSupplementalFieldMetaData supplementalFieldMetaData = entry.getValue();
|
||||
if(supplementalFieldMetaData.includeInFrontendMetaData())
|
||||
{
|
||||
if(this.supplementalFieldMetaData == null)
|
||||
{
|
||||
this.supplementalFieldMetaData = new HashMap<>();
|
||||
}
|
||||
this.supplementalFieldMetaData.put(entry.getKey(), supplementalFieldMetaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -191,17 +169,6 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isHidden
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getIsHidden()
|
||||
{
|
||||
return isHidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for gridColumns
|
||||
**
|
||||
@ -287,37 +254,4 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
{
|
||||
return behaviors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for supplementalFieldMetaData
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QSupplementalFieldMetaData> getSupplementalFieldMetaData()
|
||||
{
|
||||
return supplementalFieldMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxLength
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getMaxLength()
|
||||
{
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for possibleValueSourceFilter
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QQueryFilter getPossibleValueSourceFilter()
|
||||
{
|
||||
return possibleValueSourceFilter;
|
||||
}
|
||||
}
|
||||
|
@ -85,35 +85,23 @@ public class QFrontendTableMetaData
|
||||
// do not add setters. take values from the source-object in the constructor!! //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/***************************************************************************
|
||||
** standard constructor - uses all fields on the table.
|
||||
***************************************************************************/
|
||||
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins)
|
||||
{
|
||||
this(actionInput, backendForTable, tableMetaData, includeFullMetaData, includeJoins, tableMetaData.getFields());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** alternative constructor - takes a map of fields to use (e.g., for an old
|
||||
** api version of the table w/ different fields!)
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins, Map<String, QFieldMetaData> overrideFields)
|
||||
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins)
|
||||
{
|
||||
this.name = tableMetaData.getName();
|
||||
this.label = tableMetaData.getLabel();
|
||||
this.isHidden = tableMetaData.getIsHidden();
|
||||
|
||||
Map<String, QFieldMetaData> inputFields = overrideFields == null ? tableMetaData.getFields() : overrideFields;
|
||||
|
||||
if(includeFullMetaData)
|
||||
{
|
||||
this.primaryKeyField = tableMetaData.getPrimaryKeyField();
|
||||
this.fields = new HashMap<>();
|
||||
for(String fieldName : inputFields.keySet())
|
||||
for(String fieldName : tableMetaData.getFields().keySet())
|
||||
{
|
||||
QFieldMetaData field = inputFields.get(fieldName);
|
||||
QFieldMetaData field = tableMetaData.getField(fieldName);
|
||||
if(!field.getIsHidden())
|
||||
{
|
||||
this.fields.put(fieldName, new QFrontendFieldMetaData(field));
|
||||
|
@ -26,13 +26,10 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** meta-data definition of "Help Content" to show to a user - for use in
|
||||
** meta-data defintion of "Help Content" to show to a user - for use in
|
||||
** a specific "role" (e.g., insert screens but not view screens), and in a
|
||||
** particular "format" (e.g., plain text, html, markdown).
|
||||
**
|
||||
@ -51,12 +48,6 @@ public class QHelpContent implements QMetaDataObject
|
||||
private HelpFormat format;
|
||||
private Set<HelpRole> roles;
|
||||
|
||||
////////////////////////////////////
|
||||
// these appear to be thread safe //
|
||||
////////////////////////////////////
|
||||
private static Parser commonMarkParser = Parser.builder().build();
|
||||
private static HtmlRenderer commonMarkRenderer = HtmlRenderer.builder().build();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -80,38 +71,6 @@ public class QHelpContent implements QMetaDataObject
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Return the content as html string, based on its format.
|
||||
* Only MARKDOWN actually gets processed (via commonmark) - but TEXT and
|
||||
* HTML come out as-is.
|
||||
***************************************************************************/
|
||||
public String getContentAsHtml()
|
||||
{
|
||||
if(content == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(HelpFormat.MARKDOWN.equals(this.format))
|
||||
{
|
||||
//////////////////////////////
|
||||
// convert markdown to HTML //
|
||||
//////////////////////////////
|
||||
Node document = commonMarkParser.parse(content);
|
||||
String html = commonMarkRenderer.render(document);
|
||||
return (html);
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////
|
||||
// other formats (html & text) just output as-is //
|
||||
///////////////////////////////////////////////////
|
||||
return (content);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for content
|
||||
*******************************************************************************/
|
||||
|
@ -213,7 +213,7 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
{
|
||||
if(stepList != null)
|
||||
{
|
||||
stepList.forEach(this::withStep);
|
||||
stepList.forEach(this::addStep);
|
||||
}
|
||||
|
||||
return (this);
|
||||
@ -231,7 +231,7 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
{
|
||||
index = this.stepList.size();
|
||||
}
|
||||
withStep(index, step);
|
||||
addStep(index, step);
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
@ -26,15 +26,12 @@ import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
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.producers.annotations.ChildJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitProductionContext;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -65,7 +62,6 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
private String childTableName; // e.g., lineItem
|
||||
private String parentTableName; // e.g., order
|
||||
private String foreignKeyFieldName; // e.g., orderId
|
||||
private boolean isOneToOne;
|
||||
|
||||
private ChildJoin.OrderBy[] orderBys;
|
||||
|
||||
@ -76,7 +72,7 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName, ChildJoin.OrderBy[] orderBys, boolean isOneToOne)
|
||||
public ChildJoinFromRecordEntityGenericMetaDataProducer(String childTableName, String parentTableName, String foreignKeyFieldName, ChildJoin.OrderBy[] orderBys)
|
||||
{
|
||||
Objects.requireNonNull(childTableName, "childTableName cannot be null");
|
||||
Objects.requireNonNull(parentTableName, "parentTableName cannot be null");
|
||||
@ -86,7 +82,6 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
this.parentTableName = parentTableName;
|
||||
this.foreignKeyFieldName = foreignKeyFieldName;
|
||||
this.orderBys = orderBys;
|
||||
this.isOneToOne = isOneToOne;
|
||||
}
|
||||
|
||||
|
||||
@ -97,14 +92,23 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
@Override
|
||||
public QJoinMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
QTableMetaData parentTable = getTable(qInstance, parentTableName);
|
||||
QTableMetaData childTable = getTable(qInstance, childTableName);
|
||||
QTableMetaData parentTable = qInstance.getTable(parentTableName);
|
||||
if(parentTable == null)
|
||||
{
|
||||
throw (new QException("Could not find tableMetaData " + parentTableName));
|
||||
}
|
||||
|
||||
QTableMetaData childTable = qInstance.getTable(childTableName);
|
||||
if(childTable == null)
|
||||
{
|
||||
throw (new QException("Could not find tableMetaData " + childTable));
|
||||
}
|
||||
|
||||
QJoinMetaData join = new QJoinMetaData()
|
||||
.withLeftTable(parentTableName)
|
||||
.withRightTable(childTableName)
|
||||
.withInferredName()
|
||||
.withType(isOneToOne ? JoinType.ONE_TO_ONE : JoinType.ONE_TO_MANY)
|
||||
.withType(JoinType.ONE_TO_MANY)
|
||||
.withJoinOn(new JoinOn(parentTable.getPrimaryKeyField(), foreignKeyFieldName));
|
||||
|
||||
if(orderBys != null && orderBys.length > 0)
|
||||
@ -127,41 +131,6 @@ public class ChildJoinFromRecordEntityGenericMetaDataProducer implements MetaDat
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
private QTableMetaData getTable(QInstance qInstance, String tableName) throws QException
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
if(table == null)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// in case we're producing a QBit, and it's added a table to a multi-output, //
|
||||
// but not yet the instance, see if we can get table from there //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
for(MetaDataProducerMultiOutput metaDataProducerMultiOutput : QBitProductionContext.getReadOnlyViewOfMetaDataProducerMultiOutputStack())
|
||||
{
|
||||
table = CollectionUtils.nonNullList(metaDataProducerMultiOutput.getEach(QTableMetaData.class)).stream()
|
||||
.filter(t -> t.getName().equals(tableName))
|
||||
.findFirst().orElse(null);
|
||||
|
||||
if(table != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("Could not find tableMetaData: " + table));
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sourceClass
|
||||
**
|
||||
|
@ -25,13 +25,10 @@ package com.kingsrook.qqq.backend.core.model.metadata.producers;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildRecordListWidget;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitProductionContext;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
@ -92,26 +89,6 @@ public class ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer implem
|
||||
String name = QJoinMetaData.makeInferredJoinName(parentTableName, childTableName);
|
||||
QJoinMetaData join = qInstance.getJoin(name);
|
||||
|
||||
if(join == null)
|
||||
{
|
||||
for(MetaDataProducerMultiOutput metaDataProducerMultiOutput : QBitProductionContext.getReadOnlyViewOfMetaDataProducerMultiOutputStack())
|
||||
{
|
||||
join = CollectionUtils.nonNullList(metaDataProducerMultiOutput.getEach(QJoinMetaData.class)).stream()
|
||||
.filter(t -> t.getName().equals(name))
|
||||
.findFirst().orElse(null);
|
||||
|
||||
if(join != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(join == null)
|
||||
{
|
||||
throw (new QException("Could not find joinMetaData: " + name));
|
||||
}
|
||||
|
||||
QWidgetMetaData widget = ChildRecordListRenderer.widgetMetaDataBuilder(join)
|
||||
.withName(name)
|
||||
.withLabel(childRecordListWidget.label())
|
||||
|
@ -38,8 +38,6 @@ public @interface ChildJoin
|
||||
|
||||
OrderBy[] orderBy() default { };
|
||||
|
||||
boolean isOneToOne() default false;
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
|
||||
|
||||
|
||||
@ -32,7 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
|
||||
** Specifically exists to accept the QBitConfig as a type parameter and a value,
|
||||
** easily accessed in the producer's methods as getQBitConfig()
|
||||
*******************************************************************************/
|
||||
public abstract class QBitComponentMetaDataProducer<T extends MetaDataProducerOutput, C extends QBitConfig> implements QBitComponentMetaDataProducerInterface<T, C>
|
||||
public abstract class QBitComponentMetaDataProducer<T extends MetaDataProducerOutput, C extends QBitConfig> implements MetaDataProducerInterface<T>
|
||||
{
|
||||
private C qBitConfig = null;
|
||||
|
||||
@ -41,7 +42,6 @@ public abstract class QBitComponentMetaDataProducer<T extends MetaDataProducerOu
|
||||
/*******************************************************************************
|
||||
** Getter for qBitConfig
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public C getQBitConfig()
|
||||
{
|
||||
return (this.qBitConfig);
|
||||
@ -52,7 +52,6 @@ public abstract class QBitComponentMetaDataProducer<T extends MetaDataProducerOu
|
||||
/*******************************************************************************
|
||||
** Setter for qBitConfig
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setQBitConfig(C qBitConfig)
|
||||
{
|
||||
this.qBitConfig = qBitConfig;
|
||||
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** extension of MetaDataProducerInterface, designed for producing meta data
|
||||
** within a (java-defined, at this time) QBit.
|
||||
**
|
||||
** Specifically exists to accept the QBitConfig as a type parameter and a value,
|
||||
** easily accessed in the producer's methods as getQBitConfig()
|
||||
*******************************************************************************/
|
||||
public interface QBitComponentMetaDataProducerInterface<T extends MetaDataProducerOutput, C extends QBitConfig> extends MetaDataProducerInterface<T>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for qBitConfig
|
||||
*******************************************************************************/
|
||||
C getQBitConfig();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for qBitConfig
|
||||
*******************************************************************************/
|
||||
void setQBitConfig(C qBitConfig);
|
||||
|
||||
}
|
@ -107,14 +107,4 @@ public interface QBitConfig extends Serializable
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
default String getDefaultBackendNameForTables()
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,192 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** 2nd generation interface for top-level meta-data production classes that make
|
||||
** a qbit (evolution over QBitProducer).
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface QBitMetaDataProducer<C extends QBitConfig> extends MetaDataProducerInterface<MetaDataProducerMultiOutput>
|
||||
{
|
||||
QLogger LOG = QLogger.getLogger(QBitMetaDataProducer.class);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
C getQBitConfig();
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
QBitMetaData getQBitMetaData();
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default String getNamespace()
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void postProduceActions(MetaDataProducerMultiOutput metaDataProducerMultiOutput, QInstance qinstance) throws QException
|
||||
{
|
||||
/////////////////////
|
||||
// noop by default //
|
||||
/////////////////////
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default String getPackageNameForFindingMetaDataProducers()
|
||||
{
|
||||
Class<?> clazz = getClass();
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Walk up the hierarchy until we find the direct implementer //
|
||||
////////////////////////////////////////////////////////////////
|
||||
while(clazz != null)
|
||||
{
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
for(Class<?> interfaze : interfaces)
|
||||
{
|
||||
if(interfaze == QBitMetaDataProducer.class)
|
||||
{
|
||||
return clazz.getPackageName();
|
||||
}
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
|
||||
throw (new QRuntimeException("Unable to find packageName for QBitMetaDataProducer. You may need to implement getPackageName yourself..."));
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
default MetaDataProducerMultiOutput produce(QInstance qInstance) throws QException
|
||||
{
|
||||
MetaDataProducerMultiOutput rs = new MetaDataProducerMultiOutput();
|
||||
|
||||
QBitMetaData qBitMetaData = getQBitMetaData();
|
||||
C qBitConfig = getQBitConfig();
|
||||
|
||||
qInstance.addQBit(qBitMetaData);
|
||||
|
||||
QBitProductionContext.pushQBitConfig(qBitConfig);
|
||||
QBitProductionContext.pushMetaDataProducerMultiOutput(rs);
|
||||
|
||||
try
|
||||
{
|
||||
qBitConfig.validate(qInstance);
|
||||
|
||||
List<MetaDataProducerInterface<?>> producers = MetaDataProducerHelper.findProducers(getPackageNameForFindingMetaDataProducers());
|
||||
MetaDataProducerHelper.sortMetaDataProducers(producers);
|
||||
for(MetaDataProducerInterface<?> producer : producers)
|
||||
{
|
||||
if(producer.getClass().equals(this.getClass()))
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// avoid recursive processing of ourselves //
|
||||
/////////////////////////////////////////////
|
||||
continue;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// todo is this deprecated in favor of QBitProductionContext's stack... ? //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
if(producer instanceof QBitComponentMetaDataProducerInterface<?, ?>)
|
||||
{
|
||||
QBitComponentMetaDataProducerInterface<?, C> qBitComponentMetaDataProducer = (QBitComponentMetaDataProducerInterface<?, C>) producer;
|
||||
qBitComponentMetaDataProducer.setQBitConfig(qBitConfig);
|
||||
}
|
||||
|
||||
if(!producer.isEnabled())
|
||||
{
|
||||
LOG.debug("Not using producer which is not enabled", logPair("producer", producer.getClass().getSimpleName()));
|
||||
continue;
|
||||
}
|
||||
|
||||
MetaDataProducerOutput subProducerOutput = producer.produce(qInstance);
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// apply some things from the config to tables //
|
||||
/////////////////////////////////////////////////
|
||||
if(subProducerOutput instanceof QTableMetaData table)
|
||||
{
|
||||
if(qBitConfig.getTableMetaDataCustomizer() != null)
|
||||
{
|
||||
subProducerOutput = qBitConfig.getTableMetaDataCustomizer().customizeMetaData(qInstance, table);
|
||||
}
|
||||
|
||||
if(!StringUtils.hasContent(table.getBackendName()) && StringUtils.hasContent(qBitConfig.getDefaultBackendNameForTables()))
|
||||
{
|
||||
table.setBackendName(qBitConfig.getDefaultBackendNameForTables());
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// set source qbit, if subProducerOutput is aware of such //
|
||||
////////////////////////////////////////////////////////////
|
||||
if(subProducerOutput instanceof SourceQBitAware sourceQBitAware)
|
||||
{
|
||||
sourceQBitAware.setSourceQBitName(qBitMetaData.getName());
|
||||
}
|
||||
|
||||
rs.add(subProducerOutput);
|
||||
}
|
||||
|
||||
postProduceActions(rs, qInstance);
|
||||
|
||||
return (rs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
QBitProductionContext.popQBitConfig();
|
||||
QBitProductionContext.popMetaDataProducerMultiOutput();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -76,11 +76,14 @@ public interface QBitProducer
|
||||
{
|
||||
qBitConfig.validate(qInstance);
|
||||
|
||||
///////////////////////////////
|
||||
// todo - move to base class //
|
||||
///////////////////////////////
|
||||
for(MetaDataProducerInterface<?> producer : producers)
|
||||
{
|
||||
if(producer instanceof QBitComponentMetaDataProducerInterface<?,?>)
|
||||
if(producer instanceof QBitComponentMetaDataProducer<?, ?>)
|
||||
{
|
||||
QBitComponentMetaDataProducerInterface<?,C> qBitComponentMetaDataProducer = (QBitComponentMetaDataProducerInterface<?,C>) producer;
|
||||
QBitComponentMetaDataProducer<?, C> qBitComponentMetaDataProducer = (QBitComponentMetaDataProducer<?, C>) producer;
|
||||
qBitComponentMetaDataProducer.setQBitConfig(qBitConfig);
|
||||
}
|
||||
|
||||
|
@ -1,136 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.qbits;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** While a qbit is being produced, track the context of the current config
|
||||
** and metaDataProducerMultiOutput that is being used. also, in case one
|
||||
** qbit produces another, push these contextual objects on a stack.
|
||||
*******************************************************************************/
|
||||
public class QBitProductionContext
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QBitProductionContext.class);
|
||||
|
||||
private static Stack<QBitConfig> qbitConfigStack = new Stack<>();
|
||||
private static Stack<MetaDataProducerMultiOutput> metaDataProducerMultiOutputStack = new Stack<>();
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void pushQBitConfig(QBitConfig qBitConfig)
|
||||
{
|
||||
qbitConfigStack.push(qBitConfig);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static QBitConfig peekQBitConfig()
|
||||
{
|
||||
if(qbitConfigStack.isEmpty())
|
||||
{
|
||||
LOG.warn("Request to peek at empty QBitProductionContext configStack - returning null");
|
||||
return (null);
|
||||
}
|
||||
return qbitConfigStack.peek();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void popQBitConfig()
|
||||
{
|
||||
if(qbitConfigStack.isEmpty())
|
||||
{
|
||||
LOG.warn("Request to pop empty QBitProductionContext configStack - returning with noop");
|
||||
return;
|
||||
}
|
||||
|
||||
qbitConfigStack.pop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void pushMetaDataProducerMultiOutput(MetaDataProducerMultiOutput metaDataProducerMultiOutput)
|
||||
{
|
||||
metaDataProducerMultiOutputStack.push(metaDataProducerMultiOutput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static MetaDataProducerMultiOutput peekMetaDataProducerMultiOutput()
|
||||
{
|
||||
if(metaDataProducerMultiOutputStack.isEmpty())
|
||||
{
|
||||
LOG.warn("Request to peek at empty QBitProductionContext configStack - returning null");
|
||||
return (null);
|
||||
}
|
||||
return metaDataProducerMultiOutputStack.peek();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static List<MetaDataProducerMultiOutput> getReadOnlyViewOfMetaDataProducerMultiOutputStack()
|
||||
{
|
||||
return Collections.unmodifiableList(metaDataProducerMultiOutputStack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void popMetaDataProducerMultiOutput()
|
||||
{
|
||||
if(metaDataProducerMultiOutputStack.isEmpty())
|
||||
{
|
||||
LOG.warn("Request to pop empty QBitProductionContext metaDataProducerMultiOutput - returning with noop");
|
||||
return;
|
||||
}
|
||||
|
||||
metaDataProducerMultiOutputStack.pop();
|
||||
}
|
||||
|
||||
}
|
@ -44,7 +44,7 @@ public class MultiRecordSecurityLock extends RecordSecurityLock implements Clone
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public MultiRecordSecurityLock clone()
|
||||
protected MultiRecordSecurityLock clone() throws CloneNotSupportedException
|
||||
{
|
||||
MultiRecordSecurityLock clone = (MultiRecordSecurityLock) super.clone();
|
||||
|
||||
|
@ -57,27 +57,20 @@ public class RecordSecurityLock implements Cloneable
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public RecordSecurityLock clone()
|
||||
protected RecordSecurityLock clone() throws CloneNotSupportedException
|
||||
{
|
||||
try
|
||||
{
|
||||
RecordSecurityLock clone = (RecordSecurityLock) super.clone();
|
||||
RecordSecurityLock clone = (RecordSecurityLock) super.clone();
|
||||
|
||||
/////////////////////////
|
||||
// deep-clone the list //
|
||||
/////////////////////////
|
||||
if(joinNameChain != null)
|
||||
{
|
||||
clone.joinNameChain = new ArrayList<>();
|
||||
clone.joinNameChain.addAll(joinNameChain);
|
||||
}
|
||||
|
||||
return (clone);
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
/////////////////////////
|
||||
// deep-clone the list //
|
||||
/////////////////////////
|
||||
if(joinNameChain != null)
|
||||
{
|
||||
throw (new RuntimeException("Could not clone", e));
|
||||
clone.joinNameChain = new ArrayList<>();
|
||||
clone.joinNameChain.addAll(joinNameChain);
|
||||
}
|
||||
|
||||
return (clone);
|
||||
}
|
||||
|
||||
|
||||
|
@ -85,7 +85,7 @@ public class TablesCustomPossibleValueProvider extends BasicCustomPossibleValueP
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected boolean isTableAllowed(QTableMetaData table)
|
||||
private boolean isTableAllowed(QTableMetaData table)
|
||||
{
|
||||
if(table == null)
|
||||
{
|
||||
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesCustomPossibleValueProvider;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** subset of the tables PVS custom provider, to only include tables support automations
|
||||
*******************************************************************************/
|
||||
public class TablesSupportingAutomationsCustomPossibleValueProvider extends TablesCustomPossibleValueProvider
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected boolean isTableAllowed(QTableMetaData table)
|
||||
{
|
||||
if(table.getAutomationDetails() == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return super.isTableAllowed(table);
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.qqq.backend.core.model.metadata.tables.automation;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** subset of the tables PVS, to only include tables support automations
|
||||
*******************************************************************************/
|
||||
public class TablesSupportingAutomationsPossibleValueSourceMetaDataProvider
|
||||
{
|
||||
public static final String NAME = "tablesSupportingAutomations";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QPossibleValueSource defineTablesPossibleValueSource(QInstance qInstance)
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = new QPossibleValueSource()
|
||||
.withName(NAME)
|
||||
.withIdType(QFieldType.STRING)
|
||||
.withType(QPossibleValueSourceType.CUSTOM)
|
||||
.withCustomCodeReference(new QCodeReference(TablesSupportingAutomationsCustomPossibleValueProvider.class))
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY);
|
||||
|
||||
return (possibleValueSource);
|
||||
}
|
||||
|
||||
}
|
@ -28,7 +28,6 @@ import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -52,7 +51,7 @@ public class BackendVariantsUtil
|
||||
String variantTypeKey = backendMetaData.getBackendVariantsConfig().getVariantTypeKey();
|
||||
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(variantTypeKey))
|
||||
{
|
||||
throw (new QUserFacingException("Could not find Backend Variant information in session under key '" + variantTypeKey + "' for Backend '" + backendMetaData.getName() + "'"));
|
||||
throw (new QException("Could not find Backend Variant information in session under key '" + variantTypeKey + "' for Backend '" + backendMetaData.getName() + "'"));
|
||||
}
|
||||
Serializable variantId = session.getBackendVariants().get(variantTypeKey);
|
||||
return variantId;
|
||||
@ -84,9 +83,6 @@ public class BackendVariantsUtil
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// note, we'll consider this a programmer-error, not a user-facing one (e.g., bad submitted data), so not throw user-facing //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new QException("Backend Variant's recordLookupFunction is not of any expected type (should have been caught by instance validation??)"));
|
||||
}
|
||||
}
|
||||
@ -103,7 +99,7 @@ public class BackendVariantsUtil
|
||||
|
||||
if(record == null)
|
||||
{
|
||||
throw (new QUserFacingException("Could not find Backend Variant in table " + backendMetaData.getBackendVariantsConfig().getOptionsTableName() + " with id '" + variantId + "'"));
|
||||
throw (new QException("Could not find Backend Variant in table " + backendMetaData.getBackendVariantsConfig().getOptionsTableName() + " with id '" + variantId + "'"));
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
@ -60,6 +60,9 @@ public class SavedBulkLoadProfile extends QRecordEntity
|
||||
@QField(label = "Mapping JSON")
|
||||
private String mappingJson;
|
||||
|
||||
@QField()
|
||||
private Boolean isBulkEdit;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -251,7 +254,6 @@ public class SavedBulkLoadProfile extends QRecordEntity
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for mappingJson
|
||||
*******************************************************************************/
|
||||
@ -282,4 +284,34 @@ public class SavedBulkLoadProfile extends QRecordEntity
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isBulkEdit
|
||||
*******************************************************************************/
|
||||
public Boolean getIsBulkEdit()
|
||||
{
|
||||
return (this.isBulkEdit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isBulkEdit
|
||||
*******************************************************************************/
|
||||
public void setIsBulkEdit(Boolean isBulkEdit)
|
||||
{
|
||||
this.isBulkEdit = isBulkEdit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isBulkEdit
|
||||
*******************************************************************************/
|
||||
public SavedBulkLoadProfile withIsBulkEdit(Boolean isBulkEdit)
|
||||
{
|
||||
this.isBulkEdit = isBulkEdit;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ public class SavedBulkLoadProfileMetaDataProvider
|
||||
.withFieldsFromEntity(SavedBulkLoadProfile.class)
|
||||
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD))
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "tableName")))
|
||||
.withSection(new QFieldSection("details", new QIcon().withName("text_snippet"), Tier.T2, List.of("userId", "mappingJson")))
|
||||
.withSection(new QFieldSection("details", new QIcon().withName("text_snippet"), Tier.T2, List.of("userId", "mappingJson", "isBulkEdit")))
|
||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||
|
||||
table.getField("mappingJson").withBehavior(SavedBulkLoadProfileJsonFieldDisplayValueFormatter.getInstance());
|
||||
|
@ -62,7 +62,6 @@ 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.TablesPossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TablesSupportingAutomationsPossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.scripts.LoadScriptTestDetailsProcessStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.scripts.RunRecordScriptExtractStep;
|
||||
@ -98,7 +97,6 @@ public class ScriptsMetaDataProvider
|
||||
defineStandardScriptsJoins(instance);
|
||||
defineStandardScriptsWidgets(instance);
|
||||
instance.addPossibleValueSource(TablesPossibleValueSourceMetaDataProvider.defineTablesPossibleValueSource(instance));
|
||||
instance.addPossibleValueSource(TablesSupportingAutomationsPossibleValueSourceMetaDataProvider.defineTablesPossibleValueSource(instance));
|
||||
instance.addProcess(defineStoreScriptRevisionProcess());
|
||||
instance.addProcess(defineTestScriptProcess());
|
||||
instance.addProcess(defineLoadScriptTestDetailsProcess());
|
||||
@ -176,7 +174,7 @@ public class ScriptsMetaDataProvider
|
||||
.withLoadStepClass(RunRecordScriptLoadStep.class)
|
||||
.getProcessMetaData();
|
||||
|
||||
processMetaData.withStep(0, new QFrontendStepMetaData()
|
||||
processMetaData.addStep(0, new QFrontendStepMetaData()
|
||||
.withName("input")
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))
|
||||
.withFormField(new QFieldMetaData("scriptId", QFieldType.INTEGER).withPossibleValueSourceName(Script.TABLE_NAME)
|
||||
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
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<QRecord, Integer>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected QPossibleValue<Integer> 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<QRecord> getAllSourceObjects() throws QException
|
||||
{
|
||||
List<QRecord> records = QueryAction.execute(QQQTable.TABLE_NAME, null);
|
||||
ArrayList<QRecord> rs = new ArrayList<>();
|
||||
for(QRecord record : records)
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(record.getValueString("name"));
|
||||
if(isTableAllowed(table))
|
||||
{
|
||||
rs.add(record);
|
||||
}
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -22,29 +22,18 @@
|
||||
package com.kingsrook.qqq.backend.core.model.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.utils.GeneralProcessUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
@ -100,41 +89,4 @@ public class QQQTableTableManager
|
||||
|
||||
return getOutput.getRecord().getValueInteger("id");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static List<QRecord> setRecordLinksToRecordsFromTableDynamicForPostQuery(QueryOrGetInputInterface queryInput, List<QRecord> records, String tableIdField, String recordIdField) throws QException
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// note, this is a second copy of this logic (first being in standard process traces). //
|
||||
// let the rule of 3 apply if we find ourselves copying it again //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(queryInput.getShouldGenerateDisplayValues())
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// for records with a table id value - look up that table name, then set a display-value //
|
||||
// for the Link type adornment, to the inputRecordId record within that table. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
Set<Serializable> tableIds = records.stream().map(r -> r.getValue(tableIdField)).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
if(!tableIds.isEmpty())
|
||||
{
|
||||
Map<Serializable, QRecord> tableMap = GeneralProcessUtils.loadTableToMap(QQQTable.TABLE_NAME, "id", new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, tableIds)));
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
QRecord qqqTableRecord = tableMap.get(record.getValue(tableIdField));
|
||||
if(qqqTableRecord != null && record.getValue(recordIdField) != null)
|
||||
{
|
||||
record.setDisplayValue(recordIdField + ":" + AdornmentType.LinkValues.TO_RECORD_FROM_TABLE_DYNAMIC, qqqTableRecord.getValueString("name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,9 +27,6 @@ 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;
|
||||
@ -128,11 +125,10 @@ public class QQQTablesMetaDataProvider
|
||||
public QPossibleValueSource defineQQQTablePossibleValueSource()
|
||||
{
|
||||
return (new QPossibleValueSource()
|
||||
.withType(QPossibleValueSourceType.TABLE)
|
||||
.withName(QQQTable.TABLE_NAME)
|
||||
.withType(QPossibleValueSourceType.CUSTOM)
|
||||
.withIdType(QFieldType.INTEGER)
|
||||
.withCustomCodeReference(new QCodeReference(QQQTableCustomPossibleValueProvider.class))
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY));
|
||||
.withTableName(QQQTable.TABLE_NAME))
|
||||
.withOrderByField("label");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -42,7 +43,14 @@ public class MemoryCountAction implements CountInterface
|
||||
{
|
||||
try
|
||||
{
|
||||
CountOutput countOutput = MemoryRecordStore.getInstance().count(countInput);
|
||||
if(CollectionUtils.nullSafeHasContents(countInput.getQueryJoins()))
|
||||
{
|
||||
throw (new UnsupportedOperationException("Performing counts on tables with exposed joins is currently not supported by the Memory Backend."));
|
||||
}
|
||||
|
||||
CountOutput countOutput = new CountOutput();
|
||||
countOutput.setCount(MemoryRecordStore.getInstance().count(countInput));
|
||||
countOutput.setDistinctCount(countOutput.getCount());
|
||||
return (countOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantSetting;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** since some settings are required for a variant, if you're using memory backend
|
||||
** with variants, this is a setting you can use.
|
||||
*******************************************************************************/
|
||||
public enum MemoryModuleBackendVariantSetting implements BackendVariantSetting
|
||||
{
|
||||
PRIMARY_KEY
|
||||
}
|
@ -33,12 +33,10 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.DateTimeGroupBy;
|
||||
@ -56,7 +54,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.GroupBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByAggregate;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByGroupBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||
@ -66,22 +63,17 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
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.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsUtil;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -93,11 +85,8 @@ public class MemoryRecordStore
|
||||
|
||||
private static MemoryRecordStore instance;
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// these maps are: BackendIdentifier > tableName > data //
|
||||
//////////////////////////////////////////////////////////
|
||||
private Map<BackendIdentifier, Map<String, Map<Serializable, QRecord>>> data;
|
||||
private Map<BackendIdentifier, Map<String, Integer>> nextSerials;
|
||||
private Map<String, Map<Serializable, QRecord>> data;
|
||||
private Map<String, Integer> nextSerials;
|
||||
|
||||
private static boolean collectStatistics = false;
|
||||
|
||||
@ -161,31 +150,13 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Map<Serializable, QRecord> getTableData(QTableMetaData table) throws QException
|
||||
private Map<Serializable, QRecord> getTableData(QTableMetaData table)
|
||||
{
|
||||
BackendIdentifier backendIdentifier = getBackendIdentifier(table);
|
||||
Map<String, Map<Serializable, QRecord>> dataForBackend = data.computeIfAbsent(backendIdentifier, k -> new HashMap<>());
|
||||
return (dataForBackend.computeIfAbsent(table.getName(), k -> new HashMap<>()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private BackendIdentifier getBackendIdentifier(QTableMetaData table) throws QException
|
||||
{
|
||||
BackendIdentifier backendIdentifier = NonVariant.getInstance();
|
||||
QBackendMetaData backendMetaData = QContext.getQInstance().getBackend(table.getBackendName());
|
||||
BackendVariantsConfig backendVariantsConfig = backendMetaData.getBackendVariantsConfig();
|
||||
if(backendVariantsConfig != null)
|
||||
if(!data.containsKey(table.getName()))
|
||||
{
|
||||
String variantType = backendMetaData.getBackendVariantsConfig().getVariantTypeKey();
|
||||
QRecord variantRecord = BackendVariantsUtil.getVariantRecord(backendMetaData);
|
||||
Serializable variantId = variantRecord.getValue(QContext.getQInstance().getTable(variantRecord.getTableName()).getPrimaryKeyField());
|
||||
backendIdentifier = new Variant(variantType, variantId);
|
||||
data.put(table.getName(), new HashMap<>());
|
||||
}
|
||||
return backendIdentifier;
|
||||
return (data.get(table.getName()));
|
||||
}
|
||||
|
||||
|
||||
@ -340,55 +311,17 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountOutput count(CountInput input) throws QException
|
||||
public Integer count(CountInput input) throws QException
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set up a query input - we'll implement count by counting the records in a query output //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(input.getTableName());
|
||||
|
||||
if(input.getFilter() != null)
|
||||
{
|
||||
queryInput.setFilter(input.getFilter().clone().withSkip(null).withLimit(null));
|
||||
}
|
||||
|
||||
if(input.getQueryJoins() != null)
|
||||
{
|
||||
queryInput.setQueryJoins(new ArrayList<>());
|
||||
for(QueryJoin queryJoin : input.getQueryJoins())
|
||||
{
|
||||
queryInput.getQueryJoins().add(queryJoin.clone());
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// run the query //
|
||||
///////////////////
|
||||
List<QRecord> queryResult = query(queryInput);
|
||||
|
||||
////////////////////////
|
||||
// build count output //
|
||||
////////////////////////
|
||||
CountOutput countOutput = new CountOutput();
|
||||
countOutput.setCount(queryResult.size());
|
||||
|
||||
//////////////////////////////////////
|
||||
// figure out distinct if requested //
|
||||
//////////////////////////////////////
|
||||
if(BooleanUtils.isTrue(input.getIncludeDistinctCount()))
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(input.getTableName());
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
Set<Serializable> distinctValues = new HashSet<>();
|
||||
for(QRecord record : queryResult)
|
||||
{
|
||||
distinctValues.add(record.getValue(primaryKeyField));
|
||||
}
|
||||
countOutput.setDistinctCount(distinctValues.size());
|
||||
}
|
||||
|
||||
return (countOutput);
|
||||
return (queryResult.size());
|
||||
}
|
||||
|
||||
|
||||
@ -396,7 +329,7 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> insert(InsertInput input, boolean returnInsertedRecords) throws QException
|
||||
public List<QRecord> insert(InsertInput input, boolean returnInsertedRecords)
|
||||
{
|
||||
incrementStatistic(input);
|
||||
|
||||
@ -411,7 +344,7 @@ public class MemoryRecordStore
|
||||
////////////////////////////////////////
|
||||
// grab the next unique serial to use //
|
||||
////////////////////////////////////////
|
||||
Integer nextSerial = getNextSerial(table);
|
||||
Integer nextSerial = nextSerials.get(table.getName());
|
||||
if(nextSerial == null)
|
||||
{
|
||||
nextSerial = 1;
|
||||
@ -431,9 +364,6 @@ public class MemoryRecordStore
|
||||
// differently from other backends, because of having the same record variable in the backend store and in the user-code. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QRecord recordToInsert = new QRecord(record);
|
||||
|
||||
makeValueTypesMatchFieldTypes(table, recordToInsert);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(recordToInsert.getErrors()))
|
||||
{
|
||||
outputRecords.add(recordToInsert);
|
||||
@ -477,65 +407,17 @@ public class MemoryRecordStore
|
||||
}
|
||||
}
|
||||
|
||||
setNextSerial(table, nextSerial);
|
||||
nextSerials.put(table.getName(), nextSerial);
|
||||
|
||||
return (outputRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void setNextSerial(QTableMetaData table, Integer nextSerial) throws QException
|
||||
{
|
||||
BackendIdentifier backendIdentifier = getBackendIdentifier(table);
|
||||
Map<String, Integer> nextSerialsForBackend = nextSerials.computeIfAbsent(backendIdentifier, (k) -> new HashMap<>());
|
||||
nextSerialsForBackend.put(table.getName(), nextSerial);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private Integer getNextSerial(QTableMetaData table) throws QException
|
||||
{
|
||||
BackendIdentifier backendIdentifier = getBackendIdentifier(table);
|
||||
Map<String, Integer> nextSerialsForBackend = nextSerials.computeIfAbsent(backendIdentifier, (k) -> new HashMap<>());
|
||||
return (nextSerialsForBackend.get(table.getName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void makeValueTypesMatchFieldTypes(QTableMetaData table, QRecord recordToInsert)
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
Serializable value = recordToInsert.getValue(field.getName());
|
||||
if(value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
recordToInsert.setValue(field.getName(), ValueUtils.getValueAsFieldType(field.getType(), value));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error converting value to field's type", e, logPair("fieldName", field.getName()), logPair("value", value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> update(UpdateInput input, boolean returnUpdatedRecords) throws QException
|
||||
public List<QRecord> update(UpdateInput input, boolean returnUpdatedRecords)
|
||||
{
|
||||
if(input.getRecords() == null)
|
||||
{
|
||||
@ -562,19 +444,7 @@ public class MemoryRecordStore
|
||||
QRecord recordToUpdate = tableData.get(primaryKeyValue);
|
||||
for(Map.Entry<String, Serializable> valueEntry : record.getValues().entrySet())
|
||||
{
|
||||
String fieldName = valueEntry.getKey();
|
||||
try
|
||||
{
|
||||
///////////////////////////////////////////////
|
||||
// try to make field values match field type //
|
||||
///////////////////////////////////////////////
|
||||
recordToUpdate.setValue(fieldName, ValueUtils.getValueAsFieldType(table.getField(fieldName).getType(), valueEntry.getValue()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error converting value to field's type", e, logPair("fieldName", fieldName), logPair("value", valueEntry.getValue()));
|
||||
recordToUpdate.setValue(fieldName, valueEntry.getValue());
|
||||
}
|
||||
recordToUpdate.setValue(valueEntry.getKey(), valueEntry.getValue());
|
||||
}
|
||||
|
||||
if(returnUpdatedRecords)
|
||||
@ -592,7 +462,7 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public int delete(DeleteInput input) throws QException
|
||||
public int delete(DeleteInput input)
|
||||
{
|
||||
if(input.getPrimaryKeys() == null)
|
||||
{
|
||||
@ -1057,58 +927,4 @@ public class MemoryRecordStore
|
||||
return (filter.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** key for the internal maps of this class - either for a non-variant version
|
||||
** of the memory backend, or for one based on variants.
|
||||
***************************************************************************/
|
||||
private sealed interface BackendIdentifier permits NonVariant, Variant
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** singleton, representing non-variant instance of memory backend.
|
||||
***************************************************************************/
|
||||
private static final class NonVariant implements BackendIdentifier
|
||||
{
|
||||
private static NonVariant nonVariant = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton constructor
|
||||
*******************************************************************************/
|
||||
private NonVariant()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Singleton accessor
|
||||
*******************************************************************************/
|
||||
public static NonVariant getInstance()
|
||||
{
|
||||
if(nonVariant == null)
|
||||
{
|
||||
nonVariant = new NonVariant();
|
||||
}
|
||||
return (nonVariant);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** record representing a variant type & id
|
||||
***************************************************************************/
|
||||
private record Variant(String type, Serializable id) implements BackendIdentifier
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,14 +27,11 @@ import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.CriteriaOption;
|
||||
@ -44,12 +41,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
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.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -203,7 +197,7 @@ public class BackendQueryFilterUtils
|
||||
{
|
||||
String fieldName = field == null ? "__unknownField" : field.getName();
|
||||
|
||||
ListIterator<Serializable> valueListIterator = CollectionUtils.nonNullList(criterion.getValues()).listIterator();
|
||||
ListIterator<Serializable> valueListIterator = criterion.getValues().listIterator();
|
||||
while(valueListIterator.hasNext())
|
||||
{
|
||||
Serializable criteriaValue = valueListIterator.next();
|
||||
@ -727,50 +721,4 @@ public class BackendQueryFilterUtils
|
||||
return regex.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public static Set<String> identifyJoinTablesInFilter(String mainTableName, QQueryFilter filter) throws QException
|
||||
{
|
||||
Set<String> rs = new HashSet<>();
|
||||
|
||||
QTableMetaData mainTable = QContext.getQInstance().getTable(mainTableName);
|
||||
|
||||
for(QFilterCriteria criteria : CollectionUtils.nonNullList(filter.getCriteria()))
|
||||
{
|
||||
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(mainTable, criteria.getFieldName());
|
||||
if(!fieldAndJoinTable.joinTable().getName().equals(mainTableName))
|
||||
{
|
||||
rs.add(fieldAndJoinTable.joinTable().getName());
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(criteria.getOtherFieldName()))
|
||||
{
|
||||
FieldAndJoinTable otherFieldAndJoinTable = FieldAndJoinTable.get(mainTable, criteria.getOtherFieldName());
|
||||
if(!otherFieldAndJoinTable.joinTable().getName().equals(mainTableName))
|
||||
{
|
||||
rs.add(otherFieldAndJoinTable.joinTable().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(QFilterOrderBy orderBy : CollectionUtils.nonNullList(filter.getOrderBys()))
|
||||
{
|
||||
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(mainTable, orderBy.getFieldName());
|
||||
if(!fieldAndJoinTable.joinTable().getName().equals(mainTableName))
|
||||
{
|
||||
rs.add(fieldAndJoinTable.joinTable().getName());
|
||||
}
|
||||
}
|
||||
|
||||
for(QQueryFilter subFilter : CollectionUtils.nonNullList(filter.getSubFilters()))
|
||||
{
|
||||
rs.addAll(identifyJoinTablesInFilter(mainTableName, subFilter));
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -36,10 +36,12 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaUpdateStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ProcessSummaryProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.general.ProcessSummaryWarningsAndErrorsRollup;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import static com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep.buildInfoSummaryLines;
|
||||
|
||||
|
||||
@ -53,6 +55,9 @@ public class BulkEditLoadStep extends LoadViaUpdateStep implements ProcessSummar
|
||||
private ProcessSummaryLine okSummary = new ProcessSummaryLine(Status.OK);
|
||||
private List<ProcessSummaryLine> infoSummaries = new ArrayList<>();
|
||||
|
||||
private Serializable firstInsertedPrimaryKey = null;
|
||||
private Serializable lastInsertedPrimaryKey = null;
|
||||
|
||||
private ProcessSummaryWarningsAndErrorsRollup processSummaryWarningsAndErrorsRollup = ProcessSummaryWarningsAndErrorsRollup.build("edited");
|
||||
|
||||
private String tableLabel;
|
||||
@ -106,7 +111,15 @@ public class BulkEditLoadStep extends LoadViaUpdateStep implements ProcessSummar
|
||||
tableLabel = table.getLabel();
|
||||
}
|
||||
|
||||
buildInfoSummaryLines(runBackendStepInput, table, infoSummaries, true);
|
||||
boolean isBulkEdit = BooleanUtils.isTrue(runBackendStepInput.getValueBoolean("isBulkEdit"));
|
||||
if(isBulkEdit)
|
||||
{
|
||||
buildBulkUpdateWithFileInfoSummaryLines(runBackendStepOutput, table);
|
||||
}
|
||||
else
|
||||
{
|
||||
buildInfoSummaryLines(runBackendStepInput, table, infoSummaries, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -146,4 +159,83 @@ public class BulkEditLoadStep extends LoadViaUpdateStep implements ProcessSummar
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void buildBulkUpdateWithFileInfoSummaryLines(RunBackendStepOutput runBackendStepOutput, QTableMetaData table)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// the transform step builds summary lines that it predicts will update successfully. //
|
||||
// but those lines don't have ids, which we'd like to have (e.g., for a process trace that //
|
||||
// might link to the built record). also, it's possible that there was a fail that only //
|
||||
// happened in the actual update, so, basically, re-do the summary here //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
BulkInsertTransformStep transformStep = (BulkInsertTransformStep) getTransformStep();
|
||||
ProcessSummaryLine okSummary = transformStep.okSummary;
|
||||
okSummary.setCount(0);
|
||||
okSummary.setPrimaryKeys(new ArrayList<>());
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// but - since errors from the transform step don't even make it through to us here in the load step, //
|
||||
// do re-use the ProcessSummaryWarningsAndErrorsRollup from transform step as follows: //
|
||||
// clear out its warnings - we'll completely rebuild them here (with primary keys) //
|
||||
// and add new error lines, e.g., in case of errors that only happened past the validation if possible. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ProcessSummaryWarningsAndErrorsRollup processSummaryWarningsAndErrorsRollup = transformStep.processSummaryWarningsAndErrorsRollup;
|
||||
processSummaryWarningsAndErrorsRollup.resetWarnings();
|
||||
|
||||
List<QRecord> updatedRecords = runBackendStepOutput.getRecords();
|
||||
for(QRecord updatedRecord : updatedRecords)
|
||||
{
|
||||
Serializable primaryKey = updatedRecord.getValue(table.getPrimaryKeyField());
|
||||
if(CollectionUtils.nullSafeIsEmpty(updatedRecord.getErrors()) && primaryKey != null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// if the record had no errors, and we have a primary key for it, then //
|
||||
// keep track of the range of primary keys (first and last) //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
if(firstInsertedPrimaryKey == null)
|
||||
{
|
||||
firstInsertedPrimaryKey = primaryKey;
|
||||
}
|
||||
|
||||
lastInsertedPrimaryKey = primaryKey;
|
||||
|
||||
if(!CollectionUtils.nullSafeIsEmpty(updatedRecord.getWarnings()))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// if there were warnings on the updated record, put it in a warning line //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
String message = updatedRecord.getWarnings().get(0).getMessage();
|
||||
processSummaryWarningsAndErrorsRollup.addWarning(message, primaryKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// if no warnings for the updated record, then put it in the OK line //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
okSummary.incrementCountAndAddPrimaryKey(primaryKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// else if there were errors or no primary key, build an error line //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
String message = "Failed to update";
|
||||
if(!CollectionUtils.nullSafeIsEmpty(updatedRecord.getErrors()))
|
||||
{
|
||||
//////////////////////////////////////////////////////////
|
||||
// use the error message from the record if we have one //
|
||||
//////////////////////////////////////////////////////////
|
||||
message = updatedRecord.getErrors().get(0).getMessage();
|
||||
}
|
||||
processSummaryWarningsAndErrorsRollup.addError(message, primaryKey);
|
||||
}
|
||||
}
|
||||
|
||||
okSummary.pickMessage(true);
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
@ -65,9 +66,11 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
|
||||
{
|
||||
buildFileDetailsForMappingStep(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
boolean isBulkEdit = BooleanUtils.isTrue(runBackendStepInput.getValueBoolean("isBulkEdit"));
|
||||
String tableName = runBackendStepInput.getValueString("tableName");
|
||||
BulkLoadTableStructure tableStructure = BulkLoadTableStructureBuilder.buildTableStructure(tableName);
|
||||
BulkLoadTableStructure tableStructure = BulkLoadTableStructureBuilder.buildTableStructure(tableName, isBulkEdit);
|
||||
runBackendStepOutput.addValue("tableStructure", tableStructure);
|
||||
runBackendStepOutput.addValue("isBulkEdit", isBulkEdit);
|
||||
|
||||
boolean needSuggestedMapping = true;
|
||||
if(runBackendStepOutput.getProcessState().getIsStepBack())
|
||||
@ -81,7 +84,7 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> headerValues = (List<String>) runBackendStepOutput.getValue("headerValues");
|
||||
buildSuggestedMapping(headerValues, getPrepopulatedValues(runBackendStepInput), tableStructure, runBackendStepOutput);
|
||||
buildSuggestedMapping(isBulkEdit, headerValues, getPrepopulatedValues(runBackendStepInput), tableStructure, runBackendStepOutput);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,8 +98,8 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
|
||||
String prepopulatedValuesJson = runBackendStepInput.getValueString("prepopulatedValues");
|
||||
if(StringUtils.hasContent(prepopulatedValuesJson))
|
||||
{
|
||||
Map<String, Serializable> rs = new LinkedHashMap<>();
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(prepopulatedValuesJson);
|
||||
Map<String, Serializable> rs = new LinkedHashMap<>();
|
||||
JSONObject jsonObject = JsonUtils.toJSONObject(prepopulatedValuesJson);
|
||||
for(String key : jsonObject.keySet())
|
||||
{
|
||||
rs.put(key, jsonObject.optString(key, null));
|
||||
@ -112,16 +115,16 @@ public class BulkInsertPrepareFileMappingStep implements BackendStep
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void buildSuggestedMapping(List<String> headerValues, Map<String, Serializable> prepopulatedValues, BulkLoadTableStructure tableStructure, RunBackendStepOutput runBackendStepOutput)
|
||||
private void buildSuggestedMapping(boolean isBulkEdit, List<String> headerValues, Map<String, Serializable> prepopulatedValues, BulkLoadTableStructure tableStructure, RunBackendStepOutput runBackendStepOutput)
|
||||
{
|
||||
BulkLoadMappingSuggester bulkLoadMappingSuggester = new BulkLoadMappingSuggester();
|
||||
BulkLoadProfile bulkLoadProfile = bulkLoadMappingSuggester.suggestBulkLoadMappingProfile(tableStructure, headerValues);
|
||||
BulkLoadProfile bulkLoadProfile = bulkLoadMappingSuggester.suggestBulkLoadMappingProfile(tableStructure, headerValues, isBulkEdit);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(prepopulatedValues))
|
||||
{
|
||||
for(Map.Entry<String, Serializable> entry : prepopulatedValues.entrySet())
|
||||
{
|
||||
String fieldName = entry.getKey();
|
||||
String fieldName = entry.getKey();
|
||||
boolean foundFieldInProfile = false;
|
||||
|
||||
for(BulkLoadProfileField bulkLoadProfileField : bulkLoadProfile.getFieldList())
|
||||
|
@ -65,10 +65,12 @@ public class BulkInsertPrepareFileUploadStep implements BackendStep
|
||||
runBackendStepOutput.addValue("theFile", null);
|
||||
}
|
||||
|
||||
boolean isBulkEdit = runBackendStepInput.getProcessName().endsWith("EditWithFile");
|
||||
String tableName = runBackendStepInput.getValueString("tableName");
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
BulkLoadTableStructure tableStructure = BulkLoadTableStructureBuilder.buildTableStructure(tableName);
|
||||
runBackendStepOutput.addValue("tableStructure", tableStructure);
|
||||
runBackendStepOutput.addValue("isBulkEdit", isBulkEdit);
|
||||
|
||||
List<QFieldMetaData> requiredFields = new ArrayList<>();
|
||||
List<QFieldMetaData> additionalFields = new ArrayList<>();
|
||||
@ -84,6 +86,14 @@ public class BulkInsertPrepareFileUploadStep implements BackendStep
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// bulk edit allows primary key as a field //
|
||||
/////////////////////////////////////////////
|
||||
if(isBulkEdit)
|
||||
{
|
||||
requiredFields.add(0, table.getField(table.getPrimaryKeyField()));
|
||||
}
|
||||
|
||||
StringBuilder html;
|
||||
String childTableLabels = "";
|
||||
|
||||
@ -96,11 +106,11 @@ public class BulkInsertPrepareFileUploadStep implements BackendStep
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
boolean listFieldsInHelpText = false;
|
||||
|
||||
if(!CollectionUtils.nullSafeHasContents(tableStructure.getAssociations()))
|
||||
if(isBulkEdit || !CollectionUtils.nullSafeHasContents(tableStructure.getAssociations()))
|
||||
{
|
||||
html = new StringBuilder("""
|
||||
<p>Upload either a CSV or Excel (.xlsx) file, with one row for each record you want to
|
||||
insert in the ${tableLabel} table.</p><br />
|
||||
${action} in the ${tableLabel} table.</p><br />
|
||||
|
||||
<p>Your file can contain any number of columns. You will be prompted to map fields from
|
||||
the ${tableLabel} table to columns from your file or default values for all records that
|
||||
@ -204,6 +214,7 @@ public class BulkInsertPrepareFileUploadStep implements BackendStep
|
||||
finishCSV(flatCSV);
|
||||
|
||||
String htmlString = html.toString()
|
||||
.replace("${action}", (isBulkEdit ? "edit" : "insert"))
|
||||
.replace("${tableLabel}", table.getLabel())
|
||||
.replace("${childTableLabels}", childTableLabels)
|
||||
.replace("${flatCSV}", Base64.getEncoder().encodeToString(flatCSV.toString().getBytes(StandardCharsets.UTF_8)))
|
||||
|
@ -113,6 +113,8 @@ public class BulkInsertStepUtils
|
||||
{
|
||||
String layout = runBackendStepInput.getValueString("layout");
|
||||
Boolean hasHeaderRow = runBackendStepInput.getValueBoolean("hasHeaderRow");
|
||||
String keyFields = runBackendStepInput.getValueString("keyFields");
|
||||
Boolean isBulkEdit = runBackendStepInput.getValueBoolean("isBulkEdit");
|
||||
|
||||
ArrayList<BulkLoadProfileField> fieldList = new ArrayList<>();
|
||||
|
||||
@ -127,6 +129,7 @@ public class BulkInsertStepUtils
|
||||
bulkLoadProfileField.setColumnIndex(jsonObject.has("columnIndex") ? jsonObject.getInt("columnIndex") : null);
|
||||
bulkLoadProfileField.setDefaultValue((Serializable) jsonObject.opt("defaultValue"));
|
||||
bulkLoadProfileField.setDoValueMapping(jsonObject.optBoolean("doValueMapping"));
|
||||
bulkLoadProfileField.setClearIfEmpty(jsonObject.optBoolean("clearIfEmpty"));
|
||||
|
||||
if(BooleanUtils.isTrue(bulkLoadProfileField.getDoValueMapping()) && jsonObject.has("valueMappings"))
|
||||
{
|
||||
@ -140,6 +143,8 @@ public class BulkInsertStepUtils
|
||||
}
|
||||
|
||||
BulkLoadProfile bulkLoadProfile = new BulkLoadProfile()
|
||||
.withIsBulkEdit(isBulkEdit)
|
||||
.withKeyFields(keyFields)
|
||||
.withVersion(version)
|
||||
.withFieldList(fieldList)
|
||||
.withHasHeaderRow(hasHeaderRow)
|
||||
@ -213,7 +218,7 @@ public class BulkInsertStepUtils
|
||||
{
|
||||
return (processTracerKeyRecordMessage);
|
||||
}
|
||||
|
||||
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
@ -32,12 +33,13 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreInsertCustomizer.WhenToRun;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -48,6 +50,11 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutp
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
@ -68,6 +75,9 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -75,9 +85,9 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
*******************************************************************************/
|
||||
public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
{
|
||||
ProcessSummaryLine okSummary = new ProcessSummaryLine(Status.OK);
|
||||
public ProcessSummaryLine okSummary = new ProcessSummaryLine(Status.OK);
|
||||
|
||||
ProcessSummaryWarningsAndErrorsRollup processSummaryWarningsAndErrorsRollup = ProcessSummaryWarningsAndErrorsRollup.build("inserted")
|
||||
public ProcessSummaryWarningsAndErrorsRollup processSummaryWarningsAndErrorsRollup = ProcessSummaryWarningsAndErrorsRollup.build("inserted")
|
||||
.withDoReplaceSingletonCountLinesWithSuffixOnly(false);
|
||||
|
||||
private ListingHash<String, RowValue> errorToExampleRowValueMap = new ListingHash<>();
|
||||
@ -190,6 +200,252 @@ public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
QTableMetaData table = QContext.getQInstance().getTable(runBackendStepInput.getTableName());
|
||||
List<QRecord> records = runBackendStepInput.getRecords();
|
||||
|
||||
if(BooleanUtils.isTrue(runBackendStepInput.getValueBoolean("isBulkEdit")))
|
||||
{
|
||||
handleBulkEdit(runBackendStepInput, runBackendStepOutput, records, table);
|
||||
runBackendStepOutput.addValue("isBulkEdit", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleBulkLoad(runBackendStepInput, runBackendStepOutput, records, table);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void handleBulkEdit(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput, List<QRecord> records, QTableMetaData table) throws QException
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// get the key fields for this bulk edit //
|
||||
///////////////////////////////////////////
|
||||
String keyFieldsString = runBackendStepInput.getValueString("keyFields");
|
||||
List<String> keyFields = Arrays.asList(keyFieldsString.split("\\|"));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// if the key field is the primary key, then just look up those records //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> nonMatchingRecords = new ArrayList<>();
|
||||
List<QRecord> oldRecords = new ArrayList<>();
|
||||
List<QRecord> recordsToUpdate = new ArrayList<>();
|
||||
if(keyFields.size() == 1 && table.getPrimaryKeyField().equals(keyFields.get(0)))
|
||||
{
|
||||
recordsToUpdate = records;
|
||||
String primaryKeyName = table.getPrimaryKeyField();
|
||||
List<Serializable> primaryKeys = records.stream().map(record -> record.getValue(primaryKeyName)).toList();
|
||||
oldRecords = new QueryAction().execute(new QueryInput(table.getName()).withFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, primaryKeys)))).getRecords();
|
||||
|
||||
///////////////////////////////////////////
|
||||
// get a set of old records primary keys //
|
||||
///////////////////////////////////////////
|
||||
Set<Serializable> matchedPrimaryKeys = oldRecords.stream()
|
||||
.map(r -> r.getValue(table.getPrimaryKeyField()))
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// iterate over file records and if primary keys dont match, add to the non matching records list //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord record : records)
|
||||
{
|
||||
Serializable recordKey = record.getValue(table.getPrimaryKeyField());
|
||||
if(!matchedPrimaryKeys.contains(recordKey))
|
||||
{
|
||||
nonMatchingRecords.add(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Set<Serializable> uniqueIds = new HashSet<>();
|
||||
List<QRecord> potentialRecords = new ArrayList<>();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if not using the primary key, then we will look up all records for each part of the unique key //
|
||||
// and for each found, if all unique parts match we will add to our list of database records //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(String uniqueKeyPart : keyFields)
|
||||
{
|
||||
List<Serializable> values = records.stream().map(record -> record.getValue(uniqueKeyPart)).toList();
|
||||
for(QRecord databaseRecord : new QueryAction().execute(new QueryInput(table.getName()).withFilter(new QQueryFilter(new QFilterCriteria(uniqueKeyPart, QCriteriaOperator.IN, values)))).getRecords())
|
||||
{
|
||||
if(!uniqueIds.contains(databaseRecord.getValue(table.getPrimaryKeyField())))
|
||||
{
|
||||
potentialRecords.add(databaseRecord);
|
||||
uniqueIds.add(databaseRecord.getValue(table.getPrimaryKeyField()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// now iterate over all of the potential records checking each unique fields //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
fileRecordLoop:
|
||||
for(QRecord fileRecord : records)
|
||||
{
|
||||
for(QRecord databaseRecord : potentialRecords)
|
||||
{
|
||||
boolean allMatch = true;
|
||||
|
||||
for(String uniqueKeyPart : keyFields)
|
||||
{
|
||||
if(!Objects.equals(fileRecord.getValue(uniqueKeyPart), databaseRecord.getValue(uniqueKeyPart)))
|
||||
{
|
||||
allMatch = false;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we get here with all matching, update the record from the file's primary key, //
|
||||
// add it to the list to update, and continue looping over file records //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
if(allMatch)
|
||||
{
|
||||
oldRecords.add(databaseRecord);
|
||||
fileRecord.setValue(table.getPrimaryKeyField(), databaseRecord.getValue(table.getPrimaryKeyField()));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// iterate over the fields in the bulk load profile, if the value for that field is empty and the value //
|
||||
// of 'clear if empty' is set to true, then update the record to update with the old record's value //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
JSONArray array = new JSONArray(runBackendStepInput.getValueString("fieldListJSON"));
|
||||
for(int i = 0; i < array.length(); i++)
|
||||
{
|
||||
JSONObject jsonObject = array.getJSONObject(i);
|
||||
String fieldName = jsonObject.optString("fieldName");
|
||||
boolean clearIfEmpty = jsonObject.optBoolean("clearIfEmpty");
|
||||
|
||||
if(fileRecord.getValue(fieldName) == null)
|
||||
{
|
||||
if(clearIfEmpty)
|
||||
{
|
||||
fileRecord.setValue(fieldName, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileRecord.setValue(fieldName, databaseRecord.getValue(fieldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recordsToUpdate.add(fileRecord);
|
||||
continue fileRecordLoop;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we make it here, that means the record was not found, keep for logging warning //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
nonMatchingRecords.add(fileRecord);
|
||||
}
|
||||
}
|
||||
|
||||
for(QRecord missingRecord : CollectionUtils.nonNullList(nonMatchingRecords))
|
||||
{
|
||||
String message = "Did not have a matching existing record.";
|
||||
processSummaryWarningsAndErrorsRollup.addError(message, null);
|
||||
addToErrorToExampleRowMap(message, missingRecord);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set up an insert-input, which will be used as input to the pre-customizer as well as for additional validations //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
UpdateInput updateInput = new UpdateInput(table.getName());
|
||||
updateInput.setInputSource(QInputSource.USER);
|
||||
updateInput.setRecords(recordsToUpdate);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// load the pre-insert customizer and set it up, if there is one //
|
||||
// then we'll run it based on its WhenToRun value //
|
||||
// we do this, in case it needs to, for example, adjust values that //
|
||||
// are part of a unique key //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
boolean didAlreadyRunCustomizer = false;
|
||||
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
List<QRecord> recordsAfterCustomizer = preUpdateCustomizer.get().preUpdate(updateInput, records, true, Optional.of(oldRecords));
|
||||
runBackendStepInput.setRecords(recordsAfterCustomizer);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// so we used to have a comment here asking "do we care if the customizer runs both now, and in the validation below?" //
|
||||
// when implementing Bulk Load V2, we were seeing that some customizers were adding errors to records, both now, and //
|
||||
// when they ran below. so, at that time, we added this boolean, to track and avoid the double-run... //
|
||||
// we could also imagine this being a setting on the pre-insert customizer, similar to its whenToRun attribute... //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
didAlreadyRunCustomizer = true;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// run all validation from the insert action - in Preview mode (boolean param) //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
updateInput.setRecords(recordsToUpdate);
|
||||
UpdateAction updateAction = new UpdateAction();
|
||||
updateAction.performValidations(updateInput, Optional.of(recordsToUpdate), didAlreadyRunCustomizer);
|
||||
List<QRecord> validationResultRecords = updateInput.getRecords();
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// look at validation results to build process summary results //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
List<QRecord> outputRecords = new ArrayList<>();
|
||||
for(QRecord record : validationResultRecords)
|
||||
{
|
||||
List<QErrorMessage> errorsFromAssociations = getErrorsFromAssociations(record);
|
||||
if(CollectionUtils.nullSafeHasContents(errorsFromAssociations))
|
||||
{
|
||||
List<QErrorMessage> recordErrors = Objects.requireNonNullElseGet(record.getErrors(), () -> new ArrayList<>());
|
||||
recordErrors.addAll(errorsFromAssociations);
|
||||
record.setErrors(recordErrors);
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
for(QErrorMessage error : record.getErrors())
|
||||
{
|
||||
if(error instanceof AbstractBulkLoadRollableValueError rollableValueError)
|
||||
{
|
||||
processSummaryWarningsAndErrorsRollup.addError(rollableValueError.getMessageToUseAsProcessSummaryRollupKey(), null);
|
||||
addToErrorToExampleRowValueMap(rollableValueError, record);
|
||||
}
|
||||
else
|
||||
{
|
||||
processSummaryWarningsAndErrorsRollup.addError(error.getMessage(), null);
|
||||
addToErrorToExampleRowMap(error.getMessage(), record);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(CollectionUtils.nullSafeHasContents(record.getWarnings()))
|
||||
{
|
||||
String message = record.getWarnings().get(0).getMessage();
|
||||
processSummaryWarningsAndErrorsRollup.addWarning(message, null);
|
||||
outputRecords.add(record);
|
||||
}
|
||||
else
|
||||
{
|
||||
okSummary.incrementCountAndAddPrimaryKey(null);
|
||||
outputRecords.add(record);
|
||||
|
||||
for(Map.Entry<String, List<QRecord>> entry : CollectionUtils.nonNullMap(record.getAssociatedRecords()).entrySet())
|
||||
{
|
||||
String associationName = entry.getKey();
|
||||
ProcessSummaryLine associationToInsertLine = associationsToInsertSummaries.computeIfAbsent(associationName, x -> new ProcessSummaryLine(Status.OK));
|
||||
associationToInsertLine.incrementCount(CollectionUtils.nonNullList(entry.getValue()).size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runBackendStepOutput.setRecords(outputRecords);
|
||||
this.rowsProcessed += records.size();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void handleBulkLoad(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput, List<QRecord> records, QTableMetaData table) throws QException
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set up an insert-input, which will be used as input to the pre-customizer as well as for additional validations //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -209,7 +465,7 @@ public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
Optional<TableCustomizerInterface> preInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
|
||||
if(preInsertCustomizer.isPresent())
|
||||
{
|
||||
AbstractPreInsertCustomizer.WhenToRun whenToRun = preInsertCustomizer.get().whenToRunPreInsert(insertInput, true);
|
||||
WhenToRun whenToRun = preInsertCustomizer.get().whenToRunPreInsert(insertInput, true);
|
||||
if(WhenToRun.BEFORE_ALL_VALIDATIONS.equals(whenToRun) || WhenToRun.BEFORE_UNIQUE_KEY_CHECKS.equals(whenToRun))
|
||||
{
|
||||
List<QRecord> recordsAfterCustomizer = preInsertCustomizer.get().preInsert(insertInput, records, true);
|
||||
@ -485,11 +741,13 @@ public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
recordsProcessedLine.withPluralFutureMessage("records were");
|
||||
recordsProcessedLine.withPluralPastMessage("records were");
|
||||
|
||||
String noWarningsSuffix = processSummaryWarningsAndErrorsRollup.countWarnings() == 0 ? "" : " with no warnings";
|
||||
okSummary.setSingularFutureMessage(tableLabel + " record will be inserted" + noWarningsSuffix + ".");
|
||||
okSummary.setPluralFutureMessage(tableLabel + " records will be inserted" + noWarningsSuffix + ".");
|
||||
okSummary.setSingularPastMessage(tableLabel + " record was inserted" + noWarningsSuffix + ".");
|
||||
okSummary.setPluralPastMessage(tableLabel + " records were inserted" + noWarningsSuffix + ".");
|
||||
boolean isBulkEdit = BooleanUtils.isTrue(runBackendStepOutput.getValueBoolean("isBulkEdit"));
|
||||
String action = isBulkEdit ? "updated" : "inserted";
|
||||
String noWarningsSuffix = processSummaryWarningsAndErrorsRollup.countWarnings() == 0 ? "" : " with no warnings";
|
||||
okSummary.setSingularFutureMessage(tableLabel + " record will be " + action + noWarningsSuffix + ".");
|
||||
okSummary.setPluralFutureMessage(tableLabel + " records will be " + action + noWarningsSuffix + ".");
|
||||
okSummary.setSingularPastMessage(tableLabel + " record was " + action + noWarningsSuffix + ".");
|
||||
okSummary.setPluralPastMessage(tableLabel + " records were " + action + noWarningsSuffix + ".");
|
||||
okSummary.pickMessage(isForResultScreen);
|
||||
okSummary.addSelfToListIfAnyCount(rs);
|
||||
|
||||
@ -502,10 +760,10 @@ public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
String associationLabel = associationTable.getLabel();
|
||||
|
||||
ProcessSummaryLine line = entry.getValue();
|
||||
line.setSingularFutureMessage(associationLabel + " record will be inserted.");
|
||||
line.setPluralFutureMessage(associationLabel + " records will be inserted.");
|
||||
line.setSingularPastMessage(associationLabel + " record was inserted.");
|
||||
line.setPluralPastMessage(associationLabel + " records were inserted.");
|
||||
line.setSingularFutureMessage(associationLabel + " record will be " + action + ".");
|
||||
line.setPluralFutureMessage(associationLabel + " records will be " + action + ".");
|
||||
line.setSingularPastMessage(associationLabel + " record was " + action + ".");
|
||||
line.setPluralPastMessage(associationLabel + " records were " + action + ".");
|
||||
line.pickMessage(isForResultScreen);
|
||||
line.addSelfToListIfAnyCount(rs);
|
||||
}
|
||||
@ -518,8 +776,8 @@ public class BulkInsertTransformStep extends AbstractTransformStep
|
||||
|
||||
ukErrorSummary
|
||||
.withMessageSuffix(" inserted, because of duplicate values in a unique key on the fields (" + uniqueKey.getDescription(table) + "), with values"
|
||||
+ (ukErrorSummary.areThereMoreSampleValues ? " such as: " : ": ")
|
||||
+ StringUtils.joinWithCommasAndAnd(new ArrayList<>(ukErrorSummary.sampleValues)))
|
||||
+ (ukErrorSummary.areThereMoreSampleValues ? " such as: " : ": ")
|
||||
+ StringUtils.joinWithCommasAndAnd(new ArrayList<>(ukErrorSummary.sampleValues)))
|
||||
|
||||
.withSingularFutureMessage(" record will not be")
|
||||
.withPluralFutureMessage(" records will not be")
|
||||
|
@ -54,7 +54,7 @@ public class BulkLoadMappingSuggester
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public BulkLoadProfile suggestBulkLoadMappingProfile(BulkLoadTableStructure tableStructure, List<String> headerRow)
|
||||
public BulkLoadProfile suggestBulkLoadMappingProfile(BulkLoadTableStructure tableStructure, List<String> headerRow, boolean isBulkEdit)
|
||||
{
|
||||
massagedHeadersWithoutNumbersToIndexMap = new LinkedHashMap<>();
|
||||
for(int i = 0; i < headerRow.size(); i++)
|
||||
@ -90,6 +90,7 @@ public class BulkLoadMappingSuggester
|
||||
.withVersion("v1")
|
||||
.withLayout(layout)
|
||||
.withHasHeaderRow(true)
|
||||
.withIsBulkEdit(isBulkEdit)
|
||||
.withFieldList(fieldList);
|
||||
|
||||
return (bulkLoadProfile);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user