Compare commits

...

118 Commits

Author SHA1 Message Date
6e91149b0a Feedback from code review 2024-11-22 10:21:22 -06:00
cfeb71aa2f Merged dev into feature/pom-version-fixing 2024-11-21 19:21:04 -06:00
edaabc3523 Try to manage 'snapshot' versions ourselves, to avoid bom-pom causing floating versions to be included... 2024-11-21 15:55:15 -06:00
e53e00c520 Merge pull request #138 from Kingsrook/feature/CE-1887-mobile-android-app
Feature/ce 1887 mobile android app
2024-11-21 11:53:39 -06:00
e970d613a7 Merged dev into feature/CE-1887-mobile-android-app 2024-11-21 10:55:16 -06:00
f5c1573102 Merge pull request #139 from Kingsrook/feature/CE-1946-process-to-allow-post-wms-carton-contents-adjustments
Feature/ce 1946 process to allow post wms carton contents adjustments
2024-11-21 10:33:48 -06:00
2103d578b3 Merge pull request #140 from Kingsrook/feature/CE-1772-generate-labels-poc
Feature/ce 1772 generate labels poc
2024-11-21 10:31:32 -06:00
daad8a720a CE-1946: added more props to child record list data 2024-11-19 20:41:16 -06:00
0ef01efcaa CE-1772: updates to alert widgets 2024-11-19 15:03:02 -06:00
389f0ad16f Merged dev into feature/CE-1887-mobile-android-app 2024-11-04 07:58:14 -06:00
6ef0a89533 CE-1772: fix aws expecting content type if object metadata is given 2024-11-03 21:53:50 -06:00
ce50120234 CE-1772: s3 updates to allow content type specifications among other things 2024-11-03 21:34:50 -06:00
50ef9420f6 CE-1887 - Rebuilt to get stable set of capabilities in example 2024-10-31 14:34:28 -05:00
c9fefb45a5 CE-1887 - Rebuilt per changes in this latest iteration 2024-10-31 14:19:14 -05:00
bf97d757b0 CE-1887 - add call to build and run ValidateApiVersions 2024-10-31 12:35:29 -05:00
c481940736 CE-1887 - javadoc / checkstyle 2024-10-31 12:06:53 -05:00
5f0f4cdab3 CE-1887 - WIP on doing verification of MW API during CI 2024-10-31 11:51:18 -05:00
8356fc3f12 CE-1887 - Add QFrontendWidgetMetaData to meta-data responses 2024-10-31 11:45:37 -05:00
8534a2ca55 CE-1887 - update to map some process values using their versioned-api responses (specifically, blockWidgetData) 2024-10-31 11:45:37 -05:00
46e54ed8e3 CE-1887 - update some block attributes as needed for working-version of android mobile scan apps 2024-10-31 11:45:37 -05:00
45439d7596 CE-1887 - Update to return full icon object, not just name 2024-10-31 11:19:24 -05:00
f01301e993 CE-1887 - Add frontend, middleware, and application name & version properties 2024-10-31 11:18:24 -05:00
59edb34c12 CE-1887 - Add frontend, middleware, and application name & version properties 2024-10-31 11:17:51 -05:00
74ea6a2d90 CE-1887 - Add MetaDataFilter to the QInstance and MetaDataAction 2024-10-31 11:17:18 -05:00
93ab08cbf1 CE-1887 expose to reset additional endpointGroups / javalinRoutes 2024-10-21 14:20:13 -05:00
a5051e559a CE-1887 Test on QAppJavalinServer 2024-10-21 13:41:54 -05:00
c5fdfceae6 CE-1887 Try to exclude full tools package 2024-10-21 13:07:12 -05:00
2965338e22 CE-1887 Increasing test coverage 2024-10-21 09:54:04 -05:00
5e37beabbe CE-1887 Fix tests (pass qinstance into spec) 2024-10-18 10:52:42 -05:00
428e188b4b CE-1887 Checkstyle! 2024-10-18 10:44:43 -05:00
533822973b CE-1887 Add copyright 2024-10-18 10:25:45 -05:00
5e9bd2a7e7 CE-1887 Adding test coverage! 2024-10-18 10:15:24 -05:00
7c54006985 CE-1887 Reoreder imports after moving MetaDataProducerInterface 2024-10-18 07:57:19 -05:00
678ecfd589 CE-1887 Add withRefToSchema; allow setType to set null 2024-10-17 20:27:11 -05:00
cc55b32206 CE-1887 Initial (not quite finished) version 1 middleware api spec 2024-10-17 20:26:54 -05:00
8dedc98866 CE-1887 rename method generate to generateOpenAPIModel 2024-10-17 12:40:08 -05:00
cde7a60ae0 CE-1887 Initial checkin 2024-10-17 12:39:41 -05:00
aef366e5fe CE-1887 Initial checkin 2024-10-17 12:36:21 -05:00
a46397df39 CE-1887 Initial checkin - dev-tools for middleware api 2024-10-17 12:34:41 -05:00
8500a2559c CE-1887 Initial checkin 2024-10-17 12:28:43 -05:00
872e125810 CE-1887 Updates for javalin 6.3.0; add method addJavalinRoutes(EndpointGroup); 2024-10-17 12:24:30 -05:00
eb754b42b9 CE-1887 Move methods deal with getting classes from packages into new ClassPathUtils 2024-10-17 12:22:58 -05:00
9ea92553e7 CE-1887 Fix spelling of withVales method 2024-10-17 12:22:10 -05:00
26a33eb1a0 CE-1887 Initial checkin of more formalized classes to define a qqq application 2024-10-17 12:22:10 -05:00
e40b35154e CE-1887 Move MetaDataProducerInterface into metadata package (where it always belonged...) 2024-10-17 12:22:10 -05:00
8436f2ab0a CE-1887 Updates for javalin 6.3.0; add method addJavalinRoutes(EndpointGroup); add getter & setter for QInstance; 2024-10-17 11:55:20 -05:00
7beea514d2 CE-1887 Upgrade javalin (5.6.1 to 6.3.0); Add dep on new qqq-openapi module 2024-10-17 11:52:13 -05:00
fc23718c4f CE-1887 Migrate openAPI model classes out of qqq-middleware-api, into new qqq-openapi module (for re-use within qqq-midleware-javalin) 2024-10-17 11:46:29 -05:00
efe89c7043 Merge pull request #137 from Kingsrook/feature/allow-basepull-override-values-from-jobs
hotfix: allow basepull override values from jobs
2024-10-11 09:27:24 -05:00
bbf4c2c2ff hotfix: allow basepull override values from jobs 2024-10-10 18:12:45 -05:00
ff1e022798 CE-1836: wasn't properly using boolean values in backend step input 2024-10-10 11:31:43 -05:00
f09735c811 Merged feature/CE-1836-create-order-checkers into dev 2024-10-10 10:56:30 -05:00
7ab9171998 Merged feature/CE-1821-veryify-shipped-orders-process into dev 2024-10-10 10:56:07 -05:00
b979f413c8 Merged feature/CE-1654-warehouse-security-key-all-access-left-join into dev 2024-10-10 10:53:25 -05:00
766881dee0 CE-1836: fixed npe if last basepull runtime hadnt been set 2024-10-10 09:59:20 -05:00
f65b16df60 Merge pull request #136 from Kingsrook/feature/CE-1836-create-order-checkers
Feature/ce 1836 create order checkers
2024-10-09 14:59:54 -05:00
e0597827ef CE-1836: updates from code review 2024-10-09 10:30:49 -05:00
10014f16ae CE-1836: fixed to check as boolean 2024-10-08 16:20:23 -05:00
526ba6ca30 CE-1836: added potential to log output 2024-10-08 15:46:47 -05:00
4f92fb2ae2 CE-1836: updates to allow getting basepull key value and sync config perform insert/updates from input 2024-10-07 22:33:16 -05:00
b687d07e46 CE-1836: update abstract table sync to make members and functions protected 2024-10-04 12:24:58 -05:00
b955a20e18 CE-1654 - Checkstyle! 2024-10-02 16:22:41 -05:00
eb8781db77 CE-1654 - Update joins built for security-purposes, that if they're for an all-access key, to be outer (LEFT); update tests to reflect this 2024-10-02 16:16:16 -05:00
febda51233 CE-1821: added static utility method for returning a list of entities rather than records 2024-09-26 15:16:23 -05:00
27dbc72db4 CE-1727 - Add standardColor and isAlert 2024-09-20 19:39:16 -05:00
983a93d38c CE-1727 - Remove un-intentional commit of bulk-load-mapping wip changes 2024-09-20 10:04:38 -05:00
28ad0661d1 CE-1727 - Mark as serializable 2024-09-20 10:00:11 -05:00
a8e235c155 CE-1727 - Update JSON serialization at the end of doProcessInitOrStep to specify to include null and empty values 2024-09-20 09:59:59 -05:00
c96bb9dda8 CE-1727 - Add support for QCodeReferenceLambda 2024-09-20 09:57:30 -05:00
e4bef88406 CE-1727 - Introduce concept of stepFlow to processes - LINEAR (the previous), and STATE_MACHINE (designed to be more flexible) 2024-09-20 09:57:02 -05:00
c18aa44010 CE-1727 - Initial checkin 2024-09-20 09:53:41 -05:00
47e95d74e3 CE-1727 - Add 'conditional' attribute 2024-09-20 09:47:35 -05:00
cf4c6d2144 CE-1727 - migrating from updatedFrontendStepList to processMetaDataAdjustment - so one can update fields in a process (original intent for inline PVS's) 2024-09-20 09:44:37 -05:00
780341b5cc CE-1727 - Initial checkin 2024-09-20 09:19:29 -05:00
20d4a9ffeb CE-1727 - new Layout (FLEX_ROW_CENTER) 2024-09-20 09:16:30 -05:00
4f1310ded9 CE-1727 - Initial checkin of new blocks 2024-09-20 09:15:59 -05:00
791b77b938 Merged feature/CE-1654-warehouse-security-key into dev 2024-09-18 16:48:29 -05:00
e6864b89c1 Merged feature/javalin-query-default-limit into dev 2024-09-18 16:48:14 -05:00
c3171c335f Update to always impose a limit on queries (they were getting lost if there was a defaultQueryFilter passed in) 2024-09-17 16:41:41 -05:00
bb548b78d9 updates to allow override api utils to disable or alter request details 2024-09-10 17:28:05 -05:00
161591405b CE-1654 - do chicken-egg session before the OTHER call to finalizeCustomizeSession too... 2024-09-10 10:51:21 -05:00
3cc0cfd86c CE-1654 - Just log, don't throw, if missing a security key value (should this be a setting??) 2024-09-10 09:34:46 -05:00
fb16a041fb CE-1727 - Add inlinePossibleValueSources option to fields 2024-09-09 11:53:01 -05:00
9bf9825132 Option (turned on by default, controlled via javalin metadata) to not allow query requests without a limit 2024-09-05 18:33:37 -05:00
a7ca34ec92 CE-1546 Switch auditTable.id and auditUser.id back to INTEGER (one isn't expected to have 2,000,000,000 of those) - fixes possible-value lookups 2024-09-05 14:17:45 -05:00
403227bae1 Merge tag 'version-0.22.1' into dev
Tag release
2024-09-05 13:40:57 -05:00
ab4837ff16 Merge branch 'rel/0.22.1' 2024-09-05 13:38:04 -05:00
107acb5685 Update for next development version 2024-09-05 13:28:56 -05:00
65166150e6 Update versions for release 2024-09-05 13:28:54 -05:00
c678a8159e Merged feature/CE-1546-support-migrating-audit-detail-to-big-int into dev 2024-09-05 13:17:40 -05:00
6673a8fc47 Updating to 0.23.0 2024-09-05 08:45:49 -05:00
c4f4faf32b Merge tag 'version-0.22.0' into dev
Tag release
2024-09-05 08:45:45 -05:00
9de08be978 Merge branch 'rel/0.22.0' 2024-09-05 08:43:09 -05:00
4349b37c8d Update for next development version 2024-09-05 07:56:20 -05:00
afb6aa3b89 Update versions for release 2024-09-05 07:56:16 -05:00
6c9ce41c7b Merge pull request #130 from Kingsrook/feature/CE-1646-possible-value-filter-bug
Feature/ce 1646 possible value filter bug
2024-09-04 16:23:05 -05:00
dc34e69c3c Merge pull request #131 from Kingsrook/feature/CE-1643-query-date-bugs-2
Feature/ce 1643 query date bugs 2
2024-09-04 16:21:03 -05:00
f457fd0860 CE-1654 activate chickenAndEggSession while calling customizer.finalCustomizeSession 2024-09-03 22:01:07 -05:00
c3834efad3 CE-1546 - fixing the use long for id in test 2024-08-27 13:05:24 -05:00
d513c8431b CE-1546 - fixing the use long for id in test 2024-08-27 10:01:34 -05:00
fc4e69f059 CE-1546 - feedback from code review 2024-08-26 12:14:01 -05:00
050208cdda CE-1643 Updated sig; added some local-date tests; made instant tests less dumb i hope 2024-08-26 11:00:26 -05:00
8f4146923b CE-1643 Update AbstractFilterExpression.evaluate to take in a QFieldMetaData - so that, in the temporal-based implementations, we can handle DATE_TIMEs differently from DATEs, where we were having RDBMS queries not return expected results, due to Instants being bound instead of LocalDates. 2024-08-26 11:00:20 -05:00
666f4a872d CE-1646 add use-cases to preserve the previous behavior for whether a report w/ missing input criteria values should fail or not 2024-08-23 14:36:23 -05:00
89e0fc566d Try to fix flaky test 2024-08-23 12:17:04 -05:00
42fd5a0cb3 Merged dev into feature/CE-1646-possible-value-filter-bug 2024-08-23 11:52:50 -05:00
89cf23a65a Updating to 0.22.0 2024-08-23 11:50:41 -05:00
57b0d6c29b Merge tag 'version-0.21.0' into dev
Tag release
2024-08-23 11:50:37 -05:00
6702c06ed0 Merge branch 'rel/0.21.0' 2024-08-23 11:47:47 -05:00
c90def42f5 Update for next development version 2024-08-23 11:39:10 -05:00
9dfbd839c8 Update versions for release 2024-08-23 11:39:07 -05:00
724d5779cc Merge pull request #127 from Kingsrook/feature/CE-1405-zero-day-ledger-billing
Feature/ce 1405 zero day ledger billing
2024-08-23 11:19:46 -05:00
1fef376e65 Merge pull request #128 from Kingsrook/feature/CE-1556-ops-overview-enhanced-tooltips
Feature/ce 1556 ops overview enhanced tooltips
2024-08-23 11:02:05 -05:00
ed1e251934 CE-1646 Fix expected message on one test 2024-08-23 10:01:20 -05:00
81248a8daf CE-1646 Accept 'useCase' parameter in possibleValues function, to pass to backend, to control how possible-value filters are applied when input values are missing 2024-08-23 09:57:08 -05:00
20a5130757 CE-1546 - Moving audit ids to longs and adding general support for long ids 2024-08-21 09:35:33 -05:00
47e27d5ffc CE-1554: updates to allow widget block overlays 2024-08-20 18:06:01 -05:00
494ec00b84 CE-1556: updated to try to use composite block data within tooltips 2024-08-13 17:23:30 -05:00
304 changed files with 28666 additions and 779 deletions

View File

@ -1,23 +1,51 @@
#!/bin/bash #!/bin/bash
if [ -z "$CIRCLE_BRANCH" ] && [ -z "$CIRCLE_TAG" ]; then ############################################################################
echo "Error: env vars CIRCLE_BRANCH and CIRCLE_TAG were not set." ## adjust-pom.version.sh
exit 1; ## During CircleCI builds - edit the qqq parent pom.xml, to set the
fi ## <revision> value such that:
## - feature-branch builds, tagged as snapshot-*, deploy with a version
## number that includes that tag's name (minus the snapshot- part)
## - integration-branch builds deploy with a version number that includes
## the branch name slugified
## - we never deploy -SNAPSHOT versions any more - because we don't believe
## it is ever valid to not know exactly what versions you are getting
## (perhaps because we are too loose with our versioning?)
############################################################################
if [ "$CIRCLE_BRANCH" == "dev" ] || [ "$CIRCLE_BRANCH" == "staging" ] || [ "$CIRCLE_BRANCH" == "main" ] || [ \! -z $(echo "$CIRCLE_TAG" | grep "^version-") ]; then POM=$(dirname $0)/../pom.xml
echo "On a primary branch or tag [${CIRCLE_BRANCH}${CIRCLE_TAG}] - will not edit the pom version."; echo "On branch: $CIRCLE_BRANCH, tag: $CIRCLE_TAG..."
######################################################################
## ## only do anything if the committed pom has a -SNAPSHOT version ##
######################################################################
REVISION=$(grep '<revision>' $POM | sed 's/.*<revision>//;s/<.*//');
echo "<revision> in pom.xml is: $REVISION"
if [ \! $(echo "$REVISION" | grep SNAPSHOT) ]; then
echo "Not on a SNAPSHOT revision, so nothing to do here."
exit 0; exit 0;
fi fi
if [ -n "$CIRCLE_BRANCH" ]; then ##################################################################################
SLUG=$(echo $CIRCLE_BRANCH | sed 's/[^a-zA-Z0-9]/-/g') ## ## figure out if we need a SLUG: a snapshot- tag, or an integration/ branch ##
else ##################################################################################
SLUG=$(echo $CIRCLE_TAG | sed 's/^snapshot-//g') SLUG=""
if [ $(echo "$CIRCLE_TAG" | grep ^snapshot-) ]; then
SLUG=$(echo "$CIRCLE_TAG" | sed "s/^snapshot-//")-
echo "Using slug [$SLUG] from tag [$CIRCLE_TAG]"
elif [ $(echo "$CIRCLE_BRANCH" | grep ^integration/) ]; then
SLUG=$(echo "$CIRCLE_BRANCH" | sed "s,/,-,g")-
echo "Using slug [$SLUG] from branch [$CIRCLE_BRANCH]"
fi fi
POM=$(dirname $0)/../pom.xml ################################################################
## ## build the replcaement for -SNAPSHOT, and update the pom ##
################################################################
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
REPLACEMENT=${SLUG}${TIMESTAMP}
echo "Updating $POM <revision> to: $SLUG-SNAPSHOT" echo "Updating $POM -SNAPSHOT to: -$REPLACEMENT"
sed -i "s/<revision>.*/<revision>$SLUG-SNAPSHOT<\/revision>/" $POM sed -i "s/-SNAPSHOT<\/revision>/-$REPLACEMENT<\/revision>/" $POM
git diff $POM git diff $POM

View File

@ -79,6 +79,19 @@ commands:
- ~/.m2 - ~/.m2
key: v1-dependencies-{{ checksum "pom.xml" }} key: v1-dependencies-{{ checksum "pom.xml" }}
check_middleware_api_versions:
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }}
- run:
name: Build and Run ValidateApiVersions
command: |
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: mvn_jar_deploy:
steps: steps:
- checkout - checkout
@ -130,6 +143,7 @@ jobs:
## - localstack/startup ## - localstack/startup
- install_java17 - install_java17
- mvn_verify - mvn_verify
- check_middleware_api_versions
mvn_deploy: mvn_deploy:
executor: localstack/default executor: localstack/default
@ -137,6 +151,7 @@ jobs:
## - localstack/startup ## - localstack/startup
- install_java17 - install_java17
- mvn_verify - mvn_verify
- check_middleware_api_versions
- mvn_jar_deploy - mvn_jar_deploy
publish_asciidoc: publish_asciidoc:

View File

@ -36,6 +36,7 @@
<module>qqq-backend-module-rdbms</module> <module>qqq-backend-module-rdbms</module>
<module>qqq-backend-module-mongodb</module> <module>qqq-backend-module-mongodb</module>
<module>qqq-language-support-javascript</module> <module>qqq-language-support-javascript</module>
<module>qqq-openapi</module>
<module>qqq-middleware-picocli</module> <module>qqq-middleware-picocli</module>
<module>qqq-middleware-javalin</module> <module>qqq-middleware-javalin</module>
<module>qqq-middleware-lambda</module> <module>qqq-middleware-lambda</module>
@ -46,7 +47,7 @@
</modules> </modules>
<properties> <properties>
<revision>0.21.0-SNAPSHOT</revision> <revision>0.23.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

View File

@ -225,7 +225,13 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
{ {
if(auditSingleInput.getSecurityKeyValues() == null || !auditSingleInput.getSecurityKeyValues().containsKey(recordSecurityLock.getSecurityKeyType())) if(auditSingleInput.getSecurityKeyValues() == null || !auditSingleInput.getSecurityKeyValues().containsKey(recordSecurityLock.getSecurityKeyType()))
{ {
throw (new QException("Missing securityKeyValue [" + recordSecurityLock.getSecurityKeyType() + "] in audit request for table " + auditSingleInput.getAuditTableName())); ///////////////////////////////////////////////////////
// originally, this case threw... //
// but i think it's better to record the audit, just //
// missing its security key value, then to fail... //
///////////////////////////////////////////////////////
// throw (new QException("Missing securityKeyValue [" + recordSecurityLock.getSecurityKeyType() + "] in audit request for table " + auditSingleInput.getAuditTableName()));
LOG.info("Missing securityKeyValue in audit request", logPair("table", auditSingleInput.getAuditTableName()), logPair("securityKey", recordSecurityLock.getSecurityKeyType()));
} }
} }
@ -272,7 +278,7 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
List<QRecord> auditDetailRecords = new ArrayList<>(); List<QRecord> auditDetailRecords = new ArrayList<>();
for(AuditSingleInput auditSingleInput : CollectionUtils.nonNullList(input.getAuditSingleInputList())) for(AuditSingleInput auditSingleInput : CollectionUtils.nonNullList(input.getAuditSingleInputList()))
{ {
Integer auditId = insertOutput.getRecords().get(i++).getValueInteger("id"); Long auditId = insertOutput.getRecords().get(i++).getValueLong("id");
if(auditId == null) if(auditId == null)
{ {
LOG.warn("Missing an id for inserted audit - so won't be able to store its child details..."); LOG.warn("Missing an id for inserted audit - so won't be able to store its child details...");

View File

@ -23,11 +23,11 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.AlertData; import com.kingsrook.qqq.backend.core.model.dashboard.widgets.AlertData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType; import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
@ -40,9 +40,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
** - alertType - name of entry in AlertType enum (ERROR, WARNING, SUCCESS) ** - alertType - name of entry in AlertType enum (ERROR, WARNING, SUCCESS)
** - alertHtml - html to display inside the alert (other than its icon) ** - alertHtml - html to display inside the alert (other than its icon)
*******************************************************************************/ *******************************************************************************/
public class ProcessAlertWidget extends AbstractWidgetRenderer implements MetaDataProducerInterface<QWidgetMetaData> public class AlertWidgetRenderer extends AbstractWidgetRenderer implements MetaDataProducerInterface<QWidgetMetaData>
{ {
public static final String NAME = "ProcessAlertWidget"; public static final String NAME = "AlertWidgetRenderer";

View File

@ -301,6 +301,9 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
} }
} }
widgetData.setAllowRecordEdit(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("allowRecordEdit"))));
widgetData.setAllowRecordDelete(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("allowRecordDelete"))));
return (new RenderWidgetOutput(widgetData)); return (new RenderWidgetOutput(widgetData));
} }
catch(Exception e) catch(Exception e)

View File

@ -0,0 +1,92 @@
/*
* 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.actions.metadata;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** a default implementation of MetaDataFilterInterface, that allows all the things
*******************************************************************************/
public class AllowAllMetaDataFilter implements MetaDataFilterInterface
{
/***************************************************************************
**
***************************************************************************/
@Override
public boolean allowTable(MetaDataInput input, QTableMetaData table)
{
return (true);
}
/***************************************************************************
**
***************************************************************************/
@Override
public boolean allowProcess(MetaDataInput input, QProcessMetaData process)
{
return (true);
}
/***************************************************************************
**
***************************************************************************/
@Override
public boolean allowReport(MetaDataInput input, QReportMetaData report)
{
return (true);
}
/***************************************************************************
**
***************************************************************************/
@Override
public boolean allowApp(MetaDataInput input, QAppMetaData app)
{
return (true);
}
/***************************************************************************
**
***************************************************************************/
@Override
public boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget)
{
return (true);
}
}

View File

@ -28,13 +28,18 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult; 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.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.actions.metadata.MetaDataInput; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode; import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
@ -49,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; 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.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
/******************************************************************************* /*******************************************************************************
@ -57,6 +63,12 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
*******************************************************************************/ *******************************************************************************/
public class MetaDataAction public class MetaDataAction
{ {
private static final QLogger LOG = QLogger.getLogger(MetaDataAction.class);
private static Memoization<QInstance, MetaDataFilterInterface> metaDataFilterMemoization = new Memoization<>();
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -64,10 +76,10 @@ public class MetaDataAction
{ {
ActionHelper.validateSession(metaDataInput); ActionHelper.validateSession(metaDataInput);
// todo pre-customization - just get to modify the request? MetaDataOutput metaDataOutput = new MetaDataOutput();
MetaDataOutput metaDataOutput = new MetaDataOutput(); Map<String, AppTreeNode> treeNodes = new LinkedHashMap<>();
Map<String, AppTreeNode> treeNodes = new LinkedHashMap<>(); MetaDataFilterInterface filter = getMetaDataFilter();
///////////////////////////////////// /////////////////////////////////////
// map tables to frontend metadata // // map tables to frontend metadata //
@ -78,6 +90,11 @@ public class MetaDataAction
String tableName = entry.getKey(); String tableName = entry.getKey();
QTableMetaData table = entry.getValue(); QTableMetaData table = entry.getValue();
if(!filter.allowTable(metaDataInput, table))
{
continue;
}
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, table); PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, table);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE)) if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{ {
@ -102,6 +119,11 @@ public class MetaDataAction
String processName = entry.getKey(); String processName = entry.getKey();
QProcessMetaData process = entry.getValue(); QProcessMetaData process = entry.getValue();
if(!filter.allowProcess(metaDataInput, process))
{
continue;
}
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, process); PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, process);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE)) if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{ {
@ -122,6 +144,11 @@ public class MetaDataAction
String reportName = entry.getKey(); String reportName = entry.getKey();
QReportMetaData report = entry.getValue(); QReportMetaData report = entry.getValue();
if(!filter.allowReport(metaDataInput, report))
{
continue;
}
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, report); PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, report);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE)) if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{ {
@ -142,6 +169,11 @@ public class MetaDataAction
String widgetName = entry.getKey(); String widgetName = entry.getKey();
QWidgetMetaDataInterface widget = entry.getValue(); QWidgetMetaDataInterface widget = entry.getValue();
if(!filter.allowWidget(metaDataInput, widget))
{
continue;
}
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, widget); PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, widget);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE)) if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{ {
@ -174,9 +206,19 @@ public class MetaDataAction
continue; continue;
} }
apps.put(appName, new QFrontendAppMetaData(app, metaDataOutput)); if(!filter.allowApp(metaDataInput, app))
treeNodes.put(appName, new AppTreeNode(app)); {
continue;
}
//////////////////////////////////////
// build the frontend-app meta-data //
//////////////////////////////////////
QFrontendAppMetaData frontendAppMetaData = new QFrontendAppMetaData(app, metaDataOutput);
/////////////////////////////////////////
// add children (if they're permitted) //
/////////////////////////////////////////
if(CollectionUtils.nullSafeHasContents(app.getChildren())) if(CollectionUtils.nullSafeHasContents(app.getChildren()))
{ {
for(QAppChildMetaData child : app.getChildren()) for(QAppChildMetaData child : app.getChildren())
@ -190,9 +232,42 @@ public class MetaDataAction
} }
} }
apps.get(appName).addChild(new AppTreeNode(child)); //////////////////////////////////////////////////////////////////////////////////////////////////////
// if the child was filtered away, so it isn't in its corresponding map, then don't include it here //
//////////////////////////////////////////////////////////////////////////////////////////////////////
if(child instanceof QTableMetaData table && !tables.containsKey(table.getName()))
{
continue;
}
if(child instanceof QProcessMetaData process && !processes.containsKey(process.getName()))
{
continue;
}
if(child instanceof QReportMetaData report && !reports.containsKey(report.getName()))
{
continue;
}
if(child instanceof QAppMetaData childApp && !apps.containsKey(childApp.getName()))
{
// continue;
}
frontendAppMetaData.addChild(new AppTreeNode(child));
} }
} }
//////////////////////////////////////////////////////////////////////////////////////////////////////
// if the app ended up having no children, then discard it //
// todo - i think this was wrong, because it didn't take into account ... something nested maybe... //
//////////////////////////////////////////////////////////////////////////////////////////////////////
if(CollectionUtils.nullSafeIsEmpty(frontendAppMetaData.getChildren()) && CollectionUtils.nullSafeIsEmpty(frontendAppMetaData.getWidgets()))
{
// LOG.debug("Discarding empty app", logPair("name", frontendAppMetaData.getName()));
// continue;
}
apps.put(appName, frontendAppMetaData);
treeNodes.put(appName, new AppTreeNode(app));
} }
metaDataOutput.setApps(apps); metaDataOutput.setApps(apps);
@ -228,6 +303,33 @@ public class MetaDataAction
/***************************************************************************
**
***************************************************************************/
private MetaDataFilterInterface getMetaDataFilter()
{
return metaDataFilterMemoization.getResult(QContext.getQInstance(), i ->
{
MetaDataFilterInterface filter = null;
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
if(metaDataFilterReference != null)
{
filter = QCodeLoader.getAdHoc(MetaDataFilterInterface.class, metaDataFilterReference);
LOG.debug("Using new meta-data filter of type: " + filter.getClass().getSimpleName());
}
if(filter == null)
{
filter = new AllowAllMetaDataFilter();
LOG.debug("Using new default (allow-all) meta-data filter");
}
return (filter);
}).orElseThrow(() -> new QRuntimeException("Error getting metaDataFilter"));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -0,0 +1,64 @@
/*
* 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.actions.metadata;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
**
*******************************************************************************/
public interface MetaDataFilterInterface
{
/***************************************************************************
**
***************************************************************************/
boolean allowTable(MetaDataInput input, QTableMetaData table);
/***************************************************************************
**
***************************************************************************/
boolean allowProcess(MetaDataInput input, QProcessMetaData process);
/***************************************************************************
**
***************************************************************************/
boolean allowReport(MetaDataInput input, QReportMetaData report);
/***************************************************************************
**
***************************************************************************/
boolean allowApp(MetaDataInput input, QAppMetaData app);
/***************************************************************************
**
***************************************************************************/
boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget);
}

View File

@ -40,6 +40,7 @@ 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.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceLambda;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
@ -257,11 +258,20 @@ public class RunBackendStepAction
{ {
runBackendStepOutput.seedFromRequest(runBackendStepInput); runBackendStepOutput.seedFromRequest(runBackendStepInput);
Class<?> codeClass = Class.forName(code.getName()); Object codeObject;
Object codeObject = codeClass.getConstructor().newInstance(); if(code instanceof QCodeReferenceLambda<?> qCodeReferenceLambda)
{
codeObject = qCodeReferenceLambda.getLambda();
}
else
{
Class<?> codeClass = Class.forName(code.getName());
codeObject = codeClass.getConstructor().newInstance();
}
if(!(codeObject instanceof BackendStep backendStepCodeObject)) if(!(codeObject instanceof BackendStep backendStepCodeObject))
{ {
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of BackendStep")); throw (new QException("The supplied codeReference [" + code + "] is not a reference to a BackendStep"));
} }
backendStepCodeObject.run(runBackendStepInput, runBackendStepOutput); backendStepCodeObject.run(runBackendStepInput, runBackendStepOutput);

View File

@ -28,6 +28,7 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.ActionHelper;
@ -58,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaD
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStateMachineStep;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration; import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
@ -79,6 +81,7 @@ public class RunProcessAction
{ {
private static final QLogger LOG = QLogger.getLogger(RunProcessAction.class); private static final QLogger LOG = QLogger.getLogger(RunProcessAction.class);
public static final String BASEPULL_KEY_VALUE = "basepullKeyValue";
public static final String BASEPULL_THIS_RUNTIME_KEY = "basepullThisRuntimeKey"; public static final String BASEPULL_THIS_RUNTIME_KEY = "basepullThisRuntimeKey";
public static final String BASEPULL_LAST_RUNTIME_KEY = "basepullLastRuntimeKey"; public static final String BASEPULL_LAST_RUNTIME_KEY = "basepullLastRuntimeKey";
public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField"; public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField";
@ -133,90 +136,11 @@ public class RunProcessAction
try try
{ {
String lastStepName = runProcessInput.getStartAfterStep(); switch(Objects.requireNonNull(process.getStepFlow(), "Process [" + process.getName() + "] has a null stepFlow."))
STEP_LOOP:
while(true)
{ {
/////////////////////////////////////////////////////////////////////////////////////////////////////// case LINEAR -> runLinearStepLoop(process, processState, stateKey, runProcessInput, runProcessOutput);
// always refresh the step list - as any step that runs can modify it (in the process state). // case STATE_MACHINE -> runStateMachineStep(runProcessInput.getStartAfterStep(), process, processState, stateKey, runProcessInput, runProcessOutput, 0);
// this is why we don't do a loop over the step list - as we'd get ConcurrentModificationExceptions. // default -> throw (new QException("Unhandled process step flow: " + process.getStepFlow()));
///////////////////////////////////////////////////////////////////////////////////////////////////////
List<QStepMetaData> stepList = getAvailableStepList(processState, process, lastStepName);
if(stepList.isEmpty())
{
break;
}
QStepMetaData step = stepList.get(0);
lastStepName = step.getName();
if(step instanceof QFrontendStepMetaData frontendStep)
{
////////////////////////////////////////////////////////////////
// Handle what to do with frontend steps, per request setting //
////////////////////////////////////////////////////////////////
switch(runProcessInput.getFrontendStepBehavior())
{
case BREAK ->
{
LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
processFrontendStepFieldDefaultValues(processState, frontendStep);
processFrontendComponents(processState, frontendStep);
processState.setNextStepName(step.getName());
break STEP_LOOP;
}
case SKIP ->
{
LOG.trace("Skipping frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
//////////////////////////////////////////////////////////////////////
// much less error prone in case this code changes in the future... //
//////////////////////////////////////////////////////////////////////
// noinspection UnnecessaryContinue
continue;
}
case FAIL ->
{
LOG.trace("Throwing error for frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
throw (new QException("Failing process at step " + step.getName() + " (as requested, to fail on frontend steps)"));
}
default -> throw new IllegalStateException("Unexpected value: " + runProcessInput.getFrontendStepBehavior());
}
}
else if(step instanceof QBackendStepMetaData backendStepMetaData)
{
///////////////////////
// Run backend steps //
///////////////////////
LOG.debug("Running backend step [" + step.getName() + "] in process [" + process.getName() + "]");
RunBackendStepOutput runBackendStepOutput = runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, process, processState);
/////////////////////////////////////////////////////////////////////////////////////////
// if the step returned an override lastStepName, use that to determine how we proceed //
/////////////////////////////////////////////////////////////////////////////////////////
if(runBackendStepOutput.getOverrideLastStepName() != null)
{
LOG.debug("Process step [" + lastStepName + "] returned an overrideLastStepName [" + runBackendStepOutput.getOverrideLastStepName() + "]!");
lastStepName = runBackendStepOutput.getOverrideLastStepName();
}
/////////////////////////////////////////////////////////////////////////////////////////////
// similarly, if the step produced an updatedFrontendStepList, propagate that data outward //
/////////////////////////////////////////////////////////////////////////////////////////////
if(runBackendStepOutput.getUpdatedFrontendStepList() != null)
{
LOG.debug("Process step [" + lastStepName + "] generated an updatedFrontendStepList [" + runBackendStepOutput.getUpdatedFrontendStepList().stream().map(s -> s.getName()).toList() + "]!");
runProcessOutput.setUpdatedFrontendStepList(runBackendStepOutput.getUpdatedFrontendStepList());
}
}
else
{
//////////////////////////////////////////////////
// in case we have a different step type, throw //
//////////////////////////////////////////////////
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
}
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -258,6 +182,270 @@ public class RunProcessAction
/***************************************************************************
**
***************************************************************************/
private void runLinearStepLoop(QProcessMetaData process, ProcessState processState, UUIDAndTypeStateKey stateKey, RunProcessInput runProcessInput, RunProcessOutput runProcessOutput) throws Exception
{
String lastStepName = runProcessInput.getStartAfterStep();
while(true)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////
// always refresh the step list - as any step that runs can modify it (in the process state). //
// this is why we don't do a loop over the step list - as we'd get ConcurrentModificationExceptions. //
///////////////////////////////////////////////////////////////////////////////////////////////////////
List<QStepMetaData> stepList = getAvailableStepList(processState, process, lastStepName);
if(stepList.isEmpty())
{
break;
}
QStepMetaData step = stepList.get(0);
lastStepName = step.getName();
if(step instanceof QFrontendStepMetaData frontendStep)
{
LoopTodo loopTodo = prepareForFrontendStep(runProcessInput, process, frontendStep, processState);
if(loopTodo == LoopTodo.BREAK)
{
break;
}
}
else if(step instanceof QBackendStepMetaData backendStepMetaData)
{
RunBackendStepOutput runBackendStepOutput = runBackendStep(process, processState, stateKey, runProcessInput, runProcessOutput, backendStepMetaData, step);
/////////////////////////////////////////////////////////////////////////////////////////
// if the step returned an override lastStepName, use that to determine how we proceed //
/////////////////////////////////////////////////////////////////////////////////////////
if(runBackendStepOutput.getOverrideLastStepName() != null)
{
LOG.debug("Process step [" + lastStepName + "] returned an overrideLastStepName [" + runBackendStepOutput.getOverrideLastStepName() + "]!");
lastStepName = runBackendStepOutput.getOverrideLastStepName();
}
}
else
{
//////////////////////////////////////////////////
// in case we have a different step type, throw //
//////////////////////////////////////////////////
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
}
}
}
/***************************************************************************
**
***************************************************************************/
private enum LoopTodo
{
BREAK,
CONTINUE
}
/***************************************************************************
**
***************************************************************************/
private LoopTodo prepareForFrontendStep(RunProcessInput runProcessInput, QProcessMetaData process, QFrontendStepMetaData step, ProcessState processState) throws QException
{
////////////////////////////////////////////////////////////////
// Handle what to do with frontend steps, per request setting //
////////////////////////////////////////////////////////////////
switch(runProcessInput.getFrontendStepBehavior())
{
case BREAK ->
{
LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
processFrontendStepFieldDefaultValues(processState, step);
processFrontendComponents(processState, step);
processState.setNextStepName(step.getName());
return LoopTodo.BREAK;
}
case SKIP ->
{
LOG.trace("Skipping frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
return LoopTodo.CONTINUE;
}
case FAIL ->
{
LOG.trace("Throwing error for frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
throw (new QException("Failing process at step " + step.getName() + " (as requested, to fail on frontend steps)"));
}
default -> throw new IllegalStateException("Unexpected value: " + runProcessInput.getFrontendStepBehavior());
}
}
/***************************************************************************
**
***************************************************************************/
private void runStateMachineStep(String lastStepName, QProcessMetaData process, ProcessState processState, UUIDAndTypeStateKey stateKey, RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, int stackDepth) throws Exception
{
//////////////////////////////
// check for stack-overflow //
//////////////////////////////
Integer maxStateMachineProcessStepFlowStackDepth = Objects.requireNonNullElse(runProcessInput.getValueInteger("maxStateMachineProcessStepFlowStackDepth"), 20);
if(stackDepth > maxStateMachineProcessStepFlowStackDepth)
{
throw (new QException("StateMachine process recurred too many times (exceeded maxStateMachineProcessStepFlowStackDepth of " + maxStateMachineProcessStepFlowStackDepth + ")"));
}
//////////////////////////////////
// figure out what step to run: //
//////////////////////////////////
QStepMetaData step = null;
if(!StringUtils.hasContent(lastStepName))
{
////////////////////////////////////////////////////////////////////
// if no lastStepName is given, start at the process's first step //
////////////////////////////////////////////////////////////////////
if(CollectionUtils.nullSafeIsEmpty(process.getStepList()))
{
throw (new QException("Process [" + process.getName() + "] does not have a step list defined."));
}
step = process.getStepList().get(0);
}
else
{
/////////////////////////////////////
// else run the given lastStepName //
/////////////////////////////////////
processState.clearNextStepName();
step = process.getStep(lastStepName);
if(step == null)
{
throw (new QException("Could not find step by name [" + lastStepName + "]"));
}
}
/////////////////////////////////////////////////////////////////////////
// for the flow of: //
// we were on a frontend step (as a sub-step of a state machine step), //
// and now we're here to run that state-step's backend step - //
// find the state-machine step containing this frontend step. //
/////////////////////////////////////////////////////////////////////////
String skipSubStepsUntil = null;
if(step instanceof QFrontendStepMetaData frontendStepMetaData)
{
QStateMachineStep stateMachineStep = getStateMachineStepContainingSubStep(process, frontendStepMetaData.getName());
if(stateMachineStep == null)
{
throw (new QException("Could not find stateMachineStep that contains last-frontend step: " + frontendStepMetaData.getName()));
}
step = stateMachineStep;
//////////////////////////////////////////////////////////////////////////////////
// set this flag, to know to skip this frontend step in the sub-step loop below //
//////////////////////////////////////////////////////////////////////////////////
skipSubStepsUntil = frontendStepMetaData.getName();
}
if(!(step instanceof QStateMachineStep stateMachineStep))
{
throw (new QException("Have a non-stateMachineStep in a process using stateMachine flow... " + step.getClass().getName()));
}
///////////////////////
// run the sub-steps //
///////////////////////
boolean ranAnySubSteps = false;
for(QStepMetaData subStep : stateMachineStep.getSubSteps())
{
///////////////////////////////////////////////////////////////////////////////////////////////
// ok, well, skip them if this flag is set (and clear the flag once we've hit this sub-step) //
///////////////////////////////////////////////////////////////////////////////////////////////
if(skipSubStepsUntil != null)
{
if(skipSubStepsUntil.equals(subStep.getName()))
{
skipSubStepsUntil = null;
}
continue;
}
ranAnySubSteps = true;
if(subStep instanceof QFrontendStepMetaData frontendStep)
{
LoopTodo loopTodo = prepareForFrontendStep(runProcessInput, process, frontendStep, processState);
if(loopTodo == LoopTodo.BREAK)
{
return;
}
}
else if(subStep instanceof QBackendStepMetaData backendStepMetaData)
{
RunBackendStepOutput runBackendStepOutput = runBackendStep(process, processState, stateKey, runProcessInput, runProcessOutput, backendStepMetaData, step);
Optional<String> nextStepName = runBackendStepOutput.getProcessState().getNextStepName();
if(nextStepName.isEmpty() && StringUtils.hasContent(stateMachineStep.getDefaultNextStepName()))
{
nextStepName = Optional.of(stateMachineStep.getDefaultNextStepName());
}
if(nextStepName.isPresent())
{
//////////////////////////////////////////////////////////////////////////////////////////////////////
// if we've been given a next-step-name, go to that step now. //
// it might be a backend-only stateMachineStep, in which case, we should run that backend step now. //
// or it might be a frontend-then-backend step, in which case, we want to go to that frontend step. //
// if we weren't given a next-step-name, then we should stay in the same state - either to finish //
// its sub-steps, or, to fall out of the loop and end the process. //
//////////////////////////////////////////////////////////////////////////////////////////////////////
processState.clearNextStepName();
runStateMachineStep(nextStepName.get(), process, processState, stateKey, runProcessInput, runProcessOutput, stackDepth + 1);
return;
}
}
else
{
//////////////////////////////////////////////////
// in case we have a different step type, throw //
//////////////////////////////////////////////////
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
}
}
if(!ranAnySubSteps)
{
if(StringUtils.hasContent(stateMachineStep.getDefaultNextStepName()))
{
runStateMachineStep(stateMachineStep.getDefaultNextStepName(), process, processState, stateKey, runProcessInput, runProcessOutput, stackDepth + 1);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
public QStateMachineStep getStateMachineStepContainingSubStep(QProcessMetaData process, String stepName)
{
for(QStepMetaData step : process.getAllSteps().values())
{
if(step instanceof QStateMachineStep stateMachineStep)
{
for(QStepMetaData subStep : stateMachineStep.getSubSteps())
{
if(subStep.getName().equals(stepName))
{
return (stateMachineStep);
}
}
}
}
return (null);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -335,12 +523,12 @@ public class RunProcessAction
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
runProcessInput.seedFromProcessState(optionalProcessState.get()); runProcessInput.seedFromProcessState(optionalProcessState.get());
/////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////
// if we're restoring an old state, we can discard a previously stored updatedFrontendStepList - // // if we're restoring an old state, we can discard a previously stored processMetaDataAdjustment - //
// it is only needed on the transitional edge from a backend-step to a frontend step, but not // // it is only needed on the transitional edge from a backend-step to a frontend step, but not //
// in the other directly // // in the other directly //
/////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////
optionalProcessState.get().setUpdatedFrontendStepList(null); optionalProcessState.get().setProcessMetaDataAdjustment(null);
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// if there were values from the caller, put those (back) in the request // // if there were values from the caller, put those (back) in the request //
@ -355,16 +543,40 @@ public class RunProcessAction
} }
ProcessState processState = optionalProcessState.get(); ProcessState processState = optionalProcessState.get();
processState.clearNextStepName();
return processState; return processState;
} }
/***************************************************************************
**
***************************************************************************/
private RunBackendStepOutput runBackendStep(QProcessMetaData process, ProcessState processState, UUIDAndTypeStateKey stateKey, RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, QBackendStepMetaData backendStepMetaData, QStepMetaData step) throws Exception
{
///////////////////////
// Run backend steps //
///////////////////////
LOG.debug("Running backend step [" + step.getName() + "] in process [" + process.getName() + "]");
RunBackendStepOutput runBackendStepOutput = runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, process, processState);
//////////////////////////////////////////////////////////////////////////////////////////////
// similarly, if the step produced a processMetaDataAdjustment, propagate that data outward //
//////////////////////////////////////////////////////////////////////////////////////////////
if(runBackendStepOutput.getProcessMetaDataAdjustment() != null)
{
LOG.debug("Process step [" + step.getName() + "] generated a ProcessMetaDataAdjustment [" + runBackendStepOutput.getProcessMetaDataAdjustment() + "]!");
runProcessOutput.setProcessMetaDataAdjustment(runBackendStepOutput.getProcessMetaDataAdjustment());
}
return runBackendStepOutput;
}
/******************************************************************************* /*******************************************************************************
** Run a single backend step. ** Run a single backend step.
*******************************************************************************/ *******************************************************************************/
protected RunBackendStepOutput runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception RunBackendStepOutput runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception
{ {
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(processState); RunBackendStepInput runBackendStepInput = new RunBackendStepInput(processState);
runBackendStepInput.setProcessName(process.getName()); runBackendStepInput.setProcessName(process.getName());
@ -517,9 +729,13 @@ public class RunProcessAction
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
protected String determineBasepullKeyValue(QProcessMetaData process, BasepullConfiguration basepullConfiguration) throws QException protected String determineBasepullKeyValue(QProcessMetaData process, RunProcessInput runProcessInput, BasepullConfiguration basepullConfiguration) throws QException
{ {
String basepullKeyValue = (basepullConfiguration.getKeyValue() != null) ? basepullConfiguration.getKeyValue() : process.getName(); String basepullKeyValue = (basepullConfiguration.getKeyValue() != null) ? basepullConfiguration.getKeyValue() : process.getName();
if(runProcessInput.getValueString(BASEPULL_KEY_VALUE) != null)
{
basepullKeyValue = runProcessInput.getValueString(BASEPULL_KEY_VALUE);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if process specifies that it uses variants, look for that data in the session and append to our basepull key // // if process specifies that it uses variants, look for that data in the session and append to our basepull key //
@ -551,7 +767,7 @@ public class RunProcessAction
String basepullTableName = basepullConfiguration.getTableName(); String basepullTableName = basepullConfiguration.getTableName();
String basepullKeyFieldName = basepullConfiguration.getKeyField(); String basepullKeyFieldName = basepullConfiguration.getKeyField();
String basepullLastRunTimeFieldName = basepullConfiguration.getLastRunTimeFieldName(); String basepullLastRunTimeFieldName = basepullConfiguration.getLastRunTimeFieldName();
String basepullKeyValue = determineBasepullKeyValue(process, basepullConfiguration); String basepullKeyValue = determineBasepullKeyValue(process, runProcessInput, basepullConfiguration);
/////////////////////////////////////// ///////////////////////////////////////
// get the stored basepull timestamp // // get the stored basepull timestamp //
@ -631,7 +847,7 @@ public class RunProcessAction
String basepullKeyFieldName = basepullConfiguration.getKeyField(); String basepullKeyFieldName = basepullConfiguration.getKeyField();
String basepullLastRunTimeFieldName = basepullConfiguration.getLastRunTimeFieldName(); String basepullLastRunTimeFieldName = basepullConfiguration.getLastRunTimeFieldName();
Integer basepullHoursBackForInitialTimestamp = basepullConfiguration.getHoursBackForInitialTimestamp(); Integer basepullHoursBackForInitialTimestamp = basepullConfiguration.getHoursBackForInitialTimestamp();
String basepullKeyValue = determineBasepullKeyValue(process, basepullConfiguration); String basepullKeyValue = determineBasepullKeyValue(process, runProcessInput, basepullConfiguration);
/////////////////////////////////////// ///////////////////////////////////////
// get the stored basepull timestamp // // get the stored basepull timestamp //

View File

@ -63,6 +63,8 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint; import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; 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.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.CriteriaMissingInputValueBehavior;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.FilterUseCase;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext; import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; 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.QQueryFilter;
@ -614,7 +616,56 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
return; return;
} }
queryFilter.interpretValues(reportInput.getInputValues()); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for reports defined in meta-data, the established rule is, that missing input variable values are discarded. //
// but for non-meta-data reports (e.g., user-saved), we expect an exception for missing values. //
// so, set those use-cases up. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FilterUseCase filterUseCase;
if(StringUtils.hasContent(reportInput.getReportName()) && QContext.getQInstance().getReport(reportInput.getReportName()) != null)
{
filterUseCase = new ReportFromMetaDataFilterUseCase();
}
else
{
filterUseCase = new ReportNotFromMetaDataFilterUseCase();
}
queryFilter.interpretValues(reportInput.getInputValues(), filterUseCase);
}
/***************************************************************************
**
***************************************************************************/
private static class ReportFromMetaDataFilterUseCase implements FilterUseCase
{
/***************************************************************************
**
***************************************************************************/
@Override
public CriteriaMissingInputValueBehavior getDefaultCriteriaMissingInputValueBehavior()
{
return CriteriaMissingInputValueBehavior.REMOVE_FROM_FILTER;
}
}
/***************************************************************************
**
***************************************************************************/
private static class ReportNotFromMetaDataFilterUseCase implements FilterUseCase
{
/***************************************************************************
**
***************************************************************************/
@Override
public CriteriaMissingInputValueBehavior getDefaultCriteriaMissingInputValueBehavior()
{
return CriteriaMissingInputValueBehavior.THROW_EXCEPTION;
}
} }

View File

@ -54,6 +54,7 @@ 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.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; 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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
@ -266,6 +267,22 @@ public class QueryAction
/*******************************************************************************
** shorthand way to call for the most common use-case, when you just want the
** entities to be returned, and you just want to pass in a table name and filter.
*******************************************************************************/
public static <T extends QRecordEntity> List<T> execute(String tableName, Class<T> entityClass, QQueryFilter filter) throws QException
{
QueryAction queryAction = new QueryAction();
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
queryInput.setFilter(filter);
QueryOutput queryOutput = queryAction.execute(queryInput);
return (queryOutput.getRecordEntities(entityClass));
}
/******************************************************************************* /*******************************************************************************
** shorthand way to call for the most common use-case, when you just want the ** shorthand way to call for the most common use-case, when you just want the
** records to be returned, and you just want to pass in a table name and filter. ** records to be returned, and you just want to pass in a table name and filter.

View File

@ -28,7 +28,6 @@ import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -468,7 +467,8 @@ public class QValueFormatter
{ {
for(QFieldMetaData field : table.getFields().values()) for(QFieldMetaData field : table.getFields().values())
{ {
if(field.getType().equals(QFieldType.BLOB)) Optional<FieldAdornment> fileDownloadAdornment = field.getAdornment(AdornmentType.FILE_DOWNLOAD);
if(fileDownloadAdornment.isPresent())
{ {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// file name comes from: // // file name comes from: //
@ -478,20 +478,7 @@ public class QValueFormatter
// - tableLabel primaryKey fieldLabel // // - tableLabel primaryKey fieldLabel //
// - and - if the FILE_DOWNLOAD adornment had a DEFAULT_EXTENSION, then it gets added (preceded by a dot) // // - and - if the FILE_DOWNLOAD adornment had a DEFAULT_EXTENSION, then it gets added (preceded by a dot) //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Optional<FieldAdornment> fileDownloadAdornment = field.getAdornment(AdornmentType.FILE_DOWNLOAD); Map<String, Serializable> adornmentValues = fileDownloadAdornment.get().getValues();
Map<String, Serializable> adornmentValues = Collections.emptyMap();
if(fileDownloadAdornment.isPresent())
{
adornmentValues = fileDownloadAdornment.get().getValues();
}
else
{
///////////////////////////////////////////////////////
// don't change blobs unless they are file-downloads //
///////////////////////////////////////////////////////
continue;
}
String fileNameField = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FIELD)); String fileNameField = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FIELD));
String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT)); String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT));
@ -542,7 +529,13 @@ public class QValueFormatter
} }
} }
record.setValue(field.getName(), "/data/" + table.getName() + "/" + primaryKey + "/" + field.getName() + "/" + fileName); /////////////////////////////////////////////
// if field type is blob, update its value //
/////////////////////////////////////////////
if(QFieldType.BLOB.equals(field.getType()))
{
record.setValue(field.getName(), "/data/" + table.getName() + "/" + primaryKey + "/" + field.getName() + "/" + fileName);
}
record.setDisplayValue(field.getName(), fileName); record.setDisplayValue(field.getName(), fileName);
} }
} }

View File

@ -260,9 +260,6 @@ public class SearchPossibleValueSourceAction
} }
} }
// todo - skip & limit as params
queryFilter.setLimit(250);
/////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////
// if given a default filter, make it the 'top level' filter and the one we just created a subfilter // // if given a default filter, make it the 'top level' filter and the one we just created a subfilter //
/////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////
@ -272,6 +269,9 @@ public class SearchPossibleValueSourceAction
queryFilter = input.getDefaultQueryFilter(); queryFilter = input.getDefaultQueryFilter();
} }
// todo - skip & limit as params
queryFilter.setLimit(250);
queryFilter.setOrderBys(possibleValueSource.getOrderByFields()); queryFilter.setOrderBys(possibleValueSource.getOrderByFields());
queryInput.setFilter(queryFilter); queryInput.setFilter(queryFilter);

View File

@ -0,0 +1,55 @@
/*
* 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.instances;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Version of AbstractQQQApplication that assumes all meta-data is produced
** by MetaDataProducers in a single package.
*******************************************************************************/
public abstract class AbstractMetaDataProducerBasedQQQApplication extends AbstractQQQApplication
{
/***************************************************************************
**
***************************************************************************/
public abstract String getMetaDataPackageName();
/***************************************************************************
**
***************************************************************************/
@Override
public QInstance defineQInstance() throws QException
{
QInstance qInstance = new QInstance();
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, getMetaDataPackageName());
return (qInstance);
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.instances;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
** Base class to provide the definition of a QQQ-based application.
**
** Essentially, just how to define its meta-data - in the form of a QInstance.
**
** Also provides means to define the instance validation plugins to be used.
*******************************************************************************/
public abstract class AbstractQQQApplication
{
/***************************************************************************
**
***************************************************************************/
public abstract QInstance defineQInstance() throws QException;
/***************************************************************************
**
***************************************************************************/
public QInstance defineValidatedQInstance() throws QException, QInstanceValidationException
{
QInstance qInstance = defineQInstance();
QInstanceValidator.removeAllValidatorPlugins();
for(QInstanceValidatorPluginInterface<?> validatorPlugin : CollectionUtils.nonNullList(getValidatorPlugins()))
{
QInstanceValidator.addValidatorPlugin(validatorPlugin);
}
QInstanceValidator qInstanceValidator = new QInstanceValidator();
qInstanceValidator.validate(qInstance);
return (qInstance);
}
/***************************************************************************
**
***************************************************************************/
protected List<QInstanceValidatorPluginInterface<?>> getValidatorPlugins()
{
return new ArrayList<>();
}
}

View File

@ -58,6 +58,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStateMachineStep;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QSupplementalProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QSupplementalProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource; import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
@ -410,10 +411,27 @@ public class QInstanceEnricher
** **
*******************************************************************************/ *******************************************************************************/
private void enrichStep(QStepMetaData step) private void enrichStep(QStepMetaData step)
{
enrichStep(step, false);
}
/***************************************************************************
**
***************************************************************************/
private void enrichStep(QStepMetaData step, boolean isSubStep)
{ {
if(!StringUtils.hasContent(step.getLabel())) if(!StringUtils.hasContent(step.getLabel()))
{ {
step.setLabel(nameToLabel(step.getName())); if(isSubStep && (step.getName().endsWith(".backend") || step.getName().endsWith(".frontend")))
{
step.setLabel(nameToLabel(step.getName().replaceFirst("\\.(backend|frontend)", "")));
}
else
{
step.setLabel(nameToLabel(step.getName()));
}
} }
step.getInputFields().forEach(this::enrichField); step.getInputFields().forEach(this::enrichField);
@ -434,6 +452,13 @@ public class QInstanceEnricher
frontendStepMetaData.getRecordListFields().forEach(this::enrichField); frontendStepMetaData.getRecordListFields().forEach(this::enrichField);
} }
} }
else if(step instanceof QStateMachineStep stateMachineStep)
{
for(QStepMetaData subStep : CollectionUtils.nonNullList(stateMachineStep.getSubSteps()))
{
enrichStep(subStep, true);
}
}
} }

View File

@ -43,6 +43,7 @@ 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.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph; import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataFilterInterface;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface; import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface; import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
@ -74,6 +75,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; 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.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
@ -185,6 +187,7 @@ public class QInstanceValidator
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
try try
{ {
validateInstanceAttributes(qInstance);
validateBackends(qInstance); validateBackends(qInstance);
validateAuthentication(qInstance); validateAuthentication(qInstance);
validateAutomationProviders(qInstance); validateAutomationProviders(qInstance);
@ -225,6 +228,19 @@ public class QInstanceValidator
/***************************************************************************
**
***************************************************************************/
private void validateInstanceAttributes(QInstance qInstance)
{
if(qInstance.getMetaDataFilter() != null)
{
validateSimpleCodeReference("Instance metaDataFilter ", qInstance.getMetaDataFilter(), MetaDataFilterInterface.class);
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -928,13 +944,8 @@ public class QInstanceValidator
assertCondition(Objects.equals(fieldName, field.getName()), assertCondition(Objects.equals(fieldName, field.getName()),
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + "."); "Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
if(field.getPossibleValueSourceName() != null)
{
assertCondition(qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
}
String prefix = "Field " + fieldName + " in table " + tableName + " "; String prefix = "Field " + fieldName + " in table " + tableName + " ";
validateFieldPossibleValueSourceAttributes(qInstance, field, prefix);
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
// validate things we know about field behaviors // // validate things we know about field behaviors //
@ -1039,6 +1050,31 @@ public class QInstanceValidator
/***************************************************************************
**
***************************************************************************/
private void validateFieldPossibleValueSourceAttributes(QInstance qInstance, QFieldMetaData field, String prefix)
{
if(field.getPossibleValueSourceName() != null)
{
assertCondition(qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
prefix + "has an unrecognized possibleValueSourceName " + field.getPossibleValueSourceName());
assertCondition(field.getInlinePossibleValueSource() == null, prefix.trim() + " has both a possibleValueSourceName and an inlinePossibleValueSource, which is not allowed.");
}
if(field.getInlinePossibleValueSource() != null)
{
String name = "inlinePossibleValueSource for " + prefix.trim();
if(assertCondition(QPossibleValueSourceType.ENUM.equals(field.getInlinePossibleValueSource().getType()), name + " must have a type of ENUM."))
{
validatePossibleValueSource(qInstance, name, field.getInlinePossibleValueSource());
}
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -1546,6 +1582,16 @@ public class QInstanceValidator
} }
} }
for(QFieldMetaData field : process.getInputFields())
{
validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", input field " + field.getName());
}
for(QFieldMetaData field : process.getOutputFields())
{
validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", output field " + field.getName());
}
if(process.getCancelStep() != null) if(process.getCancelStep() != null)
{ {
if(assertCondition(process.getCancelStep().getCode() != null, "Cancel step is missing a code reference, in process " + processName)) if(assertCondition(process.getCancelStep().getCode() != null, "Cancel step is missing a code reference, in process " + processName))
@ -1948,78 +1994,88 @@ public class QInstanceValidator
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) -> qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
{ {
assertCondition(Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + "."); assertCondition(Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
if(assertCondition(possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName)) validatePossibleValueSource(qInstance, pvsName, possibleValueSource);
});
}
}
/***************************************************************************
**
***************************************************************************/
private void validatePossibleValueSource(QInstance qInstance, String name, QPossibleValueSource possibleValueSource)
{
if(assertCondition(possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + name))
{
////////////////////////////////////////////////////////////////////////////////////////////////
// assert about fields that should and should not be set, based on possible value source type //
// do additional type-specific validations as well //
////////////////////////////////////////////////////////////////////////////////////////////////
switch(possibleValueSource.getType())
{
case ENUM ->
{ {
//////////////////////////////////////////////////////////////////////////////////////////////// assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + name + " should not have a tableName.");
// assert about fields that should and should not be set, based on possible value source type // assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "enum-type possibleValueSource " + name + " should not have searchFields.");
// do additional type-specific validations as well // assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "enum-type possibleValueSource " + name + " should not have orderByFields.");
//////////////////////////////////////////////////////////////////////////////////////////////// assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + name + " should not have a customCodeReference.");
switch(possibleValueSource.getType())
assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + name + " is missing enum values");
}
case TABLE ->
{
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + name + " should not have enum values.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + name + " should not have a customCodeReference.");
QTableMetaData tableMetaData = null;
if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + name + " is missing a tableName."))
{ {
case ENUM -> tableMetaData = qInstance.getTable(possibleValueSource.getTableName());
{ assertCondition(tableMetaData != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + name + ".");
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "enum-type possibleValueSource " + pvsName + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "enum-type possibleValueSource " + pvsName + " should not have orderByFields.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values");
}
case TABLE ->
{
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
QTableMetaData tableMetaData = null;
if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName."))
{
tableMetaData = qInstance.getTable(possibleValueSource.getTableName());
assertCondition(tableMetaData != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + ".");
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "table-type possibleValueSource " + pvsName + " is missing searchFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(String searchField : possibleValueSource.getSearchFields())
{
assertNoException(() -> finalTableMetaData.getField(searchField), "possibleValueSource " + pvsName + " has an unrecognized searchField: " + searchField);
}
}
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "table-type possibleValueSource " + pvsName + " is missing orderByFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(QFilterOrderBy orderByField : possibleValueSource.getOrderByFields())
{
assertNoException(() -> finalTableMetaData.getField(orderByField.getFieldName()), "possibleValueSource " + pvsName + " has an unrecognized orderByField: " + orderByField.getFieldName());
}
}
}
}
case CUSTOM ->
{
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values.");
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "custom-type possibleValueSource " + pvsName + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "custom-type possibleValueSource " + pvsName + " should not have orderByFields.");
if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
{
validateSimpleCodeReference("PossibleValueSource " + pvsName + " custom code reference: ", possibleValueSource.getCustomCodeReference(), QCustomPossibleValueProvider.class);
}
}
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
} }
runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance); if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "table-type possibleValueSource " + name + " is missing searchFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(String searchField : possibleValueSource.getSearchFields())
{
assertNoException(() -> finalTableMetaData.getField(searchField), "possibleValueSource " + name + " has an unrecognized searchField: " + searchField);
}
}
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "table-type possibleValueSource " + name + " is missing orderByFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(QFilterOrderBy orderByField : possibleValueSource.getOrderByFields())
{
assertNoException(() -> finalTableMetaData.getField(orderByField.getFieldName()), "possibleValueSource " + name + " has an unrecognized orderByField: " + orderByField.getFieldName());
}
}
}
} }
}); case CUSTOM ->
{
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + name + " should not have enum values.");
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + name + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "custom-type possibleValueSource " + name + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "custom-type possibleValueSource " + name + " should not have orderByFields.");
if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + name + " is missing a customCodeReference."))
{
validateSimpleCodeReference("PossibleValueSource " + name + " custom code reference: ", possibleValueSource.getCustomCodeReference(), QCustomPossibleValueProvider.class);
}
}
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
}
runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance);
} }
} }

View File

@ -31,6 +31,16 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
*******************************************************************************/ *******************************************************************************/
public class MetaDataInput extends AbstractActionInput public class MetaDataInput extends AbstractActionInput
{ {
private String frontendName;
private String frontendVersion;
private String middlewareName;
private String middlewareVersion;
private String applicationName;
private String applicationVersion;
/******************************************************************************* /*******************************************************************************
** **
@ -39,4 +49,190 @@ public class MetaDataInput extends AbstractActionInput
{ {
} }
/*******************************************************************************
** Getter for frontendName
*******************************************************************************/
public String getFrontendName()
{
return (this.frontendName);
}
/*******************************************************************************
** Setter for frontendName
*******************************************************************************/
public void setFrontendName(String frontendName)
{
this.frontendName = frontendName;
}
/*******************************************************************************
** Fluent setter for frontendName
*******************************************************************************/
public MetaDataInput withFrontendName(String frontendName)
{
this.frontendName = frontendName;
return (this);
}
/*******************************************************************************
** Getter for frontendVersion
*******************************************************************************/
public String getFrontendVersion()
{
return (this.frontendVersion);
}
/*******************************************************************************
** Setter for frontendVersion
*******************************************************************************/
public void setFrontendVersion(String frontendVersion)
{
this.frontendVersion = frontendVersion;
}
/*******************************************************************************
** Fluent setter for frontendVersion
*******************************************************************************/
public MetaDataInput withFrontendVersion(String frontendVersion)
{
this.frontendVersion = frontendVersion;
return (this);
}
/*******************************************************************************
** Getter for middlewareName
*******************************************************************************/
public String getMiddlewareName()
{
return (this.middlewareName);
}
/*******************************************************************************
** Setter for middlewareName
*******************************************************************************/
public void setMiddlewareName(String middlewareName)
{
this.middlewareName = middlewareName;
}
/*******************************************************************************
** Fluent setter for middlewareName
*******************************************************************************/
public MetaDataInput withMiddlewareName(String middlewareName)
{
this.middlewareName = middlewareName;
return (this);
}
/*******************************************************************************
** Getter for middlewareVersion
*******************************************************************************/
public String getMiddlewareVersion()
{
return (this.middlewareVersion);
}
/*******************************************************************************
** Setter for middlewareVersion
*******************************************************************************/
public void setMiddlewareVersion(String middlewareVersion)
{
this.middlewareVersion = middlewareVersion;
}
/*******************************************************************************
** Fluent setter for middlewareVersion
*******************************************************************************/
public MetaDataInput withMiddlewareVersion(String middlewareVersion)
{
this.middlewareVersion = middlewareVersion;
return (this);
}
/*******************************************************************************
** Getter for applicationName
*******************************************************************************/
public String getApplicationName()
{
return (this.applicationName);
}
/*******************************************************************************
** Setter for applicationName
*******************************************************************************/
public void setApplicationName(String applicationName)
{
this.applicationName = applicationName;
}
/*******************************************************************************
** Fluent setter for applicationName
*******************************************************************************/
public MetaDataInput withApplicationName(String applicationName)
{
this.applicationName = applicationName;
return (this);
}
/*******************************************************************************
** Getter for applicationVersion
*******************************************************************************/
public String getApplicationVersion()
{
return (this.applicationVersion);
}
/*******************************************************************************
** Setter for applicationVersion
*******************************************************************************/
public void setApplicationVersion(String applicationVersion)
{
this.applicationVersion = applicationVersion;
}
/*******************************************************************************
** Fluent setter for applicationVersion
*******************************************************************************/
public MetaDataInput withApplicationVersion(String applicationVersion)
{
this.applicationVersion = applicationVersion;
return (this);
}
} }

View File

@ -0,0 +1,139 @@
/*
* 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.actions.processes;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** Object that stores adjustments that a process wants to make, at run-time,
** to its meta-data.
**
** e.g., changing the steps; updating fields (e.g., changing an inline PVS,
** or an isRequired attribute)
*******************************************************************************/
public class ProcessMetaDataAdjustment
{
private static final QLogger LOG = QLogger.getLogger(ProcessMetaDataAdjustment.class);
private List<QFrontendStepMetaData> updatedFrontendStepList = null;
private Map<String, QFieldMetaData> updatedFields = null;
/*******************************************************************************
**
*******************************************************************************/
public ProcessMetaDataAdjustment withUpdatedField(QFieldMetaData field)
{
if(updatedFields == null)
{
updatedFields = new LinkedHashMap<>();
}
if(!StringUtils.hasContent(field.getName()))
{
LOG.warn("Missing name on field in withUpdatedField - no update will happen.");
}
else
{
if(updatedFields.containsKey(field.getName()))
{
LOG.info("UpdatedFields map already contained a field with this name - overwriting it.", logPair("fieldName", field.getName()));
}
updatedFields.put(field.getName(), field);
}
return (this);
}
/*******************************************************************************
** Getter for updatedFrontendStepList
*******************************************************************************/
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
{
return (this.updatedFrontendStepList);
}
/*******************************************************************************
** Setter for updatedFrontendStepList
*******************************************************************************/
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{
this.updatedFrontendStepList = updatedFrontendStepList;
}
/*******************************************************************************
** Fluent setter for updatedFrontendStepList
*******************************************************************************/
public ProcessMetaDataAdjustment withUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{
this.updatedFrontendStepList = updatedFrontendStepList;
return (this);
}
/*******************************************************************************
** Getter for updatedFields
*******************************************************************************/
public Map<String, QFieldMetaData> getUpdatedFields()
{
return (this.updatedFields);
}
/*******************************************************************************
** Setter for updatedFields
*******************************************************************************/
public void setUpdatedFields(Map<String, QFieldMetaData> updatedFields)
{
this.updatedFields = updatedFields;
}
/*******************************************************************************
** Fluent setter for updatedFields
*******************************************************************************/
public ProcessMetaDataAdjustment withUpdatedFields(Map<String, QFieldMetaData> updatedFields)
{
this.updatedFields = updatedFields;
return (this);
}
}

View File

@ -29,7 +29,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
/******************************************************************************* /*******************************************************************************
@ -42,10 +41,7 @@ public class ProcessState implements Serializable
private List<String> stepList = new ArrayList<>(); private List<String> stepList = new ArrayList<>();
private Optional<String> nextStepName = Optional.empty(); private Optional<String> nextStepName = Optional.empty();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// private ProcessMetaDataAdjustment processMetaDataAdjustment = null;
// maybe, remove this altogether - just let the frontend compute & send if needed... but how does it know last version...? //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private List<QFrontendStepMetaData> updatedFrontendStepList = null;
@ -148,33 +144,36 @@ public class ProcessState implements Serializable
/******************************************************************************* /*******************************************************************************
** Getter for updatedFrontendStepList ** Getter for processMetaDataAdjustment
*******************************************************************************/ *******************************************************************************/
public List<QFrontendStepMetaData> getUpdatedFrontendStepList() public ProcessMetaDataAdjustment getProcessMetaDataAdjustment()
{ {
return (this.updatedFrontendStepList); return (this.processMetaDataAdjustment);
} }
/******************************************************************************* /*******************************************************************************
** Setter for updatedFrontendStepList ** Setter for processMetaDataAdjustment
*******************************************************************************/ *******************************************************************************/
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList) public void setProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment)
{ {
this.updatedFrontendStepList = updatedFrontendStepList; this.processMetaDataAdjustment = processMetaDataAdjustment;
} }
/******************************************************************************* /*******************************************************************************
** Fluent setter for updatedFrontendStepList ** Fluent setter for processMetaDataAdjustment
*******************************************************************************/ *******************************************************************************/
public ProcessState withUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList) public ProcessState withProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment)
{ {
this.updatedFrontendStepList = updatedFrontendStepList; this.processMetaDataAdjustment = processMetaDataAdjustment;
return (this); return (this);
} }
} }

View File

@ -374,7 +374,13 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
.map(step -> (QFrontendStepMetaData) step) .map(step -> (QFrontendStepMetaData) step)
.toList()); .toList());
setUpdatedFrontendStepList(updatedFrontendStepList); ProcessMetaDataAdjustment processMetaDataAdjustment = getProcessMetaDataAdjustment();
if(processMetaDataAdjustment == null)
{
processMetaDataAdjustment = new ProcessMetaDataAdjustment();
}
processMetaDataAdjustment.setUpdatedFrontendStepList(updatedFrontendStepList);
setProcessMetaDataAdjustment(processMetaDataAdjustment);
} }
@ -411,21 +417,21 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
/******************************************************************************* /*******************************************************************************
** Getter for updatedFrontendStepList ** Getter for ProcessMetaDataAdjustment (pass-through to processState)
*******************************************************************************/ *******************************************************************************/
public List<QFrontendStepMetaData> getUpdatedFrontendStepList() public ProcessMetaDataAdjustment getProcessMetaDataAdjustment()
{ {
return (this.processState.getUpdatedFrontendStepList()); return (this.processState.getProcessMetaDataAdjustment());
} }
/******************************************************************************* /*******************************************************************************
** Setter for updatedFrontendStepList ** Setter for updatedFrontendStepList (pass-through to processState)
*******************************************************************************/ *******************************************************************************/
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList) public void setProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment)
{ {
this.processState.setUpdatedFrontendStepList(updatedFrontendStepList); this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment);
} }
} }

View File

@ -33,6 +33,7 @@ import java.util.Optional;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -336,7 +337,12 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
*******************************************************************************/ *******************************************************************************/
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList) public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{ {
this.processState.setUpdatedFrontendStepList(updatedFrontendStepList); if(this.processState.getProcessMetaDataAdjustment() == null)
{
this.processState.setProcessMetaDataAdjustment(new ProcessMetaDataAdjustment());
}
this.processState.getProcessMetaDataAdjustment().setUpdatedFrontendStepList(updatedFrontendStepList);
} }
@ -346,7 +352,27 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
*******************************************************************************/ *******************************************************************************/
public List<QFrontendStepMetaData> getUpdatedFrontendStepList() public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
{ {
return this.processState.getUpdatedFrontendStepList(); return ObjectUtils.tryElse(() -> this.processState.getProcessMetaDataAdjustment().getUpdatedFrontendStepList(), null);
}
/*******************************************************************************
** Getter for processMetaDataAdjustment
*******************************************************************************/
public ProcessMetaDataAdjustment getProcessMetaDataAdjustment()
{
return (this.processState.getProcessMetaDataAdjustment());
}
/*******************************************************************************
** Setter for processMetaDataAdjustment
*******************************************************************************/
public void setProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment)
{
this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment);
} }
} }

View File

@ -0,0 +1,66 @@
/*
* 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.actions.tables.query;
/***************************************************************************
** Possible behaviors for doing interpretValues on a filter, and a criteria
** has a variable value (either as a string-that-looks-like-a-variable,
** as in ${input.foreignId} for a PVS filter, or a FilterVariableExpression),
** and a value for that variable isn't available.
**
** Used in conjunction with FilterUseCase and its implementations, e.g.,
** PossibleValueSearchFilterUseCase.
***************************************************************************/
public enum CriteriaMissingInputValueBehavior
{
//////////////////////////////////////////////////////////////////////
// this was the original behavior, before we added this enum. but, //
// it doesn't ever seem entirely valid, and isn't currently used. //
//////////////////////////////////////////////////////////////////////
INTERPRET_AS_NULL_VALUE,
//////////////////////////////////////////////////////////////////////////
// make the criteria behave as though it's not in the filter at all. //
// effectively by changing its operator to TRUE, so it always matches. //
// original intended use is for possible-values on query screens, //
// where a foreign-id isn't present, so we want to show all PV options. //
//////////////////////////////////////////////////////////////////////////
REMOVE_FROM_FILTER,
//////////////////////////////////////////////////////////////////////////////////////
// make the criteria such that it makes no rows ever match. //
// e.g., changes it to a FALSE. I suppose, within an OR, that might //
// not be powerful enough... but, it solves the immediate use-case in //
// front of us, which is forms, where a PV field should show no values //
// until a foreign key field has a value. //
// Note that this use-case used to have the same end-effect by such //
// variables being interpreted as nulls - but this approach feels more intentional. //
//////////////////////////////////////////////////////////////////////////////////////
MAKE_NO_MATCHES,
///////////////////////////////////////////////////////////////////////////////////////////
// throw an exception if a value isn't available. This is the overall default, //
// and originally was what we did for FilterVariableExpressions, e.g., for saved reports //
///////////////////////////////////////////////////////////////////////////////////////////
THROW_EXCEPTION
}

View File

@ -0,0 +1,58 @@
/*
* 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.actions.tables.query;
/*******************************************************************************
** Interface where we can associate behaviors with various use cases for
** QQueryFilters - the original being, how to handle (in the interpretValues
** method) how to handle missing input values.
**
** Includes a default implementation, with a default behavior - which is to
** throw an exception upon missing criteria variable values.
*******************************************************************************/
public interface FilterUseCase
{
FilterUseCase DEFAULT = new DefaultFilterUseCase();
/***************************************************************************
**
***************************************************************************/
CriteriaMissingInputValueBehavior getDefaultCriteriaMissingInputValueBehavior();
/***************************************************************************
**
***************************************************************************/
class DefaultFilterUseCase implements FilterUseCase
{
/***************************************************************************
**
***************************************************************************/
@Override
public CriteriaMissingInputValueBehavior getDefaultCriteriaMissingInputValueBehavior()
{
return CriteriaMissingInputValueBehavior.THROW_EXCEPTION;
}
}
}

View File

@ -82,7 +82,7 @@ public class JoinsContext
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// we will get a TON of more output if this gets turned up, so be cautious // // we will get a TON of more output if this gets turned up, so be cautious //
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
private Level logLevel = Level.OFF; private Level logLevel = Level.OFF;
private Level logLevelForFilter = Level.OFF; private Level logLevelForFilter = Level.OFF;
@ -404,6 +404,12 @@ public class JoinsContext
chainIsInner = false; chainIsInner = false;
} }
if(hasAllAccessKey(recordSecurityLock))
{
queryJoin.withType(QueryJoin.Type.LEFT);
chainIsInner = false;
}
addQueryJoin(queryJoin, "forRecordSecurityLock (non-flipped)", "- "); addQueryJoin(queryJoin, "forRecordSecurityLock (non-flipped)", "- ");
addedQueryJoins.add(queryJoin); addedQueryJoins.add(queryJoin);
tmpTable = instance.getTable(join.getRightTable()); tmpTable = instance.getTable(join.getRightTable());
@ -423,6 +429,12 @@ public class JoinsContext
chainIsInner = false; chainIsInner = false;
} }
if(hasAllAccessKey(recordSecurityLock))
{
queryJoin.withType(QueryJoin.Type.LEFT);
chainIsInner = false;
}
addQueryJoin(queryJoin, "forRecordSecurityLock (flipped)", "- "); addQueryJoin(queryJoin, "forRecordSecurityLock (flipped)", "- ");
addedQueryJoins.add(queryJoin); addedQueryJoins.add(queryJoin);
tmpTable = instance.getTable(join.getLeftTable()); tmpTable = instance.getTable(join.getLeftTable());
@ -456,44 +468,53 @@ public class JoinsContext
/***************************************************************************
**
***************************************************************************/
private boolean hasAllAccessKey(RecordSecurityLock recordSecurityLock)
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
{
QSession session = QContext.getQSession();
if(session.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
{
return (true);
}
}
return (false);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private void addSubFilterForRecordSecurityLock(RecordSecurityLock recordSecurityLock, QTableMetaData table, String tableNameOrAlias, boolean isOuter, QueryJoin sourceQueryJoin) private void addSubFilterForRecordSecurityLock(RecordSecurityLock recordSecurityLock, QTableMetaData table, String tableNameOrAlias, boolean isOuter, QueryJoin sourceQueryJoin)
{ {
QSession session = QContext.getQSession(); boolean haveAllAccessKey = hasAllAccessKey(recordSecurityLock);
if(haveAllAccessKey)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
boolean haveAllAccessKey = false;
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
{ {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if(sourceQueryJoin != null)
// if we have all-access on this key, then we don't need a criterion for it (as long as we're in an AND filter) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(session.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
{ {
haveAllAccessKey = true; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// in case the queryJoin object is re-used between queries, and its security criteria need to be different (!!), reset it //
// this can be exposed in tests - maybe not entirely expected in real-world, but seems safe enough //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sourceQueryJoin.withSecurityCriteria(new ArrayList<>());
}
if(sourceQueryJoin != null) ////////////////////////////////////////////////////////////////////////////////////////
{ // if we're in an AND filter, then we don't need a criteria for this lock, so return. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
// in case the queryJoin object is re-used between queries, and its security criteria need to be different (!!), reset it // boolean inAnAndFilter = securityFilterCursor.getBooleanOperator() == QQueryFilter.BooleanOperator.AND;
// this can be exposed in tests - maybe not entirely expected in real-world, but seems safe enough // if(inAnAndFilter)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// {
sourceQueryJoin.withSecurityCriteria(new ArrayList<>()); return;
}
////////////////////////////////////////////////////////////////////////////////////////
// if we're in an AND filter, then we don't need a criteria for this lock, so return. //
////////////////////////////////////////////////////////////////////////////////////////
boolean inAnAndFilter = securityFilterCursor.getBooleanOperator() == QQueryFilter.BooleanOperator.AND;
if(inAnAndFilter)
{
return;
}
} }
} }
@ -545,7 +566,7 @@ public class JoinsContext
} }
else else
{ {
List<Serializable> securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), type); List<Serializable> securityKeyValues = QContext.getQSession().getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), type);
if(CollectionUtils.nullSafeIsEmpty(securityKeyValues)) if(CollectionUtils.nullSafeIsEmpty(securityKeyValues))
{ {
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -528,8 +528,27 @@ public class QQueryFilter implements Serializable, Cloneable
** Note - it may be very important that you call this method on a clone of a ** Note - it may be very important that you call this method on a clone of a
** QQueryFilter - e.g., if it's one that defined in metaData, and that we don't ** QQueryFilter - e.g., if it's one that defined in metaData, and that we don't
** want to be (permanently) changed!! ** want to be (permanently) changed!!
*******************************************************************************/ **
** This overload does not take in a FilterUseCase - it uses FilterUseCase.DEFAULT
******************************************************************************/
public void interpretValues(Map<String, Serializable> inputValues) throws QException public void interpretValues(Map<String, Serializable> inputValues) throws QException
{
interpretValues(inputValues, FilterUseCase.DEFAULT);
}
/*******************************************************************************
** Replace any criteria values that look like ${input.XXX} with the value of XXX
** from the supplied inputValues map - where the handling of missing values
** is specified in the inputted FilterUseCase parameter
**
** Note - it may be very important that you call this method on a clone of a
** QQueryFilter - e.g., if it's one that defined in metaData, and that we don't
** want to be (permanently) changed!!
**
*******************************************************************************/
public void interpretValues(Map<String, Serializable> inputValues, FilterUseCase useCase) throws QException
{ {
List<Exception> caughtExceptions = new ArrayList<>(); List<Exception> caughtExceptions = new ArrayList<>();
@ -545,6 +564,9 @@ public class QQueryFilter implements Serializable, Cloneable
{ {
try try
{ {
Serializable interpretedValue = value;
Exception caughtException = null;
if(value instanceof AbstractFilterExpression<?>) if(value instanceof AbstractFilterExpression<?>)
{ {
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
@ -553,17 +575,54 @@ public class QQueryFilter implements Serializable, Cloneable
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
if(value instanceof FilterVariableExpression filterVariableExpression) if(value instanceof FilterVariableExpression filterVariableExpression)
{ {
newValues.add(filterVariableExpression.evaluateInputValues(inputValues)); try
} {
else interpretedValue = filterVariableExpression.evaluateInputValues(inputValues);
{ }
newValues.add(value); catch(Exception e)
{
caughtException = e;
interpretedValue = InputNotFound.instance;
}
}
}
else
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// for non-expressions, cast the value to a string, and see if it can be resolved a variable. //
// there are 3 possible cases here: //
// 1: it doesn't look like a variable, so it just comes back as a string version of whatever went in. //
// 2: it was resolved from a variable to a value, e.g., ${input.someVar} => someValue //
// 3: it looked like a variable, but no value for that variable was present in the interpreter's value //
// map - so we'll get back the InputNotFound.instance. //
/////////////////////////////////////////////////////////////////////////////////////////////////////////
String valueAsString = ValueUtils.getValueAsString(value);
interpretedValue = variableInterpreter.interpretForObject(valueAsString, InputNotFound.instance);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if interpreting a value returned the not-found value, or an empty string, //
// then decide how to handle the missing value, based on the use-case input //
// Note: questionable, using "" here, but that's what reality is passing a lot for cases we want to treat as missing... //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(interpretedValue == InputNotFound.instance || "".equals(interpretedValue))
{
CriteriaMissingInputValueBehavior missingInputValueBehavior = getMissingInputValueBehavior(useCase);
switch(missingInputValueBehavior)
{
case REMOVE_FROM_FILTER -> criterion.setOperator(QCriteriaOperator.TRUE);
case MAKE_NO_MATCHES -> criterion.setOperator(QCriteriaOperator.FALSE);
case INTERPRET_AS_NULL_VALUE -> newValues.add(null);
/////////////////////////////////////////////////
// handle case in the default: THROW_EXCEPTION //
/////////////////////////////////////////////////
default -> throw (Objects.requireNonNullElseGet(caughtException, () -> new QUserFacingException("Missing value for criteria on field: " + criterion.getFieldName())));
} }
} }
else else
{ {
String valueAsString = ValueUtils.getValueAsString(value);
Serializable interpretedValue = variableInterpreter.interpretForObject(valueAsString);
newValues.add(interpretedValue); newValues.add(interpretedValue);
} }
} }
@ -586,6 +645,44 @@ public class QQueryFilter implements Serializable, Cloneable
/***************************************************************************
** Note: in the original build of this, it felt like we *might* want to be
** able to specify these behaviors at the individual criteria level, where
** the implementation would be to add to QFilterCriteria:
** - Map<FilterUseCase, CriteriaMissingInputValueBehavior> missingInputValueBehaviors;
** - CriteriaMissingInputValueBehavior getMissingInputValueBehaviorForUseCase(FilterUseCase useCase) {}
*
** (and maybe do that in a sub-class of QFilterCriteria, so it isn't always
** there? idk...) and then here we'd call:
** - CriteriaMissingInputValueBehavior missingInputValueBehavior = criterion.getMissingInputValueBehaviorForUseCase(useCase);
*
** But, we don't actually have that use-case at hand now, so - let's keep it
** just at the level we need for now.
**
***************************************************************************/
private CriteriaMissingInputValueBehavior getMissingInputValueBehavior(FilterUseCase useCase)
{
if(useCase == null)
{
useCase = FilterUseCase.DEFAULT;
}
CriteriaMissingInputValueBehavior missingInputValueBehavior = useCase.getDefaultCriteriaMissingInputValueBehavior();
if(missingInputValueBehavior == null)
{
missingInputValueBehavior = useCase.getDefaultCriteriaMissingInputValueBehavior();
}
if(missingInputValueBehavior == null)
{
missingInputValueBehavior = FilterUseCase.DEFAULT.getDefaultCriteriaMissingInputValueBehavior();
}
return (missingInputValueBehavior);
}
/******************************************************************************* /*******************************************************************************
** Getter for skip ** Getter for skip
*******************************************************************************/ *******************************************************************************/
@ -678,4 +775,28 @@ public class QQueryFilter implements Serializable, Cloneable
{ {
return Objects.hash(criteria, orderBys, booleanOperator, subFilters, skip, limit); return Objects.hash(criteria, orderBys, booleanOperator, subFilters, skip, limit);
} }
/***************************************************************************
** "Token" object to be used as the defaultIfLooksLikeVariableButNotFound
** parameter to variableInterpreter.interpretForObject, so we can be
** very clear that we got this default back (e.g., instead of a null,
** which could maybe mean something else?)
***************************************************************************/
private static final class InputNotFound implements Serializable
{
private static InputNotFound instance = new InputNotFound();
/*******************************************************************************
** private singleton constructor
*******************************************************************************/
private InputNotFound()
{
}
}
} }

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
/******************************************************************************* /*******************************************************************************
@ -35,7 +36,7 @@ public abstract class AbstractFilterExpression<T extends Serializable> implement
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public abstract T evaluate() throws QException; public abstract T evaluate(QFieldMetaData field) throws QException;
@ -47,7 +48,7 @@ public abstract class AbstractFilterExpression<T extends Serializable> implement
*******************************************************************************/ *******************************************************************************/
public T evaluateInputValues(Map<String, Serializable> inputValues) throws QException public T evaluateInputValues(Map<String, Serializable> inputValues) throws QException
{ {
return evaluate(); return evaluate(null);
} }

View File

@ -26,6 +26,7 @@ import java.io.Serializable;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -45,7 +46,7 @@ public class FilterVariableExpression extends AbstractFilterExpression<Serializa
** **
*******************************************************************************/ *******************************************************************************/
@Override @Override
public Serializable evaluate() throws QException public Serializable evaluate(QFieldMetaData field) throws QException
{ {
throw (new QUserFacingException("Missing variable value.")); throw (new QUserFacingException("Missing variable value."));
} }

View File

@ -22,23 +22,42 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions; package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions;
import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
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.utils.ValueUtils;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class Now extends AbstractFilterExpression<Instant> public class Now extends AbstractFilterExpression<Serializable>
{ {
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@Override @Override
public Instant evaluate() throws QException public Serializable evaluate(QFieldMetaData field) throws QException
{ {
return (Instant.now()); QFieldType type = field == null ? QFieldType.DATE_TIME : field.getType();
if(type.equals(QFieldType.DATE_TIME))
{
return (Instant.now());
}
else if(type.equals(QFieldType.DATE))
{
ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId();
return (Instant.now().atZone(zoneId).toLocalDate());
}
else
{
throw (new QException("Unsupported field type [" + type + "]"));
}
} }
} }

View File

@ -22,19 +22,24 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions; package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions;
import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
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.utils.ValueUtils;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class NowWithOffset extends AbstractFilterExpression<Instant> public class NowWithOffset extends AbstractFilterExpression<Serializable>
{ {
private Operator operator; private Operator operator;
private int amount; private int amount;
@ -123,7 +128,30 @@ public class NowWithOffset extends AbstractFilterExpression<Instant>
** **
*******************************************************************************/ *******************************************************************************/
@Override @Override
public Instant evaluate() throws QException public Serializable evaluate(QFieldMetaData field) throws QException
{
QFieldType type = field == null ? QFieldType.DATE_TIME : field.getType();
if(type.equals(QFieldType.DATE_TIME))
{
return (evaluateForDateTime());
}
else if(type.equals(QFieldType.DATE))
{
return (evaluateForDate());
}
else
{
throw (new QException("Unsupported field type [" + type + "]"));
}
}
/***************************************************************************
**
***************************************************************************/
private Instant evaluateForDateTime()
{ {
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Instant doesn't let us plus/minus WEEK, MONTH, or YEAR... // // Instant doesn't let us plus/minus WEEK, MONTH, or YEAR... //
@ -147,6 +175,26 @@ public class NowWithOffset extends AbstractFilterExpression<Instant>
/***************************************************************************
**
***************************************************************************/
private LocalDate evaluateForDate()
{
ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId();
LocalDate now = Instant.now().atZone(zoneId).toLocalDate();
if(operator.equals(Operator.PLUS))
{
return (now.plus(amount, timeUnit));
}
else
{
return (now.minus(amount, timeUnit));
}
}
/******************************************************************************* /*******************************************************************************
** Getter for operator ** Getter for operator
** **

View File

@ -22,27 +22,32 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions; package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions;
import java.io.Serializable;
import java.time.DayOfWeek; import java.time.DayOfWeek;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.temporal.ChronoField; import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException; import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
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.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class ThisOrLastPeriod extends AbstractFilterExpression<Instant> public class ThisOrLastPeriod extends AbstractFilterExpression<Serializable>
{ {
private Operator operator; private Operator operator;
private ChronoUnit timeUnit; private ChronoUnit timeUnit;
/*************************************************************************** /***************************************************************************
** **
***************************************************************************/ ***************************************************************************/
@ -88,7 +93,7 @@ public class ThisOrLastPeriod extends AbstractFilterExpression<Instant>
** Factory ** Factory
** **
*******************************************************************************/ *******************************************************************************/
public static ThisOrLastPeriod last(int amount, ChronoUnit timeUnit) public static ThisOrLastPeriod last(ChronoUnit timeUnit)
{ {
return (new ThisOrLastPeriod(Operator.LAST, timeUnit)); return (new ThisOrLastPeriod(Operator.LAST, timeUnit));
} }
@ -99,7 +104,31 @@ public class ThisOrLastPeriod extends AbstractFilterExpression<Instant>
** **
*******************************************************************************/ *******************************************************************************/
@Override @Override
public Instant evaluate() throws QException public Serializable evaluate(QFieldMetaData field) throws QException
{
QFieldType type = field == null ? QFieldType.DATE_TIME : field.getType();
if(type.equals(QFieldType.DATE_TIME))
{
return (evaluateForDateTime());
}
else if(type.equals(QFieldType.DATE))
{
// return (evaluateForDateTime());
return (evaluateForDate());
}
else
{
throw (new QException("Unsupported field type [" + type + "]"));
}
}
/***************************************************************************
**
***************************************************************************/
private Instant evaluateForDateTime()
{ {
ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId(); ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId();
@ -154,7 +183,57 @@ public class ThisOrLastPeriod extends AbstractFilterExpression<Instant>
return operator.equals(Operator.THIS) ? startOfThisYear : startOfLastYear; return operator.equals(Operator.THIS) ? startOfThisYear : startOfLastYear;
} }
default -> throw (new QRuntimeException("Unsupported timeUnit: " + timeUnit)); default -> throw (new QRuntimeException("Unsupported unit: " + timeUnit));
}
}
/*******************************************************************************
**
*******************************************************************************/
public LocalDate evaluateForDate()
{
ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId();
LocalDate today = Instant.now().atZone(zoneId).toLocalDate();
switch(timeUnit)
{
case DAYS ->
{
return operator.equals(Operator.THIS) ? today : today.minusDays(1);
}
case WEEKS ->
{
LocalDate startOfThisWeek = today;
while(startOfThisWeek.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue())
{
////////////////////////////////////////
// go backwards until sunday is found //
////////////////////////////////////////
startOfThisWeek = startOfThisWeek.minusDays(1);
}
return operator.equals(Operator.THIS) ? startOfThisWeek : startOfThisWeek.minusDays(7);
}
case MONTHS ->
{
Instant startOfThisMonth = ValueUtils.getStartOfMonthInZoneId(zoneId.getId());
LocalDateTime startOfThisMonthLDT = LocalDateTime.ofInstant(startOfThisMonth, ZoneId.of(zoneId.getId()));
LocalDateTime startOfLastMonthLDT = startOfThisMonthLDT.minusMonths(1);
Instant startOfLastMonth = startOfLastMonthLDT.toInstant(ZoneId.of(zoneId.getId()).getRules().getOffset(Instant.now()));
return (operator.equals(Operator.THIS) ? startOfThisMonth : startOfLastMonth).atZone(zoneId).toLocalDate();
}
case YEARS ->
{
Instant startOfThisYear = ValueUtils.getStartOfYearInZoneId(zoneId.getId());
LocalDateTime startOfThisYearLDT = LocalDateTime.ofInstant(startOfThisYear, zoneId);
LocalDateTime startOfLastYearLDT = startOfThisYearLDT.minusYears(1);
Instant startOfLastYear = startOfLastYearLDT.toInstant(zoneId.getRules().getOffset(Instant.now()));
return (operator.equals(Operator.THIS) ? startOfThisYear : startOfLastYear).atZone(zoneId).toLocalDate();
}
default -> throw (new QRuntimeException("Unsupported unit: " + timeUnit));
} }
} }

View File

@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
public class StorageInput extends AbstractTableActionInput public class StorageInput extends AbstractTableActionInput
{ {
private String reference; private String reference;
private String contentType;
@ -74,4 +75,35 @@ public class StorageInput extends AbstractTableActionInput
return (this); return (this);
} }
/*******************************************************************************
** Getter for contentType
*******************************************************************************/
public String getContentType()
{
return (this.contentType);
}
/*******************************************************************************
** Setter for contentType
*******************************************************************************/
public void setContentType(String contentType)
{
this.contentType = contentType;
}
/*******************************************************************************
** Fluent setter for contentType
*******************************************************************************/
public StorageInput withContentType(String contentType)
{
this.contentType = contentType;
return (this);
}
} }

View File

@ -220,7 +220,7 @@ public class AuditsMetaDataProvider
.withRecordLabelFormat("%s %s") .withRecordLabelFormat("%s %s")
.withRecordLabelFields("auditTableId", "recordId") .withRecordLabelFields("auditTableId", "recordId")
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.LONG))
.withField(new QFieldMetaData("auditTableId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT_TABLE)) .withField(new QFieldMetaData("auditTableId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT_TABLE))
.withField(new QFieldMetaData("auditUserId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT_USER)) .withField(new QFieldMetaData("auditUserId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT_USER))
.withField(new QFieldMetaData("recordId", QFieldType.INTEGER)) .withField(new QFieldMetaData("recordId", QFieldType.INTEGER))
@ -243,8 +243,8 @@ public class AuditsMetaDataProvider
.withRecordLabelFormat("%s") .withRecordLabelFormat("%s")
.withRecordLabelFields("id") .withRecordLabelFields("id")
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.LONG))
.withField(new QFieldMetaData("auditId", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_AUDIT)) .withField(new QFieldMetaData("auditId", QFieldType.LONG).withPossibleValueSourceName(TABLE_NAME_AUDIT))
.withField(new QFieldMetaData("message", QFieldType.STRING).withMaxLength(250).withBehavior(ValueTooLongBehavior.TRUNCATE_ELLIPSIS)) .withField(new QFieldMetaData("message", QFieldType.STRING).withMaxLength(250).withBehavior(ValueTooLongBehavior.TRUNCATE_ELLIPSIS))
.withField(new QFieldMetaData("fieldName", QFieldType.STRING).withMaxLength(100).withBehavior(ValueTooLongBehavior.TRUNCATE_ELLIPSIS)) .withField(new QFieldMetaData("fieldName", QFieldType.STRING).withMaxLength(100).withBehavior(ValueTooLongBehavior.TRUNCATE_ELLIPSIS))
.withField(new QFieldMetaData("oldValue", QFieldType.STRING).withMaxLength(250).withBehavior(ValueTooLongBehavior.TRUNCATE_ELLIPSIS)) .withField(new QFieldMetaData("oldValue", QFieldType.STRING).withMaxLength(250).withBehavior(ValueTooLongBehavior.TRUNCATE_ELLIPSIS))

View File

@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.model.dashboard.widgets; package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
import java.util.List;
/******************************************************************************* /*******************************************************************************
** Model containing datastructure expected by frontend alert widget ** Model containing datastructure expected by frontend alert widget
** **
@ -40,8 +43,10 @@ public class AlertData extends QWidgetData
private String html; private String html;
private AlertType alertType; private AlertType alertType;
private Boolean hideWidget = false;
private List<String> bulletList;
@ -139,4 +144,66 @@ public class AlertData extends QWidgetData
return (this); return (this);
} }
/*******************************************************************************
** Getter for hideWidget
*******************************************************************************/
public boolean getHideWidget()
{
return (this.hideWidget);
}
/*******************************************************************************
** Setter for hideWidget
*******************************************************************************/
public void setHideWidget(boolean hideWidget)
{
this.hideWidget = hideWidget;
}
/*******************************************************************************
** Fluent setter for hideWidget
*******************************************************************************/
public AlertData withHideWidget(boolean hideWidget)
{
this.hideWidget = hideWidget;
return (this);
}
/*******************************************************************************
** Getter for bulletList
*******************************************************************************/
public List<String> getBulletList()
{
return (this.bulletList);
}
/*******************************************************************************
** Setter for bulletList
*******************************************************************************/
public void setBulletList(List<String> bulletList)
{
this.bulletList = bulletList;
}
/*******************************************************************************
** Fluent setter for bulletList
*******************************************************************************/
public AlertData withBulletList(List<String> bulletList)
{
this.bulletList = bulletList;
return (this);
}
} }

View File

@ -39,9 +39,13 @@ public class ChildRecordListData extends QWidgetData
private QueryOutput queryOutput; private QueryOutput queryOutput;
private QTableMetaData childTableMetaData; private QTableMetaData childTableMetaData;
private String tableName;
private String tablePath; private String tablePath;
private String viewAllLink; private String viewAllLink;
private Integer totalRows; private Integer totalRows;
private Boolean disableRowClick = false;
private Boolean allowRecordEdit = false;
private Boolean allowRecordDelete = false;
private boolean canAddChildRecord = false; private boolean canAddChildRecord = false;
private Map<String, Serializable> defaultValuesForNewChildRecords; private Map<String, Serializable> defaultValuesForNewChildRecords;
@ -352,4 +356,141 @@ public class ChildRecordListData extends QWidgetData
return (this); return (this);
} }
/*******************************************************************************
** Getter for tableName
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
*******************************************************************************/
public ChildRecordListData withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Fluent setter for tablePath
*******************************************************************************/
public ChildRecordListData withTablePath(String tablePath)
{
this.tablePath = tablePath;
return (this);
}
/*******************************************************************************
** Getter for disableRowClick
*******************************************************************************/
public Boolean getDisableRowClick()
{
return (this.disableRowClick);
}
/*******************************************************************************
** Setter for disableRowClick
*******************************************************************************/
public void setDisableRowClick(Boolean disableRowClick)
{
this.disableRowClick = disableRowClick;
}
/*******************************************************************************
** Fluent setter for disableRowClick
*******************************************************************************/
public ChildRecordListData withDisableRowClick(Boolean disableRowClick)
{
this.disableRowClick = disableRowClick;
return (this);
}
/*******************************************************************************
** Getter for allowRecordEdit
*******************************************************************************/
public Boolean getAllowRecordEdit()
{
return (this.allowRecordEdit);
}
/*******************************************************************************
** Setter for allowRecordEdit
*******************************************************************************/
public void setAllowRecordEdit(Boolean allowRecordEdit)
{
this.allowRecordEdit = allowRecordEdit;
}
/*******************************************************************************
** Fluent setter for allowRecordEdit
*******************************************************************************/
public ChildRecordListData withAllowRecordEdit(Boolean allowRecordEdit)
{
this.allowRecordEdit = allowRecordEdit;
return (this);
}
/*******************************************************************************
** Getter for allowRecordDelete
*******************************************************************************/
public Boolean getAllowRecordDelete()
{
return (this.allowRecordDelete);
}
/*******************************************************************************
** Setter for allowRecordDelete
*******************************************************************************/
public void setAllowRecordDelete(Boolean allowRecordDelete)
{
this.allowRecordDelete = allowRecordDelete;
}
/*******************************************************************************
** Fluent setter for allowRecordDelete
*******************************************************************************/
public ChildRecordListData withAllowRecordDelete(Boolean allowRecordDelete)
{
this.allowRecordDelete = allowRecordDelete;
return (this);
}
} }

View File

@ -40,9 +40,24 @@ public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidget
{ {
private List<AbstractBlockWidgetData<?, ?, ?, ?>> blocks = new ArrayList<>(); private List<AbstractBlockWidgetData<?, ?, ?, ?>> blocks = new ArrayList<>();
private Map<String, Serializable> styleOverrides = new HashMap<>(); private ModalMode modalMode;
private Layout layout;
/***************************************************************************
**
***************************************************************************/
public enum ModalMode
{
MODAL
}
private Layout layout;
private Map<String, Serializable> styleOverrides = new HashMap<>();
private String overlayHtml;
private Map<String, Serializable> overlayStyleOverrides = new HashMap<>();
@ -51,12 +66,14 @@ public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidget
*******************************************************************************/ *******************************************************************************/
public enum Layout public enum Layout
{ {
///////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
// note, these are used in QQQ FMD CompositeWidgetData.tsx // // note, these are used in QQQ FMD CompositeWidget.tsx //
///////////////////////////////////////////////////////////// // and qqq-android CompositeWidgetBlock.kt //
/////////////////////////////////////////////////////////
FLEX_COLUMN, FLEX_COLUMN,
FLEX_ROW_WRAPPED, FLEX_ROW_WRAPPED,
FLEX_ROW_SPACE_BETWEEN, FLEX_ROW_SPACE_BETWEEN,
FLEX_ROW_CENTER,
TABLE_SUB_ROW_DETAILS, TABLE_SUB_ROW_DETAILS,
BADGES_WRAPPER BADGES_WRAPPER
} }
@ -218,4 +235,122 @@ public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidget
return (this); return (this);
} }
/*******************************************************************************
** Getter for overlayHtml
*******************************************************************************/
public String getOverlayHtml()
{
return (this.overlayHtml);
}
/*******************************************************************************
** Setter for overlayHtml
*******************************************************************************/
public void setOverlayHtml(String overlayHtml)
{
this.overlayHtml = overlayHtml;
}
/*******************************************************************************
** Fluent setter for overlayHtml
*******************************************************************************/
public CompositeWidgetData withOverlayHtml(String overlayHtml)
{
this.overlayHtml = overlayHtml;
return (this);
}
/*******************************************************************************
** Getter for overlayStyleOverrides
*******************************************************************************/
public Map<String, Serializable> getOverlayStyleOverrides()
{
return (this.overlayStyleOverrides);
}
/*******************************************************************************
** Setter for overlayStyleOverrides
*******************************************************************************/
public void setOverlayStyleOverrides(Map<String, Serializable> overlayStyleOverrides)
{
this.overlayStyleOverrides = overlayStyleOverrides;
}
/*******************************************************************************
** Fluent setter for overlayStyleOverrides
*******************************************************************************/
public CompositeWidgetData withOverlayStyleOverrides(Map<String, Serializable> overlayStyleOverrides)
{
this.overlayStyleOverrides = overlayStyleOverrides;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public CompositeWidgetData withOverlayStyleOverride(String key, Serializable value)
{
addOverlayStyleOverride(key, value);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void addOverlayStyleOverride(String key, Serializable value)
{
if(this.overlayStyleOverrides == null)
{
this.overlayStyleOverrides = new HashMap<>();
}
this.overlayStyleOverrides.put(key, value);
}
/*******************************************************************************
** Getter for modalMode
*******************************************************************************/
public ModalMode getModalMode()
{
return (this.modalMode);
}
/*******************************************************************************
** Setter for modalMode
*******************************************************************************/
public void setModalMode(ModalMode modalMode)
{
this.modalMode = modalMode;
}
/*******************************************************************************
** Fluent setter for modalMode
*******************************************************************************/
public CompositeWidgetData withModalMode(ModalMode modalMode)
{
this.modalMode = modalMode;
return (this);
}
} }

View File

@ -31,7 +31,7 @@ import java.util.Map;
** Base class for the data returned by rendering a Widget. ** Base class for the data returned by rendering a Widget.
** **
*******************************************************************************/ *******************************************************************************/
public abstract class QWidgetData public abstract class QWidgetData implements Serializable
{ {
private String label; private String label;
private String sublabel; private String sublabel;

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.CompositeWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.QWidgetData; import com.kingsrook.qqq.backend.core.model.dashboard.widgets.QWidgetData;
@ -51,6 +52,11 @@ public abstract class AbstractBlockWidgetData<
private V values; private V values;
private SX styles; private SX styles;
///////////////////////////////////////////////////////////////////////////////////
// optional field name to act as a 'guard' for the block - e.g., only include it //
// if the value for this field is true //
///////////////////////////////////////////////////////////////////////////////////
private String conditional;
/******************************************************************************* /*******************************************************************************
@ -203,6 +209,19 @@ public abstract class AbstractBlockWidgetData<
/*******************************************************************************
** Fluent setter for tooltip
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public T withTooltip(CompositeWidgetData data)
{
this.tooltip = new BlockTooltip(data);
return (T) (this);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -398,6 +417,7 @@ public abstract class AbstractBlockWidgetData<
} }
/******************************************************************************* /*******************************************************************************
** Getter for blockId ** Getter for blockId
*******************************************************************************/ *******************************************************************************/
@ -429,4 +449,34 @@ public abstract class AbstractBlockWidgetData<
} }
/*******************************************************************************
** Getter for conditional
*******************************************************************************/
public String getConditional()
{
return (this.conditional);
}
/*******************************************************************************
** Setter for conditional
*******************************************************************************/
public void setConditional(String conditional)
{
this.conditional = conditional;
}
/*******************************************************************************
** Fluent setter for conditional
*******************************************************************************/
public AbstractBlockWidgetData withConditional(String conditional)
{
this.conditional = conditional;
return (this);
}
} }

View File

@ -22,14 +22,18 @@
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks; package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.CompositeWidgetData;
/******************************************************************************* /*******************************************************************************
** A tooltip used within a (widget) block. ** A tooltip used within a (widget) block.
** **
*******************************************************************************/ *******************************************************************************/
public class BlockTooltip public class BlockTooltip
{ {
private String title; private CompositeWidgetData blockData;
private Placement placement = Placement.BOTTOM; private String title;
private Placement placement = Placement.BOTTOM;
@ -62,6 +66,17 @@ public class BlockTooltip
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public BlockTooltip(CompositeWidgetData blockData)
{
this.blockData = blockData;
}
/******************************************************************************* /*******************************************************************************
** Getter for title ** Getter for title
*******************************************************************************/ *******************************************************************************/
@ -122,4 +137,35 @@ public class BlockTooltip
return (this); return (this);
} }
/*******************************************************************************
** Getter for blockData
*******************************************************************************/
public CompositeWidgetData getBlockData()
{
return (this.blockData);
}
/*******************************************************************************
** Setter for blockData
*******************************************************************************/
public void setBlockData(CompositeWidgetData blockData)
{
this.blockData = blockData;
}
/*******************************************************************************
** Fluent setter for blockData
*******************************************************************************/
public BlockTooltip withBlockData(CompositeWidgetData blockData)
{
this.blockData = blockData;
return (this);
}
} }

View File

@ -0,0 +1,45 @@
/*
* 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.dashboard.widgets.blocks.audio;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseStyles;
/*******************************************************************************
** block that plays an audio file
*******************************************************************************/
public class AudioBlockData extends AbstractBlockWidgetData<AudioBlockData, AudioValues, BaseSlots, BaseStyles>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getBlockTypeName()
{
return "AUDIO";
}
}

View File

@ -0,0 +1,130 @@
/*
* 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.dashboard.widgets.blocks.audio;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
/*******************************************************************************
**
*******************************************************************************/
public class AudioValues implements BlockValuesInterface
{
private String path;
private boolean showControls = false;
private boolean autoPlay = true;
/*******************************************************************************
** Getter for path
*******************************************************************************/
public String getPath()
{
return (this.path);
}
/*******************************************************************************
** Setter for path
*******************************************************************************/
public void setPath(String path)
{
this.path = path;
}
/*******************************************************************************
** Fluent setter for path
*******************************************************************************/
public AudioValues withPath(String path)
{
this.path = path;
return (this);
}
/*******************************************************************************
** Getter for showControls
*******************************************************************************/
public boolean getShowControls()
{
return (this.showControls);
}
/*******************************************************************************
** Setter for showControls
*******************************************************************************/
public void setShowControls(boolean showControls)
{
this.showControls = showControls;
}
/*******************************************************************************
** Fluent setter for showControls
*******************************************************************************/
public AudioValues withShowControls(boolean showControls)
{
this.showControls = showControls;
return (this);
}
/*******************************************************************************
** Getter for autoPlay
*******************************************************************************/
public boolean getAutoPlay()
{
return (this.autoPlay);
}
/*******************************************************************************
** Setter for autoPlay
*******************************************************************************/
public void setAutoPlay(boolean autoPlay)
{
this.autoPlay = autoPlay;
}
/*******************************************************************************
** Fluent setter for autoPlay
*******************************************************************************/
public AudioValues withAutoPlay(boolean autoPlay)
{
this.autoPlay = autoPlay;
return (this);
}
}

View File

@ -30,4 +30,335 @@ import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStyles
*******************************************************************************/ *******************************************************************************/
public class BaseStyles implements BlockStylesInterface public class BaseStyles implements BlockStylesInterface
{ {
private Directional<String> padding;
private String backgroundColor;
/***************************************************************************
**
***************************************************************************/
public static class Directional<T>
{
private T top;
private T bottom;
private T left;
private T right;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Directional()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Directional(T top, T right, T bottom, T left)
{
this.top = top;
this.right = right;
this.bottom = bottom;
this.left = left;
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> of(T top, T right, T bottom, T left)
{
return (new Directional<>(top, right, bottom, left));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> of(T value)
{
return (new Directional<>(value, value, value, value));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofTop(T top)
{
return (new Directional<>(top, null, null, null));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofRight(T right)
{
return (new Directional<>(null, right, null, null));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofBottom(T bottom)
{
return (new Directional<>(null, null, bottom, null));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofLeft(T left)
{
return (new Directional<>(null, null, null, left));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofX(T x)
{
return (new Directional<>(null, x, null, x));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofY(T y)
{
return (new Directional<>(y, null, y, null));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofXY(T x, T y)
{
return (new Directional<>(y, x, y, x));
}
/*******************************************************************************
** Getter for top
**
*******************************************************************************/
public T getTop()
{
return top;
}
/*******************************************************************************
** Setter for top
**
*******************************************************************************/
public void setTop(T top)
{
this.top = top;
}
/*******************************************************************************
** Fluent setter for top
**
*******************************************************************************/
public Directional<T> withTop(T top)
{
this.top = top;
return (this);
}
/*******************************************************************************
** Getter for bottom
**
*******************************************************************************/
public T getBottom()
{
return bottom;
}
/*******************************************************************************
** Setter for bottom
**
*******************************************************************************/
public void setBottom(T bottom)
{
this.bottom = bottom;
}
/*******************************************************************************
** Fluent setter for bottom
**
*******************************************************************************/
public Directional<T> withBottom(T bottom)
{
this.bottom = bottom;
return (this);
}
/*******************************************************************************
** Getter for left
**
*******************************************************************************/
public T getLeft()
{
return left;
}
/*******************************************************************************
** Setter for left
**
*******************************************************************************/
public void setLeft(T left)
{
this.left = left;
}
/*******************************************************************************
** Fluent setter for left
**
*******************************************************************************/
public Directional<T> withLeft(T left)
{
this.left = left;
return (this);
}
/*******************************************************************************
** Getter for right
**
*******************************************************************************/
public T getRight()
{
return right;
}
/*******************************************************************************
** Setter for right
**
*******************************************************************************/
public void setRight(T right)
{
this.right = right;
}
/*******************************************************************************
** Fluent setter for right
**
*******************************************************************************/
public Directional<T> withRight(T right)
{
this.right = right;
return (this);
}
}
/*******************************************************************************
** Getter for padding
*******************************************************************************/
public Directional<String> getPadding()
{
return (this.padding);
}
/*******************************************************************************
** Setter for padding
*******************************************************************************/
public void setPadding(Directional<String> padding)
{
this.padding = padding;
}
/*******************************************************************************
** Fluent setter for padding
*******************************************************************************/
public BaseStyles withPadding(Directional<String> padding)
{
this.padding = padding;
return (this);
}
/*******************************************************************************
** Getter for backgroundColor
*******************************************************************************/
public String getBackgroundColor()
{
return (this.backgroundColor);
}
/*******************************************************************************
** Setter for backgroundColor
*******************************************************************************/
public void setBackgroundColor(String backgroundColor)
{
this.backgroundColor = backgroundColor;
}
/*******************************************************************************
** Fluent setter for backgroundColor
*******************************************************************************/
public BaseStyles withBackgroundColor(String backgroundColor)
{
this.backgroundColor = backgroundColor;
return (this);
}
} }

View File

@ -0,0 +1,46 @@
/*
* 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.dashboard.widgets.blocks.button;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
/*******************************************************************************
** a button (for a process - not sure yet what this could do in a standalone
** widget?) to submit the process screen to run a specific action (e.g., not just
** 'next'), or do other control-ish things
*******************************************************************************/
public class ButtonBlockData extends AbstractBlockWidgetData<ButtonBlockData, ButtonValues, BaseSlots, ButtonStyles>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getBlockTypeName()
{
return "BUTTON";
}
}

View File

@ -0,0 +1,143 @@
/*
* 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.dashboard.widgets.blocks.button;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
/*******************************************************************************
**
*******************************************************************************/
public class ButtonStyles implements BlockStylesInterface
{
private String color;
private String format;
/***************************************************************************
**
***************************************************************************/
public enum StandardColor
{
SUCCESS,
WARNING,
ERROR,
INFO,
MUTED
}
/***************************************************************************
**
***************************************************************************/
public enum StandardFormat
{
OUTLINED,
FILLED,
TEXT
}
/*******************************************************************************
** Getter for color
*******************************************************************************/
public String getColor()
{
return (this.color);
}
/*******************************************************************************
** Setter for color
*******************************************************************************/
public void setColor(String color)
{
this.color = color;
}
/*******************************************************************************
** Fluent setter for color
*******************************************************************************/
public ButtonStyles withColor(String color)
{
this.color = color;
return (this);
}
/*******************************************************************************
** Getter for format
*******************************************************************************/
public String getFormat()
{
return (this.format);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(String format)
{
this.format = format;
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public ButtonStyles withFormat(String format)
{
this.format = format;
return (this);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(StandardFormat format)
{
this.format = (format == null ? null : format.name().toLowerCase());
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public ButtonStyles withFormat(StandardFormat format)
{
setFormat(format);
return (this);
}
}

View File

@ -0,0 +1,218 @@
/*
* 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.dashboard.widgets.blocks.button;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
/*******************************************************************************
**
*******************************************************************************/
public class ButtonValues implements BlockValuesInterface
{
private String label;
private String actionCode;
private String controlCode;
private QIcon startIcon;
private QIcon endIcon;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ButtonValues()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ButtonValues(String label, String actionCode)
{
setLabel(label);
setActionCode(actionCode);
}
/*******************************************************************************
** Getter for label
*******************************************************************************/
public String getLabel()
{
return (this.label);
}
/*******************************************************************************
** Setter for label
*******************************************************************************/
public void setLabel(String label)
{
this.label = label;
}
/*******************************************************************************
** Fluent setter for label
*******************************************************************************/
public ButtonValues withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for actionCode
*******************************************************************************/
public String getActionCode()
{
return (this.actionCode);
}
/*******************************************************************************
** Setter for actionCode
*******************************************************************************/
public void setActionCode(String actionCode)
{
this.actionCode = actionCode;
}
/*******************************************************************************
** Fluent setter for actionCode
*******************************************************************************/
public ButtonValues withActionCode(String actionCode)
{
this.actionCode = actionCode;
return (this);
}
/*******************************************************************************
** Getter for startIcon
*******************************************************************************/
public QIcon getStartIcon()
{
return (this.startIcon);
}
/*******************************************************************************
** Setter for startIcon
*******************************************************************************/
public void setStartIcon(QIcon startIcon)
{
this.startIcon = startIcon;
}
/*******************************************************************************
** Fluent setter for startIcon
*******************************************************************************/
public ButtonValues withStartIcon(QIcon startIcon)
{
this.startIcon = startIcon;
return (this);
}
/*******************************************************************************
** Getter for endIcon
*******************************************************************************/
public QIcon getEndIcon()
{
return (this.endIcon);
}
/*******************************************************************************
** Setter for endIcon
*******************************************************************************/
public void setEndIcon(QIcon endIcon)
{
this.endIcon = endIcon;
}
/*******************************************************************************
** Fluent setter for endIcon
*******************************************************************************/
public ButtonValues withEndIcon(QIcon endIcon)
{
this.endIcon = endIcon;
return (this);
}
/*******************************************************************************
** Getter for controlCode
*******************************************************************************/
public String getControlCode()
{
return (this.controlCode);
}
/*******************************************************************************
** Setter for controlCode
*******************************************************************************/
public void setControlCode(String controlCode)
{
this.controlCode = controlCode;
}
/*******************************************************************************
** Fluent setter for controlCode
*******************************************************************************/
public ButtonValues withControlCode(String controlCode)
{
this.controlCode = controlCode;
return (this);
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.dashboard.widgets.blocks.image;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
/*******************************************************************************
** block to display an image
*******************************************************************************/
public class ImageBlockData extends AbstractBlockWidgetData<ImageBlockData, ImageValues, BaseSlots, ImageStyles>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getBlockTypeName()
{
return "IMAGE";
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.dashboard.widgets.blocks.image;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseStyles;
/*******************************************************************************
**
*******************************************************************************/
public class ImageStyles extends BaseStyles
{
private String width;
private String height;
/*******************************************************************************
** Fluent setter for padding
*******************************************************************************/
@Override
public ImageStyles withPadding(Directional<String> padding)
{
super.setPadding(padding);
return (this);
}
/*******************************************************************************
** Getter for width
*******************************************************************************/
public String getWidth()
{
return (this.width);
}
/*******************************************************************************
** Setter for width
*******************************************************************************/
public void setWidth(String width)
{
this.width = width;
}
/*******************************************************************************
** Fluent setter for width
*******************************************************************************/
public ImageStyles withWidth(String width)
{
this.width = width;
return (this);
}
/*******************************************************************************
** Getter for height
*******************************************************************************/
public String getHeight()
{
return (this.height);
}
/*******************************************************************************
** Setter for height
*******************************************************************************/
public void setHeight(String height)
{
this.height = height;
}
/*******************************************************************************
** Fluent setter for height
*******************************************************************************/
public ImageStyles withHeight(String height)
{
this.height = height;
return (this);
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.dashboard.widgets.blocks.image;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
/*******************************************************************************
**
*******************************************************************************/
public class ImageValues implements BlockValuesInterface
{
private String path;
private String alt;
/*******************************************************************************
** Getter for path
*******************************************************************************/
public String getPath()
{
return (this.path);
}
/*******************************************************************************
** Setter for path
*******************************************************************************/
public void setPath(String path)
{
this.path = path;
}
/*******************************************************************************
** Fluent setter for path
*******************************************************************************/
public ImageValues withPath(String path)
{
this.path = path;
return (this);
}
/*******************************************************************************
** Getter for alt
*******************************************************************************/
public String getAlt()
{
return (this.alt);
}
/*******************************************************************************
** Setter for alt
*******************************************************************************/
public void setAlt(String alt)
{
this.alt = alt;
}
/*******************************************************************************
** Fluent setter for alt
*******************************************************************************/
public ImageValues withAlt(String alt)
{
this.alt = alt;
return (this);
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.dashboard.widgets.blocks.inputfield;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseStyles;
/*******************************************************************************
** block to display an input field - initially targeted at widgets-in-processes
*******************************************************************************/
public class InputFieldBlockData extends AbstractBlockWidgetData<InputFieldBlockData, InputFieldValues, BaseSlots, BaseStyles>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getBlockTypeName()
{
return "INPUT_FIELD";
}
}

View File

@ -0,0 +1,217 @@
/*
* 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.dashboard.widgets.blocks.inputfield;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class InputFieldValues implements BlockValuesInterface
{
private QFieldMetaData fieldMetaData;
private Boolean autoFocus;
private Boolean submitOnEnter;
private Boolean hideSoftKeyboard;
private String placeholder;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public InputFieldValues()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public InputFieldValues(QFieldMetaData fieldMetaData)
{
setFieldMetaData(fieldMetaData);
}
/*******************************************************************************
** Getter for fieldMetaData
*******************************************************************************/
public QFieldMetaData getFieldMetaData()
{
return (this.fieldMetaData);
}
/*******************************************************************************
** Setter for fieldMetaData
*******************************************************************************/
public void setFieldMetaData(QFieldMetaData fieldMetaData)
{
this.fieldMetaData = fieldMetaData;
}
/*******************************************************************************
** Fluent setter for fieldMetaData
*******************************************************************************/
public InputFieldValues withFieldMetaData(QFieldMetaData fieldMetaData)
{
this.fieldMetaData = fieldMetaData;
return (this);
}
/*******************************************************************************
** Getter for autoFocus
*******************************************************************************/
public Boolean getAutoFocus()
{
return (this.autoFocus);
}
/*******************************************************************************
** Setter for autoFocus
*******************************************************************************/
public void setAutoFocus(Boolean autoFocus)
{
this.autoFocus = autoFocus;
}
/*******************************************************************************
** Fluent setter for autoFocus
*******************************************************************************/
public InputFieldValues withAutoFocus(Boolean autoFocus)
{
this.autoFocus = autoFocus;
return (this);
}
/*******************************************************************************
** Getter for submitOnEnter
*******************************************************************************/
public Boolean getSubmitOnEnter()
{
return (this.submitOnEnter);
}
/*******************************************************************************
** Setter for submitOnEnter
*******************************************************************************/
public void setSubmitOnEnter(Boolean submitOnEnter)
{
this.submitOnEnter = submitOnEnter;
}
/*******************************************************************************
** Fluent setter for submitOnEnter
*******************************************************************************/
public InputFieldValues withSubmitOnEnter(Boolean submitOnEnter)
{
this.submitOnEnter = submitOnEnter;
return (this);
}
/*******************************************************************************
** Getter for placeholder
*******************************************************************************/
public String getPlaceholder()
{
return (this.placeholder);
}
/*******************************************************************************
** Setter for placeholder
*******************************************************************************/
public void setPlaceholder(String placeholder)
{
this.placeholder = placeholder;
}
/*******************************************************************************
** Fluent setter for placeholder
*******************************************************************************/
public InputFieldValues withPlaceholder(String placeholder)
{
this.placeholder = placeholder;
return (this);
}
/*******************************************************************************
** Getter for hideSoftKeyboard
*******************************************************************************/
public Boolean getHideSoftKeyboard()
{
return (this.hideSoftKeyboard);
}
/*******************************************************************************
** Setter for hideSoftKeyboard
*******************************************************************************/
public void setHideSoftKeyboard(Boolean hideSoftKeyboard)
{
this.hideSoftKeyboard = hideSoftKeyboard;
}
/*******************************************************************************
** Fluent setter for hideSoftKeyboard
*******************************************************************************/
public InputFieldValues withHideSoftKeyboard(Boolean hideSoftKeyboard)
{
this.hideSoftKeyboard = hideSoftKeyboard;
return (this);
}
}

View File

@ -30,4 +30,325 @@ import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStyles
*******************************************************************************/ *******************************************************************************/
public class TextStyles implements BlockStylesInterface public class TextStyles implements BlockStylesInterface
{ {
private String color;
private String format;
private String weight;
private String size;
/***************************************************************************
**
***************************************************************************/
public enum StandardColor
{
SUCCESS,
WARNING,
ERROR,
INFO,
MUTED
}
/***************************************************************************
**
***************************************************************************/
public enum StandardFormat
{
DEFAULT,
ALERT,
BANNER
}
/***************************************************************************
**
***************************************************************************/
public enum StandardSize
{
LARGEST,
HEADLINE,
TITLE,
BODY,
SMALLEST
}
/***************************************************************************
**
***************************************************************************/
public enum StandardWeight
{
EXTRA_LIGHT("extralight"),
THIN("thin"),
MEDIUM("medium"),
SEMI_BOLD("semibold"),
BLACK("black"),
BOLD("bold"),
EXTRA_BOLD("extrabold"),
W100("100"),
W200("200"),
W300("300"),
W400("400"),
W500("500"),
W600("600"),
W700("700"),
W800("800"),
W900("900");
private final String value;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
StandardWeight(String value)
{
this.value = value;
}
/*******************************************************************************
** Getter for value
**
*******************************************************************************/
public String getValue()
{
return value;
}
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public TextStyles()
{
}
/***************************************************************************
**
***************************************************************************/
public TextStyles(StandardColor standardColor)
{
setColor(standardColor);
}
/*******************************************************************************
** Getter for format
*******************************************************************************/
public String getFormat()
{
return (this.format);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(String format)
{
this.format = format;
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public TextStyles withFormat(String format)
{
this.format = format;
return (this);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(StandardFormat format)
{
this.format = format == null ? null : format.name().toLowerCase();
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public TextStyles withFormat(StandardFormat format)
{
this.setFormat(format);
return (this);
}
/*******************************************************************************
** Getter for weight
*******************************************************************************/
public String getWeight()
{
return (this.weight);
}
/*******************************************************************************
** Setter for weight
*******************************************************************************/
public void setWeight(String weight)
{
this.weight = weight;
}
/*******************************************************************************
** Fluent setter for weight
*******************************************************************************/
public TextStyles withWeight(String weight)
{
this.weight = weight;
return (this);
}
/*******************************************************************************
** Setter for weight
*******************************************************************************/
public void setWeight(StandardWeight weight)
{
setWeight(weight == null ? null : weight.getValue());
}
/*******************************************************************************
** Fluent setter for weight
*******************************************************************************/
public TextStyles withWeight(StandardWeight weight)
{
setWeight(weight);
return (this);
}
/*******************************************************************************
** Getter for size
*******************************************************************************/
public String getSize()
{
return (this.size);
}
/*******************************************************************************
** Setter for size
*******************************************************************************/
public void setSize(String size)
{
this.size = size;
}
/*******************************************************************************
** Fluent setter for size
*******************************************************************************/
public TextStyles withSize(String size)
{
this.size = size;
return (this);
}
/*******************************************************************************
** Setter for size
*******************************************************************************/
public void setSize(StandardSize size)
{
this.size = (size == null ? null : size.name().toLowerCase());
}
/*******************************************************************************
** Fluent setter for size
*******************************************************************************/
public TextStyles withSize(StandardSize size)
{
setSize(size);
return (this);
}
/*******************************************************************************
** Getter for color
*******************************************************************************/
public String getColor()
{
return (this.color);
}
/*******************************************************************************
** Setter for color
*******************************************************************************/
public void setColor(String color)
{
this.color = color;
}
/*******************************************************************************
** Fluent setter for color
*******************************************************************************/
public TextStyles withColor(String color)
{
this.color = color;
return (this);
}
/*******************************************************************************
** Setter for color
*******************************************************************************/
public void setColor(StandardColor color)
{
this.color = color == null ? null : color.name();
}
/*******************************************************************************
** Fluent setter for color
*******************************************************************************/
public TextStyles withColor(StandardColor color)
{
setColor(color);
return (this);
}
} }

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface; import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
/******************************************************************************* /*******************************************************************************
@ -32,6 +33,9 @@ public class TextValues implements BlockValuesInterface
{ {
private String text; private String text;
private QIcon startIcon;
private QIcon endIcon;
/******************************************************************************* /*******************************************************************************
@ -84,4 +88,66 @@ public class TextValues implements BlockValuesInterface
return (this); return (this);
} }
/*******************************************************************************
** Getter for startIcon
*******************************************************************************/
public QIcon getStartIcon()
{
return (this.startIcon);
}
/*******************************************************************************
** Setter for startIcon
*******************************************************************************/
public void setStartIcon(QIcon startIcon)
{
this.startIcon = startIcon;
}
/*******************************************************************************
** Fluent setter for startIcon
*******************************************************************************/
public TextValues withStartIcon(QIcon startIcon)
{
this.startIcon = startIcon;
return (this);
}
/*******************************************************************************
** Getter for endIcon
*******************************************************************************/
public QIcon getEndIcon()
{
return (this.endIcon);
}
/*******************************************************************************
** Setter for endIcon
*******************************************************************************/
public void setEndIcon(QIcon endIcon)
{
this.endIcon = endIcon;
}
/*******************************************************************************
** Fluent setter for endIcon
*******************************************************************************/
public TextValues withEndIcon(QIcon endIcon)
{
this.endIcon = endIcon;
return (this);
}
} }

View File

@ -22,9 +22,6 @@
package com.kingsrook.qqq.backend.core.model.metadata; package com.kingsrook.qqq.backend.core.model.metadata;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
/******************************************************************************* /*******************************************************************************
** Abstract class that knows how to produce meta data objects. Useful with ** Abstract class that knows how to produce meta data objects. Useful with
** MetaDataProducerHelper, to put point at a package full of these, and populate ** MetaDataProducerHelper, to put point at a package full of these, and populate

View File

@ -22,7 +22,6 @@
package com.kingsrook.qqq.backend.core.model.metadata; package com.kingsrook.qqq.backend.core.model.metadata;
import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
@ -30,14 +29,12 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; 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.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -51,8 +48,6 @@ public class MetaDataProducerHelper
private static Map<Class<?>, Integer> comparatorValuesByType = new HashMap<>(); private static Map<Class<?>, Integer> comparatorValuesByType = new HashMap<>();
private static Integer defaultComparatorValue; private static Integer defaultComparatorValue;
private static ImmutableSet<ClassPath.ClassInfo> topLevelClasses;
static static
{ {
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
@ -87,7 +82,7 @@ public class MetaDataProducerHelper
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// find all the meta data producer classes in (and under) the package // // find all the meta data producer classes in (and under) the package //
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
classesInPackage = getClassesInPackage(packageName); classesInPackage = ClassPathUtils.getClassesInPackage(packageName);
} }
catch(Exception e) catch(Exception e)
{ {
@ -176,51 +171,4 @@ public class MetaDataProducerHelper
} }
/*******************************************************************************
** from https://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection
** (since the original, from ChatGPT, didn't work in jars, despite GPT hallucinating that it would)
*******************************************************************************/
private static List<Class<?>> getClassesInPackage(String packageName) throws IOException
{
List<Class<?>> classes = new ArrayList<>();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
for(ClassPath.ClassInfo info : getTopLevelClasses(loader))
{
if(info.getName().startsWith(packageName))
{
classes.add(info.load());
}
}
return (classes);
}
/*******************************************************************************
**
*******************************************************************************/
private static ImmutableSet<ClassPath.ClassInfo> getTopLevelClasses(ClassLoader loader) throws IOException
{
if(topLevelClasses == null)
{
topLevelClasses = ClassPath.from(loader).getTopLevelClasses();
}
return (topLevelClasses);
}
/*******************************************************************************
**
*******************************************************************************/
public static void clearTopLevelClassCache()
{
topLevelClasses = null;
}
} }

View File

@ -1,6 +1,6 @@
/* /*
* QQQ - Low-code Application Framework for Engineers. * QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC * Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
@ -19,12 +19,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.kingsrook.qqq.backend.core.model; package com.kingsrook.qqq.backend.core.model.metadata;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/******************************************************************************* /*******************************************************************************

View File

@ -43,6 +43,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData; import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
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.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode; import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType; import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType;
@ -113,6 +114,8 @@ public class QInstance
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance(); private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone(); private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone();
private QCodeReference metaDataFilter = null;
////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////
// todo - lock down the object (no more changes allowed) after it's been validated? // // todo - lock down the object (no more changes allowed) after it's been validated? //
// if doing so, may need to copy all of the collections into read-only versions... // // if doing so, may need to copy all of the collections into read-only versions... //
@ -1485,4 +1488,35 @@ public class QInstance
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot); QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot);
} }
/*******************************************************************************
** Getter for metaDataFilter
*******************************************************************************/
public QCodeReference getMetaDataFilter()
{
return (this.metaDataFilter);
}
/*******************************************************************************
** Setter for metaDataFilter
*******************************************************************************/
public void setMetaDataFilter(QCodeReference metaDataFilter)
{
this.metaDataFilter = metaDataFilter;
}
/*******************************************************************************
** Fluent setter for metaDataFilter
*******************************************************************************/
public QInstance withMetaDataFilter(QCodeReference metaDataFilter)
{
this.metaDataFilter = metaDataFilter;
return (this);
}
} }

View File

@ -177,7 +177,7 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public QAuthenticationMetaData withVales(Map<String, String> values) public QAuthenticationMetaData withValues(Map<String, String> values)
{ {
this.values = values; this.values = values;
return (this); return (this);

View File

@ -0,0 +1,58 @@
/*
* 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.metadata.code;
/*******************************************************************************
** Specialized type of QCodeReference that takes a lambda function object.
**
** Originally intended for more concise setup of backend steps in tests - but,
** may be generally useful.
*******************************************************************************/
public class QCodeReferenceLambda<T> extends QCodeReference
{
private final T lambda;
/***************************************************************************
**
***************************************************************************/
public QCodeReferenceLambda(T lambda)
{
this.lambda = lambda;
this.setCodeType(QCodeType.JAVA);
this.setName("[Lambda:" + lambda.toString() + "]");
}
/*******************************************************************************
** Getter for lambda
**
*******************************************************************************/
public T getLambda()
{
return lambda;
}
}

View File

@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole; import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent; 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.model.metadata.security.FieldSecurityLock; import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -73,10 +74,12 @@ public class QFieldMetaData implements Cloneable
// propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" // // propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" //
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
private String displayFormat = "%s"; private String displayFormat = "%s";
private Serializable defaultValue; private Serializable defaultValue;
private String possibleValueSourceName;
private QQueryFilter possibleValueSourceFilter; private String possibleValueSourceName;
private QQueryFilter possibleValueSourceFilter;
private QPossibleValueSource inlinePossibleValueSource;
private Integer maxLength; private Integer maxLength;
private Set<FieldBehavior<?>> behaviors; private Set<FieldBehavior<?>> behaviors;
@ -1058,4 +1061,35 @@ public class QFieldMetaData implements Cloneable
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, this.helpContents); QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, this.helpContents);
} }
/*******************************************************************************
** Getter for inlinePossibleValueSource
*******************************************************************************/
public QPossibleValueSource getInlinePossibleValueSource()
{
return (this.inlinePossibleValueSource);
}
/*******************************************************************************
** Setter for inlinePossibleValueSource
*******************************************************************************/
public void setInlinePossibleValueSource(QPossibleValueSource inlinePossibleValueSource)
{
this.inlinePossibleValueSource = inlinePossibleValueSource;
}
/*******************************************************************************
** Fluent setter for inlinePossibleValueSource
*******************************************************************************/
public QFieldMetaData withInlinePossibleValueSource(QPossibleValueSource inlinePossibleValueSource)
{
this.inlinePossibleValueSource = inlinePossibleValueSource;
return (this);
}
} }

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -45,7 +46,7 @@ public class AppTreeNode
private String label; private String label;
private List<AppTreeNode> children; private List<AppTreeNode> children;
private String iconName; private QIcon icon;
@ -82,7 +83,7 @@ public class AppTreeNode
if(appChildMetaData.getIcon() != null) if(appChildMetaData.getIcon() != null)
{ {
// todo - propagate icons from parents, if they aren't set here... // todo - propagate icons from parents, if they aren't set here...
this.iconName = appChildMetaData.getIcon().getName(); this.icon = appChildMetaData.getIcon();
} }
} }
@ -138,7 +139,18 @@ public class AppTreeNode
*******************************************************************************/ *******************************************************************************/
public String getIconName() public String getIconName()
{ {
return iconName; return (icon == null ? null : icon.getName());
}
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
} }

View File

@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QSupplementalAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QSupplementalAppMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -45,7 +46,7 @@ public class QFrontendAppMetaData
{ {
private String name; private String name;
private String label; private String label;
private String iconName; private QIcon icon;
private List<String> widgets = new ArrayList<>(); private List<String> widgets = new ArrayList<>();
private List<AppTreeNode> children = new ArrayList<>(); private List<AppTreeNode> children = new ArrayList<>();
@ -56,6 +57,7 @@ public class QFrontendAppMetaData
private Map<String, QSupplementalAppMetaData> supplementalAppMetaData; private Map<String, QSupplementalAppMetaData> supplementalAppMetaData;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -63,11 +65,7 @@ public class QFrontendAppMetaData
{ {
this.name = appMetaData.getName(); this.name = appMetaData.getName();
this.label = appMetaData.getLabel(); this.label = appMetaData.getLabel();
this.icon = appMetaData.getIcon();
if(appMetaData.getIcon() != null)
{
this.iconName = appMetaData.getIcon().getName();
}
List<String> filteredWidgets = CollectionUtils.nonNullList(appMetaData.getWidgets()).stream().filter(n -> metaDataOutput.getWidgets().containsKey(n)).toList(); List<String> filteredWidgets = CollectionUtils.nonNullList(appMetaData.getWidgets()).stream().filter(n -> metaDataOutput.getWidgets().containsKey(n)).toList();
if(CollectionUtils.nullSafeHasContents(filteredWidgets)) if(CollectionUtils.nullSafeHasContents(filteredWidgets))
@ -81,6 +79,10 @@ public class QFrontendAppMetaData
List<String> filteredTables = CollectionUtils.nonNullList(section.getTables()).stream().filter(n -> metaDataOutput.getTables().containsKey(n)).toList(); List<String> filteredTables = CollectionUtils.nonNullList(section.getTables()).stream().filter(n -> metaDataOutput.getTables().containsKey(n)).toList();
List<String> filteredProcesses = CollectionUtils.nonNullList(section.getProcesses()).stream().filter(n -> metaDataOutput.getProcesses().containsKey(n)).toList(); List<String> filteredProcesses = CollectionUtils.nonNullList(section.getProcesses()).stream().filter(n -> metaDataOutput.getProcesses().containsKey(n)).toList();
List<String> filteredReports = CollectionUtils.nonNullList(section.getReports()).stream().filter(n -> metaDataOutput.getReports().containsKey(n)).toList(); List<String> filteredReports = CollectionUtils.nonNullList(section.getReports()).stream().filter(n -> metaDataOutput.getReports().containsKey(n)).toList();
//////////////////////////////////////////////////////
// only include the section if it has some contents //
//////////////////////////////////////////////////////
if(!filteredTables.isEmpty() || !filteredProcesses.isEmpty() || !filteredReports.isEmpty()) if(!filteredTables.isEmpty() || !filteredProcesses.isEmpty() || !filteredReports.isEmpty())
{ {
QAppSection clonedSection = section.clone(); QAppSection clonedSection = section.clone();
@ -174,18 +176,7 @@ public class QFrontendAppMetaData
*******************************************************************************/ *******************************************************************************/
public String getIconName() public String getIconName()
{ {
return iconName; return (icon == null ? null : icon.getName());
}
/*******************************************************************************
** Setter for iconName
**
*******************************************************************************/
public void setIconName(String iconName)
{
this.iconName = iconName;
} }
@ -235,4 +226,15 @@ public class QFrontendAppMetaData
{ {
return supplementalAppMetaData; return supplementalAppMetaData;
} }
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
}
} }

View File

@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehaviorForFron
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; 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.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent; 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; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -56,6 +57,7 @@ public class QFrontendFieldMetaData
private List<FieldAdornment> adornments; private List<FieldAdornment> adornments;
private List<QHelpContent> helpContents; private List<QHelpContent> helpContents;
private QPossibleValueSource inlinePossibleValueSource;
private List<FieldBehaviorForFrontend> behaviors; private List<FieldBehaviorForFrontend> behaviors;
@ -81,6 +83,7 @@ public class QFrontendFieldMetaData
this.adornments = fieldMetaData.getAdornments(); this.adornments = fieldMetaData.getAdornments();
this.defaultValue = fieldMetaData.getDefaultValue(); this.defaultValue = fieldMetaData.getDefaultValue();
this.helpContents = fieldMetaData.getHelpContents(); this.helpContents = fieldMetaData.getHelpContents();
this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource();
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors())) for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
{ {
@ -218,6 +221,17 @@ public class QFrontendFieldMetaData
/*******************************************************************************
** Getter for inlinePossibleValueSource
**
*******************************************************************************/
public QPossibleValueSource getInlinePossibleValueSource()
{
return inlinePossibleValueSource;
}
/******************************************************************************* /*******************************************************************************
** Getter for fieldBehaviors ** Getter for fieldBehaviors
** **

View File

@ -29,8 +29,10 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStateMachineStep;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -47,9 +49,10 @@ public class QFrontendProcessMetaData
private String tableName; private String tableName;
private boolean isHidden; private boolean isHidden;
private String iconName; private QIcon icon;
private List<QFrontendStepMetaData> frontendSteps; private List<QFrontendStepMetaData> frontendSteps;
private String stepFlow;
private boolean hasPermission; private boolean hasPermission;
@ -68,15 +71,27 @@ public class QFrontendProcessMetaData
this.label = processMetaData.getLabel(); this.label = processMetaData.getLabel();
this.tableName = processMetaData.getTableName(); this.tableName = processMetaData.getTableName();
this.isHidden = processMetaData.getIsHidden(); this.isHidden = processMetaData.getIsHidden();
this.stepFlow = processMetaData.getStepFlow().toString();
if(includeSteps) if(includeSteps)
{ {
if(CollectionUtils.nullSafeHasContents(processMetaData.getStepList())) if(CollectionUtils.nullSafeHasContents(processMetaData.getStepList()))
{ {
this.frontendSteps = processMetaData.getStepList().stream() this.frontendSteps = switch(processMetaData.getStepFlow())
.filter(QFrontendStepMetaData.class::isInstance) {
.map(QFrontendStepMetaData.class::cast) case LINEAR -> processMetaData.getStepList().stream()
.collect(Collectors.toList()); .filter(QFrontendStepMetaData.class::isInstance)
.map(QFrontendStepMetaData.class::cast)
.collect(Collectors.toList());
case STATE_MACHINE -> processMetaData.getAllSteps().values().stream()
.filter(QStateMachineStep.class::isInstance)
.map(QStateMachineStep.class::cast)
.flatMap(step -> step.getSubSteps().stream())
.filter(QFrontendStepMetaData.class::isInstance)
.map(QFrontendStepMetaData.class::cast)
.collect(Collectors.toList());
};
} }
else else
{ {
@ -84,10 +99,7 @@ public class QFrontendProcessMetaData
} }
} }
if(processMetaData.getIcon() != null) this.icon = processMetaData.getIcon();
{
this.iconName = processMetaData.getIcon().getName();
}
hasPermission = PermissionsHelper.hasProcessPermission(actionInput, name); hasPermission = PermissionsHelper.hasProcessPermission(actionInput, name);
} }
@ -166,7 +178,7 @@ public class QFrontendProcessMetaData
*******************************************************************************/ *******************************************************************************/
public String getIconName() public String getIconName()
{ {
return iconName; return icon == null ? null : icon.getName();
} }
@ -180,4 +192,25 @@ public class QFrontendProcessMetaData
return hasPermission; return hasPermission;
} }
/*******************************************************************************
** Getter for stepFlow
**
*******************************************************************************/
public String getStepFlow()
{
return stepFlow;
}
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
}
} }

View File

@ -24,7 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -40,6 +40,7 @@ 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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent; import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin; import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
@ -61,7 +62,7 @@ public class QFrontendTableMetaData
private String label; private String label;
private boolean isHidden; private boolean isHidden;
private String primaryKeyField; private String primaryKeyField;
private String iconName; private QIcon icon;
private Map<String, QFrontendFieldMetaData> fields; private Map<String, QFrontendFieldMetaData> fields;
private List<QFieldSection> sections; private List<QFieldSection> sections;
@ -156,10 +157,7 @@ public class QFrontendTableMetaData
} }
} }
if(tableMetaData.getIcon() != null) this.icon = tableMetaData.getIcon();
{
this.iconName = tableMetaData.getIcon().getName();
}
setCapabilities(backendForTable, tableMetaData); setCapabilities(backendForTable, tableMetaData);
@ -185,7 +183,7 @@ public class QFrontendTableMetaData
*******************************************************************************/ *******************************************************************************/
private void setCapabilities(QBackendMetaData backend, QTableMetaData table) private void setCapabilities(QBackendMetaData backend, QTableMetaData table)
{ {
Set<Capability> enabledCapabilities = new HashSet<>(); Set<Capability> enabledCapabilities = new LinkedHashSet<>();
for(Capability capability : Capability.values()) for(Capability capability : Capability.values())
{ {
if(table.isCapabilityEnabled(backend, capability)) if(table.isCapabilityEnabled(backend, capability))
@ -275,7 +273,7 @@ public class QFrontendTableMetaData
*******************************************************************************/ *******************************************************************************/
public String getIconName() public String getIconName()
{ {
return iconName; return (icon == null ? null : icon.getName());
} }
@ -397,4 +395,16 @@ public class QFrontendTableMetaData
{ {
return helpContents; return helpContents;
} }
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
}
} }

View File

@ -0,0 +1,72 @@
/*
* 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.metadata.possiblevalues;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.CriteriaMissingInputValueBehavior;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.FilterUseCase;
/*******************************************************************************
** FilterUseCase implementation for the ways that possible value searches
** are performed, and where we want to have different behaviors for criteria
** that are missing an input value. That is, either for a:
**
** - FORM - e.g., creating a new record, or in a process - where we want a
** missing filter value to basically block you from selecting a value in the
** PVS field - e.g., you must enter some other foreign-key value before choosing
** from this possible value - at least that's the use-case we know of now.
**
** - FILTER - e.g., a query screen - where there isn't really quite the same
** scenario of choosing that foreign-key value first - so, such a PVS should
** list all its values (e.g., a criteria missing an input value should be
** removed from the filter).
*******************************************************************************/
public enum PossibleValueSearchFilterUseCase implements FilterUseCase
{
FORM(CriteriaMissingInputValueBehavior.MAKE_NO_MATCHES),
FILTER(CriteriaMissingInputValueBehavior.REMOVE_FROM_FILTER);
private final CriteriaMissingInputValueBehavior defaultCriteriaMissingInputValueBehavior;
/***************************************************************************
**
***************************************************************************/
PossibleValueSearchFilterUseCase(CriteriaMissingInputValueBehavior defaultCriteriaMissingInputValueBehavior)
{
this.defaultCriteriaMissingInputValueBehavior = defaultCriteriaMissingInputValueBehavior;
}
/*******************************************************************************
** Getter for defaultCriteriaMissingInputValueBehavior
**
*******************************************************************************/
public CriteriaMissingInputValueBehavior getDefaultCriteriaMissingInputValueBehavior()
{
return defaultCriteriaMissingInputValueBehavior;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.metadata.processes;
/*******************************************************************************
** Possible ways the steps of a process can flow.
**
** LINEAR - (the default) - the list of steps in the process are executed in-order
**
** STATE_MACHINE - concept of "states", each which has a backend & frontend step;
** a backend step can (must?) set the field "stepState" (or "nextStepName") to
** say what the next (frontend) step is.
*******************************************************************************/
public enum ProcessStepFlow
{
LINEAR,
STATE_MACHINE
}

View File

@ -47,6 +47,8 @@ public class QFrontendStepMetaData extends QStepMetaData
private List<QFieldMetaData> recordListFields; private List<QFieldMetaData> recordListFields;
private Map<String, QFieldMetaData> formFieldMap; private Map<String, QFieldMetaData> formFieldMap;
private String format;
private List<QHelpContent> helpContents; private List<QHelpContent> helpContents;
@ -403,4 +405,35 @@ public class QFrontendStepMetaData extends QStepMetaData
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, this.helpContents); QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, this.helpContents);
} }
/*******************************************************************************
** Getter for format
*******************************************************************************/
public String getFormat()
{
return (this.format);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(String format)
{
this.format = format;
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public QFrontendStepMetaData withFormat(String format)
{
this.format = format;
return (this);
}
} }

View File

@ -57,6 +57,7 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
private Integer minInputRecords = null; private Integer minInputRecords = null;
private Integer maxInputRecords = null; private Integer maxInputRecords = null;
private ProcessStepFlow stepFlow = ProcessStepFlow.LINEAR;
private List<QStepMetaData> stepList; // these are the steps that are ran, by-default, in the order they are ran in private List<QStepMetaData> stepList; // these are the steps that are ran, by-default, in the order they are ran in
private Map<String, QStepMetaData> steps; // this is the full map of possible steps private Map<String, QStepMetaData> steps; // this is the full map of possible steps
@ -213,11 +214,10 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
/******************************************************************************* /***************************************************************************
** add a step to the stepList and map
** **
*******************************************************************************/ ***************************************************************************/
public QProcessMetaData addStep(QStepMetaData step) public QProcessMetaData withStep(QStepMetaData step)
{ {
int index = 0; int index = 0;
if(this.stepList != null) if(this.stepList != null)
@ -231,11 +231,23 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
/*******************************************************************************
** add a step to the stepList and map
**
*******************************************************************************/
@Deprecated(since = "withStep was added")
public QProcessMetaData addStep(QStepMetaData step)
{
return (withStep(step));
}
/******************************************************************************* /*******************************************************************************
** add a step to the stepList (at the specified index) and the step map ** add a step to the stepList (at the specified index) and the step map
** **
*******************************************************************************/ *******************************************************************************/
public QProcessMetaData addStep(int index, QStepMetaData step) public QProcessMetaData withStep(int index, QStepMetaData step)
{ {
if(this.stepList == null) if(this.stepList == null)
{ {
@ -260,11 +272,23 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
/*******************************************************************************
** add a step to the stepList (at the specified index) and the step map
**
*******************************************************************************/
@Deprecated(since = "withStep was added")
public QProcessMetaData addStep(int index, QStepMetaData step)
{
return (withStep(index, step));
}
/******************************************************************************* /*******************************************************************************
** add a step ONLY to the step map - NOT the list w/ default execution order. ** add a step ONLY to the step map - NOT the list w/ default execution order.
** **
*******************************************************************************/ *******************************************************************************/
public QProcessMetaData addOptionalStep(QStepMetaData step) public QProcessMetaData withOptionalStep(QStepMetaData step)
{ {
if(this.steps == null) if(this.steps == null)
{ {
@ -283,6 +307,18 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
/*******************************************************************************
** add a step ONLY to the step map - NOT the list w/ default execution order.
**
*******************************************************************************/
@Deprecated(since = "withOptionalStep was added")
public QProcessMetaData addOptionalStep(QStepMetaData step)
{
return (withOptionalStep(step));
}
/******************************************************************************* /*******************************************************************************
** Setter for stepList ** Setter for stepList
** **
@ -299,7 +335,26 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
*******************************************************************************/ *******************************************************************************/
public QStepMetaData getStep(String stepName) public QStepMetaData getStep(String stepName)
{ {
return (steps.get(stepName)); if(steps.containsKey(stepName))
{
return steps.get(stepName);
}
for(QStepMetaData step : steps.values())
{
if(step instanceof QStateMachineStep stateMachineStep)
{
for(QStepMetaData subStep : stateMachineStep.getSubSteps())
{
if(subStep.getName().equals(stepName))
{
return (subStep);
}
}
}
}
return (null);
} }
@ -780,4 +835,35 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
return (this); return (this);
} }
/*******************************************************************************
** Getter for stepFlow
*******************************************************************************/
public ProcessStepFlow getStepFlow()
{
return (this.stepFlow);
}
/*******************************************************************************
** Setter for stepFlow
*******************************************************************************/
public void setStepFlow(ProcessStepFlow stepFlow)
{
this.stepFlow = stepFlow;
}
/*******************************************************************************
** Fluent setter for stepFlow
*******************************************************************************/
public QProcessMetaData withStepFlow(ProcessStepFlow stepFlow)
{
this.stepFlow = stepFlow;
return (this);
}
} }

View File

@ -0,0 +1,188 @@
/*
* 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.metadata.processes;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** A step for a state-machine flow based Process.
**
** Consists of 1 or 2 sub-steps, which are frontend and/or backend.
*******************************************************************************/
public class QStateMachineStep extends QStepMetaData
{
private List<QStepMetaData> subSteps = new ArrayList<>();
private String defaultNextStepName;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
private QStateMachineStep(List<QStepMetaData> subSteps)
{
setStepType("stateMachine");
this.subSteps.addAll(subSteps);
}
/***************************************************************************
**
***************************************************************************/
public static QStateMachineStep frontendOnly(String name, QFrontendStepMetaData frontendStepMetaData)
{
if(!StringUtils.hasContent(frontendStepMetaData.getName()))
{
frontendStepMetaData.setName(name + ".frontend");
}
return (new QStateMachineStep(List.of(frontendStepMetaData)).withName(name));
}
/***************************************************************************
**
***************************************************************************/
public static QStateMachineStep backendOnly(String name, QBackendStepMetaData backendStepMetaData)
{
if(!StringUtils.hasContent(backendStepMetaData.getName()))
{
backendStepMetaData.setName(name + ".backend");
}
return (new QStateMachineStep(List.of(backendStepMetaData)).withName(name));
}
/***************************************************************************
**
***************************************************************************/
public static QStateMachineStep frontendThenBackend(String name, QFrontendStepMetaData frontendStepMetaData, QBackendStepMetaData backendStepMetaData)
{
if(!StringUtils.hasContent(frontendStepMetaData.getName()))
{
frontendStepMetaData.setName(name + ".frontend");
}
if(!StringUtils.hasContent(backendStepMetaData.getName()))
{
backendStepMetaData.setName(name + ".backend");
}
return (new QStateMachineStep(List.of(frontendStepMetaData, backendStepMetaData)).withName(name));
}
/***************************************************************************
**
***************************************************************************/
@Override
public QStateMachineStep withName(String name)
{
super.withName(name);
return (this);
}
/***************************************************************************
**
***************************************************************************/
@Override
public QStateMachineStep withLabel(String label)
{
super.withLabel(label);
return (this);
}
/*******************************************************************************
** Getter for subSteps
**
*******************************************************************************/
public List<QStepMetaData> getSubSteps()
{
return subSteps;
}
/*******************************************************************************
** Getter for defaultNextStepName
*******************************************************************************/
public String getDefaultNextStepName()
{
return (this.defaultNextStepName);
}
/*******************************************************************************
** Setter for defaultNextStepName
*******************************************************************************/
public void setDefaultNextStepName(String defaultNextStepName)
{
this.defaultNextStepName = defaultNextStepName;
}
/*******************************************************************************
** Fluent setter for defaultNextStepName
*******************************************************************************/
public QStateMachineStep withDefaultNextStepName(String defaultNextStepName)
{
this.defaultNextStepName = defaultNextStepName;
return (this);
}
/*******************************************************************************
** Get a list of all of the input fields used by this step (all of its sub-steps)
*******************************************************************************/
@JsonIgnore
@Override
public List<QFieldMetaData> getInputFields()
{
List<QFieldMetaData> rs = new ArrayList<>();
for(QStepMetaData subStep : subSteps)
{
rs.addAll(subStep.getInputFields());
}
return (rs);
}
}

View File

@ -23,7 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.sharing;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.processes.implementations.sharing.ShareScope; import com.kingsrook.qqq.backend.core.processes.implementations.sharing.ShareScope;

View File

@ -26,13 +26,13 @@ import java.io.Serializable;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; 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.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; 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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
@ -114,7 +114,7 @@ public class ScheduledReportSyncToScheduledJobProcess extends AbstractTableSyncT
scheduledJob = new ScheduledJob(); scheduledJob = new ScheduledJob();
scheduledJob.setLabel("Scheduled Report " + scheduledReport.getId()); scheduledJob.setLabel("Scheduled Report " + scheduledReport.getId());
scheduledJob.setDescription("Job to run Scheduled Report Id " + scheduledReport.getId() scheduledJob.setDescription("Job to run Scheduled Report Id " + scheduledReport.getId()
+ " (which runs Report Id " + scheduledReport.getSavedReportId() + ")"); + " (which runs Report Id " + scheduledReport.getSavedReportId() + ")");
scheduledJob.setSchedulerName(runBackendStepInput.getValueString(SCHEDULER_NAME_FIELD_NAME)); scheduledJob.setSchedulerName(runBackendStepInput.getValueString(SCHEDULER_NAME_FIELD_NAME));
scheduledJob.setType(ScheduledJobType.PROCESS.name()); scheduledJob.setType(ScheduledJobType.PROCESS.name());
scheduledJob.setForeignKeyType(getScheduledJobForeignKeyType()); scheduledJob.setForeignKeyType(getScheduledJobForeignKeyType());

View File

@ -247,10 +247,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
// allow customizer to do custom things here, if so desired // // allow customizer to do custom things here, if so desired //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
if(getCustomizer() != null) finalCustomizeSession(qInstance, qSession);
{
getCustomizer().finalCustomizeSession(qInstance, qSession);
}
return (qSession); return (qSession);
} }
@ -311,10 +308,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
// allow customizer to do custom things here, if so desired // // allow customizer to do custom things here, if so desired //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
if(getCustomizer() != null) finalCustomizeSession(qInstance, qSession);
{
getCustomizer().finalCustomizeSession(qInstance, qSession);
}
return (qSession); return (qSession);
} }
@ -360,6 +354,23 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
/***************************************************************************
**
***************************************************************************/
private void finalCustomizeSession(QInstance qInstance, QSession qSession)
{
if(getCustomizer() != null)
{
QContext.withTemporaryContext(QContext.capture(), () ->
{
QContext.setQSession(getChickenAndEggSession());
getCustomizer().finalCustomizeSession(qInstance, qSession);
});
}
}
/******************************************************************************* /*******************************************************************************
** Insert a session as a new record into userSession table ** Insert a session as a new record into userSession table
*******************************************************************************/ *******************************************************************************/

View File

@ -58,6 +58,7 @@ 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.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext; import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; 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.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin; 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.actions.tables.update.UpdateInput;
@ -170,6 +171,8 @@ public class MemoryRecordStore
Collection<QRecord> tableData = getTableData(input.getTable()).values(); Collection<QRecord> tableData = getTableData(input.getTable()).values();
List<QRecord> records = new ArrayList<>(); List<QRecord> records = new ArrayList<>();
QQueryFilter filter = clonedOrNewFilter(input.getFilter());
JoinsContext joinsContext = new JoinsContext(QContext.getQInstance(), input.getTableName(), input.getQueryJoins(), filter);
if(CollectionUtils.nullSafeHasContents(input.getQueryJoins())) if(CollectionUtils.nullSafeHasContents(input.getQueryJoins()))
{ {
tableData = buildJoinCrossProduct(input); tableData = buildJoinCrossProduct(input);
@ -185,7 +188,7 @@ public class MemoryRecordStore
qRecord.setTableName(input.getTableName()); qRecord.setTableName(input.getTableName());
} }
boolean recordMatches = BackendQueryFilterUtils.doesRecordMatch(input.getFilter(), qRecord); boolean recordMatches = BackendQueryFilterUtils.doesRecordMatch(input.getFilter(), joinsContext, qRecord);
if(recordMatches) if(recordMatches)
{ {
@ -224,8 +227,7 @@ public class MemoryRecordStore
*******************************************************************************/ *******************************************************************************/
private Collection<QRecord> buildJoinCrossProduct(QueryInput input) throws QException private Collection<QRecord> buildJoinCrossProduct(QueryInput input) throws QException
{ {
QInstance qInstance = QContext.getQInstance(); QInstance qInstance = QContext.getQInstance();
JoinsContext joinsContext = new JoinsContext(qInstance, input.getTableName(), input.getQueryJoins(), input.getFilter());
List<QRecord> crossProduct = new ArrayList<>(); List<QRecord> crossProduct = new ArrayList<>();
QTableMetaData leftTable = input.getTable(); QTableMetaData leftTable = input.getTable();
@ -373,7 +375,14 @@ public class MemoryRecordStore
///////////////////////////////////////////////// /////////////////////////////////////////////////
if(recordToInsert.getValue(primaryKeyField.getName()) == null && (primaryKeyField.getType().equals(QFieldType.INTEGER) || primaryKeyField.getType().equals(QFieldType.LONG))) if(recordToInsert.getValue(primaryKeyField.getName()) == null && (primaryKeyField.getType().equals(QFieldType.INTEGER) || primaryKeyField.getType().equals(QFieldType.LONG)))
{ {
recordToInsert.setValue(primaryKeyField.getName(), nextSerial++); if(primaryKeyField.getType().equals(QFieldType.LONG))
{
recordToInsert.setValue(primaryKeyField.getName(), (nextSerial++).longValue());
}
else
{
recordToInsert.setValue(primaryKeyField.getName(), nextSerial++);
}
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -383,7 +392,7 @@ public class MemoryRecordStore
{ {
nextSerial = recordToInsert.getValueInteger(primaryKeyField.getName()) + 1; nextSerial = recordToInsert.getValueInteger(primaryKeyField.getName()) + 1;
} }
else if(primaryKeyField.getType().equals(QFieldType.LONG) && recordToInsert.getValueLong(primaryKeyField.getName()) > nextSerial) else if(primaryKeyField.getType().equals(QFieldType.LONG) && recordToInsert.getValueInteger(primaryKeyField.getName()) > nextSerial)
{ {
////////////////////////////////////// //////////////////////////////////////
// todo - mmm, could overflow here? // // todo - mmm, could overflow here? //
@ -901,4 +910,21 @@ public class MemoryRecordStore
return ValueUtils.getValueAsFieldType(fieldType, aggregateValue); return ValueUtils.getValueAsFieldType(fieldType, aggregateValue);
} }
/*******************************************************************************
** Either clone the input filter (so we can change it safely), or return a new blank filter.
*******************************************************************************/
protected QQueryFilter clonedOrNewFilter(QQueryFilter filter)
{
if(filter == null)
{
return (new QQueryFilter());
}
else
{
return (filter.clone());
}
}
} }

View File

@ -34,14 +34,18 @@ import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; 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.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; 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.actions.tables.query.expressions.AbstractFilterExpression;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.NotImplementedException;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/******************************************************************************* /*******************************************************************************
@ -58,8 +62,22 @@ public class BackendQueryFilterUtils
/******************************************************************************* /*******************************************************************************
** Test if record matches filter. ** Test if record matches filter.
*******************************************************************************/ ******************************************************************************/
public static boolean doesRecordMatch(QQueryFilter filter, QRecord qRecord) public static boolean doesRecordMatch(QQueryFilter filter, QRecord qRecord)
{
return doesRecordMatch(filter, null, qRecord);
}
/*******************************************************************************
** Test if record matches filter - where we are executing a QueryAction, and
** we have a JoinsContext. Note, if you don't have one of those, you can call
** the overload of this method that doesn't take one, and everything downstream
** /should/ be tolerant of that being absent... You just might not have the
** benefit of things like knowing field-meta-data associated with criteria...
*******************************************************************************/
public static boolean doesRecordMatch(QQueryFilter filter, JoinsContext joinsContext, QRecord qRecord)
{ {
if(filter == null || !filter.hasAnyCriteria()) if(filter == null || !filter.hasAnyCriteria())
{ {
@ -97,7 +115,36 @@ public class BackendQueryFilterUtils
} }
} }
boolean criterionMatches = doesCriteriaMatch(criterion, fieldName, value); ///////////////////////////////////////////////////////////////////////////////////////////////
// Test if this criteria(on) matches the record. //
// As criteria have become more sophisticated over time, we would like to be able to know //
// what field they are for. In general, we'll try to get that from the query's JoinsContext. //
// But, in some scenarios, that isn't available - so - be safe and defer to simpler methods //
// that might not have the full field, when necessary. //
///////////////////////////////////////////////////////////////////////////////////////////////
Boolean criterionMatches = null;
if(joinsContext != null)
{
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = null;
try
{
fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(criterion.getFieldName());
}
catch(Exception e)
{
LOG.debug("Exception getting field from joinsContext", e, logPair("fieldName", criterion.getFieldName()));
}
if(fieldAndTableNameOrAlias != null)
{
criterionMatches = doesCriteriaMatch(criterion, fieldAndTableNameOrAlias.field(), value);
}
}
if(criterionMatches == null)
{
criterionMatches = doesCriteriaMatch(criterion, criterion.getFieldName(), value);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// add this new value to the existing recordMatches value - and if we can short circuit the remaining checks, do so. // // add this new value to the existing recordMatches value - and if we can short circuit the remaining checks, do so. //
@ -131,11 +178,24 @@ public class BackendQueryFilterUtils
/***************************************************************************
**
***************************************************************************/
public static boolean doesCriteriaMatch(QFilterCriteria criterion, String fieldName, Serializable value)
{
QFieldMetaData field = new QFieldMetaData(fieldName, ValueUtils.inferQFieldTypeFromValue(value, QFieldType.STRING));
return doesCriteriaMatch(criterion, field, value);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public static boolean doesCriteriaMatch(QFilterCriteria criterion, String fieldName, Serializable value) private static boolean doesCriteriaMatch(QFilterCriteria criterion, QFieldMetaData field, Serializable value)
{ {
String fieldName = field == null ? "__unknownField" : field.getName();
ListIterator<Serializable> valueListIterator = criterion.getValues().listIterator(); ListIterator<Serializable> valueListIterator = criterion.getValues().listIterator();
while(valueListIterator.hasNext()) while(valueListIterator.hasNext())
{ {
@ -144,7 +204,7 @@ public class BackendQueryFilterUtils
{ {
try try
{ {
valueListIterator.set(expression.evaluate()); valueListIterator.set(expression.evaluate(field));
} }
catch(QException qe) catch(QException qe)
{ {

View File

@ -36,7 +36,6 @@ import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
@ -47,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.NowWithOffset; import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.NowWithOffset;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; 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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.HtmlWrapper; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.HtmlWrapper;
@ -158,8 +158,8 @@ public class HealBadRecordAutomationStatusesProcessStep implements BackendStep,
@Override @Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{ {
int recordsUpdated = 0; int recordsUpdated = 0;
boolean isReview = "preview".equals(runBackendStepInput.getStepName()); boolean isReview = "preview".equals(runBackendStepInput.getStepName());
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// if a table name is given, validate it, and run for just that table // // if a table name is given, validate it, and run for just that table //

View File

@ -28,9 +28,9 @@ import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomati
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;

View File

@ -40,6 +40,10 @@ import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwith
*******************************************************************************/ *******************************************************************************/
public class ExtractViaBasepullQueryStep extends ExtractViaQueryStep implements BasepullExtractStepInterface public class ExtractViaBasepullQueryStep extends ExtractViaQueryStep implements BasepullExtractStepInterface
{ {
protected static final String SECONDS_TO_SUBTRACT_FROM_THIS_RUN_TIME_KEY = "secondsToSubtractFromThisRunTimeForTimestampQuery";
protected static final String SECONDS_TO_SUBTRACT_FROM_LAST_RUN_TIME_KEY = "secondsToSubtractFromLastRunTimeForTimestampQuery";
/******************************************************************************* /*******************************************************************************
** **
@ -124,7 +128,8 @@ public class ExtractViaBasepullQueryStep extends ExtractViaQueryStep implements
*******************************************************************************/ *******************************************************************************/
protected String getLastRunTimeString(RunBackendStepInput runBackendStepInput) throws QException protected String getLastRunTimeString(RunBackendStepInput runBackendStepInput) throws QException
{ {
Instant lastRunTime = runBackendStepInput.getBasepullLastRunTime(); Instant lastRunTime = runBackendStepInput.getBasepullLastRunTime();
Instant updatedRunTime = lastRunTime;
////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////
// allow the timestamps to be adjusted by the specified number of seconds. // // allow the timestamps to be adjusted by the specified number of seconds. //
@ -135,10 +140,19 @@ public class ExtractViaBasepullQueryStep extends ExtractViaQueryStep implements
Serializable basepullConfigurationValue = runBackendStepInput.getValue(RunProcessAction.BASEPULL_CONFIGURATION); Serializable basepullConfigurationValue = runBackendStepInput.getValue(RunProcessAction.BASEPULL_CONFIGURATION);
if(basepullConfigurationValue instanceof BasepullConfiguration basepullConfiguration && basepullConfiguration.getSecondsToSubtractFromLastRunTimeForTimestampQuery() != null) if(basepullConfigurationValue instanceof BasepullConfiguration basepullConfiguration && basepullConfiguration.getSecondsToSubtractFromLastRunTimeForTimestampQuery() != null)
{ {
lastRunTime = lastRunTime.minusSeconds(basepullConfiguration.getSecondsToSubtractFromLastRunTimeForTimestampQuery()); updatedRunTime = lastRunTime.minusSeconds(basepullConfiguration.getSecondsToSubtractFromLastRunTimeForTimestampQuery());
} }
return (lastRunTime.toString()); //////////////////////////////////////////////////////////////
// if an override was found in the params, use that instead //
//////////////////////////////////////////////////////////////
if(runBackendStepInput.getValueString(SECONDS_TO_SUBTRACT_FROM_LAST_RUN_TIME_KEY) != null)
{
int secondsBack = Integer.parseInt(runBackendStepInput.getValueString(SECONDS_TO_SUBTRACT_FROM_LAST_RUN_TIME_KEY));
updatedRunTime = lastRunTime.minusSeconds(secondsBack);
}
return (updatedRunTime.toString());
} }
@ -148,14 +162,24 @@ public class ExtractViaBasepullQueryStep extends ExtractViaQueryStep implements
*******************************************************************************/ *******************************************************************************/
protected String getThisRunTimeString(RunBackendStepInput runBackendStepInput) throws QException protected String getThisRunTimeString(RunBackendStepInput runBackendStepInput) throws QException
{ {
Instant thisRunTime = runBackendStepInput.getValueInstant(RunProcessAction.BASEPULL_THIS_RUNTIME_KEY); Instant thisRunTime = runBackendStepInput.getValueInstant(RunProcessAction.BASEPULL_THIS_RUNTIME_KEY);
Instant updatedRunTime = thisRunTime;
Serializable basepullConfigurationValue = runBackendStepInput.getValue(RunProcessAction.BASEPULL_CONFIGURATION); Serializable basepullConfigurationValue = runBackendStepInput.getValue(RunProcessAction.BASEPULL_CONFIGURATION);
if(basepullConfigurationValue instanceof BasepullConfiguration basepullConfiguration && basepullConfiguration.getSecondsToSubtractFromThisRunTimeForTimestampQuery() != null) if(basepullConfigurationValue instanceof BasepullConfiguration basepullConfiguration && basepullConfiguration.getSecondsToSubtractFromThisRunTimeForTimestampQuery() != null)
{ {
thisRunTime = thisRunTime.minusSeconds(basepullConfiguration.getSecondsToSubtractFromThisRunTimeForTimestampQuery()); updatedRunTime = thisRunTime.minusSeconds(basepullConfiguration.getSecondsToSubtractFromThisRunTimeForTimestampQuery());
} }
return (thisRunTime.toString()); //////////////////////////////////////////////////////////////
// if an override was found in the params, use that instead //
//////////////////////////////////////////////////////////////
if(runBackendStepInput.getValueString(SECONDS_TO_SUBTRACT_FROM_THIS_RUN_TIME_KEY) != null)
{
int secondsBack = Integer.parseInt(runBackendStepInput.getValueString(SECONDS_TO_SUBTRACT_FROM_THIS_RUN_TIME_KEY));
updatedRunTime = thisRunTime.minusSeconds(secondsBack);
}
return (updatedRunTime.toString());
} }
} }

View File

@ -176,9 +176,9 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state // // propagate data from inner-step state to process-level step state //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
if(postRunOutput.getUpdatedFrontendStepList() != null) if(postRunOutput.getProcessMetaDataAdjustment() != null)
{ {
runBackendStepOutput.setUpdatedFrontendStepList(postRunOutput.getUpdatedFrontendStepList()); runBackendStepOutput.setProcessMetaDataAdjustment(postRunOutput.getProcessMetaDataAdjustment());
} }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -281,10 +281,10 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state // // propagate data from inner-step state to process-level step state //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
if(streamedBackendStepOutput.getUpdatedFrontendStepList() != null) if(streamedBackendStepOutput.getProcessMetaDataAdjustment() != null)
{ {
runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList()); runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList());
runBackendStepOutput.setUpdatedFrontendStepList(streamedBackendStepOutput.getUpdatedFrontendStepList()); runBackendStepOutput.getProcessState().setProcessMetaDataAdjustment(streamedBackendStepOutput.getProcessMetaDataAdjustment());
} }
//////////////////////////////////////////////// ////////////////////////////////////////////////
@ -299,10 +299,10 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state // // propagate data from inner-step state to process-level step state //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
if(streamedBackendStepOutput.getUpdatedFrontendStepList() != null) if(streamedBackendStepOutput.getProcessMetaDataAdjustment() != null)
{ {
runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList()); runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList());
runBackendStepOutput.setUpdatedFrontendStepList(streamedBackendStepOutput.getUpdatedFrontendStepList()); runBackendStepOutput.getProcessState().setProcessMetaDataAdjustment(streamedBackendStepOutput.getProcessMetaDataAdjustment());
} }
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////

View File

@ -148,9 +148,9 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state // // propagate data from inner-step state to process-level step state //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
if(postRunOutput.getUpdatedFrontendStepList() != null) if(postRunOutput.getProcessMetaDataAdjustment() != null)
{ {
runBackendStepOutput.setUpdatedFrontendStepList(postRunOutput.getUpdatedFrontendStepList()); runBackendStepOutput.setProcessMetaDataAdjustment(postRunOutput.getProcessMetaDataAdjustment());
} }
} }
@ -219,10 +219,9 @@ public class StreamedETLPreviewStep extends BaseStreamedETLStep implements Backe
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state // // propagate data from inner-step state to process-level step state //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
if(streamedBackendStepOutput.getUpdatedFrontendStepList() != null) if(streamedBackendStepOutput.getProcessMetaDataAdjustment() != null)
{ {
runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList()); runBackendStepOutput.getProcessState().setProcessMetaDataAdjustment(streamedBackendStepOutput.getProcessMetaDataAdjustment());
runBackendStepOutput.setUpdatedFrontendStepList(streamedBackendStepOutput.getUpdatedFrontendStepList());
} }
//////////////////////////////////////////////////// ////////////////////////////////////////////////////

View File

@ -145,9 +145,9 @@ public class StreamedETLValidateStep extends BaseStreamedETLStep implements Back
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state // // propagate data from inner-step state to process-level step state //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
if(postRunOutput.getUpdatedFrontendStepList() != null) if(postRunOutput.getProcessMetaDataAdjustment() != null)
{ {
runBackendStepOutput.setUpdatedFrontendStepList(postRunOutput.getUpdatedFrontendStepList()); runBackendStepOutput.setProcessMetaDataAdjustment(postRunOutput.getProcessMetaDataAdjustment());
} }
} }
@ -183,10 +183,9 @@ public class StreamedETLValidateStep extends BaseStreamedETLStep implements Back
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// propagate data from inner-step state to process-level step state // // propagate data from inner-step state to process-level step state //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
if(streamedBackendStepOutput.getUpdatedFrontendStepList() != null) if(streamedBackendStepOutput.getProcessMetaDataAdjustment() != null)
{ {
runBackendStepOutput.getProcessState().setStepList(streamedBackendStepOutput.getProcessState().getStepList()); runBackendStepOutput.setProcessMetaDataAdjustment(streamedBackendStepOutput.getProcessMetaDataAdjustment());
runBackendStepOutput.setUpdatedFrontendStepList(streamedBackendStepOutput.getUpdatedFrontendStepList());
} }
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////

View File

@ -23,10 +23,10 @@ package com.kingsrook.qqq.backend.core.processes.implementations.savedreports;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; 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.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;
@ -58,7 +58,7 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
public static final String FIELD_NAME_STORAGE_TABLE_NAME = "storageTableName"; public static final String FIELD_NAME_STORAGE_TABLE_NAME = "storageTableName";
public static final String FIELD_NAME_REPORT_FORMAT = "reportFormat"; public static final String FIELD_NAME_REPORT_FORMAT = "reportFormat";
public static final String FIELD_NAME_EMAIL_ADDRESS = "reportDestinationEmailAddress"; public static final String FIELD_NAME_EMAIL_ADDRESS = "reportDestinationEmailAddress";
public static final String FIELD_NAME_EMAIL_SUBJECT = "emailSubject"; public static final String FIELD_NAME_EMAIL_SUBJECT = "emailSubject";

View File

@ -23,7 +23,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.savedreports;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;

View File

@ -27,12 +27,12 @@ import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction; import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
@ -87,9 +87,9 @@ public class DeleteSharedRecordProcess implements BackendStep, MetaDataProducerI
@Override @Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{ {
String tableName = runBackendStepInput.getValueString("tableName"); String tableName = runBackendStepInput.getValueString("tableName");
String recordIdString = runBackendStepInput.getValueString("recordId"); String recordIdString = runBackendStepInput.getValueString("recordId");
Integer shareId = runBackendStepInput.getValueInteger("shareId"); Integer shareId = runBackendStepInput.getValueInteger("shareId");
Objects.requireNonNull(tableName, "Missing required input: tableName"); Objects.requireNonNull(tableName, "Missing required input: tableName");
Objects.requireNonNull(recordIdString, "Missing required input: recordId"); Objects.requireNonNull(recordIdString, "Missing required input: recordId");

View File

@ -28,12 +28,12 @@ import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; 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.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;

View File

@ -37,7 +37,6 @@ import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
@ -47,6 +46,7 @@ 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.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; 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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
@ -134,7 +134,7 @@ public class GetSharedRecordsProcess implements BackendStep, MetaDataProducerInt
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// iterate results, building QRecords to output - note - we'll need to collect ids, then look them up in audience-source tables // // iterate results, building QRecords to output - note - we'll need to collect ids, then look them up in audience-source tables //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ArrayList<QRecord> resultList = new ArrayList<>(); ArrayList<QRecord> resultList = new ArrayList<>();
ListingHash<String, Serializable> audienceIds = new ListingHash<>(); ListingHash<String, Serializable> audienceIds = new ListingHash<>();
for(QRecord record : queryOutput.getRecords()) for(QRecord record : queryOutput.getRecords())
{ {

View File

@ -32,13 +32,13 @@ import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput; import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; 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.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;

View File

@ -23,6 +23,9 @@ package com.kingsrook.qqq.backend.core.processes.implementations.tablesync;
import java.io.Serializable; import java.io.Serializable;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -35,6 +38,7 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator; import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -53,6 +57,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; 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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep; import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess; import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.general.StandardProcessSummaryLineProducer; import com.kingsrook.qqq.backend.core.processes.implementations.general.StandardProcessSummaryLineProducer;
@ -72,33 +77,33 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
*******************************************************************************/ *******************************************************************************/
public abstract class AbstractTableSyncTransformStep extends AbstractTransformStep public abstract class AbstractTableSyncTransformStep extends AbstractTransformStep
{ {
private static final QLogger LOG = QLogger.getLogger(AbstractTableSyncTransformStep.class); protected static final QLogger LOG = QLogger.getLogger(AbstractTableSyncTransformStep.class);
private ProcessSummaryLine okToInsert = StandardProcessSummaryLineProducer.getOkToInsertLine(); protected ProcessSummaryLine okToInsert = StandardProcessSummaryLineProducer.getOkToInsertLine();
private ProcessSummaryLine okToUpdate = StandardProcessSummaryLineProducer.getOkToUpdateLine(); protected ProcessSummaryLine okToUpdate = StandardProcessSummaryLineProducer.getOkToUpdateLine();
private ProcessSummaryLine willNotInsert = new ProcessSummaryLine(Status.INFO) protected ProcessSummaryLine willNotInsert = new ProcessSummaryLine(Status.INFO)
.withMessageSuffix("because this process is not configured to insert records.") .withMessageSuffix("because this process is not configured to insert records.")
.withSingularFutureMessage("will not be inserted ") .withSingularFutureMessage("will not be inserted ")
.withPluralFutureMessage("will not be inserted ") .withPluralFutureMessage("will not be inserted ")
.withSingularPastMessage("was not inserted ") .withSingularPastMessage("was not inserted ")
.withPluralPastMessage("were not inserted "); .withPluralPastMessage("were not inserted ");
private ProcessSummaryLine willNotUpdate = new ProcessSummaryLine(Status.INFO) protected ProcessSummaryLine willNotUpdate = new ProcessSummaryLine(Status.INFO)
.withMessageSuffix("because this process is not configured to update records.") .withMessageSuffix("because this process is not configured to update records.")
.withSingularFutureMessage("will not be updated ") .withSingularFutureMessage("will not be updated ")
.withPluralFutureMessage("will not be updated ") .withPluralFutureMessage("will not be updated ")
.withSingularPastMessage("was not updated ") .withSingularPastMessage("was not updated ")
.withPluralPastMessage("were not updated "); .withPluralPastMessage("were not updated ");
private ProcessSummaryLine errorMissingKeyField = new ProcessSummaryLine(Status.ERROR) protected ProcessSummaryLine errorMissingKeyField = new ProcessSummaryLine(Status.ERROR)
.withMessageSuffix("missing a value for the key field.") .withMessageSuffix("missing a value for the key field.")
.withSingularFutureMessage("will not be synced, because it is ") .withSingularFutureMessage("will not be synced, because it is ")
.withPluralFutureMessage("will not be synced, because they are ") .withPluralFutureMessage("will not be synced, because they are ")
.withSingularPastMessage("was not synced, because it is ") .withSingularPastMessage("was not synced, because it is ")
.withPluralPastMessage("were not synced, because they are "); .withPluralPastMessage("were not synced, because they are ");
private ProcessSummaryLine unspecifiedError = new ProcessSummaryLine(Status.ERROR) protected ProcessSummaryLine unspecifiedError = new ProcessSummaryLine(Status.ERROR)
.withMessageSuffix("of an unexpected error: ") .withMessageSuffix("of an unexpected error: ")
.withSingularFutureMessage("will not be synced, ") .withSingularFutureMessage("will not be synced, ")
.withPluralFutureMessage("will not be synced, ") .withPluralFutureMessage("will not be synced, ")
@ -109,7 +114,11 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
protected RunBackendStepOutput runBackendStepOutput = null; protected RunBackendStepOutput runBackendStepOutput = null;
protected RecordLookupHelper recordLookupHelper = null; protected RecordLookupHelper recordLookupHelper = null;
private QPossibleValueTranslator possibleValueTranslator; protected QPossibleValueTranslator possibleValueTranslator;
protected static final String SYNC_TABLE_PERFORM_INSERTS_KEY = "syncTablePerformInsertsKey";
protected static final String SYNC_TABLE_PERFORM_UPDATES_KEY = "syncTablePerformUpdatesKey";
protected static final String LOG_TRANSFORM_RESULTS = "logTransformResults";
@ -214,6 +223,7 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
{ {
if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords())) if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords()))
{ {
LOG.info("No input records were found.");
return; return;
} }
@ -222,6 +232,19 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
SyncProcessConfig config = getSyncProcessConfig(); SyncProcessConfig config = getSyncProcessConfig();
////////////////////////////////////////////////////////////
// see if these fields have been updated via input fields //
////////////////////////////////////////////////////////////
if(runBackendStepInput.getValueString(SYNC_TABLE_PERFORM_INSERTS_KEY) != null)
{
boolean performInserts = Boolean.parseBoolean(runBackendStepInput.getValueString(SYNC_TABLE_PERFORM_INSERTS_KEY));
config = new SyncProcessConfig(config.sourceTable, config.sourceTableKeyField, config.destinationTable, config.destinationTableForeignKey, performInserts, config.performUpdates);
}
if(runBackendStepInput.getValueString(SYNC_TABLE_PERFORM_UPDATES_KEY) != null)
{
boolean performUpdates = Boolean.parseBoolean(runBackendStepInput.getValueString(SYNC_TABLE_PERFORM_UPDATES_KEY));
config = new SyncProcessConfig(config.sourceTable, config.sourceTableKeyField, config.destinationTable, config.destinationTableForeignKey, config.performUpdates, performUpdates);
}
String sourceTableKeyField = config.sourceTableKeyField; String sourceTableKeyField = config.sourceTableKeyField;
String destinationTableForeignKeyField = config.destinationTableForeignKey; String destinationTableForeignKeyField = config.destinationTableForeignKey;
String destinationTableName = config.destinationTable; String destinationTableName = config.destinationTable;
@ -371,9 +394,63 @@ public abstract class AbstractTableSyncTransformStep extends AbstractTransformSt
possibleValueTranslator.translatePossibleValuesInRecords(QContext.getQInstance().getTable(destinationTableName), runBackendStepOutput.getRecords()); possibleValueTranslator.translatePossibleValuesInRecords(QContext.getQInstance().getTable(destinationTableName), runBackendStepOutput.getRecords());
} }
} }
if(Boolean.parseBoolean(runBackendStepInput.getValueString(LOG_TRANSFORM_RESULTS)))
{
logResults(runBackendStepInput, config);
}
} }
/*******************************************************************************
** Log results of transformation
**
*******************************************************************************/
protected void logResults(RunBackendStepInput runBackendStepInput, SyncProcessConfig syncProcessConfig)
{
String timezone = QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE);
if(timezone == null)
{
timezone = QContext.getQInstance().getDefaultTimeZoneId();
}
Instant lastRunTime = Instant.now();
if(runBackendStepInput.getBasepullLastRunTime() != null)
{
lastRunTime = runBackendStepInput.getBasepullLastRunTime();
}
ZonedDateTime dateTime = lastRunTime.atZone(ZoneId.of(timezone));
if(syncProcessConfig.performInserts)
{
if(okToInsert.getCount() == 0)
{
LOG.info("No Records were found to insert since " + QValueFormatter.formatDateTimeWithZone(dateTime) + ".");
}
else
{
String pluralized = okToInsert.getCount() > 1 ? " Records were " : " Record was ";
LOG.info(okToInsert.getCount() + pluralized + " found to insert since " + QValueFormatter.formatDateTimeWithZone(dateTime) + ".", logPair("primaryKeys", okToInsert.getPrimaryKeys()));
}
}
if(syncProcessConfig.performUpdates)
{
if(okToUpdate.getCount() == 0)
{
LOG.info("No Records were found to update since " + QValueFormatter.formatDateTimeWithZone(dateTime) + ".");
}
else
{
String pluralized = okToUpdate.getCount() > 1 ? " Records were " : " Record was ";
LOG.info(okToUpdate.getCount() + pluralized + " found to update since " + QValueFormatter.formatDateTimeWithZone(dateTime) + ".", logPair("primaryKeys", okToInsert.getPrimaryKeys()));
}
}
}
/******************************************************************************* /*******************************************************************************
** Given a source record, extract what we'll use as its key from it. ** Given a source record, extract what we'll use as its key from it.
** **

View File

@ -24,7 +24,7 @@ package com.kingsrook.qqq.backend.core.processes.locks;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; 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.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.JoinOn;

View File

@ -25,9 +25,9 @@ package com.kingsrook.qqq.backend.core.scheduler.processes;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;

View File

@ -25,9 +25,9 @@ package com.kingsrook.qqq.backend.core.scheduler.processes;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;

View File

@ -25,9 +25,9 @@ package com.kingsrook.qqq.backend.core.scheduler.processes;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;

View File

@ -25,9 +25,9 @@ package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;

View File

@ -25,10 +25,10 @@ package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
import java.util.List; import java.util.List;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.QFieldMetaData;

View File

@ -25,9 +25,9 @@ package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetHtmlLine;

View File

@ -25,10 +25,10 @@ package com.kingsrook.qqq.backend.core.scheduler.quartz.processes;
import java.util.List; import java.util.List;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.QFieldMetaData;

View File

@ -0,0 +1,87 @@
/*
* 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.utils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
/*******************************************************************************
** Utilities for reading classes - e.g., finding all in a package
*******************************************************************************/
@SuppressWarnings("ALL") // the api we're using here, from google, is marked Beta
public class ClassPathUtils
{
private static ImmutableSet<ClassPath.ClassInfo> topLevelClasses;
/*******************************************************************************
** from https://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection
**
*******************************************************************************/
public static List<Class<?>> getClassesInPackage(String packageName) throws IOException
{
List<Class<?>> classes = new ArrayList<>();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
for(ClassPath.ClassInfo info : getTopLevelClasses(loader))
{
if(info.getName().startsWith(packageName))
{
classes.add(info.load());
}
}
return (classes);
}
/*******************************************************************************
**
*******************************************************************************/
private static ImmutableSet<ClassPath.ClassInfo> getTopLevelClasses(ClassLoader loader) throws IOException
{
if(topLevelClasses == null)
{
topLevelClasses = ClassPath.from(loader).getTopLevelClasses();
}
return (topLevelClasses);
}
/*******************************************************************************
**
*******************************************************************************/
public static void clearTopLevelClassCache()
{
topLevelClasses = null;
}
}

View File

@ -94,6 +94,29 @@ public class CountingHash<K extends Serializable> extends AbstractMap<K, Integer
/*******************************************************************************
** increment the value for the specified key
**
*******************************************************************************/
public Integer put(K key)
{
return (add(key));
}
/*******************************************************************************
** Set the value for the specified key by the supplied value
**
*******************************************************************************/
public Integer put(K key, Integer value)
{
this.map.put(key, value);
return (value);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -41,6 +41,7 @@ import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QValueException; import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
@ -51,6 +52,8 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
*******************************************************************************/ *******************************************************************************/
public class ValueUtils public class ValueUtils
{ {
private static final QLogger LOG = QLogger.getLogger(ValueUtils.class);
private static final DateTimeFormatter dateTimeFormatter_yyyyMMddWithDashes = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter dateTimeFormatter_yyyyMMddWithDashes = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter dateTimeFormatter_MdyyyyWithSlashes = DateTimeFormatter.ofPattern("M/d/yyyy"); private static final DateTimeFormatter dateTimeFormatter_MdyyyyWithSlashes = DateTimeFormatter.ofPattern("M/d/yyyy");
private static final DateTimeFormatter dateTimeFormatter_yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd"); private static final DateTimeFormatter dateTimeFormatter_yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd");
@ -931,4 +934,48 @@ public class ValueUtils
return (ZoneId.of(QContext.getQInstance().getDefaultTimeZoneId())); return (ZoneId.of(QContext.getQInstance().getDefaultTimeZoneId()));
} }
/***************************************************************************
**
***************************************************************************/
public static QFieldType inferQFieldTypeFromValue(Serializable value, QFieldType defaultIfCannotInfer)
{
if(value instanceof String)
{
return QFieldType.STRING;
}
else if(value instanceof Integer)
{
return QFieldType.INTEGER;
}
else if(value instanceof Long)
{
return QFieldType.LONG;
}
else if(value instanceof BigDecimal)
{
return QFieldType.DECIMAL;
}
else if(value instanceof Boolean)
{
return QFieldType.BOOLEAN;
}
else if(value instanceof Instant)
{
return QFieldType.DATE_TIME;
}
else if(value instanceof LocalDate)
{
return QFieldType.DATE;
}
else if(value instanceof LocalTime)
{
return QFieldType.TIME;
}
LOG.debug("Could not infer QFieldType from value [" + (value == null ? "null" : value.getClass().getSimpleName()) + "]");
return defaultIfCannotInfer;
}
} }

Some files were not shown because too many files have changed in this diff Show More